browsix 1.0.0__py3-none-any.whl
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.
- browsix/__init__.py +3 -0
- browsix/__main__.py +6 -0
- browsix/actions/__init__.py +1 -0
- browsix/actions/accessibility.py +53 -0
- browsix/actions/animation.py +59 -0
- browsix/actions/base.py +26 -0
- browsix/actions/bluetooth.py +68 -0
- browsix/actions/browser.py +39 -0
- browsix/actions/cast.py +67 -0
- browsix/actions/console.py +57 -0
- browsix/actions/css.py +96 -0
- browsix/actions/debug.py +134 -0
- browsix/actions/dialog.py +50 -0
- browsix/actions/dom.py +70 -0
- browsix/actions/dom_snapshot.py +45 -0
- browsix/actions/download.py +38 -0
- browsix/actions/emulation.py +45 -0
- browsix/actions/eval.py +36 -0
- browsix/actions/har.py +27 -0
- browsix/actions/input.py +62 -0
- browsix/actions/media.py +62 -0
- browsix/actions/multi.py +29 -0
- browsix/actions/navigate.py +98 -0
- browsix/actions/network.py +70 -0
- browsix/actions/overlay.py +61 -0
- browsix/actions/pdf.py +32 -0
- browsix/actions/performance.py +69 -0
- browsix/actions/permissions.py +50 -0
- browsix/actions/scrape.py +50 -0
- browsix/actions/screencast.py +44 -0
- browsix/actions/screenshot.py +37 -0
- browsix/actions/security.py +50 -0
- browsix/actions/service_worker.py +69 -0
- browsix/actions/storage.py +92 -0
- browsix/actions/tabs.py +53 -0
- browsix/actions/webaudio.py +62 -0
- browsix/actions/webauthn.py +96 -0
- browsix/auth.py +86 -0
- browsix/backend/__init__.py +1 -0
- browsix/backend/base.py +829 -0
- browsix/backend/bidi.py +953 -0
- browsix/backend/cdp.py +2339 -0
- browsix/backend/manager.py +114 -0
- browsix/cli/__init__.py +8 -0
- browsix/cli/app.py +2409 -0
- browsix/config.py +589 -0
- browsix/exceptions.py +61 -0
- browsix/multi.py +156 -0
- browsix/output.py +142 -0
- browsix/plugins.py +101 -0
- browsix/record.py +105 -0
- browsix/serve.py +343 -0
- browsix-1.0.0.dist-info/METADATA +128 -0
- browsix-1.0.0.dist-info/RECORD +57 -0
- browsix-1.0.0.dist-info/WHEEL +4 -0
- browsix-1.0.0.dist-info/entry_points.txt +2 -0
- browsix-1.0.0.dist-info/licenses/LICENSE +21 -0
browsix/actions/dom.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""DOM action for element inspection and manipulation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import DOMParams
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DOMAction(BaseAction[DOMParams, Any]):
|
|
13
|
+
"""Action for DOM operations.
|
|
14
|
+
|
|
15
|
+
Supports get (outer/inner HTML), query (single/all), attr get/set/remove,
|
|
16
|
+
remove, focus, and scroll.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
async def execute(self, backend: AbstractBackend) -> Any:
|
|
20
|
+
"""Execute the DOM action.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
backend: The browser backend to use.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str for "get" and "attr" actions, dict/list for "query",
|
|
27
|
+
None for set/remove/focus/scroll.
|
|
28
|
+
"""
|
|
29
|
+
params = self.params
|
|
30
|
+
if params.url:
|
|
31
|
+
await backend.navigate(params.url, params.wait)
|
|
32
|
+
|
|
33
|
+
if params.action == "get":
|
|
34
|
+
return await backend.dom_get(params.selector, outer=params.outer)
|
|
35
|
+
|
|
36
|
+
if params.action == "query":
|
|
37
|
+
return await backend.dom_query(params.selector, all=params.all)
|
|
38
|
+
|
|
39
|
+
if params.action == "attr":
|
|
40
|
+
if params.value is not None and params.attribute:
|
|
41
|
+
await backend.dom_set_attr(
|
|
42
|
+
params.selector, params.attribute, params.value
|
|
43
|
+
)
|
|
44
|
+
return None
|
|
45
|
+
if params.attribute:
|
|
46
|
+
return await backend.dom_get_attr(params.selector, params.attribute)
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
if params.action == "remove_attr":
|
|
50
|
+
if params.attribute:
|
|
51
|
+
await backend.dom_remove_attr(params.selector, params.attribute)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
if params.action == "remove":
|
|
55
|
+
await backend.dom_remove(params.selector)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
if params.action == "focus":
|
|
59
|
+
await backend.dom_focus(params.selector)
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
if params.action == "scroll":
|
|
63
|
+
await backend.dom_scroll(
|
|
64
|
+
selector=params.selector or None,
|
|
65
|
+
x=int(params.value or 0),
|
|
66
|
+
y=int(params.attribute or 0) if params.attribute else 0,
|
|
67
|
+
)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""DOM snapshot action for capturing raw DOM snapshots."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from browsix.actions.base import BaseAction
|
|
9
|
+
from browsix.backend.base import AbstractBackend
|
|
10
|
+
from browsix.config import BrowserOptions, WaitStrategy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class DOMSnapshotParams:
|
|
15
|
+
"""Parameters for DOM snapshot operations.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
url: URL to navigate to before capturing snapshot.
|
|
19
|
+
wait: Wait strategy after navigation.
|
|
20
|
+
browser: Browser launch options.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
url: str = ""
|
|
24
|
+
wait: WaitStrategy = field(default_factory=WaitStrategy)
|
|
25
|
+
browser: BrowserOptions = field(default_factory=BrowserOptions)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DOMSnapshotAction(BaseAction[DOMSnapshotParams, dict[str, Any]]):
|
|
29
|
+
"""Action for capturing a DOM snapshot of a web page."""
|
|
30
|
+
|
|
31
|
+
async def execute(self, backend: AbstractBackend) -> dict[str, Any]:
|
|
32
|
+
"""Execute the DOM snapshot action on the backend.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
backend: The browser backend to use.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict containing the raw DOM snapshot.
|
|
39
|
+
"""
|
|
40
|
+
await backend.launch(self.params.browser)
|
|
41
|
+
try:
|
|
42
|
+
await backend.navigate(self.params.url, self.params.wait)
|
|
43
|
+
return await backend.dom_snapshot()
|
|
44
|
+
finally:
|
|
45
|
+
await backend.close()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Download action for intercepting file downloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from browsix.actions.base import BaseAction
|
|
6
|
+
from browsix.backend.base import AbstractBackend
|
|
7
|
+
from browsix.config import BrowserOptions, WaitStrategy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DownloadAction(BaseAction[str, bytes]):
|
|
11
|
+
"""Action for intercepting file downloads."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
params: str,
|
|
16
|
+
url: str = "",
|
|
17
|
+
wait: WaitStrategy | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
self.params = params
|
|
20
|
+
self._url = url
|
|
21
|
+
self._wait = wait or WaitStrategy(strategy="load")
|
|
22
|
+
|
|
23
|
+
async def execute(self, backend: AbstractBackend) -> bytes:
|
|
24
|
+
"""Execute the download interception on the backend.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
backend: The browser backend to use.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Downloaded file bytes.
|
|
31
|
+
"""
|
|
32
|
+
await backend.launch(BrowserOptions())
|
|
33
|
+
try:
|
|
34
|
+
if self._url:
|
|
35
|
+
await backend.navigate(self._url, self._wait)
|
|
36
|
+
return await backend.intercept_download(self.params)
|
|
37
|
+
finally:
|
|
38
|
+
await backend.close()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Emulation action for device, viewport, geolocation, timezone, and dark mode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from browsix.actions.base import BaseAction
|
|
6
|
+
from browsix.backend.base import AbstractBackend
|
|
7
|
+
from browsix.config import EmulationParams
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EmulationAction(BaseAction[EmulationParams, None]):
|
|
11
|
+
"""Action for browser emulation operations.
|
|
12
|
+
|
|
13
|
+
Supports device emulation, viewport override, geolocation override,
|
|
14
|
+
timezone override, and dark mode emulation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
18
|
+
"""Execute the emulation action on the backend.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
backend: The backend to execute the action on.
|
|
22
|
+
"""
|
|
23
|
+
params = self.params
|
|
24
|
+
if params.action == "device":
|
|
25
|
+
if params.device is None:
|
|
26
|
+
raise ValueError("device is required for 'device' action")
|
|
27
|
+
await backend.emulate_device(params.device)
|
|
28
|
+
elif params.action == "viewport":
|
|
29
|
+
await backend.set_viewport(
|
|
30
|
+
params.width,
|
|
31
|
+
params.height,
|
|
32
|
+
params.device_scale_factor,
|
|
33
|
+
)
|
|
34
|
+
elif params.action == "geolocation":
|
|
35
|
+
await backend.set_geolocation(
|
|
36
|
+
params.latitude,
|
|
37
|
+
params.longitude,
|
|
38
|
+
params.accuracy,
|
|
39
|
+
)
|
|
40
|
+
elif params.action == "timezone":
|
|
41
|
+
await backend.set_timezone(params.timezone)
|
|
42
|
+
elif params.action == "dark_mode":
|
|
43
|
+
await backend.set_dark_mode(params.dark_mode)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f"Unknown emulation action: {params.action}")
|
browsix/actions/eval.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Eval action for executing JavaScript expressions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from browsix.actions.base import BaseAction
|
|
9
|
+
from browsix.backend.base import AbstractBackend
|
|
10
|
+
from browsix.config import EvalParams
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EvalAction(BaseAction[EvalParams, Any]):
|
|
14
|
+
"""Action for evaluating a JavaScript expression on a web page.
|
|
15
|
+
|
|
16
|
+
Navigates to the URL in params, then evaluates the expression.
|
|
17
|
+
Supports @file syntax to read expression from a file.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
async def execute(self, backend: AbstractBackend) -> Any:
|
|
21
|
+
"""Execute the eval action.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
backend: The browser backend to use.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The JavaScript evaluation result.
|
|
28
|
+
"""
|
|
29
|
+
params = self.params
|
|
30
|
+
await backend.navigate(params.url, params.wait)
|
|
31
|
+
|
|
32
|
+
expression = params.expression
|
|
33
|
+
if params.file:
|
|
34
|
+
expression = Path(params.file).read_text(encoding="utf-8") # noqa: ASYNC240
|
|
35
|
+
|
|
36
|
+
return await backend.eval(expression, await_promise=params.await_promise)
|
browsix/actions/har.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""HAR action for capturing network traffic as HAR 1.2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import HarParams
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HARAction(BaseAction[HarParams, dict[str, Any]]):
|
|
13
|
+
"""Action for capturing HAR data.
|
|
14
|
+
|
|
15
|
+
Navigates to a URL, captures network traffic, and returns a HAR 1.2 dict.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
async def execute(self, backend: AbstractBackend) -> dict[str, Any]:
|
|
19
|
+
"""Execute the HAR capture action.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
backend: The browser backend to use.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
HAR 1.2 compliant dict with log.entries.
|
|
26
|
+
"""
|
|
27
|
+
return await backend.capture_har(self.params)
|
browsix/actions/input.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Input action for browser interactions (click, type, fill, select, hover, key, drag, tap)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import InputParams
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InputAction(BaseAction[InputParams, Any]):
|
|
13
|
+
"""Action for performing input interactions on a web page."""
|
|
14
|
+
|
|
15
|
+
async def execute(self, backend: AbstractBackend) -> Any:
|
|
16
|
+
"""Execute the input action on the backend.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
backend: The browser backend to use.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
None for most actions; the result of the interaction.
|
|
23
|
+
"""
|
|
24
|
+
await backend.launch(self.params.browser)
|
|
25
|
+
try:
|
|
26
|
+
await backend.navigate(self.params.url, self.params.wait)
|
|
27
|
+
action = self.params.action
|
|
28
|
+
if action == "click":
|
|
29
|
+
await backend.click(
|
|
30
|
+
self.params.selector,
|
|
31
|
+
button=self.params.button,
|
|
32
|
+
click_count=self.params.click_count,
|
|
33
|
+
)
|
|
34
|
+
elif action == "type":
|
|
35
|
+
await backend.type_text(
|
|
36
|
+
self.params.selector,
|
|
37
|
+
self.params.text or "",
|
|
38
|
+
delay=self.params.delay,
|
|
39
|
+
)
|
|
40
|
+
elif action == "fill":
|
|
41
|
+
await backend.fill(
|
|
42
|
+
self.params.selector, self.params.value or ""
|
|
43
|
+
)
|
|
44
|
+
elif action == "select":
|
|
45
|
+
await backend.select_option(
|
|
46
|
+
self.params.selector, self.params.value or ""
|
|
47
|
+
)
|
|
48
|
+
elif action == "hover":
|
|
49
|
+
await backend.hover(self.params.selector)
|
|
50
|
+
elif action == "key":
|
|
51
|
+
await backend.key_press(self.params.key or "Enter")
|
|
52
|
+
elif action == "drag":
|
|
53
|
+
await backend.drag(
|
|
54
|
+
self.params.source or "", self.params.target or ""
|
|
55
|
+
)
|
|
56
|
+
elif action == "tap":
|
|
57
|
+
await backend.tap(self.params.selector)
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"Unknown input action: {action}")
|
|
60
|
+
finally:
|
|
61
|
+
await backend.close()
|
|
62
|
+
return None
|
browsix/actions/media.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Media action for listing media players and messages (experimental)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from browsix.actions.base import BaseAction
|
|
9
|
+
from browsix.backend.base import AbstractBackend
|
|
10
|
+
from browsix.config import BrowserOptions, WaitStrategy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class MediaParams:
|
|
15
|
+
"""Parameters for Media operations.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
url: URL to navigate to before Media operations.
|
|
19
|
+
action: Media action — "list", "messages".
|
|
20
|
+
player_id: Media player ID for "messages" action.
|
|
21
|
+
wait: Wait strategy after navigation.
|
|
22
|
+
browser: Browser launch options.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
url: str = ""
|
|
26
|
+
action: str = "list"
|
|
27
|
+
player_id: str | None = None
|
|
28
|
+
wait: WaitStrategy = field(default_factory=WaitStrategy)
|
|
29
|
+
browser: BrowserOptions = field(default_factory=BrowserOptions)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MediaAction(BaseAction[MediaParams, Any]):
|
|
33
|
+
"""Action for Media operations (experimental)."""
|
|
34
|
+
|
|
35
|
+
async def execute(self, backend: AbstractBackend) -> Any:
|
|
36
|
+
"""Execute the Media action on the backend.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
backend: An AbstractBackend instance.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Result of the Media operation.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
await backend.launch(self.params.browser)
|
|
46
|
+
if self.params.url:
|
|
47
|
+
await backend.navigate(self.params.url, self.params.wait)
|
|
48
|
+
|
|
49
|
+
action = self.params.action
|
|
50
|
+
|
|
51
|
+
if action == "list":
|
|
52
|
+
return await backend.media_get_players()
|
|
53
|
+
|
|
54
|
+
if action == "messages":
|
|
55
|
+
if not self.params.player_id:
|
|
56
|
+
raise ValueError("player_id is required for messages action")
|
|
57
|
+
return await backend.media_get_messages(self.params.player_id)
|
|
58
|
+
|
|
59
|
+
raise ValueError(f"Unknown Media action: {action}")
|
|
60
|
+
|
|
61
|
+
finally:
|
|
62
|
+
await backend.close()
|
browsix/actions/multi.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Multi-action: parse YAML and execute multiple actions on a single backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from browsix.actions.base import BaseAction
|
|
9
|
+
from browsix.backend.base import AbstractBackend
|
|
10
|
+
from browsix.multi import execute_actions, parse_yaml
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MultiAction(BaseAction[Path, list[Any]]):
|
|
14
|
+
"""Action that parses a YAML config and executes multiple actions sequentially.
|
|
15
|
+
|
|
16
|
+
Reuses a single backend instance for all actions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
async def execute(self, backend: AbstractBackend) -> list[Any]:
|
|
20
|
+
"""Parse the YAML config and execute all actions on the backend.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
backend: An already-launched AbstractBackend instance.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of results from each action.
|
|
27
|
+
"""
|
|
28
|
+
actions = parse_yaml(self.params)
|
|
29
|
+
return await execute_actions(actions, backend)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Navigation actions for back, forward, reload, stop, and wait."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import WaitStrategy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class NavigateParams:
|
|
14
|
+
"""Parameters for navigation actions.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
url: URL to navigate to (for navigate action).
|
|
18
|
+
wait: Wait strategy after navigation.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
url: str = ""
|
|
22
|
+
wait: WaitStrategy | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NavigateAction(BaseAction[NavigateParams, None]):
|
|
26
|
+
"""Action for navigating to a URL."""
|
|
27
|
+
|
|
28
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
29
|
+
"""Execute the navigate action.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
backend: The browser backend to use.
|
|
33
|
+
"""
|
|
34
|
+
params = self.params
|
|
35
|
+
await backend.navigate(params.url, params.wait)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BackAction(BaseAction[None, None]):
|
|
39
|
+
"""Action for navigating back in history."""
|
|
40
|
+
|
|
41
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
42
|
+
"""Execute the back action.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
backend: The browser backend to use.
|
|
46
|
+
"""
|
|
47
|
+
await backend.go_back()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ForwardAction(BaseAction[None, None]):
|
|
51
|
+
"""Action for navigating forward in history."""
|
|
52
|
+
|
|
53
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
54
|
+
"""Execute the forward action.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
backend: The browser backend to use.
|
|
58
|
+
"""
|
|
59
|
+
await backend.go_forward()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ReloadAction(BaseAction[bool, None]):
|
|
63
|
+
"""Action for reloading the current page.
|
|
64
|
+
|
|
65
|
+
Params is a bool indicating whether to ignore cache.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
69
|
+
"""Execute the reload action.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
backend: The browser backend to use.
|
|
73
|
+
"""
|
|
74
|
+
await backend.reload(ignore_cache=self.params)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class StopAction(BaseAction[None, None]):
|
|
78
|
+
"""Action for stopping all pending navigations."""
|
|
79
|
+
|
|
80
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
81
|
+
"""Execute the stop action.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
backend: The browser backend to use.
|
|
85
|
+
"""
|
|
86
|
+
await backend.stop_loading()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class WaitAction(BaseAction[WaitStrategy, None]):
|
|
90
|
+
"""Action for waiting for a condition."""
|
|
91
|
+
|
|
92
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
93
|
+
"""Execute the wait action.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
backend: The browser backend to use.
|
|
97
|
+
"""
|
|
98
|
+
await backend.wait_for(self.params)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Network action for cookies, headers, and user-agent management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import NetworkParams
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NetworkAction(BaseAction[NetworkParams, Any]):
|
|
13
|
+
"""Action for network operations.
|
|
14
|
+
|
|
15
|
+
Supports cookies get/set/delete/clear, headers, and user-agent override.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
async def execute(self, backend: AbstractBackend) -> Any:
|
|
19
|
+
"""Execute the network action.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
backend: The browser backend to use.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of cookies for "cookies_get", None for other actions.
|
|
26
|
+
"""
|
|
27
|
+
params = self.params
|
|
28
|
+
|
|
29
|
+
if params.action == "cookies_get":
|
|
30
|
+
return await backend.get_cookies()
|
|
31
|
+
|
|
32
|
+
if params.action == "cookies_set":
|
|
33
|
+
if params.cookie:
|
|
34
|
+
await backend.set_cookie(params.cookie)
|
|
35
|
+
elif params.cookies:
|
|
36
|
+
from browsix.config import CookieParams
|
|
37
|
+
|
|
38
|
+
for cookie_dict in params.cookies:
|
|
39
|
+
cookie = CookieParams(
|
|
40
|
+
name=str(cookie_dict.get("name", "")),
|
|
41
|
+
value=str(cookie_dict.get("value", "")),
|
|
42
|
+
domain=str(cookie_dict.get("domain", "")),
|
|
43
|
+
path=str(cookie_dict.get("path", "/")),
|
|
44
|
+
secure=bool(cookie_dict.get("secure", True)),
|
|
45
|
+
http_only=bool(cookie_dict.get("http_only", False)),
|
|
46
|
+
same_site=str(cookie_dict.get("same_site", "Lax")),
|
|
47
|
+
)
|
|
48
|
+
await backend.set_cookie(cookie)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
if params.action == "cookies_delete":
|
|
52
|
+
if params.name and params.domain:
|
|
53
|
+
await backend.delete_cookie(params.name, params.domain)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
if params.action == "cookies_clear":
|
|
57
|
+
await backend.clear_cookies()
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
if params.action == "headers":
|
|
61
|
+
if params.headers:
|
|
62
|
+
await backend.set_headers(params.headers)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
if params.action == "user_agent":
|
|
66
|
+
if params.user_agent:
|
|
67
|
+
await backend.set_user_agent(params.user_agent)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return None
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Overlay action for highlighting elements and clearing highlights."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
from browsix.actions.base import BaseAction
|
|
8
|
+
from browsix.backend.base import AbstractBackend
|
|
9
|
+
from browsix.config import BrowserOptions, WaitStrategy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class OverlayParams:
|
|
14
|
+
"""Parameters for overlay operations.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
url: URL to navigate to before overlay.
|
|
18
|
+
selector: CSS selector for the element to highlight.
|
|
19
|
+
color: RGBA color string for the highlight overlay.
|
|
20
|
+
action: Overlay action — "highlight" or "clear".
|
|
21
|
+
wait: Wait strategy after navigation.
|
|
22
|
+
browser: Browser launch options.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
url: str = ""
|
|
26
|
+
selector: str | None = None
|
|
27
|
+
color: str = "rgba(255,0,0,0.5)"
|
|
28
|
+
action: str = "highlight"
|
|
29
|
+
wait: WaitStrategy = field(default_factory=WaitStrategy)
|
|
30
|
+
browser: BrowserOptions = field(default_factory=BrowserOptions)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class OverlayAction(BaseAction[OverlayParams, None]):
|
|
34
|
+
"""Action for overlay highlight and clear operations."""
|
|
35
|
+
|
|
36
|
+
async def execute(self, backend: AbstractBackend) -> None:
|
|
37
|
+
"""Execute the overlay action on the backend.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
backend: The browser backend to use.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If the action is not recognized or selector is missing.
|
|
44
|
+
"""
|
|
45
|
+
await backend.launch(self.params.browser)
|
|
46
|
+
try:
|
|
47
|
+
await backend.navigate(self.params.url, self.params.wait)
|
|
48
|
+
await self._run_action(backend)
|
|
49
|
+
finally:
|
|
50
|
+
await backend.close()
|
|
51
|
+
|
|
52
|
+
async def _run_action(self, backend: AbstractBackend) -> None:
|
|
53
|
+
action = self.params.action
|
|
54
|
+
if action == "highlight":
|
|
55
|
+
if not self.params.selector:
|
|
56
|
+
raise ValueError("selector is required for highlight action")
|
|
57
|
+
await backend.overlay_highlight(self.params.selector, self.params.color)
|
|
58
|
+
elif action == "clear":
|
|
59
|
+
await backend.overlay_clear()
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError(f"Unknown overlay action: {action}")
|
browsix/actions/pdf.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""PDF action for generating page PDFs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from browsix.actions.base import BaseAction
|
|
6
|
+
from browsix.backend.base import AbstractBackend
|
|
7
|
+
from browsix.config import PDFParams
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PDFAction(BaseAction[PDFParams, bytes]):
|
|
11
|
+
"""Action for generating a PDF of a web page.
|
|
12
|
+
|
|
13
|
+
Navigates to the URL in params, optionally executes JS, and generates
|
|
14
|
+
a PDF.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
async def execute(self, backend: AbstractBackend) -> bytes:
|
|
18
|
+
"""Execute the PDF action.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
backend: The browser backend to use.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
PDF bytes.
|
|
25
|
+
"""
|
|
26
|
+
params = self.params
|
|
27
|
+
await backend.navigate(params.url, params.wait)
|
|
28
|
+
|
|
29
|
+
if params.js:
|
|
30
|
+
await backend.eval(params.js, await_promise=True)
|
|
31
|
+
|
|
32
|
+
return await backend.pdf(params)
|