From d9218709f058be302793473fb5a92ef4d4595dc5 Mon Sep 17 00:00:00 2001 From: Tejas Shasane Date: Fri, 9 Oct 2026 12:39:32 +0530 Subject: [PATCH] session token fix for captcha --- requirements.txt | 10 ++++---- tests/config_reader.py | 5 +++- tests/conftest.py | 24 ++++++++++++++++++ tests/helpers.py | 31 ++++++++++++++++-------- tests/locators.py | 4 ++- tests/setup/sessions/test_session.py | 8 +++++- tests/setup/test_combined_setup.py | 9 ++++--- tests/setup/tokens/test_trading_token.py | 8 +++++- 8 files changed, 76 insertions(+), 23 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1024a26..a58577b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ # Core test runner -pytest==8.4.1 +pytest==9.1.1 pytest-html==4.2.0 # Browser automation -selenium==4.34.2 +selenium==4.50.0 # CAPTCHA OCR and image processing easyocr==1.7.2 -opencv-python==4.12.0.88 -Pillow==11.3.0 -numpy==2.2.6 +opencv-python==5.0.0.93 +Pillow==12.3.0 +numpy==2.5.3 torch==2.14.1 torchvision==0.29.1 diff --git a/tests/config_reader.py b/tests/config_reader.py index 84a5063..ba59aab 100644 --- a/tests/config_reader.py +++ b/tests/config_reader.py @@ -67,7 +67,10 @@ def get_setup_configs(mode, config=None): session = setup.get("run_session", True) token = setup.get("run_token", True) matches = { - "combined": session and token or (name.startswith("UPC_") and token), + # The full setup run owns every enabled product that has work to do. + # Products without tokens (for example RA) still belong in this + # shared browser flow; the combined test skips only their token step. + "combined": session or token, "session": session and not token, "token": token and not session, } diff --git a/tests/conftest.py b/tests/conftest.py index d4bdb3c..d79a94b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,30 @@ def pytest_configure(config): config.option.self_contained_html = True +def pytest_collection_modifyitems(config, items): + """Keep the full setup run limited to the shared combined flow.""" + invocation = " ".join( + str(arg).replace("\\", "/") for arg in config.invocation_params.args + ).lower() + full_setup_run = ( + "tests/setup" in invocation + and "tests/setup/sessions" not in invocation + and "tests/setup/tokens" not in invocation + and "test_session.py" not in invocation + and "test_trading_token.py" not in invocation + ) + if not full_setup_run: + return + + deselected = [ + item for item in items + if "/tests/setup/sessions/" in f"/{item.nodeid.replace('\\', '/')}" + or "/tests/setup/tokens/" in f"/{item.nodeid.replace('\\', '/')}" + ] + items[:] = [item for item in items if item not in deselected] + config.hook.pytest_deselected(items=deselected) + + # ----------------------------- # WebDriver Fixture # ----------------------------- diff --git a/tests/helpers.py b/tests/helpers.py index c7a0eda..1bb0046 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -482,23 +482,34 @@ def open_trading_maintenance_page(driver, menu_xpath): wait = WebDriverWait(driver, 20) - trading_maintenance = wait.until( - EC.presence_of_element_located((By.XPATH, Locator.tradingmaintenance)) - ) - driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", trading_maintenance) + def visible_elements(xpath): + return [element for element in driver.find_elements(By.XPATH, xpath) if element.is_displayed()] - submenu = driver.find_elements(By.XPATH, menu_xpath) - submenu_visible = any(item.is_displayed() for item in submenu) + submenu_visible = any( + item.is_enabled() for item in visible_elements(menu_xpath) + ) if not submenu_visible: + maintenance_items = visible_elements(Locator.tradingmaintenance) + if not maintenance_items: + wait.until(lambda current_driver: visible_elements(Locator.tradingmaintenance)) + maintenance_items = visible_elements(Locator.tradingmaintenance) + + maintenance = maintenance_items[0] + driver.execute_script( + "arguments[0].scrollIntoView({block: 'center'});", maintenance + ) try: - wait.until(EC.element_to_be_clickable((By.XPATH, Locator.tradingmaintenance))) - trading_maintenance.click() + wait.until(lambda current_driver: maintenance.is_enabled()) + maintenance.click() except Exception: - driver.execute_script("arguments[0].click();", trading_maintenance) + driver.execute_script("arguments[0].click();", maintenance) menu_item = wait.until( - EC.element_to_be_clickable((By.XPATH, menu_xpath)) + lambda current_driver: next( + (item for item in visible_elements(menu_xpath) if item.is_enabled()), + False, + ) ) try: menu_item.click() diff --git a/tests/locators.py b/tests/locators.py index 5f4dedd..320b07a 100644 --- a/tests/locators.py +++ b/tests/locators.py @@ -8,7 +8,9 @@ class Locator(object): tacexpiryclosebtn="//div[contains(@class,'modal') and contains(@style,'display: block')]//button[contains(@class,'close')]" usermenutoggle="//li[contains(@class,'dropdown') and contains(@class,'navbar-user')]//a[contains(@class,'dropdown-toggle') and @data-toggle='dropdown']" logoutlink="//li[contains(@class,'dropdown') and contains(@class,'navbar-user')]//ul[contains(@class,'dropdown-menu')]//a[@ng-click='logout()' and normalize-space()='Log Out']" - tradingmaintenance="//*[contains(normalize-space(), 'TRADING MAINTENANCE')]" + # Click the Angular menu anchor, not the nested label span. The anchor + # owns openSubMenu(item), which is required to render its child links. + tradingmaintenance="//a[.//span[normalize-space()='TRADING MAINTENANCE']]" tradingsession="//a[normalize-space()='Trading Session Master']" tokengeneration="//a[@href='#/tradingMaintenance/generateToken' or normalize-space()='Token Generation']" tokengenerator = "//a[@href='#/tradingMaintenance/tokenGenerator']" diff --git a/tests/setup/sessions/test_session.py b/tests/setup/sessions/test_session.py index dce576b..343039d 100644 --- a/tests/setup/sessions/test_session.py +++ b/tests/setup/sessions/test_session.py @@ -44,7 +44,13 @@ def create_session(driver, step, config): @pytest.mark.session @pytest.mark.parametrize("config", get_setup_configs("session"), ids=lambda c: c["product_name"]) -def test_session(driver, test_context, config): +def test_session(driver, test_context, config, request): + + # The full setup command runs session-only products (such as RA) through + # test_combined_setup.py so they share the same browser/login context. + invocation = " ".join(str(arg).replace("\\", "/") for arg in request.config.invocation_params.args) + if "tests/setup" in invocation and "tests/setup/sessions" not in invocation and "test_session.py" not in invocation: + pytest.skip("Handled by the shared full setup flow") wait = WebDriverWait(driver, 20) diff --git a/tests/setup/test_combined_setup.py b/tests/setup/test_combined_setup.py index 24c5629..33b91ac 100644 --- a/tests/setup/test_combined_setup.py +++ b/tests/setup/test_combined_setup.py @@ -31,8 +31,9 @@ def test_session_and_token_setup(driver, test_context, config): if config.get("setup", {}).get("run_session", True): create_session(driver, step, config) - for product_code in get_product_codes(token): - product_token = dict(token) - product_token["product_code"] = product_code - run_token_setup(driver, step, product_token) + if config.get("setup", {}).get("run_token", True): + for product_code in get_product_codes(token): + product_token = dict(token) + product_token["product_code"] = product_code + run_token_setup(driver, step, product_token) diff --git a/tests/setup/tokens/test_trading_token.py b/tests/setup/tokens/test_trading_token.py index 06346ca..04c049c 100644 --- a/tests/setup/tokens/test_trading_token.py +++ b/tests/setup/tokens/test_trading_token.py @@ -339,7 +339,13 @@ def create_and_activate_token(driver, step, token): @pytest.mark.trading_token @pytest.mark.parametrize("config", get_setup_configs("token"), ids=lambda c: c["product_name"]) -def test_trading_token_creation(driver, test_context, config): +def test_trading_token_creation(driver, test_context, config, request): + + # The full setup command runs token-enabled products through the combined + # flow so session and token creation use one browser/login context. + invocation = " ".join(str(arg).replace("\\", "/") for arg in request.config.invocation_params.args) + if "tests/setup" in invocation and "tests/setup/tokens" not in invocation and "test_trading_token.py" not in invocation: + pytest.skip("Handled by the shared full setup flow") config, step, token = get_token_context(driver, test_context, config) open_app_and_login(driver, step, config)