webskrap 3.2.3__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.3 → webskrap-3.3.0}/CHANGELOG.md +59 -1
- {webskrap-3.2.3 → webskrap-3.3.0}/CONTRIBUTING.md +1 -1
- {webskrap-3.2.3 → webskrap-3.3.0}/PKG-INFO +42 -1
- {webskrap-3.2.3 → webskrap-3.3.0}/README.md +40 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/SECURITY.md +4 -4
- 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.3 → webskrap-3.3.0}/docs/diagnostics/datadome.md +3 -3
- {webskrap-3.2.3 → webskrap-3.3.0}/pyproject.toml +16 -3
- webskrap-3.3.0/scripts/benchmark_playwright_snapshots.py +125 -0
- webskrap-3.3.0/scripts/benchmark_snapshots.py +199 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/browser_session.py +8 -2
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp.py +18 -2
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_dom.py +167 -31
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_setup.py +1 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cli.py +35 -15
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/client.py +124 -35
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/guide.md +71 -7
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/mcp_server.py +66 -15
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/models.py +6 -3
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_integration.py +125 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp.py +67 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_child_targets.py +6 -3
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_session.py +1 -1
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cli.py +22 -4
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_client_unit.py +166 -13
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_mcp.py +15 -1
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_mcp_efficiency.py +68 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_parsing.py +1 -0
- webskrap-3.3.0/tests/test_snapshot_find.py +355 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/uv.lock +166 -162
- webskrap-3.2.3/.agents/skills/webskrap/SKILL.md +0 -151
- webskrap-3.2.3/.agents/skills/webskrap/references/cli.md +0 -154
- webskrap-3.2.3/.agents/skills/webskrap/references/mcp.md +0 -130
- webskrap-3.2.3/.agents/skills/webskrap/references/python-api.md +0 -147
- webskrap-3.2.3/.claude/skills/webskrap/SKILL.md +0 -1
- webskrap-3.2.3/.codex/skills/webskrap/SKILL.md +0 -1
- webskrap-3.2.3/tests/test_docs.py +0 -44
- {webskrap-3.2.3 → webskrap-3.3.0}/.gitattributes +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/.gitignore +0 -0
- {webskrap-3.2.3/.package-dist.zjESut → webskrap-3.3.0/.package-dist.qx0gMJ}/.gitignore +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/LICENSE +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/benchmarks.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/docs/design/cdp-driver.md +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/docs/migration-3.0.md +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/scripts/driver_parity.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/scripts/live_stealth_report.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/__init__.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/browser_cli.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_session.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/challenges.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cli_output.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/consent.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/diagnostics.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/display.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/errors.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/fetch_runtime.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/human.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/install.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/parsing.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/paths.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/profiles.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/proxy_auth.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/py.typed +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/search.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/text_search.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/urls.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/conftest.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/accepted-202.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-invalid-url-400.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-reference-403.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-sec-cpt.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/api-403.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/article-access-denied.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-1020.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-custom-block.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/not-found-404.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing_challenge.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing_no_results.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg_challenge.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg_no_results.html +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/live_stealth_helpers.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_bot_detection.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_bot_detection_headless.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_cli.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_session_unit.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_vision.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_browser.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_setup.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_challenges.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_consent.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_consent_live.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_diagnostics.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_display.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_errors.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_fetch_runtime.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_human.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_install.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_live_stealth_helpers.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_live_stealth_report.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_models.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_paths.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_profiles.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_search.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_search_live.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_text_search.py +0 -0
- {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_urls.py +0 -0
|
@@ -9,6 +9,62 @@ 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
|
+
|
|
47
|
+
## [3.2.4] - 2026-11-01
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Python 3.15 is a supported version. CI runs the test suite on it, and the
|
|
52
|
+
package metadata lists it.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- The source distribution no longer contains the agent skill folders and the
|
|
57
|
+
test that checks them. They stay in the Git repository. Before, the sdist
|
|
58
|
+
held only one of the linked skill reference paths, and its test failed.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- The CLI guide lists the exit status for each error code. It said before that
|
|
63
|
+
every browser command failure exits with 1.
|
|
64
|
+
- The MCP guide and the agent reference now describe `browser_wait_for`.
|
|
65
|
+
- `SECURITY.md` names the supported release line without an old version
|
|
66
|
+
number.
|
|
67
|
+
|
|
12
68
|
## [3.2.3] - 2026-10-30
|
|
13
69
|
|
|
14
70
|
### Security
|
|
@@ -980,7 +1036,9 @@ entries below.
|
|
|
980
1036
|
|
|
981
1037
|
Earlier releases (0.1.0 - 0.4.9) are recorded in the git history and tags only.
|
|
982
1038
|
|
|
983
|
-
[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
|
|
1041
|
+
[3.2.4]: https://github.com/kacigaya/webskrap/compare/v3.2.3...v3.2.4
|
|
984
1042
|
[3.2.3]: https://github.com/kacigaya/webskrap/compare/v3.2.2...v3.2.3
|
|
985
1043
|
[3.2.2]: https://github.com/kacigaya/webskrap/compare/v3.2.1...v3.2.2
|
|
986
1044
|
[3.2.1]: https://github.com/kacigaya/webskrap/compare/v3.2.0...v3.2.1
|
|
@@ -5,7 +5,7 @@ what a reviewable pull request looks like.
|
|
|
5
5
|
|
|
6
6
|
## Requirements
|
|
7
7
|
|
|
8
|
-
- Python 3.11, 3.12, 3.13, or 3.
|
|
8
|
+
- Python 3.11, 3.12, 3.13, 3.14, or 3.15 (CI runs all five).
|
|
9
9
|
- [uv](https://docs.astral.sh/uv/) for environments and dependencies.
|
|
10
10
|
- A Chromium build for browser tests, installed via `webskrap install`.
|
|
11
11
|
|
|
@@ -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
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
21
|
Classifier: Typing :: Typed
|
|
21
22
|
Requires-Python: >=3.11
|
|
22
23
|
Requires-Dist: mcp<3,>=2
|
|
@@ -86,6 +87,9 @@ Known challenge pages (DataDome, Cloudflare, Akamai, AWS WAF) and provider block
|
|
|
86
87
|
pages (DataDome, Cloudflare, Akamai) raise `blocked`. WebSkrap waits for device
|
|
87
88
|
checks that reload the page. It does not retry refusals or solve CAPTCHAs.
|
|
88
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.
|
|
89
93
|
|
|
90
94
|
For manual CAPTCHA completion, open a persistent browser with
|
|
91
95
|
`webskrap browser open --headed --session manual --proxy http://localhost:8888 URL`.
|
|
@@ -98,6 +102,30 @@ Keep `profile_mode="native"` for the browser's own identity. On Linux with Xvfb,
|
|
|
98
102
|
Full profile emulation is useful for device tests, but cannot replace every
|
|
99
103
|
worker or iframe value. See the [identity limits](src/webskrap/guide.md).
|
|
100
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
|
+
|
|
101
129
|
## Search
|
|
102
130
|
|
|
103
131
|
A search loads the engine's results page in the same stealth browser and
|
|
@@ -143,6 +171,19 @@ Coordinates are CSS pixels from the original image's top-left. Take a fresh
|
|
|
143
171
|
view after scrolling or page changes. Headless Chromium needs no Xvfb for
|
|
144
172
|
this; the agent's client and model must support image input.
|
|
145
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
|
+
|
|
146
187
|
## Documentation
|
|
147
188
|
|
|
148
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)
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
WebSkrap is developed on `main` and released from tags. Fixes land in a new
|
|
6
6
|
patch release on top of the latest minor; older minors are not backported.
|
|
7
7
|
|
|
8
|
-
| Version
|
|
9
|
-
|
|
|
10
|
-
| Latest minor
|
|
11
|
-
| Older minors
|
|
8
|
+
| Version | Supported |
|
|
9
|
+
| ------------ | --------- |
|
|
10
|
+
| Latest minor | Yes |
|
|
11
|
+
| Older minors | No |
|
|
12
12
|
|
|
13
13
|
## Reporting a vulnerability
|
|
14
14
|
|
|
@@ -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.
|