pi-lean-dimension 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/node_modules/pi-lean-portal/AGENTS.md +15 -23
  3. package/node_modules/pi-lean-portal/README.md +31 -1
  4. package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +12 -1
  5. package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +263 -1102
  6. package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +6 -32
  7. package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +1 -1
  8. package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +1 -2
  9. package/node_modules/pi-lean-portal/__tests__/helpers/setup-temp-dir.ts +26 -0
  10. package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +1 -1
  11. package/node_modules/pi-lean-portal/__tests__/plugin-contract.test.ts +1 -1
  12. package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +1 -1
  13. package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +1 -1
  14. package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +65 -69
  15. package/node_modules/pi-lean-portal/__tests__/router-dispatch.test.ts +1 -14
  16. package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +31 -16
  17. package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +1 -1
  18. package/node_modules/pi-lean-portal/__tests__/web-guides.test.ts +146 -19
  19. package/node_modules/pi-lean-portal/backends/chromium-py/__pycache__/bridge.cpython-313.pyc +0 -0
  20. package/node_modules/pi-lean-portal/backends/firefox-py/__pycache__/bridge.cpython-313.pyc +0 -0
  21. package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +1 -1
  22. package/node_modules/pi-lean-portal/backends/python-adapter.ts +3 -3
  23. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +2 -4
  24. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  26. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-312.pyc +0 -0
  27. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
  28. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
  29. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +572 -81
  30. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
  31. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313-pytest-9.1.1.pyc +0 -0
  32. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.1.pyc +0 -0
  33. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313-pytest-9.1.1.pyc +0 -0
  34. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
  35. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_py_bridges.cpython-313-pytest-9.1.1.pyc +0 -0
  36. package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +13 -43
  37. package/node_modules/pi-lean-portal/browser-profile.ts +2 -3
  38. package/node_modules/pi-lean-portal/browser-toggle.ts +168 -380
  39. package/node_modules/pi-lean-portal/contributed/CHOOSING.md +2 -2
  40. package/node_modules/pi-lean-portal/contributed/README.md +65 -6
  41. package/node_modules/pi-lean-portal/contributed/camoufox-py/__pycache__/bridge.cpython-312.pyc +0 -0
  42. package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +108 -14
  43. package/node_modules/pi-lean-portal/contributed/camoufox-py/pin.json +4 -0
  44. package/node_modules/pi-lean-portal/contributed/invisible-py/bridge.py +1 -1
  45. package/node_modules/pi-lean-portal/core/fetch-backend.ts +10 -30
  46. package/node_modules/pi-lean-portal/core/guides.ts +14 -18
  47. package/node_modules/pi-lean-portal/core/router.ts +2 -20
  48. package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +19 -30
  49. package/node_modules/pi-lean-portal/core/shared/browser-data.ts +4 -4
  50. package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +2 -2
  51. package/node_modules/pi-lean-portal/core/shared/paths.ts +16 -2
  52. package/node_modules/pi-lean-portal/core/shared/settings-reader.ts +2 -7
  53. package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +1 -1
  54. package/node_modules/pi-lean-portal/index.ts +8 -1
  55. package/node_modules/pi-lean-portal/package.json +2 -1
  56. package/node_modules/pi-lean-portal/tools/web-learn.ts +1 -4
  57. package/node_modules/pi-lean-search/__tests__/web-search.test.ts +87 -0
  58. package/node_modules/pi-lean-search/index.ts +134 -64
  59. package/node_modules/pi-lean-search/package.json +2 -1
  60. package/node_modules/playwright/README.md +3 -3
  61. package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +2 -2
  62. package/node_modules/playwright/lib/cli/reportActions.js +2 -2
  63. package/node_modules/playwright/lib/common/index.js +77 -52
  64. package/node_modules/playwright/lib/common/index.js.txt +10 -11
  65. package/node_modules/playwright/lib/errorContext.js +2 -3
  66. package/node_modules/playwright/lib/index.js +30 -8
  67. package/node_modules/playwright/lib/matchers/expect.js +4811 -4379
  68. package/node_modules/playwright/lib/matchers/expect.js.LICENSE +87 -140
  69. package/node_modules/playwright/lib/matchers/expect.js.txt +30 -35
  70. package/node_modules/playwright/lib/program.js +19 -0
  71. package/node_modules/playwright/lib/runner/index.js +309 -153
  72. package/node_modules/playwright/lib/runner/index.js.txt +13 -12
  73. package/node_modules/playwright/lib/transform/babelBundle.js +12899 -11654
  74. package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +379 -354
  75. package/node_modules/playwright/lib/transform/babelBundle.js.txt +39 -39
  76. package/node_modules/playwright/lib/transform/esmLoader.js +3129 -1116
  77. package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +81 -61
  78. package/node_modules/playwright/lib/transform/esmLoader.js.txt +21 -18
  79. package/node_modules/playwright/lib/util.js +2 -29
  80. package/node_modules/playwright/lib/worker/workerProcessEntry.js +14 -32
  81. package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +7 -8
  82. package/node_modules/playwright/package.json +3 -6
  83. package/node_modules/playwright/types/test.d.ts +391 -4
  84. package/node_modules/playwright/types/testReporter.d.ts +79 -4
  85. package/node_modules/playwright-core/browsers.json +12 -18
  86. package/node_modules/playwright-core/lib/bootstrap.js +1 -1
  87. package/node_modules/playwright-core/lib/coreBundle.js +11344 -9900
  88. package/node_modules/playwright-core/lib/server/electron/loader.js +5 -2
  89. package/node_modules/playwright-core/lib/serverRegistry.js +5 -1
  90. package/node_modules/playwright-core/lib/tools/cli-client/help.json +21 -6
  91. package/node_modules/playwright-core/lib/tools/cli-client/program.js +1 -1
  92. package/node_modules/playwright-core/lib/tools/cli-client/session.js +4 -0
  93. package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +21 -5
  94. package/node_modules/playwright-core/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
  95. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  96. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  97. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  98. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  99. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  100. package/node_modules/playwright-core/lib/tools/utils/extension.js +31 -8
  101. package/node_modules/playwright-core/lib/utilsBundle.js +37698 -33670
  102. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +391 -268
  103. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  104. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  105. package/node_modules/playwright-core/lib/vite/dashboard/index.html +2 -2
  106. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -1
  107. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +15 -55
  108. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  109. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  110. package/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  111. package/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  112. package/node_modules/playwright-core/lib/vite/recorder/index.html +2 -2
  113. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
  114. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
  115. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  116. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
  117. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  118. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  119. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  120. package/node_modules/playwright-core/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
  121. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +5 -5
  122. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  123. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +2 -2
  124. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +3 -4
  125. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  126. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
  127. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +5 -5
  128. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  129. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  130. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  131. package/node_modules/playwright-core/lib/xdg-open +338 -137
  132. package/node_modules/playwright-core/package.json +2 -2
  133. package/node_modules/playwright-core/types/protocol.d.ts +249 -36
  134. package/node_modules/playwright-core/types/structs.d.ts +3 -1
  135. package/node_modules/playwright-core/types/types.d.ts +2009 -266
  136. package/package.json +3 -3
  137. package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +0 -188
  138. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-313.pyc +0 -0
  139. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-313.pyc +0 -0
  140. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +0 -525
  141. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313.pyc +0 -0
  142. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313.pyc +0 -0
  143. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313.pyc +0 -0
  144. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_chromium_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
  145. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_firefox_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
  146. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
  147. package/node_modules/pi-lean-portal/core/shared/url-safety.ts +0 -164
  148. package/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json +0 -2739
  149. package/node_modules/playwright-core/lib/tools/cli-client/skill/references/test-generation.md +0 -134
  150. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
  151. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
  152. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
  153. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
  154. package/node_modules/playwright-core/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
  155. package/node_modules/playwright-core/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
  156. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
  157. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
  158. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
  159. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
  160. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
  161. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
  162. package/node_modules/playwright-core/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
  163. package/node_modules/playwright-core/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
  164. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
  165. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
  166. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
  167. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
  168. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
  169. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
  170. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
  171. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
  172. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
  173. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
  174. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +0 -0
  175. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
  176. /package/node_modules/playwright-core/lib/tools/{trace → skills/playwright-trace}/SKILL.md +0 -0
