From e84b7e9950d636546161e08665b15f264619cb8b Mon Sep 17 00:00:00 2001 From: Tejas Shasane Date: Tue, 8 Sep 2026 09:24:02 +0530 Subject: [PATCH] Initial commit - Selenium automation framework --- .gitignore | 20 + .vscode/settings.json | 7 + RUN_COMMANDS.md | 386 ++++++ __init__.py | 0 config.json | 46 + config/README.md | 30 + config/runtime.json | 439 +++++++ data/idas/README.md | 31 + data/idas/bidding/README.md | 61 + data/idas/bidding/block_bid/.gitkeep | 1 + data/idas/bidding/json/.gitkeep | 1 + data/idas/bidding/single_bid/.gitkeep | 1 + .../single_bid/GUJC10010001 - Copy.xlsx | Bin 0 -> 26623 bytes .../idas/bidding/single_bid/GUJC10010001.xlsx | Bin 0 -> 26623 bytes data/idas/bidding/uploads/.gitkeep | 1 + data/idas/bidding/uploads/CHHC10050003.xlsx | Bin 0 -> 10458 bytes data/idas/bidding/uploads/GUJC10030013.xlsx | Bin 0 -> 10459 bytes pytest.ini | 14 + reports/html/.gitkeep | 1 + reports/logs/.gitkeep | 1 + reports/screenshots/.gitkeep | 1 + requirements.txt | 6 + tests/__init__.py | 0 tests/browser.py | 14 + tests/config_reader.py | 62 + tests/conftest.py | 242 ++++ tests/file_generation.py | 393 +++++++ tests/helpers.py | 560 +++++++++ tests/locators.py | 49 + tests/regression/README.md | 36 + tests/regression/__init__.py | 1 + tests/regression/auth.py | 152 +++ tests/regression/config_reader.py | 272 +++++ tests/regression/exchange/__init__.py | 1 + tests/regression/exchange/idas/README.md | 32 + tests/regression/exchange/idas/__init__.py | 1 + .../exchange/idas/bidding/__init__.py | 1 + .../idas/bidding/block_bulk_upload_actions.py | 400 +++++++ .../exchange/idas/bidding/excel_reader.py | 167 +++ .../idas/bidding/file_upload_batch.py | 72 ++ .../idas/bidding/folder_upload_actions.py | 622 ++++++++++ .../exchange/idas/bidding/grid_actions.py | 97 ++ .../exchange/idas/bidding/input_manifest.py | 74 ++ .../exchange/idas/bidding/json_reader.py | 80 ++ .../exchange/idas/bidding/navigation.py | 231 ++++ .../test_idas_exchange_bidding_flow.py | 1034 +++++++++++++++++ .../exchange/idas/bidding/upload_actions.py | 191 +++ tests/regression/exchange/idas/helpers.py | 1 + .../exchange/idas/matching/__init__.py | 1 + .../exchange/idas/matching/actions.py | 345 ++++++ .../idas/matching/bid_transfer_actions.py | 207 ++++ .../test_idas_exchange_matching_flow.py | 215 ++++ .../idas/matching/test_idas_mco_login.py | 42 + .../exchange/idas/order_book/__init__.py | 1 + .../exchange/idas/reports/__init__.py | 1 + .../exchange/idas/trade_book/__init__.py | 1 + .../exchange/idas/trading_locators.py | 1 + tests/regression/trading_member/__init__.py | 1 + .../trading_member/idas/__init__.py | 1 + .../trading_member/idas/bidding/__init__.py | 1 + .../trading_member/idas/matching/__init__.py | 1 + .../idas/order_book/__init__.py | 1 + .../trading_member/idas/reports/__init__.py | 1 + .../idas/trade_book/__init__.py | 1 + tests/regression/ui_helpers.py | 264 +++++ tests/reporting.py | 20 + tests/setup/__init__.py | 1 + tests/setup/sessions/__init__.py | 1 + tests/setup/sessions/test_session.py | 72 ++ tests/setup/test_combined_setup.py | 46 + tests/setup/tokens/__init__.py | 1 + tests/setup/tokens/test_trading_token.py | 249 ++++ tests/step_actions.py | 68 ++ tests/test_fake_activity.py | 95 ++ tests/test_session.py | 7 + tests/test_trading_token.py | 7 + 76 files changed, 7484 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 RUN_COMMANDS.md create mode 100644 __init__.py create mode 100644 config.json create mode 100644 config/README.md create mode 100644 config/runtime.json create mode 100644 data/idas/README.md create mode 100644 data/idas/bidding/README.md create mode 100644 data/idas/bidding/block_bid/.gitkeep create mode 100644 data/idas/bidding/json/.gitkeep create mode 100644 data/idas/bidding/single_bid/.gitkeep create mode 100644 data/idas/bidding/single_bid/GUJC10010001 - Copy.xlsx create mode 100644 data/idas/bidding/single_bid/GUJC10010001.xlsx create mode 100644 data/idas/bidding/uploads/.gitkeep create mode 100644 data/idas/bidding/uploads/CHHC10050003.xlsx create mode 100644 data/idas/bidding/uploads/GUJC10030013.xlsx create mode 100644 pytest.ini create mode 100644 reports/html/.gitkeep create mode 100644 reports/logs/.gitkeep create mode 100644 reports/screenshots/.gitkeep create mode 100644 requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/browser.py create mode 100644 tests/config_reader.py create mode 100644 tests/conftest.py create mode 100644 tests/file_generation.py create mode 100644 tests/helpers.py create mode 100644 tests/locators.py create mode 100644 tests/regression/README.md create mode 100644 tests/regression/__init__.py create mode 100644 tests/regression/auth.py create mode 100644 tests/regression/config_reader.py create mode 100644 tests/regression/exchange/__init__.py create mode 100644 tests/regression/exchange/idas/README.md create mode 100644 tests/regression/exchange/idas/__init__.py create mode 100644 tests/regression/exchange/idas/bidding/__init__.py create mode 100644 tests/regression/exchange/idas/bidding/block_bulk_upload_actions.py create mode 100644 tests/regression/exchange/idas/bidding/excel_reader.py create mode 100644 tests/regression/exchange/idas/bidding/file_upload_batch.py create mode 100644 tests/regression/exchange/idas/bidding/folder_upload_actions.py create mode 100644 tests/regression/exchange/idas/bidding/grid_actions.py create mode 100644 tests/regression/exchange/idas/bidding/input_manifest.py create mode 100644 tests/regression/exchange/idas/bidding/json_reader.py create mode 100644 tests/regression/exchange/idas/bidding/navigation.py create mode 100644 tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py create mode 100644 tests/regression/exchange/idas/bidding/upload_actions.py create mode 100644 tests/regression/exchange/idas/helpers.py create mode 100644 tests/regression/exchange/idas/matching/__init__.py create mode 100644 tests/regression/exchange/idas/matching/actions.py create mode 100644 tests/regression/exchange/idas/matching/bid_transfer_actions.py create mode 100644 tests/regression/exchange/idas/matching/test_idas_exchange_matching_flow.py create mode 100644 tests/regression/exchange/idas/matching/test_idas_mco_login.py create mode 100644 tests/regression/exchange/idas/order_book/__init__.py create mode 100644 tests/regression/exchange/idas/reports/__init__.py create mode 100644 tests/regression/exchange/idas/trade_book/__init__.py create mode 100644 tests/regression/exchange/idas/trading_locators.py create mode 100644 tests/regression/trading_member/__init__.py create mode 100644 tests/regression/trading_member/idas/__init__.py create mode 100644 tests/regression/trading_member/idas/bidding/__init__.py create mode 100644 tests/regression/trading_member/idas/matching/__init__.py create mode 100644 tests/regression/trading_member/idas/order_book/__init__.py create mode 100644 tests/regression/trading_member/idas/reports/__init__.py create mode 100644 tests/regression/trading_member/idas/trade_book/__init__.py create mode 100644 tests/regression/ui_helpers.py create mode 100644 tests/reporting.py create mode 100644 tests/setup/__init__.py create mode 100644 tests/setup/sessions/__init__.py create mode 100644 tests/setup/sessions/test_session.py create mode 100644 tests/setup/test_combined_setup.py create mode 100644 tests/setup/tokens/__init__.py create mode 100644 tests/setup/tokens/test_trading_token.py create mode 100644 tests/step_actions.py create mode 100644 tests/test_fake_activity.py create mode 100644 tests/test_session.py create mode 100644 tests/test_trading_token.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..245807d --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Virtual environments +venv/ +.venv/ + +# Python cache +__pycache__/ +*.py[cod] + +# Pytest cache +.pytest_cache/ + +# Generated reports (keep directory structure) +reports/screenshots/* +!reports/screenshots/.gitkeep + +reports/logs/* +!reports/logs/.gitkeep + +reports/html/* +!reports/html/.gitkeep diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b38853 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/RUN_COMMANDS.md b/RUN_COMMANDS.md new file mode 100644 index 0000000..d14feb3 --- /dev/null +++ b/RUN_COMMANDS.md @@ -0,0 +1,386 @@ +# PXIL Selenium Run Commands + +Use these commands from the project folder: + +```powershell +cd "C:\Selenium Test" +.\venv\Scripts\Activate.ps1 +``` + +## Config Controls + +Edit `config.json` before running. + +Session and token together in one login: + +```json +"setup": { + "run_session": true, + "run_token": true +} +``` + +Token only: + +```json +"setup": { + "run_session": false, + "run_token": true +} +``` + +Session only: + +```json +"setup": { + "run_session": true, + "run_token": false +} +``` + +Multiple token product codes: + +```json +"product_codes": "IDAS,IDAS1,IDAS2,IDAS3,IDAS4" +``` + +Single token product code: + +```json +"product_codes": "IDAS4" +``` + +## Commands + +Run setup based on `config.json`: + +```powershell +pytest tests\setup -s --html=reports/html/setup_run.html --self-contained-html +``` + +Use this for: +- Session and token together when both flags are `true`. +- Token only when `run_session` is `false` and `run_token` is `true`. +- Session only when `run_session` is `true` and `run_token` is `false`. + +Run only token setup: + +```powershell +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`. + +Run only session setup: + +```powershell +pytest tests\setup\sessions -s --html=reports/html/session_run.html --self-contained-html +``` + +Use this when `run_session` is `true`. + +Run combined session plus token setup explicitly: + +```powershell +pytest tests\setup\test_combined_setup.py -s --html=reports/html/combined_setup_run.html --self-contained-html +``` + +Use this when both `run_session` and `run_token` are `true`. It logs in once, creates the session, then creates and activates all configured tokens in the same browser session. + +Collect tests without opening browser: + +```powershell +pytest tests\setup --collect-only -q +``` + +Use this to confirm pytest can see the setup tests. + +## Regression Config Controls + +Regression flows use `config/runtime.json`, not `config.json`. + +Common fields to edit: + +```json +"execution": { + "login_role": "trading_member", + "trading_member_key": "tm_55", + "product_family": "IDAS", + "flow_scope": "bidding" +} +``` + +Trading-member login uses `environment.login_urls.trading_member`; exchange login +uses `environment.login_urls.exchange`. + +Product and market selection: + +```json +"active_selection": { + "product_code": "IDAS1", + "trading_market": "DAM", + "bidding_screen": "single_bid" +} +``` + +Use `"trading_market": "ALL"` to run IDAS bidding for `GDAM`, `DAM`, and `HPDAM` in one test run. Use `"trading_markets": ["GDAM", "DAM"]` to run only those segments in that order. +Use `"bidding_screen": "single_and_block_bid"` to run single-bid folder upload first and then block-bid folder upload in the same logged-in browser session. + +Bidding entry method: + +```json +"entry_method": { + "bid_entry_method": "file_upload", + "file_upload_mode": "batch", + "auto_submit": true +} +``` + +Available `bid_entry_method` values: +- `excel_grid`: read Excel and paste values into the bidding grid. +- `json_grid`: read JSON and paste values into the bidding grid. +- `file_upload`: upload one Excel file or upload multiple files in batch mode. +- `folder_upload`: upload each TM folder under the configured folder-upload root, click Submit All, wait for all files to reach 100%, then fetch each file status. + +Available `file_upload_mode` values: +- `single`: upload only the configured active entry file. +- `batch`: upload all enabled upload files. + +## Regression Commands + +Run all active regression tests based on `config/runtime.json`: + +```powershell +pytest tests\regression -m regression -s --html=reports/html/regression_run.html --self-contained-html +``` + +Use this when `execution.flow_scope` is `e2e`, `bidding`, or `matching`. Current active regression tests are exchange IDAS bidding and exchange IDAS matching. + +Run IDAS exchange bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_run.html --self-contained-html +``` + +Set this in `config/runtime.json` before running: + +```json +"execution": { + "login_role": "exchange", + "product_family": "IDAS", + "flow_scope": "bidding" +} +``` + +Run Excel grid bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_excel_grid_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"entry_method": { + "bid_entry_method": "excel_grid", + "auto_submit": true +} +``` + +Run JSON grid bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_json_grid_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"entry_method": { + "bid_entry_method": "json_grid", + "auto_submit": true +} +``` + +Run single file upload bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_single_upload_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"entry_method": { + "bid_entry_method": "file_upload", + "file_upload_mode": "single", + "auto_submit": true +} +``` + +Run batch file upload bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_batch_upload_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"entry_method": { + "bid_entry_method": "file_upload", + "file_upload_mode": "batch", + "auto_submit": true +} +``` + +Batch upload uses enabled entries from `config/runtime.json > IDAS > bidding > entries`, and upload files from: + +```text +data/idas/bidding/uploads +``` + +Run folder upload bidding: + +```powershell +pytest tests\regression\exchange\idas\bidding -m bidding -s --html=reports/html/bidding_folder_upload_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"users": { + "exchange_parallel": [ + { + "label": "Parallel Exchange 1", + "username": "exchange_user_1", + "password": "exchange_password_1" + }, + { + "label": "Parallel Exchange 2", + "username": "exchange_user_2", + "password": "exchange_password_2" + } + ] +}, +"entry_method": { + "bid_entry_method": "folder_upload", + "folder_upload_execution_mode": "sequential", + "auto_submit": true +}, +"data_paths": { + "folder_upload_root": "data/idas/bidding/folder_uploads", + "folder_upload_sides": ["BUYSELL"] +}, +"timing": { + "folder_upload_max_parallel_sessions": 2, + "folder_upload_parallel_login_stagger_seconds": 20, + "folder_upload_parallel_captcha_wait_seconds": 60 +} +``` + +Folder upload uses a separate root from normal single-file uploads. Keep folders like: + +```text +data/idas/bidding/folder_uploads/DAM/Single/Buy/C1036 +data/idas/bidding/folder_uploads/DAM/Single/Sell/C1036 +``` + +If `trading_market` is `ALL`, the folder root can contain market folders like `GDAM`, `DAM`, and `HPDAM`. The runner then looks under `Market/Single/Buy` or `Market/Single/Sell` and uses each TM folder name, such as `C1036`, to select the TM ID. + +After opening each bidding screen, the runner waits for `bidding_screen_ready_wait_seconds` from `IDAS > bidding > timing`. The default is `10`. + +Set `folder_upload_execution_mode` to `parallel` to run multiple TM folders at the same time. Parallel mode opens one browser per active TM folder up to `folder_upload_max_parallel_sessions`; each browser has its own manual CAPTCHA wait from `folder_upload_parallel_captcha_wait_seconds`, and is staggered by `folder_upload_parallel_login_stagger_seconds`. + +For trading-member login, configure each folder owner under `users.trading_members` +with `member_id` or `folder_upload_tm_ids` matching the folder names, for example +`C1024`. The runner uses that mapping to choose the browser login for each folder +and skips TM dropdown selection because the application locks the TM from login. +For exchange login, configure at least as many `users.exchange_parallel` entries +as `folder_upload_max_parallel_sessions`. + +Folder-upload timing/status JSON logs are written under: + +```text +reports/logs//test_idas_exchange_bidding_flow +``` + +Run IDAS exchange matching: + +```powershell +pytest tests\regression\exchange\idas\matching -m matching -s --html=reports/html/matching_run.html --self-contained-html +``` + +Set this in `config/runtime.json` before running: + +```json +"execution": { + "login_role": "exchange", + "product_family": "IDAS", + "flow_scope": "matching" +} +``` + +Matching settings live here: + +```json +"matching": { + "matching_product_code": "IDAS(I)", + "matching_type": "Provisional Match", + "source_types": [ + "Green", + "Conventional", + "HPDAM" + ] +} +``` + +Run full IDAS e2e regression: + +```powershell +pytest tests\regression -m regression -s --html=reports/html/idas_e2e_run.html --self-contained-html +``` + +Set this in `config/runtime.json`: + +```json +"execution": { + "login_role": "exchange", + "product_family": "IDAS", + "flow_scope": "e2e" +} +``` + +This runs all active regression tests whose flow supports `e2e`. At present, that includes bidding and matching. + +Run fake activity smoke test: + +```powershell +pytest tests\test_fake_activity.py -m fake_activity -s --html=reports/html/fake_activity_run.html --self-contained-html +``` + +Use this for random menu/dropdown activity without saving data. + +Collect regression tests without opening browser: + +```powershell +pytest tests\regression --collect-only -q +``` + +Use this to confirm which regression tests exist. + +## Placeholder Markers + +These markers are registered in `pytest.ini`, but no runnable test files are currently present for them in this repo: + +- `reports` +- `order_book` +- `trade_book` + +When test files are added under `tests\regression`, use the same command shape: + +```powershell +pytest tests\regression -m reports -s --html=reports/html/reports_run.html --self-contained-html +pytest tests\regression -m order_book -s --html=reports/html/order_book_run.html --self-contained-html +pytest tests\regression -m trade_book -s --html=reports/html/trade_book_run.html --self-contained-html +``` diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config.json b/config.json new file mode 100644 index 0000000..9811e74 --- /dev/null +++ b/config.json @@ -0,0 +1,46 @@ +{ + "url": "https://utilsuat.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_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" + } + } + } +} diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..de4ec04 --- /dev/null +++ b/config/README.md @@ -0,0 +1,30 @@ +# Config Layout + +This folder controls environment, login, user, product, and flow selection for regression automation runs. + +## Files + +- `runtime.json`: the single regression config file. It contains environment settings, active execution settings, users, and product-wise sections such as `IDAS` and `RTM`. + +## Main controls + +In `runtime.json`: + +- `environment.active_environment`: selects `UAT` or `Eastern` for the entire run. +- `environment.portal_environments`: keeps the Pratyay and MCO URLs paired for each environment. Pratyay uses the role-specific exchange/trading-member URL; MCO uses its own portal URL. +- `environment.base_url` and `environment.login_urls`: legacy fallbacks used only when no active portal environment profile is configured. +- `users.mco`: credentials for the MCO portal. These are independent of `users.exchange`, which remains the Pratyay exchange login. +- `IDAS.matching.portals.mco.stage`: currently `login_only`; the MCO test signs in and verifies `/Dashboard/Index` independently from the Pratyay matching flow. +- `IDAS.active_selection.product_code`: the shared product used for bidding, Pratyay bid transfer, and initiate matching. UI suffixes such as `(I)` are resolved automatically. +- `execution.login_role`: `exchange` or `trading_member`. +- `execution.trading_member_key`: which trading member entry from `runtime.json > users.trading_members` should be used. +- `execution.product_family`: product section to load from `runtime.json`, for example `IDAS` or `RTM`. +- `execution.product_code`: selected product code, for example `IDAS1`, `IDAS2`, or `IDAS3`. +- `execution.flow_scope`: `e2e`, `bidding`, `matching`, `reports`, `order_book`, or `trade_book`. +- `users.exchange.username` and `users.exchange.password`: exchange login. +- `users.trading_members..username` and `users.trading_members..password`: trading-member login. +- `users.trading_members..member_id`: trading member / ME instance value used by flows. +- `users.trading_members..folder_upload_tm_ids`: TM folder names this trading-member login is allowed to upload. Parallel folder upload uses this mapping to choose the right login for each TM folder. +- `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`. diff --git a/config/runtime.json b/config/runtime.json new file mode 100644 index 0000000..c1e8e21 --- /dev/null +++ b/config/runtime.json @@ -0,0 +1,439 @@ +{ + "description": "Single regression config. Use execution to choose which product/flow to run, then edit the matching product section below for product-specific settings.", + "environment": { + "description": "Application URL selection. active_environment keeps the Pratyay and MCO portals on the same deployment.", + "active_environment": "UAT", + "active_environment_options": [ + "UAT", + "Eastern" + ], + "portal_environments": { + "UAT": { + "pratyay": { + "exchange": "https://tradinguat.pxil.in/Public/Loginadmin.aspx", + "trading_member": "https://tradinguat.pxil.in/Public/Login.aspx" + }, + "mco": "https://membersuat.pxil.in" + }, + "Eastern": { + "pratyay": { + "exchange": "https://tradingeastern.pxil.in/Public/Loginadmin.aspx", + "trading_member": "https://tradingeastern.pxil.in/Public/Login.aspx" + }, + "mco": "https://memberseastern.pxil.in" + } + }, + "base_url": "https://tradinguat.pxil.in/Public/Login.aspx", + "login_urls": { + "exchange": "https://tradinguat.pxil.in/Public/Loginadmin.aspx", + "trading_member": "https://tradinguat.pxil.in/Public/Login.aspx" + }, + "available_base_urls": { + "UAT": "https://tradinguat.pxil.in/Public/Login.aspx", + "Staging": "https://tradingeastern.pxil.in/Public/Loginadmin.aspx", + "Pre Prod": "" + } + }, + "execution": { + "description": "Top-level run controls used by the regression suite.", + "login_role": "trading_member", + "login_role_options": [ + "exchange", + "trading_member" + ], + "trading_member_key": "tm_55", + "product_family": "IDAS", + "product_family_options": [ + "IDAS", + "RTM" + ], + "flow_scope": "bidding", + "flow_scope_options": [ + "e2e", + "bidding", + "matching", + "reports", + "order_book", + "trade_book" + ], + "product_code_override": "", + "product_code_override_description": "Optional. Leave blank to use the active_selection.product_code inside the chosen product section." + }, + "timing": { + "description": "General wait controls used across regression flows.", + "login_captcha_wait_seconds": 10, + "login_post_submit_wait_seconds": 10, + "step_delay_seconds": 5 + }, + "users": { + "description": "Regression login credentials used when execution.login_role changes.", + "exchange": { + "username": "Tejas", + "password": "Pxil@123" + }, + "mco": { + "description": "MCO uses credentials independent of the Pratyay exchange login.", + "username": "superadmin", + "password": "SuperAdmin@123" + }, + "exchange_parallel": [ + { + "label": "Parallel Exchange 1", + "username": "Kshipra", + "password": "Pxil@123" + }, + { + "label": "Parallel Exchange 2", + "username": "Mahimaimary", + "password": "Pxil@123" + } + ], + "trading_members": { + "tm_55": { + "member_id": "C1024", + "label": "Default Trading Member", + "username": "TRDC10240001", + "password": "Pxil@123", + "folder_upload_tm_ids": [ + "C1024" + ] + }, + "tm_c1026": { + "member_id": "C1026", + "label": "Folder Upload TM C1026", + "username": "TRDC10260001", + "password": "Pxil@123", + "folder_upload_tm_ids": [ + "C1026" + ] + }, + "tm_c1015": { + "member_id": "C1015", + "label": "Folder Upload TM C1015", + "username": "TRDC10150001", + "password": "Pxil@123", + "folder_upload_tm_ids": [ + "C1015" + ] + } + } + }, + "IDAS": { + "description": "IDAS product-wise regression config.", + "active_selection": { + "description": "This block controls what the bidding and matching pages select for IDAS.", + "product_code": "IDASTEST05", + "product_code_options": [ + "IDAS", + "IDAS1", + "IDAS2", + "IDAS3", + "IDAS4" + ], + "trading_market": "DAM", + "trading_markets": [ + "GDAM", + "DAM" + ], + "trading_market_options": [ + "ALL", + "GDAM", + "DAM", + "HPDAM" + ], + "bidding_screen": "single_and_block_bid", + "bidding_screen_options": [ + "single_bid", + "block_bid", + "single_and_block_bid" + ] + }, + "defaults": { + "description": "Fallbacks used if active_selection is incomplete.", + "market_code": "NATIONAL", + "product_type": "IDAS", + "product_code": "IDAS", + "trading_market": "DAM", + "bidding_screen": "single_bid" + }, + "flows": { + "description": "Supported IDAS regression flow groups.", + "e2e": { + "description": "Complete regression flow across all enabled modules." + }, + "bidding": { + "description": "Run only bidding flows." + }, + "matching": { + "description": "Run only matching flows." + }, + "reports": { + "description": "Run only report download flows." + }, + "order_book": { + "description": "Run only order book flows." + }, + "trade_book": { + "description": "Run only trade book flows." + } + }, + "setup": { + "description": "Product-level setup controls.", + "session": { + "enabled": true + }, + "token": { + "enabled": true, + "market_code": "NATIONAL", + "product_type": "IDAS" + } + }, + "bidding": { + "description": "All IDAS bidding-page related inputs and waits.", + "entry_method": { + "bid_entry_method": "folder_upload", + "available_bid_entry_methods": [ + "excel_grid", + "json_grid", + "file_upload", + "folder_upload" + ], + "file_upload_mode": "batch", + "folder_upload_execution_mode": "sequential", + "folder_upload_execution_mode_options": [ + "sequential", + "parallel" + ], + "auto_submit": true, + "block_bid": { + "block_upload_method": "folder_upload", + "block_upload_method_options": [ + "file_upload", + "folder_upload" + ], + "product_code": "IDASTEST05", + "product_container_id": "s2id_ddlproduct3", + "segment_container_id": "s2id_ddlSegment3", + "tm_container_id": "s2id_ddlTMID3", + "block_upload_success_timeout_seconds": 5400, + "block_upload_success_poll_seconds": 2, + "block_upload_progress_log_interval_seconds": 60 + } + }, + "data_paths": { + "input_root": "data/idas/bidding/single_bid", + "json_input_root": "data/idas/bidding/json", + "upload_input_root": "data/idas/bidding/uploads", + "folder_upload_root": "data/idas/bidding/folder_uploads", + "folder_upload_sides": [ + "BUYSELL" + ] + }, + "selection": { + "active_entry_key": "gujc10010001" + }, + "timing": { + "upload_to_submit_delay_seconds": 2, + "submit_to_fetch_delay_seconds": 2, + "folder_upload_progress_timeout_seconds": 5400, + "folder_upload_progress_poll_seconds": 2, + "folder_upload_progress_log_interval_seconds": 60, + "folder_upload_continue_when_last_file_complete": true, + "folder_upload_keep_alive_interval_seconds": 30, + "folder_upload_max_parallel_sessions": 2, + "folder_upload_parallel_login_stagger_seconds": 20, + "folder_upload_parallel_captcha_wait_seconds": 60, + "folder_upload_fetch_timeout_seconds": 30, + "bidding_screen_ready_wait_seconds": 15, + "product_to_tm_delay_seconds": 15, + "tm_to_tac_delay_seconds": 3, + "tm_timeout_seconds": 30, + "tm_attempts": 3, + "tac_timeout_seconds": 15, + "tac_attempts": 2 + }, + "excel_layout": { + "column_start": "B", + "market_price_rows": { + "GDAM": 2, + "DAM": 3, + "HPDAM": 4 + }, + "common_data_start_row": 6 + }, + "entries": [ + { + "key": "gujc10010001", + "tm_id": "C1001", + "tac_id": "GUJC10010001", + "excel_file": "GUJC10010001.xlsx", + "json_file": "GUJC10010001.json", + "upload_file": "GUJC10010001.xlsx", + "sheet_name": "Sheet1", + "enabled": true + }, + { + "key": "chhc10110001", + "tm_id": "C1011", + "tac_id": "CHHC10110001", + "excel_file": "CHHC10110001.xlsx", + "json_file": "CHHC10110001.json", + "upload_file": "CHHC10110001.xlsx", + "sheet_name": "Sheet1", + "enabled": false + }, + { + "key": "apts10240001", + "tm_id": "S1024", + "tac_id": "APTS10240001", + "excel_file": "APTS10240001.xlsx", + "json_file": "APTS10240001.json", + "upload_file": "APTS10240001.xlsx", + "sheet_name": "Sheet1", + "enabled": false + } + ] + }, + "matching": { + "description": "IDAS order matching split between Pratyay preparation and the separate MCO market-coupling portal.", + "portals": { + "pratyay": { + "enabled": true, + "description": "Existing exchange-side provisional matching and flow-request preparation." + }, + "mco": { + "enabled": true, + "stage": "login_only", + "description": "MCO login and dashboard verification are active. Matching steps will be added as their pages are captured." + } + }, + "matching_product_code": "", + "matching_product_code_description": "Legacy fallback only. Bid transfer and initiate matching use IDAS.active_selection.product_code.", + "matching_type": "Provisional Match", + "source_types": [ + "Green", + "Conventional", + "HPDAM" + ], + "bid_transfer_markets": [ + "GDAM", + "DAM", + "HPDAM" + ], + "accepted_bid_transfer_statuses": [ + "ACCEPTED", + "PARTIALLY_ACCEPTED", + "NO_BIDS" + ], + "initiation_mode": "provisional_match", + "timing": { + "bid_transfer_timeout_seconds": 30, + "check_status_interval_seconds": 2, + "check_status_timeout_seconds": 120, + "flow_request_success_timeout_seconds": 30, + "download_timeout_seconds": 60 + }, + "downloads": { + "download_folder_name": "matching_files" + } + }, + "available_product_codes": [ + "IDAS", + "IDAS1", + "IDAS2", + "IDAS3", + "IDAS4" + ], + "available_trading_markets": [ + "GDAM", + "DAM", + "HPDAM" + ], + "available_bidding_screens": [ + "single_bid", + "block_bid", + "single_and_block_bid" + ] + }, + "RTM": { + "description": "Placeholder section for RTM product-wise regression config.", + "active_selection": { + "description": "Fill this section when RTM regression flows are added.", + "product_code": "RTM", + "product_code_options": [ + "RTM" + ], + "trading_market": "", + "trading_market_options": [], + "bidding_screen": "single_bid", + "bidding_screen_options": [ + "single_bid", + "block_bid" + ] + }, + "defaults": { + "description": "Fallbacks used if active_selection is incomplete.", + "market_code": "NATIONAL", + "product_type": "RTM", + "product_code": "RTM", + "trading_market": "", + "bidding_screen": "single_bid" + }, + "flows": { + "description": "Supported RTM regression flow groups.", + "e2e": { + "description": "Complete regression flow across all enabled modules." + }, + "bidding": { + "description": "Run only bidding flows." + }, + "matching": { + "description": "Run only matching flows." + }, + "reports": { + "description": "Run only report download flows." + }, + "order_book": { + "description": "Run only order book flows." + }, + "trade_book": { + "description": "Run only trade book flows." + } + }, + "setup": { + "description": "Product-level setup controls.", + "session": { + "enabled": false + }, + "token": { + "enabled": false + } + }, + "bidding": { + "description": "RTM bidding config placeholder.", + "entry_method": {}, + "data_paths": {}, + "selection": {}, + "timing": {}, + "excel_layout": {}, + "entries": [] + }, + "matching": { + "description": "RTM matching config placeholder.", + "matching_product_code": "", + "matching_type": "", + "source_types": [], + "initiation_mode": "", + "timing": {}, + "downloads": {} + }, + "available_product_codes": [ + "RTM" + ], + "available_trading_markets": [], + "available_bidding_screens": [ + "single_bid", + "block_bid" + ] + } +} diff --git a/data/idas/README.md b/data/idas/README.md new file mode 100644 index 0000000..d947d5a --- /dev/null +++ b/data/idas/README.md @@ -0,0 +1,31 @@ +# IDAS Input Data + +Keep bidding input Excel files here instead of embedding bid values in config. + +## Suggested structure + +- `data/idas/bidding/single_bid/` +- `data/idas/bidding/block_bid/` + +## Naming + +Name each file by TAC ID when possible: + +- `GUJC10010001.xlsx` +- `CHHC10110001.xlsx` +- `APTS10240001.xlsx` + +## Config link + +The run manifest lives in: + +- `config/products/idas.json` + +Each manifest entry points to: + +- `tm_id` +- `tac_id` +- `excel_file` +- optional `sheet_name` + +This lets the automation run one TAC after another without hardcoding bid data into config. diff --git a/data/idas/bidding/README.md b/data/idas/bidding/README.md new file mode 100644 index 0000000..5c483d3 --- /dev/null +++ b/data/idas/bidding/README.md @@ -0,0 +1,61 @@ +# Bidding Input Files + +Place Excel input files for IDAS bidding here. + +- `single_bid/` for normal single-bid templates +- `block_bid/` for future block-bid templates +- `json/` for direct structured bid payloads +- `uploads/` for files sent through the application's `Upload` flow +- `folder_uploads/` for folder-upload scenarios grouped by market, screen, side, and TM ID + +For folder uploads, `folder_upload_sides` can be set to `Buy`, `Sell`, or `BUYSELL`. +`BUYSELL` uses a `BUYSELL` side folder when it exists; otherwise it runs the `Buy` +and `Sell` side folders for the selected market and screen. + +Large folder uploads can take longer than normal grid entry. The regression config +uses `folder_upload_progress_timeout_seconds` for the maximum upload wait and +`folder_upload_keep_alive_interval_seconds` for periodic browser activity during +that wait. +If `folder_upload_continue_when_last_file_complete` is true, the runner can +continue when the last uploaded file reaches 100%, even if earlier rows remain +below 100% because of errors such as bid-data validation. Incomplete rows are +logged with file name, progress, and remarks. + +Folder uploads run sequentially by default. Set `folder_upload_execution_mode` to +`parallel` to run multiple TM folders at the same time, using +`folder_upload_max_parallel_sessions` to control the number of browsers and +`folder_upload_parallel_login_stagger_seconds` to stagger manual CAPTCHA entry. +Use `folder_upload_parallel_captcha_wait_seconds` to control how long each +parallel browser waits before submitting login after you type the CAPTCHA. +Parallel mode uses one login per browser slot. With exchange login it uses +`users.exchange_parallel`. With trading-member login it uses the entries under +`users.trading_members`, and each member must list the TM folders it can upload +with `folder_upload_tm_ids` or a matching `member_id`. Trading-member runs skip +the TM dropdown because the application locks the TM to the logged-in user. + +Config can now choose one of these bid-entry methods: + +- `excel_grid` +- `json_grid` +- `file_upload` +- `folder_upload` + +For block-bid bulk upload, set `active_selection.bidding_screen` to `block_bid` +and choose `IDAS > bidding > entry_method > block_bid > block_upload_method` as +`file_upload` or `folder_upload`. Folder upload uses the same grouped directory +shape, with `Block` instead of `Single`, for example: + +```text +data/idas/bidding/folder_uploads/DAM/Block/BUYSELL/C1032 +``` + +Set `active_selection.bidding_screen` to `single_and_block_bid` to run both +folder-upload screens in one browser login. The runner processes `single_bid` +first, then navigates directly to block bulk upload for `block_bid`. + +After the block folder is selected, the runner accepts the browser upload +confirmation when WebDriver exposes it, then waits until the page success message +changes to `FOLDER_UPLOADED_SUCCESSFULLY`. Timing is logged from file selection +through the success message. For large load-test folders, tune +`block_upload_success_timeout_seconds`, `block_upload_success_poll_seconds`, and +`block_upload_progress_log_interval_seconds` in `runtime.json`. diff --git a/data/idas/bidding/block_bid/.gitkeep b/data/idas/bidding/block_bid/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/idas/bidding/block_bid/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/idas/bidding/json/.gitkeep b/data/idas/bidding/json/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/idas/bidding/json/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/idas/bidding/single_bid/.gitkeep b/data/idas/bidding/single_bid/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/idas/bidding/single_bid/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/idas/bidding/single_bid/GUJC10010001 - Copy.xlsx b/data/idas/bidding/single_bid/GUJC10010001 - Copy.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..42e941818da256a089e8b988cb7a353a4a0ea213 GIT binary patch literal 26623 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 Z0aoV5tH7_mz+mj)zc!HH8II7|{{akrtbPCh literal 0 HcmV?d00001 diff --git a/data/idas/bidding/single_bid/GUJC10010001.xlsx b/data/idas/bidding/single_bid/GUJC10010001.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..42e941818da256a089e8b988cb7a353a4a0ea213 GIT binary patch literal 26623 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 Z0aoV5tH7_mz+mj)zc!HH8II7|{{akrtbPCh literal 0 HcmV?d00001 diff --git a/data/idas/bidding/uploads/.gitkeep b/data/idas/bidding/uploads/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/idas/bidding/uploads/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/idas/bidding/uploads/CHHC10050003.xlsx b/data/idas/bidding/uploads/CHHC10050003.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..896a62d556062de3b127692dc51439590a3731e7 GIT binary patch literal 10458 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 H-sS!W$RbwN literal 0 HcmV?d00001 diff --git a/data/idas/bidding/uploads/GUJC10030013.xlsx b/data/idas/bidding/uploads/GUJC10030013.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..635aef70077443d9ca3404b2b8290b4beb0b9847 GIT binary patch literal 10459 zcmeHtby$>J*Z0sVIrJbc9U|Q#2+|!=ip{NQp=YNJvO`jFfaqN+U>$lt}k` zqvyQG<2m2;Uf1{U^W0qQweP*xZ|ybfUVH7-P)0)sgD^qZAP|TFWWJSZWr_j<#h`;g zq#$fmqdSfc2&e;Dp0a!aG=OR^NOC@pi}=!dgO?Ke_}T5U!qH#0E% zgl^7vt%-YW3@#f5ZS5qi`SD+1f&!N`+X zpo5?QU5F5{jJqALo1=@Zxuc`))wq?dGp9byN93DcdD-&aD=hVbk(k~5%|{MvQ~3}N=b@t&p*{YLk2_lc!_+2vaaNI1ES3HKa4HBf z5hoR%9v3ql1h$KUf0N2~usRC*COA{Ue@LTWtcZ8Yy2Q!Qk z5=OY)j`!CX^>mvI);L*i#`V=Z^FRi)B|zFG$1Q>R1&&oQ^UoGU!ktBCdOcvx{!?a( zozKjpqAOkKtGM?Edfv8NC!>~&^xbW$;)8r3P{e*85DUdInxKMXynUv!!<2T!ch>hohS%h?dE3)W z49Cp=B8@uJ+*&jS$wr~EXN!7E#UO@Q2RbRM@kV$Vhd(ziKEsFqkS8LVF+w^&>q~c%h0$y7WcrY~x8wK#*pHco-AO17qqW}{ypbz|KKZRPV%58i^4R{x!d~PYO zWW z?Y6}+9Q$|s(Xh=oi>?PMV33k*d|fZt#3M+Clh#Sab#bMEMOKd3Sg1m?av0_I>V`uE zY(DTk=C^?l()IumueWl^|Ky4GMp0m8O$~iAJ8b!~?#}c9W_q9p-Ow%-&OM219-yHw zqYRu!E%FE0-`}0VR6Rb}wq%{)FGC8HufG+P^bSGLhjuB@`LV~rmot-)y4#--vZp=l zP1E1GdiS#t7sOfUH0 z<+8I#xay{QT4toHE6qnR?eMke#8;T=S-p#{eYnHK#@nKNt`t}8y_L=b&(Y1Pw+0)2 z))m(=7M=O0!24t3A}Ujz?gG6~6TO$J9u@i3=PlVqWAct;b46cG4(F@c=^oXvPeltO zE6OJ~BHMKvicbf;v>i*?S$T_VRE>;_`hDk4H8lygjCO_jF5idtZ8N!-BV@h;w5dk^5E5+tm-#FBxq_fn}QBeP^{X+h51w;Mb?|# z#y)x8RtKtpk_<#n73SZ1IAa<)pil!N?B%TzEjc#u>aj%`q}g;nAl4gd1_6qXxM9-IL6)6*snUy9@tJ>{T+byqKS?X4@`x zIAgl71{kFwI8*TpEiWcx#-Ron$HyOd<7EqfauEV#=zt7t@Jm3L6=u=(ut?a%24gl= zr5C+c^6l18#vH0jZ#I|Y+asZjc~q4?Y;noA7eg7}QdRo0RRpdOLiGmEGx~6+q~i^5 zUxxB?g6QK6QBY$0aEZb!^qOUuD^Rf+lgKoPx(h);iZBd_9KgZ=u;2hJ8eveqW?8QC zWHh|8E*k=_HpT+cQlf~$mA?w3=q`UNd@9j{g7>r!*FX`FFUwegN=}mUu?vzP9cl|V ze~dh zvj`WkS+^1m5M1F<$f}M?;C9;UXUSXf9_uQ~Wg(I5&kyW)o^F^;5amPL#ay#q$-F4` z82jW^%P4C}e1bsMpZ5BT_1 z#z?}ejJ}qb0R}PqZiXHR5e6jlc|x`nEJ>6SSL9AEx2SB=x<(f

