14 changed files with 717 additions and 100 deletions
+75 -1
View File
@@ -11,6 +11,80 @@ cd "C:\Selenium Test"
Edit `config.json` before running.
Common URL, login credentials, login waits, and step delay stay at the top level.
`setup.run_session` and `setup.run_token` are defaults for every enabled product.
`setup.session` provides common session timing defaults.
`products` contains separate `IDAS`, `RTM`, `UPC`, `RA`, and `REC` sections.
Each product has its own `me_instance`, optional `session` timing overrides, and
`token` settings (codes, delivery offset, expiry, and time sessions).
RTM currently runs token creation only (`run_session: false`, `run_token: true`).
If enabled again, session creation uses ME instance
`52` (`52:RTM`). It uses today's date plus a 7-minute start offset and today's
`20:30` end time, matching the current IDAS session end-time default. Change
`products.RTM.session.start_offset_minutes` and `products.RTM.session.end_time`
to configure RTM independently. The shared login, navigation, and Save steps apply
to every product. RTM token automation opens Token Generator,
selects `NATIONAL`, product code `RTM`, and generation mode `RTM`, then clicks
Add New Token. Product Type is populated automatically. It fills delivery dates
with today's `00:00` and `23:59`, session start with current time plus 5 minutes,
and session end with `20:00`, then clicks Save Token. All four timings are
configured under `products.RTM.token` as shown below. `delivery_offset_days: 0`
means today. RTM activation is not part of this stage.
Run RTM token creation through Save Token:
```powershell
pytest tests\setup -k RTM -s
```
Existing IDAS settings are preserved. The remaining product templates are disabled until
you fill in their actual ME instance/token codes and set `enabled` to `true`.
Additional products can be added using the same structure.
To override the defaults for one product, add flags directly inside that product:
```json
"RTM": {
"enabled": true,
"run_session": false,
"run_token": true,
"me_instance": "52",
"session": {
"start_offset_minutes": 7,
"end_time": "20:30"
},
"token": {
"market_code": "NATIONAL",
"product_type": "RTM",
"product_codes": ["RTM"],
"generation_mode": "RTM",
"delivery_offset_days": 0,
"delivery_from_time": "00:00",
"delivery_to_time": "23:59",
"session_start_offset_minutes": 5,
"session_end_time": "20:00"
}
}
```
Remove a product's `run_session` or `run_token` key to inherit that common flag
again. Explicit `false` overrides a common `true`; explicit `true` overrides a
common `false`. Both false skips that product; `enabled: false` always skips it.
Session timing overrides inherit any fields omitted from `setup.session`.
Token timing settings belong to each product independently.
Use `pytest tests\setup -s` to run all enabled products. Each product runs in its
own browser: both flags true runs session then the product's token flow in one login; otherwise
only its enabled action runs. Test/report names include the product name.
Screenshot and log folders use the collected product and action, such as
`IDAS_SESSION_TOKEN_001`, `RTM_TOKEN_001`, or `RTM_SESSION_001`.
The shared HTML report for `tests\setup` uses `PXIL_SETUP_###.html`.
Existing report folders retain their original names.
The session and token subfolder commands below select only products configured
for session-only and token-only respectively; use the full setup command for a
mixture of modes.
Session and token together in one login:
```json
@@ -69,7 +143,7 @@ Run only token setup:
pytest tests\setup\tokens -s --html=reports/html/token_run.html --self-contained-html
```
Use this when `run_token` is `true`. It will create one token or multiple tokens based on `trading_token_idas.product_codes`.
Use this when `run_token` is `true`. It will create one token or multiple tokens based on `products.<product>.token.product_codes`.
Run only session setup:
+154 -23
View File
@@ -1,45 +1,176 @@
{
"url": "https://utilsuat.pxil.in/Pxil/",
"url": "https://utilseastern.pxil.in/Pxil/",
"stagingurl": "https://utilseastern.pxil.in/Pxil/",
"uaturl": "https://utilsuat.pxil.in/Pxil/",
"login": {
"username": "Tejas",
"password": "Pxil@123"
},
"product": {
"me_instance": "55"
},
"login_captcha_wait_seconds": 10,
"login_post_submit_wait_seconds": 10,
"step_delay_seconds": 1,
"setup": {
"run_session": false,
"run_session": true,
"run_token": true,
"session": {
"start_offset_minutes": 7,
"end_time": "20:30"
}
},
"trading_token_idas": {
"market_code": "NATIONAL",
"product_type": "IDAS",
"product_code": "IDAS4",
"product_codes": "IDAS,IDASTEST01,IDASTEST02,IDASTEST03,IDASTEST04,IDASTEST05,IDASTEST06",
"available_codes":",IDASTEST06,IDASTEST07,IDASTEST08,IDASTEST09,IDASTEST10,IDASTEST11,IDASTEST12,IDASTEST13,IDASTEST14,IDASTEST15,IDASTEST16,IDASTEST17,IDASTEST18,IDASTEST19,IDASTEST20,IDASTEST21,IDASTEST22,IDASTEST23,IDASTEST24,IDASTEST25,IDASTEST26,IDASTEST27,IDASTEST28,IDASTEST29,IDASTEST30",
"delivery_offset_days": 1,
"expiry_time": "20:30",
"time_sessions": {
"ORDER_NOR": {
"start_offset_minutes": 5,
"end_time": "19:30"
"products": {
"IDAS": {
"enabled": false,
"me_instance": "55",
"session": {},
"token": {
"market_code": "NATIONAL",
"product_type": "IDAS",
"product_code": "IDAS4",
"product_codes": "IDAS,IDASTEST01,IDASTEST02,IDASTEST03,IDASTEST04,IDASTEST05,IDASTEST06",
"available_codes": ",IDASTEST06,IDASTEST07,IDASTEST08,IDASTEST09,IDASTEST10,IDASTEST11,IDASTEST12,IDASTEST13,IDASTEST14,IDASTEST15,IDASTEST16,IDASTEST17,IDASTEST18,IDASTEST19,IDASTEST20,IDASTEST21,IDASTEST22,IDASTEST23,IDASTEST24,IDASTEST25,IDASTEST26,IDASTEST27,IDASTEST28,IDASTEST29,IDASTEST30",
"delivery_offset_days": 1,
"expiry_time": "20:30",
"time_sessions": {
"ORDER_NOR": {
"start_offset_minutes": 7,
"end_time": "19:30"
},
"ORDER_EXCH": {
"start_time": "19:30",
"end_time": "19:45"
},
"TRADE": {
"start_time": "19:45",
"end_time": "19:50"
}
}
}
},
"RTM": {
"enabled": false,
"run_session": false,
"run_token": true,
"me_instance": "52",
"session": {
"start_offset_minutes": 7,
"end_time": "20:30"
},
"ORDER_EXCH": {
"start_time": "19:30",
"end_time": "19:45"
"token": {
"market_code": "NATIONAL",
"product_type": "RTM",
"product_codes": [
"RTM"
],
"generation_mode": "RTM",
"delivery_offset_days": 0,
"delivery_from_time": "00:00",
"delivery_to_time": "23:59",
"session_start_offset_minutes": 5,
"session_end_time": "20:00"
}
},
"UPC": {
"enabled": false,
"run_session": true,
"run_token": true,
"session_variants": {
"DAILY": {
"enabled": true,
"run_session": false,
"run_token": true,
"me_instance": "58",
"token": {
"generation_mode": "AnyDayUPC",
"create_product_codes": [
"DailyC_E"
],
"available_product_codes": [
"DailyC_D",
"DailyGR_D",
"DailyC_RTC",
"DailyC_E",
"DailyC_M"
]
}
},
"WEEKLY": {
"enabled": true,
"run_session": false,
"run_token": true,
"me_instance": "59",
"token": {
"generation_mode": "WeeklyUPC",
"create_product_codes": [
"WeeklyC_E"
],
"available_product_codes": [
"WeeklyC_D",
"WeeklyC_N",
"WeeklyGR_D",
"WeeklyC_RTC"
]
}
},
"MONTHLY": {
"enabled": true,
"run_session": true,
"run_token": true,
"me_instance": "60",
"token": {
"generation_mode": "MonthlyUPC",
"create_product_codes": [
"MonthlyC_N"
],
"available_product_codes": [
"MonthlyC_D",
"MonthlyGR_D",
"MonthlyC_RTC"
]
}
}
},
"TRADE": {
"start_time": "19:45",
"end_time": "19:50"
"session": {},
"token": {
"market_code": "NATIONAL",
"product_type": "UPC",
"product_codes": [],
"delivery_offset_days": 1,
"expiry_time": "20:30",
"upc_times": {
"order_normal_start_time": "13:00",
"order_normal_end_time": "17:00",
"trade_session_start_time": "17:00",
"trade_session_end_time": "22:00"
},
"time_sessions": {}
}
},
"RA": {
"enabled": true,
"run_session": true,
"run_token": false,
"me_instance": "64",
"session": {},
"token": {
"market_code": "NATIONAL",
"product_type": "RA",
"product_codes": [],
"delivery_offset_days": 1,
"expiry_time": "20:30",
"time_sessions": {}
}
},
"REC": {
"enabled": false,
"me_instance": "",
"session": {},
"token": {
"market_code": "NATIONAL",
"product_type": "REC",
"product_codes": [],
"delivery_offset_days": 1,
"expiry_time": "20:30",
"time_sessions": {}
}
}
}
+22
View File
@@ -28,3 +28,25 @@ In `runtime.json`:
- `IDAS`, `RTM`, and future top-level product sections: product-wise defaults, available codes, supported flows, bidding config, matching config, and future product-specific controls.
Regression config now lives entirely inside `runtime.json`.
## UPC session setup
Session creation uses the root `config.json`, independently of regression settings.
Set `products.UPC.enabled` to `true` to include UPC. Under
`products.UPC.session_variants`, enable all three variants or only the ones required:
| Variant | ME instance ID | UI name |
| --- | --- | --- |
| DAILY | 58 | UPC ANYDAY |
| WEEKLY | 59 | UPC WEEKLY |
| MONTHLY | 60 | UPC MONTHLY |
Each variant has an `enabled` flag. UPC remains disabled by default; all three
variants are preselected for when it is enabled. Set `UPC.run_session` and
`UPC.run_token` to `true` when both session and token creation are required. Each selected variant uses the
existing session creation flow and inherits `setup.session.start_offset_minutes`
and `setup.session.end_time`. Optional `UPC.session` settings override those
defaults, and a variant's `session` settings can override them individually.
Run session-only setup with `python -m pytest tests/setup/sessions/test_session.py`.
Selected UPC cases appear as `UPC_DAILY`, `UPC_WEEKLY`, and `UPC_MONTHLY`.
+72
View File
@@ -1,4 +1,5 @@
import json
from copy import deepcopy
from pathlib import Path
@@ -11,6 +12,77 @@ def load_config():
return json.load(f)
def get_product_config(product_name, config=None):
"""Resolve a product's setup flags and session defaults without mutating input."""
config = deepcopy(load_config() if config is None else config)
if "products" not in config:
if product_name != "IDAS":
raise ValueError(f"Unknown setup product: {product_name}")
config["token"] = config.get("trading_token_idas", {})
else:
product = config["products"][product_name]
setup = config.setdefault("setup", {})
for flag in ("run_session", "run_token"):
setup[flag] = product.get(flag, setup.get(flag, True))
if not isinstance(setup[flag], bool):
raise ValueError(f"products.{product_name}.{flag} must be true or false")
setup["session"] = {**setup.get("session", {}), **product.get("session", {})}
config["product"] = {"me_instance": product.get("me_instance")}
config["token"] = deepcopy(product.get("token", {}))
config["product_name"] = product_name
return config
def get_setup_configs(mode, config=None):
"""Partition products so a full setup run never creates a product twice."""
if mode not in ("combined", "session", "token"):
raise ValueError(f"Unknown setup mode: {mode}")
config = load_config() if config is None else config
products = config.get("products", {"IDAS": {}})
selected = []
for name, product in products.items():
if not product.get("enabled", True):
continue
variants = product.get("session_variants")
if variants is not None:
resolved = get_product_config(name, config)
if not resolved["setup"].get("run_session", True):
continue
variant_config = deepcopy(config)
variant_config["products"] = {}
for variant_name, variant in variants.items():
settings = deepcopy(product)
settings.pop("session_variants")
settings.update(variant)
settings["session"] = {**product.get("session", {}), **variant.get("session", {})}
settings["token"] = {**product.get("token", {}), **variant.get("token", {})}
variant_config["products"][f"{name}_{variant_name}"] = settings
selected.extend(get_setup_configs(mode, variant_config))
continue
resolved = get_product_config(name, config)
setup = resolved.get("setup", {})
session = setup.get("run_session", True)
token = setup.get("run_token", True)
matches = {
"combined": session and token or (name.startswith("UPC_") and token),
"session": session and not token,
"token": token and not session,
}
if not matches[mode]:
continue
if session and not resolved.get("product", {}).get("me_instance"):
raise ValueError(f"products.{name}.me_instance is required for session creation")
if token:
settings = resolved["token"]
if not settings.get("market_code") or not (
parse_product_codes(settings.get("create_product_codes", settings.get("product_codes")))
or parse_product_codes(settings.get("product_code"))
):
raise ValueError(f"products.{name}.token requires market_code and product_code(s)")
selected.append(resolved)
return selected
def get_step_delay_seconds(default=5):
config = load_config()
+33 -10
View File
@@ -5,15 +5,15 @@ import json
from tests.browser import create_chrome_driver
def get_run_id(config):
def get_run_id(config, prefix=None):
run_id = getattr(config, "_pxil_run_id", None)
if run_id:
return run_id
run_prefix = prefix or _get_run_prefix(config)
run_ids = getattr(config, "_pxil_run_ids", {})
if run_prefix in run_ids:
return run_ids[run_prefix]
base_folder = os.path.join("reports", "screenshots")
os.makedirs(base_folder, exist_ok=True)
run_prefix = _get_run_prefix(config)
existing = [
name for name in os.listdir(base_folder)
@@ -28,10 +28,11 @@ def get_run_id(config):
for name in existing
if name.split("_")[-1].isdigit()
]
next_number = max(numbers) + 1
next_number = max(numbers, default=0) + 1
run_id = f"{run_prefix}_{next_number:03d}"
config._pxil_run_id = run_id
run_ids[run_prefix] = run_id
config._pxil_run_ids = run_ids
return run_id
@@ -49,7 +50,7 @@ def pytest_configure(config):
# -----------------------------
# WebDriver Fixture
# -----------------------------
@pytest.fixture
@pytest.fixture(scope="module")
def driver():
driver = create_chrome_driver()
@@ -66,7 +67,7 @@ def driver():
@pytest.fixture
def test_context(request):
test_id = get_run_id(request.config)
test_id = get_run_id(request.config, _setup_item_prefix(request.node))
test_name = request.node.name
folder = os.path.abspath(
@@ -201,9 +202,28 @@ def _build_steps_html(steps):
)
def _setup_item_prefix(item):
"""Use the collected product and flow, including when -k selects a subset."""
nodeid = item.nodeid.replace("\\", "/")
if "tests/setup/" not in nodeid:
return None
callspec = getattr(item, "callspec", None)
product_config = callspec.params.get("config") if callspec else None
if not isinstance(product_config, dict):
return None
product = product_config["product_name"].upper()
if "test_combined_setup.py" in nodeid:
return f"{product}_SESSION_TOKEN"
if "/sessions/" in nodeid:
return f"{product}_SESSION"
if "/tokens/" in nodeid:
return f"{product}_TOKEN"
return f"{product}_SETUP"
def _get_run_prefix(config):
invocation_args = " ".join(str(arg) for arg in config.invocation_params.args).lower()
invocation_args = " ".join(str(arg) for arg in config.invocation_params.args).replace("\\", "/").lower()
if "tests/setup/sessions" in invocation_args or "test_session.py" in invocation_args:
return "PXIL_SESSION"
@@ -211,6 +231,9 @@ def _get_run_prefix(config):
if "tests/setup/tokens" in invocation_args or "test_trading_token.py" in invocation_args:
return "PXIL_TOKEN"
if "tests/setup" in invocation_args or "test_combined_setup.py" in invocation_args:
return "PXIL_SETUP"
runtime_path = os.path.abspath(os.path.join("config", "runtime.json"))
if os.path.exists(runtime_path):
try:
+9 -3
View File
@@ -367,7 +367,7 @@ def type_into_field(driver, locator, value):
field.send_keys(value)
def select_bootstrap_dropdown_by_id(driver, element_id, value):
def select_bootstrap_dropdown_by_id(driver, element_id, value, exact=False):
wait = WebDriverWait(driver, 20)
@@ -411,12 +411,18 @@ def select_bootstrap_dropdown_by_id(driver, element_id, value):
search_box.clear()
search_box.send_keys(value)
option_xpath = (
f"//div[@id='{element_id}']//div[contains(@class,'ui-select-choices-row')]"
f"[normalize-space()='{value}']"
if exact else
"//div[contains(@class,'ui-select-choices-row')]"
f"//*[normalize-space()='{value}' or starts-with(normalize-space(),'{value}')]"
)
option = wait.until(
EC.element_to_be_clickable(
(
By.XPATH,
"//div[contains(@class,'ui-select-choices-row')]"
f"//*[normalize-space()='{value}' or starts-with(normalize-space(),'{value}')]"
option_xpath
)
)
)
+18
View File
@@ -11,9 +11,18 @@ class Locator(object):
tradingmaintenance="//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']"
generatormarketcode = "marketCode"
generatorproductcode = "productCode"
generatorgenerationmode = "tokenMstr.genMode"
rtmdeliveryfromdate = "//div[@ng-show='showgeneration4']//input[@placeholder='Delivery From Date']"
rtmdeliverytodate = "//div[@ng-show='showgeneration4']//input[@placeholder='Delivery To Date']"
rtmsessionstarttime = "//div[@ng-show='showgeneration4']//input[@placeholder='Session Start Time']"
rtmsessionendtime = "//div[@ng-show='showgeneration4']//input[@placeholder='Session End Time']"
tokenandtimesessionview="//a[@href='#/tradingMaintenance/tokenTimeSessionMap' or normalize-space()='Token and Time Session View']"
addsessionbtn="//button[contains(text(),'Add')]"
addnewtokenbtn="//button[@data-ng-click='addToken()' and normalize-space()='Add New Token']"
tokengeneratorresetbtn = "//button[@data-ng-click='refresh()' and normalize-space()='Reset']"
setmeinstance="//span[@aria-label='meInstanceId activate']"
#inputmeid="meInstanceId activate"
#"//li[@id='ui-select-choices-178']"
@@ -27,6 +36,15 @@ class Locator(object):
tokendeliverystartdate = "//input[@placeholder='DELIVERY_START_DATE']"
tokendeliveryenddate = "//input[@placeholder='DELIVERY_END_DATE']"
tokenexpirydate = "//input[@placeholder='Token Expiry Date']"
tokentradedate = "//input[@placeholder='Trade Date']"
upcordernormalstart = "//input[@name='ses3StartTime']"
upcordernormalend = "//input[@name='ses3EndTime']"
upctradesessionstart = "//input[@name='sesStartTime']"
upctradesessionend = "//input[@name='sesEndTime']"
upcorderstart = "//input[@placeholder='Order Normal Start Time']"
upcorderend = "//input[@placeholder='Order Normal End Time']"
upctradestart = "//input[@placeholder='Trade Start Time' or @placeholder='Trade Session Start Time']"
upctradeend = "//input[@placeholder='Trade End Time' or @placeholder='Trade Session End Time']"
savetokenbtn = "//button[@data-ng-click='saveToken()' and normalize-space()='save Token']"
tokentimesessionbtn = "//button[@data-ng-click='redirectToTimeSession()' and normalize-space()='Time Session']"
viewproducttype = "pmProductType"
+6 -14
View File
@@ -5,17 +5,11 @@ from selenium.webdriver.support.ui import WebDriverWait
import pytest
from tests.step_actions import StepActions
from tests.helpers import login, select_ui_dropdown
from tests.config_reader import is_combined_setup_enabled, is_setup_session_enabled, load_config
from tests.helpers import login, select_ui_dropdown, open_trading_maintenance_page
from tests.config_reader import get_setup_configs
from tests.locators import Locator
pytestmark = pytest.mark.skipif(
is_combined_setup_enabled() or not is_setup_session_enabled(),
reason="Standalone session setup is disabled or runs inside combined setup",
)
def get_session_window(config):
session_config = config.get("setup", {}).get("session", {})
@@ -30,9 +24,8 @@ def get_session_window(config):
def create_session(driver, step, config):
step.click((By.XPATH, Locator.tradingmaintenance), "trading_maintenance")
step.click((By.XPATH, Locator.tradingsession), "trading_session")
open_trading_maintenance_page(driver, Locator.tradingsession)
step.capture("trading_session")
step.click((By.XPATH, Locator.addsessionbtn), "add_session")
@@ -50,9 +43,8 @@ def create_session(driver, step, config):
@pytest.mark.session
def test_session(driver, test_context):
config = load_config()
@pytest.mark.parametrize("config", get_setup_configs("session"), ids=lambda c: c["product_name"])
def test_session(driver, test_context, config):
wait = WebDriverWait(driver, 20)
+16 -24
View File
@@ -1,46 +1,38 @@
import pytest
from selenium.webdriver.support.ui import WebDriverWait
from tests.config_reader import is_combined_setup_enabled, load_config
from tests.config_reader import get_setup_configs
from tests.helpers import login, logout, save_session_backup
from tests.setup.sessions.test_session import create_session
from tests.setup.tokens.test_trading_token import create_and_activate_token, get_product_codes
from tests.setup.tokens.test_trading_token import run_token_setup, get_product_codes
from tests.step_actions import StepActions
pytestmark = pytest.mark.skipif(
not is_combined_setup_enabled(),
reason="Combined setup requires setup.run_session and setup.run_token to both be true",
)
@pytest.mark.session
@pytest.mark.trading_token
def test_session_and_token_setup(driver, test_context):
@pytest.mark.parametrize("config", get_setup_configs("combined"), ids=lambda c: c["product_name"])
def test_session_and_token_setup(driver, test_context, config):
config = load_config()
token = config.get("trading_token_idas")
assert token, "Add trading_token_idas in config.json before running token setup."
token = config["token"]
wait = WebDriverWait(driver, 20)
step = StepActions(driver, wait, test_context)
driver.get(config["url"])
step.capture("open_application")
if not getattr(driver, "_pxil_logged_in", False):
driver.get(config["url"])
step.capture("open_application")
login(
driver,
config["login"]["username"],
config["login"]["password"]
)
step.capture("login")
save_session_backup(driver, test_context, "login_session_backup")
if not getattr(driver, "_pxil_logged_in", False):
login(driver, config["login"]["username"], config["login"]["password"])
driver._pxil_logged_in = True
step.capture("login")
save_session_backup(driver, test_context, "login_session_backup")
create_session(driver, step, 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
create_and_activate_token(driver, step, product_token)
run_token_setup(driver, step, product_token)
logout(driver)
+125 -22
View File
@@ -4,7 +4,7 @@ import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from tests.config_reader import is_combined_setup_enabled, is_setup_token_enabled, load_config, parse_product_codes
from tests.config_reader import get_setup_configs, parse_product_codes
from tests.helpers import (
click_visible_menu_item,
click_first_grid_button,
@@ -23,20 +23,10 @@ from tests.locators import Locator
from tests.step_actions import StepActions
pytestmark = pytest.mark.skipif(
is_combined_setup_enabled() or not is_setup_token_enabled(),
reason="Standalone token setup is disabled or runs inside combined setup",
)
def get_token_context(driver, test_context):
config = load_config()
def get_token_context(driver, test_context, config):
wait = WebDriverWait(driver, 20)
step = StepActions(driver, wait, test_context)
token = config.get("trading_token_idas")
assert token, "Add trading_token_idas in config.json before running this test."
token = config["token"]
return config, step, token
@@ -83,12 +73,12 @@ def get_time_session_times(token, row_text, default_start_time, default_end_time
def get_product_codes(token):
product_codes = parse_product_codes(token.get("product_codes"))
product_codes = parse_product_codes(token.get("create_product_codes", token.get("product_codes")))
if product_codes:
return product_codes
product_code = token.get("product_code")
assert product_code, "Add product_code or product_codes in config.json trading_token_idas."
assert product_code, "Add product_code or product_codes in config.json products.<product>.token."
return parse_product_codes(product_code)
@@ -190,6 +180,87 @@ def complete_time_session_edits_from_saved_token(driver, step, token):
pause_after_step()
def open_rtm_new_token(driver, step, token):
"""Prepare the RTM generator through Add New Token."""
open_trading_maintenance_page(driver, Locator.tokengenerator)
handle_transient_popups(driver)
step.capture("open_token_generator_rtm")
for element_id, value, name in (
(Locator.generatormarketcode, token["market_code"], "market_code"),
(Locator.generatorproductcode, token["product_code"], "product_code"),
(Locator.generatorgenerationmode, token["generation_mode"], "generation_mode"),
):
select_bootstrap_dropdown_by_id(driver, element_id, value, exact=True)
step.capture(f"select_{name}_rtm")
# Product Type is populated by the application after selecting Product Code.
step.click((By.XPATH, Locator.addnewtokenbtn), "click_add_new_token_rtm")
def get_rtm_token_times(token, now=None):
now = datetime.now() if now is None else now
delivery_date = (now + timedelta(days=token.get("delivery_offset_days", 0))).strftime("%d/%m/%Y")
return (
f"{delivery_date} {token.get('delivery_from_time', '00:00')}",
f"{delivery_date} {token.get('delivery_to_time', '23:59')}",
(now + timedelta(minutes=token.get("session_start_offset_minutes", 5))).strftime("%H:%M"),
token.get("session_end_time", "20:00"),
)
def create_rtm_token(driver, step, token):
open_rtm_new_token(driver, step, token)
values = get_rtm_token_times(token)
fields = (
(Locator.rtmdeliveryfromdate, "delivery_from_date"),
(Locator.rtmdeliverytodate, "delivery_to_date"),
(Locator.rtmsessionstarttime, "session_start_time"),
(Locator.rtmsessionendtime, "session_end_time"),
)
for (locator, name), value in zip(fields, values):
type_first_visible_input(driver, locator, value)
step.capture(f"enter_{name}_rtm")
step.click((By.XPATH, Locator.savetokenbtn), "save_token_rtm")
handle_transient_popups(driver)
def create_upc_token(driver, step, token):
open_trading_maintenance_page(driver, Locator.tokengenerator)
handle_transient_popups(driver)
step.capture("open_token_generator_upc")
fields = [(Locator.generatormarketcode, token["market_code"], "market_code"), (Locator.generatorproductcode, token["product_code"], "product_code")]
if driver.find_elements(By.ID, Locator.generatorgenerationmode):
fields.append((Locator.generatorgenerationmode, token["generation_mode"], "generation_mode"))
for element_id, value, name in fields:
select_bootstrap_dropdown_by_id(driver, element_id, value, exact=True)
step.capture(f"select_{name}_upc")
step.click((By.XPATH, Locator.addnewtokenbtn), "click_add_new_token_upc")
timings = token.get("upc_times", {})
for name, locator, value in (
("order_normal_start", Locator.upcorderstart, timings.get("order_normal_start_time")),
("order_normal_end", Locator.upcorderend, timings.get("order_normal_end_time")),
("trade_start", Locator.upctradestart, timings.get("trade_session_start_time")),
("trade_end", Locator.upctradeend, timings.get("trade_session_end_time")),
):
if value:
type_first_visible_input(driver, locator, value)
step.capture(f"enter_{name}_upc")
step.click((By.XPATH, Locator.savetokenbtn), "save_token_upc")
handle_transient_popups(driver)
step.click((By.XPATH, Locator.tokengeneratorresetbtn), "reset_token_generator_upc")
def run_token_setup(driver, step, token):
if token.get("product_type", "").upper() == "UPC":
create_upc_token(driver, step, token)
return
if token.get("product_type", "").upper() == "RTM":
create_rtm_token(driver, step, token)
else:
create_and_activate_token(driver, step, token)
def create_and_activate_token(driver, step, token):
product_code = token["product_code"]
@@ -209,31 +280,63 @@ def create_and_activate_token(driver, step, token):
select_labeled_ui_dropdown(driver, "PRODUCT CODE", product_code)
step.capture(f"select_product_code_{product_code.lower()}")
generation_mode = token.get("generation_mode")
if generation_mode:
mode_elements = driver.find_elements(By.ID, Locator.generatorgenerationmode)
if mode_elements:
select_bootstrap_dropdown_by_id(driver, Locator.generatorgenerationmode, generation_mode, exact=True)
step.capture(f"select_generation_mode_{product_code.lower()}")
step.click((By.XPATH, Locator.addnewtokenbtn), f"click_add_new_token_{product_code.lower()}")
next_day, token_expiry = get_delivery_date_and_expiry(token)
step.type((By.XPATH, Locator.tokendeliverystartdate), next_day, f"enter_delivery_start_date_{product_code.lower()}")
step.type((By.XPATH, Locator.tokendeliveryenddate), next_day, f"enter_delivery_end_date_{product_code.lower()}")
step.type((By.XPATH, Locator.tokenexpirydate), token_expiry, f"enter_token_expiry_date_{product_code.lower()}")
if token.get("product_type", "").upper() == "UPC":
today = datetime.now().strftime("%d/%m/%Y")
if token.get("generation_mode") == "AnyDayUPC":
step.type((By.XPATH, Locator.tokentradedate), today, f"enter_trade_date_{product_code.lower()}")
timings = token.get("upc_times", {})
normal_start = timings.get("order_normal_start_time")
if normal_start is None and timings.get("order_normal_start_offset_minutes") is not None:
normal_start = (datetime.now() + timedelta(minutes=timings["order_normal_start_offset_minutes"])).strftime("%H:%M")
values = {
Locator.upcordernormalstart: normal_start,
Locator.upcordernormalend: timings.get("order_normal_end_time"),
Locator.upctradesessionstart: timings.get("trade_session_start_time"),
Locator.upctradesessionend: timings.get("trade_session_end_time"),
}
for locator, value in values.items():
if value is None:
continue
type_first_visible_input(driver, locator, value)
if token.get("generation_mode") not in ("WeeklyUPC", "MonthlyUPC"):
step.type((By.XPATH, Locator.tokendeliverystartdate), next_day, f"enter_delivery_start_date_{product_code.lower()}")
step.type((By.XPATH, Locator.tokendeliveryenddate), next_day, f"enter_delivery_end_date_{product_code.lower()}")
step.type((By.XPATH, Locator.tokenexpirydate), token_expiry, f"enter_token_expiry_date_{product_code.lower()}")
step.click((By.XPATH, Locator.savetokenbtn), f"save_token_{product_code.lower()}")
handle_transient_popups(driver)
if token.get("product_type", "").upper() == "UPC":
step.click((By.XPATH, Locator.tokengeneratorresetbtn), f"reset_token_generator_{product_code.lower()}")
return
complete_time_session_edits_from_saved_token(driver, step, token)
@pytest.mark.trading_token
def test_trading_token_creation_idas(driver, test_context):
@pytest.mark.parametrize("config", get_setup_configs("token"), ids=lambda c: c["product_name"])
def test_trading_token_creation(driver, test_context, config):
config, step, token = get_token_context(driver, test_context)
config, step, token = get_token_context(driver, test_context, config)
open_app_and_login(driver, step, config)
save_session_backup(driver, test_context, "login_session_backup")
for product_code in get_product_codes(token):
product_token = dict(token)
product_token["product_code"] = product_code
create_and_activate_token(driver, step, product_token)
run_token_setup(driver, step, product_token)
logout(driver)
@@ -241,7 +344,7 @@ def test_trading_token_creation_idas(driver, test_context):
# @pytest.mark.trading_token
# def test_trading_token_time_session_edit_idas(driver, test_context):
#
# config, step, token = get_token_context(driver, test_context)
# config, step, token = get_token_context(driver, test_context, config)
# open_app_and_login(driver, step, config)
#
# next_day, _ = get_delivery_date_and_expiry(token)
+3 -3
View File
@@ -5,7 +5,7 @@ import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from .config_reader import load_config
from .config_reader import get_product_config
from .helpers import (
clear_pending_console_input,
click_page_body,
@@ -23,7 +23,7 @@ from .step_actions import StepActions
def _bounce_token_generation(driver, step, config):
token = config["trading_token_idas"]
token = config["token"]
interactions = random.randint(2, 4)
open_trading_maintenance_page(driver, Locator.tokengeneration)
@@ -67,7 +67,7 @@ def _bounce_trading_session(driver, step, config):
@pytest.mark.fake_activity
def test_random_fake_activity_no_save(driver, test_context):
config = load_config()
config = get_product_config("IDAS")
wait = WebDriverWait(driver, 20)
step = StepActions(driver, wait, test_context)
+33
View File
@@ -0,0 +1,33 @@
from types import SimpleNamespace
import pytest
from tests.conftest import _get_run_prefix, _setup_item_prefix, get_run_id
@pytest.mark.parametrize("path,product,expected", [
("tests/setup/test_combined_setup.py", "IDAS", "IDAS_SESSION_TOKEN"),
("tests/setup/test_combined_setup.py", "RTM", "RTM_SESSION_TOKEN"),
("tests/setup/tokens/test_trading_token.py", "RTM", "RTM_TOKEN"),
("tests/setup/sessions/test_session.py", "RTM", "RTM_SESSION"),
])
def test_setup_artifacts_use_collected_product_and_flow(path, product, expected):
item = SimpleNamespace(nodeid=f"{path}::test[{product}]", callspec=SimpleNamespace(
params={"config": {"product_name": product}},
))
assert _setup_item_prefix(item) == expected
@pytest.mark.parametrize("path", ["tests/setup", "tests\\setup", "tests/setup/test_combined_setup.py"])
def test_setup_report_does_not_use_regression_config(path):
config = SimpleNamespace(invocation_params=SimpleNamespace(args=(path, "-k", "RTM")))
assert _get_run_prefix(config) == "PXIL_SETUP"
def test_mixed_run_ids_are_cached_separately(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
(tmp_path / "reports/screenshots/RTM_TOKEN_004").mkdir(parents=True)
config = SimpleNamespace()
assert get_run_id(config, "RTM_TOKEN") == "RTM_TOKEN_005"
assert get_run_id(config, "IDAS_SESSION_TOKEN") == "IDAS_SESSION_TOKEN_001"
assert get_run_id(config, "RTM_TOKEN") == "RTM_TOKEN_005"
+80
View File
@@ -0,0 +1,80 @@
from copy import deepcopy
import pytest
from tests.config_reader import get_product_config, get_setup_configs
@pytest.mark.parametrize("common_session", [False, True])
@pytest.mark.parametrize("common_token", [False, True])
@pytest.mark.parametrize("session_override", [None, False, True])
@pytest.mark.parametrize("token_override", [None, False, True])
def test_product_flags_inherit_or_override(common_session, common_token, session_override, token_override):
product = {"me_instance": "55", "token": {"market_code": "NATIONAL", "product_codes": "TEST"}}
if session_override is not None:
product["run_session"] = session_override
if token_override is not None:
product["run_token"] = token_override
config = {"setup": {"run_session": common_session, "run_token": common_token}, "products": {"RTM": product}}
session = common_session if session_override is None else session_override
token = common_token if token_override is None else token_override
expected = "combined" if session and token else "session" if session else "token" if token else None
for mode in ("combined", "session", "token"):
assert len(get_setup_configs(mode, config)) == int(mode == expected)
def test_mixed_products_defaults_and_no_mutation():
config = {
"setup": {"run_session": True, "run_token": False, "session": {"start_offset_minutes": 7, "end_time": "20:30"}},
"products": {
"IDAS": {"me_instance": "55", "session": {"end_time": "21:00"}},
"RTM": {"run_session": False, "run_token": True, "token": {"market_code": "NATIONAL", "product_codes": ["RTM1"]}},
"REC": {"enabled": False},
"RA": {"run_session": False},
},
}
original = deepcopy(config)
assert [c["product_name"] for c in get_setup_configs("session", config)] == ["IDAS"]
assert [c["product_name"] for c in get_setup_configs("token", config)] == ["RTM"]
assert get_setup_configs("combined", config) == []
resolved = get_product_config("IDAS", config)
assert resolved["setup"]["session"] == {"start_offset_minutes": 7, "end_time": "21:00"}
assert config == original
def test_missing_required_settings_fail_before_browser_start():
with pytest.raises(ValueError, match="me_instance"):
get_setup_configs("session", {"setup": {"run_token": False}, "products": {"REC": {}}})
with pytest.raises(ValueError, match="market_code"):
get_setup_configs("token", {"setup": {"run_session": False}, "products": {"REC": {}}})
def test_legacy_config_remains_supported():
config = {"product": {"me_instance": "55"}, "trading_token_idas": {"market_code": "NATIONAL", "product_code": "IDAS4"}}
assert get_setup_configs("combined", config)[0]["token"] == config["trading_token_idas"]
@pytest.mark.parametrize("selected", [[], ["DAILY"], ["WEEKLY"], ["MONTHLY"], ["DAILY", "WEEKLY", "MONTHLY"]])
def test_upc_session_selection_and_shared_timing(selected):
config = {
"setup": {"session": {"start_offset_minutes": 7, "end_time": "20:30"}},
"products": {"UPC": {
"run_session": True,
"run_token": False,
"session_variants": {
name: {"enabled": name in selected, "me_instance": instance}
for name, instance in (("DAILY", "58"), ("WEEKLY", "59"), ("MONTHLY", "60"))
},
}},
}
original = deepcopy(config)
sessions = get_setup_configs("session", config)
assert [c["product_name"] for c in sessions] == [f"UPC_{name}" for name in selected]
ids = {"DAILY": "58", "WEEKLY": "59", "MONTHLY": "60"}
assert [c["product"]["me_instance"] for c in sessions] == [ids[name] for name in selected]
assert all(c["setup"]["session"] == config["setup"]["session"] for c in sessions)
assert get_setup_configs("combined", config) == []
assert get_setup_configs("token", config) == []
assert config == original
config["products"]["UPC"]["enabled"] = False
assert get_setup_configs("session", config) == []
+71
View File
@@ -0,0 +1,71 @@
from unittest.mock import Mock
from datetime import datetime
from tests.locators import Locator
from tests.setup.tokens import test_trading_token as actions
def test_rtm_saves_generator_token_without_idas_activation(monkeypatch):
events = []
monkeypatch.setattr(actions, "open_trading_maintenance_page", lambda driver, menu: events.append(menu))
monkeypatch.setattr(actions, "handle_transient_popups", lambda driver: None)
monkeypatch.setattr(
actions, "select_bootstrap_dropdown_by_id",
lambda driver, field, value, exact=False: events.append((field, value, exact)),
)
idas_flow = Mock()
monkeypatch.setattr(actions, "create_and_activate_token", idas_flow)
step = Mock()
step.click.side_effect = lambda locator, name: events.append(locator[1])
monkeypatch.setattr(actions, "get_rtm_token_times", lambda token: (
"23/09/2026 00:00", "23/09/2026 23:59", "10:15", "20:00",
))
monkeypatch.setattr(actions, "type_first_visible_input", lambda driver, locator, value: events.append((locator, value)))
actions.run_token_setup(Mock(), step, {
"product_type": "RTM", "market_code": "NATIONAL",
"product_code": "RTM", "generation_mode": "RTM",
})
assert events == [
Locator.tokengenerator,
("marketCode", "NATIONAL", True),
("productCode", "RTM", True),
("tokenMstr.genMode", "RTM", True),
Locator.addnewtokenbtn,
(Locator.rtmdeliveryfromdate, "23/09/2026 00:00"),
(Locator.rtmdeliverytodate, "23/09/2026 23:59"),
(Locator.rtmsessionstarttime, "10:15"),
(Locator.rtmsessionendtime, "20:00"),
Locator.savetokenbtn,
]
step.type.assert_not_called()
idas_flow.assert_not_called()
def test_idas_keeps_existing_creation_and_activation_flow(monkeypatch):
idas_flow = Mock()
rtm_flow = Mock()
monkeypatch.setattr(actions, "create_and_activate_token", idas_flow)
monkeypatch.setattr(actions, "open_rtm_new_token", rtm_flow)
driver, step = Mock(), Mock()
token = {"product_type": "IDAS", "product_code": "IDAS4"}
actions.run_token_setup(driver, step, token)
idas_flow.assert_called_once_with(driver, step, token)
rtm_flow.assert_not_called()
def test_rtm_token_timing_defaults_and_overrides():
now = datetime(2026, 9, 23, 10, 58)
assert actions.get_rtm_token_times({}, now) == (
"23/09/2026 00:00", "23/09/2026 23:59", "11:03", "20:00",
)
assert actions.get_rtm_token_times({
"delivery_offset_days": 1,
"delivery_from_time": "01:30", "delivery_to_time": "22:00",
"session_start_offset_minutes": 12, "session_end_time": "21:15",
}, now) == (
"24/09/2026 01:30", "24/09/2026 22:00", "11:10", "21:15",
)