From 9d16b20eb45086db521472667fb0025592d6d97f Mon Sep 17 00:00:00 2001 From: Tejas Shasane Date: Wed, 23 Sep 2026 09:27:55 +0530 Subject: [PATCH] Add product-specific session and token setup --- RUN_COMMANDS.md | 42 ++++++++- config.json | 104 +++++++++++++++++------ tests/config_reader.py | 56 ++++++++++++ tests/setup/sessions/test_session.py | 13 +-- tests/setup/test_combined_setup.py | 15 +--- tests/setup/tokens/test_trading_token.py | 25 ++---- tests/test_fake_activity.py | 6 +- tests/test_setup_config.py | 54 ++++++++++++ 8 files changed, 249 insertions(+), 66 deletions(-) create mode 100644 tests/test_setup_config.py diff --git a/RUN_COMMANDS.md b/RUN_COMMANDS.md index d14feb3..f82b1b8 100644 --- a/RUN_COMMANDS.md +++ b/RUN_COMMANDS.md @@ -11,6 +11,46 @@ 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). +Existing IDAS settings are preserved. Other 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": "", + "token": { + "market_code": "NATIONAL", + "product_type": "RTM", + "product_codes": "YOUR_ACTUAL_RTM_CODE" + } +} +``` + +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 tokens in one login; otherwise +only its enabled action runs. Test/report names include the product name. +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 +109,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..token.product_codes`. Run only session setup: diff --git a/config.json b/config.json index 9811e74..6d8a1f7 100644 --- a/config.json +++ b/config.json @@ -6,40 +6,96 @@ "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" - }, - "ORDER_EXCH": { - "start_time": "19:30", - "end_time": "19:45" - }, - "TRADE": { - "start_time": "19:45", - "end_time": "19:50" + "products": { + "IDAS": { + "enabled": true, + "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, + "me_instance": "", + "session": {}, + "token": { + "market_code": "NATIONAL", + "product_type": "RTM", + "product_codes": [], + "delivery_offset_days": 1, + "expiry_time": "20:30", + "time_sessions": {} + } + }, + "UPC": { + "enabled": false, + "me_instance": "", + "session": {}, + "token": { + "market_code": "NATIONAL", + "product_type": "UPC", + "product_codes": [], + "delivery_offset_days": 1, + "expiry_time": "20:30", + "time_sessions": {} + } + }, + "RA": { + "enabled": false, + "me_instance": "", + "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": {} } } } diff --git a/tests/config_reader.py b/tests/config_reader.py index b542358..e53a167 100644 --- a/tests/config_reader.py +++ b/tests/config_reader.py @@ -1,4 +1,5 @@ import json +from copy import deepcopy from pathlib import Path @@ -11,6 +12,61 @@ 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 + 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, + "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("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() diff --git a/tests/setup/sessions/test_session.py b/tests/setup/sessions/test_session.py index c2afa00..05a2f6b 100644 --- a/tests/setup/sessions/test_session.py +++ b/tests/setup/sessions/test_session.py @@ -6,16 +6,10 @@ 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.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", {}) @@ -50,9 +44,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) diff --git a/tests/setup/test_combined_setup.py b/tests/setup/test_combined_setup.py index 04ca1b3..98ab035 100644 --- a/tests/setup/test_combined_setup.py +++ b/tests/setup/test_combined_setup.py @@ -1,26 +1,19 @@ 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.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) diff --git a/tests/setup/tokens/test_trading_token.py b/tests/setup/tokens/test_trading_token.py index f6c0b5c..a3e6f55 100644 --- a/tests/setup/tokens/test_trading_token.py +++ b/tests/setup/tokens/test_trading_token.py @@ -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 @@ -88,7 +78,7 @@ def get_product_codes(token): 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..token." return parse_product_codes(product_code) @@ -224,9 +214,10 @@ def create_and_activate_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") @@ -241,7 +232,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) diff --git a/tests/test_fake_activity.py b/tests/test_fake_activity.py index f0ddb59..7ff1551 100644 --- a/tests/test_fake_activity.py +++ b/tests/test_fake_activity.py @@ -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) diff --git a/tests/test_setup_config.py b/tests/test_setup_config.py new file mode 100644 index 0000000..c9f4443 --- /dev/null +++ b/tests/test_setup_config.py @@ -0,0 +1,54 @@ +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"]