@@ -1,525 +0,0 @@
1
- """
2
- BrowserBridge — base class for Python browser automation backends.
3
-
4
- Provides JSON-RPC command routing, session lifecycle, element caching,
5
- and a ``run()`` main loop. Subclasses override ``create_browser_session()``,
6
- ``create_browser_context()``, and the ``do_*`` operation methods to implement
7
- specific browser backends (e.g. Chromium via Playwright, Camoufox, etc.).
8
-
9
- Protocol
10
- --------
11
- All communication is JSON-RPC 2.0 over stdin/stdout with newline-delimited
12
- framing. See ``transport.py`` for details.
13
-
14
- Lifecycle
15
- ---------
16
- 1. The TypeScript ``PythonPluginAdapter`` spawns the Python process.
17
- 2. The bridge starts its ``run()`` loop, waiting for commands.
18
- 3. First command is typically ``browser.navigate``, which calls
19
- ``create_browser_session()`` (if no session exists yet for that taskId)
20
- and then navigates.
21
- 4. Subsequent commands use the existing session.
22
- 5. ``browser.cleanup`` closes the session for a taskId.
23
- 6. ``browser.shutdown`` (sent via cleanupAll) terminates the process.
24
-
25
- Subclass contract (all ``@abstractmethod``)
26
- --------------------------------------------
27
- * ``create_browser_session(task_id, config)`` — build a per-task session dict.
28
- * ``create_browser_context(config)`` — build an isolated BrowserContext.
29
- * ``do_navigate / do_snapshot / do_click / do_type / do_scroll / do_go_back
30
- / do_press / do_screenshot / do_get_console_messages / do_clear_console
31
- / do_evaluate / do_get_cookies / do_add_cookies / do_clear_cookies
32
- / do_get_storage_state`` — the 15 operation methods. Each returns a
33
- dict whose shape is documented at the dispatch site (``_h_*`` handlers).
34
- ``do_cleanup`` has a concrete default (calls ``close_browser_session``)
35
- and need not be overridden.
36
-
37
- Named profiles are fully handled by the TypeScript side via
38
- ``core/shared/storage-state.ts`` (disk persistence). The Python
39
- bridge receives ``storageState`` in the navigate request and applies
40
- it when creating a new BrowserContext — it does NOT track shared
41
- contexts across tasks.
42
- """
43
-
44
- import traceback
45
- from abc import ABC, abstractmethod
46
- from typing import Any, Optional
47
-
48
- from .transport import (
49
- read_request,
50
- write_response,
51
- make_success_response,
52
- make_error_response,
53
- make_parse_error,
54
- make_invalid_request,
55
- make_application_error,
56
- InvalidRequestError,
57
- METHOD_NOT_FOUND,
58
- INVALID_PARAMS,
59
- SESSION_ERROR,
60
- )
61
- from .accessibility import parse_snapshot, AriaParseResult
62
-
63
- # ─── Default timeout ──────────────────────────────────────────────────
64
-
65
- DEFAULT_NAVIGATION_TIMEOUT_MS: int = 30_000
66
- DEFAULT_INTERACTION_TIMEOUT_MS: int = 10_000
67
-
68
-
69
- class BrowserBridge(ABC):
70
- """Base class for Python browser automation bridges.
71
-
72
- Subclass this and override the abstract methods listed in the module
73
- docstring. Call ``run()`` to start the JSON-RPC command loop.
74
- """
75
-
76
- # ── Session storage ─────────────────────────────────────────
77
-
78
- #: Per-taskId session data: {task_id: {...}}.
79
- #: The dict contents are backend-specific (page, context, etc.).
80
- #: You can store whatever you need here.
81
- sessions: dict[str, dict[str, Any]]
82
-
83
- #: Per-taskId element cache: {task_id: AriaParseResult}.
84
- element_caches: dict[str, AriaParseResult]
85
-
86
- #: Whether the bridge is still running.
87
- _running: bool
88
-
89
- #: Plugin configuration dict forwarded from the TypeScript adapter via
90
- #: the ``browser.init`` RPC. Defaults to ``{}`` for bridges that never
91
- #: receive an init call (e.g. older adapters, or the shipped
92
- #: ``chromium-py``/``firefox-py`` when run standalone). Subclasses read
93
- #: engine-specific options from ``self.plugin_config.get("launch", {})``.
94
- _plugin_config: dict[str, Any]
95
-
96
- def __init__(self) -> None:
97
- self.sessions = {}
98
- self.element_caches = {}
99
- self._running = False
100
- self._plugin_config = {}
101
-
102
- # ── Plugin config (forwarded via browser.init) ───────────────
103
-
104
- @property
105
- def plugin_config(self) -> dict[str, Any]:
106
- """Return the plugin config dict forwarded from the TypeScript adapter.
107
-
108
- Populated by the ``browser.init`` RPC handler. Always returns a
109
- dict (empty when no init was received) so subclasses can safely
110
- call ``self.plugin_config.get("launch", {})``.
111
- """
112
- return self._plugin_config
113
-
114
- # ── Subclass hooks ──────────────────────────────────────────
115
-
116
- @abstractmethod
117
- def create_browser_session(self, task_id: str, config: dict[str, Any]) -> dict[str, Any]:
118
- """Create a new browser session for the given task.
119
-
120
- Must return a dict that will be stored in ``self.sessions[task_id]``.
121
- The dict is backend-specific (e.g. containing a Playwright page/context).
122
- """
123
-
124
- def close_browser_session(self, task_id: str) -> None:
125
- """Close and clean up the session for the given task.
126
-
127
- Default implementation removes the session from the dict. Override
128
- to close browser pages/contexts before removal.
129
- """
130
- self.sessions.pop(task_id, None)
131
- self.element_caches.pop(task_id, None)
132
-
133
- @abstractmethod
134
- def create_browser_context(self, config: dict[str, Any]) -> Any:
135
- """Create a new isolated BrowserContext for a task session.
136
-
137
- Each task gets its own BrowserContext with no sharing between
138
- tasks. Named profiles are handled by the TypeScript side via
139
- ``core/shared/storage-state.ts`` — the ``config`` may contain
140
- ``storageState`` to restore cookies and localStorage.
141
- """
142
-
143
- # ── Session helpers ─────────────────────────────────────────
144
-
145
- def get_session(self, task_id: str) -> Optional[dict[str, Any]]:
146
- """Get the session data for a task, or None."""
147
- return self.sessions.get(task_id)
148
-
149
- def require_session(self, task_id: str) -> dict[str, Any]:
150
- """Get the session for a task, raising SESSION_ERROR if absent."""
151
- session = self.get_session(task_id)
152
- if session is None:
153
- raise SessionNotFoundError(
154
- f"No active session for task '{task_id}'. "
155
- "Call browser.navigate first."
156
- )
157
- return session
158
-
159
- def ensure_session(self, task_id: str, config: Optional[dict[str, Any]] = None) -> dict[str, Any]:
160
- """Get or create a session for the given task."""
161
- session = self.get_session(task_id)
162
- if session is not None:
163
- return session
164
- new_session = self.create_browser_session(task_id, config or {})
165
- self.sessions[task_id] = new_session
166
- return new_session
167
-
168
- def get_element_cache(self, task_id: str) -> Optional[AriaParseResult]:
169
- """Get the cached element parse result for a task, or None."""
170
- return self.element_caches.get(task_id)
171
-
172
- def set_element_cache(self, task_id: str, result: AriaParseResult) -> None:
173
- """Store a parsed element cache for a task."""
174
- self.element_caches[task_id] = result
175
-
176
- # ── Operation stubs (override in subclasses) ────────────────
177
- #
178
- # The 15 ``do_*`` methods below are the operation contract. Each
179
- # returns a dict whose shape is enforced at the dispatch site
180
- # (``_h_*`` handlers in ``_DISPATCH``). ``do_cleanup`` has a concrete
181
- # default and is not abstract.
182
-
183
- @abstractmethod
184
- def do_navigate(
185
- self,
186
- task_id: str,
187
- url: str,
188
- timeout_ms: int = DEFAULT_NAVIGATION_TIMEOUT_MS,
189
- storageState: Optional[dict[str, Any]] = None,
190
- profileName: Optional[str] = None,
191
- profileMode: Optional[str] = None,
192
- ) -> dict[str, Any]:
193
- """Navigate the browser to a URL."""
194
-
195
- @abstractmethod
196
- def do_snapshot(self, task_id: str) -> dict[str, Any]:
197
- """Take an accessibility snapshot of the current page."""
198
-
199
- @abstractmethod
200
- def do_click(self, task_id: str, ref: str) -> dict[str, Any]:
201
- """Click an element by @e ref."""
202
-
203
- @abstractmethod
204
- def do_type(self, task_id: str, ref: str, text: str) -> dict[str, Any]:
205
- """Type text into an element by @e ref."""
206
-
207
- @abstractmethod
208
- def do_scroll(self, task_id: str, direction: str) -> dict[str, Any]:
209
- """Scroll the page up or down."""
210
-
211
- @abstractmethod
212
- def do_go_back(self, task_id: str) -> dict[str, Any]:
213
- """Navigate back in history."""
214
-
215
- @abstractmethod
216
- def do_press(self, task_id: str, key: str) -> dict[str, Any]:
217
- """Press a keyboard key on the current page (or focused element)."""
218
-
219
- @abstractmethod
220
- def do_screenshot(self, task_id: str, full_page: bool = False) -> dict[str, Any]:
221
- """Take a screenshot of the current page (JPEG base64 data URI)."""
222
-
223
- @abstractmethod
224
- def do_get_console_messages(self, task_id: str) -> dict[str, Any]:
225
- """Get captured console messages."""
226
-
227
- @abstractmethod
228
- def do_clear_console(self, task_id: str) -> dict[str, Any]:
229
- """Clear captured console messages."""
230
-
231
- @abstractmethod
232
- def do_evaluate(
233
- self, task_id: str, expression: str, *, read_only: bool = False
234
- ) -> dict[str, Any]:
235
- """Evaluate JavaScript in the page."""
236
-
237
- @abstractmethod
238
- def do_get_cookies(
239
- self, task_id: str, urls: Optional[list[str]] = None
240
- ) -> dict[str, Any]:
241
- """Get all cookies, optionally filtered by URL."""
242
-
243
- @abstractmethod
244
- def do_add_cookies(
245
- self, task_id: str, cookies: list[dict[str, Any]]
246
- ) -> dict[str, Any]:
247
- """Add cookies to the browser context."""
248
-
249
- @abstractmethod
250
- def do_clear_cookies(
251
- self,
252
- task_id: str,
253
- name: Optional[str] = None,
254
- domain: Optional[str] = None,
255
- path: Optional[str] = None,
256
- ) -> dict[str, Any]:
257
- """Clear cookies, optionally filtered by name/domain/path."""
258
-
259
- @abstractmethod
260
- def do_get_storage_state(self, task_id: str) -> dict[str, Any]:
261
- """Get full storage state (cookies + localStorage + IndexedDB)."""
262
-
263
- def do_cleanup(self, task_id: str) -> dict[str, Any]:
264
- """Clean up resources for a specific task.
265
-
266
- Profile persistence is handled by the TypeScript side
267
- (``python-adapter.ts`` auto-saves storage state before calling
268
- cleanup), so this default always calls ``close_browser_session()``.
269
- """
270
- self.close_browser_session(task_id)
271
- return {"success": True}
272
-
273
- # ── Command handlers ────────────────────────────────────────
274
- #
275
- # One ``_h_*`` method per JSON-RPC method. Each extracts params and
276
- # calls the matching ``do_*`` method. ``_DISPATCH`` (class attribute,
277
- # built at the bottom of the class body) maps method name → handler.
278
-
279
- def _h_ping(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
280
- return make_success_response(cmd_id, "pong")
281
-
282
- def _h_init(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
283
- # Forward plugin config from the TypeScript adapter. Sent exactly
284
- # once after the ping handshake, before any other RPC.
285
- self._plugin_config = params.get("config") or {}
286
- return make_success_response(cmd_id, {"ok": True})
287
-
288
- def _h_shutdown(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
289
- self._running = False
290
- return make_success_response(cmd_id, "shutting_down")
291
-
292
- def _h_navigate(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
293
- url = self._require_param(params, "url", str)
294
- task_id = self._require_param(params, "taskId", str)
295
- timeout_ms = params.get("timeoutMs", DEFAULT_NAVIGATION_TIMEOUT_MS)
296
- result = self.do_navigate(
297
- task_id, url, timeout_ms,
298
- storageState=params.get("storageState"),
299
- profileName=params.get("profileName"),
300
- profileMode=params.get("profileMode"),
301
- )
302
- return make_success_response(cmd_id, result)
303
-
304
- def _h_snapshot(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
305
- task_id = self._require_param(params, "taskId", str)
306
- return make_success_response(cmd_id, self.do_snapshot(task_id))
307
-
308
- def _h_click(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
309
- task_id = self._require_param(params, "taskId", str)
310
- ref = self._require_param(params, "ref", str)
311
- return make_success_response(cmd_id, self.do_click(task_id, ref))
312
-
313
- def _h_type(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
314
- task_id = self._require_param(params, "taskId", str)
315
- ref = self._require_param(params, "ref", str)
316
- text = self._require_param(params, "text", str)
317
- return make_success_response(cmd_id, self.do_type(task_id, ref, text))
318
-
319
- def _h_scroll(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
320
- task_id = self._require_param(params, "taskId", str)
321
- direction = self._require_param(params, "direction", str)
322
- if direction not in ("up", "down"):
323
- return make_error_response(
324
- cmd_id, INVALID_PARAMS,
325
- 'direction must be "up" or "down"',
326
- )
327
- return make_success_response(cmd_id, self.do_scroll(task_id, direction))
328
-
329
- def _h_go_back(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
330
- task_id = self._require_param(params, "taskId", str)
331
- return make_success_response(cmd_id, self.do_go_back(task_id))
332
-
333
- def _h_press(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
334
- task_id = self._require_param(params, "taskId", str)
335
- key = self._require_param(params, "key", str)
336
- return make_success_response(cmd_id, self.do_press(task_id, key))
337
-
338
- def _h_screenshot(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
339
- task_id = self._require_param(params, "taskId", str)
340
- full_page = params.get("fullPage", False)
341
- return make_success_response(cmd_id, self.do_screenshot(task_id, full_page))
342
-
343
- def _h_get_console_messages(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
344
- task_id = self._require_param(params, "taskId", str)
345
- return make_success_response(cmd_id, self.do_get_console_messages(task_id))
346
-
347
- def _h_clear_console(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
348
- task_id = self._require_param(params, "taskId", str)
349
- return make_success_response(cmd_id, self.do_clear_console(task_id))
350
-
351
- def _h_evaluate(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
352
- task_id = self._require_param(params, "taskId", str)
353
- expression = self._require_param(params, "expression", str)
354
- read_only = bool(params.get("readOnly", False))
355
- result = self.do_evaluate(task_id, expression, read_only=read_only)
356
- return make_success_response(cmd_id, result)
357
-
358
- def _h_get_cookies(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
359
- task_id = self._require_param(params, "taskId", str)
360
- return make_success_response(cmd_id, self.do_get_cookies(task_id, params.get("urls")))
361
-
362
- def _h_add_cookies(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
363
- task_id = self._require_param(params, "taskId", str)
364
- cookies = self._require_param(params, "cookies", list)
365
- return make_success_response(cmd_id, self.do_add_cookies(task_id, cookies))
366
-
367
- def _h_clear_cookies(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
368
- task_id = self._require_param(params, "taskId", str)
369
- # No required params beyond taskId — empty call clears ALL cookies
370
- result = self.do_clear_cookies(
371
- task_id, params.get("name"), params.get("domain"), params.get("path")
372
- )
373
- return make_success_response(cmd_id, result)
374
-
375
- def _h_get_storage_state(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
376
- task_id = self._require_param(params, "taskId", str)
377
- return make_success_response(cmd_id, self.do_get_storage_state(task_id))
378
-
379
- def _h_cleanup(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
380
- task_id = self._require_param(params, "taskId", str)
381
- return make_success_response(cmd_id, self.do_cleanup(task_id))
382
-
383
- def _h_describe_quirks(self, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
384
- # Return the bridge's declared quirks flags. Uses getattr with
385
- # defaults so a bare BrowserBridge (no Playwright quirks) returns
386
- # all-defaults rather than raising AttributeError.
387
- return make_success_response(cmd_id, {
388
- "fingerprint_managed_context": getattr(
389
- self, "_fingerprint_managed_context", False
390
- ),
391
- "eval_prefix": getattr(self, "_eval_prefix", ""),
392
- "scroll_via_wheel": getattr(self, "_scroll_via_wheel", False),
393
- "skip_default_viewport": getattr(self, "_skip_default_viewport", False),
394
- "skip_networkidle": getattr(self, "_skip_networkidle", False),
395
- "wrap_mw_eval_in_eval": getattr(self, "_wrap_mw_eval_in_eval", False),
396
- "csp_safe_readonly_via_init_script": getattr(
397
- self, "_csp_safe_readonly_via_init_script", False
398
- ),
399
- })
400
-
401
- #: JSON-RPC method name → handler. Built after the handlers are
402
- #: defined so the names are in scope. ``handle_command`` looks up
403
- #: here and calls ``handler(self, params, cmd_id)``.
404
- _DISPATCH = {
405
- "ping": _h_ping,
406
- "browser.init": _h_init,
407
- "shutdown": _h_shutdown,
408
- "browser.navigate": _h_navigate,
409
- "browser.snapshot": _h_snapshot,
410
- "browser.click": _h_click,
411
- "browser.type": _h_type,
412
- "browser.scroll": _h_scroll,
413
- "browser.goBack": _h_go_back,
414
- "browser.press": _h_press,
415
- "browser.screenshot": _h_screenshot,
416
- "browser.getConsoleMessages": _h_get_console_messages,
417
- "browser.clearConsole": _h_clear_console,
418
- "browser.evaluate": _h_evaluate,
419
- "browser.getCookies": _h_get_cookies,
420
- "browser.addCookies": _h_add_cookies,
421
- "browser.clearCookies": _h_clear_cookies,
422
- "browser.getStorageState": _h_get_storage_state,
423
- "browser.cleanup": _h_cleanup,
424
- "browser.describeQuirks": _h_describe_quirks,
425
- }
426
-
427
- # ── Command routing ─────────────────────────────────────────
428
-
429
- def handle_command(self, method: str, params: dict[str, Any], cmd_id: Any) -> dict[str, Any]:
430
- """Route a JSON-RPC method to the appropriate operation handler.
431
-
432
- Returns a JSON-RPC response dict (either result or error).
433
- """
434
- try:
435
- handler = self._DISPATCH.get(method)
436
- if handler is None:
437
- return make_error_response(
438
- cmd_id, METHOD_NOT_FOUND, f"Method not found: {method}"
439
- )
440
- return handler(self, params, cmd_id)
441
-
442
- except SessionNotFoundError as exc:
443
- return make_error_response(cmd_id, SESSION_ERROR, str(exc))
444
- except NotImplementedError as exc:
445
- return make_application_error(cmd_id, str(exc))
446
- except Exception as exc:
447
- tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
448
- return make_application_error(cmd_id, str(exc), traceback_str=tb)
449
-
450
- # ── Main loop ─────────────────────────────────────────────
451
-
452
- def run(self) -> None:
453
- """Start the main JSON-RPC command loop.
454
-
455
- Reads requests from stdin, dispatches them, and writes responses
456
- to stdout. Runs until EOF or a ``shutdown`` command.
457
- """
458
- self._running = True
459
- while self._running:
460
- try:
461
- request = read_request()
462
- if request is None:
463
- break # EOF
464
-
465
- cmd_id = request.get("id")
466
- method = request.get("method", "")
467
- params = request.get("params", {})
468
-
469
- if not isinstance(params, dict):
470
- write_response(make_error_response(
471
- cmd_id, INVALID_PARAMS,
472
- '"params" must be a JSON object',
473
- ))
474
- continue
475
-
476
- response = self.handle_command(method, params, cmd_id)
477
- write_response(response)
478
-
479
- except InvalidRequestError:
480
- # Valid JSON but not a valid JSON-RPC Request object
481
- write_response(make_invalid_request(None))
482
- except ValueError:
483
- # JSON parse error
484
- write_response(make_parse_error(None))
485
- except EOFError:
486
- break
487
- except KeyboardInterrupt:
488
- break
489
- except Exception as exc:
490
- tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
491
- write_response(make_application_error(None, str(exc), traceback_str=tb))
492
-
493
- # ── Internal helpers ──────────────────────────────────────
494
-
495
- @staticmethod
496
- def _require_param(
497
- params: dict[str, Any],
498
- key: str,
499
- expected_type: type,
500
- ) -> Any:
501
- """Require a param to exist and be of the expected type.
502
-
503
- Raises ``InvalidParamsError`` if the param is missing or has the
504
- wrong type. The caller's ``except Exception`` clause turns this
505
- into a JSON-RPC application-error response.
506
- """
507
- if key not in params:
508
- raise InvalidParamsError(f'Missing required parameter: "{key}"')
509
- value = params[key]
510
- if not isinstance(value, expected_type):
511
- raise InvalidParamsError(
512
- f'Parameter "{key}" must be of type {expected_type.__name__}, '
513
- f"got {type(value).__name__}"
514
- )
515
- return value
516
-
517
-
518
- # ─── Custom exceptions ────────────────────────────────────────────────
519
-
520
- class SessionNotFoundError(Exception):
521
- """Raised when an operation requires a session but none exists."""
522
-
523
-
524
- class InvalidParamsError(Exception):
525
- """Raised when a required parameter is missing or has the wrong type."""