wavexis 2.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.
Files changed (69) hide show
  1. wavexis/__init__.py +3 -0
  2. wavexis/__main__.py +6 -0
  3. wavexis/actions/__init__.py +1 -0
  4. wavexis/actions/accessibility.py +62 -0
  5. wavexis/actions/animation.py +59 -0
  6. wavexis/actions/base.py +31 -0
  7. wavexis/actions/bluetooth.py +68 -0
  8. wavexis/actions/browser.py +39 -0
  9. wavexis/actions/cast.py +67 -0
  10. wavexis/actions/console.py +57 -0
  11. wavexis/actions/cookies.py +57 -0
  12. wavexis/actions/crawl.py +105 -0
  13. wavexis/actions/css.py +107 -0
  14. wavexis/actions/debug.py +145 -0
  15. wavexis/actions/dialog.py +59 -0
  16. wavexis/actions/dom.py +70 -0
  17. wavexis/actions/dom_snapshot.py +45 -0
  18. wavexis/actions/download.py +45 -0
  19. wavexis/actions/emulation.py +45 -0
  20. wavexis/actions/eval.py +36 -0
  21. wavexis/actions/extract.py +86 -0
  22. wavexis/actions/form.py +73 -0
  23. wavexis/actions/har.py +27 -0
  24. wavexis/actions/headers.py +52 -0
  25. wavexis/actions/input.py +72 -0
  26. wavexis/actions/lighthouse.py +312 -0
  27. wavexis/actions/media.py +62 -0
  28. wavexis/actions/multi.py +40 -0
  29. wavexis/actions/navigate.py +98 -0
  30. wavexis/actions/network.py +70 -0
  31. wavexis/actions/overlay.py +69 -0
  32. wavexis/actions/pdf.py +32 -0
  33. wavexis/actions/performance.py +80 -0
  34. wavexis/actions/permissions.py +59 -0
  35. wavexis/actions/record.py +151 -0
  36. wavexis/actions/scrape.py +50 -0
  37. wavexis/actions/screencast.py +50 -0
  38. wavexis/actions/screenshot.py +37 -0
  39. wavexis/actions/security.py +59 -0
  40. wavexis/actions/service_worker.py +69 -0
  41. wavexis/actions/session.py +114 -0
  42. wavexis/actions/storage.py +92 -0
  43. wavexis/actions/tabs.py +53 -0
  44. wavexis/actions/wait.py +23 -0
  45. wavexis/actions/webaudio.py +62 -0
  46. wavexis/actions/webauthn.py +96 -0
  47. wavexis/actions/websocket.py +110 -0
  48. wavexis/auth.py +86 -0
  49. wavexis/backend/__init__.py +1 -0
  50. wavexis/backend/base.py +838 -0
  51. wavexis/backend/bidi.py +2160 -0
  52. wavexis/backend/cdp.py +2413 -0
  53. wavexis/backend/manager.py +120 -0
  54. wavexis/cli/__init__.py +8 -0
  55. wavexis/cli/app.py +3874 -0
  56. wavexis/config.py +651 -0
  57. wavexis/exceptions.py +95 -0
  58. wavexis/init.py +177 -0
  59. wavexis/multi.py +305 -0
  60. wavexis/output.py +195 -0
  61. wavexis/plugins.py +250 -0
  62. wavexis/record.py +114 -0
  63. wavexis/repl.py +225 -0
  64. wavexis/serve.py +665 -0
  65. wavexis-2.0.0.dist-info/METADATA +463 -0
  66. wavexis-2.0.0.dist-info/RECORD +69 -0
  67. wavexis-2.0.0.dist-info/WHEEL +4 -0
  68. wavexis-2.0.0.dist-info/entry_points.txt +2 -0
  69. wavexis-2.0.0.dist-info/licenses/LICENSE +21 -0
