Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d0d786b2 | ||
|
|
d681002b0f | ||
|
|
eb8a831edb | ||
|
|
2454ca3180 | ||
|
|
771b755ca1 | ||
|
|
d9218709f0 | ||
|
|
385a9e22cc | ||
|
|
24f2c1fe6a | ||
|
|
631f64b84f | ||
|
|
c42cc83aa1 | ||
|
|
d6c1f7a3d3 | ||
|
|
106fc29c6a | ||
|
|
0312a542a2 | ||
|
|
4218f8572d | ||
|
|
d89ebdf869 | ||
|
|
f0496b3d67 |
@@ -0,0 +1,395 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "PXIL Automation Runtime",
|
||||
"output": ".archify/architecture-pxil-runtime-20260929-151855/pxil-runtime.html",
|
||||
"quality_profile": "showcase",
|
||||
"locale": "en",
|
||||
"repository": {
|
||||
"url": "https://git.tejasshasane.xyz/tejasshasane/pxil-selenium-automation.git",
|
||||
"revision": "5ab89ce0059525b31558a1fa82251a2a6f610dd7",
|
||||
"link_mode": "local-only"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "runner",
|
||||
"type": "backend",
|
||||
"label": "pytest runner",
|
||||
"sublabel": "Selection + fixture lifecycle",
|
||||
"pos": [
|
||||
60,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 37,
|
||||
"end_line": 62
|
||||
},
|
||||
{
|
||||
"path": "pytest.ini",
|
||||
"line": 1,
|
||||
"end_line": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flows",
|
||||
"type": "backend",
|
||||
"label": "Automation flows",
|
||||
"sublabel": "Setup / bidding / matching",
|
||||
"pos": [
|
||||
360,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 54,
|
||||
"end_line": 90
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_exchange_matching_flow.py",
|
||||
"line": 76,
|
||||
"end_line": 156
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 13,
|
||||
"end_line": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "browser",
|
||||
"type": "frontend",
|
||||
"label": "Chrome + WebDriver",
|
||||
"sublabel": "Selenium browser dependency",
|
||||
"pos": [
|
||||
680,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/browser.py",
|
||||
"line": 1,
|
||||
"end_line": 14
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/auth.py",
|
||||
"line": 46,
|
||||
"end_line": 86
|
||||
}
|
||||
],
|
||||
"tag": "Manual CAPTCHA for Pratyay"
|
||||
},
|
||||
{
|
||||
"id": "pratyay",
|
||||
"type": "external",
|
||||
"label": "Pratyay portal",
|
||||
"sublabel": "Exchange / member UI",
|
||||
"pos": [
|
||||
970,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 68,
|
||||
"end_line": 90
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_exchange_matching_flow.py",
|
||||
"line": 47,
|
||||
"end_line": 59
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "config",
|
||||
"type": "database",
|
||||
"label": "Configuration",
|
||||
"sublabel": "JSON + role credentials",
|
||||
"pos": [
|
||||
360,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/config_reader.py",
|
||||
"line": 6,
|
||||
"end_line": 32
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/config_reader.py",
|
||||
"line": 5,
|
||||
"end_line": 29
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/config_reader.py",
|
||||
"line": 121,
|
||||
"end_line": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inputs",
|
||||
"type": "database",
|
||||
"label": "Bid input files",
|
||||
"sublabel": "Excel / JSON / upload folders",
|
||||
"pos": [
|
||||
360,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/input_manifest.py",
|
||||
"line": 4,
|
||||
"end_line": 28
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/json_reader.py",
|
||||
"line": 5,
|
||||
"end_line": 35
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 217,
|
||||
"end_line": 249
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "reporting",
|
||||
"type": "backend",
|
||||
"label": "Evidence reporting",
|
||||
"sublabel": "Step capture + pytest hooks",
|
||||
"pos": [
|
||||
60,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/step_actions.py",
|
||||
"line": 10,
|
||||
"end_line": 40
|
||||
},
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 104,
|
||||
"end_line": 175
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "artifacts",
|
||||
"type": "database",
|
||||
"label": "Run artifacts",
|
||||
"sublabel": "HTML / screenshots / logs",
|
||||
"pos": [
|
||||
60,
|
||||
770
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 37,
|
||||
"end_line": 48
|
||||
},
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 70,
|
||||
"end_line": 97
|
||||
},
|
||||
{
|
||||
"path": "tests/step_actions.py",
|
||||
"line": 21,
|
||||
"end_line": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mco",
|
||||
"type": "external",
|
||||
"label": "MCO portal",
|
||||
"sublabel": "Independent login-only test",
|
||||
"pos": [
|
||||
970,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_mco_login.py",
|
||||
"line": 13,
|
||||
"end_line": 36
|
||||
}
|
||||
],
|
||||
"tag": "Only when enabled"
|
||||
}
|
||||
],
|
||||
"boundaries": [
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "LOCAL AUTOMATION HOST | trusted code, inputs and credentials",
|
||||
"wraps": [
|
||||
"runner",
|
||||
"flows",
|
||||
"browser",
|
||||
"config",
|
||||
"inputs",
|
||||
"reporting",
|
||||
"artifacts"
|
||||
],
|
||||
"pad": 28
|
||||
},
|
||||
{
|
||||
"kind": "security-group",
|
||||
"label": "Browser process boundary",
|
||||
"wraps": [
|
||||
"browser"
|
||||
],
|
||||
"pad": 18
|
||||
},
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "REMOTE PORTALS | separate authentication",
|
||||
"wraps": [
|
||||
"pratyay",
|
||||
"mco"
|
||||
],
|
||||
"pad": 28
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows",
|
||||
"label": "run tests",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "ui-actions",
|
||||
"from": "flows",
|
||||
"to": "browser",
|
||||
"label": "UI commands",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "portal-session",
|
||||
"from": "browser",
|
||||
"to": "pratyay",
|
||||
"label": "login + UI",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "read-settings",
|
||||
"from": "flows",
|
||||
"to": "config",
|
||||
"label": "read settings"
|
||||
},
|
||||
{
|
||||
"id": "read-bids",
|
||||
"from": "flows",
|
||||
"to": "inputs",
|
||||
"label": "read bids"
|
||||
},
|
||||
{
|
||||
"id": "report-hooks",
|
||||
"from": "runner",
|
||||
"to": "reporting",
|
||||
"label": "test hooks"
|
||||
},
|
||||
{
|
||||
"id": "write-evidence",
|
||||
"from": "reporting",
|
||||
"to": "artifacts",
|
||||
"label": "write evidence"
|
||||
},
|
||||
{
|
||||
"id": "optional-mco",
|
||||
"from": "browser",
|
||||
"to": "mco",
|
||||
"label": "if enabled: login",
|
||||
"variant": "dashed"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "cyan",
|
||||
"title": "Primary path",
|
||||
"items": [
|
||||
"pytest runs selected tests; flow code controls Selenium and Chrome, which interacts with the remote Pratyay UI.",
|
||||
"Pratyay login fills credentials and pauses for manual CAPTCHA entry before submission (tests/regression/auth.py:46-122).",
|
||||
"Arrows describe control and I/O; results return to pytest. No direct backend API or database integration is asserted."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "violet",
|
||||
"title": "Flow selection and capabilities",
|
||||
"items": [
|
||||
"Root config.json selects setup products and session/token actions; config/runtime.json selects regression environment, role, product and flow.",
|
||||
"Bidding supports Excel/JSON grids, files and folders. Optional parallel folder workers create their own browsers (bidding flow:551-587).",
|
||||
"Matching requires the exchange role; rejected or unsupported bid-transfer status stops matching (matching flow:28-44, 123-135)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "Trust and external dependencies",
|
||||
"items": [
|
||||
"Boundaries show host/process and remote ownership, not verified network isolation or firewall policy.",
|
||||
"Credentials come from local JSON and are entered into remote login forms. Values and endpoint addresses are omitted.",
|
||||
"Chrome/WebDriver is an external runtime dependency. Pratyay and MCO server internals and transport guarantees are outside the observed source.",
|
||||
"MCO is an independent, enabled-only dashboard login test; no Pratyay-to-MCO backend connection is inferred."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "emerald",
|
||||
"title": "Evidence and scope",
|
||||
"items": [
|
||||
"StepActions requests browser screenshots; pytest hooks attach evidence and configure pytest-html. Reports live under reports/.",
|
||||
"Matching configures browser downloads separately; setup can save session backups. These details are kept off the main path.",
|
||||
"Snapshot: clean repository at 5ab89ce0059525b31558a1fa82251a2a6f610dd7. Sources use local-only links.",
|
||||
"Registered reports/order-book/trade-book markers are placeholders, not implemented runtime services (RUN_COMMANDS.md)."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"ok": true,
|
||||
"command": "browser-check",
|
||||
"evidenceKind": "automated-browser",
|
||||
"status": "pass",
|
||||
"visualReview": "not-requested",
|
||||
"provenance": "current",
|
||||
"deliveryReceiptId": "95e7d275-4f11-44f8-af68-086f2d8272bd",
|
||||
"artifact": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.html",
|
||||
"sha256": "3db6aa51a98c38d8aebafa9dd0682f3ea5c0ffd1d554c2ac6f000c94b73a193a",
|
||||
"bytes": 767845
|
||||
},
|
||||
"state": {
|
||||
"detail": "read",
|
||||
"motion": "still"
|
||||
},
|
||||
"chrome": {
|
||||
"status": "available",
|
||||
"executable": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
},
|
||||
"diagnostics": [],
|
||||
"containment": {
|
||||
"status": "pass",
|
||||
"policy": "fit-or-reader-declared-readable-vertical-scroll",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 920,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 777,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1000,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1058,
|
||||
"diagramWidth": 1028,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 780,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNonEdgeTextPx": 7.534201954397394,
|
||||
"minimumProjectedEdgeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1163,
|
||||
"diagramWidth": 1133,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNonEdgeTextPx": 8.303745928338762,
|
||||
"minimumProjectedEdgeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1476,
|
||||
"diagramWidth": 1426,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"themeStates": {
|
||||
"status": "pass",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"requestedTheme": "dark",
|
||||
"resolvedTheme": "dark",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"requestedTheme": "dark",
|
||||
"resolvedTheme": "dark",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"readability": {
|
||||
"status": "pass",
|
||||
"minimumProjectedNodeTextPx": 6,
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 920,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 777,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1000,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1058,
|
||||
"diagramWidth": 1028,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 780,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNonEdgeTextPx": 7.534201954397394,
|
||||
"minimumProjectedEdgeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1163,
|
||||
"diagramWidth": 1133,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNonEdgeTextPx": 8.303745928338762,
|
||||
"minimumProjectedEdgeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1476,
|
||||
"diagramWidth": 1426,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewerChrome": {
|
||||
"status": "pass",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 920,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 777,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1000,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1058,
|
||||
"diagramWidth": 1028,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 780,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNonEdgeTextPx": 7.534201954397394,
|
||||
"minimumProjectedEdgeTextPx": 6.697068403908795,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1163,
|
||||
"diagramWidth": 1133,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNonEdgeTextPx": 8.303745928338762,
|
||||
"minimumProjectedEdgeTextPx": 7.381107491856677,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1476,
|
||||
"diagramWidth": 1426,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 932
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"captures": {
|
||||
"status": "not-requested",
|
||||
"screenshots": [],
|
||||
"contactSheet": null
|
||||
},
|
||||
"sidecars": {
|
||||
"receipt": "pxil-runtime.browser-check.json",
|
||||
"files": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"receiptId": "95e7d275-4f11-44f8-af68-086f2d8272bd",
|
||||
"status": "current",
|
||||
"command": "deliver",
|
||||
"type": "architecture",
|
||||
"input": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\candidate.json",
|
||||
"output": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.html",
|
||||
"specification": {
|
||||
"sha256": "5255f87576eeb3afd6713d933790d481cf9e8034f24453367adc240aa958ccbc",
|
||||
"bytes": 20698
|
||||
},
|
||||
"artifact": {
|
||||
"sha256": "3db6aa51a98c38d8aebafa9dd0682f3ea5c0ffd1d554c2ac6f000c94b73a193a",
|
||||
"bytes": 767845
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"ok": true,
|
||||
"command": "finalize",
|
||||
"status": "pass",
|
||||
"type": "architecture",
|
||||
"quality": "showcase",
|
||||
"specification": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\candidate.json",
|
||||
"sha256": "5255f87576eeb3afd6713d933790d481cf9e8034f24453367adc240aa958ccbc",
|
||||
"bytes": 20698
|
||||
},
|
||||
"artifact": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.html",
|
||||
"sha256": "3db6aa51a98c38d8aebafa9dd0682f3ea5c0ffd1d554c2ac6f000c94b73a193a",
|
||||
"bytes": 767845
|
||||
},
|
||||
"gates": {
|
||||
"validate": "pass",
|
||||
"deliver": "pass",
|
||||
"check": "pass",
|
||||
"browser-check": "pass"
|
||||
},
|
||||
"diagnostics": [],
|
||||
"diagnosticSummary": {
|
||||
"total": 0,
|
||||
"shown": 0,
|
||||
"truncated": false
|
||||
},
|
||||
"evidence": {
|
||||
"receipt": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.finalize.json",
|
||||
"summaryReceipt": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.finalize-summary.json",
|
||||
"browserCheckReceipt": "D:\\Selenium Test\\.archify\\architecture-pxil-runtime-20260929-151855\\pxil-runtime.browser-check.json"
|
||||
},
|
||||
"update": {
|
||||
"status": "current",
|
||||
"installedVersion": "3.0.1",
|
||||
"availableVersion": "3.0.1",
|
||||
"releaseNotes": null,
|
||||
"checkedAt": "2026-09-29T09:50:05.829Z",
|
||||
"source": "cache",
|
||||
"noticeRequired": false,
|
||||
"noticeText": null
|
||||
},
|
||||
"visualReview": "not-requested",
|
||||
"durationMs": 10625
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,461 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "PXIL Runtime: Sessions, Tokens and Regression",
|
||||
"output": ".archify/architecture-pxil-setup-runtime-20260929-155341/pxil-setup-runtime.html",
|
||||
"quality_profile": "showcase",
|
||||
"locale": "en",
|
||||
"repository": {
|
||||
"url": "https://git.tejasshasane.xyz/tejasshasane/pxil-selenium-automation.git",
|
||||
"revision": "5ab89ce0059525b31558a1fa82251a2a6f610dd7",
|
||||
"link_mode": "local-only"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "runner",
|
||||
"type": "backend",
|
||||
"label": "pytest runner",
|
||||
"sublabel": "Selection + fixture lifecycle",
|
||||
"pos": [
|
||||
60,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 37,
|
||||
"end_line": 62
|
||||
},
|
||||
{
|
||||
"path": "pytest.ini",
|
||||
"line": 1,
|
||||
"end_line": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flows",
|
||||
"type": "backend",
|
||||
"label": "Automation flows",
|
||||
"sublabel": "Setup / bidding / matching",
|
||||
"pos": [
|
||||
360,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 54,
|
||||
"end_line": 90
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_exchange_matching_flow.py",
|
||||
"line": 76,
|
||||
"end_line": 156
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 13,
|
||||
"end_line": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "browser",
|
||||
"type": "frontend",
|
||||
"label": "Chrome + WebDriver",
|
||||
"sublabel": "Selenium browser dependency",
|
||||
"pos": [
|
||||
680,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/browser.py",
|
||||
"line": 1,
|
||||
"end_line": 14
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/auth.py",
|
||||
"line": 46,
|
||||
"end_line": 86
|
||||
}
|
||||
],
|
||||
"tag": "Manual CAPTCHA for Pratyay"
|
||||
},
|
||||
{
|
||||
"id": "pratyay",
|
||||
"type": "external",
|
||||
"label": "Pratyay portal",
|
||||
"sublabel": "Exchange / member UI",
|
||||
"pos": [
|
||||
970,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 68,
|
||||
"end_line": 90
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_exchange_matching_flow.py",
|
||||
"line": 47,
|
||||
"end_line": 59
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "config",
|
||||
"type": "database",
|
||||
"label": "Config + bid inputs",
|
||||
"sublabel": "JSON / Excel / upload folders",
|
||||
"pos": [
|
||||
360,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/config_reader.py",
|
||||
"line": 6,
|
||||
"end_line": 32
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/config_reader.py",
|
||||
"line": 5,
|
||||
"end_line": 29
|
||||
},
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/bidding/test_idas_exchange_bidding_flow.py",
|
||||
"line": 217,
|
||||
"end_line": 249
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "reporting",
|
||||
"type": "backend",
|
||||
"label": "Evidence reporting",
|
||||
"sublabel": "Step capture + pytest hooks",
|
||||
"pos": [
|
||||
60,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/step_actions.py",
|
||||
"line": 10,
|
||||
"end_line": 40
|
||||
},
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 104,
|
||||
"end_line": 175
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "artifacts",
|
||||
"type": "database",
|
||||
"label": "Run artifacts",
|
||||
"sublabel": "HTML / screenshots / logs",
|
||||
"pos": [
|
||||
60,
|
||||
770
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 37,
|
||||
"end_line": 48
|
||||
},
|
||||
{
|
||||
"path": "tests/conftest.py",
|
||||
"line": 70,
|
||||
"end_line": 97
|
||||
},
|
||||
{
|
||||
"path": "tests/step_actions.py",
|
||||
"line": 21,
|
||||
"end_line": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mco",
|
||||
"type": "external",
|
||||
"label": "MCO portal",
|
||||
"sublabel": "Independent login-only test",
|
||||
"pos": [
|
||||
970,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/regression/exchange/idas/matching/test_idas_mco_login.py",
|
||||
"line": 13,
|
||||
"end_line": 36
|
||||
}
|
||||
],
|
||||
"tag": "Only when enabled"
|
||||
},
|
||||
{
|
||||
"id": "sessions",
|
||||
"type": "backend",
|
||||
"label": "Trading session setup",
|
||||
"sublabel": "ME instance + times + Save",
|
||||
"tag": "When run_session = true",
|
||||
"pos": [
|
||||
360,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/sessions/test_session.py",
|
||||
"line": 24,
|
||||
"end_line": 64
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 21,
|
||||
"end_line": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tokens",
|
||||
"type": "backend",
|
||||
"label": "Trading token setup",
|
||||
"sublabel": "Per product code + Save",
|
||||
"tag": "When run_token = true",
|
||||
"pos": [
|
||||
360,
|
||||
800
|
||||
],
|
||||
"size": [
|
||||
190,
|
||||
74
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 150,
|
||||
"end_line": 180
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 212,
|
||||
"end_line": 326
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 328,
|
||||
"end_line": 342
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"boundaries": [
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "LOCAL AUTOMATION HOST | trusted code, inputs and credentials",
|
||||
"wraps": [
|
||||
"runner",
|
||||
"flows",
|
||||
"browser",
|
||||
"config",
|
||||
"reporting",
|
||||
"artifacts",
|
||||
"sessions",
|
||||
"tokens"
|
||||
],
|
||||
"pad": 28
|
||||
},
|
||||
{
|
||||
"kind": "security-group",
|
||||
"label": "Browser process boundary",
|
||||
"wraps": [
|
||||
"browser"
|
||||
],
|
||||
"pad": 18
|
||||
},
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "REMOTE PORTALS | separate authentication",
|
||||
"wraps": [
|
||||
"pratyay",
|
||||
"mco"
|
||||
],
|
||||
"pad": 28
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows",
|
||||
"label": "run tests",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "ui-actions",
|
||||
"from": "flows",
|
||||
"to": "browser",
|
||||
"label": "UI commands",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "portal-session",
|
||||
"from": "browser",
|
||||
"to": "pratyay",
|
||||
"label": "login + UI",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "read-settings",
|
||||
"from": "flows",
|
||||
"to": "config",
|
||||
"label": "read files"
|
||||
},
|
||||
{
|
||||
"id": "report-hooks",
|
||||
"from": "runner",
|
||||
"to": "reporting",
|
||||
"label": "test hooks"
|
||||
},
|
||||
{
|
||||
"id": "write-evidence",
|
||||
"from": "reporting",
|
||||
"to": "artifacts",
|
||||
"label": "write evidence"
|
||||
},
|
||||
{
|
||||
"id": "optional-mco",
|
||||
"from": "browser",
|
||||
"to": "mco",
|
||||
"label": "if enabled: login",
|
||||
"variant": "dashed"
|
||||
},
|
||||
{
|
||||
"id": "session-enabled",
|
||||
"from": "flows",
|
||||
"to": "sessions",
|
||||
"label": "run_session",
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "combined-order",
|
||||
"from": "sessions",
|
||||
"to": "tokens",
|
||||
"label": "both enabled: next",
|
||||
"variant": "dashed"
|
||||
},
|
||||
{
|
||||
"id": "token-only",
|
||||
"from": "flows",
|
||||
"to": "tokens",
|
||||
"label": "token only",
|
||||
"variant": "default",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "right"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "amber",
|
||||
"title": "Session and token execution",
|
||||
"items": [
|
||||
"After setup login and manual CAPTCHA: session-only runs create_session; token-only runs run_token_setup; combined runs create_session then run_token_setup for each product code.",
|
||||
"Both setup modules use the shared Selenium driver and StepActions through Automation flows. The dashed session-to-token arrow denotes controller ordering, not a direct module call (tests/setup/test_combined_setup.py:21-37).",
|
||||
"Session creation opens Trading Session, selects the ME instance, fills start/end times, then clicks Save (tests/setup/sessions/test_session.py:24-40).",
|
||||
"These are trading sessions and trading tokens, distinct from browser authentication/session backups."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "orange",
|
||||
"title": "Product-specific token behavior",
|
||||
"items": [
|
||||
"IDAS/default branch: create token, set delivery dates and expiry, Save, edit ORDER_NOR / ORDER_EXCH / TRADE times, then Save and Activate (tests/setup/tokens/test_trading_token.py:150-180, 264-325).",
|
||||
"RTM branch fills delivery and session times and saves the token; activation is not called (lines 212-225, 254-261).",
|
||||
"UPC branch fills configured order/trade times, saves, then resets the generator (lines 228-258).",
|
||||
"config.json selects enabled products and run_session/run_token flags. Disabled products and both-false products are omitted; UPC variants have additional routing rules (tests/config_reader.py:36-83)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "cyan",
|
||||
"title": "Primary path",
|
||||
"items": [
|
||||
"pytest runs selected tests; flow code controls Selenium and Chrome, which interacts with the remote Pratyay UI.",
|
||||
"Pratyay login fills credentials and pauses for manual CAPTCHA entry before submission (tests/regression/auth.py:46-122).",
|
||||
"Arrows describe control and I/O; results return to pytest. No direct backend API or database integration is asserted."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "violet",
|
||||
"title": "Flow selection and capabilities",
|
||||
"items": [
|
||||
"Root config.json selects setup products and session/token actions; config/runtime.json selects regression environment, role, product and flow.",
|
||||
"Bidding supports Excel/JSON grids, files and folders. Optional parallel folder workers create their own browsers (bidding flow:551-587).",
|
||||
"Matching requires the exchange role; rejected or unsupported bid-transfer status stops matching (matching flow:28-44, 123-135)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "Trust and external dependencies",
|
||||
"items": [
|
||||
"Boundaries show host/process and remote ownership, not verified network isolation or firewall policy.",
|
||||
"Credentials come from local JSON and are entered into remote login forms. Values and endpoint addresses are omitted.",
|
||||
"Chrome/WebDriver is an external runtime dependency. Pratyay and MCO server internals and transport guarantees are outside the observed source.",
|
||||
"MCO is an independent, enabled-only dashboard login test; no Pratyay-to-MCO backend connection is inferred."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "emerald",
|
||||
"title": "Evidence and scope",
|
||||
"items": [
|
||||
"StepActions requests browser screenshots; pytest hooks attach evidence and configure pytest-html. Reports live under reports/.",
|
||||
"Matching configures browser downloads separately; setup can save session backups. These details are kept off the main path.",
|
||||
"Source snapshot: 5ab89ce0059525b31558a1fa82251a2a6f610dd7; tracked code unchanged. Only generated .archify/ artifacts are untracked. Sources use local-only links.",
|
||||
"Registered reports/order-book/trade-book markers are placeholders, not implemented runtime services (RUN_COMMANDS.md)."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+721
@@ -0,0 +1,721 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"ok": true,
|
||||
"command": "browser-check",
|
||||
"evidenceKind": "automated-browser",
|
||||
"status": "pass",
|
||||
"visualReview": "not-requested",
|
||||
"provenance": "current",
|
||||
"deliveryReceiptId": "4a5fda98-da80-480c-a108-3987d32a0fb2",
|
||||
"artifact": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.html",
|
||||
"sha256": "7f1a2d9fcc2ed49fc6a2c2c9c541bd0620ab0ca1cf04b5e388dae27d01e5b3ec",
|
||||
"bytes": 773779
|
||||
},
|
||||
"state": {
|
||||
"detail": "read",
|
||||
"motion": "still"
|
||||
},
|
||||
"chrome": {
|
||||
"status": "available",
|
||||
"executable": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
},
|
||||
"diagnostics": [],
|
||||
"containment": {
|
||||
"status": "pass",
|
||||
"policy": "fit-or-reader-declared-readable-vertical-scroll",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 945,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1011,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1128,
|
||||
"diagramWidth": 1098,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNonEdgeTextPx": 8.047231270358306,
|
||||
"minimumProjectedEdgeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1431,
|
||||
"diagramWidth": 1381,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"themeStates": {
|
||||
"status": "pass",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"requestedTheme": "light",
|
||||
"resolvedTheme": "light",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"requestedTheme": "dark",
|
||||
"resolvedTheme": "dark",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"requestedTheme": "dark",
|
||||
"resolvedTheme": "dark",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"readability": {
|
||||
"status": "pass",
|
||||
"minimumProjectedNodeTextPx": 6,
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 945,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1011,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1128,
|
||||
"diagramWidth": 1098,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNonEdgeTextPx": 8.047231270358306,
|
||||
"minimumProjectedEdgeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1431,
|
||||
"diagramWidth": 1381,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewerChrome": {
|
||||
"status": "pass",
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"theme": "light",
|
||||
"innerWidth": 1440,
|
||||
"innerHeight": 900,
|
||||
"scrollWidth": 1440,
|
||||
"scrollHeight": 945,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 12,
|
||||
"headerPx": 52,
|
||||
"diagramChromePx": 78,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"theme": "light",
|
||||
"innerWidth": 1600,
|
||||
"innerHeight": 1000,
|
||||
"scrollWidth": 1600,
|
||||
"scrollHeight": 1011,
|
||||
"overflowX": false,
|
||||
"overflowY": true,
|
||||
"verticalScrollAccepted": true,
|
||||
"overflowDisposition": "readable-vertical-scroll",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": "authored",
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1054,
|
||||
"diagramWidth": 1024,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 802,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNonEdgeTextPx": 7.504885993485343,
|
||||
"minimumProjectedEdgeTextPx": 6.671009771986971,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"theme": "light",
|
||||
"innerWidth": 1920,
|
||||
"innerHeight": 1080,
|
||||
"scrollWidth": 1920,
|
||||
"scrollHeight": 1080,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1128,
|
||||
"diagramWidth": 1098,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 60,
|
||||
"diagramChromePx": 84,
|
||||
"svgPx": 860,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNonEdgeTextPx": 8.047231270358306,
|
||||
"minimumProjectedEdgeTextPx": 7.153094462540716,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 54,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
},
|
||||
{
|
||||
"width": 2048,
|
||||
"height": 1320,
|
||||
"theme": "light",
|
||||
"innerWidth": 2048,
|
||||
"innerHeight": 1320,
|
||||
"scrollWidth": 2048,
|
||||
"scrollHeight": 1320,
|
||||
"overflowX": false,
|
||||
"overflowY": false,
|
||||
"verticalScrollAccepted": false,
|
||||
"overflowDisposition": "contained",
|
||||
"readerLayout": "adaptive",
|
||||
"readerOverflow": null,
|
||||
"readerFit": "intrinsic-height",
|
||||
"ok": true,
|
||||
"readerWidth": 1431,
|
||||
"diagramWidth": 1381,
|
||||
"pageComposition": {
|
||||
"bodyPaddingPx": 64,
|
||||
"headerPx": 68,
|
||||
"diagramChromePx": 94,
|
||||
"svgPx": 1082,
|
||||
"cardsPx": 0,
|
||||
"viewBoxHeight": 962
|
||||
},
|
||||
"viewBoxWidth": 1228,
|
||||
"minimumProjectedNodeTextPx": 8,
|
||||
"minimumProjectedNonEdgeTextPx": 9,
|
||||
"minimumProjectedEdgeTextPx": 8,
|
||||
"minimumProjectedNodeText": "run tests",
|
||||
"minimumProjectedNodeTextDetail": "edge",
|
||||
"minimumProjectedNodeTextOwner": {
|
||||
"kind": "edge",
|
||||
"id": "selected-tests",
|
||||
"from": "runner",
|
||||
"to": "flows"
|
||||
},
|
||||
"minimumRequiredNodeTextPx": 6,
|
||||
"readabilityOk": true,
|
||||
"hasLegend": true,
|
||||
"hasNavigationDock": true,
|
||||
"legendDockIntersectionArea": 0,
|
||||
"dockStageIntersectionArea": 0,
|
||||
"dockStageGap": 10,
|
||||
"requiredDockStageGap": 10,
|
||||
"viewerChromeStageOk": true,
|
||||
"viewerChromeReserve": 44,
|
||||
"viewerChromeActive": true,
|
||||
"viewerChromeOk": true,
|
||||
"resolvedTheme": "light"
|
||||
}
|
||||
]
|
||||
},
|
||||
"captures": {
|
||||
"status": "not-requested",
|
||||
"screenshots": [],
|
||||
"contactSheet": null
|
||||
},
|
||||
"sidecars": {
|
||||
"receipt": "pxil-setup-runtime.browser-check.json",
|
||||
"files": []
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"receiptId": "4a5fda98-da80-480c-a108-3987d32a0fb2",
|
||||
"status": "current",
|
||||
"command": "deliver",
|
||||
"type": "architecture",
|
||||
"input": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\candidate.json",
|
||||
"output": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.html",
|
||||
"specification": {
|
||||
"sha256": "1419cbca3fed61e28342a8b9767aa43fa85a7b18ff380e93ebedccfa96f9549b",
|
||||
"bytes": 25091
|
||||
},
|
||||
"artifact": {
|
||||
"sha256": "7f1a2d9fcc2ed49fc6a2c2c9c541bd0620ab0ca1cf04b5e388dae27d01e5b3ec",
|
||||
"bytes": 773779
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"ok": true,
|
||||
"command": "finalize",
|
||||
"status": "pass",
|
||||
"type": "architecture",
|
||||
"quality": "showcase",
|
||||
"specification": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\candidate.json",
|
||||
"sha256": "1419cbca3fed61e28342a8b9767aa43fa85a7b18ff380e93ebedccfa96f9549b",
|
||||
"bytes": 25091
|
||||
},
|
||||
"artifact": {
|
||||
"path": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.html",
|
||||
"sha256": "7f1a2d9fcc2ed49fc6a2c2c9c541bd0620ab0ca1cf04b5e388dae27d01e5b3ec",
|
||||
"bytes": 773779
|
||||
},
|
||||
"gates": {
|
||||
"validate": "pass",
|
||||
"deliver": "pass",
|
||||
"check": "pass",
|
||||
"browser-check": "pass"
|
||||
},
|
||||
"diagnostics": [],
|
||||
"diagnosticSummary": {
|
||||
"total": 0,
|
||||
"shown": 0,
|
||||
"truncated": false
|
||||
},
|
||||
"evidence": {
|
||||
"receipt": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.finalize.json",
|
||||
"summaryReceipt": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.finalize-summary.json",
|
||||
"browserCheckReceipt": "D:\\Selenium Test\\.archify\\architecture-pxil-setup-runtime-20260929-155341\\pxil-setup-runtime.browser-check.json"
|
||||
},
|
||||
"update": {
|
||||
"status": "current",
|
||||
"installedVersion": "3.0.1",
|
||||
"availableVersion": "3.0.1",
|
||||
"releaseNotes": null,
|
||||
"checkedAt": "2026-09-29T09:50:05.829Z",
|
||||
"source": "cache",
|
||||
"noticeRequired": false,
|
||||
"noticeText": null
|
||||
},
|
||||
"visualReview": "not-requested",
|
||||
"durationMs": 10108,
|
||||
"visualReviewRecommendation": {
|
||||
"action": "inspect-route-readability",
|
||||
"signals": {
|
||||
"routesOverSuggestedBends": 1
|
||||
},
|
||||
"reason": "Automated gates passed, but crossings or detours still need perceptual review before claiming visual quality.",
|
||||
"affectedRoutes": {
|
||||
"crossings": [],
|
||||
"detours": [
|
||||
{
|
||||
"relationship": {
|
||||
"id": "token-only",
|
||||
"from": "flows",
|
||||
"to": "tokens",
|
||||
"label": "token only",
|
||||
"collectionIndex": 9,
|
||||
"artifactIndex": 10
|
||||
},
|
||||
"bends": 3,
|
||||
"stretch": 1.087,
|
||||
"directCorridorBlockers": [
|
||||
{
|
||||
"id": "sessions",
|
||||
"label": "Trading session setup",
|
||||
"box": [
|
||||
360,
|
||||
560,
|
||||
190,
|
||||
74
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"truncated": false
|
||||
},
|
||||
"repair": "Trace these relationships at the desktop viewport. For Architecture, use references/architecture-layout-repair.md: reflow a blocked main path or tangled connected scene, and repair an isolated defect locally only when the surrounding composition is accepted. Preserve all semantic content and user-fixed geometry. Rerun finalize once after the edit."
|
||||
}
|
||||
}
|
||||
+1041
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "PXIL Session + Trading Token Setup",
|
||||
"subtitle": "Configuration-driven pytest/Selenium workflow",
|
||||
"output": ".archify/workflow-pxil-session-token-20260929-160100/pxil-session-token.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
"repository": {
|
||||
"url": "https://git.tejasshasane.xyz/tejasshasane/pxil-selenium-automation.git",
|
||||
"revision": "5ab89ce0059525b31558a1fa82251a2a6f610dd7",
|
||||
"link_mode": "local-only"
|
||||
}
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "config",
|
||||
"label": "Configuration \u0026 selection"
|
||||
},
|
||||
{
|
||||
"id": "pytest",
|
||||
"label": "Pytest orchestration"
|
||||
},
|
||||
{
|
||||
"id": "browser",
|
||||
"label": "Browser automation"
|
||||
},
|
||||
{
|
||||
"id": "portal",
|
||||
"label": "Pratyay portal"
|
||||
}
|
||||
],
|
||||
"phases": [
|
||||
{
|
||||
"id": "resolve",
|
||||
"label": "Resolve run",
|
||||
"fromCol": 0,
|
||||
"toCol": 1
|
||||
},
|
||||
{
|
||||
"id": "login",
|
||||
"label": "Authenticate",
|
||||
"fromCol": 2,
|
||||
"toCol": 2,
|
||||
"variant": "security"
|
||||
},
|
||||
{
|
||||
"id": "setup",
|
||||
"label": "Create session + tokens",
|
||||
"fromCol": 3,
|
||||
"toCol": 4,
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "evidence",
|
||||
"label": "Capture evidence",
|
||||
"fromCol": 5,
|
||||
"toCol": 5,
|
||||
"variant": "dashed"
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": "config_group",
|
||||
"label": "Product and mode resolution",
|
||||
"lane": "config",
|
||||
"fromCol": 0,
|
||||
"toCol": 1,
|
||||
"variant": "dashed"
|
||||
},
|
||||
{
|
||||
"id": "auth_group",
|
||||
"label": "One browser login",
|
||||
"lane": "browser",
|
||||
"fromCol": 2,
|
||||
"toCol": 2,
|
||||
"variant": "security"
|
||||
},
|
||||
{
|
||||
"id": "session_group",
|
||||
"label": "Session creation gate",
|
||||
"lane": "portal",
|
||||
"fromCol": 3,
|
||||
"toCol": 3,
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "token_group",
|
||||
"label": "Token creation loop",
|
||||
"lane": "portal",
|
||||
"fromCol": 4,
|
||||
"toCol": 5,
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "evidence_group",
|
||||
"label": "Step artifacts",
|
||||
"lane": "config",
|
||||
"fromCol": 5,
|
||||
"toCol": 5,
|
||||
"variant": "dashed"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"pytest_start",
|
||||
"load_config",
|
||||
"parametrize",
|
||||
"driver_login",
|
||||
"token_dispatch",
|
||||
"token_save",
|
||||
"save_report"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "pytest_start",
|
||||
"lane": "pytest",
|
||||
"col": 0,
|
||||
"type": "backend",
|
||||
"label": "Pytest setup test",
|
||||
"sublabel": "test_combined_setup.py",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 11,
|
||||
"end_line": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "load_config",
|
||||
"lane": "config",
|
||||
"col": 1,
|
||||
"type": "database",
|
||||
"label": "Load config.json",
|
||||
"sublabel": "products + setup flags",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/config_reader.py",
|
||||
"line": 6,
|
||||
"end_line": 33
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "parametrize",
|
||||
"lane": "config",
|
||||
"col": 2,
|
||||
"type": "backend",
|
||||
"label": "get_setup_configs",
|
||||
"sublabel": "combined / session / token",
|
||||
"tag": "skip disabled",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/config_reader.py",
|
||||
"line": 36,
|
||||
"end_line": 83
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "driver_login",
|
||||
"lane": "browser",
|
||||
"col": 2,
|
||||
"type": "security",
|
||||
"label": "Open + login",
|
||||
"sublabel": "Chrome, credentials, CAPTCHA",
|
||||
"tag": "manual CAPTCHA",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 21,
|
||||
"end_line": 29
|
||||
},
|
||||
{
|
||||
"path": "tests/helpers.py",
|
||||
"line": 60,
|
||||
"end_line": 91
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "session_gate",
|
||||
"lane": "pytest",
|
||||
"col": 3,
|
||||
"type": "security",
|
||||
"label": "run_session?",
|
||||
"sublabel": "product flag gate",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 31,
|
||||
"end_line": 32
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "session_create",
|
||||
"lane": "portal",
|
||||
"col": 3,
|
||||
"type": "backend",
|
||||
"label": "create_session",
|
||||
"sublabel": "ME instance + dates -\u003e Save",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/sessions/test_session.py",
|
||||
"line": 24,
|
||||
"end_line": 40
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "token_loop",
|
||||
"lane": "pytest",
|
||||
"col": 4,
|
||||
"type": "backend",
|
||||
"label": "Product-code loop",
|
||||
"sublabel": "one token per configured code",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 34,
|
||||
"end_line": 37
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 74,
|
||||
"end_line": 83
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "token_dispatch",
|
||||
"lane": "portal",
|
||||
"col": 4,
|
||||
"type": "backend",
|
||||
"label": "run_token_setup",
|
||||
"sublabel": "IDAS / RTM / UPC branch",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 254,
|
||||
"end_line": 262
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "token_save",
|
||||
"lane": "portal",
|
||||
"col": 5,
|
||||
"type": "backend",
|
||||
"label": "Create + save token",
|
||||
"sublabel": "dates, expiry, time sessions",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 264,
|
||||
"end_line": 325
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "save_report",
|
||||
"lane": "config",
|
||||
"col": 5,
|
||||
"type": "database",
|
||||
"label": "Evidence + teardown",
|
||||
"sublabel": "screenshots, backup, logout",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/step_actions.py",
|
||||
"line": 21,
|
||||
"end_line": 40
|
||||
},
|
||||
{
|
||||
"path": "tests/helpers.py",
|
||||
"line": 135,
|
||||
"end_line": 159
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 330,
|
||||
"end_line": 342
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "session_skip",
|
||||
"lane": "config",
|
||||
"col": 3,
|
||||
"type": "messagebus",
|
||||
"label": "Skip session",
|
||||
"sublabel": "run_session = false",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/test_combined_setup.py",
|
||||
"line": 31,
|
||||
"end_line": 32
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pratyay_ui",
|
||||
"lane": "portal",
|
||||
"col": 2,
|
||||
"type": "external",
|
||||
"label": "Pratyay UI",
|
||||
"sublabel": "Trading maintenance pages",
|
||||
"width": 150,
|
||||
"sources": [
|
||||
{
|
||||
"path": "tests/setup/sessions/test_session.py",
|
||||
"line": 28,
|
||||
"end_line": 40
|
||||
},
|
||||
{
|
||||
"path": "tests/setup/tokens/test_trading_token.py",
|
||||
"line": 274,
|
||||
"end_line": 325
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "start-config",
|
||||
"from": "pytest_start",
|
||||
"to": "load_config",
|
||||
"label": "collect + resolve",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "config-param",
|
||||
"from": "load_config",
|
||||
"to": "parametrize",
|
||||
"label": "product settings"
|
||||
},
|
||||
{
|
||||
"id": "param-login",
|
||||
"from": "parametrize",
|
||||
"to": "driver_login",
|
||||
"label": "selected config",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "login-portal",
|
||||
"from": "driver_login",
|
||||
"to": "pratyay_ui",
|
||||
"label": "authenticate",
|
||||
"variant": "security"
|
||||
},
|
||||
{
|
||||
"id": "skip-token-loop",
|
||||
"from": "session_skip",
|
||||
"to": "token_loop",
|
||||
"label": "token-only / combined decision",
|
||||
"variant": "dashed"
|
||||
},
|
||||
{
|
||||
"id": "dispatch-save",
|
||||
"from": "token_dispatch",
|
||||
"to": "token_save",
|
||||
"label": "IDAS default",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "save-evidence",
|
||||
"from": "token_save",
|
||||
"to": "save_report",
|
||||
"label": "step captures",
|
||||
"variant": "dashed"
|
||||
},
|
||||
{
|
||||
"id": "login-dispatch",
|
||||
"from": "driver_login",
|
||||
"to": "token_dispatch",
|
||||
"label": "run token flow",
|
||||
"variant": "emphasis"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "cyan",
|
||||
"title": "How configuration is called",
|
||||
"items": [
|
||||
"pytest parametrization calls get_setup_configs(\"combined\") when test_session_and_token_setup is collected.",
|
||||
"get_product_config merges product overrides into setup.run_session/run_token and resolves token settings; disabled products are filtered out.",
|
||||
"Each selected config carries url, login, product.me_instance, token settings and product_name into the test."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "amber",
|
||||
"title": "Combined run semantics",
|
||||
"items": [
|
||||
"The test opens the portal and logs in once per browser fixture. It saves a browser cookie/session backup after login.",
|
||||
"When run_session is true, create_session selects the configured ME instance and saves the calculated start/end window.",
|
||||
"The same driver then loops product codes and calls run_token_setup for each one."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "violet",
|
||||
"title": "Token branches",
|
||||
"items": [
|
||||
"IDAS/default creates the token, fills delivery/expiry values, then edits ORDER_NOR, ORDER_EXCH and TRADE time sessions before Save and Activate.",
|
||||
"RTM selects market/product/generation mode, fills delivery and session times, then saves; activation is not part of this branch.",
|
||||
"UPC fills configured order/trade times, saves, then resets the generator."
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "Authentication and evidence",
|
||||
"items": [
|
||||
"login() sends credentials, waits for manual CAPTCHA completion, submits, waits for the dashboard marker and closes transient notices.",
|
||||
"StepActions captures screenshots after interactions. conftest.py attaches them to pytest-html and creates report folders.",
|
||||
"The diagram shows trading sessions/tokens; login session backups are browser cookies, not the trading-session records created in the portal."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+1345
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"receiptId": "194f6ca1-621e-45b4-ab5d-d915aa4711fd",
|
||||
"status": "current",
|
||||
"command": "deliver",
|
||||
"type": "workflow",
|
||||
"input": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\candidate.json",
|
||||
"output": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\pxil-session-token.html",
|
||||
"specification": {
|
||||
"sha256": "c349c44844d853c1dc013cd9f1e9380b0f1548224ef389a1960b97af956310b1",
|
||||
"bytes": 19583
|
||||
},
|
||||
"artifact": {
|
||||
"sha256": "d0c3abadeefa664a3d46893e7dc43042339bf42471f76967fe467600f0388882",
|
||||
"bytes": 771409
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"ok": true,
|
||||
"command": "finalize",
|
||||
"status": "pass",
|
||||
"type": "workflow",
|
||||
"quality": "showcase",
|
||||
"specification": {
|
||||
"path": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\candidate.json",
|
||||
"sha256": "c349c44844d853c1dc013cd9f1e9380b0f1548224ef389a1960b97af956310b1",
|
||||
"bytes": 19583
|
||||
},
|
||||
"artifact": {
|
||||
"path": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\pxil-session-token.html",
|
||||
"sha256": "d0c3abadeefa664a3d46893e7dc43042339bf42471f76967fe467600f0388882",
|
||||
"bytes": 771409
|
||||
},
|
||||
"gates": {
|
||||
"validate": "pass",
|
||||
"deliver": "pass",
|
||||
"check": "pass",
|
||||
"browser-check": "pass"
|
||||
},
|
||||
"diagnostics": [],
|
||||
"diagnosticSummary": {
|
||||
"total": 0,
|
||||
"shown": 0,
|
||||
"truncated": false
|
||||
},
|
||||
"evidence": {
|
||||
"receipt": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\pxil-session-token.finalize.json",
|
||||
"summaryReceipt": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\pxil-session-token.finalize-summary.json",
|
||||
"browserCheckReceipt": "D:\\Selenium Test\\.archify\\workflow-pxil-session-token-20260929-160100\\pxil-session-token.browser-check.json"
|
||||
},
|
||||
"update": {
|
||||
"status": "current",
|
||||
"installedVersion": "3.0.1",
|
||||
"availableVersion": "3.0.1",
|
||||
"releaseNotes": null,
|
||||
"checkedAt": "2026-09-29T09:50:05.829Z",
|
||||
"source": "cache",
|
||||
"noticeRequired": false,
|
||||
"noticeText": null
|
||||
},
|
||||
"visualReview": "not-requested",
|
||||
"durationMs": 12243,
|
||||
"visualReviewRecommendation": {
|
||||
"action": "inspect-route-readability",
|
||||
"signals": {
|
||||
"routesOverSuggestedBends": 2
|
||||
},
|
||||
"reason": "Automated gates passed, but crossings or detours still need perceptual review before claiming visual quality.",
|
||||
"affectedRoutes": {
|
||||
"crossings": [],
|
||||
"detours": [
|
||||
{
|
||||
"relationship": {
|
||||
"id": "login-dispatch",
|
||||
"from": "driver_login",
|
||||
"to": "token_dispatch",
|
||||
"label": "run token flow",
|
||||
"collectionIndex": 2,
|
||||
"artifactIndex": 3
|
||||
},
|
||||
"bends": 3,
|
||||
"stretch": 1.164
|
||||
},
|
||||
{
|
||||
"relationship": {
|
||||
"id": "param-login",
|
||||
"from": "parametrize",
|
||||
"to": "driver_login",
|
||||
"label": "selected config",
|
||||
"collectionIndex": 4,
|
||||
"artifactIndex": 5
|
||||
},
|
||||
"bends": 3,
|
||||
"stretch": 1.109
|
||||
}
|
||||
],
|
||||
"truncated": false
|
||||
},
|
||||
"hints": [
|
||||
"driver_login → token_dispatch needs 3 bends: move node driver_login or token_dispatch so they share a row or column with matching centers, or sit diagonally with a clear corner.",
|
||||
"parametrize → driver_login needs 3 bends: move node parametrize or driver_login so they share a row or column with matching centers, or sit diagonally with a clear corner."
|
||||
],
|
||||
"repair": "Trace these relationships at the desktop viewport. For Architecture, use references/architecture-layout-repair.md: reflow a blocked main path or tangled connected scene, and repair an isolated defect locally only when the surrounding composition is accepted. Preserve all semantic content and user-fixed geometry. Rerun finalize once after the edit."
|
||||
}
|
||||
}
|
||||
+1657
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -9,6 +9,9 @@ __pycache__/
|
||||
# Pytest cache
|
||||
.pytest_cache/
|
||||
|
||||
# Machine-specific test configuration
|
||||
config.json
|
||||
|
||||
# Generated reports (keep directory structure)
|
||||
reports/screenshots/*
|
||||
!reports/screenshots/.gitkeep
|
||||
@@ -18,3 +21,6 @@ reports/logs/*
|
||||
|
||||
reports/html/*
|
||||
!reports/html/.gitkeep
|
||||
|
||||
# CAPTCHA stress-test screenshots and CSV reports
|
||||
reports/captcha_login_stress/
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"url": "https://utilseastern.pxil.in/Pxil/",
|
||||
"stagingurl": "https://utilseastern.pxil.in/Pxil/",
|
||||
"uaturl": "https://utilsuat.pxil.in/Pxil/",
|
||||
"login": {
|
||||
"username": "Tejas",
|
||||
"password": "Pxil@123"
|
||||
},
|
||||
"login_captcha_wait_seconds": 10,
|
||||
"login_captcha_max_retries": 10,
|
||||
"login_post_submit_wait_seconds": 10,
|
||||
"step_delay_seconds": 1,
|
||||
"setup": {
|
||||
"run_session": true,
|
||||
"run_token": false,
|
||||
"session": {
|
||||
"time_mode": "offset",
|
||||
"start_offset_minutes": 5,
|
||||
"start_time": "14:30",
|
||||
"end_time": "20:30"
|
||||
}
|
||||
},
|
||||
"products": {
|
||||
"IDAS": {
|
||||
"enabled": true,
|
||||
"run_session": true,
|
||||
"run_token": 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": 10,
|
||||
"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": {
|
||||
"time_mode": "offset",
|
||||
"start_offset_minutes": 7,
|
||||
"start_time": "14:30",
|
||||
"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",
|
||||
"time_mode": "offset",
|
||||
"session_start_offset_minutes": 5,
|
||||
"session_start_time": "14:30",
|
||||
"session_end_time": "20:00"
|
||||
}
|
||||
},
|
||||
"UPC": {
|
||||
"enabled": true,
|
||||
"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": false,
|
||||
"run_token": true,
|
||||
"me_instance": "60",
|
||||
"token": {
|
||||
"generation_mode": "MonthlyUPC",
|
||||
"create_product_codes": [
|
||||
"MonthlyC_N"
|
||||
],
|
||||
"available_product_codes": [
|
||||
"MonthlyC_D",
|
||||
"MonthlyGR_D",
|
||||
"MonthlyC_RTC"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {},
|
||||
"token": {
|
||||
"market_code": "NATIONAL",
|
||||
"product_type": "UPC",
|
||||
"product_codes": [],
|
||||
"delivery_offset_days": 1,
|
||||
"expiry_time": "20:30",
|
||||
"upc_times": {
|
||||
"time_mode": "manual",
|
||||
"order_normal_start_time": "13:00",
|
||||
"order_normal_start_offset_minutes": 10,
|
||||
"order_normal_end_time": "17:00",
|
||||
"trade_session_start_time": "17:00",
|
||||
"trade_session_end_time": "22:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RA": {
|
||||
"enabled": false,
|
||||
"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,
|
||||
"run_session": true,
|
||||
"run_token": true,
|
||||
"me_instance": "25",
|
||||
"session": {},
|
||||
"token": {
|
||||
"market_code": "NATIONAL",
|
||||
"product_type": "REC",
|
||||
"product_codes": [
|
||||
"REC"
|
||||
],
|
||||
"delivery_offset_days": 1,
|
||||
"expiry_time": "20:30",
|
||||
"type_prefix": "REC",
|
||||
"time_sessions": {
|
||||
"ORDER_NOR": {
|
||||
"time_mode": "offset",
|
||||
"start_offset_minutes": 10,
|
||||
"start_time": "14:30",
|
||||
"end_time": "19:30"
|
||||
},
|
||||
"ORDER_EXCH": {
|
||||
"start_time": "19:30",
|
||||
"end_time": "19:45"
|
||||
},
|
||||
"TRADE": {
|
||||
"start_time": "19:45",
|
||||
"end_time": "19:50"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"INTRADAY": {
|
||||
"enabled": true,
|
||||
"run_session": true,
|
||||
"run_token": true,
|
||||
"me_instance": "53",
|
||||
"session": {},
|
||||
"token": {
|
||||
"market_code": "NATIONAL",
|
||||
"product_type": "INTRADAY",
|
||||
"product_codes": [
|
||||
"I_DayStatic_C"
|
||||
],
|
||||
"generation_mode": "Intraday24by7",
|
||||
"delivery_start_offset_minutes": 5,
|
||||
"trading_start_time": "00:20",
|
||||
"trading_start_days_before_delivery": 1,
|
||||
"duration_before_delivery_end_time": 150,
|
||||
"delivery_to_time": "23:59"
|
||||
}
|
||||
},
|
||||
"DAC": {
|
||||
"enabled": false,
|
||||
"run_session": true,
|
||||
"run_token": true,
|
||||
"me_instance": "57",
|
||||
"session": {
|
||||
"time_mode": "offset",
|
||||
"start_offset_minutes": 5,
|
||||
"end_time": "23:00"
|
||||
},
|
||||
"token": {
|
||||
"market_code": "NATIONAL",
|
||||
"product_type": "DAC",
|
||||
"product_codes": ["DACStatic"],
|
||||
"generation_mode": "DAC",
|
||||
"delivery_start_offset_minutes": 5,
|
||||
"delivery_to_time": "23:59",
|
||||
"session_start_time": "13:00",
|
||||
"session_end_time": "23:00",
|
||||
"delivery_time_period": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
{
|
||||
"url": "https://utilseastern.pxil.in/Pxil/",
|
||||
"stagingurl": "https://utilseastern.pxil.in/Pxil/",
|
||||
"uaturl": "https://utilsuat.pxil.in/Pxil/",
|
||||
"login": {
|
||||
"username": "Tejas",
|
||||
"password": "Pxil@123"
|
||||
},
|
||||
"login_captcha_wait_seconds": 10,
|
||||
"login_post_submit_wait_seconds": 10,
|
||||
"step_delay_seconds": 1,
|
||||
"setup": {
|
||||
"run_session": true,
|
||||
"run_token": true,
|
||||
"session": {
|
||||
"start_offset_minutes": 7,
|
||||
"end_time": "20: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"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,9 @@ markers =
|
||||
reports: report download coverage
|
||||
order_book: order book coverage
|
||||
trade_book: trade book coverage
|
||||
captcha: CAPTCHA login stress test
|
||||
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning:easyocr.*
|
||||
ignore::DeprecationWarning:torch.*
|
||||
ignore::UserWarning:torch.*
|
||||
|
||||
+10
-2
@@ -1,6 +1,14 @@
|
||||
# Core test runner
|
||||
pytest==8.4.1
|
||||
pytest==9.1.1
|
||||
pytest-html==4.2.0
|
||||
|
||||
# Browser automation
|
||||
selenium==4.34.2
|
||||
selenium==4.50.0
|
||||
|
||||
# CAPTCHA OCR and image processing
|
||||
easyocr==1.7.2
|
||||
opencv-python==5.0.0.93
|
||||
Pillow==12.3.0
|
||||
numpy==2.5.3
|
||||
torch==2.14.1
|
||||
torchvision==0.29.1
|
||||
|
||||
+10
-1
@@ -1,4 +1,7 @@
|
||||
import os
|
||||
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
|
||||
|
||||
def create_chrome_driver():
|
||||
@@ -10,5 +13,11 @@ def create_chrome_driver():
|
||||
options.add_argument("--disable-features=CalculateNativeWinOcclusion")
|
||||
options.add_argument("--disable-hang-monitor")
|
||||
options.add_argument("--disable-popup-blocking")
|
||||
options.add_argument("--disable-background-networking")
|
||||
options.add_argument("--disable-component-update")
|
||||
options.add_argument("--disable-sync")
|
||||
options.add_argument("--disable-default-apps")
|
||||
options.add_experimental_option("excludeSwitches", ["enable-logging"])
|
||||
|
||||
return webdriver.Chrome(options=options)
|
||||
service = Service(log_output=os.devnull)
|
||||
return webdriver.Chrome(service=service, options=options)
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
"""
|
||||
CAPTCHA Solver for PXIL Login using EasyOCR.
|
||||
Extracts base64 from input's CSS background-image, runs EasyOCR OCR, fills the input.
|
||||
"""
|
||||
import re
|
||||
import base64
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import numpy as np
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
|
||||
|
||||
|
||||
# Initialize EasyOCR once (module level)
|
||||
# First run downloads models, then works offline
|
||||
try:
|
||||
import easyocr
|
||||
_EASYOCR_READER = easyocr.Reader(['en'], gpu=False)
|
||||
except ImportError:
|
||||
_EASYOCR_READER = None
|
||||
|
||||
|
||||
# Character confusion correction map for common OCR errors
|
||||
CONFUSION_MAP = {
|
||||
'0': 'O', 'O': '0', 'D': '0',
|
||||
'1': 'I', 'I': '1', 'L': '1', 'l': '1',
|
||||
'5': 'S', 'S': '5',
|
||||
'8': 'B', 'B': '8',
|
||||
'2': 'Z', 'Z': '2',
|
||||
'6': 'G', 'G': '6',
|
||||
'9': 'g', 'g': '9',
|
||||
'Q': 'O',
|
||||
'U': 'V', 'V': 'U',
|
||||
'n': 'h', 'h': 'n',
|
||||
'r': 'f', 'f': 'r',
|
||||
'c': 'e', 'e': 'c',
|
||||
'o': '0', '0': 'o',
|
||||
's': '5', '5': 's',
|
||||
'z': '2', '2': 'z',
|
||||
'i': '1', '1': 'i',
|
||||
'l': '1', '1': 'l',
|
||||
}
|
||||
|
||||
|
||||
def find_visible_captcha_input(driver: WebDriver) -> WebElement:
|
||||
"""Find the visible captcha input element (name='captcha')."""
|
||||
wait = WebDriverWait(driver, 15)
|
||||
inputs = wait.until(EC.presence_of_all_elements_located((By.NAME, "captcha")))
|
||||
for inp in inputs:
|
||||
if inp.is_displayed():
|
||||
return inp
|
||||
raise RuntimeError("No visible captcha input found")
|
||||
|
||||
|
||||
def extract_captcha_base64(captcha_input: WebElement) -> bytes:
|
||||
"""Extract base64 image data from input's style attribute. Returns raw JPEG bytes."""
|
||||
style = captcha_input.get_attribute("style") or ""
|
||||
match = re.search(r'background:\s*url\(["\']?data:image/[^;]+;base64,([^"\')]+)["\']?\)', style)
|
||||
if not match:
|
||||
raise RuntimeError(f"No base64 background-image found in style: {style[:200]}")
|
||||
|
||||
b64_data = match.group(1)
|
||||
if not b64_data:
|
||||
raise RuntimeError("Base64 data is empty (captcha not loaded yet)")
|
||||
|
||||
b64_data += "=" * ((4 - len(b64_data) % 4) % 4) # Fix padding
|
||||
return base64.b64decode(b64_data)
|
||||
|
||||
|
||||
def wait_for_captcha_load(driver: WebDriver, captcha_input: WebElement, timeout: int = 15) -> bool:
|
||||
"""Wait for captcha base64 to be populated with actual image data."""
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
style = captcha_input.get_attribute("style") or ""
|
||||
match = re.search(r'background:\s*url\(["\']?data:image/[^;]+;base64,([^"\')]+)["\']?\)', style)
|
||||
if match and match.group(1):
|
||||
return True
|
||||
except StaleElementReferenceException:
|
||||
captcha_input = find_visible_captcha_input(driver)
|
||||
time.sleep(0.3)
|
||||
return False
|
||||
|
||||
|
||||
def preprocess_captcha_image(image_bytes: bytes) -> Image.Image:
|
||||
"""Preprocess image for better OCR accuracy - gentle preprocessing."""
|
||||
nparr = np.frombuffer(image_bytes, np.uint8)
|
||||
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # Load as color (BGR)
|
||||
|
||||
if img is None:
|
||||
raise RuntimeError("Failed to decode image bytes")
|
||||
|
||||
# Convert BGR to RGB
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
|
||||
|
||||
# Simple threshold - keep it simple
|
||||
_, gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
|
||||
|
||||
# Light denoise
|
||||
gray = cv2.fastNlMeansDenoising(gray, h=3)
|
||||
|
||||
# Very light morphological close - only connect broken parts
|
||||
kernel = np.ones((1, 1), np.uint8)
|
||||
gray = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
|
||||
|
||||
# Resize 6x for better recognition (larger for 6-char captcha)
|
||||
gray = cv2.resize(gray, None, fx=6, fy=6, interpolation=cv2.INTER_CUBIC)
|
||||
|
||||
# Convert back to RGB (3-channel) for EasyOCR
|
||||
rgb = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
|
||||
|
||||
return Image.fromarray(rgb)
|
||||
|
||||
|
||||
def apply_confusion_corrections(text: str) -> str:
|
||||
"""Apply character confusion corrections based on common OCR errors."""
|
||||
corrected = []
|
||||
for ch in text:
|
||||
corrected.append(CONFUSION_MAP.get(ch, ch))
|
||||
return ''.join(corrected)
|
||||
|
||||
|
||||
def ocr_captcha(image: Image.Image) -> str:
|
||||
"""Run OCR on preprocessed captcha image using EasyOCR."""
|
||||
if _EASYOCR_READER is None:
|
||||
raise RuntimeError("EasyOCR not installed. Run: pip install easyocr")
|
||||
|
||||
img_array = np.array(image)
|
||||
|
||||
# EasyOCR with alphanumeric allowlist for 6-char captcha
|
||||
results = _EASYOCR_READER.readtext(
|
||||
img_array,
|
||||
detail=0,
|
||||
allowlist='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
||||
width_ths=0.5,
|
||||
paragraph=False
|
||||
)
|
||||
|
||||
# Combine all detected text
|
||||
result = ''.join(results)
|
||||
|
||||
# Clean result - keep only alphanumeric, max 6 chars
|
||||
cleaned = ''.join(filter(str.isalnum, result))[:6]
|
||||
|
||||
print(f" DEBUG OCR: result='{result}', cleaned='{cleaned}', len={len(cleaned)}")
|
||||
return cleaned
|
||||
|
||||
|
||||
def find_refresh_button(driver: WebDriver, captcha_input: WebElement) -> Optional[WebElement]:
|
||||
"""Find captcha refresh button near the captcha input."""
|
||||
parent = captcha_input.find_element(By.XPATH, "..")
|
||||
for sibling in parent.find_elements(By.XPATH, "*"):
|
||||
onclick = (sibling.get_attribute("onclick") or "").lower()
|
||||
class_attr = (sibling.get_attribute("class") or "").lower()
|
||||
id_attr = (sibling.get_attribute("id") or "").lower()
|
||||
if any(kw in onclick for kw in ["refresh", "captcha", "reload", "newcaptcha"]):
|
||||
return sibling
|
||||
if any(kw in class_attr for kw in ["refresh", "captcha-refresh", "reload"]):
|
||||
return sibling
|
||||
if any(kw in id_attr for kw in ["refresh", "captcha", "reload"]):
|
||||
return sibling
|
||||
|
||||
# Fallback: search globally for refresh-like elements
|
||||
try:
|
||||
candidates = driver.find_elements(By.XPATH, "//*[self::a or self::button or self::img or self::span][contains(translate(@onclick, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'refresh') or contains(translate(@class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'refresh') or contains(translate(@title, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'refresh')]")
|
||||
for cand in candidates:
|
||||
if cand.is_displayed():
|
||||
return cand
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_new_captcha(driver: WebDriver, captcha_input: WebElement, timeout: int = 10) -> bool:
|
||||
"""Wait for captcha background-image to change (new challenge loaded)."""
|
||||
old_style = captcha_input.get_attribute("style") or ""
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
new_style = captcha_input.get_attribute("style") or ""
|
||||
if new_style != old_style and "data:image" in new_style:
|
||||
return True
|
||||
except StaleElementReferenceException:
|
||||
captcha_input = find_visible_captcha_input(driver)
|
||||
old_style = captcha_input.get_attribute("style") or ""
|
||||
time.sleep(0.3)
|
||||
return False
|
||||
|
||||
|
||||
def solve_and_fill_captcha(driver: WebDriver, max_retries: int = 5, attempt_callback=None) -> bool:
|
||||
"""
|
||||
Main entry point: solve captcha and fill input.
|
||||
Returns True if filled successfully, False if max retries exceeded.
|
||||
Each retry gets a fresh captcha via refresh button.
|
||||
"""
|
||||
for attempt in range(1, max_retries + 1):
|
||||
captcha_text = ""
|
||||
try:
|
||||
captcha_input = find_visible_captcha_input(driver)
|
||||
|
||||
# Wait for captcha base64 to be populated (async load)
|
||||
if not wait_for_captcha_load(driver, captcha_input):
|
||||
print(f" [Attempt {attempt}] Captcha failed to load in time, retrying...")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
if attempt_callback:
|
||||
attempt_callback(attempt, "", "", "not_loaded")
|
||||
continue
|
||||
|
||||
image_bytes = extract_captcha_base64(captcha_input)
|
||||
pil_image = preprocess_captcha_image(image_bytes)
|
||||
captcha_text = ocr_captcha(pil_image)
|
||||
|
||||
if not captcha_text or len(captcha_text) < 6:
|
||||
print(f" [Attempt {attempt}] OCR weak result: '{captcha_text}' (len={len(captcha_text)}), retrying...")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
if attempt_callback:
|
||||
attempt_callback(attempt, captcha_text, "", "ocr_failed")
|
||||
continue
|
||||
|
||||
# Apply confusion corrections to get best variant
|
||||
corrected = apply_confusion_corrections(captcha_text)
|
||||
|
||||
# Generate case variants for case-sensitive captcha (mixed case + numbers)
|
||||
variants = [
|
||||
captcha_text, # as-is (from OCR)
|
||||
corrected, # with confusion corrections
|
||||
captcha_text.upper(), # all uppercase
|
||||
captcha_text.capitalize(), # first letter uppercase
|
||||
captcha_text.title(), # each word capitalized
|
||||
captcha_text.swapcase(), # swap case
|
||||
]
|
||||
|
||||
# Add variations with first char variations
|
||||
if len(captcha_text) >= 1:
|
||||
variants.append(captcha_text[0].upper() + captcha_text[1:])
|
||||
variants.append(captcha_text[0].lower() + captcha_text[1:])
|
||||
if len(captcha_text) >= 2:
|
||||
variants.append(captcha_text[:2].upper() + captcha_text[2:])
|
||||
variants.append(captcha_text[:2].lower() + captcha_text[2:])
|
||||
if len(captcha_text) >= 3:
|
||||
variants.append(captcha_text[:3].upper() + captcha_text[3:])
|
||||
variants.append(captcha_text[:3].lower() + captcha_text[3:])
|
||||
|
||||
# Remove duplicates while preserving order, keep only 6-char variants
|
||||
seen = set()
|
||||
unique_variants = []
|
||||
for v in variants:
|
||||
if v not in seen and len(v) == 6:
|
||||
seen.add(v)
|
||||
unique_variants.append(v)
|
||||
variants = unique_variants
|
||||
|
||||
if attempt_callback:
|
||||
screenshot_path = attempt_callback(attempt, captcha_text, "", "before_entry") or ""
|
||||
|
||||
success = False
|
||||
# Try ALL variants before giving up on this attempt
|
||||
for variant in variants:
|
||||
if len(variant) == 6:
|
||||
print(f" [Attempt {attempt}] Trying variant: '{variant}'")
|
||||
captcha_input.clear()
|
||||
captcha_input.send_keys(variant)
|
||||
success = True
|
||||
if attempt_callback:
|
||||
attempt_callback(attempt, captcha_text, variant, "filled")
|
||||
break
|
||||
|
||||
if not success:
|
||||
print(f" [Attempt {attempt}] No valid 6-char variant found for '{captcha_text}', retrying...")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
continue
|
||||
|
||||
# Click submit
|
||||
submit_btn = WebDriverWait(driver, 10).until(
|
||||
EC.element_to_be_clickable((By.XPATH, "//button[@value='Login']"))
|
||||
)
|
||||
submit_btn.click()
|
||||
|
||||
# Wait a bit for response
|
||||
time.sleep(2)
|
||||
|
||||
# Check for invalid captcha error using the exact xpath
|
||||
try:
|
||||
# First handle any alerts that might be open
|
||||
try:
|
||||
alert = driver.switch_to.alert
|
||||
alert_text = alert.text
|
||||
print(f" DEBUG: Alert detected after submit: {alert_text}")
|
||||
if "logout" in alert_text.lower() or "session" in alert_text.lower() or "previous" in alert_text.lower():
|
||||
alert.accept()
|
||||
print(" DEBUG: Accepted session/logout alert")
|
||||
time.sleep(2)
|
||||
except:
|
||||
pass
|
||||
|
||||
invalid_captcha_elem = WebDriverWait(driver, 3).until(
|
||||
EC.visibility_of_element_located((
|
||||
By.XPATH,
|
||||
"//div[@id='error']//strong[contains(normalize-space(), 'Invalid Captcha')]",
|
||||
))
|
||||
)
|
||||
print(f" [Attempt {attempt}] Invalid captcha detected: {invalid_captcha_elem.text}")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
continue
|
||||
except TimeoutException:
|
||||
# No invalid captcha error - likely success
|
||||
print(f" [Attempt {attempt}] No invalid captcha error detected, assuming success")
|
||||
return True
|
||||
except Exception as e:
|
||||
# Handle other exceptions like NoSuchElementException
|
||||
print(f" [Attempt {attempt}] Error checking invalid captcha: {type(e).__name__}: {e}")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f" [Attempt {attempt}] Error: {e}")
|
||||
if attempt < max_retries:
|
||||
refresh_btn = find_refresh_button(driver, captcha_input)
|
||||
if refresh_btn:
|
||||
refresh_btn.click()
|
||||
wait_for_new_captcha(driver, captcha_input)
|
||||
|
||||
return False
|
||||
@@ -46,7 +46,10 @@ def get_setup_configs(mode, config=None):
|
||||
variants = product.get("session_variants")
|
||||
if variants is not None:
|
||||
resolved = get_product_config(name, config)
|
||||
if not resolved["setup"].get("run_session", True):
|
||||
# Expand variants when either session or token execution is
|
||||
# enabled. Token-only UPC runs must still reach their variants.
|
||||
setup = resolved.get("setup", {})
|
||||
if not setup.get("run_session", True) and not setup.get("run_token", True):
|
||||
continue
|
||||
variant_config = deepcopy(config)
|
||||
variant_config["products"] = {}
|
||||
@@ -64,7 +67,10 @@ def get_setup_configs(mode, config=None):
|
||||
session = setup.get("run_session", True)
|
||||
token = setup.get("run_token", True)
|
||||
matches = {
|
||||
"combined": session and token or (name.startswith("UPC_") and token),
|
||||
# The full setup run owns every enabled product that has work to do.
|
||||
# Products without tokens (for example RA) still belong in this
|
||||
# shared browser flow; the combined test skips only their token step.
|
||||
"combined": session or token,
|
||||
"session": session and not token,
|
||||
"token": token and not session,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,30 @@ def pytest_configure(config):
|
||||
config.option.self_contained_html = True
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""Keep the full setup run limited to the shared combined flow."""
|
||||
invocation = " ".join(
|
||||
str(arg).replace("\\", "/") for arg in config.invocation_params.args
|
||||
).lower()
|
||||
full_setup_run = (
|
||||
"tests/setup" in invocation
|
||||
and "tests/setup/sessions" not in invocation
|
||||
and "tests/setup/tokens" not in invocation
|
||||
and "test_session.py" not in invocation
|
||||
and "test_trading_token.py" not in invocation
|
||||
)
|
||||
if not full_setup_run:
|
||||
return
|
||||
|
||||
deselected = [
|
||||
item for item in items
|
||||
if "/tests/setup/sessions/" in f"/{item.nodeid.replace('\\', '/')}"
|
||||
or "/tests/setup/tokens/" in f"/{item.nodeid.replace('\\', '/')}"
|
||||
]
|
||||
items[:] = [item for item in items if item not in deselected]
|
||||
config.hook.pytest_deselected(items=deselected)
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# WebDriver Fixture
|
||||
# -----------------------------
|
||||
|
||||
+219
-35
@@ -57,43 +57,216 @@ def enter_pressed():
|
||||
return pressed
|
||||
|
||||
|
||||
def login(driver, username, password):
|
||||
def check_captcha_error(driver) -> str:
|
||||
"""Check if there's a CAPTCHA error message on the page."""
|
||||
try:
|
||||
# First handle any alerts
|
||||
try:
|
||||
alert = driver.switch_to.alert
|
||||
alert_text = alert.text
|
||||
print(f" DEBUG: Alert detected in check_captcha_error: {alert_text}")
|
||||
if "logout" in alert_text.lower() or "session" in alert_text.lower() or "previous" in alert_text.lower():
|
||||
alert.accept()
|
||||
print(" DEBUG: Accepted session/logout alert")
|
||||
time.sleep(2)
|
||||
except:
|
||||
pass
|
||||
|
||||
error_selectors = [
|
||||
"//div[@id='error']//strong[normalize-space()='Invalid Captcha']",
|
||||
"//div[@id='error']//span[contains(@class,'help-block')]//strong[contains(text(),'Invalid')]",
|
||||
"//*[contains(@class,'error') and contains(text(),'Invalid Captcha')]",
|
||||
]
|
||||
for selector in error_selectors:
|
||||
elements = driver.find_elements(By.XPATH, selector)
|
||||
for el in elements:
|
||||
if el.is_displayed() and el.text.strip():
|
||||
return el.text.strip()
|
||||
except Exception as e:
|
||||
print(f" DEBUG: check_captcha_error exception: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def check_login_success(driver) -> bool:
|
||||
"""Check if login was successful using multiple indicators."""
|
||||
try:
|
||||
# Handle any pending alerts first
|
||||
try:
|
||||
alert = driver.switch_to.alert
|
||||
alert_text = alert.text
|
||||
print(f" DEBUG: Alert detected: {alert_text}")
|
||||
if "logout" in alert_text.lower() or "session" in alert_text.lower() or "previous" in alert_text.lower():
|
||||
alert.accept()
|
||||
print(" DEBUG: Accepted session/logout alert")
|
||||
time.sleep(2)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Wait a bit for page to load after submit
|
||||
time.sleep(3)
|
||||
|
||||
# Print current URL and page title for debugging
|
||||
print(f" DEBUG: Current URL: {driver.current_url}")
|
||||
print(f" DEBUG: Page title: {driver.title}")
|
||||
|
||||
# Multiple dashboard indicators
|
||||
dashboard_indicators = [
|
||||
"//span[normalize-space()='TRADING MAINTENANCE']",
|
||||
"//a[contains(@href, 'tradingMaintenance')]",
|
||||
"//span[contains(text(), 'Dashboard')]",
|
||||
"//h1[contains(text(), 'Dashboard')]",
|
||||
"//div[contains(@class, 'dashboard')]",
|
||||
"//a[contains(text(), 'Trading')]",
|
||||
"//div[contains(@class, 'navbar')]//span[contains(text(), 'Trading')]",
|
||||
]
|
||||
|
||||
wait = WebDriverWait(driver, 15)
|
||||
for indicator in dashboard_indicators:
|
||||
try:
|
||||
wait.until(EC.visibility_of_element_located((By.XPATH, indicator)))
|
||||
print(f" DEBUG: Found dashboard indicator: {indicator}")
|
||||
return True
|
||||
except:
|
||||
continue
|
||||
|
||||
# URL-based check
|
||||
if "home" in driver.current_url.lower() or "dashboard" in driver.current_url.lower():
|
||||
print(" DEBUG: Login success detected via URL")
|
||||
return True
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" DEBUG: check_login_success failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def login(
|
||||
driver,
|
||||
username,
|
||||
password,
|
||||
max_captcha_retries=5,
|
||||
captcha_metrics=None,
|
||||
max_login_duration_seconds=1200,
|
||||
):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
# username
|
||||
userid_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.userid))
|
||||
)
|
||||
userid_field.send_keys(username)
|
||||
# --- AUTOMATED CAPTCHA SOLVING WITH RETRY ON FAILURE ---
|
||||
from tests.captcha_solver import solve_and_fill_captcha, find_refresh_button, wait_for_new_captcha
|
||||
|
||||
# password
|
||||
password_field = driver.find_element(By.XPATH, Locator.password)
|
||||
password_field.send_keys(password)
|
||||
login_deadline = time.monotonic() + max_login_duration_seconds
|
||||
captcha_attempt = 0
|
||||
while True:
|
||||
captcha_attempt += 1
|
||||
|
||||
print(f"Solve captcha within {LOGIN_CAPTCHA_WAIT_SECONDS} seconds...")
|
||||
# Refreshing after a failed CAPTCHA clears the login form, so refill
|
||||
# both credentials before every automatic or manual retry.
|
||||
userid_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.userid))
|
||||
)
|
||||
userid_field.clear()
|
||||
userid_field.send_keys(username)
|
||||
|
||||
# wait for manual captcha solve
|
||||
time.sleep(LOGIN_CAPTCHA_WAIT_SECONDS)
|
||||
password_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.password))
|
||||
)
|
||||
password_field.clear()
|
||||
password_field.send_keys(password)
|
||||
|
||||
# click submit
|
||||
submit_btn = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.submitbtn))
|
||||
)
|
||||
submit_btn.click()
|
||||
if captcha_metrics is not None:
|
||||
captcha_metrics["tries"] = captcha_attempt
|
||||
captcha_metrics["max_retries"] = max_captcha_retries
|
||||
print(f"Solving CAPTCHA (attempt {captcha_attempt})...")
|
||||
|
||||
time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS)
|
||||
# Once automatic solving has run for the configured duration, ask for
|
||||
# the CAPTCHA value in a browser prompt and continue the same login flow.
|
||||
if time.monotonic() >= login_deadline:
|
||||
print("Automatic CAPTCHA retries timed out; waiting for manual CAPTCHA entry.")
|
||||
manual_value = driver.execute_script(
|
||||
"return window.prompt('Automatic CAPTCHA solving timed out. Enter the CAPTCHA shown on the page:');"
|
||||
)
|
||||
if not manual_value:
|
||||
raise RuntimeError("Manual CAPTCHA entry was cancelled")
|
||||
|
||||
accept_browser_alert_if_present(driver)
|
||||
captcha_input = wait.until(
|
||||
EC.visibility_of_element_located((By.NAME, "captcha"))
|
||||
)
|
||||
captcha_input.clear()
|
||||
captcha_input.send_keys(manual_value.strip())
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, Locator.submitbtn))).click()
|
||||
time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS)
|
||||
accept_browser_alert_if_present(driver)
|
||||
handle_transient_popups(driver)
|
||||
|
||||
# wait for dashboard
|
||||
wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.dashboard_marker))
|
||||
)
|
||||
captcha_error = check_captcha_error(driver)
|
||||
if captcha_error:
|
||||
print(f" Manual CAPTCHA rejected: {captcha_error}")
|
||||
continue
|
||||
|
||||
close_tac_expiry_modal_if_present(driver)
|
||||
if check_login_success(driver):
|
||||
print("Login successful after manual CAPTCHA entry")
|
||||
if captcha_metrics is not None:
|
||||
captcha_metrics["correct_attempt"] = captcha_attempt
|
||||
captcha_metrics.setdefault("captcha_attempts", []).append({
|
||||
"attempt": captcha_attempt,
|
||||
"captcha_text": "manual",
|
||||
"variant": manual_value.strip(),
|
||||
"status": "manual",
|
||||
"screenshot": "",
|
||||
"error": "",
|
||||
})
|
||||
close_tac_expiry_modal_if_present(driver)
|
||||
return True
|
||||
continue
|
||||
|
||||
print("Login successful")
|
||||
def attempt_callback(attempt_num, captcha_text, variant, status, screenshot_path="", error=""):
|
||||
if captcha_metrics is not None:
|
||||
captcha_metrics.setdefault("captcha_attempts", []).append({
|
||||
"attempt": attempt_num,
|
||||
"captcha_text": captcha_text,
|
||||
"variant": variant,
|
||||
"status": status,
|
||||
"screenshot": screenshot_path,
|
||||
"error": error
|
||||
})
|
||||
|
||||
if not solve_and_fill_captcha(driver, max_retries=1, attempt_callback=attempt_callback):
|
||||
print(" Automatic CAPTCHA solve failed; retrying until the login deadline.")
|
||||
driver.refresh()
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
# solve_and_fill_captcha already clicks submit, so just wait and check result
|
||||
time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS)
|
||||
|
||||
accept_browser_alert_if_present(driver)
|
||||
|
||||
# Handle any popups that appear
|
||||
handle_transient_popups(driver)
|
||||
|
||||
# Check for CAPTCHA error (invalid captcha message)
|
||||
captcha_error = check_captcha_error(driver)
|
||||
if captcha_error:
|
||||
print(f" CAPTCHA validation failed: {captcha_error}")
|
||||
# Reload the page to get a fresh CAPTCHA and keep retrying until
|
||||
# the overall login deadline is reached.
|
||||
print(f" Reloading page to get fresh CAPTCHA (attempt {captcha_attempt + 1})...")
|
||||
driver.refresh()
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
# Check for successful login - use improved detection
|
||||
if check_login_success(driver):
|
||||
print("Login successful")
|
||||
if captcha_metrics is not None:
|
||||
captcha_metrics["correct_attempt"] = captcha_attempt
|
||||
close_tac_expiry_modal_if_present(driver)
|
||||
return True
|
||||
|
||||
# If we get here, something else is wrong
|
||||
print(" Login not confirmed, retrying...")
|
||||
driver.refresh()
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def accept_browser_alert_if_present(driver, timeout=5):
|
||||
@@ -309,23 +482,34 @@ def open_trading_maintenance_page(driver, menu_xpath):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
trading_maintenance = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, Locator.tradingmaintenance))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", trading_maintenance)
|
||||
def visible_elements(xpath):
|
||||
return [element for element in driver.find_elements(By.XPATH, xpath) if element.is_displayed()]
|
||||
|
||||
submenu = driver.find_elements(By.XPATH, menu_xpath)
|
||||
submenu_visible = any(item.is_displayed() for item in submenu)
|
||||
submenu_visible = any(
|
||||
item.is_enabled() for item in visible_elements(menu_xpath)
|
||||
)
|
||||
|
||||
if not submenu_visible:
|
||||
maintenance_items = visible_elements(Locator.tradingmaintenance)
|
||||
if not maintenance_items:
|
||||
wait.until(lambda current_driver: visible_elements(Locator.tradingmaintenance))
|
||||
maintenance_items = visible_elements(Locator.tradingmaintenance)
|
||||
|
||||
maintenance = maintenance_items[0]
|
||||
driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block: 'center'});", maintenance
|
||||
)
|
||||
try:
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, Locator.tradingmaintenance)))
|
||||
trading_maintenance.click()
|
||||
wait.until(lambda current_driver: maintenance.is_enabled())
|
||||
maintenance.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", trading_maintenance)
|
||||
driver.execute_script("arguments[0].click();", maintenance)
|
||||
|
||||
menu_item = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, menu_xpath))
|
||||
lambda current_driver: next(
|
||||
(item for item in visible_elements(menu_xpath) if item.is_enabled()),
|
||||
False,
|
||||
)
|
||||
)
|
||||
try:
|
||||
menu_item.click()
|
||||
|
||||
+15
-2
@@ -4,11 +4,13 @@ class Locator(object):
|
||||
userid="//input[@placeholder='User ID']"
|
||||
password="//input[@placeholder='Password']"
|
||||
submitbtn="//button[@value='Login']"
|
||||
dashboard_marker="//span[normalize-space()='TRADING MAINTENANCE']"
|
||||
dashboard_marker="//*[contains(normalize-space(), 'TRADING MAINTENANCE')]"
|
||||
tacexpiryclosebtn="//div[contains(@class,'modal') and contains(@style,'display: block')]//button[contains(@class,'close')]"
|
||||
usermenutoggle="//li[contains(@class,'dropdown') and contains(@class,'navbar-user')]//a[contains(@class,'dropdown-toggle') and @data-toggle='dropdown']"
|
||||
logoutlink="//li[contains(@class,'dropdown') and contains(@class,'navbar-user')]//ul[contains(@class,'dropdown-menu')]//a[@ng-click='logout()' and normalize-space()='Log Out']"
|
||||
tradingmaintenance="//span[normalize-space()='TRADING MAINTENANCE']"
|
||||
# Click the Angular menu anchor, not the nested label span. The anchor
|
||||
# owns openSubMenu(item), which is required to render its child links.
|
||||
tradingmaintenance="//a[.//span[normalize-space()='TRADING MAINTENANCE']]"
|
||||
tradingsession="//a[normalize-space()='Trading Session Master']"
|
||||
tokengeneration="//a[@href='#/tradingMaintenance/generateToken' or normalize-space()='Token Generation']"
|
||||
tokengenerator = "//a[@href='#/tradingMaintenance/tokenGenerator']"
|
||||
@@ -36,6 +38,17 @@ class Locator(object):
|
||||
tokendeliverystartdate = "//input[@placeholder='DELIVERY_START_DATE']"
|
||||
tokendeliveryenddate = "//input[@placeholder='DELIVERY_END_DATE']"
|
||||
tokenexpirydate = "//input[@placeholder='Token Expiry Date']"
|
||||
tokentype = "//input[@name='TYPE' or @placeholder='TYPE']"
|
||||
intradaydeliveryfromdate = "//input[@placeholder='Delivery From Date']"
|
||||
intradaydeliverytodate = "//input[@placeholder='Delivery To Date']"
|
||||
intradaytradingstarttime = "//input[@placeholder='Trading Start Time']"
|
||||
intradaydaysbeforedelivery = "//input[@placeholder='Trading Start days before delivery']"
|
||||
intradaydurationbeforeend = "//input[@placeholder='Duration before delivery end time']"
|
||||
intradaysuccessmessage = "//*[contains(normalize-space(text()), 'Tokens generated successfully')]"
|
||||
dactradingfromdate = "//input[@placeholder='Trading From Date']"
|
||||
dactradingtodate = "//input[@placeholder='Trading To Date']"
|
||||
dacsessionstarttime = "//input[@placeholder='Session Start Time']"
|
||||
dacsessionendtime = "//input[@placeholder='Session End Time']"
|
||||
tokentradedate = "//input[@placeholder='Trade Date']"
|
||||
upcordernormalstart = "//input[@name='ses3StartTime']"
|
||||
upcordernormalend = "//input[@name='ses3EndTime']"
|
||||
|
||||
@@ -13,13 +13,21 @@ from tests.locators import Locator
|
||||
def get_session_window(config):
|
||||
|
||||
session_config = config.get("setup", {}).get("session", {})
|
||||
start_offset_minutes = session_config.get("start_offset_minutes", 7)
|
||||
time_mode = session_config.get("time_mode", "offset").lower()
|
||||
if time_mode == "manual":
|
||||
start_time = session_config.get("start_time")
|
||||
if not start_time:
|
||||
raise ValueError("Session time_mode 'manual' requires start_time")
|
||||
elif time_mode == "offset":
|
||||
start_offset_minutes = session_config.get("start_offset_minutes", 7)
|
||||
start_time = (datetime.now() + timedelta(minutes=start_offset_minutes)).strftime("%H:%M")
|
||||
else:
|
||||
raise ValueError("Session time_mode must be 'manual' or 'offset'")
|
||||
end_time = session_config.get("end_time", "20:30")
|
||||
|
||||
start_datetime = datetime.now() + timedelta(minutes=start_offset_minutes)
|
||||
today = datetime.now().strftime("%d/%m/%Y")
|
||||
|
||||
return start_datetime.strftime("%d/%m/%Y %H:%M"), f"{today} {end_time}"
|
||||
return f"{today} {start_time}", f"{today} {end_time}"
|
||||
|
||||
|
||||
def create_session(driver, step, config):
|
||||
@@ -44,7 +52,13 @@ def create_session(driver, step, config):
|
||||
|
||||
@pytest.mark.session
|
||||
@pytest.mark.parametrize("config", get_setup_configs("session"), ids=lambda c: c["product_name"])
|
||||
def test_session(driver, test_context, config):
|
||||
def test_session(driver, test_context, config, request):
|
||||
|
||||
# The full setup command runs session-only products (such as RA) through
|
||||
# test_combined_setup.py so they share the same browser/login context.
|
||||
invocation = " ".join(str(arg).replace("\\", "/") for arg in request.config.invocation_params.args)
|
||||
if "tests/setup" in invocation and "tests/setup/sessions" not in invocation and "test_session.py" not in invocation:
|
||||
pytest.skip("Handled by the shared full setup flow")
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ def test_session_and_token_setup(driver, test_context, config):
|
||||
if config.get("setup", {}).get("run_session", True):
|
||||
create_session(driver, step, config)
|
||||
|
||||
for product_code in get_product_codes(token):
|
||||
product_token = dict(token)
|
||||
product_token["product_code"] = product_code
|
||||
run_token_setup(driver, step, product_token)
|
||||
if config.get("setup", {}).get("run_token", True):
|
||||
for product_code in get_product_codes(token):
|
||||
product_token = dict(token)
|
||||
product_token["product_code"] = product_code
|
||||
run_token_setup(driver, step, product_token)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
||||
import pytest
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from tests.config_reader import get_setup_configs, parse_product_codes
|
||||
from tests.helpers import (
|
||||
@@ -55,15 +56,28 @@ def get_delivery_date_and_expiry(token):
|
||||
return delivery_date, token_expiry
|
||||
|
||||
|
||||
def get_intraday_delivery_window(token, now=None):
|
||||
"""Return an Intraday delivery window that cannot start in the past."""
|
||||
now = datetime.now() if now is None else now
|
||||
start = now + timedelta(minutes=token.get("delivery_start_offset_minutes", 5))
|
||||
start_text = start.strftime("%d/%m/%Y %H:%M")
|
||||
end_text = f"{start.strftime('%d/%m/%Y')} {token.get('delivery_to_time', '23:59')}"
|
||||
return start_text, end_text
|
||||
|
||||
|
||||
def get_time_session_times(token, row_text, default_start_time, default_end_time):
|
||||
|
||||
time_session_config = token.get("time_sessions", {}).get(row_text, {})
|
||||
time_mode = time_session_config.get("time_mode")
|
||||
start_time = time_session_config.get("start_time")
|
||||
|
||||
if start_time is None:
|
||||
if time_mode == "offset":
|
||||
start_offset_minutes = time_session_config.get("start_offset_minutes")
|
||||
if start_offset_minutes is not None:
|
||||
start_time = (datetime.now() + timedelta(minutes=start_offset_minutes)).strftime("%H:%M")
|
||||
if start_offset_minutes is None:
|
||||
raise ValueError(f"{row_text} offset mode requires start_offset_minutes")
|
||||
start_time = (datetime.now() + timedelta(minutes=start_offset_minutes)).strftime("%H:%M")
|
||||
elif time_mode == "manual" and not start_time:
|
||||
raise ValueError(f"{row_text} manual mode requires start_time")
|
||||
|
||||
return (
|
||||
start_time or default_start_time,
|
||||
@@ -71,6 +85,29 @@ def get_time_session_times(token, row_text, default_start_time, default_end_time
|
||||
)
|
||||
|
||||
|
||||
def get_upc_order_normal_start_time(timings, now=None):
|
||||
"""Resolve UPC order-normal start time from manual or offset mode."""
|
||||
mode = timings.get("time_mode", "manual").lower()
|
||||
if mode == "offset":
|
||||
offset = timings.get("order_normal_start_offset_minutes")
|
||||
if offset is None:
|
||||
raise ValueError(
|
||||
"UPC time_mode 'offset' requires "
|
||||
"order_normal_start_offset_minutes"
|
||||
)
|
||||
now = datetime.now() if now is None else now
|
||||
return (now + timedelta(minutes=offset)).strftime("%H:%M")
|
||||
if mode != "manual":
|
||||
raise ValueError("UPC time_mode must be 'manual' or 'offset'")
|
||||
|
||||
start_time = timings.get("order_normal_start_time")
|
||||
if not start_time:
|
||||
raise ValueError(
|
||||
"UPC time_mode 'manual' requires order_normal_start_time"
|
||||
)
|
||||
return start_time
|
||||
|
||||
|
||||
def get_product_codes(token):
|
||||
|
||||
product_codes = parse_product_codes(token.get("create_product_codes", token.get("product_codes")))
|
||||
@@ -133,11 +170,11 @@ def complete_time_session_edits(driver, step, token, next_day):
|
||||
edit_time_session_row(
|
||||
driver,
|
||||
step,
|
||||
"ORDER_NOR",
|
||||
"ORDER_NORMAL",
|
||||
order_nor_start_time,
|
||||
order_nor_end_time
|
||||
)
|
||||
edit_time_session_row(driver, step, "ORDER_EXCH", order_exch_start_time, order_exch_end_time)
|
||||
edit_time_session_row(driver, step, "ORDER_EXCHANGE", order_exch_start_time, order_exch_end_time)
|
||||
edit_time_session_row(driver, step, "TRADE", trade_start_time, trade_end_time)
|
||||
|
||||
click_first_grid_button(driver, "Token Details")
|
||||
@@ -150,6 +187,7 @@ def complete_time_session_edits(driver, step, token, next_day):
|
||||
def complete_time_session_edits_from_saved_token(driver, step, token):
|
||||
|
||||
step.click((By.XPATH, Locator.tokentimesessionbtn), "open_time_session")
|
||||
WebDriverWait(driver, 20).until(EC.url_contains("timeSessionView"))
|
||||
click_first_grid_button(driver, "Time Session Details")
|
||||
step.capture("open_time_session_details")
|
||||
step.capture("before_edit_order_normal")
|
||||
@@ -166,11 +204,11 @@ def complete_time_session_edits_from_saved_token(driver, step, token):
|
||||
edit_time_session_row(
|
||||
driver,
|
||||
step,
|
||||
"ORDER_NOR",
|
||||
"ORDER_NORMAL",
|
||||
order_nor_start_time,
|
||||
order_nor_end_time
|
||||
)
|
||||
edit_time_session_row(driver, step, "ORDER_EXCH", order_exch_start_time, order_exch_end_time)
|
||||
edit_time_session_row(driver, step, "ORDER_EXCHANGE", order_exch_start_time, order_exch_end_time)
|
||||
edit_time_session_row(driver, step, "TRADE", trade_start_time, trade_end_time)
|
||||
|
||||
click_first_grid_button(driver, "Token Details")
|
||||
@@ -201,10 +239,19 @@ def open_rtm_new_token(driver, step, token):
|
||||
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")
|
||||
time_mode = token.get("time_mode", "offset").lower()
|
||||
if time_mode == "manual":
|
||||
session_start_time = token.get("session_start_time")
|
||||
if not session_start_time:
|
||||
raise ValueError("RTM time_mode 'manual' requires session_start_time")
|
||||
elif time_mode == "offset":
|
||||
session_start_time = (now + timedelta(minutes=token.get("session_start_offset_minutes", 5))).strftime("%H:%M")
|
||||
else:
|
||||
raise ValueError("RTM time_mode must be 'manual' or 'offset'")
|
||||
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"),
|
||||
session_start_time,
|
||||
token.get("session_end_time", "20:00"),
|
||||
)
|
||||
|
||||
@@ -225,6 +272,76 @@ def create_rtm_token(driver, step, token):
|
||||
handle_transient_popups(driver)
|
||||
|
||||
|
||||
def create_intraday_token(driver, step, token):
|
||||
"""Create an Intraday token through the Token Generator page."""
|
||||
open_trading_maintenance_page(driver, Locator.tokengenerator)
|
||||
handle_transient_popups(driver)
|
||||
step.capture("open_token_generator_intraday")
|
||||
|
||||
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}_intraday")
|
||||
|
||||
step.click((By.XPATH, Locator.addnewtokenbtn), "click_add_new_token_intraday")
|
||||
|
||||
delivery_start, delivery_end = get_intraday_delivery_window(token)
|
||||
values = (
|
||||
(Locator.intradaydeliveryfromdate, delivery_start, "delivery_from_date"),
|
||||
(Locator.intradaydeliverytodate, delivery_end, "delivery_to_date"),
|
||||
(Locator.intradaytradingstarttime, token.get("trading_start_time", "00:20"), "trading_start_time"),
|
||||
(Locator.intradaydaysbeforedelivery, str(token.get("trading_start_days_before_delivery", 1)), "days_before_delivery"),
|
||||
)
|
||||
for locator, value, name in values:
|
||||
type_first_visible_input(driver, locator, value)
|
||||
step.capture(f"enter_{name}_intraday")
|
||||
|
||||
step.click((By.XPATH, Locator.savetokenbtn), "save_token_intraday")
|
||||
WebDriverWait(driver, 30).until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.intradaysuccessmessage))
|
||||
)
|
||||
step.capture("intraday_token_generated_successfully")
|
||||
handle_transient_popups(driver)
|
||||
|
||||
|
||||
def create_dac_token(driver, step, token):
|
||||
"""Create a DAC token through the Token Generator page."""
|
||||
open_trading_maintenance_page(driver, Locator.tokengenerator)
|
||||
handle_transient_popups(driver)
|
||||
step.capture("open_token_generator_dac")
|
||||
|
||||
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}_dac")
|
||||
|
||||
step.click((By.XPATH, Locator.addnewtokenbtn), "click_add_new_token_dac")
|
||||
|
||||
trading_start, trading_end = get_intraday_delivery_window(token)
|
||||
values = (
|
||||
(Locator.dactradingfromdate, trading_start, "trading_from_date"),
|
||||
(Locator.dactradingtodate, trading_end, "trading_to_date"),
|
||||
(Locator.dacsessionstarttime, token.get("session_start_time", "13:00"), "session_start_time"),
|
||||
(Locator.dacsessionendtime, token.get("session_end_time", "23:00"), "session_end_time"),
|
||||
)
|
||||
for locator, value, name in values:
|
||||
type_first_visible_input(driver, locator, value)
|
||||
step.capture(f"enter_{name}_dac")
|
||||
|
||||
step.click((By.XPATH, Locator.savetokenbtn), "save_token_dac")
|
||||
WebDriverWait(driver, 30).until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.intradaysuccessmessage))
|
||||
)
|
||||
step.capture("dac_token_generated_successfully")
|
||||
handle_transient_popups(driver)
|
||||
|
||||
|
||||
def create_upc_token(driver, step, token):
|
||||
open_trading_maintenance_page(driver, Locator.tokengenerator)
|
||||
handle_transient_popups(driver)
|
||||
@@ -238,7 +355,7 @@ def create_upc_token(driver, step, token):
|
||||
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_start", Locator.upcorderstart, get_upc_order_normal_start_time(timings)),
|
||||
("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")),
|
||||
@@ -252,6 +369,12 @@ def create_upc_token(driver, step, token):
|
||||
|
||||
|
||||
def run_token_setup(driver, step, token):
|
||||
if token.get("product_type", "").upper() == "DAC":
|
||||
create_dac_token(driver, step, token)
|
||||
return
|
||||
if token.get("product_type", "").upper() == "INTRADAY":
|
||||
create_intraday_token(driver, step, token)
|
||||
return
|
||||
if token.get("product_type", "").upper() == "UPC":
|
||||
create_upc_token(driver, step, token)
|
||||
return
|
||||
@@ -289,16 +412,23 @@ def create_and_activate_token(driver, step, token):
|
||||
|
||||
step.click((By.XPATH, Locator.addnewtokenbtn), f"click_add_new_token_{product_code.lower()}")
|
||||
|
||||
next_day, token_expiry = get_delivery_date_and_expiry(token)
|
||||
is_intraday = token.get("product_type", "").upper() == "INTRADAY"
|
||||
if is_intraday:
|
||||
delivery_start, delivery_end = get_intraday_delivery_window(token)
|
||||
else:
|
||||
next_day, token_expiry = get_delivery_date_and_expiry(token)
|
||||
|
||||
if token.get("product_type", "").upper() == "REC":
|
||||
token_type_prefix = token.get("type_prefix", "REC")
|
||||
token_type = f"{token_type_prefix}-{datetime.now().strftime('%d%m%Y')}"
|
||||
step.type((By.XPATH, Locator.tokentype), token_type, f"enter_type_{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")
|
||||
normal_start = get_upc_order_normal_start_time(timings)
|
||||
values = {
|
||||
Locator.upcordernormalstart: normal_start,
|
||||
Locator.upcordernormalend: timings.get("order_normal_end_time"),
|
||||
@@ -310,9 +440,17 @@ def create_and_activate_token(driver, step, token):
|
||||
continue
|
||||
type_first_visible_input(driver, locator, value)
|
||||
|
||||
if token.get("generation_mode") not in ("WeeklyUPC", "MonthlyUPC"):
|
||||
if is_intraday:
|
||||
step.type((By.XPATH, Locator.tokendeliverystartdate), delivery_start, f"enter_delivery_start_date_{product_code.lower()}")
|
||||
step.type((By.XPATH, Locator.tokendeliveryenddate), delivery_end, f"enter_delivery_end_date_{product_code.lower()}")
|
||||
elif (
|
||||
token.get("generation_mode") not in ("WeeklyUPC", "MonthlyUPC")
|
||||
and token.get("product_type", "").upper() != "REC"
|
||||
):
|
||||
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()}")
|
||||
|
||||
if not is_intraday and token.get("generation_mode") not in ("WeeklyUPC", "MonthlyUPC"):
|
||||
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()}")
|
||||
@@ -322,12 +460,19 @@ def create_and_activate_token(driver, step, token):
|
||||
step.click((By.XPATH, Locator.tokengeneratorresetbtn), f"reset_token_generator_{product_code.lower()}")
|
||||
return
|
||||
|
||||
complete_time_session_edits_from_saved_token(driver, step, token)
|
||||
if not is_intraday:
|
||||
complete_time_session_edits_from_saved_token(driver, step, token)
|
||||
|
||||
|
||||
@pytest.mark.trading_token
|
||||
@pytest.mark.parametrize("config", get_setup_configs("token"), ids=lambda c: c["product_name"])
|
||||
def test_trading_token_creation(driver, test_context, config):
|
||||
def test_trading_token_creation(driver, test_context, config, request):
|
||||
|
||||
# The full setup command runs token-enabled products through the combined
|
||||
# flow so session and token creation use one browser/login context.
|
||||
invocation = " ".join(str(arg).replace("\\", "/") for arg in request.config.invocation_params.args)
|
||||
if "tests/setup" in invocation and "tests/setup/tokens" not in invocation and "test_trading_token.py" not in invocation:
|
||||
pytest.skip("Handled by the shared full setup flow")
|
||||
|
||||
config, step, token = get_token_context(driver, test_context, config)
|
||||
open_app_and_login(driver, step, config)
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import csv
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from tests.browser import create_chrome_driver
|
||||
from tests.config_reader import load_config
|
||||
from tests.helpers import login, logout
|
||||
from tests.locators import Locator
|
||||
|
||||
|
||||
LOGIN_ATTEMPTS = 50
|
||||
MAX_CAPTCHA_RETRIES = 7
|
||||
|
||||
|
||||
@pytest.mark.captcha
|
||||
def test_captcha_login_stress():
|
||||
"""Run bounded EasyOCR login attempts and retain auditable CAPTCHA evidence."""
|
||||
config = load_config()
|
||||
output_dir = os.path.abspath(os.path.join("reports", "captcha_login_stress"))
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
started = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
csv_path = os.path.join(output_dir, f"captcha_login_{started}.csv")
|
||||
rows = []
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
def record(row):
|
||||
rows.append(row)
|
||||
|
||||
for login_number in range(1, LOGIN_ATTEMPTS + 1):
|
||||
login_started = datetime.now().isoformat(timespec="seconds")
|
||||
captcha_rows = []
|
||||
|
||||
driver = create_chrome_driver()
|
||||
try:
|
||||
driver.get(config["url"])
|
||||
|
||||
login_status = "FAILED"
|
||||
logout_status = "NOT_ATTEMPTED"
|
||||
error_text = ""
|
||||
captcha_metrics = {"captcha_attempts": []}
|
||||
try:
|
||||
login(
|
||||
driver,
|
||||
config["login"]["username"],
|
||||
config["login"]["password"],
|
||||
max_captcha_retries=MAX_CAPTCHA_RETRIES,
|
||||
captcha_metrics=captcha_metrics,
|
||||
max_login_duration_seconds=1200,
|
||||
)
|
||||
login_status = "PASSED"
|
||||
passed += 1
|
||||
except Exception as exc:
|
||||
failed += 1
|
||||
error_text = f"{type(exc).__name__}: {exc}"
|
||||
try:
|
||||
driver.save_screenshot(os.path.join(output_dir, f"login_{login_number:03d}_failure.png"))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# Explicitly log out after a successful authenticated flow,
|
||||
# then close the browser session.
|
||||
if login_status == "PASSED":
|
||||
try:
|
||||
logout_status = "PASSED" if logout(driver) else "FAILED"
|
||||
except Exception as exc:
|
||||
logout_status = "FAILED"
|
||||
print(f"Logout error: {type(exc).__name__}: {exc}")
|
||||
|
||||
try:
|
||||
driver.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
captcha_rows = captcha_metrics.get("captcha_attempts", []) or [{
|
||||
"attempt": 0, "captcha_text": "", "variant": "",
|
||||
"status": "no_captcha_record", "screenshot": "", "error": error_text,
|
||||
}]
|
||||
for item in captcha_rows:
|
||||
record({
|
||||
"login_attempt": login_number,
|
||||
"started_at": login_started,
|
||||
"login_status": login_status,
|
||||
"logout_status": logout_status,
|
||||
"captcha_attempt": item["attempt"],
|
||||
"attempted_captcha": item["variant"],
|
||||
"ocr_captcha": item["captcha_text"],
|
||||
"screenshot_path": item["screenshot"],
|
||||
"captcha_status": item["status"],
|
||||
"captcha_entered_count": sum(
|
||||
1 for captcha_row in captcha_rows if captcha_row["variant"]
|
||||
),
|
||||
"error": item["error"] or error_text,
|
||||
"attempts_to_result": len(captcha_rows),
|
||||
"passed_count": passed,
|
||||
"failed_count": failed,
|
||||
})
|
||||
finally:
|
||||
# Clean up the driver after all attempts
|
||||
try:
|
||||
driver.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
fieldnames = [
|
||||
"login_attempt", "started_at", "login_status", "captcha_attempt",
|
||||
"logout_status",
|
||||
"attempted_captcha", "ocr_captcha", "screenshot_path", "captcha_status",
|
||||
"captcha_entered_count", "error", "attempts_to_result", "passed_count",
|
||||
"failed_count",
|
||||
]
|
||||
with open(csv_path, "w", newline="", encoding="utf-8") as report:
|
||||
writer = csv.DictWriter(report, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
print(f"CAPTCHA stress report: {csv_path}")
|
||||
print(f"Passed: {passed}; Failed: {failed}; Total: {LOGIN_ATTEMPTS}")
|
||||
@@ -78,3 +78,38 @@ def test_upc_session_selection_and_shared_timing(selected):
|
||||
assert config == original
|
||||
config["products"]["UPC"]["enabled"] = False
|
||||
assert get_setup_configs("session", config) == []
|
||||
|
||||
|
||||
def test_upc_token_only_expands_all_enabled_variants():
|
||||
config = {
|
||||
"setup": {"run_session": True, "run_token": True},
|
||||
"products": {
|
||||
"UPC": {
|
||||
"enabled": True,
|
||||
"run_session": False,
|
||||
"run_token": True,
|
||||
"token": {
|
||||
"market_code": "NATIONAL",
|
||||
"product_type": "UPC",
|
||||
"product_codes": [],
|
||||
},
|
||||
"session_variants": {
|
||||
name: {
|
||||
"enabled": True,
|
||||
"run_session": False,
|
||||
"run_token": True,
|
||||
"token": {"create_product_codes": [f"{name}_TOKEN"]},
|
||||
}
|
||||
for name in ("DAILY", "WEEKLY", "MONTHLY")
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
configs = get_setup_configs("token", config)
|
||||
|
||||
assert [item["product_name"] for item in configs] == [
|
||||
"UPC_DAILY",
|
||||
"UPC_WEEKLY",
|
||||
"UPC_MONTHLY",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user