diff --git a/.gitignore b/.gitignore
index 245807d..e255e4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# Virtual environments
venv/
.venv/
+.venv-playwright/
+reports/playwright/
+test-results/
# Python cache
__pycache__/
diff --git a/PLAYWRIGHT_MIGRATION.md b/PLAYWRIGHT_MIGRATION.md
new file mode 100644
index 0000000..da360af
--- /dev/null
+++ b/PLAYWRIGHT_MIGRATION.md
@@ -0,0 +1,82 @@
+# Playwright migration review
+
+## Preserved Selenium baseline
+
+- Reviewed on 2026-09-08; working tree was clean before this document was added.
+- Current branch: `development`.
+- Backup branch: `backup/selenium-working-2026-09-08`, created locally and pushed to `origin`.
+- Baseline commit: `4749efe45f47587e6d6aec228a4818576cb666d9`.
+- HEAD, the local backup, and its remote-tracking reference were verified to match.
+- The backup preserves tracked source, configuration, and committed input files. Ignored virtual environments and generated reports are not included.
+- No browser tests were executed during this review; working behavior is the user's reported baseline.
+
+To return to the baseline, first commit or stash any migration changes, then use:
+
+```powershell
+git switch backup/selenium-working-2026-09-08
+```
+
+Keep the backup branch unchanged. Start implementation on a separate branch such as `migration/playwright-python`, created from `development`.
+
+## Recommended stack
+
+Keep Python, pytest, pytest-html, existing markers, and JSON/Excel inputs. Use Playwright's synchronous Python API with the official pytest-playwright plugin. This retains the existing business logic and test runner while changing the browser layer. A TypeScript rewrite would also require porting the data readers, configuration, and reporting, without a demonstrated need here.
+
+Start with headed Chromium because exchange and trading-member logins currently require manual CAPTCHA input. Select a compatible dependency set, verify it in a separate virtual environment, and pin the validated versions. Install Chromium through Playwright as well as installing the Python packages.
+
+References: [installation](https://playwright.dev/python/docs/intro), [pytest fixtures and options](https://playwright.dev/python/docs/test-runners).
+
+## Repository-specific changes
+
+| Existing area | Recommended migration |
+| --- | --- |
+| `tests/browser.py`, `tests/conftest.py` | Replace driver creation with plugin-managed browser/context/page fixtures for migrated tests. Keep Selenium's driver fixture while both suites coexist. Start with one isolated context per test. |
+| `tests/step_actions.py`, `tests/reporting.py` | Preserve step names and report metadata; use `page.screenshot(path=...)`. Adapt the failure hook, which currently only looks for `driver`. Add failure traces and include their paths in reports. |
+| `tests/helpers.py`, `tests/regression/auth.py` | Port setup, exchange, trading-member, and MCO login separately. Wait for a successful authenticated page. Replace fixed CAPTCHA sleep with a bounded wait for manual entry and then verify login. Playwright does not remove the CAPTCHA requirement. |
+| `tests/locators.py`, `trading_locators.py` | Keep stable IDs initially. Convert Selenium By tuples into Playwright locators. Scope broad XPath matches to the active form, modal, or grid to avoid ambiguous matches. |
+| `tests/regression/ui_helpers.py` | Rewrite Bootstrap and Select2 interactions around the visible dropdown and exact option. Select2 is a custom widget; native `select_option()` is appropriate only for actual select elements. Preserve code-to-label matching deliberately. |
+| `bidding/grid_actions.py` | Port the existing Handsontable integration through `page.evaluate()` if needed. Separate waiting for the instance from mutating it. Validate the complete matrix and submitted outcome; the present helper only checks the first displayed value after populating. |
+| `bidding/upload_actions.py`, `folder_upload_actions.py`, `block_bulk_upload_actions.py` | Use `set_input_files()` for file inputs; use directory input support where the actual element supports directory selection. Preserve progress, per-file status, logs, and failure handling. Register expected confirmation-dialog handling before the triggering action. |
+| `matching/actions.py` | Replace CDP download configuration and directory polling with an `expect_download()` block around the export action, then save the download with the existing source-specific naming convention. Preserve business-status polling and timeouts. |
+| `matching/bid_transfer_actions.py` | Preserve request/status correlation and explicit rejection handling. Browser action readiness is not proof of successful bid transfer. |
+| `tests/regression/config_reader.py`, `tests/config_reader.py`, input manifests, JSON/Excel readers | Reuse browser-independent logic. Keep configuration schema and defaults stable during initial migration. |
+| `tests/file_generation.py` | Retain payload-generation logic and audit its separate dependency requirements when preparing the new environment. |
+
+Playwright auto-waits for action readiness and offers retrying assertions. Replace UI sleeps with visible/enabled/value/status assertions, but retain intentional business polling intervals. Avoid routinely replacing JavaScript click fallbacks with forced clicks; inspect overlays and locator scope first.
+
+References: [auto-waiting](https://playwright.dev/python/docs/actionability), [inputs and uploads](https://playwright.dev/python/docs/input), [downloads](https://playwright.dev/python/docs/downloads).
+
+## Parallel upload design
+
+`test_idas_exchange_bidding_flow.py` currently creates separate Chrome drivers in ThreadPoolExecutor workers and synchronizes each batch with a Barrier. It also assigns accounts per TM and staggers logins.
+
+First establish sequential Playwright upload parity. Do not share a Playwright instance, browser, context, or page across those threads: the Python API is not thread-safe. If retaining threads temporarily, each worker must create and close its own Playwright instance and browser.
+
+For independent scenarios, consider pytest-xdist processes with separate accounts and data. For coordinated uploads that must begin together, use a dedicated async orchestration flow with separate contexts and an async barrier/event mechanism; ordinary test parallelism does not preserve the existing start barrier. Keep the rest of the suite synchronous. Preserve barrier cancellation when a worker fails.
+
+Before concurrency, replace the current scan-and-increment report run numbering with collision-resistant run/worker identifiers. Preserve account-to-TM assignment and validate server-side session restrictions; browser-context isolation alone does not prevent account conflicts.
+
+Reference: [Python library threading guidance](https://playwright.dev/python/docs/library).
+
+## Suggested implementation sequence
+
+1. Create the migration branch and an isolated Playwright environment. Add a separate `tests/playwright/` tree with scoped fixtures and explicit run commands so migration tests are not accidentally executed alongside the old suite. Keep shared data utilities browser-independent.
+2. Port `test_idas_mco_login.py` as the small pilot: credentials, navigation, dashboard assertion, screenshot reporting, and failure trace. Then port exchange and trading-member login with their manual CAPTCHA steps.
+3. Port session creation, token activation, and combined setup. Preserve configuration-controlled skips and verify the created session/token state rather than relying on screenshots alone. Root session/token test files are skip-only compatibility stubs; migrate the implementations under `tests/setup/`.
+4. Port one single-bid flow, including Select2 filters, market-specific input matrix, submission, and observable success. Then cover each configured market and both login roles.
+5. Port individual file upload, batch upload, folder upload, and block bulk upload sequentially. Compare file counts, progress completion, statuses, and generated logs against the Selenium behavior.
+6. Port transfer and matching. Preserve rejection-stop behavior, provisional matching checks, flow generation, and exported files for Green, Conventional, and HPDAM. Validate downloaded content as well as file presence.
+7. Add parallel upload support using the design above. Verify distinct sessions, coordinated start if required, failure cleanup, and distinct artifacts.
+8. Revisit fake-activity/manual-run utilities, update `RUN_COMMANDS.md`, and remove Selenium dependencies only after required flows pass. Order-book, trade-book, and reports directories contain scaffolding; define new coverage separately from migration parity.
+
+During coexistence, run each suite using explicit paths. Preserve the existing combined-setup behavior; do not assume pytest test ordering will create prerequisites. Avoid automatic retries for state-changing bid/session/matching actions unless duplicate submissions are handled.
+
+## Completion criteria
+
+- Validate both implementations against controlled equivalent test data; running each against the same mutable state back-to-back can itself change the result.
+- Confirm session/token state, submitted bid data, upload success and failure outcomes, transfer rejection behavior, matching status, and downloaded content.
+- Confirm screenshots, logs, failure traces, manual CAPTCHA flow, and browser cleanup.
+- Require repeatable passing runs for every required configured flow before making Playwright the default.
+- Keep generated authentication state and traces out of Git; these can contain login/session data. Preserve the frozen backup without editing its historical configuration.
+
+This document is a migration proposal. No Selenium implementation or dependency files have been changed.
diff --git a/RUN_COMMANDS.md b/RUN_COMMANDS.md
index d14feb3..492baa5 100644
--- a/RUN_COMMANDS.md
+++ b/RUN_COMMANDS.md
@@ -384,3 +384,8 @@ pytest tests\regression -m reports -s --html=reports/html/reports_run.html --sel
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
```
+# Playwright session/token setup
+
+See [Playwright setup commands](playwright_tests/README.md) for login, session creation,
+token creation/activation, and combined setup on utilseastern. Use `-c playwright.ini`
+to select the new suite; commands below continue to describe the Selenium suite.
diff --git a/config.json b/config.json
index 9811e74..1677e83 100644
--- a/config.json
+++ b/config.json
@@ -1,5 +1,5 @@
{
- "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": {
@@ -24,22 +24,22 @@
"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",
+ "product_codes": "IDAS3",
+ "available_codes":",IDASTEST01,IDASTEST02,IDASTEST03,IDASTEST04,IDASTEST05,IDASTEST06,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",
+ "expiry_time": "22:30",
"time_sessions": {
"ORDER_NOR": {
"start_offset_minutes": 5,
- "end_time": "19:30"
+ "end_time": "21:30"
},
"ORDER_EXCH": {
- "start_time": "19:30",
- "end_time": "19:45"
+ "start_time": "21:30",
+ "end_time": "22:45"
},
"TRADE": {
- "start_time": "19:45",
- "end_time": "19:50"
+ "start_time": "22:45",
+ "end_time": "22:50"
}
}
}
diff --git a/playwright.ini b/playwright.ini
new file mode 100644
index 0000000..1306830
--- /dev/null
+++ b/playwright.ini
@@ -0,0 +1,7 @@
+[pytest]
+testpaths = playwright_tests
+pythonpath = .
+addopts = -p playwright_tests.options -s --tb=short --headed --browser chromium --tracing retain-on-failure --screenshot only-on-failure --output reports/playwright/artifacts
+markers =
+ session: session creation
+ trading_token: token creation and activation
diff --git a/playwright_checks/test_setup_helpers.py b/playwright_checks/test_setup_helpers.py
new file mode 100644
index 0000000..0391934
--- /dev/null
+++ b/playwright_checks/test_setup_helpers.py
@@ -0,0 +1,156 @@
+from datetime import datetime
+
+import pytest
+
+from playwright_tests.planning import build_plan
+from playwright_tests.setup_page import SetupPage
+
+
+@pytest.fixture
+def config():
+ return {
+ "setup": {"run_session": True, "run_token": True},
+ "trading_token_idas": {"product_codes": "IDAS,IDASTEST01", "market_code": "NATIONAL"},
+ }
+
+
+def test_combined_plan_dates_and_products(config):
+ plan = build_plan(config, now=datetime(2026, 9, 8, 10, 0))
+ assert plan["session"] == ("08/09/2026 10:07", "08/09/2026 20:30")
+ assert [t["product_code"] for t in plan["tokens"]] == ["IDAS", "IDASTEST01"]
+ assert plan["tokens"][0]["delivery_date"] == "09/09/2026"
+ assert plan["tokens"][0]["rows"][0] == ("ORDER_NOR", "10:10", "18:30")
+
+
+def test_login_ignores_expired_mutation_settings(config):
+ assert build_plan(config, "login", now=datetime(2026, 9, 8, 23)) == {"session": None, "tokens": []}
+
+
+def test_token_override_does_not_modify_config(config):
+ plan = build_plan(config, "token", "IDASTEST02", datetime(2026, 9, 8, 10))
+ assert plan["session"] is None
+ assert [t["product_code"] for t in plan["tokens"]] == ["IDASTEST02"]
+ assert config["trading_token_idas"]["product_codes"] == "IDAS,IDASTEST01"
+
+
+@pytest.mark.parametrize("flow, message", [("session", "Session end_time"), ("token", "expiry_time")])
+def test_reject_expired_schedule(config, flow, message):
+ with pytest.raises(ValueError, match=message):
+ build_plan(config, flow, now=datetime(2026, 9, 8, 23))
+
+
+def test_reject_inverted_time_session(config):
+ config["trading_token_idas"]["time_sessions"] = {"ORDER_NOR": {"start_time": "19:00", "end_time": "18:30"}}
+ with pytest.raises(ValueError, match="ORDER_NOR"):
+ build_plan(config, "token", now=datetime(2026, 9, 8, 10))
+
+
+def test_save_waits_for_delayed_confirmation(page, tmp_path):
+ page.set_content('''