webskrap 3.2.4__tar.gz → 3.3.0__tar.gz
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.
- {webskrap-3.2.4 → webskrap-3.3.0}/CHANGELOG.md +37 -1
- {webskrap-3.2.4 → webskrap-3.3.0}/PKG-INFO +41 -1
- {webskrap-3.2.4 → webskrap-3.3.0}/README.md +40 -0
- webskrap-3.3.0/docs/benchmarks/mcp-snapshots-playwright.json +83 -0
- webskrap-3.3.0/docs/benchmarks/mcp-snapshots-webskrap.json +140 -0
- webskrap-3.3.0/docs/benchmarks/mcp-snapshots.md +145 -0
- webskrap-3.3.0/docs/design/mcp-snapshots.md +92 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/pyproject.toml +1 -1
- webskrap-3.3.0/scripts/benchmark_playwright_snapshots.py +125 -0
- webskrap-3.3.0/scripts/benchmark_snapshots.py +199 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/browser_session.py +8 -2
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cdp.py +18 -2
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cdp_dom.py +167 -31
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cdp_setup.py +1 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cli.py +35 -15
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/client.py +124 -35
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/guide.md +71 -7
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/mcp_server.py +66 -15
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/models.py +6 -3
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_browser_integration.py +125 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cdp.py +67 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cdp_child_targets.py +6 -3
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cdp_session.py +1 -1
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cli.py +20 -4
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_client_unit.py +166 -13
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_mcp.py +15 -1
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_mcp_efficiency.py +68 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_parsing.py +1 -0
- webskrap-3.3.0/tests/test_snapshot_find.py +355 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/uv.lock +166 -162
- {webskrap-3.2.4 → webskrap-3.3.0}/.gitattributes +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/.gitignore +0 -0
- {webskrap-3.2.4/.package-dist.sya6Sl → webskrap-3.3.0/.package-dist.qx0gMJ}/.gitignore +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/CONTRIBUTING.md +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/LICENSE +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/SECURITY.md +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/benchmarks.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/docs/design/cdp-driver.md +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/docs/diagnostics/datadome.md +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/docs/migration-3.0.md +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/scripts/driver_parity.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/scripts/live_stealth_report.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/__init__.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/browser_cli.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cdp_session.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/challenges.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/cli_output.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/consent.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/diagnostics.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/display.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/errors.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/fetch_runtime.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/human.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/install.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/parsing.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/paths.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/profiles.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/proxy_auth.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/py.typed +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/search.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/text_search.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/src/webskrap/urls.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/conftest.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/accepted-202.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-invalid-url-400.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-reference-403.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-sec-cpt.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/api-403.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/article-access-denied.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-1020.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-custom-block.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/blocks/not-found-404.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/bing.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/bing_challenge.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/bing_no_results.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/ddg.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/ddg_challenge.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/fixtures/search/ddg_no_results.html +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/live_stealth_helpers.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_bot_detection.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_bot_detection_headless.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_browser_cli.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_browser_session_unit.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_browser_vision.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cdp_browser.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_cdp_setup.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_challenges.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_consent.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_consent_live.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_diagnostics.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_display.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_errors.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_fetch_runtime.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_human.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_install.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_live_stealth_helpers.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_live_stealth_report.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_models.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_paths.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_profiles.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_search.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_search_live.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_text_search.py +0 -0
- {webskrap-3.2.4 → webskrap-3.3.0}/tests/test_urls.py +0 -0
|
@@ -9,6 +9,41 @@ history, so they summarize each release rather than list every change.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [3.3.0] - 2026-11-14
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- MCP `browser_find` locates accessible elements and visible text with minimal
|
|
17
|
+
ancestor context. `browser_snapshot` accepts subtree targets and optional
|
|
18
|
+
bounding boxes. Existing interaction refs and response fields stay compatible.
|
|
19
|
+
- Named MCP fetch sessions reuse a browser across repeated requests. Close them
|
|
20
|
+
with `fetch_session_close`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Page extraction uses one browser evaluation.
|
|
25
|
+
- Fetch defaults use `domcontentloaded` and automatic resource policies:
|
|
26
|
+
lightweight resources for text, all resources for HTML. Use explicit
|
|
27
|
+
`networkidle` or `resource_policy="all"` when a task needs them.
|
|
28
|
+
- Refresh the Python dependency lock and update setup-uv to 10.3.3.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Preserve empty HTTP error responses instead of reporting a navigation failure.
|
|
33
|
+
- Keep CLI schema output compatible with Typer 0.29 type descriptors and older
|
|
34
|
+
Typer parameter types.
|
|
35
|
+
- Accessibility searches retain inline paragraph, status and form-value text.
|
|
36
|
+
Snapshots follow the focused dialog when native modal dialogs are stacked.
|
|
37
|
+
- Wait for the local cross-site fixture's image request before checking it in
|
|
38
|
+
browser tests.
|
|
39
|
+
|
|
40
|
+
### Security
|
|
41
|
+
|
|
42
|
+
- Omit password input values from accessibility snapshots. Escape URL metadata
|
|
43
|
+
and normalize line separators so page attributes cannot inject fake snapshot
|
|
44
|
+
lines. Detect detached refs before interaction.
|
|
45
|
+
- Update locked OpenTelemetry API to 1.45.2 for GHSA-mf5v-w3w3-f6cg.
|
|
46
|
+
|
|
12
47
|
## [3.2.4] - 2026-11-01
|
|
13
48
|
|
|
14
49
|
### Added
|
|
@@ -1001,7 +1036,8 @@ entries below.
|
|
|
1001
1036
|
|
|
1002
1037
|
Earlier releases (0.1.0 - 0.4.9) are recorded in the git history and tags only.
|
|
1003
1038
|
|
|
1004
|
-
[Unreleased]: https://github.com/kacigaya/webskrap/compare/v3.
|
|
1039
|
+
[Unreleased]: https://github.com/kacigaya/webskrap/compare/v3.3.0...HEAD
|
|
1040
|
+
[3.3.0]: https://github.com/kacigaya/webskrap/compare/v3.2.4...v3.3.0
|
|
1005
1041
|
[3.2.4]: https://github.com/kacigaya/webskrap/compare/v3.2.3...v3.2.4
|
|
1006
1042
|
[3.2.3]: https://github.com/kacigaya/webskrap/compare/v3.2.2...v3.2.3
|
|
1007
1043
|
[3.2.2]: https://github.com/kacigaya/webskrap/compare/v3.2.1...v3.2.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: webskrap
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Async Python scraping on Chromium over its own CDP client, with coherent browser profiles and session controls.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kacigaya/webskrap
|
|
6
6
|
Project-URL: Repository, https://github.com/kacigaya/webskrap
|
|
@@ -87,6 +87,9 @@ Known challenge pages (DataDome, Cloudflare, Akamai, AWS WAF) and provider block
|
|
|
87
87
|
pages (DataDome, Cloudflare, Akamai) raise `blocked`. WebSkrap waits for device
|
|
88
88
|
checks that reload the page. It does not retry refusals or solve CAPTCHAs.
|
|
89
89
|
Ordinary HTTP errors still return `ok=False`.
|
|
90
|
+
For an empty HTTP error body, Chromium can show its own error page. The returned
|
|
91
|
+
text or HTML is that rendered page. The status, headers, and `final_url` still
|
|
92
|
+
come from the HTTP response.
|
|
90
93
|
|
|
91
94
|
For manual CAPTCHA completion, open a persistent browser with
|
|
92
95
|
`webskrap browser open --headed --session manual --proxy http://localhost:8888 URL`.
|
|
@@ -99,6 +102,30 @@ Keep `profile_mode="native"` for the browser's own identity. On Linux with Xvfb,
|
|
|
99
102
|
Full profile emulation is useful for device tests, but cannot replace every
|
|
100
103
|
worker or iframe value. See the [identity limits](src/webskrap/guide.md).
|
|
101
104
|
|
|
105
|
+
## Python batches
|
|
106
|
+
|
|
107
|
+
Use `client.fetch_many(urls)` to fetch a finite batch in order with one browser:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
async with WebSkrapClient() as client:
|
|
111
|
+
results = await client.fetch_many(
|
|
112
|
+
["https://example.com", "https://example.org"], text_only=True
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each batch shares cookies and storage, then closes its browser. Separate batches
|
|
117
|
+
and `client.fetch()` calls use fresh sessions. An explicit `user_data_dir` can
|
|
118
|
+
still share state on disk. Empty batches do not launch a browser. The first page
|
|
119
|
+
error stops the batch and raises; no partial list is returned. Ordinary HTTP
|
|
120
|
+
errors remain results with `ok=False`. `screenshot=True` uses a separate generated
|
|
121
|
+
PNG filename for each page.
|
|
122
|
+
|
|
123
|
+
For large inputs, per-page options, or state across batches, create a session
|
|
124
|
+
with `session = await client.session("crawl")`. Call `await session.fetch(url)`
|
|
125
|
+
for each URL. Close it with `await client.close_session("crawl")`, or let the
|
|
126
|
+
client context close it. Session names belong to one client. Use separate
|
|
127
|
+
sessions for unrelated callers.
|
|
128
|
+
|
|
102
129
|
## Search
|
|
103
130
|
|
|
104
131
|
A search loads the engine's results page in the same stealth browser and
|
|
@@ -144,6 +171,19 @@ Coordinates are CSS pixels from the original image's top-left. Take a fresh
|
|
|
144
171
|
view after scrolling or page changes. Headless Chromium needs no Xvfb for
|
|
145
172
|
this; the agent's client and model must support image input.
|
|
146
173
|
|
|
174
|
+
## Load only what the task needs
|
|
175
|
+
The default resource policy is `auto`. Text-only fetches skip images, fonts
|
|
176
|
+
and media. HTML fetches, screenshots and interactive pages load all resources.
|
|
177
|
+
Set `resource_policy="all"`, `"lite"`, or `"documents"` to override this choice.
|
|
178
|
+
Use `documents` only when removing CSS preserves the required result.
|
|
179
|
+
Fetches use `domcontentloaded`; set `ready_selector` for deferred content.
|
|
180
|
+
Use `browser_find(text="Save")` to locate a known control with ancestor context.
|
|
181
|
+
Use `browser_snapshot(target="e15", depth=2)` for one subtree; add `boxes=true`
|
|
182
|
+
only when geometry matters. Reuse returned refs with `browser_interact`.
|
|
183
|
+
Use `networkidle`, screenshots and full snapshots only when the task needs them.
|
|
184
|
+
CLI and MCP reject cookies by default. Disable this only for known pages where
|
|
185
|
+
it is unnecessary. In Python, enable rejection with `SessionConfig(decline_cookies=True)`.
|
|
186
|
+
|
|
147
187
|
## Documentation
|
|
148
188
|
|
|
149
189
|
- [Agent skill](.agents/skills/webskrap/SKILL.md)
|
|
@@ -52,6 +52,9 @@ Known challenge pages (DataDome, Cloudflare, Akamai, AWS WAF) and provider block
|
|
|
52
52
|
pages (DataDome, Cloudflare, Akamai) raise `blocked`. WebSkrap waits for device
|
|
53
53
|
checks that reload the page. It does not retry refusals or solve CAPTCHAs.
|
|
54
54
|
Ordinary HTTP errors still return `ok=False`.
|
|
55
|
+
For an empty HTTP error body, Chromium can show its own error page. The returned
|
|
56
|
+
text or HTML is that rendered page. The status, headers, and `final_url` still
|
|
57
|
+
come from the HTTP response.
|
|
55
58
|
|
|
56
59
|
For manual CAPTCHA completion, open a persistent browser with
|
|
57
60
|
`webskrap browser open --headed --session manual --proxy http://localhost:8888 URL`.
|
|
@@ -64,6 +67,30 @@ Keep `profile_mode="native"` for the browser's own identity. On Linux with Xvfb,
|
|
|
64
67
|
Full profile emulation is useful for device tests, but cannot replace every
|
|
65
68
|
worker or iframe value. See the [identity limits](src/webskrap/guide.md).
|
|
66
69
|
|
|
70
|
+
## Python batches
|
|
71
|
+
|
|
72
|
+
Use `client.fetch_many(urls)` to fetch a finite batch in order with one browser:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
async with WebSkrapClient() as client:
|
|
76
|
+
results = await client.fetch_many(
|
|
77
|
+
["https://example.com", "https://example.org"], text_only=True
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Each batch shares cookies and storage, then closes its browser. Separate batches
|
|
82
|
+
and `client.fetch()` calls use fresh sessions. An explicit `user_data_dir` can
|
|
83
|
+
still share state on disk. Empty batches do not launch a browser. The first page
|
|
84
|
+
error stops the batch and raises; no partial list is returned. Ordinary HTTP
|
|
85
|
+
errors remain results with `ok=False`. `screenshot=True` uses a separate generated
|
|
86
|
+
PNG filename for each page.
|
|
87
|
+
|
|
88
|
+
For large inputs, per-page options, or state across batches, create a session
|
|
89
|
+
with `session = await client.session("crawl")`. Call `await session.fetch(url)`
|
|
90
|
+
for each URL. Close it with `await client.close_session("crawl")`, or let the
|
|
91
|
+
client context close it. Session names belong to one client. Use separate
|
|
92
|
+
sessions for unrelated callers.
|
|
93
|
+
|
|
67
94
|
## Search
|
|
68
95
|
|
|
69
96
|
A search loads the engine's results page in the same stealth browser and
|
|
@@ -109,6 +136,19 @@ Coordinates are CSS pixels from the original image's top-left. Take a fresh
|
|
|
109
136
|
view after scrolling or page changes. Headless Chromium needs no Xvfb for
|
|
110
137
|
this; the agent's client and model must support image input.
|
|
111
138
|
|
|
139
|
+
## Load only what the task needs
|
|
140
|
+
The default resource policy is `auto`. Text-only fetches skip images, fonts
|
|
141
|
+
and media. HTML fetches, screenshots and interactive pages load all resources.
|
|
142
|
+
Set `resource_policy="all"`, `"lite"`, or `"documents"` to override this choice.
|
|
143
|
+
Use `documents` only when removing CSS preserves the required result.
|
|
144
|
+
Fetches use `domcontentloaded`; set `ready_selector` for deferred content.
|
|
145
|
+
Use `browser_find(text="Save")` to locate a known control with ancestor context.
|
|
146
|
+
Use `browser_snapshot(target="e15", depth=2)` for one subtree; add `boxes=true`
|
|
147
|
+
only when geometry matters. Reuse returned refs with `browser_interact`.
|
|
148
|
+
Use `networkidle`, screenshots and full snapshots only when the task needs them.
|
|
149
|
+
CLI and MCP reject cookies by default. Disable this only for known pages where
|
|
150
|
+
it is unnecessary. In Python, enable rejection with `SessionConfig(decline_cookies=True)`.
|
|
151
|
+
|
|
112
152
|
## Documentation
|
|
113
153
|
|
|
114
154
|
- [Agent skill](.agents/skills/webskrap/SKILL.md)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "@playwright/mcp@0.0.85",
|
|
3
|
+
"runs": [
|
|
4
|
+
{
|
|
5
|
+
"mode": "full",
|
|
6
|
+
"repeat": 0,
|
|
7
|
+
"success": true,
|
|
8
|
+
"tool_calls": 6,
|
|
9
|
+
"request_tokens": 91,
|
|
10
|
+
"response_tokens": 479,
|
|
11
|
+
"response_bytes": 1508,
|
|
12
|
+
"snapshot_ms_median": 74.09905100939795,
|
|
13
|
+
"task_ms": 2093.8407619833015,
|
|
14
|
+
"cpu_seconds": 0.6999999999999993,
|
|
15
|
+
"rss_mib_after": 1396.22265625
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"mode": "full",
|
|
19
|
+
"repeat": 1,
|
|
20
|
+
"success": true,
|
|
21
|
+
"tool_calls": 6,
|
|
22
|
+
"request_tokens": 91,
|
|
23
|
+
"response_tokens": 479,
|
|
24
|
+
"response_bytes": 1508,
|
|
25
|
+
"snapshot_ms_median": 109.93575002066791,
|
|
26
|
+
"task_ms": 2686.555687978398,
|
|
27
|
+
"cpu_seconds": 0.7799999999999994,
|
|
28
|
+
"rss_mib_after": 1335.69921875
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"mode": "full",
|
|
32
|
+
"repeat": 2,
|
|
33
|
+
"success": true,
|
|
34
|
+
"tool_calls": 6,
|
|
35
|
+
"request_tokens": 91,
|
|
36
|
+
"response_tokens": 479,
|
|
37
|
+
"response_bytes": 1508,
|
|
38
|
+
"snapshot_ms_median": 90.022451011464,
|
|
39
|
+
"task_ms": 2248.4715349855833,
|
|
40
|
+
"cpu_seconds": 0.7300000000000004,
|
|
41
|
+
"rss_mib_after": 1335.19140625
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"mode": "none",
|
|
45
|
+
"repeat": 0,
|
|
46
|
+
"success": true,
|
|
47
|
+
"tool_calls": 6,
|
|
48
|
+
"request_tokens": 91,
|
|
49
|
+
"response_tokens": 351,
|
|
50
|
+
"response_bytes": 1162,
|
|
51
|
+
"snapshot_ms_median": 130.02865097951144,
|
|
52
|
+
"task_ms": 2340.031432977412,
|
|
53
|
+
"cpu_seconds": 0.7799999999999998,
|
|
54
|
+
"rss_mib_after": 1314.16796875
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"mode": "none",
|
|
58
|
+
"repeat": 1,
|
|
59
|
+
"success": true,
|
|
60
|
+
"tool_calls": 6,
|
|
61
|
+
"request_tokens": 91,
|
|
62
|
+
"response_tokens": 351,
|
|
63
|
+
"response_bytes": 1162,
|
|
64
|
+
"snapshot_ms_median": 77.6378289447166,
|
|
65
|
+
"task_ms": 1987.0279059978202,
|
|
66
|
+
"cpu_seconds": 0.7399999999999998,
|
|
67
|
+
"rss_mib_after": 1312.30859375
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"mode": "none",
|
|
71
|
+
"repeat": 2,
|
|
72
|
+
"success": true,
|
|
73
|
+
"tool_calls": 6,
|
|
74
|
+
"request_tokens": 91,
|
|
75
|
+
"response_tokens": 351,
|
|
76
|
+
"response_bytes": 1162,
|
|
77
|
+
"snapshot_ms_median": 73.93148995470256,
|
|
78
|
+
"task_ms": 2026.7483039642684,
|
|
79
|
+
"cpu_seconds": 0.75,
|
|
80
|
+
"rss_mib_after": 1314.65625
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tokenizer": "tiktoken 0.14.0 o200k_base",
|
|
3
|
+
"runs": [
|
|
4
|
+
{
|
|
5
|
+
"mode": "baseline",
|
|
6
|
+
"repeat": 0,
|
|
7
|
+
"success": true,
|
|
8
|
+
"tool_calls": 15,
|
|
9
|
+
"request_tokens": 245,
|
|
10
|
+
"response_tokens": 32636,
|
|
11
|
+
"response_bytes": 101972,
|
|
12
|
+
"snapshot_ms_median": 276.9699060008861,
|
|
13
|
+
"task_ms": 15578.733453992754,
|
|
14
|
+
"cpu_seconds": 4.199999999999999,
|
|
15
|
+
"rss_mib_after": 1158.53515625,
|
|
16
|
+
"describe_calls": 8688,
|
|
17
|
+
"stealth_sensitive_calls": 0
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"mode": "snapshot",
|
|
21
|
+
"repeat": 0,
|
|
22
|
+
"success": true,
|
|
23
|
+
"tool_calls": 9,
|
|
24
|
+
"request_tokens": 161,
|
|
25
|
+
"response_tokens": 11054,
|
|
26
|
+
"response_bytes": 34568,
|
|
27
|
+
"snapshot_ms_median": 132.4927629902959,
|
|
28
|
+
"task_ms": 13070.251779980026,
|
|
29
|
+
"cpu_seconds": 2.4899999999999998,
|
|
30
|
+
"rss_mib_after": 1248.1875,
|
|
31
|
+
"describe_calls": 2894,
|
|
32
|
+
"stealth_sensitive_calls": 0
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"mode": "find",
|
|
36
|
+
"repeat": 0,
|
|
37
|
+
"success": true,
|
|
38
|
+
"tool_calls": 6,
|
|
39
|
+
"request_tokens": 123,
|
|
40
|
+
"response_tokens": 302,
|
|
41
|
+
"response_bytes": 974,
|
|
42
|
+
"snapshot_ms_median": 10.353491990827024,
|
|
43
|
+
"task_ms": 12541.241928993259,
|
|
44
|
+
"cpu_seconds": 2.02,
|
|
45
|
+
"rss_mib_after": 1241.4296875,
|
|
46
|
+
"describe_calls": 7,
|
|
47
|
+
"stealth_sensitive_calls": 0
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"mode": "baseline",
|
|
51
|
+
"repeat": 1,
|
|
52
|
+
"success": true,
|
|
53
|
+
"tool_calls": 15,
|
|
54
|
+
"request_tokens": 245,
|
|
55
|
+
"response_tokens": 32636,
|
|
56
|
+
"response_bytes": 101972,
|
|
57
|
+
"snapshot_ms_median": 189.5280875323806,
|
|
58
|
+
"task_ms": 14903.852140996605,
|
|
59
|
+
"cpu_seconds": 4.0600000000000005,
|
|
60
|
+
"rss_mib_after": 1160.078125,
|
|
61
|
+
"describe_calls": 8688,
|
|
62
|
+
"stealth_sensitive_calls": 0
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"mode": "snapshot",
|
|
66
|
+
"repeat": 1,
|
|
67
|
+
"success": true,
|
|
68
|
+
"tool_calls": 9,
|
|
69
|
+
"request_tokens": 161,
|
|
70
|
+
"response_tokens": 11054,
|
|
71
|
+
"response_bytes": 34568,
|
|
72
|
+
"snapshot_ms_median": 135.92701949528418,
|
|
73
|
+
"task_ms": 13055.62697298592,
|
|
74
|
+
"cpu_seconds": 2.8599999999999994,
|
|
75
|
+
"rss_mib_after": 1247.55078125,
|
|
76
|
+
"describe_calls": 2894,
|
|
77
|
+
"stealth_sensitive_calls": 0
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"mode": "find",
|
|
81
|
+
"repeat": 1,
|
|
82
|
+
"success": true,
|
|
83
|
+
"tool_calls": 6,
|
|
84
|
+
"request_tokens": 123,
|
|
85
|
+
"response_tokens": 302,
|
|
86
|
+
"response_bytes": 974,
|
|
87
|
+
"snapshot_ms_median": 15.616477990988642,
|
|
88
|
+
"task_ms": 12361.839258985128,
|
|
89
|
+
"cpu_seconds": 1.96,
|
|
90
|
+
"rss_mib_after": 1244.92578125,
|
|
91
|
+
"describe_calls": 7,
|
|
92
|
+
"stealth_sensitive_calls": 0
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"mode": "baseline",
|
|
96
|
+
"repeat": 2,
|
|
97
|
+
"success": true,
|
|
98
|
+
"tool_calls": 15,
|
|
99
|
+
"request_tokens": 245,
|
|
100
|
+
"response_tokens": 32636,
|
|
101
|
+
"response_bytes": 101972,
|
|
102
|
+
"snapshot_ms_median": 151.91562002291903,
|
|
103
|
+
"task_ms": 13915.481942996848,
|
|
104
|
+
"cpu_seconds": 4.12,
|
|
105
|
+
"rss_mib_after": 1257.5,
|
|
106
|
+
"describe_calls": 8688,
|
|
107
|
+
"stealth_sensitive_calls": 0
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"mode": "snapshot",
|
|
111
|
+
"repeat": 2,
|
|
112
|
+
"success": true,
|
|
113
|
+
"tool_calls": 9,
|
|
114
|
+
"request_tokens": 161,
|
|
115
|
+
"response_tokens": 11054,
|
|
116
|
+
"response_bytes": 34568,
|
|
117
|
+
"snapshot_ms_median": 131.0603755118791,
|
|
118
|
+
"task_ms": 12102.267072012182,
|
|
119
|
+
"cpu_seconds": 2.6100000000000003,
|
|
120
|
+
"rss_mib_after": 1245.76171875,
|
|
121
|
+
"describe_calls": 2894,
|
|
122
|
+
"stealth_sensitive_calls": 0
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"mode": "find",
|
|
126
|
+
"repeat": 2,
|
|
127
|
+
"success": true,
|
|
128
|
+
"tool_calls": 6,
|
|
129
|
+
"request_tokens": 123,
|
|
130
|
+
"response_tokens": 302,
|
|
131
|
+
"response_bytes": 974,
|
|
132
|
+
"snapshot_ms_median": 11.524417961481959,
|
|
133
|
+
"task_ms": 11286.688952008262,
|
|
134
|
+
"cpu_seconds": 1.9299999999999997,
|
|
135
|
+
"rss_mib_after": 1244.19140625,
|
|
136
|
+
"describe_calls": 7,
|
|
137
|
+
"stealth_sensitive_calls": 0
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# MCP snapshot workflow measurements
|
|
2
|
+
|
|
3
|
+
Measured on 2026-11-14 on Linux ARM64, Python 3.14.7, uv 0.13.5 and
|
|
4
|
+
Chrome for Testing 156.0.8078.47, headless with sandbox explicitly disabled.
|
|
5
|
+
Baseline revision: `914e24576ebe92e08d46c2867d065343b1b71fdc`.
|
|
6
|
+
The measurements precede the review fixes for inline-text search, URL escaping
|
|
7
|
+
and stacked dialogs. Those fixes have separate regression coverage; the
|
|
8
|
+
benchmark has not been rerun for them.
|
|
9
|
+
No production dependency was added.
|
|
10
|
+
|
|
11
|
+
## Method
|
|
12
|
+
|
|
13
|
+
The task page has 180 records, headings, descriptions and edit buttons. The
|
|
14
|
+
workflow locates record 179, opens its native modal editor, locates and fills
|
|
15
|
+
the Title field, locates Save changes, clicks it and checks the saved value.
|
|
16
|
+
This measures a three-interaction workflow, including DOM changes and modal
|
|
17
|
+
state, rather than an isolated snapshot. It is one synthetic task, not a
|
|
18
|
+
representative sample of the web or an actual model inference run.
|
|
19
|
+
|
|
20
|
+
Each WebSkrap mode runs three times in alternating order with a fresh browser.
|
|
21
|
+
Baseline loads the original CDP module from the revision above. Snapshot mode
|
|
22
|
+
uses the new code with default depth and character limits. Both page through
|
|
23
|
+
8,000-character responses until the named control is present. Find mode uses
|
|
24
|
+
literal name search. All modes use the same native WebSkrap interactions.
|
|
25
|
+
|
|
26
|
+
The Playwright comparison uses `@playwright/mcp@0.0.85` in npm's external cache,
|
|
27
|
+
the same Chromium executable and HTML, and the same names and actions. It
|
|
28
|
+
runs three times each with default automatic snapshots and with
|
|
29
|
+
`--snapshot-mode=none`. Both use `browser_find`. Playwright can save large snapshots to local files;
|
|
30
|
+
these counts cover returned text, not unrequested file contents. The task needs
|
|
31
|
+
only find results, so it does not read those files. Playwright loads a temporary
|
|
32
|
+
file; WebSkrap injects the document into about:blank. Loading and browser
|
|
33
|
+
startup are excluded. Verification reads are excluded from tool/token counts.
|
|
34
|
+
|
|
35
|
+
Token counts use tiktoken 0.14.0, `o200k_base`. Request counts encode JSON tool
|
|
36
|
+
names and arguments. Response counts encode each WebSkrap JSON payload once,
|
|
37
|
+
or Playwright's returned text content once. These are measured payload tokens,
|
|
38
|
+
not billed model input/output usage. They exclude JSON-RPC envelopes, duplicate
|
|
39
|
+
MCP text/structured serialization, tool schemas, model reasoning and replayed
|
|
40
|
+
conversation context. A client that sends both structured and text payloads to
|
|
41
|
+
a model can consume more tokens. No model was invoked to select actions.
|
|
42
|
+
|
|
43
|
+
Snapshot latency covers each read plus shaping; the reported number is the
|
|
44
|
+
median of each run's median. WebSkrap calls Python handlers directly;
|
|
45
|
+
Playwright uses MCP stdio, so cross-driver latency includes different transport
|
|
46
|
+
costs. Task time includes trusted interactions and verification. WebSkrap's
|
|
47
|
+
human cursor, scroll and typing timing differs from Playwright's interaction
|
|
48
|
+
implementation. Compare read latency separately from total task time.
|
|
49
|
+
|
|
50
|
+
CPU is the `/proc` CPU-time delta for Python plus its current descendants.
|
|
51
|
+
Memory is their summed RSS after the task, not peak memory or unique physical
|
|
52
|
+
memory; shared pages can be counted more than once. `/proc` sampling and
|
|
53
|
+
regex-worker lifecycle are approximate. Runs shared the host with tests.
|
|
54
|
+
Timing and memory are observations, not isolated performance guarantees.
|
|
55
|
+
|
|
56
|
+
## Results
|
|
57
|
+
|
|
58
|
+
All rows are medians over three successful runs. Each driver completed 3/3
|
|
59
|
+
workflows per mode with the expected saved value.
|
|
60
|
+
|
|
61
|
+
| Mode | Request tokens | Response tokens | Response bytes | Tool calls | Read ms | Task ms | CPU s | Summed RSS MiB |
|
|
62
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
63
|
+
| WebSkrap baseline, paged snapshot | 245 | 32,636 | 101,972 | 15 | 189.53 | 14,903.85 | 4.12 | 1,160.08 |
|
|
64
|
+
| WebSkrap new, paged snapshot | 161 | 11,054 | 34,568 | 9 | 132.49 | 13,055.63 | 2.61 | 1,247.55 |
|
|
65
|
+
| WebSkrap new, targeted find | 123 | 302 | 974 | 6 | 11.52 | 12,361.84 | 1.96 | 1,244.19 |
|
|
66
|
+
| Playwright MCP, find + default snapshots | 91 | 479 | 1,508 | 6 | 90.02 | 2,248.47 | 0.73 | 1,335.70 |
|
|
67
|
+
| Playwright MCP, find + no automatic snapshots | 91 | 351 | 1,162 | 6 | 77.64 | 2,026.75 | 0.75 | 1,314.17 |
|
|
68
|
+
|
|
69
|
+
The new snapshot path saves tokens in this task mainly because it omits
|
|
70
|
+
background content behind the modal. It preserves the existing format for
|
|
71
|
+
ordinary nonmodal pages. Targeted find reduces response tokens by 99.1% against
|
|
72
|
+
the baseline workflow and uses six calls instead of fifteen. This reduction is
|
|
73
|
+
specific to a known target near the end of a long page. Small pages or tasks
|
|
74
|
+
that need broad context will yield different results.
|
|
75
|
+
|
|
76
|
+
Targeted find makes seven `DOM.describeNode` calls per task, compared with
|
|
77
|
+
8,688 in the baseline and 2,894 for the new paged snapshots. All WebSkrap modes
|
|
78
|
+
made zero `Runtime.enable`, `Console.enable`, `Accessibility.enable` or
|
|
79
|
+
`Accessibility.getFullAXTree` calls during the measured task. This is a CDP
|
|
80
|
+
regression indicator, not evidence that a protected website accepts the browser.
|
|
81
|
+
No new public anti-bot acceptance test was run.
|
|
82
|
+
|
|
83
|
+
Playwright completes trusted interactions faster in this task. WebSkrap's
|
|
84
|
+
read-only find is faster in these samples, but its total task is slower.
|
|
85
|
+
Summed RSS is higher than the old baseline in these samples; this method does
|
|
86
|
+
not establish a memory improvement. Literal find avoids regex subprocesses;
|
|
87
|
+
caller-supplied regex still pays worker startup and timeout protection costs.
|
|
88
|
+
|
|
89
|
+
The server instruction text is 656 tokens versus 658 before this change.
|
|
90
|
+
The new find tool definition is 425 tokens when encoded as its full MCP tool
|
|
91
|
+
record. A client that includes all tool schemas in each model prompt pays
|
|
92
|
+
that extra context cost, which is excluded from the task table.
|
|
93
|
+
|
|
94
|
+
## Reproduce
|
|
95
|
+
|
|
96
|
+
1. Run `git show 914e24576ebe92e08d46c2867d065343b1b71fdc:src/webskrap/cdp_dom.py > /tmp/webskrap-snapshot-baseline.py`.
|
|
97
|
+
2. Run `uv run --with tiktoken==0.14.0 python scripts/benchmark_snapshots.py --baseline /tmp/webskrap-snapshot-baseline.py`.
|
|
98
|
+
3. For the optional external comparison, run `uv run --with tiktoken==0.14.0 python scripts/benchmark_playwright_snapshots.py`.
|
|
99
|
+
|
|
100
|
+
The first script accepts `--repeats`. The second requires npx and installs only
|
|
101
|
+
into its external cache. Neither adds a WebSkrap runtime dependency. Both
|
|
102
|
+
explicitly disable Chromium's sandbox for this host. Use a sandbox on hosts
|
|
103
|
+
that support it. Load only a trusted baseline source file.
|
|
104
|
+
|
|
105
|
+
Raw measurements: [WebSkrap](mcp-snapshots-webskrap.json) and
|
|
106
|
+
[Playwright MCP](mcp-snapshots-playwright.json). Implementation decisions and
|
|
107
|
+
semantic limits are in [the design note](../design/mcp-snapshots.md).
|
|
108
|
+
|
|
109
|
+
## Validation and review
|
|
110
|
+
|
|
111
|
+
The complete local non-live suite passed: 966 tests, 30 live tests deselected,
|
|
112
|
+
88.20% coverage. Review then found that the existing frame-navigation error
|
|
113
|
+
handler could swallow an iframe regex-worker timeout. The handler now propagates
|
|
114
|
+
search usage/internal errors. The final focused run passed 47 tests with appended
|
|
115
|
+
coverage of 88.34%, including an independent regression test for that error path.
|
|
116
|
+
|
|
117
|
+
Final checks passed: Ruff lint and format, Pyright 1.1.411 for Linux, Windows
|
|
118
|
+
and Darwin, Bandit, source/wheel build and Twine metadata validation. The review
|
|
119
|
+
was performed locally. Public live tests and Windows/macOS runtime tests were
|
|
120
|
+
not run on this Linux host. Cross-platform typing does not replace those tests.
|
|
121
|
+
|
|
122
|
+
The lockfile pins `opentelemetry-api==1.45.2`, which fixes
|
|
123
|
+
[GHSA-mf5v-w3w3-f6cg](https://osv.dev/vulnerability/GHSA-mf5v-w3w3-f6cg)
|
|
124
|
+
(CVE-2026-126360, traceparent parsing denial of service). The previous locked
|
|
125
|
+
version, 1.45.1, failed the CI audit. Only that package's version and artifact
|
|
126
|
+
metadata changed. Both pip-audit and the OSV scan of 51 locked public packages
|
|
127
|
+
now report no known vulnerabilities. No new dependency was added.
|
|
128
|
+
|
|
129
|
+
After the audit repair, the full local non-live suite passed 967 tests with
|
|
130
|
+
89.15% coverage. Windows CI also exposed a fixture race: challenge detection
|
|
131
|
+
can return before an iframe's image request reaches the proxy. The local test
|
|
132
|
+
now defers that request and waits for its actual arrival with a five-second
|
|
133
|
+
deadline. The old immediate assertion failed against the delayed fixture;
|
|
134
|
+
all six transition/authentication cases pass with the bounded wait. The final
|
|
135
|
+
combined frame/snapshot run passed 30 tests. Production challenge handling is
|
|
136
|
+
unchanged.
|
|
137
|
+
|
|
138
|
+
Review follow-up validation: the full non-live run passed 980 tests with 89.15%
|
|
139
|
+
coverage. The final focused run passed 31 tests, including additional Unicode
|
|
140
|
+
separator cases. The broader session/MCP regression run passed 81 tests. New
|
|
141
|
+
regressions cover inline paragraph/status/value searches, ref-like visible text,
|
|
142
|
+
URL and ARIA line injection, and stacked modal opening/closing order. Ruff,
|
|
143
|
+
three-platform Pyright, Bandit, pip-audit, OSV, build and Twine passed. Public
|
|
144
|
+
live tests were excluded; local cross-platform runtime tests remain delegated
|
|
145
|
+
to CI. No benchmark numbers were regenerated for this follow-up.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Targeted MCP accessibility reads
|
|
2
|
+
|
|
3
|
+
WebSkrap keeps its native CDP DOM walker. It does not enable Chromium's
|
|
4
|
+
Accessibility domain or Runtime event domain to generate snapshots.
|
|
5
|
+
|
|
6
|
+
## Existing behavior and changes
|
|
7
|
+
|
|
8
|
+
The starting implementation already had implicit ARIA roles, common accessible
|
|
9
|
+
name rules, states, wrapper elision, text merging, depth limits, open shadow
|
|
10
|
+
roots, iframe traversal, backend-node refs and character pagination. Actions
|
|
11
|
+
already returned page state without an automatic snapshot.
|
|
12
|
+
|
|
13
|
+
[Playwright MCP snapshots](https://playwright.dev/mcp/snapshots) describes
|
|
14
|
+
subtree targets, depth limits, optional boxes and matching nodes under ancestor
|
|
15
|
+
paths. WebSkrap adopts these features while keeping its selector and ref
|
|
16
|
+
contracts. Playwright locator expressions are not accepted. A CSS selector
|
|
17
|
+
must uniquely match an element in the main document. A frame ref selects that
|
|
18
|
+
frame; an element ref also works inside an open shadow root.
|
|
19
|
+
|
|
20
|
+
`browser_snapshot` adds optional `target` and `boxes` arguments after the
|
|
21
|
+
existing positional arguments. Its response fields remain unchanged. Depth is
|
|
22
|
+
relative to the selected root. Zero and null retain the unlimited-depth
|
|
23
|
+
meaning. Negative depth returns a usage error. Boxes are rounded CSS pixels
|
|
24
|
+
relative to the top-level viewport, including iframe offsets.
|
|
25
|
+
|
|
26
|
+
`browser_find` returns the same snapshot-window fields, plus match counts.
|
|
27
|
+
It searches semantic lines, including text and non-password values appended
|
|
28
|
+
after refs, and retains their ancestor paths. Only structural ref and box
|
|
29
|
+
tokens are removed for matching; ref-like text in names or content is kept.
|
|
30
|
+
It omits sibling subtrees and link URL metadata. Search takes either case-insensitive literal
|
|
31
|
+
text or Python regex. Literal text is escaped before compilation. Caller regex
|
|
32
|
+
uses the existing subprocess worker and two-second timeout. Regex never runs
|
|
33
|
+
in the page. Search has a match cap and independent character pagination.
|
|
34
|
+
|
|
35
|
+
## Ref and response cost
|
|
36
|
+
|
|
37
|
+
The DOM walk runs in an isolated world and returns semantic text plus remote
|
|
38
|
+
node objects. Search filters the text before issuing `DOM.describeNode`.
|
|
39
|
+
Only matching nodes, their ancestors and iframe owners need backend IDs.
|
|
40
|
+
Iframe owners remain available while child frames are searched. A final pass
|
|
41
|
+
removes nonmatching frame paths and applies the global match limit.
|
|
42
|
+
|
|
43
|
+
The walker still builds each available frame's semantic tree. This avoids a
|
|
44
|
+
second naming implementation and keeps semantics consistent with snapshots.
|
|
45
|
+
It does not make search traversal proportional only to the number of matches.
|
|
46
|
+
There is no persistent tree cache. Remote object groups are released on errors
|
|
47
|
+
and successful reads. Ref replacement uses one substitution pass. Per-call
|
|
48
|
+
random placeholders prevent page text that resembles a placeholder from being
|
|
49
|
+
used as a remote-object index.
|
|
50
|
+
|
|
51
|
+
Refs keep Chromium's backend-node identity. Repeated snapshots, find calls and
|
|
52
|
+
SPA updates preserve refs when they preserve the underlying node. A detached
|
|
53
|
+
node or a changed document returns `stale_ref`. There is no snapshot-generation
|
|
54
|
+
counter and no model-specific ref remapping. Existing interaction mechanisms
|
|
55
|
+
remain in use.
|
|
56
|
+
|
|
57
|
+
Responses do not repeat snapshots after actions. A caller can find, interact,
|
|
58
|
+
wait for a condition and request only the next needed control. A diff cache
|
|
59
|
+
was not added: this change measures explicit targeted reads, without adding a
|
|
60
|
+
second state lifetime or requiring clients to reconstruct trees.
|
|
61
|
+
|
|
62
|
+
## Semantics and limits
|
|
63
|
+
|
|
64
|
+
Link URL metadata uses quoted JSON strings, including escaped line separators.
|
|
65
|
+
A URL cannot add structural lines or refs to the snapshot. Search still omits
|
|
66
|
+
URL metadata. Dynamic ARIA state text is normalized to one line.
|
|
67
|
+
|
|
68
|
+
With stacked native modal dialogs, snapshots select the modal containing
|
|
69
|
+
`document.activeElement`. If focus does not identify a modal, the last modal in
|
|
70
|
+
document order is used. That fallback is an approximation of top-layer order.
|
|
71
|
+
Scope checks apply to the selected modal, and closing it restores the lower
|
|
72
|
+
modal's context.
|
|
73
|
+
|
|
74
|
+
The shared walker now also omits password input values, inert content, closed
|
|
75
|
+
details content and background content behind native modal dialogs. It resolves
|
|
76
|
+
`aria-labelledby` in the element's document or shadow root. Disabled fieldsets,
|
|
77
|
+
expanded=false, mixed pressed state, required, readonly, invalid and busy state
|
|
78
|
+
are represented. Existing compact role/name and common naming rules remain;
|
|
79
|
+
this is not a complete implementation of the browser accessibility algorithm.
|
|
80
|
+
|
|
81
|
+
Closed shadow roots and nested frames inside out-of-process iframes retain the
|
|
82
|
+
driver's existing limits. Frame navigation during traversal can omit that frame.
|
|
83
|
+
A snapshot is not an atomic DOM transaction. Continuation offsets are valid
|
|
84
|
+
only while the DOM remains unchanged. A character window can split a line or
|
|
85
|
+
ref. Match caps precede character caps. There are no sibling gap markers;
|
|
86
|
+
ancestor paths are intentionally partial trees.
|
|
87
|
+
|
|
88
|
+
Password redaction does not remove secrets from visible text, labels, URLs or
|
|
89
|
+
other field values. Snapshot and find output must receive the same privacy
|
|
90
|
+
handling as other browser reads.
|
|
91
|
+
|
|
92
|
+
See [measured workflows](../benchmarks/mcp-snapshots.md) for results and limits.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "webskrap"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.3.0"
|
|
8
8
|
description = "Async Python scraping on Chromium over its own CDP client, with coherent browser profiles and session controls."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|