From a32a226492a533d9b62f53f8be4dbd9c54c7edea Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 8 Nov 2024 07:58:47 -0800 Subject: [PATCH] tests: functional: Fix visit() returning prematurely before page load - It appears from the functional tests run in Gitlab CI pipelines that visit() is returning before the page has loaded fully. In the screenshots for help app test failures, we see gitweb web page. - To fix this, wait for a proper page load in visit(). - Cleanup syntax, rename the non-existent search class, and mechanism for matching expected URLs. - Also refactor waiting for uninstall page in uninstall() method. Using the wait_for_page_update() method as context processor is more accurate. Tests: - Run all functional tests and ensure that there are no errors in visit() method. Signed-off-by: Sunil Mohan Adapa Reviewed-by: Veiko Aasa --- plinth/tests/functional/__init__.py | 47 +++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/plinth/tests/functional/__init__.py b/plinth/tests/functional/__init__.py index af6a1ca8a..7b109b9d8 100644 --- a/plinth/tests/functional/__init__.py +++ b/plinth/tests/functional/__init__.py @@ -10,6 +10,7 @@ import pathlib import subprocess import tempfile import time +import urllib.parse import warnings from contextlib import contextmanager @@ -67,7 +68,8 @@ _sys_modules = [ ###################### def visit(browser, path): """Visit a path assuming the base URL as configured.""" - browser.visit(config['DEFAULT']['url'] + path) + with wait_for_page_update(browser): + browser.visit(config['DEFAULT']['url'] + path) def eventually(function, args=[], timeout=30): @@ -88,7 +90,7 @@ def eventually(function, args=[], timeout=30): return False -class _PageLoaded(): +class _PageLoaded: """ Wait until a page (re)loaded. @@ -103,9 +105,9 @@ class _PageLoaded(): self.loaded_new_page = False def __call__(self, driver): - is_stale = False + """Return if expected page has fully loaded.""" try: - self.element.has_class('whatever_class') + self.element.has_class('x-non-existing-class') # XXX: There is still another unhandled case where the webserver # restarts after submission of a form and the browser does not switch # to error page. It continues to wait for a response from the server @@ -143,17 +145,35 @@ class _PageLoaded(): return False + # If page has not loaded fully yet, wait until it does. is_fully_loaded = driver.execute_script( 'return document.readyState;') == 'complete' if not is_fully_loaded: - is_stale = False - elif self.expected_url is None: - is_stale = True - else: - if driver.url.endswith(self.expected_url): - is_stale = True + return False - return is_stale + # If a page has fully loaded check if it is the expected URL. + return self.has_expected_url_reached(driver) + + # Should never reach here. + return False + + def has_expected_url_reached(self, driver): + """Return if the current browser URL is the expected URL.""" + if not self.expected_url: + return True # We are not expecting a specific URL, always any URL + + browser_url = urllib.parse.urlparse(driver.url) + expected_url = urllib.parse.urlparse(self.expected_url) + + if expected_url.scheme and browser_url.scheme != expected_url.scheme: + return False + + if expected_url.netloc and browser_url.netloc != expected_url.netloc: + return False + + browser_path = browser_url.path.rstrip('/') + expected_path = expected_url.path.rstrip('/') + return browser_path == expected_path @contextmanager @@ -425,8 +445,9 @@ def uninstall(browser, app_name): pytest.skip('App cannot be uninstalled') uninstall_page_url = uninstall_item[0]['href'] - uninstall_item[0].click() - wait_for_page_update(browser, expected_url=uninstall_page_url) + with wait_for_page_update(browser, expected_url=uninstall_page_url): + uninstall_item[0].click() + submit(browser, form_class='form-uninstall') while True: