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.
- package/README.md +1 -1
- package/node_modules/pi-lean-portal/AGENTS.md +15 -23
- package/node_modules/pi-lean-portal/README.md +31 -1
- package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +12 -1
- package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +263 -1102
- package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +6 -32
- package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +1 -1
- package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +1 -2
- package/node_modules/pi-lean-portal/__tests__/helpers/setup-temp-dir.ts +26 -0
- package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/plugin-contract.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +65 -69
- package/node_modules/pi-lean-portal/__tests__/router-dispatch.test.ts +1 -14
- package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +31 -16
- package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/web-guides.test.ts +146 -19
- package/node_modules/pi-lean-portal/backends/chromium-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/firefox-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +1 -1
- package/node_modules/pi-lean-portal/backends/python-adapter.ts +3 -3
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +2 -4
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +572 -81
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_py_bridges.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +13 -43
- package/node_modules/pi-lean-portal/browser-profile.ts +2 -3
- package/node_modules/pi-lean-portal/browser-toggle.ts +168 -380
- package/node_modules/pi-lean-portal/contributed/CHOOSING.md +2 -2
- package/node_modules/pi-lean-portal/contributed/README.md +65 -6
- package/node_modules/pi-lean-portal/contributed/camoufox-py/__pycache__/bridge.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +108 -14
- package/node_modules/pi-lean-portal/contributed/camoufox-py/pin.json +4 -0
- package/node_modules/pi-lean-portal/contributed/invisible-py/bridge.py +1 -1
- package/node_modules/pi-lean-portal/core/fetch-backend.ts +10 -30
- package/node_modules/pi-lean-portal/core/guides.ts +14 -18
- package/node_modules/pi-lean-portal/core/router.ts +2 -20
- package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +19 -30
- package/node_modules/pi-lean-portal/core/shared/browser-data.ts +4 -4
- package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +2 -2
- package/node_modules/pi-lean-portal/core/shared/paths.ts +16 -2
- package/node_modules/pi-lean-portal/core/shared/settings-reader.ts +2 -7
- package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +1 -1
- package/node_modules/pi-lean-portal/index.ts +8 -1
- package/node_modules/pi-lean-portal/package.json +2 -1
- package/node_modules/pi-lean-portal/tools/web-learn.ts +1 -4
- package/node_modules/pi-lean-search/__tests__/web-search.test.ts +87 -0
- package/node_modules/pi-lean-search/index.ts +134 -64
- package/node_modules/pi-lean-search/package.json +2 -1
- package/node_modules/playwright/README.md +3 -3
- package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +2 -2
- package/node_modules/playwright/lib/cli/reportActions.js +2 -2
- package/node_modules/playwright/lib/common/index.js +77 -52
- package/node_modules/playwright/lib/common/index.js.txt +10 -11
- package/node_modules/playwright/lib/errorContext.js +2 -3
- package/node_modules/playwright/lib/index.js +30 -8
- package/node_modules/playwright/lib/matchers/expect.js +4811 -4379
- package/node_modules/playwright/lib/matchers/expect.js.LICENSE +87 -140
- package/node_modules/playwright/lib/matchers/expect.js.txt +30 -35
- package/node_modules/playwright/lib/program.js +19 -0
- package/node_modules/playwright/lib/runner/index.js +309 -153
- package/node_modules/playwright/lib/runner/index.js.txt +13 -12
- package/node_modules/playwright/lib/transform/babelBundle.js +12899 -11654
- package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +379 -354
- package/node_modules/playwright/lib/transform/babelBundle.js.txt +39 -39
- package/node_modules/playwright/lib/transform/esmLoader.js +3129 -1116
- package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +81 -61
- package/node_modules/playwright/lib/transform/esmLoader.js.txt +21 -18
- package/node_modules/playwright/lib/util.js +2 -29
- package/node_modules/playwright/lib/worker/workerProcessEntry.js +14 -32
- package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +7 -8
- package/node_modules/playwright/package.json +3 -6
- package/node_modules/playwright/types/test.d.ts +391 -4
- package/node_modules/playwright/types/testReporter.d.ts +79 -4
- package/node_modules/playwright-core/browsers.json +12 -18
- package/node_modules/playwright-core/lib/bootstrap.js +1 -1
- package/node_modules/playwright-core/lib/coreBundle.js +11344 -9900
- package/node_modules/playwright-core/lib/server/electron/loader.js +5 -2
- package/node_modules/playwright-core/lib/serverRegistry.js +5 -1
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +21 -6
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +1 -1
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +4 -0
- package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +21 -5
- package/node_modules/playwright-core/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +31 -8
- package/node_modules/playwright-core/lib/utilsBundle.js +37698 -33670
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +391 -268
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -1
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +15 -55
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +5 -5
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +3 -4
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +5 -5
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/node_modules/playwright-core/lib/xdg-open +338 -137
- package/node_modules/playwright-core/package.json +2 -2
- package/node_modules/playwright-core/types/protocol.d.ts +249 -36
- package/node_modules/playwright-core/types/structs.d.ts +3 -1
- package/node_modules/playwright-core/types/types.d.ts +2009 -266
- package/package.json +3 -3
- package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +0 -188
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +0 -525
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_chromium_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_firefox_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/core/shared/url-safety.ts +0 -164
- package/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json +0 -2739
- package/node_modules/playwright-core/lib/tools/cli-client/skill/references/test-generation.md +0 -134
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
- /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."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|