Skip to content

Commit f2e2cd5

Browse files
committed
feat: add benchmarks docs page and patchright focus control
- web: benchmarks page renders stealth comparison and perf tables with coss ui - web: humanizer guide plus sessions/stealth doc updates and nav entries - config: add patchright_focus_control (omit focus_control for older Patchright) - bump version 0.5.6
1 parent f9e1cfa commit f2e2cd5

10 files changed

Lines changed: 493 additions & 6 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "webskrap"
7-
version = "0.5.5"
7+
version = "0.5.6"
88
description = "A Playwright-based Python scraping framework with coherent browser profiles and session controls."
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/webskrap/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ async def _fetch(
279279
navigation_timeout_ms=timeout_ms,
280280
resource_policy=resource_policy,
281281
patchright_context_profile=patchright_context_profile,
282+
patchright_focus_control=None,
282283
reduce_fingerprint_surface=reduce_fingerprint_surface,
283284
mask_headless_user_agent=mask_headless_user_agent,
284285
launch_args=launch_args,

src/webskrap/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ class SessionConfig(BaseModel):
185185
# scheme, reduced motion, and caller-provided extra headers) while still
186186
# avoiding viewport, user-agent, and JavaScript fingerprint patches.
187187
patchright_context_profile: bool = False
188+
# Patchright-specific focus behavior control. Set to None to omit the option
189+
# for Patchright versions that do not accept it.
190+
patchright_focus_control: bool | None = False
188191

189192
def launch_options(self) -> dict[str, Any]:
190193
options: dict[str, Any] = {
@@ -274,6 +277,8 @@ def context_options(self, profile: BrowserProfile) -> dict[str, Any]:
274277
# visible. The opt-in context profile below only applies settings
275278
# Chrome can expose natively through BrowserContext options.
276279
options: dict[str, Any] = {"no_viewport": True}
280+
if self.patchright_focus_control is not None:
281+
options["focus_control"] = self.patchright_focus_control
277282
if self.patchright_context_profile:
278283
options.update(
279284
{

tests/test_cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def test_fetch_json_is_bounded_and_uses_headless_stealth(monkeypatch: Any) -> No
6969
assert config.driver == "patchright"
7070
assert config.headless is True
7171
assert config.channel == "chrome"
72+
assert config.patchright_focus_control is None
7273

7374

7475
def test_fetch_stdout_prints_raw_content(monkeypatch: Any) -> None:

tests/test_models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def test_patchright_context_omits_profile_by_default() -> None:
6161
options = config.context_options(profile)
6262

6363
assert options["no_viewport"] is True
64+
assert options["focus_control"] is False
6465
assert "user_agent" not in options
6566
assert "extra_http_headers" not in options
6667

@@ -83,6 +84,7 @@ def test_patchright_context_profile_applies_native_context_metadata() -> None:
8384
options = config.context_options(profile)
8485

8586
assert options["no_viewport"] is True
87+
assert options["focus_control"] is False
8688
assert options["locale"] == "en-US"
8789
assert options["timezone_id"] == "Europe/Paris"
8890
assert options["color_scheme"] == "dark"
@@ -96,6 +98,16 @@ def test_patchright_context_profile_applies_native_context_metadata() -> None:
9698
assert "has_touch" not in options
9799

98100

101+
def test_patchright_focus_control_can_be_omitted() -> None:
102+
profile = BrowserProfile(name="test")
103+
config = SessionConfig(driver="patchright", patchright_focus_control=None)
104+
105+
options = config.context_options(profile)
106+
107+
assert options["no_viewport"] is True
108+
assert "focus_control" not in options
109+
110+
99111
def test_headless_chromium_gets_simulated_screen() -> None:
100112
config = SessionConfig(driver="patchright", channel="chrome", headless=True)
101113

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Humanizer
2+
3+
WebSkrap's humanizer is the `human_click` session helper. It waits for a visible
4+
target, scrolls it into view, moves the mouse along a curved eased path, and then
5+
clicks near the target center instead of calling Playwright's direct
6+
`page.click`.
7+
8+
```python
9+
async with WebSkrapClient() as client:
10+
session = await client.session("default")
11+
page = await session.context.new_page()
12+
await page.goto("https://example.com", wait_until="domcontentloaded")
13+
await session.human_click(page, "button[type='submit']")
14+
```
15+
16+
Use it when a normal page interaction should look closer to a manual browser
17+
click. It is useful for flows with hover-sensitive controls, scroll-dependent
18+
layouts, or simple behavioral checks that treat instant coordinate jumps as
19+
synthetic.
20+
21+
## Direct click fallback
22+
23+
Pass `human=False` to keep the same call shape while delegating to Playwright's
24+
click implementation.
25+
26+
```python
27+
await session.human_click(page, "button[type='submit']", human=False, timeout=5_000)
28+
```
29+
30+
## Click options
31+
32+
`human_click` accepts normal Playwright click options including `button`,
33+
`click_count`, `delay`, `modifiers`, `position`, `strict`, `timeout`, and
34+
`trial`.
35+
36+
```python
37+
await session.human_click(
38+
page,
39+
"button[type='submit']",
40+
strict=True,
41+
timeout=10_000,
42+
modifiers=["Shift"],
43+
)
44+
```
45+
46+
## Boundaries
47+
48+
Humanized clicks do not solve CAPTCHAs, bypass login walls, evade access
49+
controls, or grant permission to scrape restricted targets. Use them only on
50+
sites and workflows you are allowed to access.

web/content/user-guide/sessions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ await session.human_click(
119119
)
120120
```
121121

122+
See the [Humanizer](/docs/user-guide/humanizer) guide for the focused behavior
123+
reference and safety boundaries.
124+
122125
## Common options
123126

124127
```python

web/content/user-guide/stealth.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ config = SessionConfig(
3131
Patchright works best with real Chrome and a persistent context. If
3232
`user_data_dir` is omitted, WebSkrap creates a temporary persistent profile for
3333
Patchright sessions. Patchright contexts use the browser's real viewport and
34-
avoid synthetic viewport overrides (`no_viewport=True`).
34+
disable synthetic focus control (`no_viewport=True`, `focus_control=False`).
35+
The CLI omits `focus_control` for compatibility with Patchright versions that do
36+
not accept that option.
3537

3638
## Context profile mode
3739

0 commit comments

Comments
 (0)