a(8LC1LYdL#kz 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%0AUd^C2#P z!=ZF`6We6kq>Nr!;U`>7cHC8;t817T;AwHKVM5ruwPzas;IShAQ3sf`YW7++lJ*yw z7>1sI;7EVsSg&w_T#~=={A?GH`J?V%WIn4#-ci+mgS?y8#XpMB4a6`ENiH@hef(W6 zAtR?CZpIubepM`AF$$Qf=y@(I|H^VdOmsv`WbYRG!$#)~Ke#Xq?_OzC*u_r*Xf)*N z#(bp_Di6~?Gy>|*cxQ{wwjC~c|1|=^l69)JfF&Rd7*&aZ?RX2vM;b1UPH^5wjxNwY zu0i~F7alO3cbukjD<3#`4&(Apw^vH#W9XA=v=@@1M0%Ruc-^yl_QNr9$P2F#hcSZo z&KdakY;yp?g5(*T9nWa^!wO6smVJw;tlBq_8e1Lt8y=q>N-eh^$P z3Q!oa;VZ_ZagQsHUq_{VH3ELrOGuye?A;v?=W*)pSJKInW06f2lJieu@RglZ_&UFV z@b~59EVJiHwfquc=-5#iC^$Wa5QUo;J7Rpp7ak9FU}}a)(^Iw8T6pz5tFsH8y(AT~ zIHDdA`FXeV2)bKJTM$x3?b?p=qHaE(=0UOG@}Sva=Z(&a&!x>FhBZ6LPao+WPRj#@ zVL_V#hB@?|P1mlAxr)TzMwcR!e0-#rHiA60dDioUIf5^$mAP2)?nrz`#Q5j)h|@bg zBQj~)XX<^jE~Gc^$tNJnvYAten^2Y$PrqBnoBgXJvJ>ui_yCT00XQPjua5Z8$?3m- z2$<}T=T+P<*HI)N<6>v@+?17QLzR0&Za`x>Y*6dEM3tFPVi^|}hsx=?TU#&HSY1C+ z=rblOOL2!G=ee;3ksX(b0oj7JhZll}pP5u?@sYo*9ZGw!l!V4Em_~64bgLTMAS^jA z>E6@okg67(5;VuRiDMzPs+?2>J%+VW2R_#VmYmbl?@bUrA?eA2S+Zev8Y*83a%XKG zee)sH=`Bhb_p<0(#1aZ!LEXJQtQ~~P%q{$PXdf~cX+A)9_w`qv z`?}8|zlEsx^PAEHmTVk^+GwVrvvfA}x(aK!L5#kqcI(=|$L-zVG z8@J081yl0%Gd{=pb2Wm3$_>oa{{0%+pP%SY`Y)HtG?f1?;O`e+{sdmpmuMi+FV|mw z2mXEu;4kPqU~Tl|_ptXXJdWuX_#Z(~Lm313x*!lP@aGQ%O8FaCZ~p`Lwuf&3 literal 0 HcmV?d00001 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e984976 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,14 @@ +[pytest] +markers = + session: session creation + trading_token: trading token creation + fake_activity: random menu and dropdown activity without saving + regression: product-wise end-to-end regression flow + exchange: flow executed with exchange login + trading_member: flow executed with trading member login + bidding: bidding flow coverage + matching: matching flow coverage + mco: MCO market-coupling portal tests + reports: report download coverage + order_book: order book coverage + trade_book: trade book coverage diff --git a/reports/html/.gitkeep b/reports/html/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/reports/html/.gitkeep @@ -0,0 +1 @@ + diff --git a/reports/logs/.gitkeep b/reports/logs/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/reports/logs/.gitkeep @@ -0,0 +1 @@ + diff --git a/reports/screenshots/.gitkeep b/reports/screenshots/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/reports/screenshots/.gitkeep @@ -0,0 +1 @@ + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3dde252 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +# Core test runner +pytest==8.4.1 +pytest-html==4.2.0 + +# Browser automation +selenium==4.34.2 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/browser.py b/tests/browser.py new file mode 100644 index 0000000..f692ec3 --- /dev/null +++ b/tests/browser.py @@ -0,0 +1,14 @@ +from selenium import webdriver + + +def create_chrome_driver(): + + options = webdriver.ChromeOptions() + options.add_argument("--disable-background-timer-throttling") + options.add_argument("--disable-backgrounding-occluded-windows") + options.add_argument("--disable-renderer-backgrounding") + options.add_argument("--disable-features=CalculateNativeWinOcclusion") + options.add_argument("--disable-hang-monitor") + options.add_argument("--disable-popup-blocking") + + return webdriver.Chrome(options=options) diff --git a/tests/config_reader.py b/tests/config_reader.py new file mode 100644 index 0000000..b542358 --- /dev/null +++ b/tests/config_reader.py @@ -0,0 +1,62 @@ +import json +from pathlib import Path + + +CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.json" + + +def load_config(): + + with CONFIG_PATH.open("r", encoding="utf-8") as f: + return json.load(f) + + +def get_step_delay_seconds(default=5): + + config = load_config() + return config.get("step_delay_seconds", default) + + +def get_login_captcha_wait_seconds(default=20): + + config = load_config() + return config.get("login_captcha_wait_seconds", default) + + +def get_login_post_submit_wait_seconds(default=10): + + config = load_config() + return config.get("login_post_submit_wait_seconds", default) + + +def is_setup_session_enabled(default=True): + + config = load_config() + return config.get("setup", {}).get("run_session", default) + + +def is_setup_token_enabled(default=True): + + config = load_config() + return config.get("setup", {}).get("run_token", default) + + +def is_combined_setup_enabled(): + + config = load_config() + setup = config.get("setup", {}) + return setup.get("run_session", True) and setup.get("run_token", True) + + +def parse_product_codes(value): + + if value is None: + return [] + + if isinstance(value, str): + return [code.strip() for code in value.split(",") if code.strip()] + + if isinstance(value, list): + return [str(code).strip() for code in value if str(code).strip()] + + return [str(value).strip()] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..64d18c5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,242 @@ +import pytest +import os +import json + +from tests.browser import create_chrome_driver + + +def get_run_id(config): + + run_id = getattr(config, "_pxil_run_id", None) + if run_id: + return run_id + + 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) + if name.startswith(f"{run_prefix}_") + ] + + if not existing: + next_number = 1 + else: + numbers = [ + int(name.split("_")[-1]) + for name in existing + if name.split("_")[-1].isdigit() + ] + next_number = max(numbers) + 1 + + run_id = f"{run_prefix}_{next_number:03d}" + config._pxil_run_id = run_id + return run_id + + +def pytest_configure(config): + + run_id = get_run_id(config) + + if config.pluginmanager.hasplugin("html"): + reports_dir = os.path.abspath(os.path.join("reports", "html")) + os.makedirs(reports_dir, exist_ok=True) + config.option.htmlpath = os.path.join(reports_dir, f"{run_id}.html") + config.option.self_contained_html = True + + +# ----------------------------- +# WebDriver Fixture +# ----------------------------- +@pytest.fixture +def driver(): + + driver = create_chrome_driver() + driver.maximize_window() + + yield driver + + driver.quit() + + +# ----------------------------- +# Generate Incremental Test ID +# ----------------------------- +@pytest.fixture +def test_context(request): + + test_id = get_run_id(request.config) + test_name = request.node.name + + folder = os.path.abspath( + os.path.join("reports", "screenshots", test_id, test_name) + ) + log_folder = os.path.abspath( + os.path.join("reports", "logs", test_id, test_name) + ) + + os.makedirs(folder, exist_ok=True) + os.makedirs(log_folder, exist_ok=True) + + print(f"\nTEST ID: {test_id}") + + return { + "test_id": test_id, + "folder": folder, + "log_folder": log_folder, + "steps": [], + "extra_folders": [], + } + + +# ----------------------------- +# Attach Screenshot Folder Link +# ----------------------------- +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item, call): + + outcome = yield + report = outcome.get_result() + + if report.when == "call": + + context = item.funcargs.get("test_context", None) + + if context: + + folder = context["folder"] + steps = context.get("steps", []) + + if report.failed: + driver = item.funcargs.get("driver", None) + failure_index = len(steps) + 1 + failure_name = "failure_state" + failure_filename = f"{failure_index:02d}_{failure_name}.png" + failure_path = os.path.join(folder, failure_filename) + + if driver: + try: + driver.save_screenshot(failure_path) + steps.append( + { + "index": failure_index, + "name": failure_name, + "path": failure_path, + "status": "failed", + } + ) + except Exception: + pass + + if item.config.pluginmanager.hasplugin("html"): + + from pytest_html import extras + + extras_list = getattr(report, "extras", []) + + for step in steps: + extras_list.append( + extras.url( + _as_file_url(step["path"]), + name=f"Step {step['index']:02d} [{step['status']}] {step['name']}" + ) + ) + + if steps: + extras_list.append( + extras.html(_build_steps_html(steps)) + ) + + extras_list.append( + extras.url(_as_file_url(folder), name="Screenshots Folder") + ) + + log_folder = context.get("log_folder") + if log_folder: + extras_list.append( + extras.url(_as_file_url(log_folder), name="Logs Folder") + ) + + for extra_folder in context.get("extra_folders", []): + extras_list.append( + extras.url( + _as_file_url(extra_folder["path"]), + name=extra_folder["name"] + ) + ) + + report.extras = extras_list + + +def _as_file_url(path): + + absolute_path = os.path.abspath(path).replace("\\", "/") + return f"file:///{absolute_path}" + + +def _build_steps_html(steps): + + rows = [] + for step in steps: + step_url = _as_file_url(step["path"]) + rows.append( + "" + f"{step['index']:02d}" + f"{step['status'].capitalize()}" + f"{step['name']}" + f"Open Screenshot" + "" + ) + + return ( + "

" + "

Step Details

" + "" + "" + f"{''.join(rows)}" + "
#StatusStepScreenshot
" + "
" + ) + + +def _get_run_prefix(config): + + invocation_args = " ".join(str(arg) for arg in config.invocation_params.args).lower() + + if "tests/setup/sessions" in invocation_args or "test_session.py" in invocation_args: + return "PXIL_SESSION" + + if "tests/setup/tokens" in invocation_args or "test_trading_token.py" in invocation_args: + return "PXIL_TOKEN" + + runtime_path = os.path.abspath(os.path.join("config", "runtime.json")) + if os.path.exists(runtime_path): + try: + with open(runtime_path, "r", encoding="utf-8") as runtime_file: + runtime_config = json.load(runtime_file) + + execution = runtime_config.get("execution", {}) + product_family = execution.get("product_family", "regression").upper() + flow_scope = execution.get("flow_scope", "run").upper() + return f"{product_family}_{flow_scope}" + except Exception: + pass + + if "matching" in invocation_args: + return "REGRESSION_MATCHING" + + if "bidding" in invocation_args: + return "REGRESSION_BIDDING" + + if "reports" in invocation_args: + return "REGRESSION_REPORTS" + + if "order_book" in invocation_args: + return "REGRESSION_ORDER_BOOK" + + if "trade_book" in invocation_args: + return "REGRESSION_TRADE_BOOK" + + return "PXIL_RUN" diff --git a/tests/file_generation.py b/tests/file_generation.py new file mode 100644 index 0000000..0d955d8 --- /dev/null +++ b/tests/file_generation.py @@ -0,0 +1,393 @@ +""" +generate_bid_json.py +==================== +Bulk JSON generator for bid submission payloads. + +FOLDER STRUCTURE (input_root): + input_root/ + / e.g. GDAM | DAM | HPDAM + / e.g. Single | Block + / e.g. Buy | Sell + / e.g. C1001 → becomes userId AND tmId + .xlsx ← one file per TAC / bid sheet + +SEGMENT → JSON "segment" MAPPING: + GDAM → "GREEN" + DAM → "CONVENTIONAL" + HPDAM → "HPDAM" + +SEGMENT → JSON order "source" MAPPING: + GDAM → "SL" + DAM → "CO" + HPDAM → "HP" + +ORDER SIDE → JSON "orderSide" MAPPING: + Buy → "B" + Sell → "S" + +ORDER TYPE → JSON "orderType" MAPPING: + Single → "N" + Block → "B" + +EXCEL LAYOUT (every .xlsx): + Row 1 : "Select Source" | "Order 1" | "Order 2" ... "Order N" + Row 2 : "