Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions modules/core/navigation/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ function trackLocationSearchChanges() {
}

window.addEventListener('popstate', function(event) {
// Signal navigation start for Selenium detection
document.body.setAttribute('data-navigation-state', 'loading');
window.dispatchEvent(new CustomEvent('navigation-started', { detail: { url: window.location.href } }));

Hm_Ajax.abort_all_requests();

if (event.state) {
Expand All @@ -27,12 +31,18 @@ window.addEventListener('popstate', function(event) {
unMountSubscribers[previousLocationSearch]?.();

trackLocationSearchChanges();

// Signal navigation completion for Selenium detection
document.body.setAttribute('data-navigation-state', 'complete');
window.dispatchEvent(new CustomEvent('navigation-completed', { detail: { url: window.location.href } }));
});

window.addEventListener('load', function() {
if (!hm_is_logged()) {
return;
}
// Initialize navigation state for Selenium detection
document.body.setAttribute('data-navigation-state', 'complete');

const unMountCallback = renderPage(window.location.href);
history.replaceState({ main: $('#cypht-main').prop('outerHTML'), scripts: extractCustomScripts($(document)) }, "");
Expand Down Expand Up @@ -61,6 +71,8 @@ $(document).on('click', '.cypht-layout a', function(event) {
const targetParams = new URLSearchParams(href.split('?')[1]);
if (currentPage !== targetParams.toString()) {
Hm_Ajax.abort_all_requests();
// Signal navigation start immediately when clicking
document.body.setAttribute('data-navigation-state', 'loading');
navigate(autoAppendParamsForNavigation(href));
}
}
Expand Down Expand Up @@ -94,6 +106,10 @@ function autoAppendParamsForNavigation(href)
}

async function navigate(url, loaderMessage) {
// Signal navigation start for Selenium detection
document.body.setAttribute('data-navigation-state', 'loading');
window.dispatchEvent(new CustomEvent('navigation-started', { detail: { url } }));

showRoutingToast(loaderMessage);
Hm_Ajax.abort_all_requests();

Expand Down Expand Up @@ -168,7 +184,16 @@ async function navigate(url, loaderMessage) {
unMountSubscribers[previousLocationSearch]?.();

trackLocationSearchChanges();

// Signal navigation completion for Selenium detection
document.body.setAttribute('data-navigation-state', 'complete');
window.dispatchEvent(new CustomEvent('navigation-completed', { detail: { url } }));

} catch (error) {
// Signal navigation error for Selenium detection
document.body.setAttribute('data-navigation-state', 'error');
window.dispatchEvent(new CustomEvent('navigation-failed', { detail: { url, error: error.message } }));

Hm_Notices.show(error.message, 'danger');
console.log(error);
} finally {
Expand Down
110 changes: 68 additions & 42 deletions tests/selenium/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,50 +170,76 @@ def wait_on_sys_message(self, timeout=30):

def wait_for_navigation_to_complete(self, timeout=30):
print(" - waiting for the navigation to complete...")
# Wait for the main content to be updated and any loading indicators to disappear
try:
# Wait for any loading indicators to disappear
WebDriverWait(self.driver, 5).until_not(
lambda driver: len(driver.find_elements(By.ID, "loading_indicator")) > 0
)
except:
# Loading icon might not be present, continue
pass
import time

# Wait for the main content area to be stable
try:
WebDriverWait(self.driver, timeout).until(
lambda driver: driver.execute_script("""
return new Promise((resolve) => {
let lastContent = '';
let stableCount = 0;
const checkStability = () => {
const mainContent = document.querySelector('main')?.innerHTML || '';
if (mainContent === lastContent) {
stableCount++;
if (stableCount >= 3) {
resolve(true);
return;
}
} else {
stableCount = 0;
lastContent = mainContent;
}
setTimeout(checkStability, 100);
};
checkStability();
});
""")
)
except:
# Fallback: just wait for the main element to be present
print(" - fallback: waiting for main element")
WebDriverWait(self.driver, timeout).until(
exp_cond.presence_of_element_located((By.TAG_NAME, "main"))
)
# Additional wait for any dynamic content
import time
time.sleep(1)
# First, check if we have navigation state attributes (new method)
try:
# Wait for navigation to start (state changes to 'loading')
WebDriverWait(self.driver, 5).until(
lambda driver: driver.execute_script(
'return document.body.getAttribute("data-navigation-state") === "loading"'
)
)
print(" - navigation start detected")

# Then wait for navigation to complete (state changes to 'complete')
WebDriverWait(self.driver, timeout).until(
lambda driver: driver.execute_script(
'return document.body.getAttribute("data-navigation-state") === "complete"'
)
)
print(" - navigation completion detected via state attribute")

# Small delay to ensure DOM is settled
time.sleep(0.5)
return

except Exception as state_error:
print(f" - navigation state monitoring failed: {state_error}, trying fallback methods")

# Fallback 1: Try to detect fetch requests
try:
get_current_navigations_request_entries_length = lambda: self.driver.execute_script(
'return window.performance.getEntriesByType("resource").filter((r) => r.initiatorType === "fetch").length'
)
navigation_length = get_current_navigations_request_entries_length()

WebDriverWait(self.driver, min(timeout, 10)).until(
lambda driver: get_current_navigations_request_entries_length() > navigation_length
)
print(" - navigation detected via fetch requests")

time.sleep(0.5)
return

except Exception as fetch_error:
print(f" - fetch monitoring failed: {fetch_error}, trying final fallback")

# Fallback 2: Check for loading indicators and main element
try:
WebDriverWait(self.driver, 5).until_not(
lambda driver: len(driver.find_elements(By.ID, "loading_indicator")) > 0
)
print(" - loading indicator disappeared")
except:
pass

try:
WebDriverWait(self.driver, min(timeout, 15)).until(
exp_cond.presence_of_element_located((By.TAG_NAME, "main"))
)
print(" - main element present")

time.sleep(1)

except Exception as main_error:
print(f" - all navigation waiting methods failed: {state_error}, {fetch_error}, {main_error}")
time.sleep(2)

except Exception as e:
print(f" - unexpected error in navigation waiting: {e}")
time.sleep(2)

def wait_for_settings_to_expand(self):
print(" - waiting for the settings section to expand...")
Expand Down
10 changes: 6 additions & 4 deletions tests/selenium/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ def good_login(self):

def good_logout(self):
self.logout()
self.wait()
# self.wait()
self.safari_workaround()
self.wait_on_class('sys_messages')
sys_messages = self.by_class('sys_messages')
assert sys_messages is not None
self.wait(By.CLASS_NAME, 'login_form', 60)
# debugging line
print('debugging line')
print(self.by_class('login_form'))
assert self.by_class('login_form') != None

if __name__ == '__main__':

Expand Down
2 changes: 1 addition & 1 deletion tests/selenium/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,4 @@ def profiles(self):

if __name__ == '__main__':
print("PAGES TEST")
test_runner(PageTests)
test_runner(PageTests)
35 changes: 33 additions & 2 deletions tests/selenium/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,39 @@ def load_servers_page(self):
def server_stmp_and_imap_add(self):
self.toggle_server_section('server_config')
self.wait_on_class('imap-jmap-smtp-btn')
self.by_id('add_new_server_button').click()
# self.wait_on_class('srv_setup_stepper_profile_name')

# Wait for the add button to be clickable (combines presence and clickability)
add_button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "add_new_server_button"))
)

# Scroll the button into view before clicking
self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", add_button)
sleep(0.5)

try:
add_button.click()
print("Normal click succeeded")
except Exception as e:
print(f"Normal click failed: {e}. Trying JavaScript click...")
self.driver.execute_script("arguments[0].click();", add_button)

# Wait for the form to appear after clicking
try:
WebDriverWait(self.driver, 10).until(
EC.any_of(
EC.presence_of_element_located((By.ID, 'srv_setup_stepper_profile_name')),
EC.presence_of_element_located((By.NAME, 'srv_setup_stepper_profile_name'))
)
)
print("Server setup form appeared")
except Exception as e:
print(f"Server setup form did not appear: {e}")
# Debug: print current page state
print("Current URL:", self.driver.current_url)
print("Page title:", self.driver.title)
raise e

name = self.by_id('srv_setup_stepper_profile_name')
name.send_keys('Test')
email = self.by_name('srv_setup_stepper_email')
Expand Down
Loading