wavexis/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """wavexis — CLI de automatización de navegador."""
2
+
3
+ __version__ = "2.0.0"
wavexis/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow `python -m wavexis` execution."""
2
+
3
+ from wavexis.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1 @@
1
+ """Actions layer for wavexis."""
@@ -0,0 +1,62 @@
1
+ """Accessibility action for retrieving a11y tree, node, and ancestors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from wavexis.actions.base import BaseAction
8
+ from wavexis.backend.base import AbstractBackend
9
+ from wavexis.config import WaitStrategy
10
+
11
+
12
+ class AccessibilityAction(BaseAction[Any, Any]):
13
+ """Action for accessibility tree operations."""
14
+
15
+ def __init__(
16
+ self,
17
+ params: Any,
18
+ action: str = "tree",
19
+ node_id: str = "",
20
+ url: str = "",
21
+ wait: WaitStrategy | None = None,
22
+ ) -> None:
23
+ """Initialize the accessibility action.
24
+
25
+ Args:
26
+ params: Action parameters.
27
+ action: Accessibility action type ("tree" or "node").
28
+ node_id: Node ID for node-specific actions.
29
+ url: URL to navigate to before the action.
30
+ wait: Wait strategy after navigation.
31
+ """
32
+ self.params = params
33
+ self._action = action
34
+ self._node_id = node_id
35
+ self._url = url
36
+ self._wait = wait or WaitStrategy(strategy="load")
37
+
38
+ async def execute(self, backend: AbstractBackend) -> Any:
39
+ """Execute the accessibility action on the backend.
40
+
41
+ Args:
42
+ backend: The browser backend to use.
43
+
44
+ Returns:
45
+ Accessibility tree dict, node dict, or list of ancestor dicts.
46
+ """
47
+ from wavexis.config import BrowserOptions
48
+
49
+ await backend.launch(BrowserOptions())
50
+ try:
51
+ if self._url:
52
+ await backend.navigate(self._url, self._wait)
53
+ if self._action == "tree":
54
+ return await backend.a11y_tree()
55
+ elif self._action == "node":
56
+ return await backend.a11y_node(self._node_id)
57
+ elif self._action == "ancestors":
58
+ return await backend.a11y_ancestors(self._node_id)
59
+ else:
60
+ raise ValueError(f"Unknown a11y action: {self._action}")
61
+ finally:
62
+ await backend.close()
@@ -0,0 +1,59 @@
1
+ """Animation action for listing, pausing, playing, and seeking animations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from wavexis.actions.base import BaseAction
8
+ from wavexis.backend.base import AbstractBackend
9
+ from wavexis.config import AnimationParams
10
+
11
+
12
+ class AnimationAction(BaseAction[AnimationParams, Any]):
13
+ """Action for animation operations."""
14
+
15
+ async def execute(self, backend: AbstractBackend) -> Any:
16
+ """Execute the animation action on the backend.
17
+
18
+ Args:
19
+ backend: An AbstractBackend instance.
20
+
21
+ Returns:
22
+ Result of the animation operation.
23
+ """
24
+ try:
25
+ await backend.launch(self.params.browser)
26
+ if self.params.url:
27
+ await backend.navigate(self.params.url, self.params.wait)
28
+
29
+ action = self.params.action
30
+
31
+ if action == "list":
32
+ return await backend.animation_list()
33
+
34
+ if action == "pause":
35
+ if not self.params.animation_id:
36
+ raise ValueError("animation_id is required for pause action")
37
+ await backend.animation_pause(self.params.animation_id)
38
+ return None
39
+
40
+ if action == "play":
41
+ if not self.params.animation_id:
42
+ raise ValueError("animation_id is required for play action")
43
+ await backend.animation_play(self.params.animation_id)
44
+ return None
45
+
46
+ if action == "seek":
47
+ if not self.params.animation_id or self.params.time_ms is None:
48
+ raise ValueError(
49
+ "animation_id and time_ms are required for seek action"
50
+ )
51
+ await backend.animation_seek(
52
+ self.params.animation_id, self.params.time_ms
53
+ )
54
+ return None
55
+
56
+ raise ValueError(f"Unknown animation action: {action}")
57
+
58
+ finally:
59
+ await backend.close()
@@ -0,0 +1,31 @@
1
+ """Base action class for wavexis actions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Generic, TypeVar
7
+
8
+ from wavexis.backend.base import AbstractBackend
9
+
10
+ P = TypeVar("P")
11
+ R = TypeVar("R")
12
+
13
+
14
+ class BaseAction(ABC, Generic[P, R]):
15
+ """Abstract base class for all wavexis actions.
16
+
17
+ An action encapsulates a single operation (e.g. screenshot, eval, pdf)
18
+ that is executed against a backend.
19
+ """
20
+
21
+ def __init__(self, params: P) -> None:
22
+ """Initialize the action with parameters.
23
+
24
+ Args:
25
+ params: Action-specific parameters.
26
+ """
27
+ self.params = params
28
+
29
+ @abstractmethod
30
+ async def execute(self, backend: AbstractBackend) -> R:
31
+ """Execute the action against the given backend."""
@@ -0,0 +1,68 @@
1
+ """Bluetooth action for BLE emulation (experimental)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from wavexis.actions.base import BaseAction
9
+ from wavexis.backend.base import AbstractBackend
10
+ from wavexis.config import BrowserOptions, WaitStrategy
11
+
12
+
13
+ @dataclass
14
+ class BluetoothParams:
15
+ """Parameters for Bluetooth emulation operations.
16
+
17
+ Attributes:
18
+ url: URL to navigate to before Bluetooth operations.
19
+ action: Bluetooth action — "emulate", "stop".
20
+ name: Device name for "emulate" action.
21
+ address: Device MAC address for "emulate" action.
22
+ wait: Wait strategy after navigation.
23
+ browser: Browser launch options.
24
+ """
25
+
26
+ url: str = ""
27
+ action: str = "emulate"
28
+ name: str | None = None
29
+ address: str = "00:00:00:00:00:01"
30
+ wait: WaitStrategy = field(default_factory=WaitStrategy)
31
+ browser: BrowserOptions = field(default_factory=BrowserOptions)
32
+
33
+
34
+ class BluetoothAction(BaseAction[BluetoothParams, Any]):
35
+ """Action for Bluetooth emulation operations (experimental)."""
36
+
37
+ async def execute(self, backend: AbstractBackend) -> Any:
38
+ """Execute the Bluetooth action on the backend.
39
+
40
+ Args:
41
+ backend: An AbstractBackend instance.
42
+
43
+ Returns:
44
+ Result of the Bluetooth operation.
45
+ """
46
+ try:
47
+ await backend.launch(self.params.browser)
48
+ if self.params.url:
49
+ await backend.navigate(self.params.url, self.params.wait)
50
+
51
+ action = self.params.action
52
+
53
+ if action == "emulate":
54
+ if not self.params.name:
55
+ raise ValueError("name is required for emulate action")
56
+ await backend.bluetooth_emulate(
57
+ self.params.name, self.params.address
58
+ )
59
+ return None
60
+
61
+ if action == "stop":
62
+ await backend.bluetooth_stop()
63
+ return None
64
+
65
+ raise ValueError(f"Unknown Bluetooth action: {action}")
66
+
67
+ finally:
68
+ await backend.close()
@@ -0,0 +1,39 @@
1
+ """Browser action for context and window management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from wavexis.actions.base import BaseAction
8
+ from wavexis.backend.base import AbstractBackend
9
+
10
+
11
+ class BrowserAction(BaseAction[str, Any]):
12
+ """Action for browser management operations.
13
+
14
+ Supports contexts (new/list/close), window bounds (get/set), and version.
15
+ The params string specifies the action: "new_context", "list_contexts",
16
+ "close_context", "get_window", "set_window", or "version".
17
+ """
18
+
19
+ async def execute(self, backend: AbstractBackend) -> Any:
20
+ """Execute the browser action.
21
+
22
+ Args:
23
+ backend: The browser backend to use.
24
+
25
+ Returns:
26
+ Action-dependent result (str, list, dict, or None).
27
+ """
28
+ action = self.params
29
+
30
+ if action == "version":
31
+ return await backend.browser_version()
32
+
33
+ if action == "new_context":
34
+ return await backend.new_context()
35
+
36
+ if action == "list_contexts":
37
+ return await backend.list_contexts()
38
+
39
+ return None
@@ -0,0 +1,67 @@
1
+ """Cast action for listing sinks and controlling tab mirroring (experimental)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from wavexis.actions.base import BaseAction
9
+ from wavexis.backend.base import AbstractBackend
10
+ from wavexis.config import BrowserOptions, WaitStrategy
11
+
12
+
13
+ @dataclass
14
+ class CastParams:
15
+ """Parameters for Cast operations.
16
+
17
+ Attributes:
18
+ url: URL to navigate to before Cast operations.
19
+ action: Cast action — "list", "start-tab", "stop".
20
+ sink_name: Cast sink name for "start-tab" action.
21
+ wait: Wait strategy after navigation.
22
+ browser: Browser launch options.
23
+ """
24
+
25
+ url: str = ""
26
+ action: str = "list"
27
+ sink_name: str | None = None
28
+ wait: WaitStrategy = field(default_factory=WaitStrategy)
29
+ browser: BrowserOptions = field(default_factory=BrowserOptions)
30
+
31
+
32
+ class CastAction(BaseAction[CastParams, Any]):
33
+ """Action for Cast operations (experimental)."""
34
+
35
+ async def execute(self, backend: AbstractBackend) -> Any:
36
+ """Execute the Cast action on the backend.
37
+
38
+ Args:
39
+ backend: An AbstractBackend instance.
40
+
41
+ Returns:
42
+ Result of the Cast 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.cast_list()
53
+
54
+ if action == "start-tab":
55
+ if not self.params.sink_name:
56
+ raise ValueError("sink_name is required for start-tab action")
57
+ await backend.cast_start_tab(self.params.sink_name)
58
+ return None
59
+
60
+ if action == "stop":
61
+ await backend.cast_stop()
62
+ return None
63
+
64
+ raise ValueError(f"Unknown Cast action: {action}")
65
+
66
+ finally:
67
+ await backend.close()
@@ -0,0 +1,57 @@
1
+ """Console action for capturing console messages and browser logs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from wavexis.actions.base import BaseAction
9
+ from wavexis.backend.base import AbstractBackend
10
+ from wavexis.config import WaitStrategy
11
+
12
+
13
+ @dataclass
14
+ class ConsoleParams:
15
+ """Parameters for console capture.
16
+
17
+ Attributes:
18
+ url: URL to navigate to before capturing.
19
+ level: Minimum log level — "all", "error", "warning", "info".
20
+ wait: Wait strategy after navigation.
21
+ capture: What to capture — "console", "logs", or "both".
22
+ """
23
+
24
+ url: str = ""
25
+ level: str = "all"
26
+ wait: WaitStrategy | None = None
27
+ capture: str = "console"
28
+
29
+
30
+ class ConsoleAction(BaseAction[ConsoleParams, dict[str, Any]]):
31
+ """Action for capturing console messages and browser logs.
32
+
33
+ Navigates to the URL, then captures console messages and/or log entries.
34
+ """
35
+
36
+ async def execute(self, backend: AbstractBackend) -> dict[str, Any]:
37
+ """Execute the console action.
38
+
39
+ Args:
40
+ backend: The browser backend to use.
41
+
42
+ Returns:
43
+ Dict with "console" and/or "logs" keys containing entry lists.
44
+ """
45
+ params = self.params
46
+ if params.url:
47
+ await backend.navigate(params.url, params.wait)
48
+
49
+ result: dict[str, Any] = {}
50
+
51
+ if params.capture in ("console", "both"):
52
+ result["console"] = await backend.capture_console(level=params.level)
53
+
54
+ if params.capture in ("logs", "both"):
55
+ result["logs"] = await backend.capture_logs()
56
+
57
+ return result
@@ -0,0 +1,57 @@
1
+ """Cookie action for browser cookie operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from wavexis.actions.base import BaseAction
8
+ from wavexis.backend.base import AbstractBackend
9
+ from wavexis.config import CookieActionParams
10
+
11
+
12
+ class CookieAction(BaseAction[CookieActionParams, Any]):
13
+ """Action for cookie operations: get, set, delete, clear.
14
+
15
+ Navigates to the URL in params, then performs the requested
16
+ cookie operation against the backend.
17
+ """
18
+
19
+ async def execute(self, backend: AbstractBackend) -> Any:
20
+ """Execute the cookie action on the backend.
21
+
22
+ Args:
23
+ backend: An AbstractBackend instance.
24
+
25
+ Returns:
26
+ Result of the cookie operation.
27
+
28
+ Raises:
29
+ ValueError: If required parameters are missing.
30
+ """
31
+ try:
32
+ await backend.launch(self.params.browser)
33
+ if self.params.url:
34
+ await backend.navigate(self.params.url, self.params.wait)
35
+
36
+ action = self.params.action
37
+
38
+ if action == "get":
39
+ return await backend.get_cookies()
40
+
41
+ if action == "set":
42
+ await backend.set_cookie(self.params.cookie)
43
+ return None
44
+
45
+ if action == "delete":
46
+ if not self.params.name:
47
+ raise ValueError("name is required for delete action")
48
+ await backend.delete_cookie(self.params.name, self.params.domain)
49
+ return None
50
+
51
+ if action == "clear":
52
+ await backend.clear_cookies()
53
+ return None
54
+
55
+ raise ValueError(f"Unknown cookie action: {action}")
56
+ finally:
57
+ await backend.close()
@@ -0,0 +1,105 @@
1
+ """Crawl action for spidering a website with depth limit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+ from urllib.parse import urljoin, urlparse
10
+
11
+ from wavexis.actions.base import BaseAction
12
+ from wavexis.backend.base import AbstractBackend
13
+ from wavexis.config import WaitStrategy
14
+
15
+
16
+ @dataclass
17
+ class CrawlParams:
18
+ """Parameters for crawling a website.
19
+
20
+ Attributes:
21
+ start_url: Starting URL for the crawl.
22
+ max_depth: Maximum crawl depth (1 = start page only, 2 = start + links).
23
+ max_pages: Maximum number of pages to visit.
24
+ same_origin: If True, only crawl links on the same origin.
25
+ url_pattern: Regex pattern to filter URLs (empty = all).
26
+ wait: Wait strategy after each navigation.
27
+ """
28
+
29
+ start_url: str = ""
30
+ max_depth: int = 2
31
+ max_pages: int = 50
32
+ same_origin: bool = True
33
+ url_pattern: str = ""
34
+ wait: WaitStrategy = field(default_factory=WaitStrategy)
35
+
36
+
37
+ class CrawlAction(BaseAction[CrawlParams, list[dict[str, Any]]]):
38
+ """Action for crawling a website and collecting page data."""
39
+
40
+ async def execute(
41
+ self, backend: AbstractBackend
42
+ ) -> list[dict[str, Any]]:
43
+ """Execute the crawl on the backend.
44
+
45
+ Args:
46
+ backend: The browser backend to use.
47
+
48
+ Returns:
49
+ List of page dicts with url, title, links, and depth.
50
+ """
51
+ results: list[dict[str, Any]] = []
52
+ visited: set[str] = set()
53
+ queue: list[tuple[str, int]] = [(self.params.start_url, 0)]
54
+ origin = urlparse(self.params.start_url).netloc
55
+ pattern = re.compile(self.params.url_pattern) if self.params.url_pattern else None
56
+
57
+ while queue and len(results) < self.params.max_pages:
58
+ url, depth = queue.pop(0)
59
+ normalized = url.rstrip("/")
60
+ if normalized in visited or depth > self.params.max_depth:
61
+ continue
62
+ visited.add(normalized)
63
+
64
+ try:
65
+ await backend.navigate(url, self.params.wait)
66
+ except Exception:
67
+ continue
68
+
69
+ title = ""
70
+ links: list[str] = []
71
+ with contextlib.suppress(Exception):
72
+ title = await backend.eval(
73
+ "document.title", await_promise=False
74
+ )
75
+ with contextlib.suppress(Exception):
76
+ raw_links = await backend.eval(
77
+ "Array.from(document.querySelectorAll('a[href]')).map(a => a.href)",
78
+ await_promise=False,
79
+ )
80
+ if isinstance(raw_links, list):
81
+ links = [str(link) for link in raw_links if isinstance(link, str)]
82
+
83
+ page_data: dict[str, Any] = {
84
+ "url": url,
85
+ "title": title,
86
+ "depth": depth,
87
+ "links_found": len(links),
88
+ }
89
+ results.append(page_data)
90
+
91
+ if depth < self.params.max_depth:
92
+ for link in links:
93
+ absolute = urljoin(url, link)
94
+ parsed = urlparse(absolute)
95
+ if parsed.scheme not in ("http", "https"):
96
+ continue
97
+ if self.params.same_origin and parsed.netloc != origin:
98
+ continue
99
+ if pattern and not pattern.search(absolute):
100
+ continue
101
+ norm = absolute.rstrip("/")
102
+ if norm not in visited:
103
+ queue.append((absolute, depth + 1))
104
+
105
+ return results
wavexis/actions/css.py ADDED
@@ -0,0 +1,107 @@
1
+ """CSS action for inspecting styles, stylesheets, rules, and computed styles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from wavexis.actions.base import BaseAction
9
+ from wavexis.backend.base import AbstractBackend
10
+ from wavexis.config import BrowserOptions, CSSParams, WaitStrategy
11
+
12
+
13
+ @dataclass
14
+ class CSSActionParams:
15
+ """Parameters for CSS inspection operations.
16
+
17
+ Attributes:
18
+ url: URL to navigate to before CSS inspection.
19
+ selector: CSS selector for the target element.
20
+ stylesheet_id: Stylesheet ID for rules action.
21
+ action: CSS action — "styles", "stylesheets", "rules", "computed".
22
+ wait: Wait strategy after navigation.
23
+ browser: Browser launch options.
24
+ """
25
+
26
+ url: str = ""
27
+ selector: str | None = None
28
+ stylesheet_id: str | None = None
29
+ action: str = "styles"
30
+ wait: WaitStrategy = field(default_factory=WaitStrategy)
31
+ browser: BrowserOptions = field(default_factory=BrowserOptions)
32
+
33
+
34
+ class CSSAction(BaseAction[CSSActionParams, dict[str, Any] | list[dict[str, Any]]]):
35
+ """Action for CSS inspection operations."""
36
+
37
+ async def execute(
38
+ self, backend: AbstractBackend
39
+ ) -> dict[str, Any] | list[dict[str, Any]]:
40
+ """Execute the CSS action on the backend.
41
+
42
+ Args:
43
+ backend: The browser backend to use.
44
+
45
+ Returns:
46
+ Dict or list of dicts containing CSS data.
47
+
48
+ Raises:
49
+ ValueError: If the action is not recognized or required params missing.
50
+ """
51
+ await backend.launch(self.params.browser)
52
+ try:
53
+ await backend.navigate(self.params.url, self.params.wait)
54
+ return await self._run_action(backend)
55
+ finally:
56
+ await backend.close()
57
+
58
+ async def _run_action(
59
+ self, backend: AbstractBackend
60
+ ) -> dict[str, Any] | list[dict[str, Any]]:
61
+ """Execute the CSS action against the backend.
62
+
63
+ Args:
64
+ backend: The browser backend to use.
65
+
66
+ Returns:
67
+ CSS data as a dict or list of dicts depending on the action.
68
+
69
+ Raises:
70
+ ValueError: If required parameters are missing for the action.
71
+ """
72
+ action = self.params.action
73
+ if action == "styles":
74
+ if not self.params.selector:
75
+ raise ValueError("selector is required for styles action")
76
+ return await backend.css_get_styles(self.params.selector)
77
+ if action == "stylesheets":
78
+ return await backend.css_get_stylesheets()
79
+ if action == "rules":
80
+ if not self.params.stylesheet_id:
81
+ raise ValueError("stylesheet_id is required for rules action")
82
+ return await backend.css_get_rules(self.params.stylesheet_id)
83
+ if action == "computed":
84
+ if not self.params.selector:
85
+ raise ValueError("selector is required for computed action")
86
+ return await backend.css_get_computed(self.params.selector)
87
+ raise ValueError(f"Unknown CSS action: {action}")
88
+
89
+
90
+ def css_action_from_config(params: CSSParams) -> CSSAction:
91
+ """Create a CSSAction from CSSParams config dataclass.
92
+
93
+ Args:
94
+ params: CSSParams from wavexis.config.
95
+
96
+ Returns:
97
+ CSSAction instance with mapped parameters.
98
+ """
99
+ action_params = CSSActionParams(
100
+ url=params.url,
101
+ selector=params.selector,
102
+ stylesheet_id=params.stylesheet_id,
103
+ action=params.action,
104
+ wait=params.wait,
105
+ browser=params.browser,
106
+ )
107
+ return CSSAction(action_params)