From ac8939ba78d6eaefd0390de2990657f015dab5ba Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:22:13 +0100 Subject: [PATCH 1/7] fix(codec): preserve explicit null and reject missing required fields --- CHANGELOG.md | 11 +++++ cdpify/__init__.py | 5 ++ cdpify/codec.py | 47 +++++++++++++++++-- cdpify/domains/accessibility/types.py | 3 +- cdpify/domains/runtime/types.py | 8 ++-- cdpify/domains/webmcp/events.py | 3 +- cdpify/exceptions.py | 23 +++++++++ cdpify/generator/generators/commands.py | 1 + cdpify/generator/generators/domain.py | 1 + cdpify/generator/generators/events.py | 1 + cdpify/generator/generators/types.py | 1 + cdpify/generator/generators/views.py | 13 +++-- cdpify/generator/templates/commands.py.jinja2 | 4 ++ cdpify/generator/templates/domain.py.jinja2 | 3 ++ cdpify/generator/templates/events.py.jinja2 | 4 ++ cdpify/generator/templates/types.py.jinja2 | 4 ++ cdpify/unset.py | 13 +++++ .../generators/test_types_generator.py | 31 ++++++++++++ tests/test_codec.py | 47 ++++++++++++++++++- 19 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 cdpify/unset.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c1d339..24271a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ Breaking changes are listed first in each release. ## [Unreleased] +### Breaking changes + +- Optional `any` fields now default to `UNSET`, distinguishing omission from + JSON `null`. For example, `CallArgument()` omits `value`, while + `CallArgument(value=None)` sends `{"value": null}`. Ordinary optional scalar + fields still use `None` to mean omitted. `UNSET` and `UnsetType` are public. +- `decode_cdp` now raises `CDPDecodeError` for missing required fields, including + nested models. Unknown fields and future enum values remain supported. Use + `decode_cdp(Model, payload, strict=False)` for the previous tolerant behavior + that logs a warning and fills missing required fields with `None`. + ## [0.4.0] - 2026-10-25 ### Breaking changes diff --git a/cdpify/__init__.py b/cdpify/__init__.py index 3edbfb8e..e2edec13 100644 --- a/cdpify/__init__.py +++ b/cdpify/__init__.py @@ -4,6 +4,7 @@ from .exceptions import ( CDPCommandError, CDPConnectionError, + CDPDecodeError, CDPError, CDPStreamClosedError, CDPTimeoutError, @@ -12,6 +13,7 @@ from .executor import CommandExecutor from .session import CdpSession from .transport import Transport, TransportEvent +from .unset import UNSET, UnsetType __all__ = [ "CdpClient", @@ -28,6 +30,9 @@ "CDPCommandError", "CDPTimeoutError", "CDPStreamClosedError", + "CDPDecodeError", + "UNSET", + "UnsetType", ] diff --git a/cdpify/codec.py b/cdpify/codec.py index 08af42b2..d126ff02 100644 --- a/cdpify/codec.py +++ b/cdpify/codec.py @@ -1,16 +1,21 @@ import logging import sys from collections.abc import Callable +from contextvars import ContextVar from dataclasses import MISSING, dataclass, fields, is_dataclass from enum import StrEnum from functools import cache from types import UnionType from typing import Any, TypeAliasType, Union, get_args, get_origin, get_type_hints +from cdpify.exceptions import CDPDecodeError +from cdpify.unset import UNSET + logger = logging.getLogger(__name__) _CDP_ENABLED_METADATA_KEY = "cdp" _CDP_NAME_METADATA_KEY = "cdp_name" +_strict_decoding: ContextVar[bool] = ContextVar("cdpify_strict_decoding", default=True) # Converts a non-None wire value; ``None`` means the value is used as is. @@ -25,6 +30,7 @@ class _FieldPlan: init: bool required: bool wire_enabled: bool + omit_none: bool @dataclass(frozen=True, slots=True) @@ -44,7 +50,7 @@ def encode_cdp(model: Any) -> dict[str, Any]: continue value = getattr(model, model_field.name) - if value is not None: + if value is not UNSET and (value is not None or not model_field.omit_none): encoded[model_field.cdp_name] = _encode_value(value) return encoded @@ -53,8 +59,23 @@ def encode_cdp(model: Any) -> dict[str, Any]: def decode_cdp[T]( model_type: type[T], data: dict[str, Any], + *, + strict: bool = True, ) -> T: - """Construct a generated CDP dataclass from its wire representation.""" + """Construct a CDP dataclass, rejecting missing required fields by default. + + ``strict=False`` fills missing required fields with None and logs a warning, + including in nested models. Extra wire fields are always ignored. This is + structural decoding, not validation of primitive values against a schema. + """ + token = _strict_decoding.set(strict) + try: + return _decode_model(model_type, data) + finally: + _strict_decoding.reset(token) + + +def _decode_model[T](model_type: type[T], data: dict[str, Any]) -> T: plan = _compile_model(model_type) values: dict[str, Any] = {} @@ -63,7 +84,10 @@ def decode_cdp[T]( if model_field is None: continue if value is not None and model_field.decoder is not None: - value = model_field.decoder(value) + try: + value = model_field.decoder(value) + except CDPDecodeError as error: + raise error._prepend(cdp_name) from error values[model_field.name] = value if not values.keys() >= plan.required_names: @@ -73,6 +97,8 @@ def decode_cdp[T]( if model_field.name in plan.required_names and model_field.name not in values ] + if _strict_decoding.get(): + raise CDPDecodeError(model_type, tuple(missing)) logger.warning( "CDP spec mismatch for %s. Missing fields: %s. Data keys: %s", model_type.__name__, @@ -101,6 +127,7 @@ def _compile_model(model_type: type) -> _ModelPlan: and model_field.default_factory is MISSING ), wire_enabled=model_field.metadata.get(_CDP_ENABLED_METADATA_KEY, True), + omit_none=not _is_any(resolved_types.get(model_field.name, Any)), ) for model_field in fields(model_type) ) @@ -150,6 +177,8 @@ def _resolve_field_types(model_type: type) -> dict[str, Any]: def _encode_value(value: Any) -> Any: + if value is None or isinstance(value, (str, int, float, bool)): + return value if is_dataclass(value) and not isinstance(value, type): return encode_cdp(value) if isinstance(value, (list, tuple)): @@ -159,6 +188,16 @@ def _encode_value(value: Any) -> Any: return value +def _is_any(field_type: Any) -> bool: + if field_type is Any: + return True + if isinstance(field_type, TypeAliasType): + return _is_any(field_type.__value__) + return get_origin(field_type) in (Union, UnionType) and any( + _is_any(arg) for arg in get_args(field_type) + ) + + def _build_decoder(field_type: Any) -> _Decoder: """Build a converter for one annotation once, instead of per value.""" if field_type is Any: @@ -224,7 +263,7 @@ def _enum_decoder(enum_type: type[StrEnum]) -> Callable[[str], StrEnum]: def _model_decoder(model_type: type) -> Callable[[Any], Any]: # Resolved lazily so recursive models (e.g. a StackTrace parent) work. def decode(value: Any) -> Any: - return decode_cdp(model_type, value) if isinstance(value, dict) else value + return _decode_model(model_type, value) if isinstance(value, dict) else value return decode diff --git a/cdpify/domains/accessibility/types.py b/cdpify/domains/accessibility/types.py index 26444865..f2368f9e 100644 --- a/cdpify/domains/accessibility/types.py +++ b/cdpify/domains/accessibility/types.py @@ -9,6 +9,7 @@ from cdpify.domains import dom, page from cdpify.shared.enums import CDPStrEnum +from cdpify.unset import UNSET, UnsetType type AXNodeId = str """ @@ -118,7 +119,7 @@ class AXValue: """ type: AXValueType - value: Any | None = None + value: Any | UnsetType = UNSET related_nodes: list[AXRelatedNode] | None = field( default=None, metadata={"cdp_name": "relatedNodes"} ) diff --git a/cdpify/domains/runtime/types.py b/cdpify/domains/runtime/types.py index 81c9dcbb..16f7ecaf 100644 --- a/cdpify/domains/runtime/types.py +++ b/cdpify/domains/runtime/types.py @@ -7,6 +7,8 @@ from dataclasses import dataclass, field from typing import Any, Literal +from cdpify.unset import UNSET, UnsetType + type ScriptId = str """ Unique script identifier. @@ -59,7 +61,7 @@ class DeepSerializedValue: "window", "generator", ] - value: Any | None = None + value: Any | UnsetType = UNSET object_id: str | None = field(default=None, metadata={"cdp_name": "objectId"}) weak_local_object_reference: int | None = field( default=None, metadata={"cdp_name": "weakLocalObjectReference"} @@ -121,7 +123,7 @@ class RemoteObject: | None ) = None class_name: str | None = field(default=None, metadata={"cdp_name": "className"}) - value: Any | None = None + value: Any | UnsetType = UNSET unserializable_value: UnserializableValue | None = field( default=None, metadata={"cdp_name": "unserializableValue"} ) @@ -295,7 +297,7 @@ class CallArgument: specified. """ - value: Any | None = None + value: Any | UnsetType = UNSET unserializable_value: UnserializableValue | None = field( default=None, metadata={"cdp_name": "unserializableValue"} ) diff --git a/cdpify/domains/webmcp/events.py b/cdpify/domains/webmcp/events.py index e5810369..3877cd2c 100644 --- a/cdpify/domains/webmcp/events.py +++ b/cdpify/domains/webmcp/events.py @@ -9,6 +9,7 @@ from typing import Any, ClassVar from cdpify.domains import page, runtime +from cdpify.unset import UNSET, UnsetType from .types import ( InvocationStatus, @@ -70,6 +71,6 @@ class ToolRespondedEvent: invocation_id: str = field(metadata={"cdp_name": "invocationId"}) status: InvocationStatus - output: Any | None = None + output: Any | UnsetType = UNSET error_text: str | None = field(default=None, metadata={"cdp_name": "errorText"}) exception: runtime.RemoteObject | None = None diff --git a/cdpify/exceptions.py b/cdpify/exceptions.py index 7e8b1086..5c2fa80b 100644 --- a/cdpify/exceptions.py +++ b/cdpify/exceptions.py @@ -47,6 +47,29 @@ class CDPStreamClosedError(CDPError): """An event subscription ended before the next event arrived.""" +class CDPDecodeError(CDPError): + """A CDP payload is missing fields required by its generated model.""" + + def __init__( + self, + model_type: type, + missing_fields: tuple[str, ...], + *, + path: tuple[str, ...] = (), + ) -> None: + self.model_type = model_type + self.missing_fields = missing_fields + self.path = path + location = ".".join(path) or "" + super().__init__( + f"Cannot decode {model_type.__module__}.{model_type.__qualname__} " + f"at {location}: missing required fields {', '.join(missing_fields)}" + ) + + def _prepend(self, name: str) -> "CDPDecodeError": + return type(self)(self.model_type, self.missing_fields, path=(name, *self.path)) + + _DEPRECATED_NAMES = { "CDPException": CDPError, "CDPConnectionException": CDPConnectionError, diff --git a/cdpify/generator/generators/commands.py b/cdpify/generator/generators/commands.py index 0de749a2..e476652c 100644 --- a/cdpify/generator/generators/commands.py +++ b/cdpify/generator/generators/commands.py @@ -44,6 +44,7 @@ def generate(domain: Domain) -> str: for command in domain.commands ), models=models, + uses_unset=any(f.uses_unset for m in models for f in m.fields), dataclass_import=dataclass_import(models), ) diff --git a/cdpify/generator/generators/domain.py b/cdpify/generator/generators/domain.py index da57307c..cf37f4e3 100644 --- a/cdpify/generator/generators/domain.py +++ b/cdpify/generator/generators/domain.py @@ -27,6 +27,7 @@ def generate(domain: Domain) -> str: header=HEADER, future_annotations=FUTURE_ANNOTATIONS, typing_names=ctx.sorted_typing_names, + uses_unset=any(p.uses_unset for m in methods for p in m.parameters), has_deprecated=any(command.deprecated for command in domain.commands), command_imports=_command_imports(domain), local_type_names=tuple(name for name, _ in ctx.sorted_local_types()), diff --git a/cdpify/generator/generators/events.py b/cdpify/generator/generators/events.py index 96f06208..93becfbc 100644 --- a/cdpify/generator/generators/events.py +++ b/cdpify/generator/generators/events.py @@ -49,6 +49,7 @@ def generate(domain: Domain) -> str: for event in domain.events ), models=models, + uses_unset=any(f.uses_unset for m in models for f in m.fields), dataclass_import=dataclass_import(models), ) diff --git a/cdpify/generator/generators/types.py b/cdpify/generator/generators/types.py index 4a1a5858..fbefc990 100644 --- a/cdpify/generator/generators/types.py +++ b/cdpify/generator/generators/types.py @@ -33,6 +33,7 @@ def generate(domain: Domain) -> str: cross_domain_modules=ctx.cross_domain_modules, definitions=definitions, has_enums=any(definition.kind == "enum" for definition in definitions), + uses_unset=any(f.uses_unset for m in definitions for f in m.fields), dataclass_import=dataclass_import(definitions), ) diff --git a/cdpify/generator/generators/views.py b/cdpify/generator/generators/views.py index 241c39ca..43d57ba5 100644 --- a/cdpify/generator/generators/views.py +++ b/cdpify/generator/generators/views.py @@ -9,6 +9,10 @@ class FieldView: optional: bool = False cdp_name: str | None = None + @property + def uses_unset(self) -> bool: + return self.optional and self.annotation == "Any" + @property def needs_field(self) -> bool: """Whether the declaration needs ``field()`` to keep the CDP wire name.""" @@ -16,14 +20,17 @@ def needs_field(self) -> bool: @property def declaration(self) -> str: - annotation = f"{self.annotation} | None" if self.optional else self.annotation + annotation = self.annotation + default_value = "UNSET" if self.uses_unset else "None" + if self.optional: + annotation += " | UnsetType" if self.uses_unset else " | None" if not self.needs_field: - default = " = None" if self.optional else "" + default = f" = {default_value}" if self.optional else "" return f"{self.name}: {annotation}{default}" arguments = [] if self.optional: - arguments.append("default=None") + arguments.append(f"default={default_value}") arguments.append(f'metadata={{"cdp_name": "{self.cdp_name}"}}') declaration = f"{self.name}: {annotation}" if len(declaration) + len(" = field(") + 4 > 88: diff --git a/cdpify/generator/templates/commands.py.jinja2 b/cdpify/generator/templates/commands.py.jinja2 index 38f4fac2..1856584d 100644 --- a/cdpify/generator/templates/commands.py.jinja2 +++ b/cdpify/generator/templates/commands.py.jinja2 @@ -7,6 +7,10 @@ from typing import {{ typing_names | join(", ") }} {% endif %} {{ dataclass_import }} +{% if uses_unset %} + +from cdpify.unset import UNSET, UnsetType +{% endif %} from enum import StrEnum {% if local_type_names %} diff --git a/cdpify/generator/templates/domain.py.jinja2 b/cdpify/generator/templates/domain.py.jinja2 index 4670513e..1db9c02f 100644 --- a/cdpify/generator/templates/domain.py.jinja2 +++ b/cdpify/generator/templates/domain.py.jinja2 @@ -8,6 +8,9 @@ from typing import {{ typing_names | join(", ") }} from cdpify.codec import decode_cdp, encode_cdp from cdpify.executor import CommandExecutor +{% if uses_unset %} +from cdpify.unset import UNSET, UnsetType +{% endif %} {% if has_deprecated %} from cdpify.shared.decorators import deprecated {% endif %} diff --git a/cdpify/generator/templates/events.py.jinja2 b/cdpify/generator/templates/events.py.jinja2 index 62c6f12a..1d840a9f 100644 --- a/cdpify/generator/templates/events.py.jinja2 +++ b/cdpify/generator/templates/events.py.jinja2 @@ -8,6 +8,10 @@ from typing import {{ typing_names | join(", ") }} {% endif %} {{ dataclass_import }} +{% if uses_unset %} + +from cdpify.unset import UNSET, UnsetType +{% endif %} from enum import StrEnum {% if local_type_names %} diff --git a/cdpify/generator/templates/types.py.jinja2 b/cdpify/generator/templates/types.py.jinja2 index 29814a6f..3316177e 100644 --- a/cdpify/generator/templates/types.py.jinja2 +++ b/cdpify/generator/templates/types.py.jinja2 @@ -7,6 +7,10 @@ from typing import {{ typing_names | join(", ") }} {% endif %} {{ dataclass_import }} +{% if uses_unset %} + +from cdpify.unset import UNSET, UnsetType +{% endif %} {% if has_enums %} from cdpify.shared.enums import CDPStrEnum diff --git a/cdpify/unset.py b/cdpify/unset.py new file mode 100644 index 00000000..c74fdad0 --- /dev/null +++ b/cdpify/unset.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class UnsetType(Enum): + """The singleton marker for an omitted, potentially null-valued CDP field.""" + + UNSET = 0 + + def __repr__(self) -> str: + return "UNSET" + + +UNSET = UnsetType.UNSET diff --git a/tests/generator/generators/test_types_generator.py b/tests/generator/generators/test_types_generator.py index 64d439ed..9f46dea2 100644 --- a/tests/generator/generators/test_types_generator.py +++ b/tests/generator/generators/test_types_generator.py @@ -2,6 +2,37 @@ from cdpify.generator.schemas import Domain, Parameter, TypeDefinition +def test_optional_any_uses_unset_and_retains_explicit_null() -> None: + from typing import Any + + from cdpify import UNSET, decode_cdp, encode_cdp + + domain = Domain( + domain="Sample", + types=[ + TypeDefinition( + id="Argument", + type="object", + properties=[ + Parameter(name="value", type="any", optional=True), + Parameter(name="extraValue", type="any", optional=True), + ], + ) + ], + ) + namespace = {} + exec(types.generate(domain), namespace) + model = namespace["Argument"] + # exec() has no importable module; its annotations are resolved explicitly. + model.__annotations__ = {"value": Any, "extra_value": Any} + assert model().value is UNSET + assert "value: Any | UnsetType = UNSET" in types.generate(domain) + assert "default=UNSET" in types.generate(domain) + assert decode_cdp(model, {}).value is UNSET + assert encode_cdp(model(value=None)) == {"value": None} + assert encode_cdp(model(extra_value=None)) == {"extraValue": None} + + def test_generates_header_and_dataclass_imports(simple_domain: Domain) -> None: output = types.generate(simple_domain) diff --git a/tests/test_codec.py b/tests/test_codec.py index df386121..734dbd6c 100644 --- a/tests/test_codec.py +++ b/tests/test_codec.py @@ -3,6 +3,7 @@ import pytest +from cdpify import UNSET, CDPDecodeError, UnsetType from cdpify import decode_cdp as public_decode_cdp from cdpify import encode_cdp as public_encode_cdp from cdpify.codec import _compile_model, decode_cdp, encode_cdp @@ -54,12 +55,56 @@ def test_serializes_nested_models_with_their_own_aliases() -> None: def test_construct_handles_missing_required_fields_with_none(caplog) -> None: - model = decode_cdp(_Parent, {}) + model = decode_cdp(_Parent, {}, strict=False) assert model.child is None assert "CDP spec mismatch for _Parent" in caplog.text +def test_missing_required_fields_raise_structured_decode_error() -> None: + with pytest.raises(CDPDecodeError) as error: + decode_cdp(_Parent, {}) + assert error.value.model_type is _Parent + assert error.value.missing_fields == ("child",) + + +def test_strictness_propagates_to_nested_models_and_does_not_leak() -> None: + assert decode_cdp(_Parent, {"child": {}}, strict=False).child.child_value is None + with pytest.raises(CDPDecodeError) as error: + decode_cdp(_Parent, {"child": {}}) + assert error.value.path == ("child",) + assert error.value.missing_fields == ("child_value",) + + +def test_any_preserves_null_and_unset_omits_the_field() -> None: + from cdpify.domains.runtime import CallArgument + + assert encode_cdp(CallArgument()) == {} + assert encode_cdp(CallArgument(value=None)) == {"value": None} + assert decode_cdp(CallArgument, {}).value is UNSET + assert decode_cdp(CallArgument, {"value": None}).value is None + + +def test_required_any_accepts_explicit_null() -> None: + @dataclass + class Required: + value: Any + + assert encode_cdp(Required(value=None)) == {"value": None} + assert decode_cdp(Required, {"value": None}).value is None + with pytest.raises(CDPDecodeError): + decode_cdp(Required, {}) + + +def test_unset_survives_copy_and_pickle() -> None: + import copy + import pickle + + assert isinstance(UNSET, UnsetType) + assert copy.deepcopy(UNSET) is UNSET + assert pickle.loads(pickle.dumps(UNSET)) is UNSET + + def test_ignores_unknown_wire_fields() -> None: model = decode_cdp( _Parent, From 269d03d23cadd8b941cc03e470b560435a1133ee Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:24:39 +0100 Subject: [PATCH 2/7] fix(client): enforce total command deadlines across transports --- CHANGELOG.md | 8 ++++ cdpify/client.py | 30 +++++++++---- cdpify/executor.py | 49 ++++++++++++++++---- cdpify/session.py | 9 +++- cdpify/transport.py | 9 +++- cdpify/transports/websocket.py | 30 +++++++------ tests/test_client.py | 2 +- tests/test_executor.py | 74 +++++++++++++++++++++++++++++++ tests/test_session.py | 8 ++-- tests/test_websocket_transport.py | 29 ++++++------ 10 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 tests/test_executor.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 24271a80..beb50acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ Breaking changes are listed first in each release. nested models. Unknown fields and future enum values remain supported. Use `decode_cdp(Model, payload, strict=False)` for the previous tolerant behavior that logs a warning and fills missing required fields with `None`. +- Command timeouts now cover sending and receiving. `CdpClient.default_timeout` + is enforced for every transport and inherited by session views. Omit the raw + `execute(timeout=...)` argument to inherit the default; pass `None` to disable + it. Set `default_timeout=None` to disable the client default entirely. +- `WebSocketTransport` no longer accepts `default_timeout`; configure it on + `CdpClient` instead. Standalone transport calls have no deadline unless + `execute(timeout=seconds)` is specified. Generated methods retain only CDP's + own parameters; use `asyncio.timeout(seconds)` for an individual caller budget. ## [0.4.0] - 2026-10-25 diff --git a/cdpify/client.py b/cdpify/client.py index 9099fa41..10116d1d 100644 --- a/cdpify/client.py +++ b/cdpify/client.py @@ -9,6 +9,7 @@ from cdpify.executor import BoundCommandExecutor from cdpify.session import CdpSession from cdpify.transport import CallbackTransport, Transport, TransportEvent +from cdpify.unset import UNSET, UnsetType logger = logging.getLogger(__name__) @@ -21,7 +22,7 @@ def __init__( transport: Transport | None = None, additional_headers: dict[str, str] | None = None, max_frame_size: int = 100 * 1024 * 1024, - default_timeout: float = 30.0, + default_timeout: float | None = 30.0, max_queue_size: int = 0, ) -> None: if transport is not None and url is not None: @@ -33,12 +34,14 @@ def __init__( url, additional_headers=additional_headers, max_frame_size=max_frame_size, - default_timeout=default_timeout, max_queue_size=max_queue_size, ) self._transport = transport - self._executor = BoundCommandExecutor(transport) + self._default_timeout = default_timeout + self._executor = BoundCommandExecutor( + transport, default_timeout=default_timeout + ) EventSource.__init__(self, EventRouter(max_queue_size=max_queue_size), None) self._event_loop_task: asyncio.Task[None] | None = None self._disconnect_task: asyncio.Task[None] | None = None @@ -97,20 +100,31 @@ async def _disconnect(self) -> None: def session(self, session_id: str) -> CdpSession: """Create an immutable domain view bound to ``session_id``.""" - return CdpSession(self._transport, self._event_router, session_id) + return CdpSession( + self._transport, + self._event_router, + session_id, + default_timeout=self._default_timeout, + ) async def execute( self, method: str, params: dict[str, Any] | None = None, session_id: str | None = None, - timeout: float | None = None, + timeout: float | None | UnsetType = UNSET, ) -> dict[str, Any]: """Execute a low-level command, optionally routed to a target session.""" - return await self._transport.execute( + executor = ( + self._executor + if session_id is None + else BoundCommandExecutor( + self._transport, session_id, default_timeout=self._default_timeout + ) + ) + return await executor.execute( method=method, params=params, - session_id=session_id, timeout=timeout, ) @@ -169,7 +183,6 @@ def _create_websocket_transport( *, additional_headers: dict[str, str] | None, max_frame_size: int, - default_timeout: float, max_queue_size: int, ) -> Transport: try: @@ -185,6 +198,5 @@ def _create_websocket_transport( url, additional_headers=additional_headers, max_frame_size=max_frame_size, - default_timeout=default_timeout, max_queue_size=max_queue_size, ) diff --git a/cdpify/executor.py b/cdpify/executor.py index 148f604a..bff4f285 100644 --- a/cdpify/executor.py +++ b/cdpify/executor.py @@ -1,6 +1,10 @@ +import asyncio +from collections.abc import Awaitable from typing import Any, Protocol +from cdpify.exceptions import CDPTimeoutError from cdpify.transport import Transport +from cdpify.unset import UNSET, UnsetType class CommandExecutor(Protocol): @@ -10,16 +14,23 @@ async def execute( self, method: str, params: dict[str, Any] | None = None, - timeout: float | None = None, + timeout: float | None | UnsetType = UNSET, ) -> dict[str, Any]: ... class BoundCommandExecutor: """Bind command execution to the root connection or one CDP session.""" - def __init__(self, transport: Transport, session_id: str | None = None) -> None: + def __init__( + self, + transport: Transport, + session_id: str | None = None, + *, + default_timeout: float | None = 30.0, + ) -> None: self._transport = transport self._session_id = session_id + self._default_timeout = default_timeout @property def session_id(self) -> str | None: @@ -29,11 +40,33 @@ async def execute( self, method: str, params: dict[str, Any] | None = None, - timeout: float | None = None, + timeout: float | None | UnsetType = UNSET, ) -> dict[str, Any]: - return await self._transport.execute( - method=method, - params=params, - session_id=self._session_id, - timeout=timeout, + request_timeout = self._default_timeout if timeout is UNSET else timeout + return await _with_timeout( + method, + request_timeout, + self._transport.execute( + method=method, + params=params, + session_id=self._session_id, + timeout=request_timeout, + ), ) + + +async def _with_timeout[T]( + method: str, timeout: float | None, operation: Awaitable[T] +) -> T: + if timeout is None: + return await operation + deadline = asyncio.timeout(timeout) + try: + async with deadline: + return await operation + except TimeoutError: + # Preserve errors from the transport and timeouts owned by an outer + # caller. Only translate expiration of this particular deadline. + if not deadline.expired(): + raise + raise CDPTimeoutError(f"{method} timed out after {timeout}s") from None diff --git a/cdpify/session.py b/cdpify/session.py index c6dbc9eb..6938a69e 100644 --- a/cdpify/session.py +++ b/cdpify/session.py @@ -5,6 +5,7 @@ from cdpify.events.source import EventSource from cdpify.executor import BoundCommandExecutor from cdpify.transport import Transport +from cdpify.unset import UNSET, UnsetType class CdpSession(DomainAccessors, EventSource): @@ -15,13 +16,17 @@ def __init__( transport: Transport, event_router: EventRouter, session_id: str, + *, + default_timeout: float | None = 30.0, ) -> None: if not session_id: raise ValueError("session_id must not be empty") EventSource.__init__(self, event_router, session_id) self._session_id: str = session_id - self._executor = BoundCommandExecutor(transport, session_id) + self._executor = BoundCommandExecutor( + transport, session_id, default_timeout=default_timeout + ) @property def session_id(self) -> str: @@ -31,7 +36,7 @@ async def execute( self, method: str, params: dict[str, Any] | None = None, - timeout: float | None = None, + timeout: float | None | UnsetType = UNSET, ) -> dict[str, Any]: return await self._executor.execute( method=method, diff --git a/cdpify/transport.py b/cdpify/transport.py index ce9c185b..a147412b 100644 --- a/cdpify/transport.py +++ b/cdpify/transport.py @@ -24,7 +24,14 @@ async def execute( params: dict[str, Any] | None = None, session_id: str | None = None, timeout: float | None = None, - ) -> dict[str, Any]: ... + ) -> dict[str, Any]: + """Send and await a command; timeout is a total budget in seconds. + + None disables the transport deadline. CdpClient also enforces its + resolved deadline, even if a custom transport ignores this argument. + Cancellation must release resources associated with the request. + """ + ... def events(self) -> AsyncIterator[TransportEvent]: """Yield incoming events until the connection closes. diff --git a/cdpify/transports/websocket.py b/cdpify/transports/websocket.py index 523f8341..61800a0e 100644 --- a/cdpify/transports/websocket.py +++ b/cdpify/transports/websocket.py @@ -9,7 +9,8 @@ from websockets.asyncio.client import ClientConnection, connect from cdpify.event_queue import EventQueue -from cdpify.exceptions import CDPCommandError, CDPConnectionError, CDPTimeoutError +from cdpify.exceptions import CDPCommandError, CDPConnectionError +from cdpify.executor import _with_timeout from cdpify.transport import EventHandler, TransportEvent try: @@ -27,13 +28,11 @@ def __init__( *, additional_headers: dict[str, str] | None = None, max_frame_size: int = 100 * 1024 * 1024, - default_timeout: float = 30.0, max_queue_size: int = 0, ) -> None: self.url = url self._additional_headers = additional_headers self._max_frame_size = max_frame_size - self._default_timeout = default_timeout self._max_queue_size = max_queue_size self._ws: ClientConnection | None = None @@ -126,19 +125,16 @@ async def execute( if not self.is_connected or self._is_shutting_down: raise CDPConnectionError("Not connected") - request_timeout = self._default_timeout if timeout is None else timeout message_id = next(self._message_ids) message = _build_message(message_id, method, params, session_id) future = _create_pending_request(self._pending_requests, message_id) try: - await self._send(message_id, method, message) - return await self._await_response( - message_id, + return await _with_timeout( method, - future, - request_timeout, + timeout, + self._send_and_receive(message_id, method, message, future), ) finally: self._pending_requests.pop(message_id, None) @@ -149,6 +145,16 @@ async def execute( # waiting. Observe that failure even if sending raises first. future.exception() + async def _send_and_receive( + self, + message_id: int, + method: str, + message: dict[str, Any], + future: asyncio.Future[dict[str, Any]], + ) -> dict[str, Any]: + await self._send(message_id, method, message) + return await self._await_response(message_id, method, future) + async def events(self) -> AsyncIterator[TransportEvent]: if self._event_handler is not None: raise RuntimeError("Events are being delivered to the event handler") @@ -274,12 +280,8 @@ async def _await_response( message_id: int, method: str, future: asyncio.Future[dict[str, Any]], - timeout: float, ) -> dict[str, Any]: - try: - response = await asyncio.wait_for(future, timeout=timeout) - except TimeoutError: - raise CDPTimeoutError(f"{method} timed out after {timeout}s") from None + response = await future if "error" in response: logger.debug("<- #%s: error", message_id) diff --git a/tests/test_client.py b/tests/test_client.py index 42991f5b..e110eaf1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -173,7 +173,7 @@ async def test_generated_domain_uses_transport_execute() -> None: "method": "Tracing.end", "params": None, "session_id": None, - "timeout": None, + "timeout": 30.0, } ] diff --git a/tests/test_executor.py b/tests/test_executor.py new file mode 100644 index 00000000..fc42c72c --- /dev/null +++ b/tests/test_executor.py @@ -0,0 +1,74 @@ +import asyncio + +import pytest + +from cdpify import CdpClient, CDPTimeoutError +from cdpify.executor import BoundCommandExecutor + + +class SlowTransport: + def __init__(self) -> None: + self.cancelled = False + self.timeout = None + + async def execute(self, method, params=None, session_id=None, timeout=None): + self.timeout = timeout + try: + await asyncio.Event().wait() + finally: + self.cancelled = True + + +@pytest.mark.asyncio +@pytest.mark.parametrize("scope", ["root", "session", "raw", "raw_session"]) +async def test_client_deadline_enforces_custom_transports(scope) -> None: + transport = SlowTransport() + client = CdpClient(transport=transport, default_timeout=0.01) + with pytest.raises(CDPTimeoutError): + if scope == "root": + await client.runtime.enable() + elif scope == "session": + await client.session("S1").runtime.enable() + else: + await client.execute( + "Runtime.enable", session_id="S1" if scope == "raw_session" else None + ) + assert transport.cancelled + assert transport.timeout == 0.01 + + +@pytest.mark.asyncio +async def test_outer_timeout_preserves_builtin_exception_and_cleans_request() -> None: + transport = SlowTransport() + executor = BoundCommandExecutor(transport, default_timeout=30) + with pytest.raises(TimeoutError) as error: + async with asyncio.timeout(0.01): + await executor.execute("Runtime.enable") + assert not isinstance(error.value, CDPTimeoutError) + assert transport.cancelled + + +@pytest.mark.asyncio +async def test_none_explicitly_disables_default_and_cancellation_is_preserved() -> None: + transport = SlowTransport() + executor = BoundCommandExecutor(transport, default_timeout=0.001) + task = asyncio.create_task(executor.execute("Runtime.enable", timeout=None)) + await asyncio.sleep(0.01) + assert not task.done() + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + assert transport.cancelled + assert transport.timeout is None + + +@pytest.mark.asyncio +async def test_transport_timeout_error_is_not_relabelled_as_own_deadline() -> None: + class FailingTransport: + async def execute(self, **kwargs): + raise TimeoutError("transport-specific failure") + + executor = BoundCommandExecutor(FailingTransport()) + with pytest.raises(TimeoutError, match="transport-specific failure") as error: + await executor.execute("Runtime.enable") + assert not isinstance(error.value, CDPTimeoutError) diff --git a/tests/test_session.py b/tests/test_session.py index 58ee7486..85f4e01d 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -71,7 +71,7 @@ async def test_generated_commands_are_bound_to_session() -> None: method="Page.enable", params={}, session_id="session-2", - timeout=None, + timeout=30.0, ) @@ -86,7 +86,7 @@ async def test_low_level_execute_cannot_override_bound_session() -> None: method="Runtime.evaluate", params={"expression": "1 + 1"}, session_id="session-2", - timeout=None, + timeout=30.0, ) with pytest.raises(TypeError): await session.execute( # type: ignore[call-arg] @@ -113,13 +113,13 @@ async def test_sessions_are_safe_to_use_concurrently() -> None: method="Page.enable", params={}, session_id="A", - timeout=None, + timeout=30.0, ), call( method="Page.enable", params={}, session_id="B", - timeout=None, + timeout=30.0, ), ], any_order=True, diff --git a/tests/test_websocket_transport.py b/tests/test_websocket_transport.py index e8ea5bb9..3d60b03b 100644 --- a/tests/test_websocket_transport.py +++ b/tests/test_websocket_transport.py @@ -55,7 +55,7 @@ async def test_await_response_returns_result() -> None: future = _create_pending_request(transport._pending_requests, 1) transport._handle_response({"id": 1, "result": {"ok": True}}) - result = await transport._await_response(1, "Page.enable", future, timeout=1.0) + result = await transport._await_response(1, "Page.enable", future) assert result == {"ok": True} @@ -68,7 +68,7 @@ async def test_await_response_raises_command_error_with_method() -> None: transport._handle_response({"id": 2, "error": {"code": 9, "message": "failed"}}) with pytest.raises(CDPCommandError) as error_info: - await transport._await_response(2, "Page.enable", future, timeout=1.0) + await transport._await_response(2, "Page.enable", future) assert error_info.value.method == "Page.enable" assert error_info.value.code == 9 @@ -112,17 +112,22 @@ async def test_send_serializes_payload_as_json() -> None: @pytest.mark.asyncio -async def test_await_response_raises_timeout() -> None: +async def test_execute_timeout_includes_sending_and_cleans_pending_requests() -> None: transport = WebSocketTransport("ws://example") - unresolved: asyncio.Future[dict[str, object]] = asyncio.Future() + transport._ws = _FakeWebSocket() # type: ignore[assignment] + cancelled = asyncio.Event() + + async def blocked_send(*args) -> None: + try: + await asyncio.Event().wait() + finally: + cancelled.set() + transport._send = blocked_send # type: ignore[method-assign] with pytest.raises(CDPTimeoutError): - await transport._await_response( - 1, - "Page.enable", - unresolved, # type: ignore[arg-type] - timeout=0.001, - ) + await transport.execute("Runtime.enable", timeout=0.01) + assert cancelled.is_set() + assert transport._pending_requests == {} @pytest.mark.asyncio @@ -277,9 +282,7 @@ async def test_json_backends_send_text_and_decode_events_and_responses( assert json.loads(socket.sent_messages[0]) == payload pending = _create_pending_request(transport._pending_requests, 1) await transport._process_message(b'{"id":1,"result":{"ok":true}}') - assert await transport._await_response(1, "Test.command", pending, 1.0) == { - "ok": True - } + assert await transport._await_response(1, "Test.command", pending) == {"ok": True} await transport._process_message( json.dumps({"method": "Test.event", "params": {"text": "Grüße 🌍"}}).encode() ) From 152d8632afefd09b23d08b7fbd97edf86bf33932 Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:26:17 +0100 Subject: [PATCH 3/7] perf(transport): default to uncompressed CDP and avoid UTF-8 copies --- CHANGELOG.md | 14 +++++++++++ cdpify/client.py | 14 ++++++++++- cdpify/transports/websocket.py | 22 ++++++++++------ tests/test_websocket_client.py | 11 +++++--- tests/test_websocket_transport.py | 42 +++++++++++++++++++++++++------ 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beb50acd..c344b0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,20 @@ Breaking changes are listed first in each release. `CdpClient` instead. Standalone transport calls have no deadline unless `execute(timeout=seconds)` is specified. Generated methods retain only CDP's own parameters; use `asyncio.timeout(seconds)` for an individual caller budget. +- WebSocket compression is disabled by default for lower local latency. Pass + `compression="deflate"` to `CdpClient` or `WebSocketTransport` to opt into + compression when bandwidth matters more. + +### Added + +- `compression`, `open_timeout` and `close_timeout` options for URL-based clients + and the standalone WebSocket transport. Connection timeouts are in seconds. + +### Performance + +- The orjson transport sends UTF-8 bytes directly as text frames and receives + bytes without first decoding to a Python string. This removes redundant + UTF-8 conversions while preserving the CDP wire format. ## [0.4.0] - 2026-10-25 diff --git a/cdpify/client.py b/cdpify/client.py index 10116d1d..6942152b 100644 --- a/cdpify/client.py +++ b/cdpify/client.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Any, Self +from typing import Any, Literal, Self from cdpify.domains import DomainAccessors from cdpify.events import EventRouter, EventSubscription, ReceivedEvent @@ -24,6 +24,9 @@ def __init__( max_frame_size: int = 100 * 1024 * 1024, default_timeout: float | None = 30.0, max_queue_size: int = 0, + compression: Literal["deflate"] | None = None, + open_timeout: float | None = 10.0, + close_timeout: float | None = 10.0, ) -> None: if transport is not None and url is not None: raise TypeError("Pass either url or transport, not both") @@ -35,6 +38,9 @@ def __init__( additional_headers=additional_headers, max_frame_size=max_frame_size, max_queue_size=max_queue_size, + compression=compression, + open_timeout=open_timeout, + close_timeout=close_timeout, ) self._transport = transport @@ -184,6 +190,9 @@ def _create_websocket_transport( additional_headers: dict[str, str] | None, max_frame_size: int, max_queue_size: int, + compression: Literal["deflate"] | None, + open_timeout: float | None, + close_timeout: float | None, ) -> Transport: try: from cdpify.transports.websocket import WebSocketTransport @@ -199,4 +208,7 @@ def _create_websocket_transport( additional_headers=additional_headers, max_frame_size=max_frame_size, max_queue_size=max_queue_size, + compression=compression, + open_timeout=open_timeout, + close_timeout=close_timeout, ) diff --git a/cdpify/transports/websocket.py b/cdpify/transports/websocket.py index 61800a0e..e0165982 100644 --- a/cdpify/transports/websocket.py +++ b/cdpify/transports/websocket.py @@ -3,7 +3,7 @@ import json import logging from collections.abc import AsyncIterator -from typing import Any, Self +from typing import Any, Literal, Self import websockets from websockets.asyncio.client import ClientConnection, connect @@ -29,11 +29,17 @@ def __init__( additional_headers: dict[str, str] | None = None, max_frame_size: int = 100 * 1024 * 1024, max_queue_size: int = 0, + compression: Literal["deflate"] | None = None, + open_timeout: float | None = 10.0, + close_timeout: float | None = 10.0, ) -> None: self.url = url self._additional_headers = additional_headers self._max_frame_size = max_frame_size self._max_queue_size = max_queue_size + self._compression = compression + self._open_timeout = open_timeout + self._close_timeout = close_timeout self._ws: ClientConnection | None = None self._message_ids = itertools.count() @@ -79,6 +85,9 @@ async def connect(self) -> None: self.url, max_size=self._max_frame_size, additional_headers=self._additional_headers, + compression=self._compression, + open_timeout=self._open_timeout, + close_timeout=self._close_timeout, ) except Exception as error: raise CDPConnectionError(f"Connection failed: {error}") from error @@ -171,7 +180,8 @@ async def _run_message_loop(self) -> None: return try: - async for raw_message in connection: + while True: + raw_message = await connection.recv(decode=False) if self._is_shutting_down: break try: @@ -265,13 +275,9 @@ async def _send( if connection is None: raise CDPConnectionError("Not connected") logger.debug("-> #%s: %s", message_id, method) - payload = ( - orjson.dumps(message).decode("utf-8") - if orjson is not None - else json.dumps(message) - ) + payload = orjson.dumps(message) if orjson is not None else json.dumps(message) try: - await connection.send(payload) + await connection.send(payload, text=True) except websockets.exceptions.ConnectionClosed as error: raise CDPConnectionError("Connection closed while sending") from error diff --git a/tests/test_websocket_client.py b/tests/test_websocket_client.py index dcc8b071..856abb1a 100644 --- a/tests/test_websocket_client.py +++ b/tests/test_websocket_client.py @@ -6,6 +6,7 @@ import pytest from websockets.asyncio.server import ServerConnection, serve +from websockets.exceptions import ConnectionClosedOK from cdpify import CdpClient from cdpify.exceptions import CDPConnectionError, CDPStreamClosedError @@ -22,7 +23,9 @@ class _Event: @pytest.mark.asyncio async def test_websocket_client_routes_bursts_without_blocking_responses() -> None: async def respond(socket: ServerConnection) -> None: - command = json.loads(await socket.recv()) + raw_command = await socket.recv() + assert isinstance(raw_command, str) + command = json.loads(raw_command) for session_id in (None, "S1"): for value in range(5): await socket.send( @@ -40,6 +43,7 @@ async def respond(socket: ServerConnection) -> None: async with serve(respond, "127.0.0.1", 0) as server: port = server.sockets[0].getsockname()[1] async with CdpClient(f"ws://127.0.0.1:{port}", max_queue_size=2) as client: + assert client.transport._ws.protocol.extensions == [] root_events = client.listen(_Event) session_events = client.session("S1").listen(_Event, max_queue_size=1) all_events = client.listen_all(_Event, max_queue_size=0) @@ -115,10 +119,9 @@ def __init__(self) -> None: self.finish_close = asyncio.Event() self.close_calls = 0 - async def __aiter__(self): + async def recv(self, decode=None): await self.peer_closed.wait() - if False: - yield "" + raise ConnectionClosedOK(None, None) async def close(self) -> None: self.close_calls += 1 diff --git a/tests/test_websocket_transport.py b/tests/test_websocket_transport.py index 3d60b03b..fc266e10 100644 --- a/tests/test_websocket_transport.py +++ b/tests/test_websocket_transport.py @@ -21,11 +21,13 @@ class _FakeWebSocket: def __init__(self) -> None: - self.sent_messages: list[str] = [] + self.sent_messages: list[str | bytes] = [] + self.sent_text: list[bool | None] = [] self.closed = False - async def send(self, payload: str) -> None: + async def send(self, payload: str | bytes, *, text: bool | None = None) -> None: self.sent_messages.append(payload) + self.sent_text.append(text) async def close(self) -> None: self.closed = True @@ -107,7 +109,7 @@ async def test_send_serializes_payload_as_json() -> None: ) payload = transport._ws.sent_messages[0] # type: ignore[union-attr] - assert isinstance(payload, str) + assert transport._ws.sent_text == [True] assert json.loads(payload) == {"id": 3, "method": "Page.enable", "params": {}} @@ -186,9 +188,32 @@ def __init__(self, messages: list[str]) -> None: super().__init__() self._messages = messages - async def __aiter__(self): - for message in self._messages: - yield message + async def recv(self, decode=None): + assert decode is False + if not self._messages: + raise ConnectionClosedOK(None, None) + return self._messages.pop(0).encode() + + +@pytest.mark.asyncio +async def test_client_forwards_explicit_connection_options(monkeypatch) -> None: + from cdpify import CdpClient + + dial = AsyncMock(return_value=_ScriptedWebSocket([])) + monkeypatch.setattr(websocket_module, "connect", dial) + client = CdpClient( + "ws://example", compression="deflate", open_timeout=4, close_timeout=3 + ) + await client.connect() + dial.assert_awaited_once_with( + "ws://example", + max_size=100 * 1024 * 1024, + additional_headers=None, + compression="deflate", + open_timeout=4, + close_timeout=3, + ) + await client.disconnect() @pytest.mark.asyncio @@ -278,7 +303,8 @@ async def test_json_backends_send_text_and_decode_events_and_responses( payload = {"id": 1, "method": "Test.command", "params": {"text": "Grüße 🌍"}} await transport._send(1, "Test.command", payload) - assert isinstance(socket.sent_messages[0], str) + assert isinstance(socket.sent_messages[0], bytes if backend == "orjson" else str) + assert socket.sent_text == [True] assert json.loads(socket.sent_messages[0]) == payload pending = _create_pending_request(transport._pending_requests, 1) await transport._process_message(b'{"id":1,"result":{"ok":true}}') @@ -352,7 +378,7 @@ def __init__(self) -> None: self.sending = asyncio.Event() self.finished = asyncio.Event() - async def send(self, payload: str) -> None: + async def send(self, payload: str | bytes, *, text=None) -> None: self.sending.set() await self.finished.wait() raise ConnectionClosedOK(None, None) From a7c3881711093ca2fc46c096460234af92dbfc07 Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:27:57 +0100 Subject: [PATCH 4/7] perf(generator): serialize command arguments directly to wire dictionaries --- CHANGELOG.md | 5 + cdpify/domains/accessibility/domain.py | 80 +-- cdpify/domains/animation/domain.py | 59 +- cdpify/domains/audits/domain.py | 20 +- cdpify/domains/autofill/domain.py | 24 +- cdpify/domains/backgroundservice/domain.py | 29 +- cdpify/domains/bluetoothemulation/domain.py | 123 ++--- cdpify/domains/browser/domain.py | 149 +++--- cdpify/domains/cachestorage/domain.py | 59 +- cdpify/domains/cast/domain.py | 34 +- cdpify/domains/css/domain.py | 283 +++++----- cdpify/domains/debugger/domain.py | 290 +++++----- cdpify/domains/deviceaccess/domain.py | 17 +- cdpify/domains/deviceorientation/domain.py | 11 +- cdpify/domains/digitalcredentials/domain.py | 16 +- cdpify/domains/dom/domain.py | 473 ++++++++-------- cdpify/domains/domdebugger/domain.py | 80 +-- cdpify/domains/domsnapshot/domain.py | 43 +- cdpify/domains/domstorage/domain.py | 31 +- cdpify/domains/emulation/domain.py | 441 ++++++++------- cdpify/domains/eventbreakpoints/domain.py | 15 +- cdpify/domains/extensions/domain.py | 55 +- cdpify/domains/fedcm/domain.py | 49 +- cdpify/domains/fetch/domain.py | 108 ++-- cdpify/domains/filesystem/domain.py | 12 +- cdpify/domains/findinpage/domain.py | 9 +- cdpify/domains/headlessexperimental/domain.py | 22 +- cdpify/domains/heapprofiler/domain.py | 91 ++-- cdpify/domains/indexeddb/domain.py | 141 ++--- cdpify/domains/input/domain.py | 276 +++++----- cdpify/domains/io/domain.py | 26 +- cdpify/domains/layertree/domain.py | 65 +-- cdpify/domains/log/domain.py | 10 +- cdpify/domains/memory/domain.py | 27 +- cdpify/domains/network/domain.py | 310 ++++++----- cdpify/domains/overlay/domain.py | 261 ++++----- cdpify/domains/page/domain.py | 505 ++++++++++-------- cdpify/domains/performance/domain.py | 17 +- cdpify/domains/performancetimeline/domain.py | 10 +- cdpify/domains/profiler/domain.py | 25 +- cdpify/domains/pwa/domain.py | 61 ++- cdpify/domains/runtime/domain.py | 254 +++++---- cdpify/domains/security/domain.py | 23 +- cdpify/domains/serviceworker/domain.py | 76 ++- cdpify/domains/smartcardemulation/domain.py | 92 ++-- cdpify/domains/storage/domain.py | 156 +++--- cdpify/domains/systeminfo/domain.py | 10 +- cdpify/domains/target/domain.py | 200 ++++--- cdpify/domains/tethering/domain.py | 15 +- cdpify/domains/tracing/domain.py | 60 ++- cdpify/domains/webaudio/domain.py | 10 +- cdpify/domains/webauthn/domain.py | 124 ++--- cdpify/domains/webmcp/domain.py | 16 +- cdpify/generator/generators/domain.py | 12 +- cdpify/generator/generators/views.py | 19 +- cdpify/generator/templates/domain.py.jinja2 | 18 +- .../generators/test_domain_generator.py | 13 +- tests/test_generated_params.py | 40 ++ 58 files changed, 3026 insertions(+), 2474 deletions(-) create mode 100644 tests/test_generated_params.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c344b0f5..7e4696dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,11 @@ Breaking changes are listed first in each release. ### Performance +- Generated command methods build wire dictionaries directly, avoiding an + intermediate Params dataclass and reflective field traversal. Public Params + models and keyword-only command signatures are retained. Nested models still + serialize through the shared codec. + - The orjson transport sends UTF-8 bytes directly as text frames and receives bytes without first decoding to a Python string. This removes redundant UTF-8 conversions while preserving the CDP wire format. diff --git a/cdpify/domains/accessibility/domain.py b/cdpify/domains/accessibility/domain.py index 7456f17c..3a44671c 100644 --- a/cdpify/domains/accessibility/domain.py +++ b/cdpify/domains/accessibility/domain.py @@ -4,24 +4,18 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( AccessibilityCommand, - GetAXNodeAndAncestorsParams, GetAXNodeAndAncestorsResult, - GetChildAXNodesParams, GetChildAXNodesResult, - GetFullAXTreeParams, GetFullAXTreeResult, - GetPartialAXTreeParams, GetPartialAXTreeResult, - GetRootAXNodeParams, GetRootAXNodeResult, - QueryAXTreeParams, QueryAXTreeResult, ) from .types import ( @@ -72,16 +66,19 @@ async def get_partial_ax_tree( Fetches the accessibility node and partial accessibility tree for this DOM node, if it exists. """ - params = GetPartialAXTreeParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - fetch_relatives=fetch_relatives, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if fetch_relatives is not None: + _params["fetchRelatives"] = fetch_relatives result = await self._executor.execute( method=AccessibilityCommand.GET_PARTIAL_AX_TREE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetPartialAXTreeResult, result) @@ -94,11 +91,15 @@ async def get_full_ax_tree( """ Fetches the entire accessibility tree for the root Document """ - params = GetFullAXTreeParams(depth=depth, frame_id=frame_id) + _params: dict[str, Any] = {} + if depth is not None: + _params["depth"] = depth + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=AccessibilityCommand.GET_FULL_AX_TREE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetFullAXTreeResult, result) @@ -110,11 +111,13 @@ async def get_root_ax_node( """ Fetches the root node. Requires `enable()` to have been called previously. """ - params = GetRootAXNodeParams(frame_id=frame_id) + _params: dict[str, Any] = {} + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=AccessibilityCommand.GET_ROOT_AX_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetRootAXNodeResult, result) @@ -129,13 +132,17 @@ async def get_ax_node_and_ancestors( Fetches a node and all ancestors up to and including the root. Requires `enable()` to have been called previously. """ - params = GetAXNodeAndAncestorsParams( - node_id=node_id, backend_node_id=backend_node_id, object_id=object_id - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=AccessibilityCommand.GET_AX_NODE_AND_ANCESTORS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAXNodeAndAncestorsResult, result) @@ -149,11 +156,14 @@ async def get_child_ax_nodes( Fetches a particular accessibility node by AXNodeId. Requires `enable()` to have been called previously. """ - params = GetChildAXNodesParams(id=id, frame_id=frame_id) + _params: dict[str, Any] = {} + _params["id"] = _encode_value(id) + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=AccessibilityCommand.GET_CHILD_AX_NODES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetChildAXNodesResult, result) @@ -174,16 +184,20 @@ async def query_ax_tree( command returns an error. If neither `accessibleName` or `role` is specified, it returns all the accessibility nodes in the subtree. """ - params = QueryAXTreeParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - accessible_name=accessible_name, - role=role, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if accessible_name is not None: + _params["accessibleName"] = accessible_name + if role is not None: + _params["role"] = role result = await self._executor.execute( method=AccessibilityCommand.QUERY_AX_TREE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(QueryAXTreeResult, result) diff --git a/cdpify/domains/animation/domain.py b/cdpify/domains/animation/domain.py index d9330d9e..f3be0cb9 100644 --- a/cdpify/domains/animation/domain.py +++ b/cdpify/domains/animation/domain.py @@ -4,21 +4,16 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( AnimationCommand, - GetCurrentTimeParams, GetCurrentTimeResult, GetPlaybackRateResult, - ReleaseAnimationsParams, - ResolveAnimationParams, ResolveAnimationResult, - SeekAnimationsParams, - SetPausedParams, - SetPlaybackRateParams, - SetTimingParams, ) @@ -56,11 +51,12 @@ async def get_current_time( """ Returns the current time of the an animation. """ - params = GetCurrentTimeParams(id=id) + _params: dict[str, Any] = {} + _params["id"] = id result = await self._executor.execute( method=AnimationCommand.GET_CURRENT_TIME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCurrentTimeResult, result) @@ -84,11 +80,12 @@ async def release_animations( """ Releases a set of animations to no longer be manipulated. """ - params = ReleaseAnimationsParams(animations=animations) + _params: dict[str, Any] = {} + _params["animations"] = _encode_value(animations) await self._executor.execute( method=AnimationCommand.RELEASE_ANIMATIONS, - params=encode_cdp(params), + params=_params, ) async def resolve_animation( @@ -99,11 +96,12 @@ async def resolve_animation( """ Gets the remote object of the Animation. """ - params = ResolveAnimationParams(animation_id=animation_id) + _params: dict[str, Any] = {} + _params["animationId"] = animation_id result = await self._executor.execute( method=AnimationCommand.RESOLVE_ANIMATION, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ResolveAnimationResult, result) @@ -117,15 +115,16 @@ async def seek_animations( """ Seek a set of animations to a particular time within each animation. """ - params = SeekAnimationsParams( - animations=animations, - current_time=current_time, - current_times=current_times, - ) + _params: dict[str, Any] = {} + _params["animations"] = _encode_value(animations) + if current_time is not None: + _params["currentTime"] = current_time + if current_times is not None: + _params["currentTimes"] = _encode_value(current_times) await self._executor.execute( method=AnimationCommand.SEEK_ANIMATIONS, - params=encode_cdp(params), + params=_params, ) async def set_paused( @@ -137,11 +136,13 @@ async def set_paused( """ Sets the paused state of a set of animations. """ - params = SetPausedParams(animations=animations, paused=paused) + _params: dict[str, Any] = {} + _params["animations"] = _encode_value(animations) + _params["paused"] = paused await self._executor.execute( method=AnimationCommand.SET_PAUSED, - params=encode_cdp(params), + params=_params, ) async def set_playback_rate( @@ -152,11 +153,12 @@ async def set_playback_rate( """ Sets the playback rate of the document timeline. """ - params = SetPlaybackRateParams(playback_rate=playback_rate) + _params: dict[str, Any] = {} + _params["playbackRate"] = playback_rate await self._executor.execute( method=AnimationCommand.SET_PLAYBACK_RATE, - params=encode_cdp(params), + params=_params, ) async def set_timing( @@ -169,11 +171,12 @@ async def set_timing( """ Sets the timing of an animation node. """ - params = SetTimingParams( - animation_id=animation_id, duration=duration, delay=delay - ) + _params: dict[str, Any] = {} + _params["animationId"] = animation_id + _params["duration"] = duration + _params["delay"] = delay await self._executor.execute( method=AnimationCommand.SET_TIMING, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/audits/domain.py b/cdpify/domains/audits/domain.py index 3d3a2912..8dca06df 100644 --- a/cdpify/domains/audits/domain.py +++ b/cdpify/domains/audits/domain.py @@ -4,15 +4,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( AuditsCommand, CheckFormsIssuesResult, - GetEncodedResponseParams, GetEncodedResponseResult, ) @@ -36,16 +35,17 @@ async def get_encoded_response( Returns the response body and size if it were re-encoded with the specified settings. Only applies to images. """ - params = GetEncodedResponseParams( - request_id=request_id, - encoding=encoding, - quality=quality, - size_only=size_only, - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + _params["encoding"] = encoding + if quality is not None: + _params["quality"] = quality + if size_only is not None: + _params["sizeOnly"] = size_only result = await self._executor.execute( method=AuditsCommand.GET_ENCODED_RESPONSE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetEncodedResponseResult, result) diff --git a/cdpify/domains/autofill/domain.py b/cdpify/domains/autofill/domain.py index 479a4ac2..30d87cc5 100644 --- a/cdpify/domains/autofill/domain.py +++ b/cdpify/domains/autofill/domain.py @@ -4,15 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import encode_cdp +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( AutofillCommand, - SetAddressesParams, - TriggerParams, ) from .types import ( Address, @@ -40,13 +38,18 @@ async def trigger( form cannot be autofilled, returns an error. Returns once the values have been applied to the form fields. """ - params = TriggerParams( - field_id=field_id, frame_id=frame_id, card=card, address=address - ) + _params: dict[str, Any] = {} + _params["fieldId"] = _encode_value(field_id) + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) + if card is not None: + _params["card"] = _encode_value(card) + if address is not None: + _params["address"] = _encode_value(address) await self._executor.execute( method=AutofillCommand.TRIGGER, - params=encode_cdp(params), + params=_params, ) async def set_addresses( @@ -57,11 +60,12 @@ async def set_addresses( """ Set addresses so that developers can verify their forms implementation. """ - params = SetAddressesParams(addresses=addresses) + _params: dict[str, Any] = {} + _params["addresses"] = _encode_value(addresses) await self._executor.execute( method=AutofillCommand.SET_ADDRESSES, - params=encode_cdp(params), + params=_params, ) async def disable( diff --git a/cdpify/domains/backgroundservice/domain.py b/cdpify/domains/backgroundservice/domain.py index 0431c307..34fd84aa 100644 --- a/cdpify/domains/backgroundservice/domain.py +++ b/cdpify/domains/backgroundservice/domain.py @@ -4,15 +4,13 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( BackgroundServiceCommand, - ClearEventsParams, - SetRecordingParams, - StartObservingParams, - StopObservingParams, ) from .types import ( ServiceName, @@ -31,11 +29,12 @@ async def start_observing( """ Enables event updates for the service. """ - params = StartObservingParams(service=service) + _params: dict[str, Any] = {} + _params["service"] = _encode_value(service) await self._executor.execute( method=BackgroundServiceCommand.START_OBSERVING, - params=encode_cdp(params), + params=_params, ) async def stop_observing( @@ -46,11 +45,12 @@ async def stop_observing( """ Disables event updates for the service. """ - params = StopObservingParams(service=service) + _params: dict[str, Any] = {} + _params["service"] = _encode_value(service) await self._executor.execute( method=BackgroundServiceCommand.STOP_OBSERVING, - params=encode_cdp(params), + params=_params, ) async def set_recording( @@ -62,11 +62,13 @@ async def set_recording( """ Set the recording state for the service. """ - params = SetRecordingParams(should_record=should_record, service=service) + _params: dict[str, Any] = {} + _params["shouldRecord"] = should_record + _params["service"] = _encode_value(service) await self._executor.execute( method=BackgroundServiceCommand.SET_RECORDING, - params=encode_cdp(params), + params=_params, ) async def clear_events( @@ -77,9 +79,10 @@ async def clear_events( """ Clears all stored data for the service. """ - params = ClearEventsParams(service=service) + _params: dict[str, Any] = {} + _params["service"] = _encode_value(service) await self._executor.execute( method=BackgroundServiceCommand.CLEAR_EVENTS, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/bluetoothemulation/domain.py b/cdpify/domains/bluetoothemulation/domain.py index 338786ad..97b8c442 100644 --- a/cdpify/domains/bluetoothemulation/domain.py +++ b/cdpify/domains/bluetoothemulation/domain.py @@ -4,28 +4,16 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - AddCharacteristicParams, AddCharacteristicResult, - AddDescriptorParams, AddDescriptorResult, - AddServiceParams, AddServiceResult, BluetoothEmulationCommand, - EnableParams, - RemoveCharacteristicParams, - RemoveDescriptorParams, - RemoveServiceParams, - SetSimulatedCentralStateParams, - SimulateAdvertisementParams, - SimulateCharacteristicOperationResponseParams, - SimulateDescriptorOperationResponseParams, - SimulateGATTDisconnectionParams, - SimulateGATTOperationResponseParams, - SimulatePreconnectedPeripheralParams, ) from .types import ( CentralState, @@ -51,11 +39,13 @@ async def enable( """ Enable the BluetoothEmulation domain. """ - params = EnableParams(state=state, le_supported=le_supported) + _params: dict[str, Any] = {} + _params["state"] = _encode_value(state) + _params["leSupported"] = le_supported await self._executor.execute( method=BluetoothEmulationCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def set_simulated_central_state( @@ -66,11 +56,12 @@ async def set_simulated_central_state( """ Set the state of the simulated central. """ - params = SetSimulatedCentralStateParams(state=state) + _params: dict[str, Any] = {} + _params["state"] = _encode_value(state) await self._executor.execute( method=BluetoothEmulationCommand.SET_SIMULATED_CENTRAL_STATE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -96,16 +87,15 @@ async def simulate_preconnected_peripheral( Simulates a peripheral with |address|, |name| and |knownServiceUuids| that has already been connected to the system. """ - params = SimulatePreconnectedPeripheralParams( - address=address, - name=name, - manufacturer_data=manufacturer_data, - known_service_uuids=known_service_uuids, - ) + _params: dict[str, Any] = {} + _params["address"] = address + _params["name"] = name + _params["manufacturerData"] = _encode_value(manufacturer_data) + _params["knownServiceUuids"] = _encode_value(known_service_uuids) await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_PRECONNECTED_PERIPHERAL, - params=encode_cdp(params), + params=_params, ) async def simulate_advertisement( @@ -117,11 +107,12 @@ async def simulate_advertisement( Simulates an advertisement packet described in |entry| being received by the central. """ - params = SimulateAdvertisementParams(entry=entry) + _params: dict[str, Any] = {} + _params["entry"] = _encode_value(entry) await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_ADVERTISEMENT, - params=encode_cdp(params), + params=_params, ) async def simulate_gatt_operation_response( @@ -136,13 +127,14 @@ async def simulate_gatt_operation_response( operation of |type|. The |code| value follows the HCI Error Codes from Bluetooth Core Specification Vol 2 Part D 1.3 List Of Error Codes. """ - params = SimulateGATTOperationResponseParams( - address=address, type=type, code=code - ) + _params: dict[str, Any] = {} + _params["address"] = address + _params["type"] = _encode_value(type) + _params["code"] = code await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_GATT_OPERATION_RESPONSE, - params=encode_cdp(params), + params=_params, ) async def simulate_characteristic_operation_response( @@ -160,13 +152,16 @@ async def simulate_characteristic_operation_response( |data| is expected to exist when simulating a successful read operation response. """ - params = SimulateCharacteristicOperationResponseParams( - characteristic_id=characteristic_id, type=type, code=code, data=data - ) + _params: dict[str, Any] = {} + _params["characteristicId"] = characteristic_id + _params["type"] = _encode_value(type) + _params["code"] = code + if data is not None: + _params["data"] = data await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_CHARACTERISTIC_OPERATION_RESPONSE, - params=encode_cdp(params), + params=_params, ) async def simulate_descriptor_operation_response( @@ -183,13 +178,16 @@ async def simulate_descriptor_operation_response( Core Specification Vol 3 Part F 3.4.1.1 Error Response. The |data| is expected to exist when simulating a successful read operation response. """ - params = SimulateDescriptorOperationResponseParams( - descriptor_id=descriptor_id, type=type, code=code, data=data - ) + _params: dict[str, Any] = {} + _params["descriptorId"] = descriptor_id + _params["type"] = _encode_value(type) + _params["code"] = code + if data is not None: + _params["data"] = data await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_DESCRIPTOR_OPERATION_RESPONSE, - params=encode_cdp(params), + params=_params, ) async def add_service( @@ -201,11 +199,13 @@ async def add_service( """ Adds a service with |serviceUuid| to the peripheral with |address|. """ - params = AddServiceParams(address=address, service_uuid=service_uuid) + _params: dict[str, Any] = {} + _params["address"] = address + _params["serviceUuid"] = service_uuid result = await self._executor.execute( method=BluetoothEmulationCommand.ADD_SERVICE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddServiceResult, result) @@ -217,11 +217,12 @@ async def remove_service( """ Removes the service respresented by |serviceId| from the simulated central. """ - params = RemoveServiceParams(service_id=service_id) + _params: dict[str, Any] = {} + _params["serviceId"] = service_id await self._executor.execute( method=BluetoothEmulationCommand.REMOVE_SERVICE, - params=encode_cdp(params), + params=_params, ) async def add_characteristic( @@ -235,15 +236,14 @@ async def add_characteristic( Adds a characteristic with |characteristicUuid| and |properties| to the service represented by |serviceId|. """ - params = AddCharacteristicParams( - service_id=service_id, - characteristic_uuid=characteristic_uuid, - properties=properties, - ) + _params: dict[str, Any] = {} + _params["serviceId"] = service_id + _params["characteristicUuid"] = characteristic_uuid + _params["properties"] = _encode_value(properties) result = await self._executor.execute( method=BluetoothEmulationCommand.ADD_CHARACTERISTIC, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddCharacteristicResult, result) @@ -256,11 +256,12 @@ async def remove_characteristic( Removes the characteristic respresented by |characteristicId| from the simulated central. """ - params = RemoveCharacteristicParams(characteristic_id=characteristic_id) + _params: dict[str, Any] = {} + _params["characteristicId"] = characteristic_id await self._executor.execute( method=BluetoothEmulationCommand.REMOVE_CHARACTERISTIC, - params=encode_cdp(params), + params=_params, ) async def add_descriptor( @@ -273,13 +274,13 @@ async def add_descriptor( Adds a descriptor with |descriptorUuid| to the characteristic respresented by |characteristicId|. """ - params = AddDescriptorParams( - characteristic_id=characteristic_id, descriptor_uuid=descriptor_uuid - ) + _params: dict[str, Any] = {} + _params["characteristicId"] = characteristic_id + _params["descriptorUuid"] = descriptor_uuid result = await self._executor.execute( method=BluetoothEmulationCommand.ADD_DESCRIPTOR, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddDescriptorResult, result) @@ -291,11 +292,12 @@ async def remove_descriptor( """ Removes the descriptor with |descriptorId| from the simulated central. """ - params = RemoveDescriptorParams(descriptor_id=descriptor_id) + _params: dict[str, Any] = {} + _params["descriptorId"] = descriptor_id await self._executor.execute( method=BluetoothEmulationCommand.REMOVE_DESCRIPTOR, - params=encode_cdp(params), + params=_params, ) async def simulate_gatt_disconnection( @@ -306,9 +308,10 @@ async def simulate_gatt_disconnection( """ Simulates a GATT disconnection from the peripheral with |address|. """ - params = SimulateGATTDisconnectionParams(address=address) + _params: dict[str, Any] = {} + _params["address"] = address await self._executor.execute( method=BluetoothEmulationCommand.SIMULATE_GATT_DISCONNECTION, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/browser/domain.py b/cdpify/domains/browser/domain.py index 775148d6..f98e9650 100644 --- a/cdpify/domains/browser/domain.py +++ b/cdpify/domains/browser/domain.py @@ -4,38 +4,22 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - AddMockCameraParams, - AddPrivacySandboxEnrollmentOverrideParams, BrowserCommand, - CancelDownloadParams, - ExecuteBrowserCommandParams, GetBrowserCommandLineResult, GetGlobalPrivacyControlResult, - GetHistogramParams, GetHistogramResult, - GetHistogramsParams, GetHistogramsResult, GetVersionResult, - GetWindowBoundsParams, GetWindowBoundsResult, - GetWindowForTargetParams, GetWindowForTargetResult, - GrantPermissionsParams, - ResetPermissionsParams, - SetContentsSizeParams, - SetDockTileParams, - SetDownloadBehaviorParams, - SetGlobalPrivacyControlParams, SetGlobalPrivacyControlResult, - SetPermissionParams, - SetWindowBoundsParams, ) from .types import ( Bounds, @@ -67,17 +51,19 @@ async def set_permission( """ Set permission settings for given embedding and embedded origins. """ - params = SetPermissionParams( - permission=permission, - setting=setting, - origin=origin, - embedded_origin=embedded_origin, - browser_context_id=browser_context_id, - ) + _params: dict[str, Any] = {} + _params["permission"] = _encode_value(permission) + _params["setting"] = _encode_value(setting) + if origin is not None: + _params["origin"] = origin + if embedded_origin is not None: + _params["embeddedOrigin"] = embedded_origin + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=BrowserCommand.SET_PERMISSION, - params=encode_cdp(params), + params=_params, ) @deprecated("Browser.grantPermissions is deprecated") @@ -92,15 +78,16 @@ async def grant_permissions( Grant specific permissions to the given origin and reject all others. Deprecated. Use setPermission instead. """ - params = GrantPermissionsParams( - permissions=permissions, - origin=origin, - browser_context_id=browser_context_id, - ) + _params: dict[str, Any] = {} + _params["permissions"] = _encode_value(permissions) + if origin is not None: + _params["origin"] = origin + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=BrowserCommand.GRANT_PERMISSIONS, - params=encode_cdp(params), + params=_params, ) async def reset_permissions( @@ -111,11 +98,13 @@ async def reset_permissions( """ Reset all permission management for all origins. """ - params = ResetPermissionsParams(browser_context_id=browser_context_id) + _params: dict[str, Any] = {} + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=BrowserCommand.RESET_PERMISSIONS, - params=encode_cdp(params), + params=_params, ) async def set_download_behavior( @@ -129,16 +118,18 @@ async def set_download_behavior( """ Set the behavior when downloading a file. """ - params = SetDownloadBehaviorParams( - behavior=behavior, - browser_context_id=browser_context_id, - download_path=download_path, - events_enabled=events_enabled, - ) + _params: dict[str, Any] = {} + _params["behavior"] = behavior + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) + if download_path is not None: + _params["downloadPath"] = download_path + if events_enabled is not None: + _params["eventsEnabled"] = events_enabled await self._executor.execute( method=BrowserCommand.SET_DOWNLOAD_BEHAVIOR, - params=encode_cdp(params), + params=_params, ) async def cancel_download( @@ -150,11 +141,14 @@ async def cancel_download( """ Cancel a download if in progress """ - params = CancelDownloadParams(guid=guid, browser_context_id=browser_context_id) + _params: dict[str, Any] = {} + _params["guid"] = guid + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=BrowserCommand.CANCEL_DOWNLOAD, - params=encode_cdp(params), + params=_params, ) async def close( @@ -225,11 +219,12 @@ async def add_mock_camera( automation. The mock camera is not scoped to a particular page or frame and is removed when the DevTools session that created it disconnects. """ - params = AddMockCameraParams(device_id=device_id) + _params: dict[str, Any] = {} + _params["deviceId"] = device_id await self._executor.execute( method=BrowserCommand.ADD_MOCK_CAMERA, - params=encode_cdp(params), + params=_params, ) async def get_histograms( @@ -241,11 +236,15 @@ async def get_histograms( """ Get Chrome histograms. """ - params = GetHistogramsParams(query=query, delta=delta) + _params: dict[str, Any] = {} + if query is not None: + _params["query"] = query + if delta is not None: + _params["delta"] = delta result = await self._executor.execute( method=BrowserCommand.GET_HISTOGRAMS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetHistogramsResult, result) @@ -258,11 +257,14 @@ async def get_histogram( """ Get a Chrome histogram by name. """ - params = GetHistogramParams(name=name, delta=delta) + _params: dict[str, Any] = {} + _params["name"] = name + if delta is not None: + _params["delta"] = delta result = await self._executor.execute( method=BrowserCommand.GET_HISTOGRAM, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetHistogramResult, result) @@ -274,11 +276,12 @@ async def get_window_bounds( """ Get position and size of the browser window. """ - params = GetWindowBoundsParams(window_id=window_id) + _params: dict[str, Any] = {} + _params["windowId"] = _encode_value(window_id) result = await self._executor.execute( method=BrowserCommand.GET_WINDOW_BOUNDS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetWindowBoundsResult, result) @@ -290,11 +293,13 @@ async def get_window_for_target( """ Get the browser window that contains the devtools target. """ - params = GetWindowForTargetParams(target_id=target_id) + _params: dict[str, Any] = {} + if target_id is not None: + _params["targetId"] = _encode_value(target_id) result = await self._executor.execute( method=BrowserCommand.GET_WINDOW_FOR_TARGET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetWindowForTargetResult, result) @@ -307,11 +312,13 @@ async def set_window_bounds( """ Set position and/or size of the browser window. """ - params = SetWindowBoundsParams(window_id=window_id, bounds=bounds) + _params: dict[str, Any] = {} + _params["windowId"] = _encode_value(window_id) + _params["bounds"] = _encode_value(bounds) await self._executor.execute( method=BrowserCommand.SET_WINDOW_BOUNDS, - params=encode_cdp(params), + params=_params, ) async def set_contents_size( @@ -324,11 +331,16 @@ async def set_contents_size( """ Set size of the browser contents resizing browser window as necessary. """ - params = SetContentsSizeParams(window_id=window_id, width=width, height=height) + _params: dict[str, Any] = {} + _params["windowId"] = _encode_value(window_id) + if width is not None: + _params["width"] = width + if height is not None: + _params["height"] = height await self._executor.execute( method=BrowserCommand.SET_CONTENTS_SIZE, - params=encode_cdp(params), + params=_params, ) async def set_dock_tile( @@ -340,11 +352,15 @@ async def set_dock_tile( """ Set dock tile details, platform-specific. """ - params = SetDockTileParams(badge_label=badge_label, image=image) + _params: dict[str, Any] = {} + if badge_label is not None: + _params["badgeLabel"] = badge_label + if image is not None: + _params["image"] = image await self._executor.execute( method=BrowserCommand.SET_DOCK_TILE, - params=encode_cdp(params), + params=_params, ) async def execute_browser_command( @@ -355,11 +371,12 @@ async def execute_browser_command( """ Invoke custom browser commands used by telemetry. """ - params = ExecuteBrowserCommandParams(command_id=command_id) + _params: dict[str, Any] = {} + _params["commandId"] = _encode_value(command_id) await self._executor.execute( method=BrowserCommand.EXECUTE_BROWSER_COMMAND, - params=encode_cdp(params), + params=_params, ) async def add_privacy_sandbox_enrollment_override( @@ -371,11 +388,12 @@ async def add_privacy_sandbox_enrollment_override( Allows a site to use privacy sandbox features that require enrollment without the site actually being enrolled. Only supported on page targets. """ - params = AddPrivacySandboxEnrollmentOverrideParams(url=url) + _params: dict[str, Any] = {} + _params["url"] = url await self._executor.execute( method=BrowserCommand.ADD_PRIVACY_SANDBOX_ENROLLMENT_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def get_global_privacy_control( @@ -400,10 +418,11 @@ async def set_global_privacy_control( Sets and then gets the current globally-applied privacy control status See https://www.w3.org/TR/gpc/#set-global-privacy-control """ - params = SetGlobalPrivacyControlParams(gpc=gpc) + _params: dict[str, Any] = {} + _params["gpc"] = gpc result = await self._executor.execute( method=BrowserCommand.SET_GLOBAL_PRIVACY_CONTROL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetGlobalPrivacyControlResult, result) diff --git a/cdpify/domains/cachestorage/domain.py b/cdpify/domains/cachestorage/domain.py index 112a6fa7..284be941 100644 --- a/cdpify/domains/cachestorage/domain.py +++ b/cdpify/domains/cachestorage/domain.py @@ -4,20 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( CacheStorageCommand, - DeleteCacheParams, - DeleteEntryParams, - RequestCachedResponseParams, RequestCachedResponseResult, - RequestCacheNamesParams, RequestCacheNamesResult, - RequestEntriesParams, RequestEntriesResult, ) from .types import ( @@ -41,11 +36,12 @@ async def delete_cache( """ Deletes a cache. """ - params = DeleteCacheParams(cache_id=cache_id) + _params: dict[str, Any] = {} + _params["cacheId"] = _encode_value(cache_id) await self._executor.execute( method=CacheStorageCommand.DELETE_CACHE, - params=encode_cdp(params), + params=_params, ) async def delete_entry( @@ -57,11 +53,13 @@ async def delete_entry( """ Deletes a cache entry. """ - params = DeleteEntryParams(cache_id=cache_id, request=request) + _params: dict[str, Any] = {} + _params["cacheId"] = _encode_value(cache_id) + _params["request"] = request await self._executor.execute( method=CacheStorageCommand.DELETE_ENTRY, - params=encode_cdp(params), + params=_params, ) async def request_cache_names( @@ -74,15 +72,17 @@ async def request_cache_names( """ Requests cache names. """ - params = RequestCacheNamesParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) result = await self._executor.execute( method=CacheStorageCommand.REQUEST_CACHE_NAMES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestCacheNamesResult, result) @@ -96,13 +96,14 @@ async def request_cached_response( """ Fetches cache entry. """ - params = RequestCachedResponseParams( - cache_id=cache_id, request_url=request_url, request_headers=request_headers - ) + _params: dict[str, Any] = {} + _params["cacheId"] = _encode_value(cache_id) + _params["requestURL"] = request_url + _params["requestHeaders"] = _encode_value(request_headers) result = await self._executor.execute( method=CacheStorageCommand.REQUEST_CACHED_RESPONSE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestCachedResponseResult, result) @@ -117,15 +118,17 @@ async def request_entries( """ Requests data from cache. """ - params = RequestEntriesParams( - cache_id=cache_id, - skip_count=skip_count, - page_size=page_size, - path_filter=path_filter, - ) + _params: dict[str, Any] = {} + _params["cacheId"] = _encode_value(cache_id) + if skip_count is not None: + _params["skipCount"] = skip_count + if page_size is not None: + _params["pageSize"] = page_size + if path_filter is not None: + _params["pathFilter"] = path_filter result = await self._executor.execute( method=CacheStorageCommand.REQUEST_ENTRIES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestEntriesResult, result) diff --git a/cdpify/domains/cast/domain.py b/cdpify/domains/cast/domain.py index 6f0fd1b9..efc37e4b 100644 --- a/cdpify/domains/cast/domain.py +++ b/cdpify/domains/cast/domain.py @@ -4,16 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + from cdpify.executor import CommandExecutor from .commands import ( CastCommand, - EnableParams, - SetSinkToUseParams, - StartDesktopMirroringParams, - StartTabMirroringParams, - StopCastingParams, ) @@ -32,11 +28,13 @@ async def enable( |sinksUpdated| event is fired. Also starts observing for issue messages. When an issue is added or removed, an |issueUpdated| event is fired. """ - params = EnableParams(presentation_url=presentation_url) + _params: dict[str, Any] = {} + if presentation_url is not None: + _params["presentationUrl"] = presentation_url await self._executor.execute( method=CastCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -59,11 +57,12 @@ async def set_sink_to_use( Sets a sink to be used when the web page requests the browser to choose a sink via Presentation API, Remote Playback API, or Cast SDK. """ - params = SetSinkToUseParams(sink_name=sink_name) + _params: dict[str, Any] = {} + _params["sinkName"] = sink_name await self._executor.execute( method=CastCommand.SET_SINK_TO_USE, - params=encode_cdp(params), + params=_params, ) async def start_desktop_mirroring( @@ -74,11 +73,12 @@ async def start_desktop_mirroring( """ Starts mirroring the desktop to the sink. """ - params = StartDesktopMirroringParams(sink_name=sink_name) + _params: dict[str, Any] = {} + _params["sinkName"] = sink_name await self._executor.execute( method=CastCommand.START_DESKTOP_MIRRORING, - params=encode_cdp(params), + params=_params, ) async def start_tab_mirroring( @@ -89,11 +89,12 @@ async def start_tab_mirroring( """ Starts mirroring the tab to the sink. """ - params = StartTabMirroringParams(sink_name=sink_name) + _params: dict[str, Any] = {} + _params["sinkName"] = sink_name await self._executor.execute( method=CastCommand.START_TAB_MIRRORING, - params=encode_cdp(params), + params=_params, ) async def stop_casting( @@ -104,9 +105,10 @@ async def stop_casting( """ Stops the active Cast session on the sink. """ - params = StopCastingParams(sink_name=sink_name) + _params: dict[str, Any] = {} + _params["sinkName"] = sink_name await self._executor.execute( method=CastCommand.STOP_CASTING, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/css/domain.py b/cdpify/domains/css/domain.py index cb9bf81b..0956d8da 100644 --- a/cdpify/domains/css/domain.py +++ b/cdpify/domains/css/domain.py @@ -4,76 +4,44 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - AddRuleParams, AddRuleResult, - CollectClassNamesParams, CollectClassNamesResult, - CreateStyleSheetParams, CreateStyleSheetResult, CSSCommand, - ForcePositionTryOptionParams, - ForcePseudoStateParams, - ForceStartingStyleParams, - GetAnimatedStylesForNodeParams, GetAnimatedStylesForNodeResult, - GetBackgroundColorsParams, GetBackgroundColorsResult, - GetComputedStyleForNodeParams, GetComputedStyleForNodeResult, GetEnvironmentVariablesResult, - GetInlineStylesForNodeParams, GetInlineStylesForNodeResult, - GetLayersForNodeParams, GetLayersForNodeResult, - GetLocationForSelectorParams, GetLocationForSelectorResult, - GetLonghandPropertiesParams, GetLonghandPropertiesResult, - GetMatchedStylesForNodeParams, GetMatchedStylesForNodeResult, GetMediaQueriesResult, - GetPlatformFontsForNodeParams, GetPlatformFontsForNodeResult, - GetStyleSheetTextParams, GetStyleSheetTextResult, - ResolveValuesParams, ResolveValuesResult, - SetContainerQueryConditionTextParams, SetContainerQueryConditionTextResult, - SetContainerQueryTextParams, SetContainerQueryTextResult, - SetEffectivePropertyValueForNodeParams, - SetKeyframeKeyParams, SetKeyframeKeyResult, - SetLocalFontsEnabledParams, - SetMediaTextParams, SetMediaTextResult, - SetNavigationTextParams, SetNavigationTextResult, - SetPropertyRulePropertyNameParams, SetPropertyRulePropertyNameResult, - SetRuleSelectorParams, SetRuleSelectorResult, - SetScopeTextParams, SetScopeTextResult, - SetStyleSheetTextParams, SetStyleSheetTextResult, - SetStyleTextsParams, SetStyleTextsResult, - SetSupportsTextParams, SetSupportsTextResult, StopRuleUsageTrackingResult, TakeComputedStyleUpdatesResult, TakeCoverageDeltaResult, - TrackComputedStyleUpdatesForNodeParams, - TrackComputedStyleUpdatesParams, ) from .types import ( CSSComputedStyleProperty, @@ -101,16 +69,18 @@ async def add_rule( Inserts a new rule with the given `ruleText` in a stylesheet with given `styleSheetId`, at the position specified by `location`. """ - params = AddRuleParams( - style_sheet_id=style_sheet_id, - rule_text=rule_text, - location=location, - node_for_property_syntax_validation=node_for_property_syntax_validation, - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["ruleText"] = rule_text + _params["location"] = _encode_value(location) + if node_for_property_syntax_validation is not None: + _params["nodeForPropertySyntaxValidation"] = _encode_value( + node_for_property_syntax_validation + ) result = await self._executor.execute( method=CSSCommand.ADD_RULE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddRuleResult, result) @@ -122,11 +92,12 @@ async def collect_class_names( """ Returns all class names from specified stylesheet. """ - params = CollectClassNamesParams(style_sheet_id=style_sheet_id) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) result = await self._executor.execute( method=CSSCommand.COLLECT_CLASS_NAMES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CollectClassNamesResult, result) @@ -140,11 +111,14 @@ async def create_style_sheet( Creates a new special "via-inspector" stylesheet in the frame with given `frameId`. """ - params = CreateStyleSheetParams(frame_id=frame_id, force=force) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + if force is not None: + _params["force"] = force result = await self._executor.execute( method=CSSCommand.CREATE_STYLE_SHEET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CreateStyleSheetResult, result) @@ -181,13 +155,13 @@ async def force_pseudo_state( Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser. """ - params = ForcePseudoStateParams( - node_id=node_id, forced_pseudo_classes=forced_pseudo_classes - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["forcedPseudoClasses"] = _encode_value(forced_pseudo_classes) await self._executor.execute( method=CSSCommand.FORCE_PSEUDO_STATE, - params=encode_cdp(params), + params=_params, ) async def force_starting_style( @@ -199,11 +173,13 @@ async def force_starting_style( """ Ensures that the given node is in its starting-style state. """ - params = ForceStartingStyleParams(node_id=node_id, forced=forced) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["forced"] = forced await self._executor.execute( method=CSSCommand.FORCE_STARTING_STYLE, - params=encode_cdp(params), + params=_params, ) async def force_position_try_option( @@ -215,11 +191,14 @@ async def force_position_try_option( """ Forces a position-try option for the given node. """ - params = ForcePositionTryOptionParams(node_id=node_id, index=index) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + if index is not None: + _params["index"] = index await self._executor.execute( method=CSSCommand.FORCE_POSITION_TRY_OPTION, - params=encode_cdp(params), + params=_params, ) async def get_background_colors( @@ -227,11 +206,12 @@ async def get_background_colors( *, node_id: dom.NodeId, ) -> GetBackgroundColorsResult: - params = GetBackgroundColorsParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_BACKGROUND_COLORS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetBackgroundColorsResult, result) @@ -243,11 +223,12 @@ async def get_computed_style_for_node( """ Returns the computed style for a DOM node identified by `nodeId`. """ - params = GetComputedStyleForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_COMPUTED_STYLE_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetComputedStyleForNodeResult, result) @@ -271,17 +252,19 @@ async def resolve_values( the provided value without any changes. Note: this function currently does not resolve CSS random() function, it returns unmodified random() function parts.` """ - params = ResolveValuesParams( - values=values, - node_id=node_id, - property_name=property_name, - pseudo_type=pseudo_type, - pseudo_identifier=pseudo_identifier, - ) + _params: dict[str, Any] = {} + _params["values"] = _encode_value(values) + _params["nodeId"] = _encode_value(node_id) + if property_name is not None: + _params["propertyName"] = property_name + if pseudo_type is not None: + _params["pseudoType"] = _encode_value(pseudo_type) + if pseudo_identifier is not None: + _params["pseudoIdentifier"] = pseudo_identifier result = await self._executor.execute( method=CSSCommand.RESOLVE_VALUES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ResolveValuesResult, result) @@ -291,11 +274,13 @@ async def get_longhand_properties( shorthand_name: str, value: str, ) -> GetLonghandPropertiesResult: - params = GetLonghandPropertiesParams(shorthand_name=shorthand_name, value=value) + _params: dict[str, Any] = {} + _params["shorthandName"] = shorthand_name + _params["value"] = value result = await self._executor.execute( method=CSSCommand.GET_LONGHAND_PROPERTIES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetLonghandPropertiesResult, result) @@ -308,11 +293,12 @@ async def get_inline_styles_for_node( Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by `nodeId`. """ - params = GetInlineStylesForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_INLINE_STYLES_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetInlineStylesForNodeResult, result) @@ -325,11 +311,12 @@ async def get_animated_styles_for_node( Returns the styles coming from animations & transitions including the animation & transition styles coming from inheritance chain. """ - params = GetAnimatedStylesForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_ANIMATED_STYLES_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAnimatedStylesForNodeResult, result) @@ -341,11 +328,12 @@ async def get_matched_styles_for_node( """ Returns requested styles for a DOM node identified by `nodeId`. """ - params = GetMatchedStylesForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_MATCHED_STYLES_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetMatchedStylesForNodeResult, result) @@ -383,11 +371,12 @@ async def get_platform_fonts_for_node( Requests information about platform fonts which we used to render child TextNodes in the given node. """ - params = GetPlatformFontsForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_PLATFORM_FONTS_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetPlatformFontsForNodeResult, result) @@ -399,11 +388,12 @@ async def get_style_sheet_text( """ Returns the current textual content for a stylesheet. """ - params = GetStyleSheetTextParams(style_sheet_id=style_sheet_id) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) result = await self._executor.execute( method=CSSCommand.GET_STYLE_SHEET_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetStyleSheetTextResult, result) @@ -418,11 +408,12 @@ async def get_layers_for_node( layer for the nearest ancestor document or shadow root. The layer root contains the full layer tree for the tree scope and their ordering. """ - params = GetLayersForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=CSSCommand.GET_LAYERS_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetLayersForNodeResult, result) @@ -436,13 +427,13 @@ async def get_location_for_selector( Given a CSS selector text and a style sheet ID, getLocationForSelector returns an array of locations of the CSS selector in the style sheet. """ - params = GetLocationForSelectorParams( - style_sheet_id=style_sheet_id, selector_text=selector_text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["selectorText"] = selector_text result = await self._executor.execute( method=CSSCommand.GET_LOCATION_FOR_SELECTOR, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetLocationForSelectorResult, result) @@ -458,11 +449,13 @@ async def track_computed_style_updates_for_node( passing a new node id removes tracking from the previous node. Pass `undefined` to disable tracking. """ - params = TrackComputedStyleUpdatesForNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) await self._executor.execute( method=CSSCommand.TRACK_COMPUTED_STYLE_UPDATES_FOR_NODE, - params=encode_cdp(params), + params=_params, ) async def track_computed_style_updates( @@ -479,13 +472,12 @@ async def track_computed_style_updates( properties occur after the node has been pushed to the front-end, no updates will be issued for the node. """ - params = TrackComputedStyleUpdatesParams( - properties_to_track=properties_to_track - ) + _params: dict[str, Any] = {} + _params["propertiesToTrack"] = _encode_value(properties_to_track) await self._executor.execute( method=CSSCommand.TRACK_COMPUTED_STYLE_UPDATES, - params=encode_cdp(params), + params=_params, ) async def take_computed_style_updates( @@ -511,13 +503,14 @@ async def set_effective_property_value_for_node( Find a rule with the given active property for the given node and set the new value for this property """ - params = SetEffectivePropertyValueForNodeParams( - node_id=node_id, property_name=property_name, value=value - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["propertyName"] = property_name + _params["value"] = value await self._executor.execute( method=CSSCommand.SET_EFFECTIVE_PROPERTY_VALUE_FOR_NODE, - params=encode_cdp(params), + params=_params, ) async def set_property_rule_property_name( @@ -530,13 +523,14 @@ async def set_property_rule_property_name( """ Modifies the property rule property name. """ - params = SetPropertyRulePropertyNameParams( - style_sheet_id=style_sheet_id, range=range, property_name=property_name - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["propertyName"] = property_name result = await self._executor.execute( method=CSSCommand.SET_PROPERTY_RULE_PROPERTY_NAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetPropertyRulePropertyNameResult, result) @@ -550,13 +544,14 @@ async def set_keyframe_key( """ Modifies the keyframe rule key text. """ - params = SetKeyframeKeyParams( - style_sheet_id=style_sheet_id, range=range, key_text=key_text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["keyText"] = key_text result = await self._executor.execute( method=CSSCommand.SET_KEYFRAME_KEY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetKeyframeKeyResult, result) @@ -570,13 +565,14 @@ async def set_media_text( """ Modifies the rule selector. """ - params = SetMediaTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_MEDIA_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetMediaTextResult, result) @@ -592,13 +588,14 @@ async def set_container_query_text( Modifies the expression of a container query. Deprecated. Use setContainerQueryConditionText instead. """ - params = SetContainerQueryTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_CONTAINER_QUERY_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetContainerQueryTextResult, result) @@ -609,13 +606,14 @@ async def set_container_query_condition_text( range: SourceRange, text: str, ) -> SetContainerQueryConditionTextResult: - params = SetContainerQueryConditionTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_CONTAINER_QUERY_CONDITION_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetContainerQueryConditionTextResult, result) @@ -629,13 +627,14 @@ async def set_supports_text( """ Modifies the expression of a supports at-rule. """ - params = SetSupportsTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_SUPPORTS_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetSupportsTextResult, result) @@ -649,13 +648,14 @@ async def set_navigation_text( """ Modifies the expression of a navigation at-rule. """ - params = SetNavigationTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_NAVIGATION_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetNavigationTextResult, result) @@ -669,13 +669,14 @@ async def set_scope_text( """ Modifies the expression of a scope at-rule. """ - params = SetScopeTextParams( - style_sheet_id=style_sheet_id, range=range, text=text - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_SCOPE_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetScopeTextResult, result) @@ -689,13 +690,14 @@ async def set_rule_selector( """ Modifies the rule selector. """ - params = SetRuleSelectorParams( - style_sheet_id=style_sheet_id, range=range, selector=selector - ) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["range"] = _encode_value(range) + _params["selector"] = selector result = await self._executor.execute( method=CSSCommand.SET_RULE_SELECTOR, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetRuleSelectorResult, result) @@ -708,11 +710,13 @@ async def set_style_sheet_text( """ Sets the new stylesheet text. """ - params = SetStyleSheetTextParams(style_sheet_id=style_sheet_id, text=text) + _params: dict[str, Any] = {} + _params["styleSheetId"] = _encode_value(style_sheet_id) + _params["text"] = text result = await self._executor.execute( method=CSSCommand.SET_STYLE_SHEET_TEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetStyleSheetTextResult, result) @@ -725,14 +729,16 @@ async def set_style_texts( """ Applies specified style edits one after another in the given order. """ - params = SetStyleTextsParams( - edits=edits, - node_for_property_syntax_validation=node_for_property_syntax_validation, - ) + _params: dict[str, Any] = {} + _params["edits"] = _encode_value(edits) + if node_for_property_syntax_validation is not None: + _params["nodeForPropertySyntaxValidation"] = _encode_value( + node_for_property_syntax_validation + ) result = await self._executor.execute( method=CSSCommand.SET_STYLE_TEXTS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetStyleTextsResult, result) @@ -781,9 +787,10 @@ async def set_local_fonts_enabled( """ Enables/disables rendering of local CSS fonts (enabled by default). """ - params = SetLocalFontsEnabledParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=CSSCommand.SET_LOCAL_FONTS_ENABLED, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/debugger/domain.py b/cdpify/domains/debugger/domain.py index 52825b1c..cd85e0d0 100644 --- a/cdpify/domains/debugger/domain.py +++ b/cdpify/domains/debugger/domain.py @@ -4,59 +4,29 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - ContinueToLocationParams, DebuggerCommand, - DisassembleWasmModuleParams, DisassembleWasmModuleResult, - EnableParams, EnableResult, - EvaluateOnCallFrameParams, EvaluateOnCallFrameResult, - GetPossibleBreakpointsParams, GetPossibleBreakpointsResult, - GetScriptSourceParams, GetScriptSourceResult, - GetStackTraceParams, GetStackTraceResult, - GetWasmBytecodeParams, GetWasmBytecodeResult, - NextWasmDisassemblyChunkParams, NextWasmDisassemblyChunkResult, - PauseOnAsyncCallParams, - RemoveBreakpointParams, - RestartFrameParams, RestartFrameResult, - ResumeParams, - SearchInContentParams, SearchInContentResult, - SetAsyncCallStackDepthParams, - SetBlackboxedRangesParams, - SetBlackboxExecutionContextsParams, - SetBlackboxPatternsParams, - SetBreakpointByUrlParams, SetBreakpointByUrlResult, - SetBreakpointOnFunctionCallParams, SetBreakpointOnFunctionCallResult, - SetBreakpointParams, SetBreakpointResult, - SetBreakpointsActiveParams, - SetInstrumentationBreakpointParams, SetInstrumentationBreakpointResult, - SetPauseOnExceptionsParams, - SetReturnValueParams, - SetScriptSourceParams, SetScriptSourceResult, - SetSkipAllPausesParams, - SetVariableValueParams, - StepIntoParams, - StepOverParams, ) from .types import ( BreakpointId, @@ -83,13 +53,14 @@ async def continue_to_location( """ Continues execution until specific location is reached. """ - params = ContinueToLocationParams( - location=location, target_call_frames=target_call_frames - ) + _params: dict[str, Any] = {} + _params["location"] = _encode_value(location) + if target_call_frames is not None: + _params["targetCallFrames"] = target_call_frames await self._executor.execute( method=DebuggerCommand.CONTINUE_TO_LOCATION, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -112,11 +83,13 @@ async def enable( Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received. """ - params = EnableParams(max_scripts_cache_size=max_scripts_cache_size) + _params: dict[str, Any] = {} + if max_scripts_cache_size is not None: + _params["maxScriptsCacheSize"] = max_scripts_cache_size result = await self._executor.execute( method=DebuggerCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(EnableResult, result) @@ -137,22 +110,29 @@ async def evaluate_on_call_frame( """ Evaluates expression on a given call frame. """ - params = EvaluateOnCallFrameParams( - call_frame_id=call_frame_id, - expression=expression, - object_group=object_group, - include_command_line_api=include_command_line_api, - silent=silent, - return_by_value=return_by_value, - generate_preview=generate_preview, - throw_on_side_effect=throw_on_side_effect, - timeout=timeout, - scope_number=scope_number, - ) + _params: dict[str, Any] = {} + _params["callFrameId"] = _encode_value(call_frame_id) + _params["expression"] = expression + if object_group is not None: + _params["objectGroup"] = object_group + if include_command_line_api is not None: + _params["includeCommandLineAPI"] = include_command_line_api + if silent is not None: + _params["silent"] = silent + if return_by_value is not None: + _params["returnByValue"] = return_by_value + if generate_preview is not None: + _params["generatePreview"] = generate_preview + if throw_on_side_effect is not None: + _params["throwOnSideEffect"] = throw_on_side_effect + if timeout is not None: + _params["timeout"] = _encode_value(timeout) + if scope_number is not None: + _params["scopeNumber"] = scope_number result = await self._executor.execute( method=DebuggerCommand.EVALUATE_ON_CALL_FRAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(EvaluateOnCallFrameResult, result) @@ -167,13 +147,16 @@ async def get_possible_breakpoints( Returns possible locations for breakpoint. scriptId in start and end range locations should be the same. """ - params = GetPossibleBreakpointsParams( - start=start, end=end, restrict_to_function=restrict_to_function - ) + _params: dict[str, Any] = {} + _params["start"] = _encode_value(start) + if end is not None: + _params["end"] = _encode_value(end) + if restrict_to_function is not None: + _params["restrictToFunction"] = restrict_to_function result = await self._executor.execute( method=DebuggerCommand.GET_POSSIBLE_BREAKPOINTS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetPossibleBreakpointsResult, result) @@ -185,11 +168,12 @@ async def get_script_source( """ Returns source for the script with given id. """ - params = GetScriptSourceParams(script_id=script_id) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) result = await self._executor.execute( method=DebuggerCommand.GET_SCRIPT_SOURCE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetScriptSourceResult, result) @@ -198,11 +182,12 @@ async def disassemble_wasm_module( *, script_id: runtime.ScriptId, ) -> DisassembleWasmModuleResult: - params = DisassembleWasmModuleParams(script_id=script_id) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) result = await self._executor.execute( method=DebuggerCommand.DISASSEMBLE_WASM_MODULE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(DisassembleWasmModuleResult, result) @@ -216,11 +201,12 @@ async def next_wasm_disassembly_chunk( If disassembly is complete, this API will invalidate the streamId and return an empty chunk. Any subsequent calls for the now invalid stream will return errors. """ - params = NextWasmDisassemblyChunkParams(stream_id=stream_id) + _params: dict[str, Any] = {} + _params["streamId"] = stream_id result = await self._executor.execute( method=DebuggerCommand.NEXT_WASM_DISASSEMBLY_CHUNK, - params=encode_cdp(params), + params=_params, ) return decode_cdp(NextWasmDisassemblyChunkResult, result) @@ -233,11 +219,12 @@ async def get_wasm_bytecode( """ This command is deprecated. Use getScriptSource instead. """ - params = GetWasmBytecodeParams(script_id=script_id) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) result = await self._executor.execute( method=DebuggerCommand.GET_WASM_BYTECODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetWasmBytecodeResult, result) @@ -249,11 +236,12 @@ async def get_stack_trace( """ Returns stack trace with given `stackTraceId`. """ - params = GetStackTraceParams(stack_trace_id=stack_trace_id) + _params: dict[str, Any] = {} + _params["stackTraceId"] = _encode_value(stack_trace_id) result = await self._executor.execute( method=DebuggerCommand.GET_STACK_TRACE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetStackTraceResult, result) @@ -274,11 +262,12 @@ async def pause_on_async_call( *, parent_stack_trace_id: runtime.StackTraceId, ) -> None: - params = PauseOnAsyncCallParams(parent_stack_trace_id=parent_stack_trace_id) + _params: dict[str, Any] = {} + _params["parentStackTraceId"] = _encode_value(parent_stack_trace_id) await self._executor.execute( method=DebuggerCommand.PAUSE_ON_ASYNC_CALL, - params=encode_cdp(params), + params=_params, ) async def remove_breakpoint( @@ -289,11 +278,12 @@ async def remove_breakpoint( """ Removes JavaScript breakpoint. """ - params = RemoveBreakpointParams(breakpoint_id=breakpoint_id) + _params: dict[str, Any] = {} + _params["breakpointId"] = _encode_value(breakpoint_id) await self._executor.execute( method=DebuggerCommand.REMOVE_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def restart_frame( @@ -314,11 +304,14 @@ async def restart_frame( events instead, that fires once V8 pauses at the beginning of the restarted function. """ - params = RestartFrameParams(call_frame_id=call_frame_id, mode=mode) + _params: dict[str, Any] = {} + _params["callFrameId"] = _encode_value(call_frame_id) + if mode is not None: + _params["mode"] = mode result = await self._executor.execute( method=DebuggerCommand.RESTART_FRAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RestartFrameResult, result) @@ -330,11 +323,13 @@ async def resume( """ Resumes JavaScript execution. """ - params = ResumeParams(terminate_on_resume=terminate_on_resume) + _params: dict[str, Any] = {} + if terminate_on_resume is not None: + _params["terminateOnResume"] = terminate_on_resume await self._executor.execute( method=DebuggerCommand.RESUME, - params=encode_cdp(params), + params=_params, ) async def search_in_content( @@ -348,16 +343,17 @@ async def search_in_content( """ Searches for given string in script content. """ - params = SearchInContentParams( - script_id=script_id, - query=query, - case_sensitive=case_sensitive, - is_regex=is_regex, - ) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) + _params["query"] = query + if case_sensitive is not None: + _params["caseSensitive"] = case_sensitive + if is_regex is not None: + _params["isRegex"] = is_regex result = await self._executor.execute( method=DebuggerCommand.SEARCH_IN_CONTENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SearchInContentResult, result) @@ -369,11 +365,12 @@ async def set_async_call_stack_depth( """ Enables or disables async call stacks tracking. """ - params = SetAsyncCallStackDepthParams(max_depth=max_depth) + _params: dict[str, Any] = {} + _params["maxDepth"] = max_depth await self._executor.execute( method=DebuggerCommand.SET_ASYNC_CALL_STACK_DEPTH, - params=encode_cdp(params), + params=_params, ) async def set_blackbox_execution_contexts( @@ -387,11 +384,12 @@ async def set_blackbox_execution_contexts( leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. """ - params = SetBlackboxExecutionContextsParams(unique_ids=unique_ids) + _params: dict[str, Any] = {} + _params["uniqueIds"] = _encode_value(unique_ids) await self._executor.execute( method=DebuggerCommand.SET_BLACKBOX_EXECUTION_CONTEXTS, - params=encode_cdp(params), + params=_params, ) async def set_blackbox_patterns( @@ -406,13 +404,14 @@ async def set_blackbox_patterns( to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. """ - params = SetBlackboxPatternsParams( - patterns=patterns, skip_anonymous=skip_anonymous - ) + _params: dict[str, Any] = {} + _params["patterns"] = _encode_value(patterns) + if skip_anonymous is not None: + _params["skipAnonymous"] = skip_anonymous await self._executor.execute( method=DebuggerCommand.SET_BLACKBOX_PATTERNS, - params=encode_cdp(params), + params=_params, ) async def set_blackboxed_ranges( @@ -427,11 +426,13 @@ async def set_blackboxed_ranges( 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted. """ - params = SetBlackboxedRangesParams(script_id=script_id, positions=positions) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) + _params["positions"] = _encode_value(positions) await self._executor.execute( method=DebuggerCommand.SET_BLACKBOXED_RANGES, - params=encode_cdp(params), + params=_params, ) async def set_breakpoint( @@ -443,11 +444,14 @@ async def set_breakpoint( """ Sets JavaScript breakpoint at a given location. """ - params = SetBreakpointParams(location=location, condition=condition) + _params: dict[str, Any] = {} + _params["location"] = _encode_value(location) + if condition is not None: + _params["condition"] = condition result = await self._executor.execute( method=DebuggerCommand.SET_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetBreakpointResult, result) @@ -461,11 +465,12 @@ async def set_instrumentation_breakpoint( """ Sets instrumentation breakpoint. """ - params = SetInstrumentationBreakpointParams(instrumentation=instrumentation) + _params: dict[str, Any] = {} + _params["instrumentation"] = instrumentation result = await self._executor.execute( method=DebuggerCommand.SET_INSTRUMENTATION_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetInstrumentationBreakpointResult, result) @@ -486,18 +491,22 @@ async def set_breakpoint_by_url( script parsing will result in subsequent `breakpointResolved` events issued. This logical breakpoint will survive page reloads. """ - params = SetBreakpointByUrlParams( - line_number=line_number, - url=url, - url_regex=url_regex, - script_hash=script_hash, - column_number=column_number, - condition=condition, - ) + _params: dict[str, Any] = {} + _params["lineNumber"] = line_number + if url is not None: + _params["url"] = url + if url_regex is not None: + _params["urlRegex"] = url_regex + if script_hash is not None: + _params["scriptHash"] = script_hash + if column_number is not None: + _params["columnNumber"] = column_number + if condition is not None: + _params["condition"] = condition result = await self._executor.execute( method=DebuggerCommand.SET_BREAKPOINT_BY_URL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetBreakpointByUrlResult, result) @@ -512,13 +521,14 @@ async def set_breakpoint_on_function_call( function was created from the same source as a given one, calling it will also trigger the breakpoint. """ - params = SetBreakpointOnFunctionCallParams( - object_id=object_id, condition=condition - ) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) + if condition is not None: + _params["condition"] = condition result = await self._executor.execute( method=DebuggerCommand.SET_BREAKPOINT_ON_FUNCTION_CALL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetBreakpointOnFunctionCallResult, result) @@ -530,11 +540,12 @@ async def set_breakpoints_active( """ Activates / deactivates all breakpoints on the page. """ - params = SetBreakpointsActiveParams(active=active) + _params: dict[str, Any] = {} + _params["active"] = active await self._executor.execute( method=DebuggerCommand.SET_BREAKPOINTS_ACTIVE, - params=encode_cdp(params), + params=_params, ) async def set_pause_on_exceptions( @@ -547,11 +558,12 @@ async def set_pause_on_exceptions( uncaught exceptions, or caught exceptions, no exceptions. Initial pause on exceptions state is `none`. """ - params = SetPauseOnExceptionsParams(state=state) + _params: dict[str, Any] = {} + _params["state"] = state await self._executor.execute( method=DebuggerCommand.SET_PAUSE_ON_EXCEPTIONS, - params=encode_cdp(params), + params=_params, ) async def set_return_value( @@ -562,11 +574,12 @@ async def set_return_value( """ Changes return value in top frame. Available only at return break position. """ - params = SetReturnValueParams(new_value=new_value) + _params: dict[str, Any] = {} + _params["newValue"] = _encode_value(new_value) await self._executor.execute( method=DebuggerCommand.SET_RETURN_VALUE, - params=encode_cdp(params), + params=_params, ) @deprecated("Debugger.setScriptSource is deprecated") @@ -582,16 +595,17 @@ async def set_script_source( Live edit is no longer supported and this command always fails with a "no longer available" error. """ - params = SetScriptSourceParams( - script_id=script_id, - script_source=script_source, - dry_run=dry_run, - allow_top_frame_editing=allow_top_frame_editing, - ) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) + _params["scriptSource"] = script_source + if dry_run is not None: + _params["dryRun"] = dry_run + if allow_top_frame_editing is not None: + _params["allowTopFrameEditing"] = allow_top_frame_editing result = await self._executor.execute( method=DebuggerCommand.SET_SCRIPT_SOURCE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetScriptSourceResult, result) @@ -604,11 +618,12 @@ async def set_skip_all_pauses( Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). """ - params = SetSkipAllPausesParams(skip=skip) + _params: dict[str, Any] = {} + _params["skip"] = skip await self._executor.execute( method=DebuggerCommand.SET_SKIP_ALL_PAUSES, - params=encode_cdp(params), + params=_params, ) async def set_variable_value( @@ -623,16 +638,15 @@ async def set_variable_value( Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually. """ - params = SetVariableValueParams( - scope_number=scope_number, - variable_name=variable_name, - new_value=new_value, - call_frame_id=call_frame_id, - ) + _params: dict[str, Any] = {} + _params["scopeNumber"] = scope_number + _params["variableName"] = variable_name + _params["newValue"] = _encode_value(new_value) + _params["callFrameId"] = _encode_value(call_frame_id) await self._executor.execute( method=DebuggerCommand.SET_VARIABLE_VALUE, - params=encode_cdp(params), + params=_params, ) async def step_into( @@ -644,13 +658,15 @@ async def step_into( """ Steps into the function call. """ - params = StepIntoParams( - break_on_async_call=break_on_async_call, skip_list=skip_list - ) + _params: dict[str, Any] = {} + if break_on_async_call is not None: + _params["breakOnAsyncCall"] = break_on_async_call + if skip_list is not None: + _params["skipList"] = _encode_value(skip_list) await self._executor.execute( method=DebuggerCommand.STEP_INTO, - params=encode_cdp(params), + params=_params, ) async def step_out( @@ -673,9 +689,13 @@ async def step_over( """ Steps over the statement. """ - params = StepOverParams(skip_list=skip_list, enter_ranges=enter_ranges) + _params: dict[str, Any] = {} + if skip_list is not None: + _params["skipList"] = _encode_value(skip_list) + if enter_ranges is not None: + _params["enterRanges"] = _encode_value(enter_ranges) await self._executor.execute( method=DebuggerCommand.STEP_OVER, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/deviceaccess/domain.py b/cdpify/domains/deviceaccess/domain.py index 674b86c9..dc305873 100644 --- a/cdpify/domains/deviceaccess/domain.py +++ b/cdpify/domains/deviceaccess/domain.py @@ -4,13 +4,13 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - CancelPromptParams, DeviceAccessCommand, - SelectPromptParams, ) from .types import ( DeviceId, @@ -53,11 +53,13 @@ async def select_prompt( """ Select a device in response to a DeviceAccess.deviceRequestPrompted event. """ - params = SelectPromptParams(id=id, device_id=device_id) + _params: dict[str, Any] = {} + _params["id"] = _encode_value(id) + _params["deviceId"] = _encode_value(device_id) await self._executor.execute( method=DeviceAccessCommand.SELECT_PROMPT, - params=encode_cdp(params), + params=_params, ) async def cancel_prompt( @@ -68,9 +70,10 @@ async def cancel_prompt( """ Cancel a prompt in response to a DeviceAccess.deviceRequestPrompted event. """ - params = CancelPromptParams(id=id) + _params: dict[str, Any] = {} + _params["id"] = _encode_value(id) await self._executor.execute( method=DeviceAccessCommand.CANCEL_PROMPT, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/deviceorientation/domain.py b/cdpify/domains/deviceorientation/domain.py index 390d8dad..71050ee0 100644 --- a/cdpify/domains/deviceorientation/domain.py +++ b/cdpify/domains/deviceorientation/domain.py @@ -4,12 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + from cdpify.executor import CommandExecutor from .commands import ( DeviceOrientationCommand, - SetDeviceOrientationOverrideParams, ) @@ -38,9 +38,12 @@ async def set_device_orientation_override( """ Overrides the Device Orientation. """ - params = SetDeviceOrientationOverrideParams(alpha=alpha, beta=beta, gamma=gamma) + _params: dict[str, Any] = {} + _params["alpha"] = alpha + _params["beta"] = beta + _params["gamma"] = gamma await self._executor.execute( method=DeviceOrientationCommand.SET_DEVICE_ORIENTATION_OVERRIDE, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/digitalcredentials/domain.py b/cdpify/domains/digitalcredentials/domain.py index 045f08d2..dff56245 100644 --- a/cdpify/domains/digitalcredentials/domain.py +++ b/cdpify/domains/digitalcredentials/domain.py @@ -6,12 +6,11 @@ from typing import TYPE_CHECKING, Any -from cdpify.codec import encode_cdp +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( DigitalCredentialsCommand, - SetVirtualWalletBehaviorParams, ) from .types import ( VirtualWalletAction, @@ -37,11 +36,16 @@ async def set_virtual_wallet_behavior( Sets the behavior of the virtual wallet for digital credential requests issued from this frame. """ - params = SetVirtualWalletBehaviorParams( - action=action, protocol=protocol, response=response, frame_id=frame_id - ) + _params: dict[str, Any] = {} + _params["action"] = _encode_value(action) + if protocol is not None: + _params["protocol"] = protocol + if response is not None: + _params["response"] = _encode_value(response) + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) await self._executor.execute( method=DigitalCredentialsCommand.SET_VIRTUAL_WALLET_BEHAVIOR, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/dom/domain.py b/cdpify/domains/dom/domain.py index 62fa0b2b..995eb810 100644 --- a/cdpify/domains/dom/domain.py +++ b/cdpify/domains/dom/domain.py @@ -4,94 +4,47 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - CollectClassNamesFromSubtreeParams, CollectClassNamesFromSubtreeResult, - CopyToParams, CopyToResult, - DescribeNodeParams, DescribeNodeResult, - DiscardSearchResultsParams, DOMCommand, - EnableParams, - FocusParams, - ForceShowInterestParams, - ForceShowPopoverParams, ForceShowPopoverResult, - GetAnchorElementParams, GetAnchorElementResult, - GetAttributesParams, GetAttributesResult, - GetBoxModelParams, GetBoxModelResult, - GetContainerForNodeParams, GetContainerForNodeResult, - GetContentQuadsParams, GetContentQuadsResult, GetDetachedDomNodesResult, - GetDocumentParams, GetDocumentResult, - GetElementByRelationParams, GetElementByRelationResult, - GetFileInfoParams, GetFileInfoResult, - GetFlattenedDocumentParams, GetFlattenedDocumentResult, - GetFrameOwnerParams, GetFrameOwnerResult, - GetImplicitAnchorCandidatesParams, GetImplicitAnchorCandidatesResult, - GetNodeForLocationParams, GetNodeForLocationResult, - GetNodesForSubtreeByStyleParams, GetNodesForSubtreeByStyleResult, - GetNodeStackTracesParams, GetNodeStackTracesResult, - GetOuterHTMLParams, GetOuterHTMLResult, - GetQueryingDescendantsForContainerParams, GetQueryingDescendantsForContainerResult, - GetRelayoutBoundaryParams, GetRelayoutBoundaryResult, - GetSearchResultsParams, GetSearchResultsResult, GetTopLayerElementsResult, - MoveToParams, MoveToResult, - PerformSearchParams, PerformSearchResult, - PushNodeByPathToFrontendParams, PushNodeByPathToFrontendResult, - PushNodesByBackendIdsToFrontendParams, PushNodesByBackendIdsToFrontendResult, - QuerySelectorAllParams, QuerySelectorAllResult, - QuerySelectorParams, QuerySelectorResult, - RemoveAttributeParams, - RemoveNodeParams, - RequestChildNodesParams, - RequestNodeParams, RequestNodeResult, - ResolveNodeParams, ResolveNodeResult, - ScrollIntoViewIfNeededParams, - SetAttributesAsTextParams, - SetAttributeValueParams, - SetFileInputFilesParams, - SetInspectedNodeParams, - SetNodeNameParams, SetNodeNameResult, - SetNodeStackTracesEnabledParams, - SetNodeValueParams, - SetOuterHTMLParams, - SetTextMarkerParams, ) from .types import ( BackendNodeId, @@ -118,11 +71,12 @@ async def collect_class_names_from_subtree( """ Collects class names for the node with given id and all of it's child nodes. """ - params = CollectClassNamesFromSubtreeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.COLLECT_CLASS_NAMES_FROM_SUBTREE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CollectClassNamesFromSubtreeResult, result) @@ -137,15 +91,15 @@ async def copy_to( Creates a deep copy of the specified node and places it into the target container before the given anchor. """ - params = CopyToParams( - node_id=node_id, - target_node_id=target_node_id, - insert_before_node_id=insert_before_node_id, - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["targetNodeId"] = _encode_value(target_node_id) + if insert_before_node_id is not None: + _params["insertBeforeNodeId"] = _encode_value(insert_before_node_id) result = await self._executor.execute( method=DOMCommand.COPY_TO, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CopyToResult, result) @@ -162,17 +116,21 @@ async def describe_node( Describes node given its id, does not require domain to be enabled. Does not start tracking any objects, can be used for automation. """ - params = DescribeNodeParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - depth=depth, - pierce=pierce, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if depth is not None: + _params["depth"] = depth + if pierce is not None: + _params["pierce"] = pierce result = await self._executor.execute( method=DOMCommand.DESCRIBE_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(DescribeNodeResult, result) @@ -189,16 +147,19 @@ async def scroll_into_view_if_needed( Note: exactly one between nodeId, backendNodeId and objectId should be passed to identify the node. """ - params = ScrollIntoViewIfNeededParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - rect=rect, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if rect is not None: + _params["rect"] = _encode_value(rect) await self._executor.execute( method=DOMCommand.SCROLL_INTO_VIEW_IF_NEEDED, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -221,11 +182,12 @@ async def discard_search_results( Discards search results from the session with the given id. `getSearchResults` should no longer be called for that search. """ - params = DiscardSearchResultsParams(search_id=search_id) + _params: dict[str, Any] = {} + _params["searchId"] = search_id await self._executor.execute( method=DOMCommand.DISCARD_SEARCH_RESULTS, - params=encode_cdp(params), + params=_params, ) async def enable( @@ -236,11 +198,13 @@ async def enable( """ Enables DOM agent for the given page. """ - params = EnableParams(include_whitespace=include_whitespace) + _params: dict[str, Any] = {} + if include_whitespace is not None: + _params["includeWhitespace"] = include_whitespace await self._executor.execute( method=DOMCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def focus( @@ -253,13 +217,17 @@ async def focus( """ Focuses the given element. """ - params = FocusParams( - node_id=node_id, backend_node_id=backend_node_id, object_id=object_id - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) await self._executor.execute( method=DOMCommand.FOCUS, - params=encode_cdp(params), + params=_params, ) async def get_attributes( @@ -270,11 +238,12 @@ async def get_attributes( """ Returns attributes for the specified node. """ - params = GetAttributesParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.GET_ATTRIBUTES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAttributesResult, result) @@ -288,13 +257,17 @@ async def get_box_model( """ Returns boxes for the given node. """ - params = GetBoxModelParams( - node_id=node_id, backend_node_id=backend_node_id, object_id=object_id - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=DOMCommand.GET_BOX_MODEL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetBoxModelResult, result) @@ -309,13 +282,17 @@ async def get_content_quads( Returns quads that describe node position on the page. This method might return multiple quads for inline nodes. """ - params = GetContentQuadsParams( - node_id=node_id, backend_node_id=backend_node_id, object_id=object_id - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=DOMCommand.GET_CONTENT_QUADS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetContentQuadsResult, result) @@ -329,11 +306,15 @@ async def get_document( Returns the root DOM node (and optionally the subtree) to the caller. Implicitly enables the DOM domain events for the current target. """ - params = GetDocumentParams(depth=depth, pierce=pierce) + _params: dict[str, Any] = {} + if depth is not None: + _params["depth"] = depth + if pierce is not None: + _params["pierce"] = pierce result = await self._executor.execute( method=DOMCommand.GET_DOCUMENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetDocumentResult, result) @@ -349,11 +330,15 @@ async def get_flattened_document( Deprecated, as it is not designed to work well with the rest of the DOM agent. Use DOMSnapshot.captureSnapshot instead. """ - params = GetFlattenedDocumentParams(depth=depth, pierce=pierce) + _params: dict[str, Any] = {} + if depth is not None: + _params["depth"] = depth + if pierce is not None: + _params["pierce"] = pierce result = await self._executor.execute( method=DOMCommand.GET_FLATTENED_DOCUMENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetFlattenedDocumentResult, result) @@ -367,13 +352,15 @@ async def get_nodes_for_subtree_by_style( """ Finds nodes with a given computed style in a subtree. """ - params = GetNodesForSubtreeByStyleParams( - node_id=node_id, computed_styles=computed_styles, pierce=pierce - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["computedStyles"] = _encode_value(computed_styles) + if pierce is not None: + _params["pierce"] = pierce result = await self._executor.execute( method=DOMCommand.GET_NODES_FOR_SUBTREE_BY_STYLE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetNodesForSubtreeByStyleResult, result) @@ -389,16 +376,17 @@ async def get_node_for_location( Returns node id at given location. Depending on whether DOM domain is enabled, nodeId is either returned or not. """ - params = GetNodeForLocationParams( - x=x, - y=y, - include_user_agent_shadow_dom=include_user_agent_shadow_dom, - ignore_pointer_events_none=ignore_pointer_events_none, - ) + _params: dict[str, Any] = {} + _params["x"] = x + _params["y"] = y + if include_user_agent_shadow_dom is not None: + _params["includeUserAgentShadowDOM"] = include_user_agent_shadow_dom + if ignore_pointer_events_none is not None: + _params["ignorePointerEventsNone"] = ignore_pointer_events_none result = await self._executor.execute( method=DOMCommand.GET_NODE_FOR_LOCATION, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetNodeForLocationResult, result) @@ -413,16 +401,19 @@ async def get_outer_html( """ Returns node's HTML markup. """ - params = GetOuterHTMLParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - include_shadow_dom=include_shadow_dom, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if include_shadow_dom is not None: + _params["includeShadowDOM"] = include_shadow_dom result = await self._executor.execute( method=DOMCommand.GET_OUTER_HTML, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetOuterHTMLResult, result) @@ -434,11 +425,12 @@ async def get_relayout_boundary( """ Returns the id of the nearest ancestor that is a relayout boundary. """ - params = GetRelayoutBoundaryParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.GET_RELAYOUT_BOUNDARY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetRelayoutBoundaryResult, result) @@ -453,13 +445,14 @@ async def get_search_results( Returns search results from given `fromIndex` to given `toIndex` from the search with the given identifier. """ - params = GetSearchResultsParams( - search_id=search_id, from_index=from_index, to_index=to_index - ) + _params: dict[str, Any] = {} + _params["searchId"] = search_id + _params["fromIndex"] = from_index + _params["toIndex"] = to_index result = await self._executor.execute( method=DOMCommand.GET_SEARCH_RESULTS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetSearchResultsResult, result) @@ -517,15 +510,15 @@ async def move_to( """ Moves node into the new container, places it before the given anchor. """ - params = MoveToParams( - node_id=node_id, - target_node_id=target_node_id, - insert_before_node_id=insert_before_node_id, - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["targetNodeId"] = _encode_value(target_node_id) + if insert_before_node_id is not None: + _params["insertBeforeNodeId"] = _encode_value(insert_before_node_id) result = await self._executor.execute( method=DOMCommand.MOVE_TO, - params=encode_cdp(params), + params=_params, ) return decode_cdp(MoveToResult, result) @@ -539,13 +532,14 @@ async def perform_search( Searches for a given string in the DOM tree. Use `getSearchResults` to access search results or `cancelSearch` to end this search session. """ - params = PerformSearchParams( - query=query, include_user_agent_shadow_dom=include_user_agent_shadow_dom - ) + _params: dict[str, Any] = {} + _params["query"] = query + if include_user_agent_shadow_dom is not None: + _params["includeUserAgentShadowDOM"] = include_user_agent_shadow_dom result = await self._executor.execute( method=DOMCommand.PERFORM_SEARCH, - params=encode_cdp(params), + params=_params, ) return decode_cdp(PerformSearchResult, result) @@ -558,11 +552,12 @@ async def push_node_by_path_to_frontend( Requests that the node is sent to the caller given its path. // FIXME, use XPath """ - params = PushNodeByPathToFrontendParams(path=path) + _params: dict[str, Any] = {} + _params["path"] = path result = await self._executor.execute( method=DOMCommand.PUSH_NODE_BY_PATH_TO_FRONTEND, - params=encode_cdp(params), + params=_params, ) return decode_cdp(PushNodeByPathToFrontendResult, result) @@ -575,13 +570,12 @@ async def push_nodes_by_backend_ids_to_frontend( Requests that a batch of nodes is sent to the caller given their backend node ids. """ - params = PushNodesByBackendIdsToFrontendParams( - backend_node_ids=backend_node_ids - ) + _params: dict[str, Any] = {} + _params["backendNodeIds"] = _encode_value(backend_node_ids) result = await self._executor.execute( method=DOMCommand.PUSH_NODES_BY_BACKEND_IDS_TO_FRONTEND, - params=encode_cdp(params), + params=_params, ) return decode_cdp(PushNodesByBackendIdsToFrontendResult, result) @@ -594,11 +588,13 @@ async def query_selector( """ Executes `querySelector` on a given node. """ - params = QuerySelectorParams(node_id=node_id, selector=selector) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["selector"] = selector result = await self._executor.execute( method=DOMCommand.QUERY_SELECTOR, - params=encode_cdp(params), + params=_params, ) return decode_cdp(QuerySelectorResult, result) @@ -611,11 +607,13 @@ async def query_selector_all( """ Executes `querySelectorAll` on a given node. """ - params = QuerySelectorAllParams(node_id=node_id, selector=selector) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["selector"] = selector result = await self._executor.execute( method=DOMCommand.QUERY_SELECTOR_ALL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(QuerySelectorAllResult, result) @@ -642,11 +640,13 @@ async def get_element_by_relation( """ Returns the NodeId of the matched element according to certain relations. """ - params = GetElementByRelationParams(node_id=node_id, relation=relation) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["relation"] = relation result = await self._executor.execute( method=DOMCommand.GET_ELEMENT_BY_RELATION, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetElementByRelationResult, result) @@ -670,11 +670,13 @@ async def remove_attribute( """ Removes attribute with given name from an element with given id. """ - params = RemoveAttributeParams(node_id=node_id, name=name) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["name"] = name await self._executor.execute( method=DOMCommand.REMOVE_ATTRIBUTE, - params=encode_cdp(params), + params=_params, ) async def remove_node( @@ -685,11 +687,12 @@ async def remove_node( """ Removes node with given id. """ - params = RemoveNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) await self._executor.execute( method=DOMCommand.REMOVE_NODE, - params=encode_cdp(params), + params=_params, ) async def request_child_nodes( @@ -704,11 +707,16 @@ async def request_child_nodes( form of `setChildNodes` events where not only immediate children are retrieved, but all children down to the specified depth. """ - params = RequestChildNodesParams(node_id=node_id, depth=depth, pierce=pierce) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + if depth is not None: + _params["depth"] = depth + if pierce is not None: + _params["pierce"] = pierce await self._executor.execute( method=DOMCommand.REQUEST_CHILD_NODES, - params=encode_cdp(params), + params=_params, ) async def request_node( @@ -721,11 +729,12 @@ async def request_node( reference. All nodes that form the path from the node to the root are also sent to the client as a series of `setChildNodes` notifications. """ - params = RequestNodeParams(object_id=object_id) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=DOMCommand.REQUEST_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestNodeResult, result) @@ -740,16 +749,19 @@ async def resolve_node( """ Resolves the JavaScript node object for a given NodeId or BackendNodeId. """ - params = ResolveNodeParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_group=object_group, - execution_context_id=execution_context_id, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_group is not None: + _params["objectGroup"] = object_group + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) result = await self._executor.execute( method=DOMCommand.RESOLVE_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ResolveNodeResult, result) @@ -763,11 +775,14 @@ async def set_attribute_value( """ Sets attribute for an element with given id. """ - params = SetAttributeValueParams(node_id=node_id, name=name, value=value) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["name"] = name + _params["value"] = value await self._executor.execute( method=DOMCommand.SET_ATTRIBUTE_VALUE, - params=encode_cdp(params), + params=_params, ) async def set_attributes_as_text( @@ -781,11 +796,15 @@ async def set_attributes_as_text( Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs. """ - params = SetAttributesAsTextParams(node_id=node_id, text=text, name=name) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["text"] = text + if name is not None: + _params["name"] = name await self._executor.execute( method=DOMCommand.SET_ATTRIBUTES_AS_TEXT, - params=encode_cdp(params), + params=_params, ) async def set_file_input_files( @@ -799,16 +818,18 @@ async def set_file_input_files( """ Sets files for the given file input element. """ - params = SetFileInputFilesParams( - files=files, - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - ) + _params: dict[str, Any] = {} + _params["files"] = _encode_value(files) + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) await self._executor.execute( method=DOMCommand.SET_FILE_INPUT_FILES, - params=encode_cdp(params), + params=_params, ) async def set_node_stack_traces_enabled( @@ -820,11 +841,12 @@ async def set_node_stack_traces_enabled( Sets if stack traces should be captured for Nodes. See `Node.getNodeStackTraces`. Default is disabled. """ - params = SetNodeStackTracesEnabledParams(enable=enable) + _params: dict[str, Any] = {} + _params["enable"] = enable await self._executor.execute( method=DOMCommand.SET_NODE_STACK_TRACES_ENABLED, - params=encode_cdp(params), + params=_params, ) async def get_node_stack_traces( @@ -836,11 +858,12 @@ async def get_node_stack_traces( Gets stack traces associated with a Node. As of now, only provides stack trace for Node creation. """ - params = GetNodeStackTracesParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.GET_NODE_STACK_TRACES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetNodeStackTracesResult, result) @@ -852,11 +875,12 @@ async def get_file_info( """ Returns file information for the given File wrapper. """ - params = GetFileInfoParams(object_id=object_id) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=DOMCommand.GET_FILE_INFO, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetFileInfoResult, result) @@ -881,11 +905,12 @@ async def set_inspected_node( Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). """ - params = SetInspectedNodeParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) await self._executor.execute( method=DOMCommand.SET_INSPECTED_NODE, - params=encode_cdp(params), + params=_params, ) async def set_node_name( @@ -897,11 +922,13 @@ async def set_node_name( """ Sets node name for a node with given id. """ - params = SetNodeNameParams(node_id=node_id, name=name) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["name"] = name result = await self._executor.execute( method=DOMCommand.SET_NODE_NAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetNodeNameResult, result) @@ -914,11 +941,13 @@ async def set_node_value( """ Sets node value for a node with given id. """ - params = SetNodeValueParams(node_id=node_id, value=value) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["value"] = value await self._executor.execute( method=DOMCommand.SET_NODE_VALUE, - params=encode_cdp(params), + params=_params, ) async def set_outer_html( @@ -930,11 +959,13 @@ async def set_outer_html( """ Sets node HTML markup, returns new node id. """ - params = SetOuterHTMLParams(node_id=node_id, outer_html=outer_html) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["outerHTML"] = outer_html await self._executor.execute( method=DOMCommand.SET_OUTER_HTML, - params=encode_cdp(params), + params=_params, ) async def undo( @@ -956,11 +987,12 @@ async def get_frame_owner( """ Returns iframe node that owns iframe with the given domain. """ - params = GetFrameOwnerParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=DOMCommand.GET_FRAME_OWNER, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetFrameOwnerResult, result) @@ -981,18 +1013,22 @@ async def get_container_for_node( queriesScrollState is false, the style container is returned, which is the direct parent or the closest element with a matching container-name. """ - params = GetContainerForNodeParams( - node_id=node_id, - container_name=container_name, - physical_axes=physical_axes, - logical_axes=logical_axes, - queries_scroll_state=queries_scroll_state, - queries_anchored=queries_anchored, - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + if container_name is not None: + _params["containerName"] = container_name + if physical_axes is not None: + _params["physicalAxes"] = _encode_value(physical_axes) + if logical_axes is not None: + _params["logicalAxes"] = _encode_value(logical_axes) + if queries_scroll_state is not None: + _params["queriesScrollState"] = queries_scroll_state + if queries_anchored is not None: + _params["queriesAnchored"] = queries_anchored result = await self._executor.execute( method=DOMCommand.GET_CONTAINER_FOR_NODE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetContainerForNodeResult, result) @@ -1005,11 +1041,12 @@ async def get_querying_descendants_for_container( Returns the descendants of a container query container that have container queries against this container. """ - params = GetQueryingDescendantsForContainerParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.GET_QUERYING_DESCENDANTS_FOR_CONTAINER, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetQueryingDescendantsForContainerResult, result) @@ -1023,13 +1060,14 @@ async def get_anchor_element( Returns the target anchor element of the given anchor query according to https://www.w3.org/TR/css-anchor-position-1/#target. """ - params = GetAnchorElementParams( - node_id=node_id, anchor_specifier=anchor_specifier - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + if anchor_specifier is not None: + _params["anchorSpecifier"] = anchor_specifier result = await self._executor.execute( method=DOMCommand.GET_ANCHOR_ELEMENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAnchorElementResult, result) @@ -1044,13 +1082,15 @@ async def force_show_popover( When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled. """ - params = ForceShowPopoverParams( - node_id=node_id, enable=enable, invoker_node_id=invoker_node_id - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["enable"] = enable + if invoker_node_id is not None: + _params["invokerNodeId"] = _encode_value(invoker_node_id) result = await self._executor.execute( method=DOMCommand.FORCE_SHOW_POPOVER, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ForceShowPopoverResult, result) @@ -1062,11 +1102,12 @@ async def get_implicit_anchor_candidates( """ Returns candidate nodes that are configured as triggers for the given popover. """ - params = GetImplicitAnchorCandidatesParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=DOMCommand.GET_IMPLICIT_ANCHOR_CANDIDATES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetImplicitAnchorCandidatesResult, result) @@ -1080,11 +1121,13 @@ async def force_show_interest( When enabling, this API forces an element to gain interest in its target, keeping interest active until disabled. """ - params = ForceShowInterestParams(node_id=node_id, enable=enable) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["enable"] = enable await self._executor.execute( method=DOMCommand.FORCE_SHOW_INTEREST, - params=encode_cdp(params), + params=_params, ) async def set_text_marker( @@ -1103,18 +1146,20 @@ async def set_text_marker( Note: exactly one between nodeId, backendNodeId and objectId should be passed to identify the node. """ - params = SetTextMarkerParams( - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - type=type, - start=start, - end=end, - ) + _params: dict[str, Any] = {} + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + _params["type"] = type + _params["start"] = start + _params["end"] = end await self._executor.execute( method=DOMCommand.SET_TEXT_MARKER, - params=encode_cdp(params), + params=_params, ) async def clear_text_markers( diff --git a/cdpify/domains/domdebugger/domain.py b/cdpify/domains/domdebugger/domain.py index 00385167..c82f48ab 100644 --- a/cdpify/domains/domdebugger/domain.py +++ b/cdpify/domains/domdebugger/domain.py @@ -4,25 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( DOMDebuggerCommand, - GetEventListenersParams, GetEventListenersResult, - RemoveDOMBreakpointParams, - RemoveEventListenerBreakpointParams, - RemoveInstrumentationBreakpointParams, - RemoveXHRBreakpointParams, - SetBreakOnCSPViolationParams, - SetDOMBreakpointParams, - SetEventListenerBreakpointParams, - SetInstrumentationBreakpointParams, - SetXHRBreakpointParams, ) from .types import ( CSPViolationType, @@ -47,13 +37,16 @@ async def get_event_listeners( """ Returns event listeners of the given object. """ - params = GetEventListenersParams( - object_id=object_id, depth=depth, pierce=pierce - ) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) + if depth is not None: + _params["depth"] = depth + if pierce is not None: + _params["pierce"] = pierce result = await self._executor.execute( method=DOMDebuggerCommand.GET_EVENT_LISTENERS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetEventListenersResult, result) @@ -66,11 +59,13 @@ async def remove_dom_breakpoint( """ Removes DOM breakpoint that was set using `setDOMBreakpoint`. """ - params = RemoveDOMBreakpointParams(node_id=node_id, type=type) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["type"] = _encode_value(type) await self._executor.execute( method=DOMDebuggerCommand.REMOVE_DOM_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def remove_event_listener_breakpoint( @@ -82,13 +77,14 @@ async def remove_event_listener_breakpoint( """ Removes breakpoint on particular DOM event. """ - params = RemoveEventListenerBreakpointParams( - event_name=event_name, target_name=target_name - ) + _params: dict[str, Any] = {} + _params["eventName"] = event_name + if target_name is not None: + _params["targetName"] = target_name await self._executor.execute( method=DOMDebuggerCommand.REMOVE_EVENT_LISTENER_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) @deprecated("DOMDebugger.removeInstrumentationBreakpoint is deprecated") @@ -100,11 +96,12 @@ async def remove_instrumentation_breakpoint( """ Removes breakpoint on particular native event. """ - params = RemoveInstrumentationBreakpointParams(event_name=event_name) + _params: dict[str, Any] = {} + _params["eventName"] = event_name await self._executor.execute( method=DOMDebuggerCommand.REMOVE_INSTRUMENTATION_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def remove_xhr_breakpoint( @@ -115,11 +112,12 @@ async def remove_xhr_breakpoint( """ Removes breakpoint from XMLHttpRequest. """ - params = RemoveXHRBreakpointParams(url=url) + _params: dict[str, Any] = {} + _params["url"] = url await self._executor.execute( method=DOMDebuggerCommand.REMOVE_XHR_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def set_break_on_csp_violation( @@ -130,11 +128,12 @@ async def set_break_on_csp_violation( """ Sets breakpoint on particular CSP violations. """ - params = SetBreakOnCSPViolationParams(violation_types=violation_types) + _params: dict[str, Any] = {} + _params["violationTypes"] = _encode_value(violation_types) await self._executor.execute( method=DOMDebuggerCommand.SET_BREAK_ON_CSP_VIOLATION, - params=encode_cdp(params), + params=_params, ) async def set_dom_breakpoint( @@ -146,11 +145,13 @@ async def set_dom_breakpoint( """ Sets breakpoint on particular operation with DOM. """ - params = SetDOMBreakpointParams(node_id=node_id, type=type) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + _params["type"] = _encode_value(type) await self._executor.execute( method=DOMDebuggerCommand.SET_DOM_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def set_event_listener_breakpoint( @@ -162,13 +163,14 @@ async def set_event_listener_breakpoint( """ Sets breakpoint on particular DOM event. """ - params = SetEventListenerBreakpointParams( - event_name=event_name, target_name=target_name - ) + _params: dict[str, Any] = {} + _params["eventName"] = event_name + if target_name is not None: + _params["targetName"] = target_name await self._executor.execute( method=DOMDebuggerCommand.SET_EVENT_LISTENER_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) @deprecated("DOMDebugger.setInstrumentationBreakpoint is deprecated") @@ -180,11 +182,12 @@ async def set_instrumentation_breakpoint( """ Sets breakpoint on particular native event. """ - params = SetInstrumentationBreakpointParams(event_name=event_name) + _params: dict[str, Any] = {} + _params["eventName"] = event_name await self._executor.execute( method=DOMDebuggerCommand.SET_INSTRUMENTATION_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def set_xhr_breakpoint( @@ -195,9 +198,10 @@ async def set_xhr_breakpoint( """ Sets breakpoint on XMLHttpRequest. """ - params = SetXHRBreakpointParams(url=url) + _params: dict[str, Any] = {} + _params["url"] = url await self._executor.execute( method=DOMDebuggerCommand.SET_XHR_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/domsnapshot/domain.py b/cdpify/domains/domsnapshot/domain.py index ae3008b8..940897c9 100644 --- a/cdpify/domains/domsnapshot/domain.py +++ b/cdpify/domains/domsnapshot/domain.py @@ -4,15 +4,15 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - CaptureSnapshotParams, CaptureSnapshotResult, DOMSnapshotCommand, - GetSnapshotParams, GetSnapshotResult, ) @@ -58,16 +58,18 @@ async def get_snapshot( array, as well as layout and white-listed computed style information for the nodes. Shadow DOM in the returned DOM tree is flattened. """ - params = GetSnapshotParams( - computed_style_whitelist=computed_style_whitelist, - include_event_listeners=include_event_listeners, - include_paint_order=include_paint_order, - include_user_agent_shadow_tree=include_user_agent_shadow_tree, - ) + _params: dict[str, Any] = {} + _params["computedStyleWhitelist"] = _encode_value(computed_style_whitelist) + if include_event_listeners is not None: + _params["includeEventListeners"] = include_event_listeners + if include_paint_order is not None: + _params["includePaintOrder"] = include_paint_order + if include_user_agent_shadow_tree is not None: + _params["includeUserAgentShadowTree"] = include_user_agent_shadow_tree result = await self._executor.execute( method=DOMSnapshotCommand.GET_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetSnapshotResult, result) @@ -86,16 +88,21 @@ async def capture_snapshot( array, as well as layout and white-listed computed style information for the nodes. Shadow DOM in the returned DOM tree is flattened. """ - params = CaptureSnapshotParams( - computed_styles=computed_styles, - include_paint_order=include_paint_order, - include_dom_rects=include_dom_rects, - include_blended_background_colors=include_blended_background_colors, - include_text_color_opacities=include_text_color_opacities, - ) + _params: dict[str, Any] = {} + _params["computedStyles"] = _encode_value(computed_styles) + if include_paint_order is not None: + _params["includePaintOrder"] = include_paint_order + if include_dom_rects is not None: + _params["includeDOMRects"] = include_dom_rects + if include_blended_background_colors is not None: + _params["includeBlendedBackgroundColors"] = ( + include_blended_background_colors + ) + if include_text_color_opacities is not None: + _params["includeTextColorOpacities"] = include_text_color_opacities result = await self._executor.execute( method=DOMSnapshotCommand.CAPTURE_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CaptureSnapshotResult, result) diff --git a/cdpify/domains/domstorage/domain.py b/cdpify/domains/domstorage/domain.py index 756eb971..f6520407 100644 --- a/cdpify/domains/domstorage/domain.py +++ b/cdpify/domains/domstorage/domain.py @@ -4,16 +4,14 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - ClearParams, DOMStorageCommand, - GetDOMStorageItemsParams, GetDOMStorageItemsResult, - RemoveDOMStorageItemParams, - SetDOMStorageItemParams, ) from .types import ( StorageId, @@ -29,11 +27,12 @@ async def clear( *, storage_id: StorageId, ) -> None: - params = ClearParams(storage_id=storage_id) + _params: dict[str, Any] = {} + _params["storageId"] = _encode_value(storage_id) await self._executor.execute( method=DOMStorageCommand.CLEAR, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -64,11 +63,12 @@ async def get_dom_storage_items( *, storage_id: StorageId, ) -> GetDOMStorageItemsResult: - params = GetDOMStorageItemsParams(storage_id=storage_id) + _params: dict[str, Any] = {} + _params["storageId"] = _encode_value(storage_id) result = await self._executor.execute( method=DOMStorageCommand.GET_DOM_STORAGE_ITEMS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetDOMStorageItemsResult, result) @@ -78,11 +78,13 @@ async def remove_dom_storage_item( storage_id: StorageId, key: str, ) -> None: - params = RemoveDOMStorageItemParams(storage_id=storage_id, key=key) + _params: dict[str, Any] = {} + _params["storageId"] = _encode_value(storage_id) + _params["key"] = key await self._executor.execute( method=DOMStorageCommand.REMOVE_DOM_STORAGE_ITEM, - params=encode_cdp(params), + params=_params, ) async def set_dom_storage_item( @@ -92,9 +94,12 @@ async def set_dom_storage_item( key: str, value: str, ) -> None: - params = SetDOMStorageItemParams(storage_id=storage_id, key=key, value=value) + _params: dict[str, Any] = {} + _params["storageId"] = _encode_value(storage_id) + _params["key"] = key + _params["value"] = value await self._executor.execute( method=DOMStorageCommand.SET_DOM_STORAGE_ITEM, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/emulation/domain.py b/cdpify/domains/emulation/domain.py index a1e31d34..35991f5c 100644 --- a/cdpify/domains/emulation/domain.py +++ b/cdpify/domains/emulation/domain.py @@ -4,60 +4,19 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - AddScreenParams, AddScreenResult, CanEmulateResult, EmulationCommand, - GetOverriddenSensorInformationParams, GetOverriddenSensorInformationResult, GetScreenInfosResult, - RemoveScreenParams, - SetAutoDarkModeOverrideParams, - SetAutomationOverrideParams, - SetCPUPerformanceOverrideParams, - SetCPUThrottlingRateParams, - SetDataSaverOverrideParams, - SetDefaultBackgroundColorOverrideParams, - SetDeviceMetricsOverrideParams, - SetDevicePostureOverrideParams, - SetDisabledImageTypesParams, - SetDisplayFeaturesOverrideParams, - SetDocumentCookieDisabledParams, - SetEmitTouchEventsForMouseParams, - SetEmulatedMediaParams, - SetEmulatedOSTextScaleParams, - SetEmulatedVisionDeficiencyParams, - SetFocusEmulationEnabledParams, - SetGeolocationOverrideParams, - SetHardwareConcurrencyOverrideParams, - SetIdleOverrideParams, - SetLocaleOverrideParams, - SetNavigatorOverridesParams, - SetPageScaleFactorParams, - SetPressureSourceOverrideEnabledParams, - SetPressureStateOverrideParams, - SetPrimaryScreenParams, - SetSafeAreaInsetsOverrideParams, - SetScriptExecutionDisabledParams, - SetScrollbarsHiddenParams, - SetSensorOverrideEnabledParams, - SetSensorOverrideReadingsParams, - SetSmallViewportHeightDifferenceOverrideParams, - SetTimezoneOverrideParams, - SetTouchEmulationEnabledParams, - SetUserAgentOverrideParams, - SetVirtualKeyboardGeometryOverrideParams, - SetVirtualTimePolicyParams, SetVirtualTimePolicyResult, - SetVisibleSizeParams, - UpdateScreenParams, UpdateScreenResult, ) from .types import ( @@ -141,11 +100,12 @@ async def set_focus_emulation_enabled( """ Enables or disables simulating a focused and active page. """ - params = SetFocusEmulationEnabledParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=EmulationCommand.SET_FOCUS_EMULATION_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_auto_dark_mode_override( @@ -156,11 +116,13 @@ async def set_auto_dark_mode_override( """ Automatically render all web contents using a dark theme. """ - params = SetAutoDarkModeOverrideParams(enabled=enabled) + _params: dict[str, Any] = {} + if enabled is not None: + _params["enabled"] = enabled await self._executor.execute( method=EmulationCommand.SET_AUTO_DARK_MODE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_cpu_throttling_rate( @@ -171,11 +133,12 @@ async def set_cpu_throttling_rate( """ Enables CPU throttling to emulate slow CPUs. """ - params = SetCPUThrottlingRateParams(rate=rate) + _params: dict[str, Any] = {} + _params["rate"] = rate await self._executor.execute( method=EmulationCommand.SET_CPU_THROTTLING_RATE, - params=encode_cdp(params), + params=_params, ) async def set_default_background_color_override( @@ -187,11 +150,13 @@ async def set_default_background_color_override( Sets or clears an override of the default background color of the frame. This override is used if the content does not specify one. """ - params = SetDefaultBackgroundColorOverrideParams(color=color) + _params: dict[str, Any] = {} + if color is not None: + _params["color"] = _encode_value(color) await self._executor.execute( method=EmulationCommand.SET_DEFAULT_BACKGROUND_COLOR_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_safe_area_insets_override( @@ -204,11 +169,12 @@ async def set_safe_area_insets_override( Unset values will cause the respective variables to be undefined, even if previously overridden. """ - params = SetSafeAreaInsetsOverrideParams(insets=insets) + _params: dict[str, Any] = {} + _params["insets"] = _encode_value(insets) await self._executor.execute( method=EmulationCommand.SET_SAFE_AREA_INSETS_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_virtual_keyboard_geometry_override( @@ -224,11 +190,13 @@ async def set_virtual_keyboard_geometry_override( so clients can preview overlay geometry without mutating page state. Values are rounded to the nearest CSS pixel. Omitting the rect clears the override. """ - params = SetVirtualKeyboardGeometryOverrideParams(keyboard_rect=keyboard_rect) + _params: dict[str, Any] = {} + if keyboard_rect is not None: + _params["keyboardRect"] = _encode_value(keyboard_rect) await self._executor.execute( method=EmulationCommand.SET_VIRTUAL_KEYBOARD_GEOMETRY_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_device_metrics_override( @@ -258,30 +226,45 @@ async def set_device_metrics_override( window.screen.height, window.innerWidth, window.innerHeight, and "device-width"/"device-height"-related CSS media query results). """ - params = SetDeviceMetricsOverrideParams( - width=width, - height=height, - device_scale_factor=device_scale_factor, - mobile=mobile, - scale=scale, - screen_width=screen_width, - screen_height=screen_height, - position_x=position_x, - position_y=position_y, - dont_set_visible_size=dont_set_visible_size, - screen_orientation=screen_orientation, - viewport=viewport, - display_feature=display_feature, - device_posture=device_posture, - scrollbar_type=scrollbar_type, - screen_orientation_lock_emulation=screen_orientation_lock_emulation, - viewport_meta=viewport_meta, - text_layout_mode=text_layout_mode, - ) + _params: dict[str, Any] = {} + _params["width"] = width + _params["height"] = height + _params["deviceScaleFactor"] = device_scale_factor + _params["mobile"] = mobile + if scale is not None: + _params["scale"] = scale + if screen_width is not None: + _params["screenWidth"] = screen_width + if screen_height is not None: + _params["screenHeight"] = screen_height + if position_x is not None: + _params["positionX"] = position_x + if position_y is not None: + _params["positionY"] = position_y + if dont_set_visible_size is not None: + _params["dontSetVisibleSize"] = dont_set_visible_size + if screen_orientation is not None: + _params["screenOrientation"] = _encode_value(screen_orientation) + if viewport is not None: + _params["viewport"] = _encode_value(viewport) + if display_feature is not None: + _params["displayFeature"] = _encode_value(display_feature) + if device_posture is not None: + _params["devicePosture"] = _encode_value(device_posture) + if scrollbar_type is not None: + _params["scrollbarType"] = scrollbar_type + if screen_orientation_lock_emulation is not None: + _params["screenOrientationLockEmulation"] = ( + screen_orientation_lock_emulation + ) + if viewport_meta is not None: + _params["viewportMeta"] = viewport_meta + if text_layout_mode is not None: + _params["textLayoutMode"] = text_layout_mode await self._executor.execute( method=EmulationCommand.SET_DEVICE_METRICS_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_device_posture_override( @@ -293,11 +276,12 @@ async def set_device_posture_override( Start reporting the given posture value to the Device Posture API. This override can also be set in setDeviceMetricsOverride(). """ - params = SetDevicePostureOverrideParams(posture=posture) + _params: dict[str, Any] = {} + _params["posture"] = _encode_value(posture) await self._executor.execute( method=EmulationCommand.SET_DEVICE_POSTURE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def clear_device_posture_override( @@ -322,11 +306,12 @@ async def set_display_features_override( Start using the given display features to pupulate the Viewport Segments API. This override can also be set in setDeviceMetricsOverride(). """ - params = SetDisplayFeaturesOverrideParams(features=features) + _params: dict[str, Any] = {} + _params["features"] = _encode_value(features) await self._executor.execute( method=EmulationCommand.SET_DISPLAY_FEATURES_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def clear_display_features_override( @@ -347,11 +332,12 @@ async def set_scrollbars_hidden( *, hidden: bool, ) -> None: - params = SetScrollbarsHiddenParams(hidden=hidden) + _params: dict[str, Any] = {} + _params["hidden"] = hidden await self._executor.execute( method=EmulationCommand.SET_SCROLLBARS_HIDDEN, - params=encode_cdp(params), + params=_params, ) async def set_document_cookie_disabled( @@ -359,11 +345,12 @@ async def set_document_cookie_disabled( *, disabled: bool, ) -> None: - params = SetDocumentCookieDisabledParams(disabled=disabled) + _params: dict[str, Any] = {} + _params["disabled"] = disabled await self._executor.execute( method=EmulationCommand.SET_DOCUMENT_COOKIE_DISABLED, - params=encode_cdp(params), + params=_params, ) async def set_emit_touch_events_for_mouse( @@ -372,13 +359,14 @@ async def set_emit_touch_events_for_mouse( enabled: bool, configuration: Literal["mobile", "desktop"] | None = None, ) -> None: - params = SetEmitTouchEventsForMouseParams( - enabled=enabled, configuration=configuration - ) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + if configuration is not None: + _params["configuration"] = configuration await self._executor.execute( method=EmulationCommand.SET_EMIT_TOUCH_EVENTS_FOR_MOUSE, - params=encode_cdp(params), + params=_params, ) async def set_emulated_media( @@ -390,11 +378,15 @@ async def set_emulated_media( """ Emulates the given media type or media feature for CSS media queries. """ - params = SetEmulatedMediaParams(media=media, features=features) + _params: dict[str, Any] = {} + if media is not None: + _params["media"] = media + if features is not None: + _params["features"] = _encode_value(features) await self._executor.execute( method=EmulationCommand.SET_EMULATED_MEDIA, - params=encode_cdp(params), + params=_params, ) async def set_emulated_vision_deficiency( @@ -413,11 +405,12 @@ async def set_emulated_vision_deficiency( """ Emulates the given vision deficiency. """ - params = SetEmulatedVisionDeficiencyParams(type=type) + _params: dict[str, Any] = {} + _params["type"] = type await self._executor.execute( method=EmulationCommand.SET_EMULATED_VISION_DEFICIENCY, - params=encode_cdp(params), + params=_params, ) async def set_emulated_os_text_scale( @@ -428,11 +421,13 @@ async def set_emulated_os_text_scale( """ Emulates the given OS text scale. """ - params = SetEmulatedOSTextScaleParams(scale=scale) + _params: dict[str, Any] = {} + if scale is not None: + _params["scale"] = scale await self._executor.execute( method=EmulationCommand.SET_EMULATED_OS_TEXT_SCALE, - params=encode_cdp(params), + params=_params, ) async def set_geolocation_override( @@ -450,19 +445,25 @@ async def set_geolocation_override( Overrides the Geolocation Position or Error. Omitting latitude, longitude or accuracy emulates position unavailable. """ - params = SetGeolocationOverrideParams( - latitude=latitude, - longitude=longitude, - accuracy=accuracy, - altitude=altitude, - altitude_accuracy=altitude_accuracy, - heading=heading, - speed=speed, - ) + _params: dict[str, Any] = {} + if latitude is not None: + _params["latitude"] = latitude + if longitude is not None: + _params["longitude"] = longitude + if accuracy is not None: + _params["accuracy"] = accuracy + if altitude is not None: + _params["altitude"] = altitude + if altitude_accuracy is not None: + _params["altitudeAccuracy"] = altitude_accuracy + if heading is not None: + _params["heading"] = heading + if speed is not None: + _params["speed"] = speed await self._executor.execute( method=EmulationCommand.SET_GEOLOCATION_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def get_overridden_sensor_information( @@ -470,11 +471,12 @@ async def get_overridden_sensor_information( *, type: SensorType, ) -> GetOverriddenSensorInformationResult: - params = GetOverriddenSensorInformationParams(type=type) + _params: dict[str, Any] = {} + _params["type"] = _encode_value(type) result = await self._executor.execute( method=EmulationCommand.GET_OVERRIDDEN_SENSOR_INFORMATION, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetOverriddenSensorInformationResult, result) @@ -492,13 +494,15 @@ async def set_sensor_override_enabled( objects will fire an error event and new calls to Sensor.start() will attempt to use a real sensor instead. """ - params = SetSensorOverrideEnabledParams( - enabled=enabled, type=type, metadata=metadata - ) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + _params["type"] = _encode_value(type) + if metadata is not None: + _params["metadata"] = _encode_value(metadata) await self._executor.execute( method=EmulationCommand.SET_SENSOR_OVERRIDE_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_sensor_override_readings( @@ -511,11 +515,13 @@ async def set_sensor_override_readings( Updates the sensor readings reported by a sensor type previously overridden by setSensorOverrideEnabled. """ - params = SetSensorOverrideReadingsParams(type=type, reading=reading) + _params: dict[str, Any] = {} + _params["type"] = _encode_value(type) + _params["reading"] = _encode_value(reading) await self._executor.execute( method=EmulationCommand.SET_SENSOR_OVERRIDE_READINGS, - params=encode_cdp(params), + params=_params, ) async def set_pressure_source_override_enabled( @@ -531,13 +537,15 @@ async def set_pressure_source_override_enabled( setPressureStateOverride instead of being retrieved from platform-provided telemetry data. """ - params = SetPressureSourceOverrideEnabledParams( - enabled=enabled, source=source, metadata=metadata - ) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + _params["source"] = _encode_value(source) + if metadata is not None: + _params["metadata"] = _encode_value(metadata) await self._executor.execute( method=EmulationCommand.SET_PRESSURE_SOURCE_OVERRIDE_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_pressure_state_override( @@ -551,11 +559,13 @@ async def set_pressure_state_override( delivered to PressureObserver users. |source| must have been previously overridden by setPressureSourceOverrideEnabled. """ - params = SetPressureStateOverrideParams(source=source, state=state) + _params: dict[str, Any] = {} + _params["source"] = _encode_value(source) + _params["state"] = _encode_value(state) await self._executor.execute( method=EmulationCommand.SET_PRESSURE_STATE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_idle_override( @@ -567,13 +577,13 @@ async def set_idle_override( """ Overrides the Idle state. """ - params = SetIdleOverrideParams( - is_user_active=is_user_active, is_screen_unlocked=is_screen_unlocked - ) + _params: dict[str, Any] = {} + _params["isUserActive"] = is_user_active + _params["isScreenUnlocked"] = is_screen_unlocked await self._executor.execute( method=EmulationCommand.SET_IDLE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def clear_idle_override( @@ -596,11 +606,12 @@ async def set_navigator_overrides( """ Overrides value returned by the javascript navigator object. """ - params = SetNavigatorOverridesParams(platform=platform) + _params: dict[str, Any] = {} + _params["platform"] = platform await self._executor.execute( method=EmulationCommand.SET_NAVIGATOR_OVERRIDES, - params=encode_cdp(params), + params=_params, ) async def set_page_scale_factor( @@ -611,11 +622,12 @@ async def set_page_scale_factor( """ Sets a specified page scale factor. """ - params = SetPageScaleFactorParams(page_scale_factor=page_scale_factor) + _params: dict[str, Any] = {} + _params["pageScaleFactor"] = page_scale_factor await self._executor.execute( method=EmulationCommand.SET_PAGE_SCALE_FACTOR, - params=encode_cdp(params), + params=_params, ) async def set_script_execution_disabled( @@ -626,11 +638,12 @@ async def set_script_execution_disabled( """ Switches script execution in the page. """ - params = SetScriptExecutionDisabledParams(value=value) + _params: dict[str, Any] = {} + _params["value"] = value await self._executor.execute( method=EmulationCommand.SET_SCRIPT_EXECUTION_DISABLED, - params=encode_cdp(params), + params=_params, ) async def set_touch_emulation_enabled( @@ -642,13 +655,14 @@ async def set_touch_emulation_enabled( """ Enables touch on platforms which do not support them. """ - params = SetTouchEmulationEnabledParams( - enabled=enabled, max_touch_points=max_touch_points - ) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + if max_touch_points is not None: + _params["maxTouchPoints"] = max_touch_points await self._executor.execute( method=EmulationCommand.SET_TOUCH_EMULATION_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_virtual_time_policy( @@ -664,16 +678,20 @@ async def set_virtual_time_policy( source) and sets the current virtual time policy. Note this supersedes any previous time budget. """ - params = SetVirtualTimePolicyParams( - policy=policy, - budget=budget, - max_virtual_time_task_starvation_count=max_virtual_time_task_starvation_count, - initial_virtual_time=initial_virtual_time, - ) + _params: dict[str, Any] = {} + _params["policy"] = _encode_value(policy) + if budget is not None: + _params["budget"] = budget + if max_virtual_time_task_starvation_count is not None: + _params["maxVirtualTimeTaskStarvationCount"] = ( + max_virtual_time_task_starvation_count + ) + if initial_virtual_time is not None: + _params["initialVirtualTime"] = _encode_value(initial_virtual_time) result = await self._executor.execute( method=EmulationCommand.SET_VIRTUAL_TIME_POLICY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetVirtualTimePolicyResult, result) @@ -685,11 +703,13 @@ async def set_locale_override( """ Overrides default host system locale with the specified one. """ - params = SetLocaleOverrideParams(locale=locale) + _params: dict[str, Any] = {} + if locale is not None: + _params["locale"] = locale await self._executor.execute( method=EmulationCommand.SET_LOCALE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_timezone_override( @@ -700,11 +720,12 @@ async def set_timezone_override( """ Overrides default host system timezone with the specified one. """ - params = SetTimezoneOverrideParams(timezone_id=timezone_id) + _params: dict[str, Any] = {} + _params["timezoneId"] = timezone_id await self._executor.execute( method=EmulationCommand.SET_TIMEZONE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) @deprecated("Emulation.setVisibleSize is deprecated") @@ -719,11 +740,13 @@ async def set_visible_size( frame's container (e.g. browser window). Can be used to produce screenshots of the specified size. Not supported on Android. """ - params = SetVisibleSizeParams(width=width, height=height) + _params: dict[str, Any] = {} + _params["width"] = width + _params["height"] = height await self._executor.execute( method=EmulationCommand.SET_VISIBLE_SIZE, - params=encode_cdp(params), + params=_params, ) async def set_disabled_image_types( @@ -731,11 +754,12 @@ async def set_disabled_image_types( *, image_types: list[DisabledImageType], ) -> None: - params = SetDisabledImageTypesParams(image_types=image_types) + _params: dict[str, Any] = {} + _params["imageTypes"] = _encode_value(image_types) await self._executor.execute( method=EmulationCommand.SET_DISABLED_IMAGE_TYPES, - params=encode_cdp(params), + params=_params, ) async def set_data_saver_override( @@ -746,11 +770,13 @@ async def set_data_saver_override( """ Override the value of navigator.connection.saveData """ - params = SetDataSaverOverrideParams(data_saver_enabled=data_saver_enabled) + _params: dict[str, Any] = {} + if data_saver_enabled is not None: + _params["dataSaverEnabled"] = data_saver_enabled await self._executor.execute( method=EmulationCommand.SET_DATA_SAVER_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_hardware_concurrency_override( @@ -758,13 +784,12 @@ async def set_hardware_concurrency_override( *, hardware_concurrency: int, ) -> None: - params = SetHardwareConcurrencyOverrideParams( - hardware_concurrency=hardware_concurrency - ) + _params: dict[str, Any] = {} + _params["hardwareConcurrency"] = hardware_concurrency await self._executor.execute( method=EmulationCommand.SET_HARDWARE_CONCURRENCY_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_cpu_performance_override( @@ -776,11 +801,13 @@ async def set_cpu_performance_override( """ Overrides the value of navigator.cpuPerformance """ - params = SetCPUPerformanceOverrideParams(performance_tier=performance_tier) + _params: dict[str, Any] = {} + if performance_tier is not None: + _params["performanceTier"] = performance_tier await self._executor.execute( method=EmulationCommand.SET_CPU_PERFORMANCE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_user_agent_override( @@ -795,16 +822,18 @@ async def set_user_agent_override( Allows overriding user agent with the given string. `userAgentMetadata` must be set for Client Hint headers to be sent. """ - params = SetUserAgentOverrideParams( - user_agent=user_agent, - accept_language=accept_language, - platform=platform, - user_agent_metadata=user_agent_metadata, - ) + _params: dict[str, Any] = {} + _params["userAgent"] = user_agent + if accept_language is not None: + _params["acceptLanguage"] = accept_language + if platform is not None: + _params["platform"] = platform + if user_agent_metadata is not None: + _params["userAgentMetadata"] = _encode_value(user_agent_metadata) await self._executor.execute( method=EmulationCommand.SET_USER_AGENT_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_automation_override( @@ -815,11 +844,12 @@ async def set_automation_override( """ Allows overriding the automation flag. """ - params = SetAutomationOverrideParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=EmulationCommand.SET_AUTOMATION_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_small_viewport_height_difference_override( @@ -832,11 +862,12 @@ async def set_small_viewport_height_difference_override( which determine the value of the `svh` and `lvh` unit, respectively. Only supported for top-level frames. """ - params = SetSmallViewportHeightDifferenceOverrideParams(difference=difference) + _params: dict[str, Any] = {} + _params["difference"] = difference await self._executor.execute( method=EmulationCommand.SET_SMALL_VIEWPORT_HEIGHT_DIFFERENCE_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def get_screen_infos( @@ -870,22 +901,27 @@ async def add_screen( """ Add a new screen to the device. Only supported in headless mode. """ - params = AddScreenParams( - left=left, - top=top, - width=width, - height=height, - work_area_insets=work_area_insets, - device_pixel_ratio=device_pixel_ratio, - rotation=rotation, - color_depth=color_depth, - label=label, - is_internal=is_internal, - ) + _params: dict[str, Any] = {} + _params["left"] = left + _params["top"] = top + _params["width"] = width + _params["height"] = height + if work_area_insets is not None: + _params["workAreaInsets"] = _encode_value(work_area_insets) + if device_pixel_ratio is not None: + _params["devicePixelRatio"] = device_pixel_ratio + if rotation is not None: + _params["rotation"] = rotation + if color_depth is not None: + _params["colorDepth"] = color_depth + if label is not None: + _params["label"] = label + if is_internal is not None: + _params["isInternal"] = is_internal result = await self._executor.execute( method=EmulationCommand.ADD_SCREEN, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddScreenResult, result) @@ -907,23 +943,32 @@ async def update_screen( """ Updates specified screen parameters. Only supported in headless mode. """ - params = UpdateScreenParams( - screen_id=screen_id, - left=left, - top=top, - width=width, - height=height, - work_area_insets=work_area_insets, - device_pixel_ratio=device_pixel_ratio, - rotation=rotation, - color_depth=color_depth, - label=label, - is_internal=is_internal, - ) + _params: dict[str, Any] = {} + _params["screenId"] = _encode_value(screen_id) + if left is not None: + _params["left"] = left + if top is not None: + _params["top"] = top + if width is not None: + _params["width"] = width + if height is not None: + _params["height"] = height + if work_area_insets is not None: + _params["workAreaInsets"] = _encode_value(work_area_insets) + if device_pixel_ratio is not None: + _params["devicePixelRatio"] = device_pixel_ratio + if rotation is not None: + _params["rotation"] = rotation + if color_depth is not None: + _params["colorDepth"] = color_depth + if label is not None: + _params["label"] = label + if is_internal is not None: + _params["isInternal"] = is_internal result = await self._executor.execute( method=EmulationCommand.UPDATE_SCREEN, - params=encode_cdp(params), + params=_params, ) return decode_cdp(UpdateScreenResult, result) @@ -935,11 +980,12 @@ async def remove_screen( """ Remove screen from the device. Only supported in headless mode. """ - params = RemoveScreenParams(screen_id=screen_id) + _params: dict[str, Any] = {} + _params["screenId"] = _encode_value(screen_id) await self._executor.execute( method=EmulationCommand.REMOVE_SCREEN, - params=encode_cdp(params), + params=_params, ) async def set_primary_screen( @@ -952,9 +998,10 @@ async def set_primary_screen( coordinate system origin to the top-left of the new primary screen, updating the bounds and work areas of all existing screens accordingly. """ - params = SetPrimaryScreenParams(screen_id=screen_id) + _params: dict[str, Any] = {} + _params["screenId"] = _encode_value(screen_id) await self._executor.execute( method=EmulationCommand.SET_PRIMARY_SCREEN, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/eventbreakpoints/domain.py b/cdpify/domains/eventbreakpoints/domain.py index 844f4497..40ec185e 100644 --- a/cdpify/domains/eventbreakpoints/domain.py +++ b/cdpify/domains/eventbreakpoints/domain.py @@ -4,13 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + from cdpify.executor import CommandExecutor from .commands import ( EventBreakpointsCommand, - RemoveInstrumentationBreakpointParams, - SetInstrumentationBreakpointParams, ) @@ -26,11 +25,12 @@ async def set_instrumentation_breakpoint( """ Sets breakpoint on particular native event. """ - params = SetInstrumentationBreakpointParams(event_name=event_name) + _params: dict[str, Any] = {} + _params["eventName"] = event_name await self._executor.execute( method=EventBreakpointsCommand.SET_INSTRUMENTATION_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def remove_instrumentation_breakpoint( @@ -41,11 +41,12 @@ async def remove_instrumentation_breakpoint( """ Removes breakpoint on particular native event. """ - params = RemoveInstrumentationBreakpointParams(event_name=event_name) + _params: dict[str, Any] = {} + _params["eventName"] = event_name await self._executor.execute( method=EventBreakpointsCommand.REMOVE_INSTRUMENTATION_BREAKPOINT, - params=encode_cdp(params), + params=_params, ) async def disable( diff --git a/cdpify/domains/extensions/domain.py b/cdpify/domains/extensions/domain.py index 61a35df7..befb804c 100644 --- a/cdpify/domains/extensions/domain.py +++ b/cdpify/domains/extensions/domain.py @@ -6,21 +6,14 @@ from typing import Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - ClearStorageItemsParams, ExtensionsCommand, GetExtensionsResult, - GetStorageItemsParams, GetStorageItemsResult, - LoadUnpackedParams, LoadUnpackedResult, - RemoveStorageItemsParams, - SetStorageItemsParams, - TriggerActionParams, - UninstallParams, ) from .types import ( StorageArea, @@ -40,11 +33,13 @@ async def trigger_action( """ Runs an extension default action. """ - params = TriggerActionParams(id=id, target_id=target_id) + _params: dict[str, Any] = {} + _params["id"] = id + _params["targetId"] = target_id await self._executor.execute( method=ExtensionsCommand.TRIGGER_ACTION, - params=encode_cdp(params), + params=_params, ) async def load_unpacked( @@ -57,11 +52,14 @@ async def load_unpacked( Installs an unpacked extension from the filesystem similar to --load-extension CLI flags. Returns extension ID once the extension has been installed. """ - params = LoadUnpackedParams(path=path, enable_in_incognito=enable_in_incognito) + _params: dict[str, Any] = {} + _params["path"] = path + if enable_in_incognito is not None: + _params["enableInIncognito"] = enable_in_incognito result = await self._executor.execute( method=ExtensionsCommand.LOAD_UNPACKED, - params=encode_cdp(params), + params=_params, ) return decode_cdp(LoadUnpackedResult, result) @@ -85,11 +83,12 @@ async def uninstall( """ Uninstalls an unpacked extension (others not supported) from the profile. """ - params = UninstallParams(id=id) + _params: dict[str, Any] = {} + _params["id"] = id await self._executor.execute( method=ExtensionsCommand.UNINSTALL, - params=encode_cdp(params), + params=_params, ) async def get_storage_items( @@ -103,11 +102,15 @@ async def get_storage_items( Gets data from extension storage in the given `storageArea`. If `keys` is specified, these are used to filter the result. """ - params = GetStorageItemsParams(id=id, storage_area=storage_area, keys=keys) + _params: dict[str, Any] = {} + _params["id"] = id + _params["storageArea"] = _encode_value(storage_area) + if keys is not None: + _params["keys"] = _encode_value(keys) result = await self._executor.execute( method=ExtensionsCommand.GET_STORAGE_ITEMS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetStorageItemsResult, result) @@ -121,11 +124,14 @@ async def remove_storage_items( """ Removes `keys` from extension storage in the given `storageArea`. """ - params = RemoveStorageItemsParams(id=id, storage_area=storage_area, keys=keys) + _params: dict[str, Any] = {} + _params["id"] = id + _params["storageArea"] = _encode_value(storage_area) + _params["keys"] = _encode_value(keys) await self._executor.execute( method=ExtensionsCommand.REMOVE_STORAGE_ITEMS, - params=encode_cdp(params), + params=_params, ) async def clear_storage_items( @@ -137,11 +143,13 @@ async def clear_storage_items( """ Clears extension storage in the given `storageArea`. """ - params = ClearStorageItemsParams(id=id, storage_area=storage_area) + _params: dict[str, Any] = {} + _params["id"] = id + _params["storageArea"] = _encode_value(storage_area) await self._executor.execute( method=ExtensionsCommand.CLEAR_STORAGE_ITEMS, - params=encode_cdp(params), + params=_params, ) async def set_storage_items( @@ -155,9 +163,12 @@ async def set_storage_items( Sets `values` in extension storage in the given `storageArea`. The provided `values` will be merged with existing values in the storage area. """ - params = SetStorageItemsParams(id=id, storage_area=storage_area, values=values) + _params: dict[str, Any] = {} + _params["id"] = id + _params["storageArea"] = _encode_value(storage_area) + _params["values"] = _encode_value(values) await self._executor.execute( method=ExtensionsCommand.SET_STORAGE_ITEMS, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/fedcm/domain.py b/cdpify/domains/fedcm/domain.py index 452c6f84..69e013ba 100644 --- a/cdpify/domains/fedcm/domain.py +++ b/cdpify/domains/fedcm/domain.py @@ -4,16 +4,13 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - ClickDialogButtonParams, - DismissDialogParams, - EnableParams, FedCmCommand, - OpenUrlParams, - SelectAccountParams, ) from .types import ( AccountUrlType, @@ -30,11 +27,13 @@ async def enable( *, disable_rejection_delay: bool | None = None, ) -> None: - params = EnableParams(disable_rejection_delay=disable_rejection_delay) + _params: dict[str, Any] = {} + if disable_rejection_delay is not None: + _params["disableRejectionDelay"] = disable_rejection_delay await self._executor.execute( method=FedCmCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -51,11 +50,13 @@ async def select_account( dialog_id: str, account_index: int, ) -> None: - params = SelectAccountParams(dialog_id=dialog_id, account_index=account_index) + _params: dict[str, Any] = {} + _params["dialogId"] = dialog_id + _params["accountIndex"] = account_index await self._executor.execute( method=FedCmCommand.SELECT_ACCOUNT, - params=encode_cdp(params), + params=_params, ) async def click_dialog_button( @@ -64,13 +65,13 @@ async def click_dialog_button( dialog_id: str, dialog_button: DialogButton, ) -> None: - params = ClickDialogButtonParams( - dialog_id=dialog_id, dialog_button=dialog_button - ) + _params: dict[str, Any] = {} + _params["dialogId"] = dialog_id + _params["dialogButton"] = _encode_value(dialog_button) await self._executor.execute( method=FedCmCommand.CLICK_DIALOG_BUTTON, - params=encode_cdp(params), + params=_params, ) async def open_url( @@ -80,15 +81,14 @@ async def open_url( account_index: int, account_url_type: AccountUrlType, ) -> None: - params = OpenUrlParams( - dialog_id=dialog_id, - account_index=account_index, - account_url_type=account_url_type, - ) + _params: dict[str, Any] = {} + _params["dialogId"] = dialog_id + _params["accountIndex"] = account_index + _params["accountUrlType"] = _encode_value(account_url_type) await self._executor.execute( method=FedCmCommand.OPEN_URL, - params=encode_cdp(params), + params=_params, ) async def dismiss_dialog( @@ -97,13 +97,14 @@ async def dismiss_dialog( dialog_id: str, trigger_cooldown: bool | None = None, ) -> None: - params = DismissDialogParams( - dialog_id=dialog_id, trigger_cooldown=trigger_cooldown - ) + _params: dict[str, Any] = {} + _params["dialogId"] = dialog_id + if trigger_cooldown is not None: + _params["triggerCooldown"] = trigger_cooldown await self._executor.execute( method=FedCmCommand.DISMISS_DIALOG, - params=encode_cdp(params), + params=_params, ) async def reset_cooldown( diff --git a/cdpify/domains/fetch/domain.py b/cdpify/domains/fetch/domain.py index aa4609a7..b1a7db45 100644 --- a/cdpify/domains/fetch/domain.py +++ b/cdpify/domains/fetch/domain.py @@ -4,22 +4,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - ContinueRequestParams, - ContinueResponseParams, - ContinueWithAuthParams, - EnableParams, - FailRequestParams, FetchCommand, - FulfillRequestParams, - GetResponseBodyParams, GetResponseBodyResult, - TakeResponseBodyAsStreamParams, TakeResponseBodyAsStreamResult, ) from .types import ( @@ -58,13 +50,15 @@ async def enable( Enables issuing of requestPaused events. A request will be paused until client calls one of failRequest, fulfillRequest or continueRequest/continueWithAuth. """ - params = EnableParams( - patterns=patterns, handle_auth_requests=handle_auth_requests - ) + _params: dict[str, Any] = {} + if patterns is not None: + _params["patterns"] = _encode_value(patterns) + if handle_auth_requests is not None: + _params["handleAuthRequests"] = handle_auth_requests await self._executor.execute( method=FetchCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def fail_request( @@ -76,11 +70,13 @@ async def fail_request( """ Causes the request to fail with specified reason. """ - params = FailRequestParams(request_id=request_id, error_reason=error_reason) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + _params["errorReason"] = _encode_value(error_reason) await self._executor.execute( method=FetchCommand.FAIL_REQUEST, - params=encode_cdp(params), + params=_params, ) async def fulfill_request( @@ -96,18 +92,21 @@ async def fulfill_request( """ Provides response to the request. """ - params = FulfillRequestParams( - request_id=request_id, - response_code=response_code, - response_headers=response_headers, - binary_response_headers=binary_response_headers, - body=body, - response_phrase=response_phrase, - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + _params["responseCode"] = response_code + if response_headers is not None: + _params["responseHeaders"] = _encode_value(response_headers) + if binary_response_headers is not None: + _params["binaryResponseHeaders"] = binary_response_headers + if body is not None: + _params["body"] = body + if response_phrase is not None: + _params["responsePhrase"] = response_phrase await self._executor.execute( method=FetchCommand.FULFILL_REQUEST, - params=encode_cdp(params), + params=_params, ) async def continue_request( @@ -123,18 +122,22 @@ async def continue_request( """ Continues the request, optionally modifying some of its parameters. """ - params = ContinueRequestParams( - request_id=request_id, - url=url, - method=method, - post_data=post_data, - headers=headers, - intercept_response=intercept_response, - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + if url is not None: + _params["url"] = url + if method is not None: + _params["method"] = method + if post_data is not None: + _params["postData"] = post_data + if headers is not None: + _params["headers"] = _encode_value(headers) + if intercept_response is not None: + _params["interceptResponse"] = intercept_response await self._executor.execute( method=FetchCommand.CONTINUE_REQUEST, - params=encode_cdp(params), + params=_params, ) async def continue_with_auth( @@ -147,13 +150,13 @@ async def continue_with_auth( Continues a request supplying authChallengeResponse following authRequired event. """ - params = ContinueWithAuthParams( - request_id=request_id, auth_challenge_response=auth_challenge_response - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + _params["authChallengeResponse"] = _encode_value(auth_challenge_response) await self._executor.execute( method=FetchCommand.CONTINUE_WITH_AUTH, - params=encode_cdp(params), + params=_params, ) async def continue_response( @@ -170,17 +173,20 @@ async def continue_response( headers. If either responseCode or headers are modified, all of them must be present. """ - params = ContinueResponseParams( - request_id=request_id, - response_code=response_code, - response_phrase=response_phrase, - response_headers=response_headers, - binary_response_headers=binary_response_headers, - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + if response_code is not None: + _params["responseCode"] = response_code + if response_phrase is not None: + _params["responsePhrase"] = response_phrase + if response_headers is not None: + _params["responseHeaders"] = _encode_value(response_headers) + if binary_response_headers is not None: + _params["binaryResponseHeaders"] = binary_response_headers await self._executor.execute( method=FetchCommand.CONTINUE_RESPONSE, - params=encode_cdp(params), + params=_params, ) async def get_response_body( @@ -198,11 +204,12 @@ async def get_response_body( may be differentiated by `responseCode` and presence of `location` response header, see comments to `requestPaused` for details. """ - params = GetResponseBodyParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) result = await self._executor.execute( method=FetchCommand.GET_RESPONSE_BODY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetResponseBodyResult, result) @@ -220,10 +227,11 @@ async def take_response_body_as_stream( getResponseBody. Calling other methods that affect the request or disabling fetch domain before body is received results in an undefined behavior. """ - params = TakeResponseBodyAsStreamParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) result = await self._executor.execute( method=FetchCommand.TAKE_RESPONSE_BODY_AS_STREAM, - params=encode_cdp(params), + params=_params, ) return decode_cdp(TakeResponseBodyAsStreamResult, result) diff --git a/cdpify/domains/filesystem/domain.py b/cdpify/domains/filesystem/domain.py index 5e66bdf7..e352c9a3 100644 --- a/cdpify/domains/filesystem/domain.py +++ b/cdpify/domains/filesystem/domain.py @@ -4,12 +4,13 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( FileSystemCommand, - GetDirectoryParams, GetDirectoryResult, ) from .types import ( @@ -26,12 +27,11 @@ async def get_directory( *, bucket_file_system_locator: BucketFileSystemLocator, ) -> GetDirectoryResult: - params = GetDirectoryParams( - bucket_file_system_locator=bucket_file_system_locator - ) + _params: dict[str, Any] = {} + _params["bucketFileSystemLocator"] = _encode_value(bucket_file_system_locator) result = await self._executor.execute( method=FileSystemCommand.GET_DIRECTORY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetDirectoryResult, result) diff --git a/cdpify/domains/findinpage/domain.py b/cdpify/domains/findinpage/domain.py index 57ac0c53..f7f1c52e 100644 --- a/cdpify/domains/findinpage/domain.py +++ b/cdpify/domains/findinpage/domain.py @@ -4,11 +4,11 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + from cdpify.executor import CommandExecutor from .commands import ( - FindFirstParams, FindInPageCommand, ) @@ -26,11 +26,12 @@ async def find_first( Forwards `query` to the find-in-page facility, starting a new find session. Where exactly the search starts from is implementation-specific. """ - params = FindFirstParams(query=query) + _params: dict[str, Any] = {} + _params["query"] = query await self._executor.execute( method=FindInPageCommand.FIND_FIRST, - params=encode_cdp(params), + params=_params, ) async def find_next( diff --git a/cdpify/domains/headlessexperimental/domain.py b/cdpify/domains/headlessexperimental/domain.py index 67bd7607..6eeb5790 100644 --- a/cdpify/domains/headlessexperimental/domain.py +++ b/cdpify/domains/headlessexperimental/domain.py @@ -4,12 +4,13 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - BeginFrameParams, BeginFrameResult, HeadlessExperimentalCommand, ) @@ -37,16 +38,19 @@ async def begin_frame( --run-all-compositor-stages-before-draw, see also https://goo.gle/chrome-headless-rendering for more background. """ - params = BeginFrameParams( - frame_time_ticks=frame_time_ticks, - interval=interval, - no_display_updates=no_display_updates, - screenshot=screenshot, - ) + _params: dict[str, Any] = {} + if frame_time_ticks is not None: + _params["frameTimeTicks"] = frame_time_ticks + if interval is not None: + _params["interval"] = interval + if no_display_updates is not None: + _params["noDisplayUpdates"] = no_display_updates + if screenshot is not None: + _params["screenshot"] = _encode_value(screenshot) result = await self._executor.execute( method=HeadlessExperimentalCommand.BEGIN_FRAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(BeginFrameResult, result) diff --git a/cdpify/domains/heapprofiler/domain.py b/cdpify/domains/heapprofiler/domain.py index 5a96c60b..9a1482eb 100644 --- a/cdpify/domains/heapprofiler/domain.py +++ b/cdpify/domains/heapprofiler/domain.py @@ -4,24 +4,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - AddInspectedHeapObjectParams, - GetHeapObjectIdParams, GetHeapObjectIdResult, - GetObjectByHeapObjectIdParams, GetObjectByHeapObjectIdResult, GetSamplingProfileResult, HeapProfilerCommand, - StartSamplingParams, - StartTrackingHeapObjectsParams, StopSamplingResult, - StopTrackingHeapObjectsParams, - TakeHeapSnapshotParams, ) from .types import ( HeapSnapshotObjectId, @@ -44,11 +37,12 @@ async def add_inspected_heap_object( Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). """ - params = AddInspectedHeapObjectParams(heap_object_id=heap_object_id) + _params: dict[str, Any] = {} + _params["heapObjectId"] = _encode_value(heap_object_id) await self._executor.execute( method=HeapProfilerCommand.ADD_INSPECTED_HEAP_OBJECT, - params=encode_cdp(params), + params=_params, ) async def collect_garbage( @@ -80,11 +74,12 @@ async def get_heap_object_id( *, object_id: runtime.RemoteObjectId, ) -> GetHeapObjectIdResult: - params = GetHeapObjectIdParams(object_id=object_id) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=HeapProfilerCommand.GET_HEAP_OBJECT_ID, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetHeapObjectIdResult, result) @@ -94,13 +89,14 @@ async def get_object_by_heap_object_id( object_id: HeapSnapshotObjectId, object_group: str | None = None, ) -> GetObjectByHeapObjectIdResult: - params = GetObjectByHeapObjectIdParams( - object_id=object_id, object_group=object_group - ) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) + if object_group is not None: + _params["objectGroup"] = object_group result = await self._executor.execute( method=HeapProfilerCommand.GET_OBJECT_BY_HEAP_OBJECT_ID, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetObjectByHeapObjectIdResult, result) @@ -121,16 +117,23 @@ async def start_sampling( include_objects_collected_by_major_gc: bool | None = None, include_objects_collected_by_minor_gc: bool | None = None, ) -> None: - params = StartSamplingParams( - sampling_interval=sampling_interval, - stack_depth=stack_depth, - include_objects_collected_by_major_gc=include_objects_collected_by_major_gc, - include_objects_collected_by_minor_gc=include_objects_collected_by_minor_gc, - ) + _params: dict[str, Any] = {} + if sampling_interval is not None: + _params["samplingInterval"] = sampling_interval + if stack_depth is not None: + _params["stackDepth"] = stack_depth + if include_objects_collected_by_major_gc is not None: + _params["includeObjectsCollectedByMajorGC"] = ( + include_objects_collected_by_major_gc + ) + if include_objects_collected_by_minor_gc is not None: + _params["includeObjectsCollectedByMinorGC"] = ( + include_objects_collected_by_minor_gc + ) await self._executor.execute( method=HeapProfilerCommand.START_SAMPLING, - params=encode_cdp(params), + params=_params, ) async def start_tracking_heap_objects( @@ -138,11 +141,13 @@ async def start_tracking_heap_objects( *, track_allocations: bool | None = None, ) -> None: - params = StartTrackingHeapObjectsParams(track_allocations=track_allocations) + _params: dict[str, Any] = {} + if track_allocations is not None: + _params["trackAllocations"] = track_allocations await self._executor.execute( method=HeapProfilerCommand.START_TRACKING_HEAP_OBJECTS, - params=encode_cdp(params), + params=_params, ) async def stop_sampling( @@ -162,16 +167,19 @@ async def stop_tracking_heap_objects( capture_numeric_value: bool | None = None, expose_internals: bool | None = None, ) -> None: - params = StopTrackingHeapObjectsParams( - report_progress=report_progress, - treat_global_objects_as_roots=treat_global_objects_as_roots, - capture_numeric_value=capture_numeric_value, - expose_internals=expose_internals, - ) + _params: dict[str, Any] = {} + if report_progress is not None: + _params["reportProgress"] = report_progress + if treat_global_objects_as_roots is not None: + _params["treatGlobalObjectsAsRoots"] = treat_global_objects_as_roots + if capture_numeric_value is not None: + _params["captureNumericValue"] = capture_numeric_value + if expose_internals is not None: + _params["exposeInternals"] = expose_internals await self._executor.execute( method=HeapProfilerCommand.STOP_TRACKING_HEAP_OBJECTS, - params=encode_cdp(params), + params=_params, ) async def take_heap_snapshot( @@ -182,14 +190,17 @@ async def take_heap_snapshot( capture_numeric_value: bool | None = None, expose_internals: bool | None = None, ) -> None: - params = TakeHeapSnapshotParams( - report_progress=report_progress, - treat_global_objects_as_roots=treat_global_objects_as_roots, - capture_numeric_value=capture_numeric_value, - expose_internals=expose_internals, - ) + _params: dict[str, Any] = {} + if report_progress is not None: + _params["reportProgress"] = report_progress + if treat_global_objects_as_roots is not None: + _params["treatGlobalObjectsAsRoots"] = treat_global_objects_as_roots + if capture_numeric_value is not None: + _params["captureNumericValue"] = capture_numeric_value + if expose_internals is not None: + _params["exposeInternals"] = expose_internals await self._executor.execute( method=HeapProfilerCommand.TAKE_HEAP_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/indexeddb/domain.py b/cdpify/domains/indexeddb/domain.py index 89531386..9763d80b 100644 --- a/cdpify/domains/indexeddb/domain.py +++ b/cdpify/domains/indexeddb/domain.py @@ -4,23 +4,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - ClearObjectStoreParams, - DeleteDatabaseParams, - DeleteObjectStoreEntriesParams, - GetMetadataParams, GetMetadataResult, IndexedDBCommand, - RequestDatabaseNamesParams, RequestDatabaseNamesResult, - RequestDatabaseParams, RequestDatabaseResult, - RequestDataParams, RequestDataResult, ) from .types import ( @@ -47,17 +40,19 @@ async def clear_object_store( """ Clears all entries from an object store. """ - params = ClearObjectStoreParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - object_store_name=object_store_name, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name + _params["objectStoreName"] = object_store_name await self._executor.execute( method=IndexedDBCommand.CLEAR_OBJECT_STORE, - params=encode_cdp(params), + params=_params, ) async def delete_database( @@ -71,16 +66,18 @@ async def delete_database( """ Deletes a database. """ - params = DeleteDatabaseParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name await self._executor.execute( method=IndexedDBCommand.DELETE_DATABASE, - params=encode_cdp(params), + params=_params, ) async def delete_object_store_entries( @@ -96,18 +93,20 @@ async def delete_object_store_entries( """ Delete a range of entries from an object store """ - params = DeleteObjectStoreEntriesParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - object_store_name=object_store_name, - key_range=key_range, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name + _params["objectStoreName"] = object_store_name + _params["keyRange"] = _encode_value(key_range) await self._executor.execute( method=IndexedDBCommand.DELETE_OBJECT_STORE_ENTRIES, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -148,21 +147,25 @@ async def request_data( """ Requests data from object store or index. """ - params = RequestDataParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - object_store_name=object_store_name, - index_name=index_name, - skip_count=skip_count, - page_size=page_size, - key_range=key_range, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name + _params["objectStoreName"] = object_store_name + if index_name is not None: + _params["indexName"] = index_name + _params["skipCount"] = skip_count + _params["pageSize"] = page_size + if key_range is not None: + _params["keyRange"] = _encode_value(key_range) result = await self._executor.execute( method=IndexedDBCommand.REQUEST_DATA, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestDataResult, result) @@ -178,17 +181,19 @@ async def get_metadata( """ Gets metadata of an object store. """ - params = GetMetadataParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - object_store_name=object_store_name, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name + _params["objectStoreName"] = object_store_name result = await self._executor.execute( method=IndexedDBCommand.GET_METADATA, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetMetadataResult, result) @@ -203,16 +208,18 @@ async def request_database( """ Requests database with given name in given frame. """ - params = RequestDatabaseParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - database_name=database_name, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) + _params["databaseName"] = database_name result = await self._executor.execute( method=IndexedDBCommand.REQUEST_DATABASE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestDatabaseResult, result) @@ -226,14 +233,16 @@ async def request_database_names( """ Requests database names for given security origin. """ - params = RequestDatabaseNamesParams( - security_origin=security_origin, - storage_key=storage_key, - storage_bucket=storage_bucket, - ) + _params: dict[str, Any] = {} + if security_origin is not None: + _params["securityOrigin"] = security_origin + if storage_key is not None: + _params["storageKey"] = storage_key + if storage_bucket is not None: + _params["storageBucket"] = _encode_value(storage_bucket) result = await self._executor.execute( method=IndexedDBCommand.REQUEST_DATABASE_NAMES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestDatabaseNamesResult, result) diff --git a/cdpify/domains/input/domain.py b/cdpify/domains/input/domain.py index f75e1dcc..6a10593b 100644 --- a/cdpify/domains/input/domain.py +++ b/cdpify/domains/input/domain.py @@ -4,25 +4,13 @@ from __future__ import annotations -from typing import Literal +from typing import Any, Literal -from cdpify.codec import encode_cdp +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - DispatchDragEventParams, - DispatchKeyEventParams, - DispatchMouseEventParams, - DispatchTouchEventParams, - EmulateTouchFromMouseEventParams, - ImeSetCompositionParams, InputCommand, - InsertTextParams, - SetIgnoreInputEventsParams, - SetInterceptDragsParams, - SynthesizePinchGestureParams, - SynthesizeScrollGestureParams, - SynthesizeTapGestureParams, ) from .types import ( DragData, @@ -49,13 +37,17 @@ async def dispatch_drag_event( """ Dispatches a drag event into the page. """ - params = DispatchDragEventParams( - type=type, x=x, y=y, data=data, modifiers=modifiers - ) + _params: dict[str, Any] = {} + _params["type"] = type + _params["x"] = x + _params["y"] = y + _params["data"] = _encode_value(data) + if modifiers is not None: + _params["modifiers"] = modifiers await self._executor.execute( method=InputCommand.DISPATCH_DRAG_EVENT, - params=encode_cdp(params), + params=_params, ) async def dispatch_key_event( @@ -80,27 +72,40 @@ async def dispatch_key_event( """ Dispatches a key event to the page. """ - params = DispatchKeyEventParams( - type=type, - modifiers=modifiers, - timestamp=timestamp, - text=text, - unmodified_text=unmodified_text, - key_identifier=key_identifier, - code=code, - key=key, - windows_virtual_key_code=windows_virtual_key_code, - native_virtual_key_code=native_virtual_key_code, - auto_repeat=auto_repeat, - is_keypad=is_keypad, - is_system_key=is_system_key, - location=location, - commands=commands, - ) + _params: dict[str, Any] = {} + _params["type"] = type + if modifiers is not None: + _params["modifiers"] = modifiers + if timestamp is not None: + _params["timestamp"] = _encode_value(timestamp) + if text is not None: + _params["text"] = text + if unmodified_text is not None: + _params["unmodifiedText"] = unmodified_text + if key_identifier is not None: + _params["keyIdentifier"] = key_identifier + if code is not None: + _params["code"] = code + if key is not None: + _params["key"] = key + if windows_virtual_key_code is not None: + _params["windowsVirtualKeyCode"] = windows_virtual_key_code + if native_virtual_key_code is not None: + _params["nativeVirtualKeyCode"] = native_virtual_key_code + if auto_repeat is not None: + _params["autoRepeat"] = auto_repeat + if is_keypad is not None: + _params["isKeypad"] = is_keypad + if is_system_key is not None: + _params["isSystemKey"] = is_system_key + if location is not None: + _params["location"] = location + if commands is not None: + _params["commands"] = _encode_value(commands) await self._executor.execute( method=InputCommand.DISPATCH_KEY_EVENT, - params=encode_cdp(params), + params=_params, ) async def insert_text( @@ -112,11 +117,12 @@ async def insert_text( This method emulates inserting text that doesn't come from a key press, for example an emoji keyboard or an IME. """ - params = InsertTextParams(text=text) + _params: dict[str, Any] = {} + _params["text"] = text await self._executor.execute( method=InputCommand.INSERT_TEXT, - params=encode_cdp(params), + params=_params, ) async def ime_set_composition( @@ -133,17 +139,18 @@ async def ime_set_composition( to commit the final text. Use imeSetComposition with empty string as text to cancel composition. """ - params = ImeSetCompositionParams( - text=text, - selection_start=selection_start, - selection_end=selection_end, - replacement_start=replacement_start, - replacement_end=replacement_end, - ) + _params: dict[str, Any] = {} + _params["text"] = text + _params["selectionStart"] = selection_start + _params["selectionEnd"] = selection_end + if replacement_start is not None: + _params["replacementStart"] = replacement_start + if replacement_end is not None: + _params["replacementEnd"] = replacement_end await self._executor.execute( method=InputCommand.IME_SET_COMPOSITION, - params=encode_cdp(params), + params=_params, ) async def dispatch_mouse_event( @@ -169,28 +176,40 @@ async def dispatch_mouse_event( """ Dispatches a mouse event to the page. """ - params = DispatchMouseEventParams( - type=type, - x=x, - y=y, - modifiers=modifiers, - timestamp=timestamp, - button=button, - buttons=buttons, - click_count=click_count, - force=force, - tangential_pressure=tangential_pressure, - tilt_x=tilt_x, - tilt_y=tilt_y, - twist=twist, - delta_x=delta_x, - delta_y=delta_y, - pointer_type=pointer_type, - ) + _params: dict[str, Any] = {} + _params["type"] = type + _params["x"] = x + _params["y"] = y + if modifiers is not None: + _params["modifiers"] = modifiers + if timestamp is not None: + _params["timestamp"] = _encode_value(timestamp) + if button is not None: + _params["button"] = _encode_value(button) + if buttons is not None: + _params["buttons"] = buttons + if click_count is not None: + _params["clickCount"] = click_count + if force is not None: + _params["force"] = force + if tangential_pressure is not None: + _params["tangentialPressure"] = tangential_pressure + if tilt_x is not None: + _params["tiltX"] = tilt_x + if tilt_y is not None: + _params["tiltY"] = tilt_y + if twist is not None: + _params["twist"] = twist + if delta_x is not None: + _params["deltaX"] = delta_x + if delta_y is not None: + _params["deltaY"] = delta_y + if pointer_type is not None: + _params["pointerType"] = pointer_type await self._executor.execute( method=InputCommand.DISPATCH_MOUSE_EVENT, - params=encode_cdp(params), + params=_params, ) async def dispatch_touch_event( @@ -204,16 +223,17 @@ async def dispatch_touch_event( """ Dispatches a touch event to the page. """ - params = DispatchTouchEventParams( - type=type, - touch_points=touch_points, - modifiers=modifiers, - timestamp=timestamp, - ) + _params: dict[str, Any] = {} + _params["type"] = type + _params["touchPoints"] = _encode_value(touch_points) + if modifiers is not None: + _params["modifiers"] = modifiers + if timestamp is not None: + _params["timestamp"] = _encode_value(timestamp) await self._executor.execute( method=InputCommand.DISPATCH_TOUCH_EVENT, - params=encode_cdp(params), + params=_params, ) async def cancel_dragging( @@ -243,21 +263,25 @@ async def emulate_touch_from_mouse_event( """ Emulates touch event from the mouse event parameters. """ - params = EmulateTouchFromMouseEventParams( - type=type, - x=x, - y=y, - button=button, - timestamp=timestamp, - delta_x=delta_x, - delta_y=delta_y, - modifiers=modifiers, - click_count=click_count, - ) + _params: dict[str, Any] = {} + _params["type"] = type + _params["x"] = x + _params["y"] = y + _params["button"] = _encode_value(button) + if timestamp is not None: + _params["timestamp"] = _encode_value(timestamp) + if delta_x is not None: + _params["deltaX"] = delta_x + if delta_y is not None: + _params["deltaY"] = delta_y + if modifiers is not None: + _params["modifiers"] = modifiers + if click_count is not None: + _params["clickCount"] = click_count await self._executor.execute( method=InputCommand.EMULATE_TOUCH_FROM_MOUSE_EVENT, - params=encode_cdp(params), + params=_params, ) async def set_ignore_input_events( @@ -268,11 +292,12 @@ async def set_ignore_input_events( """ Ignores input events (useful while auditing page). """ - params = SetIgnoreInputEventsParams(ignore=ignore) + _params: dict[str, Any] = {} + _params["ignore"] = ignore await self._executor.execute( method=InputCommand.SET_IGNORE_INPUT_EVENTS, - params=encode_cdp(params), + params=_params, ) async def set_intercept_drags( @@ -285,11 +310,12 @@ async def set_intercept_drags( `Input.dragIntercepted` events. Drag and drop behavior can be directly controlled via `Input.dispatchDragEvent`. """ - params = SetInterceptDragsParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=InputCommand.SET_INTERCEPT_DRAGS, - params=encode_cdp(params), + params=_params, ) async def synthesize_pinch_gesture( @@ -305,17 +331,18 @@ async def synthesize_pinch_gesture( Synthesizes a pinch gesture over a time period by issuing appropriate touch events. """ - params = SynthesizePinchGestureParams( - x=x, - y=y, - scale_factor=scale_factor, - relative_speed=relative_speed, - gesture_source_type=gesture_source_type, - ) + _params: dict[str, Any] = {} + _params["x"] = x + _params["y"] = y + _params["scaleFactor"] = scale_factor + if relative_speed is not None: + _params["relativeSpeed"] = relative_speed + if gesture_source_type is not None: + _params["gestureSourceType"] = _encode_value(gesture_source_type) await self._executor.execute( method=InputCommand.SYNTHESIZE_PINCH_GESTURE, - params=encode_cdp(params), + params=_params, ) async def synthesize_scroll_gesture( @@ -338,24 +365,33 @@ async def synthesize_scroll_gesture( Synthesizes a scroll gesture over a time period by issuing appropriate touch events. """ - params = SynthesizeScrollGestureParams( - x=x, - y=y, - x_distance=x_distance, - y_distance=y_distance, - x_overscroll=x_overscroll, - y_overscroll=y_overscroll, - prevent_fling=prevent_fling, - speed=speed, - gesture_source_type=gesture_source_type, - repeat_count=repeat_count, - repeat_delay_ms=repeat_delay_ms, - interaction_marker_name=interaction_marker_name, - ) + _params: dict[str, Any] = {} + _params["x"] = x + _params["y"] = y + if x_distance is not None: + _params["xDistance"] = x_distance + if y_distance is not None: + _params["yDistance"] = y_distance + if x_overscroll is not None: + _params["xOverscroll"] = x_overscroll + if y_overscroll is not None: + _params["yOverscroll"] = y_overscroll + if prevent_fling is not None: + _params["preventFling"] = prevent_fling + if speed is not None: + _params["speed"] = speed + if gesture_source_type is not None: + _params["gestureSourceType"] = _encode_value(gesture_source_type) + if repeat_count is not None: + _params["repeatCount"] = repeat_count + if repeat_delay_ms is not None: + _params["repeatDelayMs"] = repeat_delay_ms + if interaction_marker_name is not None: + _params["interactionMarkerName"] = interaction_marker_name await self._executor.execute( method=InputCommand.SYNTHESIZE_SCROLL_GESTURE, - params=encode_cdp(params), + params=_params, ) async def synthesize_tap_gesture( @@ -371,15 +407,17 @@ async def synthesize_tap_gesture( Synthesizes a tap gesture over a time period by issuing appropriate touch events. """ - params = SynthesizeTapGestureParams( - x=x, - y=y, - duration=duration, - tap_count=tap_count, - gesture_source_type=gesture_source_type, - ) + _params: dict[str, Any] = {} + _params["x"] = x + _params["y"] = y + if duration is not None: + _params["duration"] = duration + if tap_count is not None: + _params["tapCount"] = tap_count + if gesture_source_type is not None: + _params["gestureSourceType"] = _encode_value(gesture_source_type) await self._executor.execute( method=InputCommand.SYNTHESIZE_TAP_GESTURE, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/io/domain.py b/cdpify/domains/io/domain.py index aadab701..b4f48360 100644 --- a/cdpify/domains/io/domain.py +++ b/cdpify/domains/io/domain.py @@ -4,18 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - CloseParams, IOCommand, - ReadParams, ReadResult, - ResolveBlobParams, ResolveBlobResult, ) from .types import ( @@ -38,11 +35,12 @@ async def close( """ Close the stream, discard any temporary backing storage. """ - params = CloseParams(handle=handle) + _params: dict[str, Any] = {} + _params["handle"] = _encode_value(handle) await self._executor.execute( method=IOCommand.CLOSE, - params=encode_cdp(params), + params=_params, ) async def read( @@ -55,11 +53,16 @@ async def read( """ Read a chunk of the stream """ - params = ReadParams(handle=handle, offset=offset, size=size) + _params: dict[str, Any] = {} + _params["handle"] = _encode_value(handle) + if offset is not None: + _params["offset"] = offset + if size is not None: + _params["size"] = size result = await self._executor.execute( method=IOCommand.READ, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ReadResult, result) @@ -72,10 +75,11 @@ async def resolve_blob( """ Return UUID of Blob object specified by a remote object id. """ - params = ResolveBlobParams(object_id=object_id) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) result = await self._executor.execute( method=IOCommand.RESOLVE_BLOB, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ResolveBlobResult, result) diff --git a/cdpify/domains/layertree/domain.py b/cdpify/domains/layertree/domain.py index bf5eb273..4c2930c1 100644 --- a/cdpify/domains/layertree/domain.py +++ b/cdpify/domains/layertree/domain.py @@ -4,25 +4,18 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - CompositingReasonsParams, CompositingReasonsResult, LayerTreeCommand, - LoadSnapshotParams, LoadSnapshotResult, - MakeSnapshotParams, MakeSnapshotResult, - ProfileSnapshotParams, ProfileSnapshotResult, - ReleaseSnapshotParams, - ReplaySnapshotParams, ReplaySnapshotResult, - SnapshotCommandLogParams, SnapshotCommandLogResult, ) from .types import ( @@ -47,11 +40,12 @@ async def compositing_reasons( """ Provides the reasons why the given layer was composited. """ - params = CompositingReasonsParams(layer_id=layer_id) + _params: dict[str, Any] = {} + _params["layerId"] = _encode_value(layer_id) result = await self._executor.execute( method=LayerTreeCommand.COMPOSITING_REASONS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CompositingReasonsResult, result) @@ -85,11 +79,12 @@ async def load_snapshot( """ Returns the snapshot identifier. """ - params = LoadSnapshotParams(tiles=tiles) + _params: dict[str, Any] = {} + _params["tiles"] = _encode_value(tiles) result = await self._executor.execute( method=LayerTreeCommand.LOAD_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(LoadSnapshotResult, result) @@ -101,11 +96,12 @@ async def make_snapshot( """ Returns the layer snapshot identifier. """ - params = MakeSnapshotParams(layer_id=layer_id) + _params: dict[str, Any] = {} + _params["layerId"] = _encode_value(layer_id) result = await self._executor.execute( method=LayerTreeCommand.MAKE_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(MakeSnapshotResult, result) @@ -117,16 +113,18 @@ async def profile_snapshot( min_duration: float | None = None, clip_rect: dom.Rect | None = None, ) -> ProfileSnapshotResult: - params = ProfileSnapshotParams( - snapshot_id=snapshot_id, - min_repeat_count=min_repeat_count, - min_duration=min_duration, - clip_rect=clip_rect, - ) + _params: dict[str, Any] = {} + _params["snapshotId"] = _encode_value(snapshot_id) + if min_repeat_count is not None: + _params["minRepeatCount"] = min_repeat_count + if min_duration is not None: + _params["minDuration"] = min_duration + if clip_rect is not None: + _params["clipRect"] = _encode_value(clip_rect) result = await self._executor.execute( method=LayerTreeCommand.PROFILE_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ProfileSnapshotResult, result) @@ -138,11 +136,12 @@ async def release_snapshot( """ Releases layer snapshot captured by the back-end. """ - params = ReleaseSnapshotParams(snapshot_id=snapshot_id) + _params: dict[str, Any] = {} + _params["snapshotId"] = _encode_value(snapshot_id) await self._executor.execute( method=LayerTreeCommand.RELEASE_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) async def replay_snapshot( @@ -156,13 +155,18 @@ async def replay_snapshot( """ Replays the layer snapshot and returns the resulting bitmap. """ - params = ReplaySnapshotParams( - snapshot_id=snapshot_id, from_step=from_step, to_step=to_step, scale=scale - ) + _params: dict[str, Any] = {} + _params["snapshotId"] = _encode_value(snapshot_id) + if from_step is not None: + _params["fromStep"] = from_step + if to_step is not None: + _params["toStep"] = to_step + if scale is not None: + _params["scale"] = scale result = await self._executor.execute( method=LayerTreeCommand.REPLAY_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ReplaySnapshotResult, result) @@ -174,10 +178,11 @@ async def snapshot_command_log( """ Replays the layer snapshot and returns canvas log. """ - params = SnapshotCommandLogParams(snapshot_id=snapshot_id) + _params: dict[str, Any] = {} + _params["snapshotId"] = _encode_value(snapshot_id) result = await self._executor.execute( method=LayerTreeCommand.SNAPSHOT_COMMAND_LOG, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SnapshotCommandLogResult, result) diff --git a/cdpify/domains/log/domain.py b/cdpify/domains/log/domain.py index 7f10117f..264e353e 100644 --- a/cdpify/domains/log/domain.py +++ b/cdpify/domains/log/domain.py @@ -4,12 +4,13 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( LogCommand, - StartViolationsReportParams, ) from .types import ( ViolationSetting, @@ -63,11 +64,12 @@ async def start_violations_report( """ start violation reporting. """ - params = StartViolationsReportParams(config=config) + _params: dict[str, Any] = {} + _params["config"] = _encode_value(config) await self._executor.execute( method=LogCommand.START_VIOLATIONS_REPORT, - params=encode_cdp(params), + params=_params, ) async def stop_violations_report( diff --git a/cdpify/domains/memory/domain.py b/cdpify/domains/memory/domain.py index 9ebded7d..72309f61 100644 --- a/cdpify/domains/memory/domain.py +++ b/cdpify/domains/memory/domain.py @@ -4,7 +4,9 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( @@ -14,9 +16,6 @@ GetDOMCountersResult, GetSamplingProfileResult, MemoryCommand, - SetPressureNotificationsSuppressedParams, - SimulatePressureNotificationParams, - StartSamplingParams, ) from .types import ( PressureLevel, @@ -82,11 +81,12 @@ async def set_pressure_notifications_suppressed( """ Enable/disable suppressing memory pressure notifications in all processes. """ - params = SetPressureNotificationsSuppressedParams(suppressed=suppressed) + _params: dict[str, Any] = {} + _params["suppressed"] = suppressed await self._executor.execute( method=MemoryCommand.SET_PRESSURE_NOTIFICATIONS_SUPPRESSED, - params=encode_cdp(params), + params=_params, ) async def simulate_pressure_notification( @@ -97,11 +97,12 @@ async def simulate_pressure_notification( """ Simulate a memory pressure notification in all processes. """ - params = SimulatePressureNotificationParams(level=level) + _params: dict[str, Any] = {} + _params["level"] = _encode_value(level) await self._executor.execute( method=MemoryCommand.SIMULATE_PRESSURE_NOTIFICATION, - params=encode_cdp(params), + params=_params, ) async def start_sampling( @@ -113,13 +114,15 @@ async def start_sampling( """ Start collecting native memory profile. """ - params = StartSamplingParams( - sampling_interval=sampling_interval, suppress_randomness=suppress_randomness - ) + _params: dict[str, Any] = {} + if sampling_interval is not None: + _params["samplingInterval"] = sampling_interval + if suppress_randomness is not None: + _params["suppressRandomness"] = suppress_randomness await self._executor.execute( method=MemoryCommand.START_SAMPLING, - params=encode_cdp(params), + params=_params, ) async def stop_sampling( diff --git a/cdpify/domains/network/domain.py b/cdpify/domains/network/domain.py index 7fd531f7..36fea6d8 100644 --- a/cdpify/domains/network/domain.py +++ b/cdpify/domains/network/domain.py @@ -4,9 +4,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated @@ -14,46 +14,18 @@ CanClearBrowserCacheResult, CanClearBrowserCookiesResult, CanEmulateNetworkConditionsResult, - ConfigureDurableMessagesParams, - DeleteCookiesParams, - DeleteDeviceBoundSessionParams, - EmulateNetworkConditionsByRuleParams, EmulateNetworkConditionsByRuleResult, - EmulateNetworkConditionsParams, - EnableDeviceBoundSessionsParams, - EnableParams, - EnableReportingApiParams, - FetchSchemefulSiteParams, FetchSchemefulSiteResult, GetAllCookiesResult, - GetCertificateParams, GetCertificateResult, - GetCookiesParams, GetCookiesResult, - GetRequestPostDataParams, GetRequestPostDataResult, - GetResponseBodyParams, GetResponseBodyResult, - GetSecurityIsolationStatusParams, GetSecurityIsolationStatusResult, - LoadNetworkResourceParams, LoadNetworkResourceResult, NetworkCommand, - OverrideNetworkStateParams, - ReplayXHRParams, - SearchInResponseBodyParams, SearchInResponseBodyResult, - SetAttachDebugStackParams, - SetBlockedURLsParams, - SetBypassServiceWorkerParams, - SetCacheDisabledParams, - SetCookieControlsParams, - SetCookieParams, SetCookieResult, - SetCookiesParams, - SetExtraHTTPHeadersParams, - SetUserAgentOverrideParams, - StreamResourceContentParams, StreamResourceContentResult, ) from .types import ( @@ -154,13 +126,20 @@ async def delete_cookies( Deletes browser cookies with matching name and url or domain/path/partitionKey pair. """ - params = DeleteCookiesParams( - name=name, url=url, domain=domain, path=path, partition_key=partition_key - ) + _params: dict[str, Any] = {} + _params["name"] = name + if url is not None: + _params["url"] = url + if domain is not None: + _params["domain"] = domain + if path is not None: + _params["path"] = path + if partition_key is not None: + _params["partitionKey"] = _encode_value(partition_key) await self._executor.execute( method=NetworkCommand.DELETE_COOKIES, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -193,20 +172,23 @@ async def emulate_network_conditions( of the emulateNetworkConditionsByRule and overrideNetworkState commands, which can be used together to the same effect. """ - params = EmulateNetworkConditionsParams( - offline=offline, - latency=latency, - download_throughput=download_throughput, - upload_throughput=upload_throughput, - connection_type=connection_type, - packet_loss=packet_loss, - packet_queue_length=packet_queue_length, - packet_reordering=packet_reordering, - ) + _params: dict[str, Any] = {} + _params["offline"] = offline + _params["latency"] = latency + _params["downloadThroughput"] = download_throughput + _params["uploadThroughput"] = upload_throughput + if connection_type is not None: + _params["connectionType"] = _encode_value(connection_type) + if packet_loss is not None: + _params["packetLoss"] = packet_loss + if packet_queue_length is not None: + _params["packetQueueLength"] = packet_queue_length + if packet_reordering is not None: + _params["packetReordering"] = packet_reordering await self._executor.execute( method=NetworkCommand.EMULATE_NETWORK_CONDITIONS, - params=encode_cdp(params), + params=_params, ) async def emulate_network_conditions_by_rule( @@ -222,15 +204,16 @@ async def emulate_network_conditions_by_rule( method does not affect `navigator` state. Use Network.overrideNetworkState to explicitly modify `navigator` behavior. """ - params = EmulateNetworkConditionsByRuleParams( - offline=offline, - emulate_offline_service_worker=emulate_offline_service_worker, - matched_network_conditions=matched_network_conditions, - ) + _params: dict[str, Any] = {} + if offline is not None: + _params["offline"] = offline + if emulate_offline_service_worker is not None: + _params["emulateOfflineServiceWorker"] = emulate_offline_service_worker + _params["matchedNetworkConditions"] = _encode_value(matched_network_conditions) result = await self._executor.execute( method=NetworkCommand.EMULATE_NETWORK_CONDITIONS_BY_RULE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(EmulateNetworkConditionsByRuleResult, result) @@ -246,17 +229,17 @@ async def override_network_state( """ Override the state of navigator.onLine and navigator.connection. """ - params = OverrideNetworkStateParams( - offline=offline, - latency=latency, - download_throughput=download_throughput, - upload_throughput=upload_throughput, - connection_type=connection_type, - ) + _params: dict[str, Any] = {} + _params["offline"] = offline + _params["latency"] = latency + _params["downloadThroughput"] = download_throughput + _params["uploadThroughput"] = upload_throughput + if connection_type is not None: + _params["connectionType"] = _encode_value(connection_type) await self._executor.execute( method=NetworkCommand.OVERRIDE_NETWORK_STATE, - params=encode_cdp(params), + params=_params, ) async def enable( @@ -271,17 +254,21 @@ async def enable( """ Enables network tracking, network events will now be delivered to the client. """ - params = EnableParams( - max_total_buffer_size=max_total_buffer_size, - max_resource_buffer_size=max_resource_buffer_size, - max_post_data_size=max_post_data_size, - report_direct_socket_traffic=report_direct_socket_traffic, - enable_durable_messages=enable_durable_messages, - ) + _params: dict[str, Any] = {} + if max_total_buffer_size is not None: + _params["maxTotalBufferSize"] = max_total_buffer_size + if max_resource_buffer_size is not None: + _params["maxResourceBufferSize"] = max_resource_buffer_size + if max_post_data_size is not None: + _params["maxPostDataSize"] = max_post_data_size + if report_direct_socket_traffic is not None: + _params["reportDirectSocketTraffic"] = report_direct_socket_traffic + if enable_durable_messages is not None: + _params["enableDurableMessages"] = enable_durable_messages await self._executor.execute( method=NetworkCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def configure_durable_messages( @@ -295,14 +282,15 @@ async def configure_durable_messages( cross-process navigation. If maxTotalBufferSize is not set, durable messages are disabled. """ - params = ConfigureDurableMessagesParams( - max_total_buffer_size=max_total_buffer_size, - max_resource_buffer_size=max_resource_buffer_size, - ) + _params: dict[str, Any] = {} + if max_total_buffer_size is not None: + _params["maxTotalBufferSize"] = max_total_buffer_size + if max_resource_buffer_size is not None: + _params["maxResourceBufferSize"] = max_resource_buffer_size await self._executor.execute( method=NetworkCommand.CONFIGURE_DURABLE_MESSAGES, - params=encode_cdp(params), + params=_params, ) @deprecated("Network.getAllCookies is deprecated") @@ -328,11 +316,12 @@ async def get_certificate( """ Returns the DER-encoded certificate. """ - params = GetCertificateParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin result = await self._executor.execute( method=NetworkCommand.GET_CERTIFICATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCertificateResult, result) @@ -345,11 +334,13 @@ async def get_cookies( Returns all browser cookies for the current URL. Depending on the backend support, will return detailed cookie information in the `cookies` field. """ - params = GetCookiesParams(urls=urls) + _params: dict[str, Any] = {} + if urls is not None: + _params["urls"] = _encode_value(urls) result = await self._executor.execute( method=NetworkCommand.GET_COOKIES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCookiesResult, result) @@ -361,11 +352,12 @@ async def get_response_body( """ Returns content served for the given request. """ - params = GetResponseBodyParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) result = await self._executor.execute( method=NetworkCommand.GET_RESPONSE_BODY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetResponseBodyResult, result) @@ -378,11 +370,12 @@ async def get_request_post_data( Returns post data sent with the request. Returns an error when no data was sent with the request. """ - params = GetRequestPostDataParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) result = await self._executor.execute( method=NetworkCommand.GET_REQUEST_POST_DATA, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetRequestPostDataResult, result) @@ -396,11 +389,12 @@ async def replay_xhr( The following parameters should be identical: method, url, async, request body, extra headers, withCredentials attribute, user, password. """ - params = ReplayXHRParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) await self._executor.execute( method=NetworkCommand.REPLAY_XHR, - params=encode_cdp(params), + params=_params, ) async def search_in_response_body( @@ -414,16 +408,17 @@ async def search_in_response_body( """ Searches for given string in response content. """ - params = SearchInResponseBodyParams( - request_id=request_id, - query=query, - case_sensitive=case_sensitive, - is_regex=is_regex, - ) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) + _params["query"] = query + if case_sensitive is not None: + _params["caseSensitive"] = case_sensitive + if is_regex is not None: + _params["isRegex"] = is_regex result = await self._executor.execute( method=NetworkCommand.SEARCH_IN_RESPONSE_BODY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SearchInResponseBodyResult, result) @@ -436,11 +431,15 @@ async def set_blocked_ur_ls( """ Blocks URLs from loading. """ - params = SetBlockedURLsParams(url_patterns=url_patterns, urls=urls) + _params: dict[str, Any] = {} + if url_patterns is not None: + _params["urlPatterns"] = _encode_value(url_patterns) + if urls is not None: + _params["urls"] = _encode_value(urls) await self._executor.execute( method=NetworkCommand.SET_BLOCKED_UR_LS, - params=encode_cdp(params), + params=_params, ) async def set_bypass_service_worker( @@ -451,11 +450,12 @@ async def set_bypass_service_worker( """ Toggles ignoring of service worker for each request. """ - params = SetBypassServiceWorkerParams(bypass=bypass) + _params: dict[str, Any] = {} + _params["bypass"] = bypass await self._executor.execute( method=NetworkCommand.SET_BYPASS_SERVICE_WORKER, - params=encode_cdp(params), + params=_params, ) async def set_cache_disabled( @@ -466,11 +466,12 @@ async def set_cache_disabled( """ Toggles ignoring cache for each request. If `true`, cache will not be used. """ - params = SetCacheDisabledParams(cache_disabled=cache_disabled) + _params: dict[str, Any] = {} + _params["cacheDisabled"] = cache_disabled await self._executor.execute( method=NetworkCommand.SET_CACHE_DISABLED, - params=encode_cdp(params), + params=_params, ) async def set_cookie( @@ -494,25 +495,35 @@ async def set_cookie( Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist. """ - params = SetCookieParams( - name=name, - value=value, - url=url, - domain=domain, - path=path, - secure=secure, - http_only=http_only, - same_site=same_site, - expires=expires, - priority=priority, - source_scheme=source_scheme, - source_port=source_port, - partition_key=partition_key, - ) + _params: dict[str, Any] = {} + _params["name"] = name + _params["value"] = value + if url is not None: + _params["url"] = url + if domain is not None: + _params["domain"] = domain + if path is not None: + _params["path"] = path + if secure is not None: + _params["secure"] = secure + if http_only is not None: + _params["httpOnly"] = http_only + if same_site is not None: + _params["sameSite"] = _encode_value(same_site) + if expires is not None: + _params["expires"] = _encode_value(expires) + if priority is not None: + _params["priority"] = _encode_value(priority) + if source_scheme is not None: + _params["sourceScheme"] = _encode_value(source_scheme) + if source_port is not None: + _params["sourcePort"] = source_port + if partition_key is not None: + _params["partitionKey"] = _encode_value(partition_key) result = await self._executor.execute( method=NetworkCommand.SET_COOKIE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SetCookieResult, result) @@ -524,11 +535,12 @@ async def set_cookies( """ Sets given cookies. """ - params = SetCookiesParams(cookies=cookies) + _params: dict[str, Any] = {} + _params["cookies"] = _encode_value(cookies) await self._executor.execute( method=NetworkCommand.SET_COOKIES, - params=encode_cdp(params), + params=_params, ) async def set_extra_http_headers( @@ -540,11 +552,12 @@ async def set_extra_http_headers( Specifies whether to always send extra HTTP headers with the requests from this page. """ - params = SetExtraHTTPHeadersParams(headers=headers) + _params: dict[str, Any] = {} + _params["headers"] = _encode_value(headers) await self._executor.execute( method=NetworkCommand.SET_EXTRA_HTTP_HEADERS, - params=encode_cdp(params), + params=_params, ) async def set_attach_debug_stack( @@ -555,11 +568,12 @@ async def set_attach_debug_stack( """ Specifies whether to attach a page script stack id in requests """ - params = SetAttachDebugStackParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=NetworkCommand.SET_ATTACH_DEBUG_STACK, - params=encode_cdp(params), + params=_params, ) async def set_user_agent_override( @@ -573,16 +587,18 @@ async def set_user_agent_override( """ Allows overriding user agent with the given string. """ - params = SetUserAgentOverrideParams( - user_agent=user_agent, - accept_language=accept_language, - platform=platform, - user_agent_metadata=user_agent_metadata, - ) + _params: dict[str, Any] = {} + _params["userAgent"] = user_agent + if accept_language is not None: + _params["acceptLanguage"] = accept_language + if platform is not None: + _params["platform"] = platform + if user_agent_metadata is not None: + _params["userAgentMetadata"] = _encode_value(user_agent_metadata) await self._executor.execute( method=NetworkCommand.SET_USER_AGENT_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def stream_resource_content( @@ -594,11 +610,12 @@ async def stream_resource_content( Enables streaming of the response for the given requestId. If enabled, the dataReceived event contains the data that was received during streaming. """ - params = StreamResourceContentParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = _encode_value(request_id) result = await self._executor.execute( method=NetworkCommand.STREAM_RESOURCE_CONTENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(StreamResourceContentResult, result) @@ -610,11 +627,13 @@ async def get_security_isolation_status( """ Returns information about the COEP/COOP isolation status. """ - params = GetSecurityIsolationStatusParams(frame_id=frame_id) + _params: dict[str, Any] = {} + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=NetworkCommand.GET_SECURITY_ISOLATION_STATUS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetSecurityIsolationStatusResult, result) @@ -628,11 +647,12 @@ async def enable_reporting_api( will now be delivered to the client. Enabling triggers 'reportingApiReportAdded' for all existing reports. """ - params = EnableReportingApiParams(enable=enable) + _params: dict[str, Any] = {} + _params["enable"] = enable await self._executor.execute( method=NetworkCommand.ENABLE_REPORTING_API, - params=encode_cdp(params), + params=_params, ) async def enable_device_bound_sessions( @@ -643,11 +663,12 @@ async def enable_device_bound_sessions( """ Sets up tracking device bound sessions and fetching of initial set of sessions. """ - params = EnableDeviceBoundSessionsParams(enable=enable) + _params: dict[str, Any] = {} + _params["enable"] = enable await self._executor.execute( method=NetworkCommand.ENABLE_DEVICE_BOUND_SESSIONS, - params=encode_cdp(params), + params=_params, ) async def delete_device_bound_session( @@ -658,11 +679,12 @@ async def delete_device_bound_session( """ Deletes a device bound session. """ - params = DeleteDeviceBoundSessionParams(key=key) + _params: dict[str, Any] = {} + _params["key"] = _encode_value(key) await self._executor.execute( method=NetworkCommand.DELETE_DEVICE_BOUND_SESSION, - params=encode_cdp(params), + params=_params, ) async def fetch_schemeful_site( @@ -673,11 +695,12 @@ async def fetch_schemeful_site( """ Fetches the schemeful site for a specific origin. """ - params = FetchSchemefulSiteParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin result = await self._executor.execute( method=NetworkCommand.FETCH_SCHEMEFUL_SITE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(FetchSchemefulSiteResult, result) @@ -691,11 +714,15 @@ async def load_network_resource( """ Fetches the resource and returns the content. """ - params = LoadNetworkResourceParams(frame_id=frame_id, url=url, options=options) + _params: dict[str, Any] = {} + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) + _params["url"] = url + _params["options"] = _encode_value(options) result = await self._executor.execute( method=NetworkCommand.LOAD_NETWORK_RESOURCE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(LoadNetworkResourceResult, result) @@ -708,11 +735,12 @@ async def set_cookie_controls( Sets Controls for third-party cookie access Page reload is required before the new cookie behavior will be observed """ - params = SetCookieControlsParams( - enable_third_party_cookie_restriction=enable_third_party_cookie_restriction + _params: dict[str, Any] = {} + _params["enableThirdPartyCookieRestriction"] = ( + enable_third_party_cookie_restriction ) await self._executor.execute( method=NetworkCommand.SET_COOKIE_CONTROLS, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/overlay/domain.py b/cdpify/domains/overlay/domain.py index 5f063e97..10414d87 100644 --- a/cdpify/domains/overlay/domain.py +++ b/cdpify/domains/overlay/domain.py @@ -4,45 +4,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - GetGridHighlightObjectsForTestParams, GetGridHighlightObjectsForTestResult, - GetHighlightObjectForTestParams, GetHighlightObjectForTestResult, - GetSourceOrderHighlightObjectForTestParams, GetSourceOrderHighlightObjectForTestResult, - HighlightFrameParams, - HighlightNodeParams, - HighlightQuadParams, - HighlightRectParams, - HighlightSourceOrderParams, OverlayCommand, - SetInspectModeParams, - SetPausedInDebuggerMessageParams, - SetShowAdHighlightsParams, - SetShowContainerQueryOverlaysParams, - SetShowDebugBordersParams, - SetShowDisplayCutoutParams, - SetShowFlexOverlaysParams, - SetShowFPSCounterParams, - SetShowGridOverlaysParams, - SetShowHingeParams, - SetShowHitTestBordersParams, - SetShowInspectedElementAnchorParams, - SetShowIsolatedElementsParams, - SetShowLayoutShiftRegionsParams, - SetShowPaintRectsParams, - SetShowScrollBottleneckRectsParams, - SetShowScrollSnapOverlaysParams, - SetShowViewportSizeOnResizeParams, - SetShowWebVitalsParams, - SetShowWindowControlsOverlayParams, ) from .types import ( ColorFormat, @@ -102,17 +74,20 @@ async def get_highlight_object_for_test( """ For testing. """ - params = GetHighlightObjectForTestParams( - node_id=node_id, - include_distance=include_distance, - include_style=include_style, - color_format=color_format, - show_accessibility_info=show_accessibility_info, - ) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) + if include_distance is not None: + _params["includeDistance"] = include_distance + if include_style is not None: + _params["includeStyle"] = include_style + if color_format is not None: + _params["colorFormat"] = _encode_value(color_format) + if show_accessibility_info is not None: + _params["showAccessibilityInfo"] = show_accessibility_info result = await self._executor.execute( method=OverlayCommand.GET_HIGHLIGHT_OBJECT_FOR_TEST, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetHighlightObjectForTestResult, result) @@ -124,11 +99,12 @@ async def get_grid_highlight_objects_for_test( """ For Persistent Grid testing. """ - params = GetGridHighlightObjectsForTestParams(node_ids=node_ids) + _params: dict[str, Any] = {} + _params["nodeIds"] = _encode_value(node_ids) result = await self._executor.execute( method=OverlayCommand.GET_GRID_HIGHLIGHT_OBJECTS_FOR_TEST, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetGridHighlightObjectsForTestResult, result) @@ -140,11 +116,12 @@ async def get_source_order_highlight_object_for_test( """ For Source Order Viewer testing. """ - params = GetSourceOrderHighlightObjectForTestParams(node_id=node_id) + _params: dict[str, Any] = {} + _params["nodeId"] = _encode_value(node_id) result = await self._executor.execute( method=OverlayCommand.GET_SOURCE_ORDER_HIGHLIGHT_OBJECT_FOR_TEST, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetSourceOrderHighlightObjectForTestResult, result) @@ -173,15 +150,16 @@ async def highlight_frame( in a different process). Determine the owner node in the client and use highlightNode. """ - params = HighlightFrameParams( - frame_id=frame_id, - content_color=content_color, - content_outline_color=content_outline_color, - ) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + if content_color is not None: + _params["contentColor"] = _encode_value(content_color) + if content_outline_color is not None: + _params["contentOutlineColor"] = _encode_value(content_outline_color) await self._executor.execute( method=OverlayCommand.HIGHLIGHT_FRAME, - params=encode_cdp(params), + params=_params, ) async def highlight_node( @@ -197,17 +175,20 @@ async def highlight_node( Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified. """ - params = HighlightNodeParams( - highlight_config=highlight_config, - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - selector=selector, - ) + _params: dict[str, Any] = {} + _params["highlightConfig"] = _encode_value(highlight_config) + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if selector is not None: + _params["selector"] = selector await self._executor.execute( method=OverlayCommand.HIGHLIGHT_NODE, - params=encode_cdp(params), + params=_params, ) async def highlight_quad( @@ -221,13 +202,16 @@ async def highlight_quad( Highlights given quad. Coordinates are absolute with respect to the main frame viewport. """ - params = HighlightQuadParams( - quad=quad, color=color, outline_color=outline_color - ) + _params: dict[str, Any] = {} + _params["quad"] = _encode_value(quad) + if color is not None: + _params["color"] = _encode_value(color) + if outline_color is not None: + _params["outlineColor"] = _encode_value(outline_color) await self._executor.execute( method=OverlayCommand.HIGHLIGHT_QUAD, - params=encode_cdp(params), + params=_params, ) async def highlight_rect( @@ -246,18 +230,19 @@ async def highlight_rect( correctly. The coordinates currently have to be adjusted by the client if DPR is not 1 (see crbug.com/437807128). """ - params = HighlightRectParams( - x=x, - y=y, - width=width, - height=height, - color=color, - outline_color=outline_color, - ) + _params: dict[str, Any] = {} + _params["x"] = x + _params["y"] = y + _params["width"] = width + _params["height"] = height + if color is not None: + _params["color"] = _encode_value(color) + if outline_color is not None: + _params["outlineColor"] = _encode_value(outline_color) await self._executor.execute( method=OverlayCommand.HIGHLIGHT_RECT, - params=encode_cdp(params), + params=_params, ) async def highlight_source_order( @@ -273,16 +258,18 @@ async def highlight_source_order( with the given JavaScript object wrapper. Either nodeId or objectId must be specified. """ - params = HighlightSourceOrderParams( - source_order_config=source_order_config, - node_id=node_id, - backend_node_id=backend_node_id, - object_id=object_id, - ) + _params: dict[str, Any] = {} + _params["sourceOrderConfig"] = _encode_value(source_order_config) + if node_id is not None: + _params["nodeId"] = _encode_value(node_id) + if backend_node_id is not None: + _params["backendNodeId"] = _encode_value(backend_node_id) + if object_id is not None: + _params["objectId"] = _encode_value(object_id) await self._executor.execute( method=OverlayCommand.HIGHLIGHT_SOURCE_ORDER, - params=encode_cdp(params), + params=_params, ) async def set_inspect_mode( @@ -296,11 +283,14 @@ async def set_inspect_mode( are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection. """ - params = SetInspectModeParams(mode=mode, highlight_config=highlight_config) + _params: dict[str, Any] = {} + _params["mode"] = _encode_value(mode) + if highlight_config is not None: + _params["highlightConfig"] = _encode_value(highlight_config) await self._executor.execute( method=OverlayCommand.SET_INSPECT_MODE, - params=encode_cdp(params), + params=_params, ) async def set_show_ad_highlights( @@ -311,11 +301,12 @@ async def set_show_ad_highlights( """ Highlights owner element of all frames detected to be ads. """ - params = SetShowAdHighlightsParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_AD_HIGHLIGHTS, - params=encode_cdp(params), + params=_params, ) async def set_paused_in_debugger_message( @@ -323,11 +314,13 @@ async def set_paused_in_debugger_message( *, message: str | None = None, ) -> None: - params = SetPausedInDebuggerMessageParams(message=message) + _params: dict[str, Any] = {} + if message is not None: + _params["message"] = message await self._executor.execute( method=OverlayCommand.SET_PAUSED_IN_DEBUGGER_MESSAGE, - params=encode_cdp(params), + params=_params, ) async def set_show_debug_borders( @@ -338,11 +331,12 @@ async def set_show_debug_borders( """ Requests that backend shows debug borders on layers """ - params = SetShowDebugBordersParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_DEBUG_BORDERS, - params=encode_cdp(params), + params=_params, ) async def set_show_fps_counter( @@ -353,11 +347,12 @@ async def set_show_fps_counter( """ Requests that backend shows the FPS counter """ - params = SetShowFPSCounterParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_FPS_COUNTER, - params=encode_cdp(params), + params=_params, ) async def set_show_grid_overlays( @@ -368,13 +363,12 @@ async def set_show_grid_overlays( """ Highlight multiple elements with the CSS Grid overlay. """ - params = SetShowGridOverlaysParams( - grid_node_highlight_configs=grid_node_highlight_configs - ) + _params: dict[str, Any] = {} + _params["gridNodeHighlightConfigs"] = _encode_value(grid_node_highlight_configs) await self._executor.execute( method=OverlayCommand.SET_SHOW_GRID_OVERLAYS, - params=encode_cdp(params), + params=_params, ) async def set_show_flex_overlays( @@ -382,13 +376,12 @@ async def set_show_flex_overlays( *, flex_node_highlight_configs: list[FlexNodeHighlightConfig], ) -> None: - params = SetShowFlexOverlaysParams( - flex_node_highlight_configs=flex_node_highlight_configs - ) + _params: dict[str, Any] = {} + _params["flexNodeHighlightConfigs"] = _encode_value(flex_node_highlight_configs) await self._executor.execute( method=OverlayCommand.SET_SHOW_FLEX_OVERLAYS, - params=encode_cdp(params), + params=_params, ) async def set_show_scroll_snap_overlays( @@ -396,13 +389,14 @@ async def set_show_scroll_snap_overlays( *, scroll_snap_highlight_configs: list[ScrollSnapHighlightConfig], ) -> None: - params = SetShowScrollSnapOverlaysParams( - scroll_snap_highlight_configs=scroll_snap_highlight_configs + _params: dict[str, Any] = {} + _params["scrollSnapHighlightConfigs"] = _encode_value( + scroll_snap_highlight_configs ) await self._executor.execute( method=OverlayCommand.SET_SHOW_SCROLL_SNAP_OVERLAYS, - params=encode_cdp(params), + params=_params, ) async def set_show_container_query_overlays( @@ -410,13 +404,14 @@ async def set_show_container_query_overlays( *, container_query_highlight_configs: list[ContainerQueryHighlightConfig], ) -> None: - params = SetShowContainerQueryOverlaysParams( - container_query_highlight_configs=container_query_highlight_configs + _params: dict[str, Any] = {} + _params["containerQueryHighlightConfigs"] = _encode_value( + container_query_highlight_configs ) await self._executor.execute( method=OverlayCommand.SET_SHOW_CONTAINER_QUERY_OVERLAYS, - params=encode_cdp(params), + params=_params, ) async def set_show_inspected_element_anchor( @@ -424,13 +419,14 @@ async def set_show_inspected_element_anchor( *, inspected_element_anchor_config: InspectedElementAnchorConfig, ) -> None: - params = SetShowInspectedElementAnchorParams( - inspected_element_anchor_config=inspected_element_anchor_config + _params: dict[str, Any] = {} + _params["inspectedElementAnchorConfig"] = _encode_value( + inspected_element_anchor_config ) await self._executor.execute( method=OverlayCommand.SET_SHOW_INSPECTED_ELEMENT_ANCHOR, - params=encode_cdp(params), + params=_params, ) async def set_show_paint_rects( @@ -441,11 +437,12 @@ async def set_show_paint_rects( """ Requests that backend shows paint rectangles """ - params = SetShowPaintRectsParams(result=result) + _params: dict[str, Any] = {} + _params["result"] = result await self._executor.execute( method=OverlayCommand.SET_SHOW_PAINT_RECTS, - params=encode_cdp(params), + params=_params, ) async def set_show_layout_shift_regions( @@ -456,11 +453,12 @@ async def set_show_layout_shift_regions( """ Requests that backend shows layout shift regions """ - params = SetShowLayoutShiftRegionsParams(result=result) + _params: dict[str, Any] = {} + _params["result"] = result await self._executor.execute( method=OverlayCommand.SET_SHOW_LAYOUT_SHIFT_REGIONS, - params=encode_cdp(params), + params=_params, ) async def set_show_scroll_bottleneck_rects( @@ -471,11 +469,12 @@ async def set_show_scroll_bottleneck_rects( """ Requests that backend shows scroll bottleneck rects """ - params = SetShowScrollBottleneckRectsParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_SCROLL_BOTTLENECK_RECTS, - params=encode_cdp(params), + params=_params, ) @deprecated("Overlay.setShowHitTestBorders is deprecated") @@ -487,11 +486,12 @@ async def set_show_hit_test_borders( """ Deprecated, no longer has any effect. """ - params = SetShowHitTestBordersParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_HIT_TEST_BORDERS, - params=encode_cdp(params), + params=_params, ) @deprecated("Overlay.setShowWebVitals is deprecated") @@ -503,11 +503,12 @@ async def set_show_web_vitals( """ Deprecated, no longer has any effect. """ - params = SetShowWebVitalsParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_WEB_VITALS, - params=encode_cdp(params), + params=_params, ) async def set_show_viewport_size_on_resize( @@ -518,11 +519,12 @@ async def set_show_viewport_size_on_resize( """ Paints viewport size upon main frame resize. """ - params = SetShowViewportSizeOnResizeParams(show=show) + _params: dict[str, Any] = {} + _params["show"] = show await self._executor.execute( method=OverlayCommand.SET_SHOW_VIEWPORT_SIZE_ON_RESIZE, - params=encode_cdp(params), + params=_params, ) async def set_show_hinge( @@ -533,11 +535,13 @@ async def set_show_hinge( """ Add a dual screen device hinge """ - params = SetShowHingeParams(hinge_config=hinge_config) + _params: dict[str, Any] = {} + if hinge_config is not None: + _params["hingeConfig"] = _encode_value(hinge_config) await self._executor.execute( method=OverlayCommand.SET_SHOW_HINGE, - params=encode_cdp(params), + params=_params, ) async def set_show_display_cutout( @@ -548,11 +552,13 @@ async def set_show_display_cutout( """ Add a display cutout overlay. """ - params = SetShowDisplayCutoutParams(display_cutout_config=display_cutout_config) + _params: dict[str, Any] = {} + if display_cutout_config is not None: + _params["displayCutoutConfig"] = _encode_value(display_cutout_config) await self._executor.execute( method=OverlayCommand.SET_SHOW_DISPLAY_CUTOUT, - params=encode_cdp(params), + params=_params, ) async def set_show_isolated_elements( @@ -563,13 +569,14 @@ async def set_show_isolated_elements( """ Show elements in isolation mode with overlays. """ - params = SetShowIsolatedElementsParams( - isolated_element_highlight_configs=isolated_element_highlight_configs + _params: dict[str, Any] = {} + _params["isolatedElementHighlightConfigs"] = _encode_value( + isolated_element_highlight_configs ) await self._executor.execute( method=OverlayCommand.SET_SHOW_ISOLATED_ELEMENTS, - params=encode_cdp(params), + params=_params, ) async def set_show_window_controls_overlay( @@ -580,11 +587,13 @@ async def set_show_window_controls_overlay( """ Show Window Controls Overlay for PWA """ - params = SetShowWindowControlsOverlayParams( - window_controls_overlay_config=window_controls_overlay_config - ) + _params: dict[str, Any] = {} + if window_controls_overlay_config is not None: + _params["windowControlsOverlayConfig"] = _encode_value( + window_controls_overlay_config + ) await self._executor.execute( method=OverlayCommand.SET_SHOW_WINDOW_CONTROLS_OVERLAY, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/page/domain.py b/cdpify/domains/page/domain.py index a516baf0..27781f28 100644 --- a/cdpify/domains/page/domain.py +++ b/cdpify/domains/page/domain.py @@ -4,83 +4,38 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - AddCompilationCacheParams, - AddScriptToEvaluateOnLoadParams, AddScriptToEvaluateOnLoadResult, - AddScriptToEvaluateOnNewDocumentParams, AddScriptToEvaluateOnNewDocumentResult, - CaptureScreenshotParams, CaptureScreenshotResult, - CaptureSnapshotParams, CaptureSnapshotResult, - CreateIsolatedWorldParams, CreateIsolatedWorldResult, - DeleteCookieParams, - EnableParams, - GenerateTestReportParams, - GetAdScriptAncestryParams, GetAdScriptAncestryResult, - GetAnnotatedPageContentParams, GetAnnotatedPageContentResult, GetAppIdResult, - GetAppManifestParams, GetAppManifestResult, GetFrameTreeResult, GetInstallabilityErrorsResult, GetLayoutMetricsResult, GetManifestIconsResult, GetNavigationHistoryResult, - GetOriginTrialsParams, GetOriginTrialsResult, - GetPermissionsPolicyStateParams, GetPermissionsPolicyStateResult, - GetResourceContentParams, GetResourceContentResult, GetResourceTreeResult, GetSiblingSubAppsResult, - GetSpellCheckCustomDictionaryParams, GetSpellCheckCustomDictionaryResult, GetSubAppsResult, - HandleJavaScriptDialogParams, - NavigateParams, NavigateResult, - NavigateToHistoryEntryParams, PageCommand, - PrintToPDFParams, PrintToPDFResult, - ProduceCompilationCacheParams, - ReloadParams, - RemoveScriptToEvaluateOnLoadParams, - RemoveScriptToEvaluateOnNewDocumentParams, - ScreencastFrameAckParams, - SearchInResourceParams, SearchInResourceResult, - SetAdBlockingEnabledParams, - SetBypassCSPParams, - SetDeviceMetricsOverrideParams, - SetDeviceOrientationOverrideParams, - SetDocumentContentParams, - SetDownloadBehaviorParams, - SetFontFamiliesParams, - SetFontSizesParams, - SetGeolocationOverrideParams, - SetInputVisualizationParams, - SetInterceptFileChooserDialogParams, - SetLifecycleEventsEnabledParams, - SetPrerenderingAllowedParams, - SetRPHRegistrationModeParams, - SetSPCTransactionModeParams, - SetTouchEmulationEnabledParams, - SetWebLifecycleStateParams, - StartScreencastParams, - StartScreenRecordingParams, StartScreenRecordingResult, StopScreenRecordingResult, ) @@ -113,11 +68,12 @@ async def add_script_to_evaluate_on_load( """ Deprecated, please use addScriptToEvaluateOnNewDocument instead. """ - params = AddScriptToEvaluateOnLoadParams(script_source=script_source) + _params: dict[str, Any] = {} + _params["scriptSource"] = script_source result = await self._executor.execute( method=PageCommand.ADD_SCRIPT_TO_EVALUATE_ON_LOAD, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddScriptToEvaluateOnLoadResult, result) @@ -133,16 +89,18 @@ async def add_script_to_evaluate_on_new_document( Evaluates given script in every frame upon creation (before loading frame's scripts). """ - params = AddScriptToEvaluateOnNewDocumentParams( - source=source, - world_name=world_name, - include_command_line_api=include_command_line_api, - run_immediately=run_immediately, - ) + _params: dict[str, Any] = {} + _params["source"] = source + if world_name is not None: + _params["worldName"] = world_name + if include_command_line_api is not None: + _params["includeCommandLineAPI"] = include_command_line_api + if run_immediately is not None: + _params["runImmediately"] = run_immediately result = await self._executor.execute( method=PageCommand.ADD_SCRIPT_TO_EVALUATE_ON_NEW_DOCUMENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddScriptToEvaluateOnNewDocumentResult, result) @@ -170,18 +128,23 @@ async def capture_screenshot( """ Capture page screenshot. """ - params = CaptureScreenshotParams( - format=format, - quality=quality, - clip=clip, - from_surface=from_surface, - capture_beyond_viewport=capture_beyond_viewport, - optimize_for_speed=optimize_for_speed, - ) + _params: dict[str, Any] = {} + if format is not None: + _params["format"] = format + if quality is not None: + _params["quality"] = quality + if clip is not None: + _params["clip"] = _encode_value(clip) + if from_surface is not None: + _params["fromSurface"] = from_surface + if capture_beyond_viewport is not None: + _params["captureBeyondViewport"] = capture_beyond_viewport + if optimize_for_speed is not None: + _params["optimizeForSpeed"] = optimize_for_speed result = await self._executor.execute( method=PageCommand.CAPTURE_SCREENSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CaptureScreenshotResult, result) @@ -194,11 +157,13 @@ async def capture_snapshot( Returns a snapshot of the page as a string. For MHTML format, the serialization includes iframes, shadow DOM, external resources, and element-inline styles. """ - params = CaptureSnapshotParams(format=format) + _params: dict[str, Any] = {} + if format is not None: + _params["format"] = format result = await self._executor.execute( method=PageCommand.CAPTURE_SNAPSHOT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CaptureSnapshotResult, result) @@ -249,16 +214,18 @@ async def create_isolated_world( """ Creates an isolated world for the given frame. """ - params = CreateIsolatedWorldParams( - frame_id=frame_id, - world_name=world_name, - grant_univeral_access=grant_univeral_access, - content_security_policy=content_security_policy, - ) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + if world_name is not None: + _params["worldName"] = world_name + if grant_univeral_access is not None: + _params["grantUniveralAccess"] = grant_univeral_access + if content_security_policy is not None: + _params["contentSecurityPolicy"] = content_security_policy result = await self._executor.execute( method=PageCommand.CREATE_ISOLATED_WORLD, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CreateIsolatedWorldResult, result) @@ -272,11 +239,13 @@ async def delete_cookie( """ Deletes browser cookie with given name, domain and path. """ - params = DeleteCookieParams(cookie_name=cookie_name, url=url) + _params: dict[str, Any] = {} + _params["cookieName"] = cookie_name + _params["url"] = url await self._executor.execute( method=PageCommand.DELETE_COOKIE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -298,13 +267,13 @@ async def enable( """ Enables page domain notifications. """ - params = EnableParams( - enable_file_chooser_opened_event=enable_file_chooser_opened_event - ) + _params: dict[str, Any] = {} + if enable_file_chooser_opened_event is not None: + _params["enableFileChooserOpenedEvent"] = enable_file_chooser_opened_event await self._executor.execute( method=PageCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def get_app_manifest( @@ -318,11 +287,13 @@ async def get_app_manifest( the manifest of the current document, this API errors out. If there is not a loaded page, this API errors out immediately. """ - params = GetAppManifestParams(manifest_id=manifest_id) + _params: dict[str, Any] = {} + if manifest_id is not None: + _params["manifestId"] = manifest_id result = await self._executor.execute( method=PageCommand.GET_APP_MANIFEST, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAppManifestResult, result) @@ -392,11 +363,12 @@ async def get_ad_script_ancestry( *, frame_id: FrameId, ) -> GetAdScriptAncestryResult: - params = GetAdScriptAncestryParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=PageCommand.GET_AD_SCRIPT_ANCESTRY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAdScriptAncestryResult, result) @@ -410,11 +382,12 @@ async def get_spell_check_custom_dictionary( dictionary with `document.spellCheckCustomDictionary.addWords()`, sorted. Page script cannot read the dictionary back; this lets developers inspect it. """ - params = GetSpellCheckCustomDictionaryParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=PageCommand.GET_SPELL_CHECK_CUSTOM_DICTIONARY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetSpellCheckCustomDictionaryResult, result) @@ -475,11 +448,13 @@ async def get_resource_content( """ Returns content of the given resource. """ - params = GetResourceContentParams(frame_id=frame_id, url=url) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + _params["url"] = url result = await self._executor.execute( method=PageCommand.GET_RESOURCE_CONTENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetResourceContentResult, result) @@ -505,11 +480,14 @@ async def handle_java_script_dialog( Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload). """ - params = HandleJavaScriptDialogParams(accept=accept, prompt_text=prompt_text) + _params: dict[str, Any] = {} + _params["accept"] = accept + if prompt_text is not None: + _params["promptText"] = prompt_text await self._executor.execute( method=PageCommand.HANDLE_JAVA_SCRIPT_DIALOG, - params=encode_cdp(params), + params=_params, ) async def navigate( @@ -524,17 +502,20 @@ async def navigate( """ Navigates current page to the given URL. """ - params = NavigateParams( - url=url, - referrer=referrer, - transition_type=transition_type, - frame_id=frame_id, - referrer_policy=referrer_policy, - ) + _params: dict[str, Any] = {} + _params["url"] = url + if referrer is not None: + _params["referrer"] = referrer + if transition_type is not None: + _params["transitionType"] = _encode_value(transition_type) + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) + if referrer_policy is not None: + _params["referrerPolicy"] = _encode_value(referrer_policy) result = await self._executor.execute( method=PageCommand.NAVIGATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(NavigateResult, result) @@ -546,11 +527,12 @@ async def navigate_to_history_entry( """ Navigates current page to the given history entry. """ - params = NavigateToHistoryEntryParams(entry_id=entry_id) + _params: dict[str, Any] = {} + _params["entryId"] = entry_id await self._executor.execute( method=PageCommand.NAVIGATE_TO_HISTORY_ENTRY, - params=encode_cdp(params), + params=_params, ) async def print_to_pdf( @@ -577,29 +559,45 @@ async def print_to_pdf( """ Print page as PDF. """ - params = PrintToPDFParams( - landscape=landscape, - display_header_footer=display_header_footer, - print_background=print_background, - scale=scale, - paper_width=paper_width, - paper_height=paper_height, - margin_top=margin_top, - margin_bottom=margin_bottom, - margin_left=margin_left, - margin_right=margin_right, - page_ranges=page_ranges, - header_template=header_template, - footer_template=footer_template, - prefer_css_page_size=prefer_css_page_size, - transfer_mode=transfer_mode, - generate_tagged_pdf=generate_tagged_pdf, - generate_document_outline=generate_document_outline, - ) + _params: dict[str, Any] = {} + if landscape is not None: + _params["landscape"] = landscape + if display_header_footer is not None: + _params["displayHeaderFooter"] = display_header_footer + if print_background is not None: + _params["printBackground"] = print_background + if scale is not None: + _params["scale"] = scale + if paper_width is not None: + _params["paperWidth"] = paper_width + if paper_height is not None: + _params["paperHeight"] = paper_height + if margin_top is not None: + _params["marginTop"] = margin_top + if margin_bottom is not None: + _params["marginBottom"] = margin_bottom + if margin_left is not None: + _params["marginLeft"] = margin_left + if margin_right is not None: + _params["marginRight"] = margin_right + if page_ranges is not None: + _params["pageRanges"] = page_ranges + if header_template is not None: + _params["headerTemplate"] = header_template + if footer_template is not None: + _params["footerTemplate"] = footer_template + if prefer_css_page_size is not None: + _params["preferCSSPageSize"] = prefer_css_page_size + if transfer_mode is not None: + _params["transferMode"] = transfer_mode + if generate_tagged_pdf is not None: + _params["generateTaggedPDF"] = generate_tagged_pdf + if generate_document_outline is not None: + _params["generateDocumentOutline"] = generate_document_outline result = await self._executor.execute( method=PageCommand.PRINT_TO_PDF, - params=encode_cdp(params), + params=_params, ) return decode_cdp(PrintToPDFResult, result) @@ -613,15 +611,17 @@ async def reload( """ Reloads given page optionally ignoring the cache. """ - params = ReloadParams( - ignore_cache=ignore_cache, - script_to_evaluate_on_load=script_to_evaluate_on_load, - loader_id=loader_id, - ) + _params: dict[str, Any] = {} + if ignore_cache is not None: + _params["ignoreCache"] = ignore_cache + if script_to_evaluate_on_load is not None: + _params["scriptToEvaluateOnLoad"] = script_to_evaluate_on_load + if loader_id is not None: + _params["loaderId"] = _encode_value(loader_id) await self._executor.execute( method=PageCommand.RELOAD, - params=encode_cdp(params), + params=_params, ) @deprecated("Page.removeScriptToEvaluateOnLoad is deprecated") @@ -633,11 +633,12 @@ async def remove_script_to_evaluate_on_load( """ Deprecated, please use removeScriptToEvaluateOnNewDocument instead. """ - params = RemoveScriptToEvaluateOnLoadParams(identifier=identifier) + _params: dict[str, Any] = {} + _params["identifier"] = _encode_value(identifier) await self._executor.execute( method=PageCommand.REMOVE_SCRIPT_TO_EVALUATE_ON_LOAD, - params=encode_cdp(params), + params=_params, ) async def remove_script_to_evaluate_on_new_document( @@ -648,11 +649,12 @@ async def remove_script_to_evaluate_on_new_document( """ Removes given script from the list. """ - params = RemoveScriptToEvaluateOnNewDocumentParams(identifier=identifier) + _params: dict[str, Any] = {} + _params["identifier"] = _encode_value(identifier) await self._executor.execute( method=PageCommand.REMOVE_SCRIPT_TO_EVALUATE_ON_NEW_DOCUMENT, - params=encode_cdp(params), + params=_params, ) async def screencast_frame_ack( @@ -663,11 +665,12 @@ async def screencast_frame_ack( """ Acknowledges that a screencast frame has been received by the frontend. """ - params = ScreencastFrameAckParams(session_id=session_id) + _params: dict[str, Any] = {} + _params["sessionId"] = session_id await self._executor.execute( method=PageCommand.SCREENCAST_FRAME_ACK, - params=encode_cdp(params), + params=_params, ) async def search_in_resource( @@ -682,17 +685,18 @@ async def search_in_resource( """ Searches for given string in resource content. """ - params = SearchInResourceParams( - frame_id=frame_id, - url=url, - query=query, - case_sensitive=case_sensitive, - is_regex=is_regex, - ) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + _params["url"] = url + _params["query"] = query + if case_sensitive is not None: + _params["caseSensitive"] = case_sensitive + if is_regex is not None: + _params["isRegex"] = is_regex result = await self._executor.execute( method=PageCommand.SEARCH_IN_RESOURCE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(SearchInResourceResult, result) @@ -704,11 +708,12 @@ async def set_ad_blocking_enabled( """ Enable Chrome's experimental ad filter on all sites. """ - params = SetAdBlockingEnabledParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=PageCommand.SET_AD_BLOCKING_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_bypass_csp( @@ -719,11 +724,12 @@ async def set_bypass_csp( """ Enable page Content Security Policy by-passing. """ - params = SetBypassCSPParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=PageCommand.SET_BYPASS_CSP, - params=encode_cdp(params), + params=_params, ) async def get_permissions_policy_state( @@ -734,11 +740,12 @@ async def get_permissions_policy_state( """ Get Permissions Policy state on given frame. """ - params = GetPermissionsPolicyStateParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=PageCommand.GET_PERMISSIONS_POLICY_STATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetPermissionsPolicyStateResult, result) @@ -750,11 +757,12 @@ async def get_origin_trials( """ Get Origin Trials on given frame. """ - params = GetOriginTrialsParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=PageCommand.GET_ORIGIN_TRIALS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetOriginTrialsResult, result) @@ -780,24 +788,31 @@ async def set_device_metrics_override( window.screen.height, window.innerWidth, window.innerHeight, and "device-width"/"device-height"-related CSS media query results). """ - params = SetDeviceMetricsOverrideParams( - width=width, - height=height, - device_scale_factor=device_scale_factor, - mobile=mobile, - scale=scale, - screen_width=screen_width, - screen_height=screen_height, - position_x=position_x, - position_y=position_y, - dont_set_visible_size=dont_set_visible_size, - screen_orientation=screen_orientation, - viewport=viewport, - ) + _params: dict[str, Any] = {} + _params["width"] = width + _params["height"] = height + _params["deviceScaleFactor"] = device_scale_factor + _params["mobile"] = mobile + if scale is not None: + _params["scale"] = scale + if screen_width is not None: + _params["screenWidth"] = screen_width + if screen_height is not None: + _params["screenHeight"] = screen_height + if position_x is not None: + _params["positionX"] = position_x + if position_y is not None: + _params["positionY"] = position_y + if dont_set_visible_size is not None: + _params["dontSetVisibleSize"] = dont_set_visible_size + if screen_orientation is not None: + _params["screenOrientation"] = _encode_value(screen_orientation) + if viewport is not None: + _params["viewport"] = _encode_value(viewport) await self._executor.execute( method=PageCommand.SET_DEVICE_METRICS_OVERRIDE, - params=encode_cdp(params), + params=_params, ) @deprecated("Page.setDeviceOrientationOverride is deprecated") @@ -811,11 +826,14 @@ async def set_device_orientation_override( """ Overrides the Device Orientation. """ - params = SetDeviceOrientationOverrideParams(alpha=alpha, beta=beta, gamma=gamma) + _params: dict[str, Any] = {} + _params["alpha"] = alpha + _params["beta"] = beta + _params["gamma"] = gamma await self._executor.execute( method=PageCommand.SET_DEVICE_ORIENTATION_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_font_families( @@ -827,13 +845,14 @@ async def set_font_families( """ Set generic font families. """ - params = SetFontFamiliesParams( - font_families=font_families, for_scripts=for_scripts - ) + _params: dict[str, Any] = {} + _params["fontFamilies"] = _encode_value(font_families) + if for_scripts is not None: + _params["forScripts"] = _encode_value(for_scripts) await self._executor.execute( method=PageCommand.SET_FONT_FAMILIES, - params=encode_cdp(params), + params=_params, ) async def set_font_sizes( @@ -844,11 +863,12 @@ async def set_font_sizes( """ Set default font sizes. """ - params = SetFontSizesParams(font_sizes=font_sizes) + _params: dict[str, Any] = {} + _params["fontSizes"] = _encode_value(font_sizes) await self._executor.execute( method=PageCommand.SET_FONT_SIZES, - params=encode_cdp(params), + params=_params, ) async def set_document_content( @@ -860,11 +880,13 @@ async def set_document_content( """ Sets given markup as the document's HTML. """ - params = SetDocumentContentParams(frame_id=frame_id, html=html) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + _params["html"] = html await self._executor.execute( method=PageCommand.SET_DOCUMENT_CONTENT, - params=encode_cdp(params), + params=_params, ) @deprecated("Page.setDownloadBehavior is deprecated") @@ -877,13 +899,14 @@ async def set_download_behavior( """ Set the behavior when downloading a file. """ - params = SetDownloadBehaviorParams( - behavior=behavior, download_path=download_path - ) + _params: dict[str, Any] = {} + _params["behavior"] = behavior + if download_path is not None: + _params["downloadPath"] = download_path await self._executor.execute( method=PageCommand.SET_DOWNLOAD_BEHAVIOR, - params=encode_cdp(params), + params=_params, ) @deprecated("Page.setGeolocationOverride is deprecated") @@ -898,13 +921,17 @@ async def set_geolocation_override( Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable. """ - params = SetGeolocationOverrideParams( - latitude=latitude, longitude=longitude, accuracy=accuracy - ) + _params: dict[str, Any] = {} + if latitude is not None: + _params["latitude"] = latitude + if longitude is not None: + _params["longitude"] = longitude + if accuracy is not None: + _params["accuracy"] = accuracy await self._executor.execute( method=PageCommand.SET_GEOLOCATION_OVERRIDE, - params=encode_cdp(params), + params=_params, ) async def set_lifecycle_events_enabled( @@ -915,11 +942,12 @@ async def set_lifecycle_events_enabled( """ Controls whether page will emit lifecycle events. """ - params = SetLifecycleEventsEnabledParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=PageCommand.SET_LIFECYCLE_EVENTS_ENABLED, - params=encode_cdp(params), + params=_params, ) @deprecated("Page.setTouchEmulationEnabled is deprecated") @@ -932,13 +960,14 @@ async def set_touch_emulation_enabled( """ Toggles mouse event-based touch event emulation. """ - params = SetTouchEmulationEnabledParams( - enabled=enabled, configuration=configuration - ) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + if configuration is not None: + _params["configuration"] = configuration await self._executor.execute( method=PageCommand.SET_TOUCH_EMULATION_ENABLED, - params=encode_cdp(params), + params=_params, ) async def start_screencast( @@ -955,19 +984,25 @@ async def start_screencast( """ Starts sending each frame using the `screencastFrame` event. """ - params = StartScreencastParams( - format=format, - quality=quality, - max_width=max_width, - max_height=max_height, - every_nth_frame=every_nth_frame, - max_frames_in_flight=max_frames_in_flight, - send_last_frame=send_last_frame, - ) + _params: dict[str, Any] = {} + if format is not None: + _params["format"] = format + if quality is not None: + _params["quality"] = quality + if max_width is not None: + _params["maxWidth"] = max_width + if max_height is not None: + _params["maxHeight"] = max_height + if every_nth_frame is not None: + _params["everyNthFrame"] = every_nth_frame + if max_frames_in_flight is not None: + _params["maxFramesInFlight"] = max_frames_in_flight + if send_last_frame is not None: + _params["sendLastFrame"] = send_last_frame await self._executor.execute( method=PageCommand.START_SCREENCAST, - params=encode_cdp(params), + params=_params, ) async def start_screen_recording( @@ -981,16 +1016,19 @@ async def start_screen_recording( """ Starts screencast video recording. """ - params = StartScreenRecordingParams( - audio=audio, - max_width=max_width, - max_height=max_height, - frame_rate=frame_rate, - ) + _params: dict[str, Any] = {} + if audio is not None: + _params["audio"] = audio + if max_width is not None: + _params["maxWidth"] = max_width + if max_height is not None: + _params["maxHeight"] = max_height + if frame_rate is not None: + _params["frameRate"] = frame_rate result = await self._executor.execute( method=PageCommand.START_SCREEN_RECORDING, - params=encode_cdp(params), + params=_params, ) return decode_cdp(StartScreenRecordingResult, result) @@ -1048,11 +1086,12 @@ async def set_web_lifecycle_state( Tries to update the web lifecycle state of the page. It will transition the page to the given state according to: https://github.com/WICG/web-lifecycle/ """ - params = SetWebLifecycleStateParams(state=state) + _params: dict[str, Any] = {} + _params["state"] = state await self._executor.execute( method=PageCommand.SET_WEB_LIFECYCLE_STATE, - params=encode_cdp(params), + params=_params, ) async def stop_screencast( @@ -1079,11 +1118,12 @@ async def produce_compilation_cache( discretion, based on internal heuristics. See also: `Page.compilationCacheProduced`. """ - params = ProduceCompilationCacheParams(scripts=scripts) + _params: dict[str, Any] = {} + _params["scripts"] = _encode_value(scripts) await self._executor.execute( method=PageCommand.PRODUCE_COMPILATION_CACHE, - params=encode_cdp(params), + params=_params, ) async def add_compilation_cache( @@ -1096,11 +1136,13 @@ async def add_compilation_cache( Seeds compilation cache for given url. Compilation cache does not survive cross-process navigation. """ - params = AddCompilationCacheParams(url=url, data=data) + _params: dict[str, Any] = {} + _params["url"] = url + _params["data"] = data await self._executor.execute( method=PageCommand.ADD_COMPILATION_CACHE, - params=encode_cdp(params), + params=_params, ) async def clear_compilation_cache( @@ -1129,11 +1171,12 @@ async def set_spc_transaction_mode( Sets the Secure Payment Confirmation transaction mode. https://w3c.github.io/secure-payment-confirmation/#sctn-automation-set-spc-transaction-mode """ - params = SetSPCTransactionModeParams(mode=mode) + _params: dict[str, Any] = {} + _params["mode"] = mode await self._executor.execute( method=PageCommand.SET_SPC_TRANSACTION_MODE, - params=encode_cdp(params), + params=_params, ) async def set_rph_registration_mode( @@ -1145,11 +1188,12 @@ async def set_rph_registration_mode( Extensions for Custom Handlers API: https://html.spec.whatwg.org/multipage/system-state.html#rph-automation """ - params = SetRPHRegistrationModeParams(mode=mode) + _params: dict[str, Any] = {} + _params["mode"] = mode await self._executor.execute( method=PageCommand.SET_RPH_REGISTRATION_MODE, - params=encode_cdp(params), + params=_params, ) async def generate_test_report( @@ -1161,11 +1205,14 @@ async def generate_test_report( """ Generates a report for testing. """ - params = GenerateTestReportParams(message=message, group=group) + _params: dict[str, Any] = {} + _params["message"] = message + if group is not None: + _params["group"] = group await self._executor.execute( method=PageCommand.GENERATE_TEST_REPORT, - params=encode_cdp(params), + params=_params, ) async def wait_for_debugger( @@ -1191,11 +1238,14 @@ async def set_intercept_file_chooser_dialog( file chooser interception is enabled, native file chooser dialog is not shown. Instead, a protocol event `Page.fileChooserOpened` is emitted. """ - params = SetInterceptFileChooserDialogParams(enabled=enabled, cancel=cancel) + _params: dict[str, Any] = {} + _params["enabled"] = enabled + if cancel is not None: + _params["cancel"] = cancel await self._executor.execute( method=PageCommand.SET_INTERCEPT_FILE_CHOOSER_DIALOG, - params=encode_cdp(params), + params=_params, ) async def set_prerendering_allowed( @@ -1210,11 +1260,12 @@ async def set_prerendering_allowed( for more details. TODO(https://crbug.com/1440085): Remove this once Puppeteer supports tab targets. """ - params = SetPrerenderingAllowedParams(is_allowed=is_allowed) + _params: dict[str, Any] = {} + _params["isAllowed"] = is_allowed await self._executor.execute( method=PageCommand.SET_PRERENDERING_ALLOWED, - params=encode_cdp(params), + params=_params, ) async def get_annotated_page_content( @@ -1226,13 +1277,13 @@ async def get_annotated_page_content( Get the annotated page content for the main frame. This is an experimental command that is subject to change. """ - params = GetAnnotatedPageContentParams( - include_actionable_information=include_actionable_information - ) + _params: dict[str, Any] = {} + if include_actionable_information is not None: + _params["includeActionableInformation"] = include_actionable_information result = await self._executor.execute( method=PageCommand.GET_ANNOTATED_PAGE_CONTENT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetAnnotatedPageContentResult, result) @@ -1244,9 +1295,11 @@ async def set_input_visualization( """ Configures input visualization on the page. """ - params = SetInputVisualizationParams(clicks=clicks) + _params: dict[str, Any] = {} + if clicks is not None: + _params["clicks"] = clicks await self._executor.execute( method=PageCommand.SET_INPUT_VISUALIZATION, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/performance/domain.py b/cdpify/domains/performance/domain.py index 6c061265..ab7861ef 100644 --- a/cdpify/domains/performance/domain.py +++ b/cdpify/domains/performance/domain.py @@ -4,17 +4,15 @@ from __future__ import annotations -from typing import Literal +from typing import Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - EnableParams, GetMetricsResult, PerformanceCommand, - SetTimeDomainParams, ) @@ -41,11 +39,13 @@ async def enable( """ Enable collecting and reporting metrics. """ - params = EnableParams(time_domain=time_domain) + _params: dict[str, Any] = {} + if time_domain is not None: + _params["timeDomain"] = time_domain await self._executor.execute( method=PerformanceCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) @deprecated("Performance.setTimeDomain is deprecated") @@ -59,11 +59,12 @@ async def set_time_domain( that this must be called before enabling metrics collection. Calling this method while metrics collection is enabled returns an error. """ - params = SetTimeDomainParams(time_domain=time_domain) + _params: dict[str, Any] = {} + _params["timeDomain"] = time_domain await self._executor.execute( method=PerformanceCommand.SET_TIME_DOMAIN, - params=encode_cdp(params), + params=_params, ) async def get_metrics( diff --git a/cdpify/domains/performancetimeline/domain.py b/cdpify/domains/performancetimeline/domain.py index ea2df8e6..e1bc50eb 100644 --- a/cdpify/domains/performancetimeline/domain.py +++ b/cdpify/domains/performancetimeline/domain.py @@ -4,11 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - EnableParams, PerformanceTimelineCommand, ) @@ -26,9 +27,10 @@ async def enable( Previously buffered events would be reported before method returns. See also: timelineEventAdded """ - params = EnableParams(event_types=event_types) + _params: dict[str, Any] = {} + _params["eventTypes"] = _encode_value(event_types) await self._executor.execute( method=PerformanceTimelineCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/profiler/domain.py b/cdpify/domains/profiler/domain.py index c957db91..5c0da83f 100644 --- a/cdpify/domains/profiler/domain.py +++ b/cdpify/domains/profiler/domain.py @@ -4,14 +4,14 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import decode_cdp from cdpify.executor import CommandExecutor from .commands import ( GetBestEffortCoverageResult, ProfilerCommand, - SetSamplingIntervalParams, - StartPreciseCoverageParams, StartPreciseCoverageResult, StopResult, TakePreciseCoverageResult, @@ -60,11 +60,12 @@ async def set_sampling_interval( Changes CPU profiler sampling interval. Must be called before CPU profiles recording started. """ - params = SetSamplingIntervalParams(interval=interval) + _params: dict[str, Any] = {} + _params["interval"] = interval await self._executor.execute( method=ProfilerCommand.SET_SAMPLING_INTERVAL, - params=encode_cdp(params), + params=_params, ) async def start( @@ -87,15 +88,17 @@ async def start_precise_coverage( enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters. """ - params = StartPreciseCoverageParams( - call_count=call_count, - detailed=detailed, - allow_triggered_updates=allow_triggered_updates, - ) + _params: dict[str, Any] = {} + if call_count is not None: + _params["callCount"] = call_count + if detailed is not None: + _params["detailed"] = detailed + if allow_triggered_updates is not None: + _params["allowTriggeredUpdates"] = allow_triggered_updates result = await self._executor.execute( method=ProfilerCommand.START_PRECISE_COVERAGE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(StartPreciseCoverageResult, result) diff --git a/cdpify/domains/pwa/domain.py b/cdpify/domains/pwa/domain.py index 5bd6769a..439cce1d 100644 --- a/cdpify/domains/pwa/domain.py +++ b/cdpify/domains/pwa/domain.py @@ -4,21 +4,16 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - ChangeAppUserSettingsParams, - GetOsAppStateParams, GetOsAppStateResult, - InstallParams, - LaunchFilesInAppParams, LaunchFilesInAppResult, - LaunchParams, LaunchResult, - OpenCurrentPageInAppParams, PWACommand, - UninstallParams, ) from .types import ( DisplayMode, @@ -37,11 +32,12 @@ async def get_os_app_state( """ Returns the following OS state for the given manifest id. """ - params = GetOsAppStateParams(manifest_id=manifest_id) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id result = await self._executor.execute( method=PWACommand.GET_OS_APP_STATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetOsAppStateResult, result) @@ -67,13 +63,14 @@ async def install( the entire sequence using Base32 without padding. If Chrome is not in IWA dev mode, the installation will fail, regardless of the state of the allowlist. """ - params = InstallParams( - manifest_id=manifest_id, install_url_or_bundle_url=install_url_or_bundle_url - ) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id + if install_url_or_bundle_url is not None: + _params["installUrlOrBundleUrl"] = install_url_or_bundle_url await self._executor.execute( method=PWACommand.INSTALL, - params=encode_cdp(params), + params=_params, ) async def uninstall( @@ -84,11 +81,12 @@ async def uninstall( """ Uninstalls the given manifest_id and closes any opened app windows. """ - params = UninstallParams(manifest_id=manifest_id) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id await self._executor.execute( method=PWACommand.UNINSTALL, - params=encode_cdp(params), + params=_params, ) async def launch( @@ -102,11 +100,14 @@ async def launch( default start url if it is provided. Returns a page Target.TargetID which can be used to attach to via Target.attachToTarget or similar APIs. """ - params = LaunchParams(manifest_id=manifest_id, url=url) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id + if url is not None: + _params["url"] = url result = await self._executor.execute( method=PWACommand.LAUNCH, - params=encode_cdp(params), + params=_params, ) return decode_cdp(LaunchResult, result) @@ -128,11 +129,13 @@ async def launch_files_in_app( handling one or more files. The order of the returned Target.TargetIDs is not guaranteed. TODO(crbug.com/339454034): Check the existences of the input files. """ - params = LaunchFilesInAppParams(manifest_id=manifest_id, files=files) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id + _params["files"] = _encode_value(files) result = await self._executor.execute( method=PWACommand.LAUNCH_FILES_IN_APP, - params=encode_cdp(params), + params=_params, ) return decode_cdp(LaunchFilesInAppResult, result) @@ -146,11 +149,12 @@ async def open_current_page_in_app( be called on a page target. This function returns immediately without waiting for the app to finish loading. """ - params = OpenCurrentPageInAppParams(manifest_id=manifest_id) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id await self._executor.execute( method=PWACommand.OPEN_CURRENT_PAGE_IN_APP, - params=encode_cdp(params), + params=_params, ) async def change_app_user_settings( @@ -168,13 +172,14 @@ async def change_app_user_settings( controlled by the users, they impact the way the browser handling the web apps. See the comment of each parameter. """ - params = ChangeAppUserSettingsParams( - manifest_id=manifest_id, - link_capturing=link_capturing, - display_mode=display_mode, - ) + _params: dict[str, Any] = {} + _params["manifestId"] = manifest_id + if link_capturing is not None: + _params["linkCapturing"] = link_capturing + if display_mode is not None: + _params["displayMode"] = _encode_value(display_mode) await self._executor.execute( method=PWACommand.CHANGE_APP_USER_SETTINGS, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/runtime/domain.py b/cdpify/domains/runtime/domain.py index d3d95dc9..5908d2d3 100644 --- a/cdpify/domains/runtime/domain.py +++ b/cdpify/domains/runtime/domain.py @@ -4,38 +4,24 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - AddBindingParams, - AwaitPromiseParams, AwaitPromiseResult, - CallFunctionOnParams, CallFunctionOnResult, - CompileScriptParams, CompileScriptResult, - EvaluateParams, EvaluateResult, - GetExceptionDetailsParams, GetExceptionDetailsResult, GetHeapUsageResult, GetIsolateIdResult, - GetPropertiesParams, GetPropertiesResult, - GlobalLexicalScopeNamesParams, GlobalLexicalScopeNamesResult, - QueryObjectsParams, QueryObjectsResult, - ReleaseObjectGroupParams, - ReleaseObjectParams, - RemoveBindingParams, - RunScriptParams, RunScriptResult, RuntimeCommand, - SetAsyncCallStackDepthParams, - SetCustomObjectFormatterEnabledParams, - SetMaxCallStackSizeToCaptureParams, ) from .types import ( CallArgument, @@ -61,15 +47,16 @@ async def await_promise( """ Add handler to promise with given promise object id. """ - params = AwaitPromiseParams( - promise_object_id=promise_object_id, - return_by_value=return_by_value, - generate_preview=generate_preview, - ) + _params: dict[str, Any] = {} + _params["promiseObjectId"] = _encode_value(promise_object_id) + if return_by_value is not None: + _params["returnByValue"] = return_by_value + if generate_preview is not None: + _params["generatePreview"] = generate_preview result = await self._executor.execute( method=RuntimeCommand.AWAIT_PROMISE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AwaitPromiseResult, result) @@ -94,25 +81,36 @@ async def call_function_on( Calls function with given declaration on the given object. Object group of the result is inherited from the target object. """ - params = CallFunctionOnParams( - function_declaration=function_declaration, - object_id=object_id, - arguments=arguments, - silent=silent, - return_by_value=return_by_value, - generate_preview=generate_preview, - user_gesture=user_gesture, - await_promise=await_promise, - execution_context_id=execution_context_id, - object_group=object_group, - throw_on_side_effect=throw_on_side_effect, - unique_context_id=unique_context_id, - serialization_options=serialization_options, - ) + _params: dict[str, Any] = {} + _params["functionDeclaration"] = function_declaration + if object_id is not None: + _params["objectId"] = _encode_value(object_id) + if arguments is not None: + _params["arguments"] = _encode_value(arguments) + if silent is not None: + _params["silent"] = silent + if return_by_value is not None: + _params["returnByValue"] = return_by_value + if generate_preview is not None: + _params["generatePreview"] = generate_preview + if user_gesture is not None: + _params["userGesture"] = user_gesture + if await_promise is not None: + _params["awaitPromise"] = await_promise + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) + if object_group is not None: + _params["objectGroup"] = object_group + if throw_on_side_effect is not None: + _params["throwOnSideEffect"] = throw_on_side_effect + if unique_context_id is not None: + _params["uniqueContextId"] = unique_context_id + if serialization_options is not None: + _params["serializationOptions"] = _encode_value(serialization_options) result = await self._executor.execute( method=RuntimeCommand.CALL_FUNCTION_ON, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CallFunctionOnResult, result) @@ -127,16 +125,16 @@ async def compile_script( """ Compiles expression. """ - params = CompileScriptParams( - expression=expression, - source_url=source_url, - persist_script=persist_script, - execution_context_id=execution_context_id, - ) + _params: dict[str, Any] = {} + _params["expression"] = expression + _params["sourceURL"] = source_url + _params["persistScript"] = persist_script + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) result = await self._executor.execute( method=RuntimeCommand.COMPILE_SCRIPT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CompileScriptResult, result) @@ -198,28 +196,42 @@ async def evaluate( """ Evaluates expression on global object. """ - params = EvaluateParams( - expression=expression, - object_group=object_group, - include_command_line_api=include_command_line_api, - silent=silent, - context_id=context_id, - return_by_value=return_by_value, - generate_preview=generate_preview, - user_gesture=user_gesture, - await_promise=await_promise, - throw_on_side_effect=throw_on_side_effect, - timeout=timeout, - disable_breaks=disable_breaks, - repl_mode=repl_mode, - allow_unsafe_eval_blocked_by_csp=allow_unsafe_eval_blocked_by_csp, - unique_context_id=unique_context_id, - serialization_options=serialization_options, - ) + _params: dict[str, Any] = {} + _params["expression"] = expression + if object_group is not None: + _params["objectGroup"] = object_group + if include_command_line_api is not None: + _params["includeCommandLineAPI"] = include_command_line_api + if silent is not None: + _params["silent"] = silent + if context_id is not None: + _params["contextId"] = _encode_value(context_id) + if return_by_value is not None: + _params["returnByValue"] = return_by_value + if generate_preview is not None: + _params["generatePreview"] = generate_preview + if user_gesture is not None: + _params["userGesture"] = user_gesture + if await_promise is not None: + _params["awaitPromise"] = await_promise + if throw_on_side_effect is not None: + _params["throwOnSideEffect"] = throw_on_side_effect + if timeout is not None: + _params["timeout"] = _encode_value(timeout) + if disable_breaks is not None: + _params["disableBreaks"] = disable_breaks + if repl_mode is not None: + _params["replMode"] = repl_mode + if allow_unsafe_eval_blocked_by_csp is not None: + _params["allowUnsafeEvalBlockedByCSP"] = allow_unsafe_eval_blocked_by_csp + if unique_context_id is not None: + _params["uniqueContextId"] = unique_context_id + if serialization_options is not None: + _params["serializationOptions"] = _encode_value(serialization_options) result = await self._executor.execute( method=RuntimeCommand.EVALUATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(EvaluateResult, result) @@ -261,17 +273,20 @@ async def get_properties( Returns properties of a given object. Object group of the result is inherited from the target object. """ - params = GetPropertiesParams( - object_id=object_id, - own_properties=own_properties, - accessor_properties_only=accessor_properties_only, - generate_preview=generate_preview, - non_indexed_properties_only=non_indexed_properties_only, - ) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) + if own_properties is not None: + _params["ownProperties"] = own_properties + if accessor_properties_only is not None: + _params["accessorPropertiesOnly"] = accessor_properties_only + if generate_preview is not None: + _params["generatePreview"] = generate_preview + if non_indexed_properties_only is not None: + _params["nonIndexedPropertiesOnly"] = non_indexed_properties_only result = await self._executor.execute( method=RuntimeCommand.GET_PROPERTIES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetPropertiesResult, result) @@ -283,13 +298,13 @@ async def global_lexical_scope_names( """ Returns all let, const and class variables from global scope. """ - params = GlobalLexicalScopeNamesParams( - execution_context_id=execution_context_id - ) + _params: dict[str, Any] = {} + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) result = await self._executor.execute( method=RuntimeCommand.GLOBAL_LEXICAL_SCOPE_NAMES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GlobalLexicalScopeNamesResult, result) @@ -299,13 +314,14 @@ async def query_objects( prototype_object_id: RemoteObjectId, object_group: str | None = None, ) -> QueryObjectsResult: - params = QueryObjectsParams( - prototype_object_id=prototype_object_id, object_group=object_group - ) + _params: dict[str, Any] = {} + _params["prototypeObjectId"] = _encode_value(prototype_object_id) + if object_group is not None: + _params["objectGroup"] = object_group result = await self._executor.execute( method=RuntimeCommand.QUERY_OBJECTS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(QueryObjectsResult, result) @@ -317,11 +333,12 @@ async def release_object( """ Releases remote object with given id. """ - params = ReleaseObjectParams(object_id=object_id) + _params: dict[str, Any] = {} + _params["objectId"] = _encode_value(object_id) await self._executor.execute( method=RuntimeCommand.RELEASE_OBJECT, - params=encode_cdp(params), + params=_params, ) async def release_object_group( @@ -332,11 +349,12 @@ async def release_object_group( """ Releases all remote objects that belong to a given group. """ - params = ReleaseObjectGroupParams(object_group=object_group) + _params: dict[str, Any] = {} + _params["objectGroup"] = object_group await self._executor.execute( method=RuntimeCommand.RELEASE_OBJECT_GROUP, - params=encode_cdp(params), + params=_params, ) async def run_if_waiting_for_debugger( @@ -365,20 +383,26 @@ async def run_script( """ Runs script with given id in a given context. """ - params = RunScriptParams( - script_id=script_id, - execution_context_id=execution_context_id, - object_group=object_group, - silent=silent, - include_command_line_api=include_command_line_api, - return_by_value=return_by_value, - generate_preview=generate_preview, - await_promise=await_promise, - ) + _params: dict[str, Any] = {} + _params["scriptId"] = _encode_value(script_id) + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) + if object_group is not None: + _params["objectGroup"] = object_group + if silent is not None: + _params["silent"] = silent + if include_command_line_api is not None: + _params["includeCommandLineAPI"] = include_command_line_api + if return_by_value is not None: + _params["returnByValue"] = return_by_value + if generate_preview is not None: + _params["generatePreview"] = generate_preview + if await_promise is not None: + _params["awaitPromise"] = await_promise result = await self._executor.execute( method=RuntimeCommand.RUN_SCRIPT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RunScriptResult, result) @@ -390,11 +414,12 @@ async def set_async_call_stack_depth( """ Enables or disables async call stacks tracking. """ - params = SetAsyncCallStackDepthParams(max_depth=max_depth) + _params: dict[str, Any] = {} + _params["maxDepth"] = max_depth await self._executor.execute( method=RuntimeCommand.SET_ASYNC_CALL_STACK_DEPTH, - params=encode_cdp(params), + params=_params, ) async def set_custom_object_formatter_enabled( @@ -402,11 +427,12 @@ async def set_custom_object_formatter_enabled( *, enabled: bool, ) -> None: - params = SetCustomObjectFormatterEnabledParams(enabled=enabled) + _params: dict[str, Any] = {} + _params["enabled"] = enabled await self._executor.execute( method=RuntimeCommand.SET_CUSTOM_OBJECT_FORMATTER_ENABLED, - params=encode_cdp(params), + params=_params, ) async def set_max_call_stack_size_to_capture( @@ -414,11 +440,12 @@ async def set_max_call_stack_size_to_capture( *, size: int, ) -> None: - params = SetMaxCallStackSizeToCaptureParams(size=size) + _params: dict[str, Any] = {} + _params["size"] = size await self._executor.execute( method=RuntimeCommand.SET_MAX_CALL_STACK_SIZE_TO_CAPTURE, - params=encode_cdp(params), + params=_params, ) async def terminate_execution( @@ -447,15 +474,16 @@ async def add_binding( should be string, in case of any other input, function throws an exception. Each binding function call produces Runtime.bindingCalled notification. """ - params = AddBindingParams( - name=name, - execution_context_id=execution_context_id, - execution_context_name=execution_context_name, - ) + _params: dict[str, Any] = {} + _params["name"] = name + if execution_context_id is not None: + _params["executionContextId"] = _encode_value(execution_context_id) + if execution_context_name is not None: + _params["executionContextName"] = execution_context_name await self._executor.execute( method=RuntimeCommand.ADD_BINDING, - params=encode_cdp(params), + params=_params, ) async def remove_binding( @@ -467,11 +495,12 @@ async def remove_binding( This method does not remove binding function from global object but unsubscribes current runtime agent from Runtime.bindingCalled notifications. """ - params = RemoveBindingParams(name=name) + _params: dict[str, Any] = {} + _params["name"] = name await self._executor.execute( method=RuntimeCommand.REMOVE_BINDING, - params=encode_cdp(params), + params=_params, ) async def get_exception_details( @@ -485,10 +514,11 @@ async def get_exception_details( will only be populated if the Runtime domain was enabled at the time when the Error was thrown. """ - params = GetExceptionDetailsParams(error_object_id=error_object_id) + _params: dict[str, Any] = {} + _params["errorObjectId"] = _encode_value(error_object_id) result = await self._executor.execute( method=RuntimeCommand.GET_EXCEPTION_DETAILS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetExceptionDetailsResult, result) diff --git a/cdpify/domains/security/domain.py b/cdpify/domains/security/domain.py index b3c41949..8bd2ef1c 100644 --- a/cdpify/domains/security/domain.py +++ b/cdpify/domains/security/domain.py @@ -4,15 +4,14 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - HandleCertificateErrorParams, SecurityCommand, - SetIgnoreCertificateErrorsParams, - SetOverrideCertificateErrorsParams, ) from .types import ( CertificateErrorAction, @@ -53,11 +52,12 @@ async def set_ignore_certificate_errors( """ Enable/disable whether all certificate errors should be ignored. """ - params = SetIgnoreCertificateErrorsParams(ignore=ignore) + _params: dict[str, Any] = {} + _params["ignore"] = ignore await self._executor.execute( method=SecurityCommand.SET_IGNORE_CERTIFICATE_ERRORS, - params=encode_cdp(params), + params=_params, ) @deprecated("Security.handleCertificateError is deprecated") @@ -70,11 +70,13 @@ async def handle_certificate_error( """ Handles a certificate error that fired a certificateError event. """ - params = HandleCertificateErrorParams(event_id=event_id, action=action) + _params: dict[str, Any] = {} + _params["eventId"] = event_id + _params["action"] = _encode_value(action) await self._executor.execute( method=SecurityCommand.HANDLE_CERTIFICATE_ERROR, - params=encode_cdp(params), + params=_params, ) @deprecated("Security.setOverrideCertificateErrors is deprecated") @@ -88,9 +90,10 @@ async def set_override_certificate_errors( events need to be handled by the DevTools client and should be answered with `handleCertificateError` commands. """ - params = SetOverrideCertificateErrorsParams(override=override) + _params: dict[str, Any] = {} + _params["override"] = override await self._executor.execute( method=SecurityCommand.SET_OVERRIDE_CERTIFICATE_ERRORS, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/serviceworker/domain.py b/cdpify/domains/serviceworker/domain.py index 8a772e4a..d995c06a 100644 --- a/cdpify/domains/serviceworker/domain.py +++ b/cdpify/domains/serviceworker/domain.py @@ -4,20 +4,13 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - DeliverPushMessageParams, - DispatchPeriodicSyncEventParams, - DispatchSyncEventParams, ServiceWorkerCommand, - SetForceUpdateOnPageLoadParams, - SkipWaitingParams, - StartWorkerParams, - StopWorkerParams, - UnregisterParams, - UpdateRegistrationParams, ) from .types import ( RegistrationID, @@ -35,13 +28,14 @@ async def deliver_push_message( registration_id: RegistrationID, data: str, ) -> None: - params = DeliverPushMessageParams( - origin=origin, registration_id=registration_id, data=data - ) + _params: dict[str, Any] = {} + _params["origin"] = origin + _params["registrationId"] = _encode_value(registration_id) + _params["data"] = data await self._executor.execute( method=ServiceWorkerCommand.DELIVER_PUSH_MESSAGE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -60,16 +54,15 @@ async def dispatch_sync_event( tag: str, last_chance: bool, ) -> None: - params = DispatchSyncEventParams( - origin=origin, - registration_id=registration_id, - tag=tag, - last_chance=last_chance, - ) + _params: dict[str, Any] = {} + _params["origin"] = origin + _params["registrationId"] = _encode_value(registration_id) + _params["tag"] = tag + _params["lastChance"] = last_chance await self._executor.execute( method=ServiceWorkerCommand.DISPATCH_SYNC_EVENT, - params=encode_cdp(params), + params=_params, ) async def dispatch_periodic_sync_event( @@ -79,13 +72,14 @@ async def dispatch_periodic_sync_event( registration_id: RegistrationID, tag: str, ) -> None: - params = DispatchPeriodicSyncEventParams( - origin=origin, registration_id=registration_id, tag=tag - ) + _params: dict[str, Any] = {} + _params["origin"] = origin + _params["registrationId"] = _encode_value(registration_id) + _params["tag"] = tag await self._executor.execute( method=ServiceWorkerCommand.DISPATCH_PERIODIC_SYNC_EVENT, - params=encode_cdp(params), + params=_params, ) async def enable( @@ -101,13 +95,12 @@ async def set_force_update_on_page_load( *, force_update_on_page_load: bool, ) -> None: - params = SetForceUpdateOnPageLoadParams( - force_update_on_page_load=force_update_on_page_load - ) + _params: dict[str, Any] = {} + _params["forceUpdateOnPageLoad"] = force_update_on_page_load await self._executor.execute( method=ServiceWorkerCommand.SET_FORCE_UPDATE_ON_PAGE_LOAD, - params=encode_cdp(params), + params=_params, ) async def skip_waiting( @@ -115,11 +108,12 @@ async def skip_waiting( *, scope_url: str, ) -> None: - params = SkipWaitingParams(scope_url=scope_url) + _params: dict[str, Any] = {} + _params["scopeURL"] = scope_url await self._executor.execute( method=ServiceWorkerCommand.SKIP_WAITING, - params=encode_cdp(params), + params=_params, ) async def start_worker( @@ -127,11 +121,12 @@ async def start_worker( *, scope_url: str, ) -> None: - params = StartWorkerParams(scope_url=scope_url) + _params: dict[str, Any] = {} + _params["scopeURL"] = scope_url await self._executor.execute( method=ServiceWorkerCommand.START_WORKER, - params=encode_cdp(params), + params=_params, ) async def stop_all_workers( @@ -147,11 +142,12 @@ async def stop_worker( *, version_id: str, ) -> None: - params = StopWorkerParams(version_id=version_id) + _params: dict[str, Any] = {} + _params["versionId"] = version_id await self._executor.execute( method=ServiceWorkerCommand.STOP_WORKER, - params=encode_cdp(params), + params=_params, ) async def unregister( @@ -159,11 +155,12 @@ async def unregister( *, scope_url: str, ) -> None: - params = UnregisterParams(scope_url=scope_url) + _params: dict[str, Any] = {} + _params["scopeURL"] = scope_url await self._executor.execute( method=ServiceWorkerCommand.UNREGISTER, - params=encode_cdp(params), + params=_params, ) async def update_registration( @@ -171,9 +168,10 @@ async def update_registration( *, scope_url: str, ) -> None: - params = UpdateRegistrationParams(scope_url=scope_url) + _params: dict[str, Any] = {} + _params["scopeURL"] = scope_url await self._executor.execute( method=ServiceWorkerCommand.UPDATE_REGISTRATION, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/smartcardemulation/domain.py b/cdpify/domains/smartcardemulation/domain.py index d0b60328..050d4dff 100644 --- a/cdpify/domains/smartcardemulation/domain.py +++ b/cdpify/domains/smartcardemulation/domain.py @@ -4,20 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + +from cdpify.codec import _encode_value from cdpify.executor import CommandExecutor from .commands import ( - ReportBeginTransactionResultParams, - ReportConnectResultParams, - ReportDataResultParams, - ReportErrorParams, - ReportEstablishContextResultParams, - ReportGetStatusChangeResultParams, - ReportListReadersResultParams, - ReportPlainResultParams, - ReportReleaseContextResultParams, - ReportStatusResultParams, SmartCardEmulationCommand, ) from .types import ( @@ -67,13 +59,13 @@ async def report_establish_context_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardestablishcontext """ - params = ReportEstablishContextResultParams( - request_id=request_id, context_id=context_id - ) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["contextId"] = context_id await self._executor.execute( method=SmartCardEmulationCommand.REPORT_ESTABLISH_CONTEXT_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_release_context_result( @@ -88,11 +80,12 @@ async def report_release_context_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardreleasecontext """ - params = ReportReleaseContextResultParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = request_id await self._executor.execute( method=SmartCardEmulationCommand.REPORT_RELEASE_CONTEXT_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_list_readers_result( @@ -108,11 +101,13 @@ async def report_list_readers_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardlistreadersa """ - params = ReportListReadersResultParams(request_id=request_id, readers=readers) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["readers"] = _encode_value(readers) await self._executor.execute( method=SmartCardEmulationCommand.REPORT_LIST_READERS_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_get_status_change_result( @@ -128,13 +123,13 @@ async def report_get_status_change_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetstatuschangea """ - params = ReportGetStatusChangeResultParams( - request_id=request_id, reader_states=reader_states - ) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["readerStates"] = _encode_value(reader_states) await self._executor.execute( method=SmartCardEmulationCommand.REPORT_GET_STATUS_CHANGE_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_begin_transaction_result( @@ -150,13 +145,13 @@ async def report_begin_transaction_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardbegintransaction """ - params = ReportBeginTransactionResultParams( - request_id=request_id, handle=handle - ) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["handle"] = handle await self._executor.execute( method=SmartCardEmulationCommand.REPORT_BEGIN_TRANSACTION_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_plain_result( @@ -184,11 +179,12 @@ async def report_plain_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardendtransaction """ - params = ReportPlainResultParams(request_id=request_id) + _params: dict[str, Any] = {} + _params["requestId"] = request_id await self._executor.execute( method=SmartCardEmulationCommand.REPORT_PLAIN_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_connect_result( @@ -205,13 +201,15 @@ async def report_connect_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardconnecta """ - params = ReportConnectResultParams( - request_id=request_id, handle=handle, active_protocol=active_protocol - ) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["handle"] = handle + if active_protocol is not None: + _params["activeProtocol"] = _encode_value(active_protocol) await self._executor.execute( method=SmartCardEmulationCommand.REPORT_CONNECT_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_data_result( @@ -236,11 +234,13 @@ async def report_data_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetattrib """ - params = ReportDataResultParams(request_id=request_id, data=data) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["data"] = data await self._executor.execute( method=SmartCardEmulationCommand.REPORT_DATA_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_status_result( @@ -259,17 +259,17 @@ async def report_status_result( Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardstatusa """ - params = ReportStatusResultParams( - request_id=request_id, - reader_name=reader_name, - state=state, - atr=atr, - protocol=protocol, - ) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["readerName"] = reader_name + _params["state"] = _encode_value(state) + _params["atr"] = atr + if protocol is not None: + _params["protocol"] = _encode_value(protocol) await self._executor.execute( method=SmartCardEmulationCommand.REPORT_STATUS_RESULT, - params=encode_cdp(params), + params=_params, ) async def report_error( @@ -281,9 +281,11 @@ async def report_error( """ Reports an error result for the given request. """ - params = ReportErrorParams(request_id=request_id, result_code=result_code) + _params: dict[str, Any] = {} + _params["requestId"] = request_id + _params["resultCode"] = _encode_value(result_code) await self._executor.execute( method=SmartCardEmulationCommand.REPORT_ERROR, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/storage/domain.py b/cdpify/domains/storage/domain.py index b48df3c1..d77ffd2c 100644 --- a/cdpify/domains/storage/domain.py +++ b/cdpify/domains/storage/domain.py @@ -4,46 +4,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - ClearCookiesParams, - ClearDataForOriginParams, - ClearDataForStorageKeyParams, - ClearPrivateVerificationTokensParams, - ClearTrustTokensParams, ClearTrustTokensResult, - DeletePrivateVerificationTokenParams, - DeleteStorageBucketParams, - GetCookiesParams, GetCookiesResult, GetPrivateVerificationTokensIssuerConfigsResult, GetPrivateVerificationTokensResult, - GetStorageKeyForFrameParams, GetStorageKeyForFrameResult, - GetStorageKeyParams, GetStorageKeyResult, GetTrustTokensResult, - GetUsageAndQuotaParams, GetUsageAndQuotaResult, - OverrideQuotaForOriginParams, RunBounceTrackingMitigationsResult, - SetCookiesParams, - SetPrivateVerificationTokensTrackingParams, - SetStorageBucketTrackingParams, StorageCommand, - TrackCacheStorageForOriginParams, - TrackCacheStorageForStorageKeyParams, - TrackIndexedDBForOriginParams, - TrackIndexedDBForStorageKeyParams, - UntrackCacheStorageForOriginParams, - UntrackCacheStorageForStorageKeyParams, - UntrackIndexedDBForOriginParams, - UntrackIndexedDBForStorageKeyParams, ) from .types import ( StorageBucket, @@ -67,11 +44,12 @@ async def get_storage_key_for_frame( Returns a storage key given a frame id. Deprecated. Please use Storage.getStorageKey instead. """ - params = GetStorageKeyForFrameParams(frame_id=frame_id) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=StorageCommand.GET_STORAGE_KEY_FOR_FRAME, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetStorageKeyForFrameResult, result) @@ -84,11 +62,13 @@ async def get_storage_key( Returns storage key for the given frame. If no frame ID is provided, the storage key of the target executing this command is returned. """ - params = GetStorageKeyParams(frame_id=frame_id) + _params: dict[str, Any] = {} + if frame_id is not None: + _params["frameId"] = _encode_value(frame_id) result = await self._executor.execute( method=StorageCommand.GET_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetStorageKeyResult, result) @@ -101,11 +81,13 @@ async def clear_data_for_origin( """ Clears storage for origin. """ - params = ClearDataForOriginParams(origin=origin, storage_types=storage_types) + _params: dict[str, Any] = {} + _params["origin"] = origin + _params["storageTypes"] = storage_types await self._executor.execute( method=StorageCommand.CLEAR_DATA_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def clear_data_for_storage_key( @@ -117,13 +99,13 @@ async def clear_data_for_storage_key( """ Clears storage for storage key. """ - params = ClearDataForStorageKeyParams( - storage_key=storage_key, storage_types=storage_types - ) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key + _params["storageTypes"] = storage_types await self._executor.execute( method=StorageCommand.CLEAR_DATA_FOR_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) async def get_cookies( @@ -134,11 +116,13 @@ async def get_cookies( """ Returns all browser cookies. """ - params = GetCookiesParams(browser_context_id=browser_context_id) + _params: dict[str, Any] = {} + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) result = await self._executor.execute( method=StorageCommand.GET_COOKIES, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCookiesResult, result) @@ -151,13 +135,14 @@ async def set_cookies( """ Sets given cookies. """ - params = SetCookiesParams( - cookies=cookies, browser_context_id=browser_context_id - ) + _params: dict[str, Any] = {} + _params["cookies"] = _encode_value(cookies) + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=StorageCommand.SET_COOKIES, - params=encode_cdp(params), + params=_params, ) async def clear_cookies( @@ -168,11 +153,13 @@ async def clear_cookies( """ Clears cookies. """ - params = ClearCookiesParams(browser_context_id=browser_context_id) + _params: dict[str, Any] = {} + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=StorageCommand.CLEAR_COOKIES, - params=encode_cdp(params), + params=_params, ) async def get_usage_and_quota( @@ -183,11 +170,12 @@ async def get_usage_and_quota( """ Returns usage and quota in bytes. """ - params = GetUsageAndQuotaParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin result = await self._executor.execute( method=StorageCommand.GET_USAGE_AND_QUOTA, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetUsageAndQuotaResult, result) @@ -200,11 +188,14 @@ async def override_quota_for_origin( """ Override quota for the specified origin """ - params = OverrideQuotaForOriginParams(origin=origin, quota_size=quota_size) + _params: dict[str, Any] = {} + _params["origin"] = origin + if quota_size is not None: + _params["quotaSize"] = quota_size await self._executor.execute( method=StorageCommand.OVERRIDE_QUOTA_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def track_cache_storage_for_origin( @@ -216,11 +207,12 @@ async def track_cache_storage_for_origin( Registers origin to be notified when an update occurs to its cache storage list. """ - params = TrackCacheStorageForOriginParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin await self._executor.execute( method=StorageCommand.TRACK_CACHE_STORAGE_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def track_cache_storage_for_storage_key( @@ -232,11 +224,12 @@ async def track_cache_storage_for_storage_key( Registers storage key to be notified when an update occurs to its cache storage list. """ - params = TrackCacheStorageForStorageKeyParams(storage_key=storage_key) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key await self._executor.execute( method=StorageCommand.TRACK_CACHE_STORAGE_FOR_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) async def track_indexed_db_for_origin( @@ -247,11 +240,12 @@ async def track_indexed_db_for_origin( """ Registers origin to be notified when an update occurs to its IndexedDB. """ - params = TrackIndexedDBForOriginParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin await self._executor.execute( method=StorageCommand.TRACK_INDEXED_DB_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def track_indexed_db_for_storage_key( @@ -262,11 +256,12 @@ async def track_indexed_db_for_storage_key( """ Registers storage key to be notified when an update occurs to its IndexedDB. """ - params = TrackIndexedDBForStorageKeyParams(storage_key=storage_key) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key await self._executor.execute( method=StorageCommand.TRACK_INDEXED_DB_FOR_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) async def untrack_cache_storage_for_origin( @@ -277,11 +272,12 @@ async def untrack_cache_storage_for_origin( """ Unregisters origin from receiving notifications for cache storage. """ - params = UntrackCacheStorageForOriginParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin await self._executor.execute( method=StorageCommand.UNTRACK_CACHE_STORAGE_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def untrack_cache_storage_for_storage_key( @@ -292,11 +288,12 @@ async def untrack_cache_storage_for_storage_key( """ Unregisters storage key from receiving notifications for cache storage. """ - params = UntrackCacheStorageForStorageKeyParams(storage_key=storage_key) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key await self._executor.execute( method=StorageCommand.UNTRACK_CACHE_STORAGE_FOR_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) async def untrack_indexed_db_for_origin( @@ -307,11 +304,12 @@ async def untrack_indexed_db_for_origin( """ Unregisters origin from receiving notifications for IndexedDB. """ - params = UntrackIndexedDBForOriginParams(origin=origin) + _params: dict[str, Any] = {} + _params["origin"] = origin await self._executor.execute( method=StorageCommand.UNTRACK_INDEXED_DB_FOR_ORIGIN, - params=encode_cdp(params), + params=_params, ) async def untrack_indexed_db_for_storage_key( @@ -322,11 +320,12 @@ async def untrack_indexed_db_for_storage_key( """ Unregisters storage key from receiving notifications for IndexedDB. """ - params = UntrackIndexedDBForStorageKeyParams(storage_key=storage_key) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key await self._executor.execute( method=StorageCommand.UNTRACK_INDEXED_DB_FOR_STORAGE_KEY, - params=encode_cdp(params), + params=_params, ) async def get_trust_tokens( @@ -351,11 +350,12 @@ async def clear_trust_tokens( Removes all Trust Tokens issued by the provided issuerOrigin. Leaves other stored data, including the issuer's Redemption Records, intact. """ - params = ClearTrustTokensParams(issuer_origin=issuer_origin) + _params: dict[str, Any] = {} + _params["issuerOrigin"] = issuer_origin result = await self._executor.execute( method=StorageCommand.CLEAR_TRUST_TOKENS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(ClearTrustTokensResult, result) @@ -393,11 +393,12 @@ async def clear_private_verification_tokens( """ Removes all Private Verification Tokens issued by the provided issuerOrigin. """ - params = ClearPrivateVerificationTokensParams(issuer_origin=issuer_origin) + _params: dict[str, Any] = {} + _params["issuerOrigin"] = issuer_origin await self._executor.execute( method=StorageCommand.CLEAR_PRIVATE_VERIFICATION_TOKENS, - params=encode_cdp(params), + params=_params, ) async def delete_private_verification_token( @@ -408,11 +409,12 @@ async def delete_private_verification_token( """ Removes a specific Private Verification Token by its ID. """ - params = DeletePrivateVerificationTokenParams(token_id=token_id) + _params: dict[str, Any] = {} + _params["tokenId"] = token_id await self._executor.execute( method=StorageCommand.DELETE_PRIVATE_VERIFICATION_TOKEN, - params=encode_cdp(params), + params=_params, ) async def set_private_verification_tokens_tracking( @@ -423,11 +425,12 @@ async def set_private_verification_tokens_tracking( """ Set tracking for Private Verification Tokens. """ - params = SetPrivateVerificationTokensTrackingParams(enable=enable) + _params: dict[str, Any] = {} + _params["enable"] = enable await self._executor.execute( method=StorageCommand.SET_PRIVATE_VERIFICATION_TOKENS_TRACKING, - params=encode_cdp(params), + params=_params, ) async def set_storage_bucket_tracking( @@ -439,11 +442,13 @@ async def set_storage_bucket_tracking( """ Set tracking for a storage key's buckets. """ - params = SetStorageBucketTrackingParams(storage_key=storage_key, enable=enable) + _params: dict[str, Any] = {} + _params["storageKey"] = storage_key + _params["enable"] = enable await self._executor.execute( method=StorageCommand.SET_STORAGE_BUCKET_TRACKING, - params=encode_cdp(params), + params=_params, ) async def delete_storage_bucket( @@ -454,11 +459,12 @@ async def delete_storage_bucket( """ Deletes the Storage Bucket with the given storage key and bucket name. """ - params = DeleteStorageBucketParams(bucket=bucket) + _params: dict[str, Any] = {} + _params["bucket"] = _encode_value(bucket) await self._executor.execute( method=StorageCommand.DELETE_STORAGE_BUCKET, - params=encode_cdp(params), + params=_params, ) async def run_bounce_tracking_mitigations( diff --git a/cdpify/domains/systeminfo/domain.py b/cdpify/domains/systeminfo/domain.py index b049c5f7..4fb6e848 100644 --- a/cdpify/domains/systeminfo/domain.py +++ b/cdpify/domains/systeminfo/domain.py @@ -4,11 +4,12 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - GetFeatureStateParams, GetFeatureStateResult, GetInfoResult, GetProcessInfoResult, @@ -40,11 +41,12 @@ async def get_feature_state( """ Returns information about the feature state. """ - params = GetFeatureStateParams(feature_state=feature_state) + _params: dict[str, Any] = {} + _params["featureState"] = feature_state result = await self._executor.execute( method=SystemInfoCommand.GET_FEATURE_STATE, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetFeatureStateResult, result) diff --git a/cdpify/domains/target/domain.py b/cdpify/domains/target/domain.py index aeaba7c4..d80efb09 100644 --- a/cdpify/domains/target/domain.py +++ b/cdpify/domains/target/domain.py @@ -4,40 +4,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from cdpify.shared.decorators import deprecated from .commands import ( - ActivateTargetParams, AttachToBrowserTargetResult, - AttachToTargetParams, AttachToTargetResult, - AutoAttachRelatedParams, - CloseTargetParams, CloseTargetResult, - CreateBrowserContextParams, CreateBrowserContextResult, - CreateTargetParams, CreateTargetResult, - DetachFromTargetParams, - DisposeBrowserContextParams, - ExposeDevToolsProtocolParams, GetBrowserContextsResult, - GetDevToolsTargetParams, GetDevToolsTargetResult, - GetTargetInfoParams, GetTargetInfoResult, - GetTargetsParams, GetTargetsResult, - OpenDevToolsParams, OpenDevToolsResult, - SendMessageToTargetParams, - SetAutoAttachParams, - SetDiscoverTargetsParams, - SetRemoteLocationsParams, TargetCommand, ) from .types import ( @@ -64,11 +47,12 @@ async def activate_target( """ Activates (focuses) the target. """ - params = ActivateTargetParams(target_id=target_id) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) await self._executor.execute( method=TargetCommand.ACTIVATE_TARGET, - params=encode_cdp(params), + params=_params, ) async def attach_to_target( @@ -80,11 +64,14 @@ async def attach_to_target( """ Attaches to the target with given id. """ - params = AttachToTargetParams(target_id=target_id, flatten=flatten) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) + if flatten is not None: + _params["flatten"] = flatten result = await self._executor.execute( method=TargetCommand.ATTACH_TO_TARGET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AttachToTargetResult, result) @@ -108,11 +95,12 @@ async def close_target( """ Closes the target. If the target is a page that gets closed too. """ - params = CloseTargetParams(target_id=target_id) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) result = await self._executor.execute( method=TargetCommand.CLOSE_TARGET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CloseTargetResult, result) @@ -131,15 +119,16 @@ async def expose_dev_tools_protocol( handleMessage(json)` - a callback that will be called for the protocol notifications and command responses. """ - params = ExposeDevToolsProtocolParams( - target_id=target_id, - binding_name=binding_name, - inherit_permissions=inherit_permissions, - ) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) + if binding_name is not None: + _params["bindingName"] = binding_name + if inherit_permissions is not None: + _params["inheritPermissions"] = inherit_permissions await self._executor.execute( method=TargetCommand.EXPOSE_DEV_TOOLS_PROTOCOL, - params=encode_cdp(params), + params=_params, ) async def create_browser_context( @@ -154,16 +143,21 @@ async def create_browser_context( Creates a new empty BrowserContext. Similar to an incognito profile but you can have more than one. """ - params = CreateBrowserContextParams( - dispose_on_detach=dispose_on_detach, - proxy_server=proxy_server, - proxy_bypass_list=proxy_bypass_list, - origins_with_universal_network_access=origins_with_universal_network_access, - ) + _params: dict[str, Any] = {} + if dispose_on_detach is not None: + _params["disposeOnDetach"] = dispose_on_detach + if proxy_server is not None: + _params["proxyServer"] = proxy_server + if proxy_bypass_list is not None: + _params["proxyBypassList"] = proxy_bypass_list + if origins_with_universal_network_access is not None: + _params["originsWithUniversalNetworkAccess"] = _encode_value( + origins_with_universal_network_access + ) result = await self._executor.execute( method=TargetCommand.CREATE_BROWSER_CONTEXT, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CreateBrowserContextResult, result) @@ -199,25 +193,36 @@ async def create_target( """ Creates a new page. """ - params = CreateTargetParams( - url=url, - left=left, - top=top, - width=width, - height=height, - window_state=window_state, - browser_context_id=browser_context_id, - enable_begin_frame_control=enable_begin_frame_control, - new_window=new_window, - background=background, - for_tab=for_tab, - hidden=hidden, - focus=focus, - ) + _params: dict[str, Any] = {} + _params["url"] = url + if left is not None: + _params["left"] = left + if top is not None: + _params["top"] = top + if width is not None: + _params["width"] = width + if height is not None: + _params["height"] = height + if window_state is not None: + _params["windowState"] = _encode_value(window_state) + if browser_context_id is not None: + _params["browserContextId"] = _encode_value(browser_context_id) + if enable_begin_frame_control is not None: + _params["enableBeginFrameControl"] = enable_begin_frame_control + if new_window is not None: + _params["newWindow"] = new_window + if background is not None: + _params["background"] = background + if for_tab is not None: + _params["forTab"] = for_tab + if hidden is not None: + _params["hidden"] = hidden + if focus is not None: + _params["focus"] = focus result = await self._executor.execute( method=TargetCommand.CREATE_TARGET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(CreateTargetResult, result) @@ -230,11 +235,15 @@ async def detach_from_target( """ Detaches session with given id. """ - params = DetachFromTargetParams(session_id=session_id, target_id=target_id) + _params: dict[str, Any] = {} + if session_id is not None: + _params["sessionId"] = _encode_value(session_id) + if target_id is not None: + _params["targetId"] = _encode_value(target_id) await self._executor.execute( method=TargetCommand.DETACH_FROM_TARGET, - params=encode_cdp(params), + params=_params, ) async def dispose_browser_context( @@ -246,11 +255,12 @@ async def dispose_browser_context( Deletes a BrowserContext. All the belonging pages will be closed without calling their beforeunload hooks. """ - params = DisposeBrowserContextParams(browser_context_id=browser_context_id) + _params: dict[str, Any] = {} + _params["browserContextId"] = _encode_value(browser_context_id) await self._executor.execute( method=TargetCommand.DISPOSE_BROWSER_CONTEXT, - params=encode_cdp(params), + params=_params, ) async def get_target_info( @@ -261,11 +271,13 @@ async def get_target_info( """ Returns information about a target. """ - params = GetTargetInfoParams(target_id=target_id) + _params: dict[str, Any] = {} + if target_id is not None: + _params["targetId"] = _encode_value(target_id) result = await self._executor.execute( method=TargetCommand.GET_TARGET_INFO, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetTargetInfoResult, result) @@ -277,11 +289,13 @@ async def get_targets( """ Retrieves a list of available targets. """ - params = GetTargetsParams(filter=filter) + _params: dict[str, Any] = {} + if filter is not None: + _params["filter"] = _encode_value(filter) result = await self._executor.execute( method=TargetCommand.GET_TARGETS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetTargetsResult, result) @@ -297,13 +311,16 @@ async def send_message_to_target( Sends protocol message over session with given id. Consider using flat mode instead; see commands attachToTarget, setAutoAttach, and crbug.com/991325. """ - params = SendMessageToTargetParams( - message=message, session_id=session_id, target_id=target_id - ) + _params: dict[str, Any] = {} + _params["message"] = message + if session_id is not None: + _params["sessionId"] = _encode_value(session_id) + if target_id is not None: + _params["targetId"] = _encode_value(target_id) await self._executor.execute( method=TargetCommand.SEND_MESSAGE_TO_TARGET, - params=encode_cdp(params), + params=_params, ) async def set_auto_attach( @@ -323,16 +340,17 @@ async def set_auto_attach( creation of related targets. You might want to call this recursively for auto-attached targets to attach to all available targets. """ - params = SetAutoAttachParams( - auto_attach=auto_attach, - wait_for_debugger_on_start=wait_for_debugger_on_start, - flatten=flatten, - filter=filter, - ) + _params: dict[str, Any] = {} + _params["autoAttach"] = auto_attach + _params["waitForDebuggerOnStart"] = wait_for_debugger_on_start + if flatten is not None: + _params["flatten"] = flatten + if filter is not None: + _params["filter"] = _encode_value(filter) await self._executor.execute( method=TargetCommand.SET_AUTO_ATTACH, - params=encode_cdp(params), + params=_params, ) async def auto_attach_related( @@ -350,15 +368,15 @@ async def auto_attach_related( is also cancelled by subsequent `setAutoAttach`. Only available at the Browser target. """ - params = AutoAttachRelatedParams( - target_id=target_id, - wait_for_debugger_on_start=wait_for_debugger_on_start, - filter=filter, - ) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) + _params["waitForDebuggerOnStart"] = wait_for_debugger_on_start + if filter is not None: + _params["filter"] = _encode_value(filter) await self._executor.execute( method=TargetCommand.AUTO_ATTACH_RELATED, - params=encode_cdp(params), + params=_params, ) async def set_discover_targets( @@ -371,11 +389,14 @@ async def set_discover_targets( Controls whether to discover available targets and notify via `targetCreated/targetInfoChanged/targetDestroyed` events. """ - params = SetDiscoverTargetsParams(discover=discover, filter=filter) + _params: dict[str, Any] = {} + _params["discover"] = discover + if filter is not None: + _params["filter"] = _encode_value(filter) await self._executor.execute( method=TargetCommand.SET_DISCOVER_TARGETS, - params=encode_cdp(params), + params=_params, ) async def set_remote_locations( @@ -387,11 +408,12 @@ async def set_remote_locations( Enables target discovery for the specified locations, when `setDiscoverTargets` was set to `true`. """ - params = SetRemoteLocationsParams(locations=locations) + _params: dict[str, Any] = {} + _params["locations"] = _encode_value(locations) await self._executor.execute( method=TargetCommand.SET_REMOTE_LOCATIONS, - params=encode_cdp(params), + params=_params, ) async def get_dev_tools_target( @@ -403,11 +425,12 @@ async def get_dev_tools_target( Gets the targetId of the DevTools page target opened for the given target (if any). """ - params = GetDevToolsTargetParams(target_id=target_id) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) result = await self._executor.execute( method=TargetCommand.GET_DEV_TOOLS_TARGET, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetDevToolsTargetResult, result) @@ -420,10 +443,13 @@ async def open_dev_tools( """ Opens a DevTools window for the target. """ - params = OpenDevToolsParams(target_id=target_id, panel_id=panel_id) + _params: dict[str, Any] = {} + _params["targetId"] = _encode_value(target_id) + if panel_id is not None: + _params["panelId"] = panel_id result = await self._executor.execute( method=TargetCommand.OPEN_DEV_TOOLS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(OpenDevToolsResult, result) diff --git a/cdpify/domains/tethering/domain.py b/cdpify/domains/tethering/domain.py index 158dd0ae..6742b804 100644 --- a/cdpify/domains/tethering/domain.py +++ b/cdpify/domains/tethering/domain.py @@ -4,13 +4,12 @@ from __future__ import annotations -from cdpify.codec import encode_cdp +from typing import Any + from cdpify.executor import CommandExecutor from .commands import ( - BindParams, TetheringCommand, - UnbindParams, ) @@ -26,11 +25,12 @@ async def bind( """ Request browser port binding. """ - params = BindParams(port=port) + _params: dict[str, Any] = {} + _params["port"] = port await self._executor.execute( method=TetheringCommand.BIND, - params=encode_cdp(params), + params=_params, ) async def unbind( @@ -41,9 +41,10 @@ async def unbind( """ Request browser port unbinding. """ - params = UnbindParams(port=port) + _params: dict[str, Any] = {} + _params["port"] = port await self._executor.execute( method=TetheringCommand.UNBIND, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/tracing/domain.py b/cdpify/domains/tracing/domain.py index e95e8041..f033d445 100644 --- a/cdpify/domains/tracing/domain.py +++ b/cdpify/domains/tracing/domain.py @@ -4,18 +4,15 @@ from __future__ import annotations -from typing import Literal +from typing import Any, Literal -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( GetCategoriesResult, GetTrackEventDescriptorResult, - RecordClockSyncMarkerParams, - RequestMemoryDumpParams, RequestMemoryDumpResult, - StartParams, TracingCommand, ) from .types import ( @@ -74,11 +71,12 @@ async def record_clock_sync_marker( """ Record a clock sync marker in the trace. """ - params = RecordClockSyncMarkerParams(sync_id=sync_id) + _params: dict[str, Any] = {} + _params["syncId"] = sync_id await self._executor.execute( method=TracingCommand.RECORD_CLOCK_SYNC_MARKER, - params=encode_cdp(params), + params=_params, ) async def request_memory_dump( @@ -90,13 +88,15 @@ async def request_memory_dump( """ Request a global memory dump. """ - params = RequestMemoryDumpParams( - deterministic=deterministic, level_of_detail=level_of_detail - ) + _params: dict[str, Any] = {} + if deterministic is not None: + _params["deterministic"] = deterministic + if level_of_detail is not None: + _params["levelOfDetail"] = _encode_value(level_of_detail) result = await self._executor.execute( method=TracingCommand.REQUEST_MEMORY_DUMP, - params=encode_cdp(params), + params=_params, ) return decode_cdp(RequestMemoryDumpResult, result) @@ -118,21 +118,31 @@ async def start( """ Start trace events collection. """ - params = StartParams( - categories=categories, - options=options, - buffer_usage_reporting_interval=buffer_usage_reporting_interval, - transfer_mode=transfer_mode, - stream_format=stream_format, - stream_compression=stream_compression, - trace_config=trace_config, - perfetto_config=perfetto_config, - tracing_backend=tracing_backend, - screenshot_max_size=screenshot_max_size, - screenshot_max_count=screenshot_max_count, - ) + _params: dict[str, Any] = {} + if categories is not None: + _params["categories"] = categories + if options is not None: + _params["options"] = options + if buffer_usage_reporting_interval is not None: + _params["bufferUsageReportingInterval"] = buffer_usage_reporting_interval + if transfer_mode is not None: + _params["transferMode"] = transfer_mode + if stream_format is not None: + _params["streamFormat"] = _encode_value(stream_format) + if stream_compression is not None: + _params["streamCompression"] = _encode_value(stream_compression) + if trace_config is not None: + _params["traceConfig"] = _encode_value(trace_config) + if perfetto_config is not None: + _params["perfettoConfig"] = perfetto_config + if tracing_backend is not None: + _params["tracingBackend"] = _encode_value(tracing_backend) + if screenshot_max_size is not None: + _params["screenshotMaxSize"] = screenshot_max_size + if screenshot_max_count is not None: + _params["screenshotMaxCount"] = screenshot_max_count await self._executor.execute( method=TracingCommand.START, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/webaudio/domain.py b/cdpify/domains/webaudio/domain.py index 9f3aa4b2..b9c1865f 100644 --- a/cdpify/domains/webaudio/domain.py +++ b/cdpify/domains/webaudio/domain.py @@ -4,11 +4,12 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - GetRealtimeDataParams, GetRealtimeDataResult, WebAudioCommand, ) @@ -51,10 +52,11 @@ async def get_realtime_data( """ Fetch the realtime data from the registered contexts. """ - params = GetRealtimeDataParams(context_id=context_id) + _params: dict[str, Any] = {} + _params["contextId"] = _encode_value(context_id) result = await self._executor.execute( method=WebAudioCommand.GET_REALTIME_DATA, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetRealtimeDataResult, result) diff --git a/cdpify/domains/webauthn/domain.py b/cdpify/domains/webauthn/domain.py index b433d293..1679cefb 100644 --- a/cdpify/domains/webauthn/domain.py +++ b/cdpify/domains/webauthn/domain.py @@ -4,25 +4,15 @@ from __future__ import annotations -from cdpify.codec import decode_cdp, encode_cdp +from typing import Any + +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - AddCredentialParams, - AddVirtualAuthenticatorParams, AddVirtualAuthenticatorResult, - ClearCredentialsParams, - EnableParams, - GetCredentialParams, GetCredentialResult, - GetCredentialsParams, GetCredentialsResult, - RemoveCredentialParams, - RemoveVirtualAuthenticatorParams, - SetAutomaticPresenceSimulationParams, - SetCredentialPropertiesParams, - SetResponseOverrideBitsParams, - SetUserVerifiedParams, WebAuthnCommand, ) from .types import ( @@ -45,11 +35,13 @@ async def enable( Enable the WebAuthn domain and start intercepting credential storage and retrieval with a virtual authenticator. """ - params = EnableParams(enable_ui=enable_ui) + _params: dict[str, Any] = {} + if enable_ui is not None: + _params["enableUI"] = enable_ui await self._executor.execute( method=WebAuthnCommand.ENABLE, - params=encode_cdp(params), + params=_params, ) async def disable( @@ -71,11 +63,12 @@ async def add_virtual_authenticator( """ Creates and adds a virtual authenticator. """ - params = AddVirtualAuthenticatorParams(options=options) + _params: dict[str, Any] = {} + _params["options"] = _encode_value(options) result = await self._executor.execute( method=WebAuthnCommand.ADD_VIRTUAL_AUTHENTICATOR, - params=encode_cdp(params), + params=_params, ) return decode_cdp(AddVirtualAuthenticatorResult, result) @@ -91,16 +84,18 @@ async def set_response_override_bits( Resets parameters isBogusSignature, isBadUV, isBadUP to false if they are not present. """ - params = SetResponseOverrideBitsParams( - authenticator_id=authenticator_id, - is_bogus_signature=is_bogus_signature, - is_bad_uv=is_bad_uv, - is_bad_up=is_bad_up, - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + if is_bogus_signature is not None: + _params["isBogusSignature"] = is_bogus_signature + if is_bad_uv is not None: + _params["isBadUV"] = is_bad_uv + if is_bad_up is not None: + _params["isBadUP"] = is_bad_up await self._executor.execute( method=WebAuthnCommand.SET_RESPONSE_OVERRIDE_BITS, - params=encode_cdp(params), + params=_params, ) async def remove_virtual_authenticator( @@ -111,11 +106,12 @@ async def remove_virtual_authenticator( """ Removes the given authenticator. """ - params = RemoveVirtualAuthenticatorParams(authenticator_id=authenticator_id) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) await self._executor.execute( method=WebAuthnCommand.REMOVE_VIRTUAL_AUTHENTICATOR, - params=encode_cdp(params), + params=_params, ) async def add_credential( @@ -127,13 +123,13 @@ async def add_credential( """ Adds the credential to the specified authenticator. """ - params = AddCredentialParams( - authenticator_id=authenticator_id, credential=credential - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["credential"] = _encode_value(credential) await self._executor.execute( method=WebAuthnCommand.ADD_CREDENTIAL, - params=encode_cdp(params), + params=_params, ) async def get_credential( @@ -146,13 +142,13 @@ async def get_credential( Returns a single credential stored in the given virtual authenticator that matches the credential ID. """ - params = GetCredentialParams( - authenticator_id=authenticator_id, credential_id=credential_id - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["credentialId"] = credential_id result = await self._executor.execute( method=WebAuthnCommand.GET_CREDENTIAL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCredentialResult, result) @@ -164,11 +160,12 @@ async def get_credentials( """ Returns all the credentials stored in the given virtual authenticator. """ - params = GetCredentialsParams(authenticator_id=authenticator_id) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) result = await self._executor.execute( method=WebAuthnCommand.GET_CREDENTIALS, - params=encode_cdp(params), + params=_params, ) return decode_cdp(GetCredentialsResult, result) @@ -181,13 +178,13 @@ async def remove_credential( """ Removes a credential from the authenticator. """ - params = RemoveCredentialParams( - authenticator_id=authenticator_id, credential_id=credential_id - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["credentialId"] = credential_id await self._executor.execute( method=WebAuthnCommand.REMOVE_CREDENTIAL, - params=encode_cdp(params), + params=_params, ) async def clear_credentials( @@ -198,11 +195,12 @@ async def clear_credentials( """ Clears all the credentials from the specified device. """ - params = ClearCredentialsParams(authenticator_id=authenticator_id) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) await self._executor.execute( method=WebAuthnCommand.CLEAR_CREDENTIALS, - params=encode_cdp(params), + params=_params, ) async def set_user_verified( @@ -215,13 +213,13 @@ async def set_user_verified( Sets whether User Verification succeeds or fails for an authenticator. The default is true. """ - params = SetUserVerifiedParams( - authenticator_id=authenticator_id, is_user_verified=is_user_verified - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["isUserVerified"] = is_user_verified await self._executor.execute( method=WebAuthnCommand.SET_USER_VERIFIED, - params=encode_cdp(params), + params=_params, ) async def set_automatic_presence_simulation( @@ -234,13 +232,13 @@ async def set_automatic_presence_simulation( Sets whether tests of user presence will succeed immediately (if true) or fail to resolve (if false) for an authenticator. The default is true. """ - params = SetAutomaticPresenceSimulationParams( - authenticator_id=authenticator_id, enabled=enabled - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["enabled"] = enabled await self._executor.execute( method=WebAuthnCommand.SET_AUTOMATIC_PRESENCE_SIMULATION, - params=encode_cdp(params), + params=_params, ) async def set_credential_properties( @@ -258,17 +256,23 @@ async def set_credential_properties( Allows setting credential properties. https://w3c.github.io/webauthn/#sctn-automation-set-credential-properties """ - params = SetCredentialPropertiesParams( - authenticator_id=authenticator_id, - credential_id=credential_id, - backup_eligibility=backup_eligibility, - backup_state=backup_state, - active_cmtg_key_index=active_cmtg_key_index, - generate_cmtg_key_on_next_operation=generate_cmtg_key_on_next_operation, - sign_count=sign_count, - ) + _params: dict[str, Any] = {} + _params["authenticatorId"] = _encode_value(authenticator_id) + _params["credentialId"] = credential_id + if backup_eligibility is not None: + _params["backupEligibility"] = backup_eligibility + if backup_state is not None: + _params["backupState"] = backup_state + if active_cmtg_key_index is not None: + _params["activeCmtgKeyIndex"] = active_cmtg_key_index + if generate_cmtg_key_on_next_operation is not None: + _params["generateCmtgKeyOnNextOperation"] = ( + generate_cmtg_key_on_next_operation + ) + if sign_count is not None: + _params["signCount"] = sign_count await self._executor.execute( method=WebAuthnCommand.SET_CREDENTIAL_PROPERTIES, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/domains/webmcp/domain.py b/cdpify/domains/webmcp/domain.py index 1d3aed1c..6d7cc616 100644 --- a/cdpify/domains/webmcp/domain.py +++ b/cdpify/domains/webmcp/domain.py @@ -6,12 +6,10 @@ from typing import TYPE_CHECKING, Any -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor from .commands import ( - CancelInvocationParams, - InvokeToolParams, InvokeToolResult, WebMCPCommand, ) @@ -57,11 +55,14 @@ async def invoke_tool( """ Invokes a registered tool. """ - params = InvokeToolParams(frame_id=frame_id, tool_name=tool_name, input=input) + _params: dict[str, Any] = {} + _params["frameId"] = _encode_value(frame_id) + _params["toolName"] = tool_name + _params["input"] = _encode_value(input) result = await self._executor.execute( method=WebMCPCommand.INVOKE_TOOL, - params=encode_cdp(params), + params=_params, ) return decode_cdp(InvokeToolResult, result) @@ -73,9 +74,10 @@ async def cancel_invocation( """ Cancels a pending tool invocation. """ - params = CancelInvocationParams(invocation_id=invocation_id) + _params: dict[str, Any] = {} + _params["invocationId"] = invocation_id await self._executor.execute( method=WebMCPCommand.CANCEL_INVOCATION, - params=encode_cdp(params), + params=_params, ) diff --git a/cdpify/generator/generators/domain.py b/cdpify/generator/generators/domain.py index cf37f4e3..b612d045 100644 --- a/cdpify/generator/generators/domain.py +++ b/cdpify/generator/generators/domain.py @@ -21,6 +21,8 @@ def generate(domain: Domain) -> str: if ctx.cross_domain_modules: ctx.use_typing("TYPE_CHECKING") + if any(method.parameters for method in methods): + ctx.use_typing("Any") return render_template( "domain.py.jinja2", @@ -42,15 +44,10 @@ def _command_imports(domain: Domain) -> tuple[str, ...]: return () model_names = { - f"{to_pascal_case(command.name)}Params" - for command in domain.commands - if command.parameters - } - model_names.update( f"{to_pascal_case(command.name)}Result" for command in domain.commands if command.returns - ) + } return (f"{domain.domain}Command", *sorted(model_names)) @@ -104,4 +101,7 @@ def _build_parameter_view(parameter: Parameter, ctx: GenerationContext) -> Field name=to_identifier(parameter.name), annotation=annotation, optional=parameter.optional, + cdp_name=parameter.name, + encode_value=bool(parameter.ref) + or parameter.type in ("array", "object", "any"), ) diff --git a/cdpify/generator/generators/views.py b/cdpify/generator/generators/views.py index 43d57ba5..c78103da 100644 --- a/cdpify/generator/generators/views.py +++ b/cdpify/generator/generators/views.py @@ -1,5 +1,5 @@ from collections.abc import Iterable -from dataclasses import dataclass +from dataclasses import dataclass, replace @dataclass(frozen=True, slots=True) @@ -8,11 +8,20 @@ class FieldView: annotation: str optional: bool = False cdp_name: str | None = None + encode_value: bool = False @property def uses_unset(self) -> bool: return self.optional and self.annotation == "Any" + @property + def encoded_value(self) -> str: + return f"_encode_value({self.name})" if self.encode_value else self.name + + @property + def parameter_declaration(self) -> str: + return replace(self, cdp_name=None).declaration + @property def needs_field(self) -> bool: """Whether the declaration needs ``field()`` to keep the CDP wire name.""" @@ -83,6 +92,14 @@ class MethodView: constructor_args: str = "" result_model: str | None = None + @property + def wire_params_name(self) -> str: + name = "_params" + names = {p.name for p in self.parameters} + while name in names: + name += "_" + return name + @dataclass(frozen=True, slots=True) class DomainView: diff --git a/cdpify/generator/templates/domain.py.jinja2 b/cdpify/generator/templates/domain.py.jinja2 index 1db9c02f..adacbc84 100644 --- a/cdpify/generator/templates/domain.py.jinja2 +++ b/cdpify/generator/templates/domain.py.jinja2 @@ -6,7 +6,7 @@ from typing import {{ typing_names | join(", ") }} {% endif %} -from cdpify.codec import decode_cdp, encode_cdp +from cdpify.codec import _encode_value, decode_cdp from cdpify.executor import CommandExecutor {% if uses_unset %} from cdpify.unset import UNSET, UnsetType @@ -43,26 +43,34 @@ class {{ domain_name }}: self, {% if method.parameters %} *, - {{ method.parameters | map(attribute="declaration") | join(",\n ") }}, + {{ method.parameters | map(attribute="parameter_declaration") | join(",\n ") }}, {% endif %} ) -> {{ method.return_type }}: {% if method.docstring %} {{ method.docstring }} {% endif %} {% if method.params_model %} - params = {{ method.params_model }}({{ method.constructor_args }}) + {{ method.wire_params_name }}: dict[str, Any] = {} +{% for parameter in method.parameters %} +{% if parameter.optional %} + if {{ parameter.name }} is not {{ "UNSET" if parameter.uses_unset else "None" }}: + {{ method.wire_params_name }}["{{ parameter.cdp_name }}"] = {{ parameter.encoded_value }} +{% else %} + {{ method.wire_params_name }}["{{ parameter.cdp_name }}"] = {{ parameter.encoded_value }} +{% endif %} +{% endfor %} {% endif %} {% if method.result_model %} result = await self._executor.execute( method={{ domain_name }}Command.{{ method.command_member }}, - params={% if method.params_model %}encode_cdp(params){% else %}None{% endif %}, + params={% if method.params_model %}{{ method.wire_params_name }}{% else %}None{% endif %}, ) return decode_cdp({{ method.result_model }}, result) {% else %} await self._executor.execute( method={{ domain_name }}Command.{{ method.command_member }}, - params={% if method.params_model %}encode_cdp(params){% else %}None{% endif %}, + params={% if method.params_model %}{{ method.wire_params_name }}{% else %}None{% endif %}, ) {% endif %} {% endfor %} diff --git a/tests/generator/generators/test_domain_generator.py b/tests/generator/generators/test_domain_generator.py index 3bd35be9..30be4283 100644 --- a/tests/generator/generators/test_domain_generator.py +++ b/tests/generator/generators/test_domain_generator.py @@ -24,11 +24,12 @@ def test_method_with_params(simple_domain: Domain) -> None: assert "node_id: NodeId," in output assert "session_id: str | None = None," not in output assert "-> GetNodeResult:" in output - assert "params = GetNodeParams(node_id=node_id)" in output + assert "GetNodeParams(" not in output + assert '_params["nodeId"] = _encode_value(node_id)' in output assert "method=SampleCommand.GET_NODE" in output assert "result = await self._executor.execute(" in output - assert "from cdpify.codec import decode_cdp, encode_cdp" in output - assert "params=encode_cdp(params)" in output + assert "from cdpify.codec import _encode_value, decode_cdp" in output + assert "params=_params" in output assert "return decode_cdp(GetNodeResult, result)" in output @@ -79,7 +80,7 @@ def test_protocol_session_id_keeps_its_natural_name() -> None: output = domain_generator.generate(domain) assert "session_id: str," in output - assert "AttachToTargetParams(session_id=session_id)" in output + assert '_params["sessionId"] = session_id' in output def test_cross_domain_param_resolved_via_type_checking() -> None: @@ -105,9 +106,9 @@ def test_command_imports_include_enum_and_classes(simple_domain: Domain) -> None imports_section = output.split("from .commands import (")[1].split(")")[0] assert "SampleCommand" in imports_section - assert "GetNodeParams" in imports_section + assert "GetNodeParams" not in imports_section assert "GetNodeResult" in imports_section - assert "LegacyOpParams" in imports_section + assert "LegacyOpParams" not in imports_section def test_kw_only_marker_appears_when_command_has_params(simple_domain: Domain) -> None: diff --git a/tests/test_generated_params.py b/tests/test_generated_params.py new file mode 100644 index 00000000..c701474c --- /dev/null +++ b/tests/test_generated_params.py @@ -0,0 +1,40 @@ +from dataclasses import fields + +import pytest + +from cdpify import encode_cdp +from cdpify.domains.runtime import CallArgument, CallFunctionOnParams, EvaluateParams +from cdpify.domains.runtime.domain import Runtime + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "params,method", + [ + ( + EvaluateParams(expression="1", return_by_value=False, context_id=0), + "evaluate", + ), + ( + CallFunctionOnParams( + function_declaration="function(x) { return x; }", + arguments=[CallArgument(value=None)], + ), + "call_function_on", + ), + ( + CallFunctionOnParams(function_declaration="function() {}", arguments=[]), + "call_function_on", + ), + ], +) +async def test_generated_wire_params_match_public_params_models(params, method) -> None: + class Executor: + async def execute(self, *, method, params): + assert params == encode_cdp(expected) + return {"result": {"type": "undefined"}} + + expected = params + await getattr(Runtime(Executor()), method)( + **{f.name: getattr(params, f.name) for f in fields(params)} + ) From d4e0ae96e0e902f1f88023b9855dfb0fa7010522 Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:31:04 +0100 Subject: [PATCH 5/7] feat(events): expose raw streams and fail explicitly on bounded overflow --- CHANGELOG.md | 11 +++ cdpify/__init__.py | 4 + cdpify/client.py | 29 ++++++- cdpify/event_queue.py | 38 +++++++++- cdpify/events/dispatcher.py | 1 + cdpify/events/router.py | 46 +++++++++-- cdpify/events/source.py | 33 +++++++- cdpify/events/subscription.py | 21 +++--- cdpify/exceptions.py | 13 ++++ cdpify/transports/websocket.py | 10 ++- tests/test_event_overflow_and_raw.py | 109 +++++++++++++++++++++++++++ tests/test_event_subscription.py | 2 +- tests/test_websocket_client.py | 4 +- tests/test_websocket_transport.py | 8 +- 14 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 tests/test_event_overflow_and_raw.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4696dc..afd481b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,20 @@ Breaking changes are listed first in each release. - WebSocket compression is disabled by default for lower local latency. Pass `compression="deflate"` to `CdpClient` or `WebSocketTransport` to opt into compression when bandwidth matters more. +- Bounded event buffers now default to `overflow="error"`: overflow terminates + only the affected stream and raises `CDPEventOverflowError` on its next read. + Pass `overflow="drop_oldest"` on the client, transport or subscription to keep + the old lossy behavior. The default queue size remains unlimited (`0`). ### Added +- `listen_raw(method=None)` on clients and sessions, and `listen_all_raw()` on + clients, return `TransportEvent` values with wire names, parameter dictionaries + and session IDs. None matches every method in the selected scope. +- Per-subscription overflow overrides and structured `CDPEventOverflowError` + details (`max_queue_size`, `dropped_events`). Overflow never blocks responses + or other subscriptions; failed listeners unregister immediately. + - `compression`, `open_timeout` and `close_timeout` options for URL-based clients and the standalone WebSocket transport. Connection timeouts are in seconds. diff --git a/cdpify/__init__.py b/cdpify/__init__.py index e2edec13..f972a02e 100644 --- a/cdpify/__init__.py +++ b/cdpify/__init__.py @@ -1,11 +1,13 @@ from .client import CdpClient from .codec import decode_cdp, encode_cdp +from .event_queue import OverflowPolicy from .events import EventSubscription, ReceivedEvent from .exceptions import ( CDPCommandError, CDPConnectionError, CDPDecodeError, CDPError, + CDPEventOverflowError, CDPStreamClosedError, CDPTimeoutError, deprecated_exception, @@ -31,6 +33,8 @@ "CDPTimeoutError", "CDPStreamClosedError", "CDPDecodeError", + "CDPEventOverflowError", + "OverflowPolicy", "UNSET", "UnsetType", ] diff --git a/cdpify/client.py b/cdpify/client.py index 6942152b..1b0ad7b0 100644 --- a/cdpify/client.py +++ b/cdpify/client.py @@ -3,6 +3,7 @@ from typing import Any, Literal, Self from cdpify.domains import DomainAccessors +from cdpify.event_queue import OverflowPolicy from cdpify.events import EventRouter, EventSubscription, ReceivedEvent from cdpify.events.source import EventSource from cdpify.exceptions import CDPConnectionError @@ -24,6 +25,7 @@ def __init__( max_frame_size: int = 100 * 1024 * 1024, default_timeout: float | None = 30.0, max_queue_size: int = 0, + overflow: OverflowPolicy = "error", compression: Literal["deflate"] | None = None, open_timeout: float | None = 10.0, close_timeout: float | None = 10.0, @@ -38,6 +40,7 @@ def __init__( additional_headers=additional_headers, max_frame_size=max_frame_size, max_queue_size=max_queue_size, + overflow=overflow, compression=compression, open_timeout=open_timeout, close_timeout=close_timeout, @@ -48,7 +51,9 @@ def __init__( self._executor = BoundCommandExecutor( transport, default_timeout=default_timeout ) - EventSource.__init__(self, EventRouter(max_queue_size=max_queue_size), None) + EventSource.__init__( + self, EventRouter(max_queue_size=max_queue_size, overflow=overflow), None + ) self._event_loop_task: asyncio.Task[None] | None = None self._disconnect_task: asyncio.Task[None] | None = None @@ -141,6 +146,7 @@ def listen_all[T]( timeout: float | None = None, event_name: str | None = None, max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, ) -> EventSubscription[ReceivedEvent[T]]: """Subscribe to matching events from the root and every target session. @@ -151,6 +157,25 @@ def listen_all[T]( timeout=timeout, event_name=event_name, max_queue_size=max_queue_size, + overflow=overflow, + ) + + def listen_all_raw( + self, + method: str | None = None, + *, + timeout: float | None = None, + max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, + ) -> EventSubscription[TransportEvent]: + """Listen to wire dictionaries from the root and every target session.""" + return self._event_router.listen_raw( + method, + session_id=None, + all_sessions=True, + timeout=timeout, + max_queue_size=max_queue_size, + overflow=overflow, ) async def _run_event_loop(self) -> None: @@ -190,6 +215,7 @@ def _create_websocket_transport( additional_headers: dict[str, str] | None, max_frame_size: int, max_queue_size: int, + overflow: OverflowPolicy, compression: Literal["deflate"] | None, open_timeout: float | None, close_timeout: float | None, @@ -208,6 +234,7 @@ def _create_websocket_transport( additional_headers=additional_headers, max_frame_size=max_frame_size, max_queue_size=max_queue_size, + overflow=overflow, compression=compression, open_timeout=open_timeout, close_timeout=close_timeout, diff --git a/cdpify/event_queue.py b/cdpify/event_queue.py index 267526a2..47bfec65 100644 --- a/cdpify/event_queue.py +++ b/cdpify/event_queue.py @@ -1,38 +1,66 @@ import asyncio from collections import deque +from typing import Literal + +from cdpify.exceptions import CDPEventOverflowError + +type OverflowPolicy = Literal["error", "drop_oldest"] class EventQueue[T]: - """A non-blocking event buffer that drops the oldest item on overflow. + """A non-blocking event buffer with an explicit overflow policy. Use from one asyncio event loop. Zero means unlimited buffering. Items must not be None, which get() returns once a closed queue is fully drained. Closing wakes readers without consuming capacity or evicting queued events. """ - __slots__ = ("_items", "_ready", "_closed", "_max_size", "dropped_events") + __slots__ = ( + "_items", + "_ready", + "_closed", + "_max_size", + "_overflow", + "_error", + "dropped_events", + ) - def __init__(self, max_queue_size: int = 0) -> None: + def __init__( + self, max_queue_size: int = 0, *, overflow: OverflowPolicy = "error" + ) -> None: if not isinstance(max_queue_size, int): raise TypeError("max_queue_size must be an integer") if max_queue_size < 0: raise ValueError("max_queue_size must be non-negative") + if overflow not in ("error", "drop_oldest"): + raise ValueError('overflow must be "error" or "drop_oldest"') self._items: deque[T] = deque() self._ready = asyncio.Event() self._closed = False self._max_size = max_queue_size + self._overflow: OverflowPolicy = overflow + self._error: CDPEventOverflowError | None = None self.dropped_events = 0 def push(self, item: T) -> None: - """Append an event, evicting the oldest if full; ignore events after close.""" + """Append without blocking; apply the configured policy when full.""" if self._closed: return if self._max_size and len(self._items) >= self._max_size: + if self._overflow == "error": + self.dropped_events += len(self._items) + 1 + self._error = CDPEventOverflowError(self._max_size, self.dropped_events) + self.finish(discard=True) + return self._items.popleft() self.dropped_events += 1 self._items.append(item) self._ready.set() + @property + def failed(self) -> bool: + return self._error is not None + def finish(self, *, discard: bool = False) -> None: """Close and wake all readers, optionally releasing buffered events.""" self._closed = True @@ -48,6 +76,8 @@ async def get(self, timeout: float | None = None) -> T | None: async def _receive(self) -> T | None: while not self._items: + if self._error is not None: + raise self._error if self._closed: return None await self._ready.wait() diff --git a/cdpify/events/dispatcher.py b/cdpify/events/dispatcher.py index 98293f36..9aa26a78 100644 --- a/cdpify/events/dispatcher.py +++ b/cdpify/events/dispatcher.py @@ -13,6 +13,7 @@ class RawCDPEvent: params: dict[str, Any] session_id: str | None = None + method: str = "" _decoded: dict[type, Any] = field( default_factory=dict, init=False, repr=False, compare=False ) diff --git a/cdpify/events/router.py b/cdpify/events/router.py index aae16c79..bb279b95 100644 --- a/cdpify/events/router.py +++ b/cdpify/events/router.py @@ -1,7 +1,7 @@ from collections.abc import Callable from dataclasses import dataclass -from cdpify.event_queue import EventQueue +from cdpify.event_queue import EventQueue, OverflowPolicy from cdpify.transport import TransportEvent from .dispatcher import EventDispatcher, RawCDPEvent @@ -19,10 +19,13 @@ class ReceivedEvent[T]: class EventRouter: """Decode and route transport events to root, session, or global listeners.""" - def __init__(self, *, max_queue_size: int = 0) -> None: + def __init__( + self, *, max_queue_size: int = 0, overflow: OverflowPolicy = "error" + ) -> None: # Validate the default even when no listeners are registered yet. - EventQueue(max_queue_size) + EventQueue(max_queue_size, overflow=overflow) self._max_queue_size = max_queue_size + self._overflow: OverflowPolicy = overflow self._dispatcher = EventDispatcher() self._buffers: set[EventQueue[RawCDPEvent]] = set() self._closed = False @@ -50,7 +53,9 @@ async def dispatch(self, event: TransportEvent) -> bool: return False return await self._dispatcher.dispatch( event.method, - RawCDPEvent(params=event.params, session_id=event.session_id), + RawCDPEvent( + params=event.params, session_id=event.session_id, method=event.method + ), ) def listen[T]( @@ -61,6 +66,7 @@ def listen[T]( timeout: float | None = None, event_name: str | None = None, max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, ) -> EventSubscription[T]: return self._subscribe( _resolve_event_name(event_type, event_name), @@ -68,6 +74,7 @@ def listen[T]( session_id=session_id, timeout=timeout, max_queue_size=max_queue_size, + overflow=overflow, ) def listen_all[T]( @@ -77,6 +84,7 @@ def listen_all[T]( timeout: float | None = None, event_name: str | None = None, max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, ) -> EventSubscription[ReceivedEvent[T]]: return self._subscribe( _resolve_event_name(event_type, event_name), @@ -87,20 +95,44 @@ def listen_all[T]( all_sessions=True, timeout=timeout, max_queue_size=max_queue_size, + overflow=overflow, + ) + + def listen_raw( + self, + method: str | None = None, + *, + session_id: str | None, + all_sessions: bool = False, + timeout: float | None = None, + max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, + ) -> EventSubscription[TransportEvent]: + """Subscribe to wire dictionaries; None matches every method in scope.""" + return self._subscribe( + method, + lambda event: TransportEvent(event.method, event.params, event.session_id), + session_id=session_id, + all_sessions=all_sessions, + timeout=timeout, + max_queue_size=max_queue_size, + overflow=overflow, ) def _subscribe[T]( self, - event_name: str, + event_name: str | None, convert: Callable[[RawCDPEvent], T], *, session_id: str | None = None, all_sessions: bool = False, timeout: float | None, max_queue_size: int | None, + overflow: OverflowPolicy | None, ) -> EventSubscription[T]: buffer = EventQueue[RawCDPEvent]( - self._max_queue_size if max_queue_size is None else max_queue_size + self._max_queue_size if max_queue_size is None else max_queue_size, + overflow=self._overflow if overflow is None else overflow, ) if self._closed: buffer.finish() @@ -110,6 +142,8 @@ def _subscribe[T]( async def handler(event: RawCDPEvent) -> None: buffer.push(event) + if buffer.failed: + unsubscribe() def unsubscribe() -> None: buffer.finish(discard=True) diff --git a/cdpify/events/source.py b/cdpify/events/source.py index 3c0c399f..edb9eb07 100644 --- a/cdpify/events/source.py +++ b/cdpify/events/source.py @@ -1,3 +1,6 @@ +from cdpify.event_queue import OverflowPolicy +from cdpify.transport import TransportEvent + from .router import EventRouter from .subscription import EventSubscription @@ -16,14 +19,17 @@ def listen[T]( timeout: float | None = None, event_name: str | None = None, max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, ) -> EventSubscription[T]: """Subscribe to events from this connection or bound session only. Pass a generated event class such as ``RequestWillBeSentEvent`` or a custom dataclass with ``event_name``. ``timeout`` limits each read. ``max_queue_size`` overrides the client's default for this subscription; - zero is unlimited. Full buffers discard the oldest event without - blocking command responses. See ``EventSubscription.dropped_events``. + zero is unlimited. Full buffers terminate the stream with + CDPEventOverflowError by default. Explicit overflow="drop_oldest" + discards older events instead. Neither policy blocks command responses. + See EventSubscription.dropped_events for the count of discarded events. Matching listeners share the same decoded model; copy it before making changes that should be private to this listener. """ @@ -33,4 +39,27 @@ def listen[T]( timeout=timeout, event_name=event_name, max_queue_size=max_queue_size, + overflow=overflow, + ) + + def listen_raw( + self, + method: str | None = None, + *, + timeout: float | None = None, + max_queue_size: int | None = None, + overflow: OverflowPolicy | None = None, + ) -> EventSubscription[TransportEvent]: + """Listen to undecoded events in this root/session scope. + + None matches all methods. Values carry the wire method, raw parameter + dictionary and session ID. Subscribe before sending a triggering command. + The lifetime, overflow and read-only data rules match listen(). + """ + return self._event_router.listen_raw( + method, + session_id=self._session_id, + timeout=timeout, + max_queue_size=max_queue_size, + overflow=overflow, ) diff --git a/cdpify/events/subscription.py b/cdpify/events/subscription.py index 777c9903..22c0fb81 100644 --- a/cdpify/events/subscription.py +++ b/cdpify/events/subscription.py @@ -1,15 +1,14 @@ import weakref from collections.abc import Callable from types import TracebackType -from typing import Final, Self +from typing import Self from cdpify.event_queue import EventQueue -from cdpify.exceptions import CDPStreamClosedError +from cdpify.exceptions import CDPEventOverflowError, CDPStreamClosedError +from cdpify.unset import UNSET, UnsetType from .dispatcher import RawCDPEvent -_UNSET: Final = object() - class EventSubscription[T]: """A stream of events that is registered as soon as it is created. @@ -46,14 +45,14 @@ def __init__( @property def closed(self) -> bool: """Whether the stream has ended and no further events will be returned.""" - return self._ended + return self._ended or self._buffer.failed @property def dropped_events(self) -> int: - """Number of oldest events discarded because this buffer was full.""" + """Number of events discarded by the configured overflow policy.""" return self._buffer.dropped_events - async def next(self, timeout: float | None | object = _UNSET) -> T: + async def next(self, timeout: float | None | UnsetType = UNSET) -> T: """Wait for the next event. ``timeout`` defaults to the timeout passed to ``listen()``; ``None`` waits @@ -63,7 +62,7 @@ async def next(self, timeout: float | None | object = _UNSET) -> T: TimeoutError: No event arrived within ``timeout`` seconds. CDPStreamClosedError: The stream ended before another event arrived. """ - event = await self._receive(self._timeout if timeout is _UNSET else timeout) + event = await self._receive(self._timeout if timeout is UNSET else timeout) if event is None: raise CDPStreamClosedError("Event stream is closed") return self._convert(event) @@ -99,7 +98,11 @@ async def __aexit__( async def _receive(self, timeout: float | None) -> RawCDPEvent | None: if self._ended: return None - event = await self._buffer.get(timeout) + try: + event = await self._buffer.get(timeout) + except CDPEventOverflowError: + await self.aclose() + raise if event is None: self._ended = True self._unsubscribe() diff --git a/cdpify/exceptions.py b/cdpify/exceptions.py index 5c2fa80b..659a5283 100644 --- a/cdpify/exceptions.py +++ b/cdpify/exceptions.py @@ -47,6 +47,19 @@ class CDPStreamClosedError(CDPError): """An event subscription ended before the next event arrived.""" +class CDPEventOverflowError(CDPError): + """An event buffer exceeded its limit and the subscription was terminated.""" + + def __init__(self, max_queue_size: int, dropped_events: int) -> None: + self.max_queue_size = max_queue_size + self.dropped_events = dropped_events + super().__init__( + f"Event buffer exceeded max_queue_size={max_queue_size}; " + f"discarded {dropped_events} events. Increase the limit or explicitly " + 'select overflow="drop_oldest" for a lossy stream.' + ) + + class CDPDecodeError(CDPError): """A CDP payload is missing fields required by its generated model.""" diff --git a/cdpify/transports/websocket.py b/cdpify/transports/websocket.py index e0165982..44ce5bf4 100644 --- a/cdpify/transports/websocket.py +++ b/cdpify/transports/websocket.py @@ -8,7 +8,7 @@ import websockets from websockets.asyncio.client import ClientConnection, connect -from cdpify.event_queue import EventQueue +from cdpify.event_queue import EventQueue, OverflowPolicy from cdpify.exceptions import CDPCommandError, CDPConnectionError from cdpify.executor import _with_timeout from cdpify.transport import EventHandler, TransportEvent @@ -29,6 +29,7 @@ def __init__( additional_headers: dict[str, str] | None = None, max_frame_size: int = 100 * 1024 * 1024, max_queue_size: int = 0, + overflow: OverflowPolicy = "error", compression: Literal["deflate"] | None = None, open_timeout: float | None = 10.0, close_timeout: float | None = 10.0, @@ -37,6 +38,7 @@ def __init__( self._additional_headers = additional_headers self._max_frame_size = max_frame_size self._max_queue_size = max_queue_size + self._overflow: OverflowPolicy = overflow self._compression = compression self._open_timeout = open_timeout self._close_timeout = close_timeout @@ -46,7 +48,9 @@ def __init__( self._pending_requests: dict[int, asyncio.Future[dict[str, Any]]] = {} self._message_loop_task: asyncio.Task[None] | None = None self._shutdown_task: asyncio.Task[None] | None = None - self._event_queue = EventQueue[TransportEvent](max_queue_size) + self._event_queue = EventQueue[TransportEvent]( + max_queue_size, overflow=overflow + ) self._event_handler: EventHandler | None = None self._event_stream_closed = False self._is_shutting_down = False @@ -94,7 +98,7 @@ async def connect(self) -> None: self._is_shutting_down = False self._shutdown_task = None - self._event_queue = EventQueue(self._max_queue_size) + self._event_queue = EventQueue(self._max_queue_size, overflow=self._overflow) self._event_stream_closed = False self._message_loop_task = asyncio.create_task(self._run_message_loop()) logger.info("Connected") diff --git a/tests/test_event_overflow_and_raw.py b/tests/test_event_overflow_and_raw.py new file mode 100644 index 00000000..d2fcbc7f --- /dev/null +++ b/tests/test_event_overflow_and_raw.py @@ -0,0 +1,109 @@ +import asyncio +from dataclasses import dataclass +from typing import ClassVar + +import pytest + +from cdpify import ( + CdpClient, + CDPEventOverflowError, + CDPStreamClosedError, + TransportEvent, +) +from cdpify.events import EventRouter + + +@dataclass +class Event: + __cdp_event__: ClassVar[str] = "Test.event" + value: int + + +@pytest.mark.asyncio +async def test_overflow_fails_only_affected_stream_and_unregisters_immediately() -> ( + None +): + router = EventRouter(max_queue_size=1) + bounded = router.listen(Event, session_id=None) + unlimited = router.listen(Event, session_id=None, max_queue_size=0) + for i in range(3): + await router.dispatch(TransportEvent("Test.event", {"value": i})) + assert bounded.closed + assert len(router._buffers) == 1 + with pytest.raises(CDPEventOverflowError) as error: + await bounded.next() + assert error.value.max_queue_size == 1 + assert error.value.dropped_events == 2 + with pytest.raises(CDPStreamClosedError): + await bounded.next() + router.close() + assert [e.value async for e in unlimited] == [0, 1, 2] + + +@pytest.mark.asyncio +async def test_subscription_can_override_client_overflow_policy() -> None: + router = EventRouter(max_queue_size=1) + events = router.listen(Event, session_id=None, overflow="drop_oldest") + await router.dispatch(TransportEvent("Test.event", {"value": 1})) + await router.dispatch(TransportEvent("Test.event", {"value": 2})) + assert (await events.next()).value == 2 + assert events.dropped_events == 1 + await events.aclose() + + +@pytest.mark.asyncio +async def test_raw_listeners_preserve_scope_method_and_wire_names() -> None: + client = CdpClient("ws://unused") + root = client.listen_raw("Future.event") + tab = client.session("S1").listen_raw() + all_events = client.listen_all_raw() + messages = [ + TransportEvent("Future.event", {"wireName": 1}), + TransportEvent("Another.event", {"wireName": 2}, "S1"), + TransportEvent("Future.event", {"wireName": 3}, "S2"), + ] + for event in messages: + await client._dispatch_event(event) + client._event_router.close() + assert [e async for e in root] == messages[:1] + assert [e async for e in tab] == messages[1:2] + assert [e async for e in all_events] == messages + + +@pytest.mark.asyncio +async def test_overflow_does_not_block_websocket_command_response() -> None: + import json + + from websockets.asyncio.server import serve + + async def respond(socket): + command = json.loads(await socket.recv()) + for i in range(3): + await socket.send( + json.dumps({"method": "Test.event", "params": {"value": i}}) + ) + await socket.send(json.dumps({"id": command["id"], "result": {}})) + await socket.wait_closed() + + async with serve(respond, "127.0.0.1", 0) as server: + url = f"ws://127.0.0.1:{server.sockets[0].getsockname()[1]}" + async with CdpClient(url, max_queue_size=1) as client: + events = client.listen_raw() + assert await client.execute("Test.command", timeout=1) == {} + with pytest.raises(CDPEventOverflowError): + await events.next() + + +@pytest.mark.asyncio +async def test_raw_subscription_close_wakes_waiting_reader() -> None: + router = EventRouter() + events = router.listen_raw(session_id=None) + task = asyncio.create_task(anext(events, None)) + await asyncio.sleep(0) + await events.aclose() + assert await task is None + + +def test_invalid_overflow_policy_is_rejected() -> None: + with pytest.raises(ValueError, match="overflow"): + EventRouter(overflow="block") diff --git a/tests/test_event_subscription.py b/tests/test_event_subscription.py index 1496dc03..ac2842a7 100644 --- a/tests/test_event_subscription.py +++ b/tests/test_event_subscription.py @@ -170,7 +170,7 @@ async def test_event_name_overrides_cdp_event() -> None: @pytest.mark.asyncio async def test_bounded_listeners_drop_oldest_independently_and_drain_on_close() -> None: - router = EventRouter(max_queue_size=2) + router = EventRouter(max_queue_size=2, overflow="drop_oldest") bounded = router.listen(_Event, session_id=None) unlimited = router.listen(_Event, session_id=None, max_queue_size=0) global_events = router.listen_all(_Event, max_queue_size=1) diff --git a/tests/test_websocket_client.py b/tests/test_websocket_client.py index 856abb1a..734a1404 100644 --- a/tests/test_websocket_client.py +++ b/tests/test_websocket_client.py @@ -42,7 +42,9 @@ async def respond(socket: ServerConnection) -> None: async with serve(respond, "127.0.0.1", 0) as server: port = server.sockets[0].getsockname()[1] - async with CdpClient(f"ws://127.0.0.1:{port}", max_queue_size=2) as client: + async with CdpClient( + f"ws://127.0.0.1:{port}", max_queue_size=2, overflow="drop_oldest" + ) as client: assert client.transport._ws.protocol.extensions == [] root_events = client.listen(_Event) session_events = client.session("S1").listen(_Event, max_queue_size=1) diff --git a/tests/test_websocket_transport.py b/tests/test_websocket_transport.py index fc266e10..e113bd26 100644 --- a/tests/test_websocket_transport.py +++ b/tests/test_websocket_transport.py @@ -263,7 +263,9 @@ async def test_event_without_params_has_empty_params() -> None: @pytest.mark.asyncio async def test_bounded_transport_drains_latest_events_on_disconnect() -> None: - transport = WebSocketTransport("ws://example", max_queue_size=2) + transport = WebSocketTransport( + "ws://example", max_queue_size=2, overflow="drop_oldest" + ) for value in range(5): await transport._process_message( json.dumps({"method": "Test.event", "params": {"value": value}}) @@ -356,7 +358,9 @@ def messages(values: list[int]) -> _ScriptedWebSocket: "connect", AsyncMock(side_effect=[messages([1, 2, 3]), messages([4])]), ) - transport = WebSocketTransport("ws://example", max_queue_size=2) + transport = WebSocketTransport( + "ws://example", max_queue_size=2, overflow="drop_oldest" + ) await transport.connect() old_events = transport.events() assert (await anext(old_events)).params == {"value": 2} From 64d633655bc5025d9c8f919f17802896bd363c74 Mon Sep 17 00:00:00 2001 From: mathisarends Date: Sat, 7 Nov 2026 10:33:16 +0100 Subject: [PATCH 6/7] docs(generator): preserve spec parameter documentation and lifecycle markers --- CHANGELOG.md | 5 + cdpify/domains/accessibility/commands.py | 70 +- cdpify/domains/accessibility/domain.py | 49 +- cdpify/domains/accessibility/events.py | 11 +- cdpify/domains/accessibility/types.py | 93 ++ cdpify/domains/ads/types.py | 46 + cdpify/domains/animation/commands.py | 46 + cdpify/domains/animation/domain.py | 28 + cdpify/domains/animation/events.py | 12 + cdpify/domains/animation/types.py | 97 ++ cdpify/domains/audits/commands.py | 22 + cdpify/domains/audits/domain.py | 6 + cdpify/domains/audits/types.py | 154 +++ cdpify/domains/autofill/commands.py | 14 + cdpify/domains/autofill/domain.py | 8 + cdpify/domains/autofill/events.py | 7 + cdpify/domains/autofill/types.py | 53 + cdpify/domains/backgroundservice/types.py | 24 + cdpify/domains/bluetoothemulation/commands.py | 18 + cdpify/domains/bluetoothemulation/domain.py | 7 + cdpify/domains/bluetoothemulation/types.py | 18 + cdpify/domains/browser/commands.py | 169 ++- cdpify/domains/browser/domain.py | 125 +- cdpify/domains/browser/events.py | 36 +- cdpify/domains/browser/types.py | 85 +- cdpify/domains/cachestorage/commands.py | 53 + cdpify/domains/cachestorage/domain.py | 26 + cdpify/domains/cachestorage/types.py | 42 + cdpify/domains/cast/types.py | 4 + cdpify/domains/console/events.py | 3 + cdpify/domains/console/types.py | 18 + cdpify/domains/crashreportcontext/types.py | 3 + cdpify/domains/css/commands.py | 253 +++- cdpify/domains/css/domain.py | 97 +- cdpify/domains/css/events.py | 16 + cdpify/domains/css/types.py | 640 +++++++++- cdpify/domains/debugger/commands.py | 320 ++++- cdpify/domains/debugger/domain.py | 182 ++- cdpify/domains/debugger/events.py | 163 ++- cdpify/domains/debugger/types.py | 104 +- cdpify/domains/deviceaccess/types.py | 3 + cdpify/domains/deviceorientation/commands.py | 9 + cdpify/domains/deviceorientation/domain.py | 5 + cdpify/domains/digitalcredentials/commands.py | 14 + cdpify/domains/digitalcredentials/domain.py | 8 + cdpify/domains/dom/commands.py | 493 +++++++- cdpify/domains/dom/domain.py | 296 ++++- cdpify/domains/dom/events.py | 136 ++- cdpify/domains/dom/types.py | 171 +++ cdpify/domains/domdebugger/commands.py | 64 +- cdpify/domains/domdebugger/domain.py | 53 +- cdpify/domains/domdebugger/types.py | 32 +- cdpify/domains/domsnapshot/commands.py | 53 +- cdpify/domains/domsnapshot/domain.py | 28 +- cdpify/domains/domsnapshot/types.py | 288 +++++ cdpify/domains/domstorage/types.py | 9 + cdpify/domains/emulation/commands.py | 375 +++++- cdpify/domains/emulation/domain.py | 283 ++++- cdpify/domains/emulation/events.py | 11 +- cdpify/domains/emulation/types.py | 180 ++- cdpify/domains/eventbreakpoints/commands.py | 6 + cdpify/domains/eventbreakpoints/domain.py | 6 + cdpify/domains/extensions/commands.py | 51 + cdpify/domains/extensions/domain.py | 30 + cdpify/domains/extensions/types.py | 15 + cdpify/domains/fedcm/commands.py | 5 + cdpify/domains/fedcm/domain.py | 7 + cdpify/domains/fedcm/events.py | 4 + cdpify/domains/fedcm/types.py | 3 + cdpify/domains/fetch/commands.py | 97 +- cdpify/domains/fetch/domain.py | 63 +- cdpify/domains/fetch/events.py | 49 + cdpify/domains/fetch/types.py | 35 + cdpify/domains/filesystem/commands.py | 3 + cdpify/domains/filesystem/types.py | 19 + .../domains/headlessexperimental/commands.py | 29 + cdpify/domains/headlessexperimental/domain.py | 21 +- cdpify/domains/headlessexperimental/types.py | 9 + cdpify/domains/heapprofiler/commands.py | 74 ++ cdpify/domains/heapprofiler/domain.py | 56 + cdpify/domains/heapprofiler/events.py | 5 + cdpify/domains/heapprofiler/types.py | 23 + cdpify/domains/indexeddb/commands.py | 128 ++ cdpify/domains/indexeddb/domain.py | 63 + cdpify/domains/indexeddb/types.py | 79 ++ cdpify/domains/input/commands.py | 280 ++++- cdpify/domains/input/domain.py | 166 ++- cdpify/domains/input/events.py | 3 +- cdpify/domains/input/types.py | 74 ++ cdpify/domains/inspector/events.py | 5 +- cdpify/domains/io/commands.py | 31 +- cdpify/domains/io/domain.py | 17 +- cdpify/domains/layertree/commands.py | 60 + cdpify/domains/layertree/domain.py | 31 + cdpify/domains/layertree/events.py | 9 + cdpify/domains/layertree/types.py | 76 ++ cdpify/domains/log/commands.py | 3 + cdpify/domains/log/domain.py | 3 + cdpify/domains/log/events.py | 3 + cdpify/domains/log/types.py | 36 + cdpify/domains/media/types.py | 23 + cdpify/domains/memory/commands.py | 15 + cdpify/domains/memory/domain.py | 10 + cdpify/domains/memory/types.py | 29 + cdpify/domains/network/commands.py | 330 +++++- cdpify/domains/network/domain.py | 231 +++- cdpify/domains/network/events.py | 462 +++++++- cdpify/domains/network/types.py | 1037 ++++++++++++++++- cdpify/domains/overlay/commands.py | 159 ++- cdpify/domains/overlay/domain.py | 125 +- cdpify/domains/overlay/events.py | 12 + cdpify/domains/overlay/types.py | 314 +++++ cdpify/domains/page/commands.py | 544 ++++++++- cdpify/domains/page/domain.py | 344 +++++- cdpify/domains/page/events.py | 237 +++- cdpify/domains/page/types.py | 491 +++++++- cdpify/domains/performance/commands.py | 12 +- cdpify/domains/performance/domain.py | 10 +- cdpify/domains/performance/events.py | 6 + cdpify/domains/performance/types.py | 6 + .../domains/performancetimeline/commands.py | 7 + cdpify/domains/performancetimeline/domain.py | 7 + cdpify/domains/performancetimeline/types.py | 29 + cdpify/domains/preload/events.py | 11 + cdpify/domains/preload/types.py | 30 + cdpify/domains/profiler/commands.py | 29 + cdpify/domains/profiler/domain.py | 10 + cdpify/domains/profiler/events.py | 23 + cdpify/domains/profiler/types.py | 68 ++ cdpify/domains/pwa/commands.py | 21 + cdpify/domains/pwa/domain.py | 19 + cdpify/domains/pwa/types.py | 4 + cdpify/domains/runtime/commands.py | 298 ++++- cdpify/domains/runtime/domain.py | 181 ++- cdpify/domains/runtime/events.py | 52 +- cdpify/domains/runtime/types.py | 267 ++++- cdpify/domains/schema/commands.py | 3 + cdpify/domains/schema/types.py | 6 + cdpify/domains/security/commands.py | 17 +- cdpify/domains/security/domain.py | 15 +- cdpify/domains/security/events.py | 40 +- cdpify/domains/security/types.py | 134 ++- cdpify/domains/serviceworker/types.py | 21 + cdpify/domains/smartcardemulation/events.py | 3 + cdpify/domains/storage/commands.py | 102 +- cdpify/domains/storage/domain.py | 87 +- cdpify/domains/storage/events.py | 48 +- cdpify/domains/storage/types.py | 47 +- cdpify/domains/systeminfo/commands.py | 18 + cdpify/domains/systeminfo/types.py | 77 ++ cdpify/domains/target/commands.py | 195 +++- cdpify/domains/target/domain.py | 128 +- cdpify/domains/target/events.py | 24 +- cdpify/domains/target/types.py | 56 +- cdpify/domains/tethering/commands.py | 6 + cdpify/domains/tethering/domain.py | 6 + cdpify/domains/tethering/events.py | 6 + cdpify/domains/tracing/commands.py | 73 +- cdpify/domains/tracing/domain.py | 54 +- cdpify/domains/tracing/events.py | 30 +- cdpify/domains/tracing/types.py | 45 +- cdpify/domains/webaudio/types.py | 23 + cdpify/domains/webauthn/commands.py | 23 + cdpify/domains/webauthn/domain.py | 21 + cdpify/domains/webauthn/types.py | 106 ++ cdpify/domains/webmcp/commands.py | 15 + cdpify/domains/webmcp/domain.py | 8 + cdpify/domains/webmcp/events.py | 36 + cdpify/domains/webmcp/types.py | 45 + cdpify/generator/generators/commands.py | 16 +- cdpify/generator/generators/domain.py | 8 +- cdpify/generator/generators/events.py | 14 +- cdpify/generator/generators/types.py | 19 +- cdpify/generator/generators/utils.py | 62 +- cdpify/generator/generators/views.py | 14 + cdpify/generator/schemas.py | 6 +- cdpify/generator/templates/commands.py.jinja2 | 7 +- cdpify/generator/templates/events.py.jinja2 | 7 +- cdpify/generator/templates/types.py.jinja2 | 7 +- tests/generator/generators/test_spec_docs.py | 102 ++ 180 files changed, 14033 insertions(+), 515 deletions(-) create mode 100644 tests/generator/generators/test_spec_docs.py diff --git a/CHANGELOG.md b/CHANGELOG.md index afd481b3..567b21b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,11 @@ Breaking changes are listed first in each release. ### Added +- Generated method docstrings include parameter descriptions, units and defaults + from the spec. Model fields retain their attribute documentation. Experimental + and deprecated markers are preserved on types, events and fields as well as + commands. + - `listen_raw(method=None)` on clients and sessions, and `listen_all_raw()` on clients, return `TransportEvent` values with wire names, parameter dictionaries and session IDs. None matches every method in the selected scope. diff --git a/cdpify/domains/accessibility/commands.py b/cdpify/domains/accessibility/commands.py index 7d9fbb83..2b94c27f 100644 --- a/cdpify/domains/accessibility/commands.py +++ b/cdpify/domains/accessibility/commands.py @@ -30,36 +30,61 @@ class AccessibilityCommand(StrEnum): class GetPartialAXTreeParams: """ Fetches the accessibility node and partial accessibility tree for this DOM node, if - it exists. + it exists. Experimental: this API may change or be removed. """ node_id: dom.NodeId | None = field(default=None, metadata={"cdp_name": "nodeId"}) + """ + Identifier of the node to get the partial accessibility tree for. + """ backend_node_id: dom.BackendNodeId | None = field( default=None, metadata={"cdp_name": "backendNodeId"} ) + """ + Identifier of the backend node to get the partial accessibility tree for. + """ object_id: runtime.RemoteObjectId | None = field( default=None, metadata={"cdp_name": "objectId"} ) + """ + JavaScript object id of the node wrapper to get the partial accessibility tree for. + """ fetch_relatives: bool | None = field( default=None, metadata={"cdp_name": "fetchRelatives"} ) + """ + Whether to fetch this node's ancestors, siblings and children. Defaults to true. + """ @dataclass(kw_only=True, slots=True) class GetPartialAXTreeResult: nodes: list[AXNode] + """ + The `Accessibility.AXNode` for this DOM node, if it exists, plus its ancestors, + siblings and children, if requested. + """ @dataclass(kw_only=True, slots=True) class GetFullAXTreeParams: """ - Fetches the entire accessibility tree for the root Document + Fetches the entire accessibility tree for the root Document Experimental: this API + may change or be removed. """ depth: int | None = None + """ + The maximum depth at which descendants of the root node should be retrieved. If + omitted, the full tree is returned. + """ frame_id: page.FrameId | None = field( default=None, metadata={"cdp_name": "frameId"} ) + """ + The frame for whose document the AX tree should be retrieved. If omitted, the root + frame is used. + """ @dataclass(kw_only=True, slots=True) @@ -71,11 +96,15 @@ class GetFullAXTreeResult: class GetRootAXNodeParams: """ Fetches the root node. Requires `enable()` to have been called previously. + Experimental: this API may change or be removed. """ frame_id: page.FrameId | None = field( default=None, metadata={"cdp_name": "frameId"} ) + """ + The frame in whose document the node resides. If omitted, the root frame is used. + """ @dataclass(kw_only=True, slots=True) @@ -87,16 +116,25 @@ class GetRootAXNodeResult: class GetAXNodeAndAncestorsParams: """ Fetches a node and all ancestors up to and including the root. Requires `enable()` - to have been called previously. + to have been called previously. Experimental: this API may change or be removed. """ node_id: dom.NodeId | None = field(default=None, metadata={"cdp_name": "nodeId"}) + """ + Identifier of the node to get. + """ backend_node_id: dom.BackendNodeId | None = field( default=None, metadata={"cdp_name": "backendNodeId"} ) + """ + Identifier of the backend node to get. + """ object_id: runtime.RemoteObjectId | None = field( default=None, metadata={"cdp_name": "objectId"} ) + """ + JavaScript object id of the node wrapper to get. + """ @dataclass(kw_only=True, slots=True) @@ -108,13 +146,16 @@ class GetAXNodeAndAncestorsResult: class GetChildAXNodesParams: """ Fetches a particular accessibility node by AXNodeId. Requires `enable()` to have - been called previously. + been called previously. Experimental: this API may change or be removed. """ id: AXNodeId frame_id: page.FrameId | None = field( default=None, metadata={"cdp_name": "frameId"} ) + """ + The frame in whose document the node resides. If omitted, the root frame is used. + """ @dataclass(kw_only=True, slots=True) @@ -130,22 +171,41 @@ class QueryAXTreeParams: ignored for accessibility, and returns those that match the specified name and role. If no DOM node is specified, or the DOM node does not exist, the command returns an error. If neither `accessibleName` or `role` is specified, it returns all the - accessibility nodes in the subtree. + accessibility nodes in the subtree. Experimental: this API may change or be removed. """ node_id: dom.NodeId | None = field(default=None, metadata={"cdp_name": "nodeId"}) + """ + Identifier of the node for the root to query. + """ backend_node_id: dom.BackendNodeId | None = field( default=None, metadata={"cdp_name": "backendNodeId"} ) + """ + Identifier of the backend node for the root to query. + """ object_id: runtime.RemoteObjectId | None = field( default=None, metadata={"cdp_name": "objectId"} ) + """ + JavaScript object id of the node wrapper for the root to query. + """ accessible_name: str | None = field( default=None, metadata={"cdp_name": "accessibleName"} ) + """ + Find nodes with this computed name. + """ role: str | None = None + """ + Find nodes with this computed role. + """ @dataclass(kw_only=True, slots=True) class QueryAXTreeResult: nodes: list[AXNode] + """ + A list of `Accessibility.AXNode` matching the specified attributes, including nodes + that are ignored for accessibility. + """ diff --git a/cdpify/domains/accessibility/domain.py b/cdpify/domains/accessibility/domain.py index 3a44671c..91c24a64 100644 --- a/cdpify/domains/accessibility/domain.py +++ b/cdpify/domains/accessibility/domain.py @@ -64,7 +64,16 @@ async def get_partial_ax_tree( ) -> GetPartialAXTreeResult: """ Fetches the accessibility node and partial accessibility tree for this DOM - node, if it exists. + node, if it exists. Experimental: this API may change or be removed. + + Args: + node_id: Identifier of the node to get the partial accessibility tree for. + backend_node_id: Identifier of the backend node to get the partial + accessibility tree for. + object_id: JavaScript object id of the node wrapper to get the partial + accessibility tree for. + fetch_relatives: Whether to fetch this node's ancestors, siblings and + children. Defaults to true. """ _params: dict[str, Any] = {} if node_id is not None: @@ -89,7 +98,14 @@ async def get_full_ax_tree( frame_id: page.FrameId | None = None, ) -> GetFullAXTreeResult: """ - Fetches the entire accessibility tree for the root Document + Fetches the entire accessibility tree for the root Document Experimental: this + API may change or be removed. + + Args: + depth: The maximum depth at which descendants of the root node should be + retrieved. If omitted, the full tree is returned. + frame_id: The frame for whose document the AX tree should be retrieved. If + omitted, the root frame is used. """ _params: dict[str, Any] = {} if depth is not None: @@ -110,6 +126,11 @@ async def get_root_ax_node( ) -> GetRootAXNodeResult: """ Fetches the root node. Requires `enable()` to have been called previously. + Experimental: this API may change or be removed. + + Args: + frame_id: The frame in whose document the node resides. If omitted, the root + frame is used. """ _params: dict[str, Any] = {} if frame_id is not None: @@ -130,7 +151,13 @@ async def get_ax_node_and_ancestors( ) -> GetAXNodeAndAncestorsResult: """ Fetches a node and all ancestors up to and including the root. Requires - `enable()` to have been called previously. + `enable()` to have been called previously. Experimental: this API may change or + be removed. + + Args: + node_id: Identifier of the node to get. + backend_node_id: Identifier of the backend node to get. + object_id: JavaScript object id of the node wrapper to get. """ _params: dict[str, Any] = {} if node_id is not None: @@ -154,7 +181,11 @@ async def get_child_ax_nodes( ) -> GetChildAXNodesResult: """ Fetches a particular accessibility node by AXNodeId. Requires `enable()` to - have been called previously. + have been called previously. Experimental: this API may change or be removed. + + Args: + frame_id: The frame in whose document the node resides. If omitted, the root + frame is used. """ _params: dict[str, Any] = {} _params["id"] = _encode_value(id) @@ -182,7 +213,15 @@ async def query_ax_tree( that are ignored for accessibility, and returns those that match the specified name and role. If no DOM node is specified, or the DOM node does not exist, the command returns an error. If neither `accessibleName` or `role` is specified, it - returns all the accessibility nodes in the subtree. + returns all the accessibility nodes in the subtree. Experimental: this API may + change or be removed. + + Args: + node_id: Identifier of the node for the root to query. + backend_node_id: Identifier of the backend node for the root to query. + object_id: JavaScript object id of the node wrapper for the root to query. + accessible_name: Find nodes with this computed name. + role: Find nodes with this computed role. """ _params: dict[str, Any] = {} if node_id is not None: diff --git a/cdpify/domains/accessibility/events.py b/cdpify/domains/accessibility/events.py index 804c1a7a..311871c0 100644 --- a/cdpify/domains/accessibility/events.py +++ b/cdpify/domains/accessibility/events.py @@ -22,21 +22,28 @@ class AccessibilityEvent(StrEnum): class LoadCompleteEvent: """ The loadComplete event mirrors the load complete event sent by the browser to - assistive technology when the web page has finished loading. + assistive technology when the web page has finished loading. Experimental: this API + may change or be removed. """ __cdp_event__: ClassVar[AccessibilityEvent] = AccessibilityEvent.LOAD_COMPLETE root: AXNode + """ + New document root node. + """ @dataclass(kw_only=True, slots=True) class NodesUpdatedEvent: """ The nodesUpdated event is sent every time a previously requested node has changed - the in tree. + the in tree. Experimental: this API may change or be removed. """ __cdp_event__: ClassVar[AccessibilityEvent] = AccessibilityEvent.NODES_UPDATED nodes: list[AXNode] + """ + Updated node data. + """ diff --git a/cdpify/domains/accessibility/types.py b/cdpify/domains/accessibility/types.py index f2368f9e..2d9091b0 100644 --- a/cdpify/domains/accessibility/types.py +++ b/cdpify/domains/accessibility/types.py @@ -79,22 +79,49 @@ class AXValueSource: """ type: AXValueSourceType + """ + What type of source this is. + """ value: AXValue | None = None + """ + The value of this property source. + """ attribute: str | None = None + """ + The name of the relevant attribute, if any. + """ attribute_value: AXValue | None = field( default=None, metadata={"cdp_name": "attributeValue"} ) + """ + The value of the relevant attribute, if any. + """ superseded: bool | None = None + """ + Whether this source is superseded by a higher priority source. + """ native_source: AXValueNativeSourceType | None = field( default=None, metadata={"cdp_name": "nativeSource"} ) + """ + The native markup source for this value, e.g. a `