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.
Files changed (111) hide show
  1. {webskrap-3.2.3 → webskrap-3.3.0}/CHANGELOG.md +59 -1
  2. {webskrap-3.2.3 → webskrap-3.3.0}/CONTRIBUTING.md +1 -1
  3. {webskrap-3.2.3 → webskrap-3.3.0}/PKG-INFO +42 -1
  4. {webskrap-3.2.3 → webskrap-3.3.0}/README.md +40 -0
  5. {webskrap-3.2.3 → webskrap-3.3.0}/SECURITY.md +4 -4
  6. webskrap-3.3.0/docs/benchmarks/mcp-snapshots-playwright.json +83 -0
  7. webskrap-3.3.0/docs/benchmarks/mcp-snapshots-webskrap.json +140 -0
  8. webskrap-3.3.0/docs/benchmarks/mcp-snapshots.md +145 -0
  9. webskrap-3.3.0/docs/design/mcp-snapshots.md +92 -0
  10. {webskrap-3.2.3 → webskrap-3.3.0}/docs/diagnostics/datadome.md +3 -3
  11. {webskrap-3.2.3 → webskrap-3.3.0}/pyproject.toml +16 -3
  12. webskrap-3.3.0/scripts/benchmark_playwright_snapshots.py +125 -0
  13. webskrap-3.3.0/scripts/benchmark_snapshots.py +199 -0
  14. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/browser_session.py +8 -2
  15. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp.py +18 -2
  16. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_dom.py +167 -31
  17. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_setup.py +1 -0
  18. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cli.py +35 -15
  19. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/client.py +124 -35
  20. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/guide.md +71 -7
  21. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/mcp_server.py +66 -15
  22. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/models.py +6 -3
  23. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_integration.py +125 -0
  24. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp.py +67 -0
  25. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_child_targets.py +6 -3
  26. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_session.py +1 -1
  27. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cli.py +22 -4
  28. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_client_unit.py +166 -13
  29. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_mcp.py +15 -1
  30. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_mcp_efficiency.py +68 -0
  31. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_parsing.py +1 -0
  32. webskrap-3.3.0/tests/test_snapshot_find.py +355 -0
  33. {webskrap-3.2.3 → webskrap-3.3.0}/uv.lock +166 -162
  34. webskrap-3.2.3/.agents/skills/webskrap/SKILL.md +0 -151
  35. webskrap-3.2.3/.agents/skills/webskrap/references/cli.md +0 -154
  36. webskrap-3.2.3/.agents/skills/webskrap/references/mcp.md +0 -130
  37. webskrap-3.2.3/.agents/skills/webskrap/references/python-api.md +0 -147
  38. webskrap-3.2.3/.claude/skills/webskrap/SKILL.md +0 -1
  39. webskrap-3.2.3/.codex/skills/webskrap/SKILL.md +0 -1
  40. webskrap-3.2.3/tests/test_docs.py +0 -44
  41. {webskrap-3.2.3 → webskrap-3.3.0}/.gitattributes +0 -0
  42. {webskrap-3.2.3 → webskrap-3.3.0}/.gitignore +0 -0
  43. {webskrap-3.2.3/.package-dist.zjESut → webskrap-3.3.0/.package-dist.qx0gMJ}/.gitignore +0 -0
  44. {webskrap-3.2.3 → webskrap-3.3.0}/LICENSE +0 -0
  45. {webskrap-3.2.3 → webskrap-3.3.0}/benchmarks.py +0 -0
  46. {webskrap-3.2.3 → webskrap-3.3.0}/docs/design/cdp-driver.md +0 -0
  47. {webskrap-3.2.3 → webskrap-3.3.0}/docs/migration-3.0.md +0 -0
  48. {webskrap-3.2.3 → webskrap-3.3.0}/scripts/driver_parity.py +0 -0
  49. {webskrap-3.2.3 → webskrap-3.3.0}/scripts/live_stealth_report.py +0 -0
  50. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/__init__.py +0 -0
  51. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/browser_cli.py +0 -0
  52. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cdp_session.py +0 -0
  53. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/challenges.py +0 -0
  54. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/cli_output.py +0 -0
  55. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/consent.py +0 -0
  56. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/diagnostics.py +0 -0
  57. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/display.py +0 -0
  58. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/errors.py +0 -0
  59. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/fetch_runtime.py +0 -0
  60. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/human.py +0 -0
  61. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/install.py +0 -0
  62. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/parsing.py +0 -0
  63. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/paths.py +0 -0
  64. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/profiles.py +0 -0
  65. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/proxy_auth.py +0 -0
  66. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/py.typed +0 -0
  67. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/search.py +0 -0
  68. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/text_search.py +0 -0
  69. {webskrap-3.2.3 → webskrap-3.3.0}/src/webskrap/urls.py +0 -0
  70. {webskrap-3.2.3 → webskrap-3.3.0}/tests/conftest.py +0 -0
  71. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/accepted-202.html +0 -0
  72. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-invalid-url-400.html +0 -0
  73. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-reference-403.html +0 -0
  74. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/akamai-sec-cpt.html +0 -0
  75. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/api-403.html +0 -0
  76. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/article-access-denied.html +0 -0
  77. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-1020.html +0 -0
  78. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/cloudflare-custom-block.html +0 -0
  79. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/blocks/not-found-404.html +0 -0
  80. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing.html +0 -0
  81. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing_challenge.html +0 -0
  82. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/bing_no_results.html +0 -0
  83. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg.html +0 -0
  84. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg_challenge.html +0 -0
  85. {webskrap-3.2.3 → webskrap-3.3.0}/tests/fixtures/search/ddg_no_results.html +0 -0
  86. {webskrap-3.2.3 → webskrap-3.3.0}/tests/live_stealth_helpers.py +0 -0
  87. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_bot_detection.py +0 -0
  88. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_bot_detection_headless.py +0 -0
  89. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_cli.py +0 -0
  90. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_session_unit.py +0 -0
  91. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_browser_vision.py +0 -0
  92. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_browser.py +0 -0
  93. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_cdp_setup.py +0 -0
  94. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_challenges.py +0 -0
  95. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_consent.py +0 -0
  96. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_consent_live.py +0 -0
  97. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_diagnostics.py +0 -0
  98. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_display.py +0 -0
  99. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_errors.py +0 -0
  100. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_fetch_runtime.py +0 -0
  101. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_human.py +0 -0
  102. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_install.py +0 -0
  103. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_live_stealth_helpers.py +0 -0
  104. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_live_stealth_report.py +0 -0
  105. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_models.py +0 -0
  106. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_paths.py +0 -0
  107. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_profiles.py +0 -0
  108. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_search.py +0 -0
  109. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_search_live.py +0 -0
  110. {webskrap-3.2.3 → webskrap-3.3.0}/tests/test_text_search.py +0 -0
  111. {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.2.3...HEAD
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.14 (CI runs all four).
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.2.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 | Supported |
9
- | ----------------------------------------- | --------- |
10
- | Latest minor (2.13.x as of 2026-10-01) | Yes |
11
- | Older minors | No |
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.