Try captcha automation #9
+5
-5
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
# -----------------------------
|
||||
|
||||
+21
-10
@@ -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()
|
||||
|
||||
+3
-1
@@ -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']"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user