diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 465bdaa018790..526e7a4db6a50 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -532,6 +532,7 @@ py_library( visibility = ["//visibility:public"], deps = [ ":bidi", + ":bidi_protocol", ":chrome", ":chromium", ":common", @@ -561,6 +562,7 @@ py_package( "py.selenium", "py.selenium.common", "py.selenium.webdriver", + "py.selenium.webdriver._bidi", "py.selenium.webdriver.chrome", "py.selenium.webdriver.chromium", "py.selenium.webdriver.common", @@ -748,6 +750,27 @@ generate_bidi( spec_version = "1.0", ) +# Schema-driven BiDi protocol layer (ADR 17701): generated from the shared BiDi +# schema into the internal `selenium/webdriver/_bidi/` package. The files are +# checked in here only so the generated output's shape is reviewable; the final +# PR will generate them at build time rather than commit them. +py_binary( + name = "generate-bidi-protocol", + srcs = ["generate_bidi_protocol.py"], + args = ["$(location //javascript/selenium-webdriver:create-bidi-src_schema)"], + data = ["//javascript/selenium-webdriver:create-bidi-src_schema"], + main = "generate_bidi_protocol.py", + visibility = ["//visibility:public"], +) + +py_library( + name = "bidi_protocol", + srcs = glob(["selenium/webdriver/_bidi/**/*.py"]), + imports = ["."], + visibility = ["//visibility:public"], + deps = [":exceptions"], +) + py_test_suite( name = "unit", size = "small", @@ -1036,6 +1059,7 @@ FEATURE_SUITE_DEFS = { target_compatible_with = BROWSERS[browser]["target_compatible_with"], test_suffix = "%s-bidi" % browser, deps = [ + ":bidi_protocol", # bidi_protocol_tests.py imports the generated _bidi layer directly ":common_alert", # bidi_browsing_context_tests.py calls EC.alert_is_present() ":init-tree", ":webserver", diff --git a/py/generate_bidi_protocol.py b/py/generate_bidi_protocol.py new file mode 100644 index 0000000000000..2e3a14bb2b8df --- /dev/null +++ b/py/generate_bidi_protocol.py @@ -0,0 +1,1003 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Generate the Python WebDriver BiDi protocol layer from the shared schema. + +Consumes the binding-neutral schema produced by the JavaScript projector +(``//javascript/selenium-webdriver:create-bidi-src_schema`` -> schema.json). The +schema is already normalized (inline enums hoisted, unions canonicalized with a +dispatch ``selector``, group composition flattened, wire names + nullability +preserved), so this generator is a straight projection into Python — no CDDL +interpretation. Ported from the Ruby generator (PR #17731). + +Output is close to ``ruff format`` style; the build applies a format pass so the +checked-in files (and the verify test's comparison) are canonical. + +Usage: + python generate_bidi_protocol.py +""" + +from __future__ import annotations + +import argparse +import json +import keyword +import os +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +BIDI_DOC_URL = "https://www.selenium.dev/documentation/warnings/bidi-implementation/" +LINE_LIMIT = 120 +DEFAULT_OUTPUT = "py/selenium/webdriver/_bidi" + +_HEADER = """# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol""" + +_RESERVED_FIELDS = {"as_json", "from_json", "extensions"} + + +# --------------------------------------------------------------------------- # +# Naming helpers +# --------------------------------------------------------------------------- # +def camel_to_snake(name: str) -> str: + name = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name) + name = re.sub(r"([a-z\d])([A-Z])", r"\1_\2", name) + return name.lower() + + +def snake_to_class(name: str) -> str: + return "".join(part.capitalize() for part in name.split("_")) + + +def type_class_name(type_name: str) -> str: + """Local class name for a schema type; synthetic ``X_Y`` becomes CamelCase ``XY``.""" + local = type_name.split(".", 1)[-1] + parts = [p for p in local.split("_") if p] + return "".join(p[:1].upper() + p[1:] for p in parts) or local + + +def value_alias(type_name: str) -> str: + """The TypeAlias name for a union's set of concrete variants.""" + return f"{type_class_name(type_name)}Value" + + +def safe_name(name: str) -> str: + """A snake_case identifier safe as a Python attribute / parameter. + + Escapes hard keywords and names that would shadow the runtime API; soft + keywords (``type``, ``match``) are valid identifiers and left alone. + """ + return f"{name}_" if keyword.iskeyword(name) or name in _RESERVED_FIELDS else name + + +def enum_member(value: Any) -> str: + token = camel_to_snake(str(value)) + token = re.sub(r"\A-(?=\d)", "neg", token) + token = re.sub(r"\A-", "neg_", token) + token = re.sub(r"[^a-z0-9]+", "_", token).strip("_").upper() + if not token or token[0].isdigit(): + token = f"_{token}" + return token + + +def lit(value: Any) -> str: + """A Python source literal, double-quoted (ruff-format style) for strings.""" + if isinstance(value, bool): + return "True" if value else "False" + if isinstance(value, str): + return json.dumps(value) + if value is None: + return "None" + return repr(value) + + +def tuple_lit(items: tuple) -> str: + """A tuple literal with double-quoted elements (single element keeps its comma).""" + inner = ", ".join(lit(i) for i in items) + return f"({inner},)" if len(items) == 1 else f"({inner})" + + +def frozenset_lit(values: list[str], indent: int) -> str: + """`frozenset({...})` on one line, or ruff's wrapped form when it would overflow.""" + one_line = f"frozenset({{{', '.join(values)}}})" + if indent + len(one_line) <= LINE_LIMIT: + return one_line + inner = ",\n".join(f"{' ' * (indent + 8)}{v}" for v in values) + return f"frozenset(\n{' ' * (indent + 4)}{{\n{inner},\n{' ' * (indent + 4)}}}\n{' ' * indent})" + + +def wrap_call(prefix: str, args: list[str], suffix: str, indent: int) -> str: + """`prefix(args)suffix` on one line, or one arg per line when it would exceed the limit.""" + one_line = f"{prefix}{', '.join(args)}{suffix}" + if not args or indent + len(one_line) <= LINE_LIMIT: + return one_line + pad = " " * (indent + 4) + body = ",\n".join(f"{pad}{a}" for a in args) + return f"{prefix}\n{body},\n{' ' * indent}{suffix.lstrip()}" + + +# --------------------------------------------------------------------------- # +# IR +# --------------------------------------------------------------------------- # +_NO_FIXED = object() + + +@dataclass +class ParamIR: + name: str + wire: str + required: bool + py_type: str + + +@dataclass +class CommandIR: + wire: str + method: str + params: list[ParamIR] + params_class: str | None + union_params: bool + result_class: str | None # runtime class passed to _execute (dispatch class) + result_type: str # return annotation + + +@dataclass +class EventIR: + wire: str + name: str + payload_class: str | None + + +@dataclass +class EnumIR: + class_name: str + schema_name: str + members: list[tuple[str, Any]] + + +@dataclass +class FieldIR: + name: str + wire: str + required: bool + nullable: bool + ref: str | None + is_list: bool + enum: str | None + primitive: str | None + fixed: Any + py_type: str + + +@dataclass +class RecordIR: + class_name: str + schema_name: str + fields: list[FieldIR] + discriminator: FieldIR | None + extensible: bool + + +@dataclass +class VariantIR: + mode: str + value: Any + ref: str + requires: tuple[str, ...] | None + + +@dataclass +class UnionIR: + class_name: str + schema_name: str + discriminator: str | None + discriminator_values: list[Any] | None + variants: list[VariantIR] + variant_types: list[str] # annotation names for the value alias + + +@dataclass +class ModuleIR: + domain: str + class_name: str + filename: str + enums: list[EnumIR] + records: list[RecordIR] + unions: list[UnionIR] + commands: list[CommandIR] + events: list[EventIR] + imports: dict[str, set[str]] # module filename -> annotation names (cross-domain) + + +# --------------------------------------------------------------------------- # +# Schema projection (ported from Ruby bidi_generate.rb Schema) +# --------------------------------------------------------------------------- # +# The complete set of primitives the projector emits; it rejects `unknown` upstream +# (an unhandled CDDL construct fails the schema check), so we never see one. +_PRIMITIVE = {"string": "str", "integer": "int", "number": "float", "boolean": "bool", "null": "None"} +_LEAF_PRIMITIVE = {"string": "str", "integer": "int", "number": "float", "boolean": "bool"} + + +class Schema: + def __init__(self, raw: dict) -> None: + self.types: dict = raw["types"] + self.commands: list = raw["commands"] + self.events: list = raw["events"] + self._promote_command_params_records() + + def _promote_command_params_records(self) -> None: + for cmd in self.commands: + ref = (cmd.get("params") or {}).get("ref") + if ref and ref in self.types and self._envelope_synthetic(self.types[ref]): + for tag in ("synthetic", "owner", "label"): + self.types[ref].pop(tag, None) + + def domains(self) -> list[str]: + seen: list[str] = [] + for entry in self.commands + self.events: + if entry["domain"] not in seen: + seen.append(entry["domain"]) + return seen + + def commands_for(self, domain: str) -> list: + return [c for c in self.commands if c["domain"] == domain] + + def events_for(self, domain: str) -> list: + return [e for e in self.events if e["domain"] == domain] + + def type_kind(self, ref: str | None) -> str | None: + return self.types.get(ref, {}).get("kind") if ref else None + + def enums_for(self, domain: str) -> list[EnumIR]: + prefix = f"{domain}." + out = [] + for name, type_ in self.types.items(): + if type_.get("kind") != "enum" or not name.startswith(prefix): + continue + members = [(enum_member(v), v) for v in type_["values"]] + out.append(EnumIR(class_name=type_class_name(name), schema_name=name, members=members)) + return out + + def types_for(self, domain: str) -> tuple[list[RecordIR], list[UnionIR]]: + prefix = f"{domain}." + records: list[RecordIR] = [] + unions: list[UnionIR] = [] + for name, type_ in self.types.items(): + if not name.startswith(prefix): + continue + kind = type_["kind"] + if kind == "record": + if not type_["fields"] or self._suppressed_record(type_): + continue + records.append(self._record_ir(name, type_)) + elif kind == "union": + unions.append(self._union_ir(name)) + elif kind == "alias" and "union" in type_["type"]: + unions.append(self._union_ir(name)) + return records, unions + + def _suppressed_record(self, type_: dict) -> bool: + return self._message_envelope(type_) or self._envelope_synthetic(type_) + + def _message_envelope(self, type_: dict) -> bool: + return any(f["wire"] == "method" and "const" in f["type"] for f in type_.get("fields", [])) + + def _envelope_synthetic(self, type_: dict) -> bool: + if not type_.get("synthetic"): + return False + owner = self.types.get(type_.get("owner")) + return bool(owner and owner["kind"] == "record" and self._message_envelope(owner)) + + def _record_ir(self, name: str, type_: dict) -> RecordIR: + disc_field = next((f for f in type_["fields"] if self._baked_discriminator(f)), None) + discriminator = self._field_ir(disc_field) if disc_field else None + fields = [self._field_ir(f) for f in type_["fields"] if not self._baked_discriminator(f)] + return RecordIR( + class_name=type_class_name(name), + schema_name=name, + fields=fields, + discriminator=discriminator, + extensible=bool(type_.get("extensible")), + ) + + def _baked_discriminator(self, field_: dict) -> bool: + return "const" in field_["type"] and not field_["type"].get("nullable") + + def _field_ir(self, field_: dict) -> FieldIR: + resolved = self._resolve(field_["type"]) + const = field_["type"]["const"] if self._baked_discriminator(field_) else _NO_FIXED + py, refs = self.py_type(field_["type"]) + self._pending_refs |= refs + return FieldIR( + name=safe_name(camel_to_snake(field_["name"])), + wire=field_["wire"], + required=bool(field_["required"]), + nullable=resolved["nullable"], + ref=resolved["ref"], + is_list=resolved["list"], + enum=self._enum_ref(field_["type"]), + primitive=self._leaf_primitive(field_["type"]), + fixed=const, + py_type=py, + ) + + def _union_ir(self, name: str) -> UnionIR: + type_ = self.types[name] + if type_["kind"] == "union": + ir = self._union_from_selector(name, type_["selector"]) + else: + ir = self._union_from_alias(name) + for v in ir.variants: + py = self._py_ref(v.ref, self._pending_refs) + ir.variant_types.append(py) + return ir + + def _union_from_selector(self, name: str, selector: dict) -> UnionIR: + if selector.get("correlated"): + raise ValueError(f"correlated union {name} must not be emitted (resolved by request id)") + variants = self._discriminated_variants(selector) if selector.get("by") else self._ordered_variants(selector) + if not variants: + raise ValueError(f"union {name} selector yielded no dispatch variants") + values = self._discriminator_values(selector) if selector.get("by") else None + return UnionIR( + class_name=type_class_name(name), + schema_name=name, + discriminator=selector.get("by"), + discriminator_values=values, + variants=variants, + variant_types=[], + ) + + def _discriminated_variants(self, selector: dict) -> list[VariantIR]: + variants = [VariantIR("value", v["value"], v["ref"], None) for v in selector["variants"]] + if selector.get("default"): + variants.append(VariantIR("fallback", None, selector["default"], None)) + return variants + + def _ordered_variants(self, selector: dict) -> list[VariantIR]: + return [VariantIR("presence", None, arm["ref"], tuple(arm["requires"])) for arm in selector.get("ordered", [])] + + # The union-wide allowed discriminator set: each variant's tag plus the default + # variant's own enum values for the discriminator field (spec-faithful outbound check). + def _discriminator_values(self, selector: dict) -> list[Any] | None: + tagged = [v["value"] for v in selector["variants"]] + if not tagged or not all(isinstance(v, str) for v in tagged): + return None # boolean/number tags need no membership check + default = selector.get("default") + by = selector["by"] + extra: list[Any] = [] + default_type = self.types.get(default, {}) + if default and default_type.get("kind") == "record": + df = next((f for f in default_type["fields"] if f["wire"] == by), None) + ref = df and df["type"].get("ref") + if ref and self.types.get(ref, {}).get("kind") == "enum": + extra = self.types[ref]["values"] + return list(dict.fromkeys(tagged + extra)) + + def _union_from_alias(self, name: str) -> UnionIR: + variants = [] + discriminator = None + for arm in self.types[name]["type"]["union"]: + ref = arm.get("ref") + if not ref: + continue + const = next((f for f in self.types[ref]["fields"] if "const" in f["type"]), None) + if not const: + raise ValueError(f"alias-union {name} arm {ref} has no const discriminator") + discriminator = const["wire"] + variants.append(VariantIR("value", const["type"]["const"], ref, None)) + values = [v.value for v in variants] if all(isinstance(v.value, str) for v in variants) else None + return UnionIR(type_class_name(name), name, discriminator, values, variants, []) + + def params_for(self, params_ref: dict | None) -> list[ParamIR] | None: + if not params_ref: + return [] + type_ = self.types.get(params_ref["ref"]) + if not type_: + return None + if type_["kind"] == "record": + return [self._param_ir(f) for f in type_["fields"]] + if type_["kind"] == "union": + return self._union_params(type_, params_ref["ref"]) + return None + + def _param_ir(self, field_: dict, required: bool | None = None) -> ParamIR: + py, refs = self.py_type(field_["type"]) + self._pending_refs |= refs + return ParamIR( + name=safe_name(camel_to_snake(field_["name"])), + wire=field_["wire"], + required=bool(field_["required"]) if required is None else required, + py_type=py, + ) + + def _union_params(self, type_: dict, ref: str) -> list[ParamIR] | None: + variants = [self.types.get(v) for v in type_["variants"]] + if not all(v and v["kind"] == "record" for v in variants): + return None + self._guard_union_dispatch_keys(type_["selector"], ref) + return self._merged_params([v["fields"] for v in variants]) + + def _merged_params(self, variant_fields: list[list[dict]]) -> list[ParamIR]: + all_fields = [f for fields in variant_fields for f in fields] + out: list[ParamIR] = [] + for wire_name in dict.fromkeys(f["wire"] for f in all_fields): + field_ = next(f for f in all_fields if f["wire"] == wire_name) + required = all(any(f["wire"] == wire_name and f["required"] for f in fields) for fields in variant_fields) + out.append(self._param_ir(field_, required=required)) + return out + + def _guard_union_dispatch_keys(self, selector: dict, ref: str) -> None: + if selector.get("by"): + keys = [selector["by"]] + else: + keys = [k for arm in selector.get("ordered", []) for k in arm["requires"]] + camel = [k for k in keys if camel_to_snake(k) != k] + if camel: + raise ValueError( + f"union command param {ref} dispatches on non-snake wire key(s) {camel}; " + "Union.build matches kwargs to dispatch keys by name — add an explicit mapping first." + ) + + def structured_ref(self, name: str) -> str | None: + resolved = self._resolve_named(name, {}) + return None if resolved["list"] else resolved["ref"] + + # --- type resolution (serialization facts) --- + def _resolve(self, node: dict) -> dict: + nullable = bool(node.get("nullable")) + if "list" in node: + element = self._resolve(node["list"]) + return {"ref": element["ref"], "list": True, "nullable": nullable} + if "ref" in node: + named = self._resolve_named(node["ref"], {}) + return {"ref": named["ref"], "list": named["list"], "nullable": nullable} + if "union" in node: + return self._resolve_union(node, nullable) + return {"ref": None, "list": False, "nullable": nullable} + + def _resolve_union(self, node: dict, nullable: bool) -> dict: + refs = [arm for arm in node["union"] if "ref" in arm] + if len(refs) == 1 and self._union_ref(refs[0]["ref"]): + named = self._resolve_named(refs[0]["ref"], {}) + return {"ref": named["ref"], "list": named["list"], "nullable": nullable} + return {"ref": None, "list": False, "nullable": nullable} + + def _union_ref(self, name: str) -> bool: + type_ = self.types.get(name) + if not type_: + return False + if type_["kind"] == "alias" and "ref" in type_["type"]: + return self._union_ref(type_["type"]["ref"]) + return type_["kind"] == "union" + + _OPAQUE = {"ref": None, "list": False} + + def _resolve_named(self, name: str | None, seen: dict) -> dict: + if name is None or seen.get(name): + return dict(self._OPAQUE) + seen[name] = True + type_ = self.types.get(name) + if not type_: + return dict(self._OPAQUE) + kind = type_["kind"] + if kind == "record": + return dict(self._OPAQUE) if not type_["fields"] else {"ref": self._domain_ref(name), "list": False} + if kind == "union": + return {"ref": self._domain_ref(name), "list": False} + if kind == "enum": + return {"ref": None, "list": False} + if kind == "alias": + return self._resolve_named_alias(name, type_["type"], seen) + return dict(self._OPAQUE) + + def _resolve_named_alias(self, name: str, inner: dict, seen: dict) -> dict: + if "union" in inner: + return {"ref": self._domain_ref(name), "list": False} + if "ref" in inner: + return self._resolve_named(inner["ref"], seen) + if "list" in inner: + element = self._resolve(inner["list"]) + return {"ref": element["ref"], "list": True} + return {"ref": None, "list": False} + + def _domain_ref(self, name: str) -> str | None: + return name if "." in name else None + + def _enum_ref(self, node: dict) -> str | None: + ref = node.get("ref") or (node.get("list") or {}).get("ref") + if ref and self.types.get(ref, {}).get("kind") == "enum": + return ref + return None + + # The runtime-checkable scalar type of a field (or of a list's elements), + # following alias chains (e.g. js-uint -> integer). None for a record ref, + # union, const, enum, or opaque/unknown type — those are validated elsewhere. + def _leaf_primitive(self, node: dict, seen: set[str] | None = None) -> str | None: + seen = seen or set() + if "list" in node: + return self._leaf_primitive(node["list"], seen) + if "primitive" in node: + return _LEAF_PRIMITIVE.get(node["primitive"]) + if "ref" in node: + name = node["ref"] + type_ = self.types.get(name) + if name in seen or not type_ or type_["kind"] != "alias": + return None + seen.add(name) + return self._leaf_primitive(type_["type"], seen) + return None + + # --- Python type annotations --- + def py_type(self, node: dict) -> tuple[str, set[str]]: + refs: set[str] = set() + base = self._py_base(node, refs) + nullable = node.get("nullable") and base != "None" + return (f"{base} | None" if nullable else base), refs + + def _py_base(self, node: dict, refs: set[str]) -> str: + if "list" in node: + inner, _ = self._py_inner(node["list"], refs) + return f"list[{inner}]" + if "ref" in node: + return self._py_ref(node["ref"], refs) + if "union" in node: + return "Any" + if "const" in node: + value = node["const"] + if isinstance(value, bool): + return "bool" + if isinstance(value, str): + return "str" + return "float" if isinstance(value, float) else "int" + if "primitive" in node: + return _PRIMITIVE[node["primitive"]] # KeyError = a new schema primitive to handle (fail loud) + return "Any" + + def _py_inner(self, node: dict, refs: set[str]) -> tuple[str, set[str]]: + base = self._py_base(node, refs) + return (f"{base} | None" if node.get("nullable") and base != "None" else base), refs + + def _py_ref(self, name: str, refs: set[str], seen: set[str] | None = None) -> str: + seen = seen or set() + if name in seen: + return "Any" + seen.add(name) + type_ = self.types.get(name) + if not type_: + return "Any" + kind = type_["kind"] + if kind == "enum": + refs.add(name) + return type_class_name(name) + if kind == "record": + if not type_["fields"]: + return "Any" + refs.add(name) + return type_class_name(name) + if kind == "union": + refs.add(name) + return value_alias(name) + if kind == "alias": + inner = type_["type"] + if "union" in inner: + refs.add(name) + return value_alias(name) + if "ref" in inner: + return self._py_ref(inner["ref"], refs, seen) + if "list" in inner: + elem, _ = self._py_inner(inner["list"], refs) + return f"list[{elem}]" + return self._py_base(inner, refs) + return "Any" + + _pending_refs: set[str] = set() + + +# --------------------------------------------------------------------------- # +# IR builders +# --------------------------------------------------------------------------- # +_PARAMS_CLASS_KINDS = {"record", "union"} + + +def build_command(schema: Schema, cmd: dict) -> CommandIR: + params = schema.params_for(cmd.get("params")) + if cmd.get("params") and params is None: + raise ValueError(f"command {cmd['method']} has params that cannot be expressed as a typed object") + params_ref = (cmd.get("params") or {}).get("ref") + kind = schema.type_kind(params_ref) + params_class = type_class_name(params_ref) if params and kind in _PARAMS_CLASS_KINDS else None + result_ref = (cmd.get("result") or {}).get("ref") + result_name = schema.structured_ref(result_ref) if result_ref else None + _guard_same_domain(cmd["domain"], params_ref if params_class else None, result_name, cmd["method"]) + result_class = type_class_name(result_name) if result_name else None + if result_name and schema.type_kind(result_name) == "union": + result_type = value_alias(result_name) + elif result_class: + result_type = result_class + else: + result_type = "Any" + return CommandIR( + wire=cmd["method"], + method=safe_name(camel_to_snake(cmd["name"])), + params=params or [], + params_class=params_class, + union_params=(kind == "union"), + result_class=result_class, + result_type=result_type, + ) + + +def build_event(schema: Schema, event: dict) -> EventIR: + params = event.get("params") + payload_ref = params.get("ref") if params else None + payload = schema.structured_ref(payload_ref) if payload_ref else None + _guard_same_domain(event["domain"], None, payload, event["method"]) + return EventIR( + wire=event["method"], + name=safe_name(camel_to_snake(event["name"])), + payload_class=type_class_name(payload) if payload else None, + ) + + +def _guard_same_domain(domain: str, *type_names: str | None) -> None: + for name in type_names: + if name and "." in name and name.split(".", 1)[0] != domain: + raise ValueError(f"{domain}: cross-domain type reference {name!r} not yet supported") + + +def build_module(schema: Schema, domain: str) -> ModuleIR: + schema._pending_refs = set() + records, unions = schema.types_for(domain) + commands = [build_command(schema, c) for c in schema.commands_for(domain)] + events = [build_event(schema, e) for e in schema.events_for(domain)] + imports = _cross_domain_imports(schema, domain) + return ModuleIR( + domain=domain, + class_name=snake_to_class(camel_to_snake(domain)), + filename=camel_to_snake(domain), + enums=schema.enums_for(domain), + records=records, + unions=unions, + commands=commands, + events=events, + imports=imports, + ) + + +def _cross_domain_imports(schema: Schema, domain: str) -> dict[str, set[str]]: + imports: dict[str, set[str]] = {} + for ref in schema._pending_refs: + ref_domain = ref.split(".", 1)[0] + if ref_domain == domain: + continue + module = camel_to_snake(ref_domain) + is_union = schema.type_kind(ref) == "union" or _is_alias_union(schema, ref) + name = value_alias(ref) if is_union else type_class_name(ref) + imports.setdefault(module, set()).add(name) + return imports + + +def _is_alias_union(schema: Schema, ref: str) -> bool: + t = schema.types.get(ref, {}) + return t.get("kind") == "alias" and "union" in t.get("type", {}) + + +# --------------------------------------------------------------------------- # +# Emission +# --------------------------------------------------------------------------- # +def _annotation(f: FieldIR) -> str: + if f.fixed is not _NO_FIXED: + return f.py_type + if f.required: + return f.py_type + return f"{f.py_type} | UnsetType" + + +def _wire_args(f: FieldIR) -> list[str]: + args = [lit(f.wire)] + if f.required: + args.append("required=True") + if f.nullable: + args.append("nullable=True") + if f.ref: + args.append(f"ref={lit(f.ref)}") + if f.is_list: + args.append("is_list=True") + if f.enum: + args.append(f"enum={lit(f.enum)}") + if f.primitive: + args.append(f"primitive={lit(f.primitive)}") + if f.fixed is not _NO_FIXED: + args.append(f"fixed={lit(f.fixed)}") + return args + + +def _emit_field(f: FieldIR, optional_default: bool) -> str: + meta_call = f"meta({', '.join(_wire_args(f))})" + field_args = [] + if f.fixed is not _NO_FIXED: + field_args += [f"default={lit(f.fixed)}", "init=False"] + elif optional_default: + field_args.append("default=UNSET") + field_args.append(f"metadata={meta_call}") + prefix = f" {f.name}: {_annotation(f)} = field(" + return wrap_call(prefix, field_args, ")", 4) + + +def _emit_enum(e: EnumIR) -> str: + lines = [f"@register({lit(e.schema_name)})", f"class {e.class_name}(str, Enum):"] + for member, value in e.members: + lines.append(f" {member} = {lit(value)}") + return "\n".join(lines) + + +def _emit_record(r: RecordIR) -> str: + lines = [f"@register({lit(r.schema_name)})", "@dataclass(frozen=True)", f"class {r.class_name}(Record):"] + if r.extensible: + lines.append(" _EXTENSIBLE = True") + required = [f for f in r.fields if f.required] + optional = [f for f in r.fields if not f.required] + for f in required: + lines.append(_emit_field(f, optional_default=False)) + if r.discriminator: + lines.append(_emit_field(r.discriminator, optional_default=False)) + for f in optional: + lines.append(_emit_field(f, optional_default=True)) + if r.extensible: + lines.append(' extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions"))') + if not r.fields and not r.discriminator and not r.extensible: + lines.append(" pass") + return "\n".join(lines) + + +def _emit_union(u: UnionIR) -> str: + lines = [f"@register({lit(u.schema_name)})", f"class {u.class_name}(Union):"] + value_variants = [v for v in u.variants if v.mode == "value"] + fallback = next((v for v in u.variants if v.mode == "fallback"), None) + presence = [v for v in u.variants if v.mode == "presence"] + if u.discriminator is not None: + lines.append(f" _DISCRIMINATOR = {lit(u.discriminator)}") + if value_variants: + lines.append(" _VARIANTS = {") + for v in value_variants: + lines.append(f" {lit(v.value)}: {lit(v.ref)},") + lines.append(" }") + if presence: + entries = [f"({lit(v.ref)}, {tuple_lit(v.requires)})" for v in presence] + lines.append(wrap_call(" _PRESENCE = (", entries, ")", 4)) + if fallback: + lines.append(f" _FALLBACK = {lit(fallback.ref)}") + if u.discriminator_values: + # Set literal inside frozenset (never frozenset(("x")) — a single value with no + # trailing comma is not a tuple, so it would iterate the string into characters). + values = [lit(v) for v in u.discriminator_values] + lines.append(f" _DISCRIMINATOR_VALUES = {frozenset_lit(values, 4)}") + alias = _emit_type_alias(value_alias(u.schema_name), u.variant_types) + return "\n".join(lines) + "\n\n\n" + alias + + +def _emit_type_alias(name: str, variants: list[str]) -> str: + unique = list(dict.fromkeys(variants)) + one_line = f'{name}: TypeAlias = "{" | ".join(unique)}"' + if len(one_line) <= LINE_LIMIT: + return one_line + # Wrap into parenthesized implicit string concatenation (E501-safe); adjacent + # string literals concatenate, and the " | " separators are kept intact. + tokens = [unique[0]] + [f" | {v}" for v in unique[1:]] + lines: list[str] = [] + cur = "" + for tok in tokens: + if cur and len(f' "{cur}{tok}"') > LINE_LIMIT: + lines.append(f' "{cur}"') + cur = tok + else: + cur += tok + lines.append(f' "{cur}"') + return f"{name}: TypeAlias = (\n" + "\n".join(lines) + "\n)" + + +def _emit_command(c: CommandIR) -> list[str]: + required = [p for p in c.params if p.required] + optional = [p for p in c.params if not p.required] + sig = ["self"] + sig += [f"{p.name}: {p.py_type}" for p in required] + sig += [f"{p.name}: {p.py_type} | UnsetType = UNSET" for p in optional] + lines = [wrap_call(f" def {c.method}(", sig, f") -> {c.result_type}:", 4)] + lines.append(f' """Execute {c.wire} (internal, unsupported)."""') + if c.params_class: + kwargs = [f"{p.name}={p.name}" for p in c.params] + method = "build" if c.union_params else "" + prefix = f" params = {c.params_class}.{method}(" if method else f" params = {c.params_class}(" + lines.append(wrap_call(prefix, kwargs, ")", 8)) + params_arg = "params=params" + else: + params_arg = "params=None" + result_arg = f"result={c.result_class}" if c.result_class else "result=None" + lines.append(f" return self._execute({lit(c.wire)}, {params_arg}, {result_arg})") + return lines + + +def _emit_domain_class(mod: ModuleIR) -> str: + lines = [f"class {mod.class_name}(Domain):", f" # Internal, unsupported. See {BIDI_DOC_URL}"] + if mod.events: + lines.append(" EVENTS = {") + for e in mod.events: + lines.append(f" {lit(e.name)}: {lit(e.wire)},") + lines.append(" }") + lines.append(" EVENT_TYPES = {") + for e in mod.events: + payload = lit(f"{mod.domain}.{e.payload_class}") if e.payload_class else "None" + lines.append(f" {lit(e.wire)}: {payload},") + lines.append(" }") + if not mod.commands and not mod.events: + lines.append(" pass") + body = [] + for c in mod.commands: + body.append("") + body.extend(_emit_command(c)) + return "\n".join(lines + body) + + +def _import_order(name: str) -> tuple[int, str]: + """Ruff isort 'order-by-type': constants (UPPER), then classes, then functions.""" + if name.isupper(): + rank = 0 + elif name[0].isupper(): + rank = 1 + else: + rank = 2 + return (rank, name) + + +def _imports(mod: ModuleIR) -> list[str]: + has_records = bool(mod.records) + has_unions = bool(mod.unions) + uses_unset = ( + any(not f.required for r in mod.records for f in r.fields) + or any(r.extensible for r in mod.records) + or any(not p.required for c in mod.commands for p in c.params) + ) + # "Any" appears in any emitted annotation + text_blob = " ".join( + [f.py_type for r in mod.records for f in r.fields] + + [p.py_type for c in mod.commands for p in c.params] + + [c.result_type for c in mod.commands] + + ["dict[str, Any]" if any(r.extensible for r in mod.records) else ""] + ) + uses_any = "Any" in text_blob.split() or "Any]" in text_blob or "[Any" in text_blob + + typing_names = ["TYPE_CHECKING"] if mod.imports else [] + if uses_any: + typing_names.append("Any") + if has_unions: + typing_names.append("TypeAlias") + + runtime = [] + if has_records: + runtime += ["Record", "meta"] + if has_unions: + runtime.append("Union") + if uses_unset: + runtime += ["UNSET", "UnsetType"] + if mod.enums or has_records or has_unions: + runtime.append("register") + + lines = ["from __future__ import annotations", ""] + if has_records: + lines.append("from dataclasses import dataclass, field") + if mod.enums: + lines.append("from enum import Enum") + if typing_names: + lines.append(f"from typing import {', '.join(dict.fromkeys(typing_names))}") + lines.append("") + if runtime: + names = ", ".join(sorted(set(runtime), key=_import_order)) + lines.append(f"from selenium.webdriver._bidi.serialization import {names}") + lines.append("from selenium.webdriver._bidi.transport import Domain") + if mod.imports: + lines += ["", "if TYPE_CHECKING:"] + for module in sorted(mod.imports): + names = ", ".join(sorted(mod.imports[module])) + lines.append(f" from selenium.webdriver._bidi.{module} import {names}") + return lines + + +def render_module(mod: ModuleIR) -> str: + blocks: list[str] = [_HEADER, "\n".join(_imports(mod))] + for e in mod.enums: + blocks.append(_emit_enum(e)) + for r in mod.records: + blocks.append(_emit_record(r)) + for u in mod.unions: + blocks.append(_emit_union(u)) + blocks.append(_emit_domain_class(mod)) + return "\n\n\n".join(blocks) + "\n" + + +def render_init(modules: list[ModuleIR]) -> str: + ordered = sorted(modules, key=lambda m: m.class_name) + lines = [ + _HEADER, + '"""Internal WebDriver BiDi protocol layer, generated from the BiDi specification.\n\n' + "This package is an internal implementation detail, not a supported public API.\n" + "It carries no stability guarantee and may change without warning between releases.\n" + "Program against the high-level driver APIs instead. See\n" + f"{BIDI_DOC_URL}\n" + '"""', + ] + body = [f"from selenium.webdriver._bidi.{m.filename} import {m.class_name}" for m in ordered] + all_names = ",\n".join(f" {lit(m.class_name)}" for m in ordered) + body_str = "\n".join(body) + "\n\n" + f"__all__ = [\n{all_names},\n]\n" + return "\n\n\n".join(lines) + "\n\n" + body_str + + +def render_all(schema_path: str) -> dict[str, str]: + """Render every domain module + ``__init__``; returns {filename: contents}.""" + schema = Schema(json.loads(Path(schema_path).read_text())) + modules = [build_module(schema, domain) for domain in schema.domains()] + files = {f"{mod.filename}.py": render_module(mod) for mod in modules} + files["__init__.py"] = render_init(modules) + return files + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("schema", help="Path to the binding-neutral BiDi schema JSON") + parser.add_argument( + "output_dir", + nargs="?", + help="Output directory (default: /py/selenium/webdriver/_bidi under `bazel run`)", + ) + args = parser.parse_args() + + out_dir = args.output_dir + if out_dir is None: + workspace = os.environ.get("BUILD_WORKSPACE_DIRECTORY") + if not workspace: + parser.error("output_dir is required unless run via `bazel run` (sets BUILD_WORKSPACE_DIRECTORY)") + out_dir = os.path.join(workspace, DEFAULT_OUTPUT) + + out = Path(out_dir) + out.mkdir(parents=True, exist_ok=True) + for name, contents in render_all(args.schema).items(): + (out / name).write_text(contents) + print(f"bidi-generate: wrote {name}") + + +if __name__ == "__main__": + main() diff --git a/py/selenium/webdriver/_bidi/__init__.py b/py/selenium/webdriver/_bidi/__init__.py new file mode 100644 index 0000000000000..38797bdea9a5c --- /dev/null +++ b/py/selenium/webdriver/_bidi/__init__.py @@ -0,0 +1,61 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +"""Internal WebDriver BiDi protocol layer, generated from the BiDi specification. + +This package is an internal implementation detail, not a supported public API. +It carries no stability guarantee and may change without warning between releases. +Program against the high-level driver APIs instead. See +https://www.selenium.dev/documentation/warnings/bidi-implementation/ +""" + +from selenium.webdriver._bidi.bluetooth import Bluetooth +from selenium.webdriver._bidi.browser import Browser +from selenium.webdriver._bidi.browsing_context import BrowsingContext +from selenium.webdriver._bidi.emulation import Emulation +from selenium.webdriver._bidi.input import Input +from selenium.webdriver._bidi.log import Log +from selenium.webdriver._bidi.network import Network +from selenium.webdriver._bidi.permissions import Permissions +from selenium.webdriver._bidi.script import Script +from selenium.webdriver._bidi.session import Session +from selenium.webdriver._bidi.speculation import Speculation +from selenium.webdriver._bidi.storage import Storage +from selenium.webdriver._bidi.user_agent_client_hints import UserAgentClientHints +from selenium.webdriver._bidi.web_extension import WebExtension + +__all__ = [ + "Bluetooth", + "Browser", + "BrowsingContext", + "Emulation", + "Input", + "Log", + "Network", + "Permissions", + "Script", + "Session", + "Speculation", + "Storage", + "UserAgentClientHints", + "WebExtension", +] diff --git a/py/selenium/webdriver/_bidi/bluetooth.py b/py/selenium/webdriver/_bidi/bluetooth.py new file mode 100644 index 0000000000000..5023ac97288b3 --- /dev/null +++ b/py/selenium/webdriver/_bidi/bluetooth.py @@ -0,0 +1,505 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("bluetooth.SimulateAdapterParametersState") +class SimulateAdapterParametersState(str, Enum): + ABSENT = "absent" + POWERED_OFF = "powered-off" + POWERED_ON = "powered-on" + + +@register("bluetooth.SimulateServiceParametersType") +class SimulateServiceParametersType(str, Enum): + ADD = "add" + REMOVE = "remove" + + +@register("bluetooth.SimulateCharacteristicParametersType") +class SimulateCharacteristicParametersType(str, Enum): + ADD = "add" + REMOVE = "remove" + + +@register("bluetooth.SimulateCharacteristicResponseParametersType") +class SimulateCharacteristicResponseParametersType(str, Enum): + READ = "read" + WRITE = "write" + SUBSCRIBE_TO_NOTIFICATIONS = "subscribe-to-notifications" + UNSUBSCRIBE_FROM_NOTIFICATIONS = "unsubscribe-from-notifications" + + +@register("bluetooth.SimulateDescriptorParametersType") +class SimulateDescriptorParametersType(str, Enum): + ADD = "add" + REMOVE = "remove" + + +@register("bluetooth.SimulateDescriptorResponseParametersType") +class SimulateDescriptorResponseParametersType(str, Enum): + READ = "read" + WRITE = "write" + + +@register("bluetooth.CharacteristicEventGeneratedParametersType") +class CharacteristicEventGeneratedParametersType(str, Enum): + READ = "read" + WRITE_WITH_RESPONSE = "write-with-response" + WRITE_WITHOUT_RESPONSE = "write-without-response" + SUBSCRIBE_TO_NOTIFICATIONS = "subscribe-to-notifications" + UNSUBSCRIBE_FROM_NOTIFICATIONS = "unsubscribe-from-notifications" + + +@register("bluetooth.DescriptorEventGeneratedParametersType") +class DescriptorEventGeneratedParametersType(str, Enum): + READ = "read" + WRITE = "write" + + +@register("bluetooth.BluetoothManufacturerData") +@dataclass(frozen=True) +class BluetoothManufacturerData(Record): + key: int = field(metadata=meta("key", required=True, primitive="int")) + data: str = field(metadata=meta("data", required=True, primitive="str")) + + +@register("bluetooth.CharacteristicProperties") +@dataclass(frozen=True) +class CharacteristicProperties(Record): + broadcast: bool | UnsetType = field(default=UNSET, metadata=meta("broadcast", primitive="bool")) + read: bool | UnsetType = field(default=UNSET, metadata=meta("read", primitive="bool")) + write_without_response: bool | UnsetType = field( + default=UNSET, + metadata=meta("writeWithoutResponse", primitive="bool"), + ) + write: bool | UnsetType = field(default=UNSET, metadata=meta("write", primitive="bool")) + notify: bool | UnsetType = field(default=UNSET, metadata=meta("notify", primitive="bool")) + indicate: bool | UnsetType = field(default=UNSET, metadata=meta("indicate", primitive="bool")) + authenticated_signed_writes: bool | UnsetType = field( + default=UNSET, + metadata=meta("authenticatedSignedWrites", primitive="bool"), + ) + extended_properties: bool | UnsetType = field( + default=UNSET, + metadata=meta("extendedProperties", primitive="bool"), + ) + + +@register("bluetooth.RequestDeviceInfo") +@dataclass(frozen=True) +class RequestDeviceInfo(Record): + id: str = field(metadata=meta("id", required=True, primitive="str")) + name: str | None = field(metadata=meta("name", required=True, nullable=True, primitive="str")) + + +@register("bluetooth.ScanRecord") +@dataclass(frozen=True) +class ScanRecord(Record): + name: str | UnsetType = field(default=UNSET, metadata=meta("name", primitive="str")) + uuids: list[str] | UnsetType = field(default=UNSET, metadata=meta("uuids", is_list=True, primitive="str")) + appearance: float | UnsetType = field(default=UNSET, metadata=meta("appearance", primitive="float")) + manufacturer_data: list[BluetoothManufacturerData] | UnsetType = field( + default=UNSET, + metadata=meta("manufacturerData", ref="bluetooth.BluetoothManufacturerData", is_list=True), + ) + + +@register("bluetooth.SimulateAdapterParameters") +@dataclass(frozen=True) +class SimulateAdapterParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + state: SimulateAdapterParametersState = field( + metadata=meta("state", required=True, enum="bluetooth.SimulateAdapterParametersState"), + ) + le_supported: bool | UnsetType = field(default=UNSET, metadata=meta("leSupported", primitive="bool")) + + +@register("bluetooth.DisableSimulationParameters") +@dataclass(frozen=True) +class DisableSimulationParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("bluetooth.SimulatePreconnectedPeripheralParameters") +@dataclass(frozen=True) +class SimulatePreconnectedPeripheralParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + name: str = field(metadata=meta("name", required=True, primitive="str")) + manufacturer_data: list[BluetoothManufacturerData] = field( + metadata=meta("manufacturerData", required=True, ref="bluetooth.BluetoothManufacturerData", is_list=True), + ) + known_service_uuids: list[str] = field( + metadata=meta("knownServiceUuids", required=True, is_list=True, primitive="str"), + ) + + +@register("bluetooth.SimulateAdvertisementParameters") +@dataclass(frozen=True) +class SimulateAdvertisementParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + scan_entry: SimulateAdvertisementScanEntryParameters = field( + metadata=meta("scanEntry", required=True, ref="bluetooth.SimulateAdvertisementScanEntryParameters"), + ) + + +@register("bluetooth.SimulateAdvertisementScanEntryParameters") +@dataclass(frozen=True) +class SimulateAdvertisementScanEntryParameters(Record): + device_address: str = field(metadata=meta("deviceAddress", required=True, primitive="str")) + rssi: float = field(metadata=meta("rssi", required=True, primitive="float")) + scan_record: ScanRecord = field(metadata=meta("scanRecord", required=True, ref="bluetooth.ScanRecord")) + + +@register("bluetooth.SimulateGattConnectionResponseParameters") +@dataclass(frozen=True) +class SimulateGattConnectionResponseParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + code: int = field(metadata=meta("code", required=True, primitive="int")) + + +@register("bluetooth.SimulateGattDisconnectionParameters") +@dataclass(frozen=True) +class SimulateGattDisconnectionParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + + +@register("bluetooth.SimulateServiceParameters") +@dataclass(frozen=True) +class SimulateServiceParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + uuid: str = field(metadata=meta("uuid", required=True, primitive="str")) + type: SimulateServiceParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.SimulateServiceParametersType"), + ) + + +@register("bluetooth.SimulateCharacteristicParameters") +@dataclass(frozen=True) +class SimulateCharacteristicParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + type: SimulateCharacteristicParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.SimulateCharacteristicParametersType"), + ) + characteristic_properties: CharacteristicProperties | UnsetType = field( + default=UNSET, + metadata=meta("characteristicProperties", ref="bluetooth.CharacteristicProperties"), + ) + + +@register("bluetooth.SimulateCharacteristicResponseParameters") +@dataclass(frozen=True) +class SimulateCharacteristicResponseParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + type: SimulateCharacteristicResponseParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.SimulateCharacteristicResponseParametersType"), + ) + code: int = field(metadata=meta("code", required=True, primitive="int")) + data: list[int] | UnsetType = field(default=UNSET, metadata=meta("data", is_list=True, primitive="int")) + + +@register("bluetooth.SimulateDescriptorParameters") +@dataclass(frozen=True) +class SimulateDescriptorParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + descriptor_uuid: str = field(metadata=meta("descriptorUuid", required=True, primitive="str")) + type: SimulateDescriptorParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.SimulateDescriptorParametersType"), + ) + + +@register("bluetooth.SimulateDescriptorResponseParameters") +@dataclass(frozen=True) +class SimulateDescriptorResponseParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + descriptor_uuid: str = field(metadata=meta("descriptorUuid", required=True, primitive="str")) + type: SimulateDescriptorResponseParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.SimulateDescriptorResponseParametersType"), + ) + code: int = field(metadata=meta("code", required=True, primitive="int")) + data: list[int] | UnsetType = field(default=UNSET, metadata=meta("data", is_list=True, primitive="int")) + + +@register("bluetooth.RequestDevicePromptUpdatedParameters") +@dataclass(frozen=True) +class RequestDevicePromptUpdatedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + prompt: str = field(metadata=meta("prompt", required=True, primitive="str")) + devices: list[RequestDeviceInfo] = field( + metadata=meta("devices", required=True, ref="bluetooth.RequestDeviceInfo", is_list=True), + ) + + +@register("bluetooth.GattConnectionAttemptedParameters") +@dataclass(frozen=True) +class GattConnectionAttemptedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + + +@register("bluetooth.CharacteristicEventGeneratedParameters") +@dataclass(frozen=True) +class CharacteristicEventGeneratedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + type: CharacteristicEventGeneratedParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.CharacteristicEventGeneratedParametersType"), + ) + data: list[int] | UnsetType = field(default=UNSET, metadata=meta("data", is_list=True, primitive="int")) + + +@register("bluetooth.DescriptorEventGeneratedParameters") +@dataclass(frozen=True) +class DescriptorEventGeneratedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + address: str = field(metadata=meta("address", required=True, primitive="str")) + service_uuid: str = field(metadata=meta("serviceUuid", required=True, primitive="str")) + characteristic_uuid: str = field(metadata=meta("characteristicUuid", required=True, primitive="str")) + descriptor_uuid: str = field(metadata=meta("descriptorUuid", required=True, primitive="str")) + type: DescriptorEventGeneratedParametersType = field( + metadata=meta("type", required=True, enum="bluetooth.DescriptorEventGeneratedParametersType"), + ) + data: list[int] | UnsetType = field(default=UNSET, metadata=meta("data", is_list=True, primitive="int")) + + +@register("bluetooth.HandleRequestDevicePromptParameters_AcceptParameters") +@dataclass(frozen=True) +class HandleRequestDevicePromptParametersAcceptParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + prompt: str = field(metadata=meta("prompt", required=True, primitive="str")) + device: str = field(metadata=meta("device", required=True, primitive="str")) + accept: bool = field(default=True, init=False, metadata=meta("accept", required=True, fixed=True)) + + +@register("bluetooth.HandleRequestDevicePromptParameters_CancelParameters") +@dataclass(frozen=True) +class HandleRequestDevicePromptParametersCancelParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + prompt: str = field(metadata=meta("prompt", required=True, primitive="str")) + accept: bool = field(default=False, init=False, metadata=meta("accept", required=True, fixed=False)) + + +@register("bluetooth.HandleRequestDevicePromptParameters") +class HandleRequestDevicePromptParameters(Union): + _DISCRIMINATOR = "accept" + _VARIANTS = { + True: "bluetooth.HandleRequestDevicePromptParameters_AcceptParameters", + False: "bluetooth.HandleRequestDevicePromptParameters_CancelParameters", + } + + +HandleRequestDevicePromptParametersValue: TypeAlias = ( + "HandleRequestDevicePromptParametersAcceptParameters | HandleRequestDevicePromptParametersCancelParameters" +) + + +class Bluetooth(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "request_device_prompt_updated": "bluetooth.requestDevicePromptUpdated", + "gatt_connection_attempted": "bluetooth.gattConnectionAttempted", + } + EVENT_TYPES = { + "bluetooth.requestDevicePromptUpdated": "bluetooth.RequestDevicePromptUpdatedParameters", + "bluetooth.gattConnectionAttempted": "bluetooth.GattConnectionAttemptedParameters", + } + + def handle_request_device_prompt( + self, + context: str, + prompt: str, + accept: bool, + device: str | UnsetType = UNSET, + ) -> Any: + """Execute bluetooth.handleRequestDevicePrompt (internal, unsupported).""" + params = HandleRequestDevicePromptParameters.build( + context=context, + prompt=prompt, + accept=accept, + device=device, + ) + return self._execute("bluetooth.handleRequestDevicePrompt", params=params, result=None) + + def simulate_adapter( + self, + context: str, + state: SimulateAdapterParametersState, + le_supported: bool | UnsetType = UNSET, + ) -> Any: + """Execute bluetooth.simulateAdapter (internal, unsupported).""" + params = SimulateAdapterParameters(context=context, le_supported=le_supported, state=state) + return self._execute("bluetooth.simulateAdapter", params=params, result=None) + + def disable_simulation(self, context: str) -> Any: + """Execute bluetooth.disableSimulation (internal, unsupported).""" + params = DisableSimulationParameters(context=context) + return self._execute("bluetooth.disableSimulation", params=params, result=None) + + def simulate_preconnected_peripheral( + self, + context: str, + address: str, + name: str, + manufacturer_data: list[BluetoothManufacturerData], + known_service_uuids: list[str], + ) -> Any: + """Execute bluetooth.simulatePreconnectedPeripheral (internal, unsupported).""" + params = SimulatePreconnectedPeripheralParameters( + context=context, + address=address, + name=name, + manufacturer_data=manufacturer_data, + known_service_uuids=known_service_uuids, + ) + return self._execute("bluetooth.simulatePreconnectedPeripheral", params=params, result=None) + + def simulate_advertisement(self, context: str, scan_entry: SimulateAdvertisementScanEntryParameters) -> Any: + """Execute bluetooth.simulateAdvertisement (internal, unsupported).""" + params = SimulateAdvertisementParameters(context=context, scan_entry=scan_entry) + return self._execute("bluetooth.simulateAdvertisement", params=params, result=None) + + def simulate_gatt_connection_response(self, context: str, address: str, code: int) -> Any: + """Execute bluetooth.simulateGattConnectionResponse (internal, unsupported).""" + params = SimulateGattConnectionResponseParameters(context=context, address=address, code=code) + return self._execute("bluetooth.simulateGattConnectionResponse", params=params, result=None) + + def simulate_gatt_disconnection(self, context: str, address: str) -> Any: + """Execute bluetooth.simulateGattDisconnection (internal, unsupported).""" + params = SimulateGattDisconnectionParameters(context=context, address=address) + return self._execute("bluetooth.simulateGattDisconnection", params=params, result=None) + + def simulate_service(self, context: str, address: str, uuid: str, type: SimulateServiceParametersType) -> Any: + """Execute bluetooth.simulateService (internal, unsupported).""" + params = SimulateServiceParameters(context=context, address=address, uuid=uuid, type=type) + return self._execute("bluetooth.simulateService", params=params, result=None) + + def simulate_characteristic( + self, + context: str, + address: str, + service_uuid: str, + characteristic_uuid: str, + type: SimulateCharacteristicParametersType, + characteristic_properties: CharacteristicProperties | UnsetType = UNSET, + ) -> Any: + """Execute bluetooth.simulateCharacteristic (internal, unsupported).""" + params = SimulateCharacteristicParameters( + context=context, + address=address, + service_uuid=service_uuid, + characteristic_uuid=characteristic_uuid, + characteristic_properties=characteristic_properties, + type=type, + ) + return self._execute("bluetooth.simulateCharacteristic", params=params, result=None) + + def simulate_characteristic_response( + self, + context: str, + address: str, + service_uuid: str, + characteristic_uuid: str, + type: SimulateCharacteristicResponseParametersType, + code: int, + data: list[int] | UnsetType = UNSET, + ) -> Any: + """Execute bluetooth.simulateCharacteristicResponse (internal, unsupported).""" + params = SimulateCharacteristicResponseParameters( + context=context, + address=address, + service_uuid=service_uuid, + characteristic_uuid=characteristic_uuid, + type=type, + code=code, + data=data, + ) + return self._execute("bluetooth.simulateCharacteristicResponse", params=params, result=None) + + def simulate_descriptor( + self, + context: str, + address: str, + service_uuid: str, + characteristic_uuid: str, + descriptor_uuid: str, + type: SimulateDescriptorParametersType, + ) -> Any: + """Execute bluetooth.simulateDescriptor (internal, unsupported).""" + params = SimulateDescriptorParameters( + context=context, + address=address, + service_uuid=service_uuid, + characteristic_uuid=characteristic_uuid, + descriptor_uuid=descriptor_uuid, + type=type, + ) + return self._execute("bluetooth.simulateDescriptor", params=params, result=None) + + def simulate_descriptor_response( + self, + context: str, + address: str, + service_uuid: str, + characteristic_uuid: str, + descriptor_uuid: str, + type: SimulateDescriptorResponseParametersType, + code: int, + data: list[int] | UnsetType = UNSET, + ) -> Any: + """Execute bluetooth.simulateDescriptorResponse (internal, unsupported).""" + params = SimulateDescriptorResponseParameters( + context=context, + address=address, + service_uuid=service_uuid, + characteristic_uuid=characteristic_uuid, + descriptor_uuid=descriptor_uuid, + type=type, + code=code, + data=data, + ) + return self._execute("bluetooth.simulateDescriptorResponse", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/browser.py b/py/selenium/webdriver/_bidi/browser.py new file mode 100644 index 0000000000000..ba51387ece4c2 --- /dev/null +++ b/py/selenium/webdriver/_bidi/browser.py @@ -0,0 +1,244 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import TYPE_CHECKING, Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.session import ProxyConfigurationValue, UserPromptHandler + + +@register("browser.ClientWindowInfoState") +class ClientWindowInfoState(str, Enum): + FULLSCREEN = "fullscreen" + MAXIMIZED = "maximized" + MINIMIZED = "minimized" + NORMAL = "normal" + + +@register("browser.ClientWindowNamedStateState") +class ClientWindowNamedStateState(str, Enum): + FULLSCREEN = "fullscreen" + MAXIMIZED = "maximized" + MINIMIZED = "minimized" + + +@register("browser.ClientWindowInfo") +@dataclass(frozen=True) +class ClientWindowInfo(Record): + active: bool = field(metadata=meta("active", required=True, primitive="bool")) + client_window: str = field(metadata=meta("clientWindow", required=True, primitive="str")) + height: int = field(metadata=meta("height", required=True, primitive="int")) + state: ClientWindowInfoState = field( + metadata=meta("state", required=True, enum="browser.ClientWindowInfoState"), + ) + width: int = field(metadata=meta("width", required=True, primitive="int")) + x: int = field(metadata=meta("x", required=True, primitive="int")) + y: int = field(metadata=meta("y", required=True, primitive="int")) + + +@register("browser.UserContextInfo") +@dataclass(frozen=True) +class UserContextInfo(Record): + user_context: str = field(metadata=meta("userContext", required=True, primitive="str")) + + +@register("browser.CreateUserContextParameters") +@dataclass(frozen=True) +class CreateUserContextParameters(Record): + accept_insecure_certs: bool | UnsetType = field( + default=UNSET, + metadata=meta("acceptInsecureCerts", primitive="bool"), + ) + proxy: ProxyConfigurationValue | UnsetType = field( + default=UNSET, + metadata=meta("proxy", ref="session.ProxyConfiguration"), + ) + unhandled_prompt_behavior: UserPromptHandler | UnsetType = field( + default=UNSET, + metadata=meta("unhandledPromptBehavior", ref="session.UserPromptHandler"), + ) + + +@register("browser.GetClientWindowsResult") +@dataclass(frozen=True) +class GetClientWindowsResult(Record): + client_windows: list[ClientWindowInfo] = field( + metadata=meta("clientWindows", required=True, ref="browser.ClientWindowInfo", is_list=True), + ) + + +@register("browser.GetUserContextsResult") +@dataclass(frozen=True) +class GetUserContextsResult(Record): + user_contexts: list[UserContextInfo] = field( + metadata=meta("userContexts", required=True, ref="browser.UserContextInfo", is_list=True), + ) + + +@register("browser.RemoveUserContextParameters") +@dataclass(frozen=True) +class RemoveUserContextParameters(Record): + user_context: str = field(metadata=meta("userContext", required=True, primitive="str")) + + +@register("browser.SetDownloadBehaviorParameters") +@dataclass(frozen=True) +class SetDownloadBehaviorParameters(Record): + download_behavior: DownloadBehaviorValue | None = field( + metadata=meta("downloadBehavior", required=True, nullable=True, ref="browser.DownloadBehavior"), + ) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("browser.SetClientWindowStateParameters_ClientWindowNamedState") +@dataclass(frozen=True) +class SetClientWindowStateParametersClientWindowNamedState(Record): + client_window: str = field(metadata=meta("clientWindow", required=True, primitive="str")) + state: ClientWindowNamedStateState = field( + metadata=meta("state", required=True, enum="browser.ClientWindowNamedStateState"), + ) + + +@register("browser.SetClientWindowStateParameters_ClientWindowRectState") +@dataclass(frozen=True) +class SetClientWindowStateParametersClientWindowRectState(Record): + client_window: str = field(metadata=meta("clientWindow", required=True, primitive="str")) + state: str = field(default="normal", init=False, metadata=meta("state", required=True, fixed="normal")) + width: int | UnsetType = field(default=UNSET, metadata=meta("width", primitive="int")) + height: int | UnsetType = field(default=UNSET, metadata=meta("height", primitive="int")) + x: int | UnsetType = field(default=UNSET, metadata=meta("x", primitive="int")) + y: int | UnsetType = field(default=UNSET, metadata=meta("y", primitive="int")) + + +@register("browser.DownloadBehavior_Allowed") +@dataclass(frozen=True) +class DownloadBehaviorAllowed(Record): + destination_folder: str = field(metadata=meta("destinationFolder", required=True, primitive="str")) + type: str = field(default="allowed", init=False, metadata=meta("type", required=True, fixed="allowed")) + + +@register("browser.DownloadBehavior_Denied") +@dataclass(frozen=True) +class DownloadBehaviorDenied(Record): + type: str = field(default="denied", init=False, metadata=meta("type", required=True, fixed="denied")) + + +@register("browser.SetClientWindowStateParameters") +class SetClientWindowStateParameters(Union): + _DISCRIMINATOR = "state" + _VARIANTS = { + "normal": "browser.SetClientWindowStateParameters_ClientWindowRectState", + } + _FALLBACK = "browser.SetClientWindowStateParameters_ClientWindowNamedState" + _DISCRIMINATOR_VALUES = frozenset({"normal", "fullscreen", "maximized", "minimized"}) + + +SetClientWindowStateParametersValue: TypeAlias = ( + "SetClientWindowStateParametersClientWindowRectState | SetClientWindowStateParametersClientWindowNamedState" +) + + +@register("browser.DownloadBehavior") +class DownloadBehavior(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "allowed": "browser.DownloadBehavior_Allowed", + "denied": "browser.DownloadBehavior_Denied", + } + _DISCRIMINATOR_VALUES = frozenset({"allowed", "denied"}) + + +DownloadBehaviorValue: TypeAlias = "DownloadBehaviorAllowed | DownloadBehaviorDenied" + + +class Browser(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def close(self) -> Any: + """Execute browser.close (internal, unsupported).""" + return self._execute("browser.close", params=None, result=None) + + def create_user_context( + self, + accept_insecure_certs: bool | UnsetType = UNSET, + proxy: ProxyConfigurationValue | UnsetType = UNSET, + unhandled_prompt_behavior: UserPromptHandler | UnsetType = UNSET, + ) -> UserContextInfo: + """Execute browser.createUserContext (internal, unsupported).""" + params = CreateUserContextParameters( + accept_insecure_certs=accept_insecure_certs, + proxy=proxy, + unhandled_prompt_behavior=unhandled_prompt_behavior, + ) + return self._execute("browser.createUserContext", params=params, result=UserContextInfo) + + def get_client_windows(self) -> GetClientWindowsResult: + """Execute browser.getClientWindows (internal, unsupported).""" + return self._execute("browser.getClientWindows", params=None, result=GetClientWindowsResult) + + def get_user_contexts(self) -> GetUserContextsResult: + """Execute browser.getUserContexts (internal, unsupported).""" + return self._execute("browser.getUserContexts", params=None, result=GetUserContextsResult) + + def remove_user_context(self, user_context: str) -> Any: + """Execute browser.removeUserContext (internal, unsupported).""" + params = RemoveUserContextParameters(user_context=user_context) + return self._execute("browser.removeUserContext", params=params, result=None) + + def set_client_window_state( + self, + client_window: str, + state: ClientWindowNamedStateState, + width: int | UnsetType = UNSET, + height: int | UnsetType = UNSET, + x: int | UnsetType = UNSET, + y: int | UnsetType = UNSET, + ) -> ClientWindowInfo: + """Execute browser.setClientWindowState (internal, unsupported).""" + params = SetClientWindowStateParameters.build( + client_window=client_window, + state=state, + width=width, + height=height, + x=x, + y=y, + ) + return self._execute("browser.setClientWindowState", params=params, result=ClientWindowInfo) + + def set_download_behavior( + self, + download_behavior: DownloadBehaviorValue | None, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute browser.setDownloadBehavior (internal, unsupported).""" + params = SetDownloadBehaviorParameters(download_behavior=download_behavior, user_contexts=user_contexts) + return self._execute("browser.setDownloadBehavior", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/browsing_context.py b/py/selenium/webdriver/_bidi/browsing_context.py new file mode 100644 index 0000000000000..ec152895a9917 --- /dev/null +++ b/py/selenium/webdriver/_bidi/browsing_context.py @@ -0,0 +1,693 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import TYPE_CHECKING, Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.script import NodeRemoteValue, SerializationOptions, SharedReference + from selenium.webdriver._bidi.session import UserPromptHandlerType + + +@register("browsingContext.ReadinessState") +class ReadinessState(str, Enum): + NONE = "none" + INTERACTIVE = "interactive" + COMPLETE = "complete" + + +@register("browsingContext.UserPromptType") +class UserPromptType(str, Enum): + ALERT = "alert" + BEFOREUNLOAD = "beforeunload" + CONFIRM = "confirm" + PROMPT = "prompt" + + +@register("browsingContext.CreateType") +class CreateType(str, Enum): + TAB = "tab" + WINDOW = "window" + + +@register("browsingContext.InnerTextLocatorMatchType") +class InnerTextLocatorMatchType(str, Enum): + FULL = "full" + PARTIAL = "partial" + + +@register("browsingContext.CaptureScreenshotParametersOrigin") +class CaptureScreenshotParametersOrigin(str, Enum): + VIEWPORT = "viewport" + DOCUMENT = "document" + + +@register("browsingContext.PrintParametersOrientation") +class PrintParametersOrientation(str, Enum): + PORTRAIT = "portrait" + LANDSCAPE = "landscape" + + +@register("browsingContext.Info") +@dataclass(frozen=True) +class Info(Record): + children: list[Info] | None = field( + metadata=meta("children", required=True, nullable=True, ref="browsingContext.Info", is_list=True), + ) + client_window: str = field(metadata=meta("clientWindow", required=True, primitive="str")) + context: str = field(metadata=meta("context", required=True, primitive="str")) + original_opener: str | None = field( + metadata=meta("originalOpener", required=True, nullable=True, primitive="str"), + ) + url: str = field(metadata=meta("url", required=True, primitive="str")) + user_context: str = field(metadata=meta("userContext", required=True, primitive="str")) + parent: str | None | UnsetType = field(default=UNSET, metadata=meta("parent", nullable=True, primitive="str")) + + +@register("browsingContext.AccessibilityLocator") +@dataclass(frozen=True) +class AccessibilityLocator(Record): + value: AccessibilityLocatorValue = field( + metadata=meta("value", required=True, ref="browsingContext.AccessibilityLocatorValue"), + ) + type: str = field( + default="accessibility", + init=False, + metadata=meta("type", required=True, fixed="accessibility"), + ) + + +@register("browsingContext.CssLocator") +@dataclass(frozen=True) +class CssLocator(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="css", init=False, metadata=meta("type", required=True, fixed="css")) + + +@register("browsingContext.ContextLocator") +@dataclass(frozen=True) +class ContextLocator(Record): + value: ContextLocatorValue = field( + metadata=meta("value", required=True, ref="browsingContext.ContextLocatorValue"), + ) + type: str = field(default="context", init=False, metadata=meta("type", required=True, fixed="context")) + + +@register("browsingContext.InnerTextLocator") +@dataclass(frozen=True) +class InnerTextLocator(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="innerText", init=False, metadata=meta("type", required=True, fixed="innerText")) + ignore_case: bool | UnsetType = field(default=UNSET, metadata=meta("ignoreCase", primitive="bool")) + match_type: InnerTextLocatorMatchType | UnsetType = field( + default=UNSET, + metadata=meta("matchType", enum="browsingContext.InnerTextLocatorMatchType"), + ) + max_depth: int | UnsetType = field(default=UNSET, metadata=meta("maxDepth", primitive="int")) + + +@register("browsingContext.XPathLocator") +@dataclass(frozen=True) +class XPathLocator(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="xpath", init=False, metadata=meta("type", required=True, fixed="xpath")) + + +@register("browsingContext.BaseNavigationInfo") +@dataclass(frozen=True) +class BaseNavigationInfo(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.NavigationInfo") +@dataclass(frozen=True) +class NavigationInfo(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.ActivateParameters") +@dataclass(frozen=True) +class ActivateParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("browsingContext.CaptureScreenshotParameters") +@dataclass(frozen=True) +class CaptureScreenshotParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + origin: CaptureScreenshotParametersOrigin | UnsetType = field( + default=UNSET, + metadata=meta("origin", enum="browsingContext.CaptureScreenshotParametersOrigin"), + ) + format: ImageFormat | UnsetType = field( + default=UNSET, + metadata=meta("format", ref="browsingContext.ImageFormat"), + ) + clip: ClipRectangleValue | UnsetType = field( + default=UNSET, + metadata=meta("clip", ref="browsingContext.ClipRectangle"), + ) + + +@register("browsingContext.ImageFormat") +@dataclass(frozen=True) +class ImageFormat(Record): + type: str = field(metadata=meta("type", required=True, primitive="str")) + quality: int | UnsetType = field(default=UNSET, metadata=meta("quality", primitive="int")) + + +@register("browsingContext.ElementClipRectangle") +@dataclass(frozen=True) +class ElementClipRectangle(Record): + element: SharedReference = field(metadata=meta("element", required=True, ref="script.SharedReference")) + type: str = field(default="element", init=False, metadata=meta("type", required=True, fixed="element")) + + +@register("browsingContext.BoxClipRectangle") +@dataclass(frozen=True) +class BoxClipRectangle(Record): + x: float = field(metadata=meta("x", required=True, primitive="float")) + y: float = field(metadata=meta("y", required=True, primitive="float")) + width: float = field(metadata=meta("width", required=True, primitive="float")) + height: float = field(metadata=meta("height", required=True, primitive="float")) + type: str = field(default="box", init=False, metadata=meta("type", required=True, fixed="box")) + + +@register("browsingContext.CaptureScreenshotResult") +@dataclass(frozen=True) +class CaptureScreenshotResult(Record): + data: str = field(metadata=meta("data", required=True, primitive="str")) + + +@register("browsingContext.CloseParameters") +@dataclass(frozen=True) +class CloseParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + prompt_unload: bool | UnsetType = field(default=UNSET, metadata=meta("promptUnload", primitive="bool")) + + +@register("browsingContext.CreateParameters") +@dataclass(frozen=True) +class CreateParameters(Record): + type: CreateType = field(metadata=meta("type", required=True, enum="browsingContext.CreateType")) + reference_context: str | UnsetType = field(default=UNSET, metadata=meta("referenceContext", primitive="str")) + background: bool | UnsetType = field(default=UNSET, metadata=meta("background", primitive="bool")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.CreateResult") +@dataclass(frozen=True) +class CreateResult(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.GetTreeParameters") +@dataclass(frozen=True) +class GetTreeParameters(Record): + max_depth: int | UnsetType = field(default=UNSET, metadata=meta("maxDepth", primitive="int")) + root: str | UnsetType = field(default=UNSET, metadata=meta("root", primitive="str")) + + +@register("browsingContext.GetTreeResult") +@dataclass(frozen=True) +class GetTreeResult(Record): + contexts: list[Info] = field(metadata=meta("contexts", required=True, ref="browsingContext.Info", is_list=True)) + + +@register("browsingContext.HandleUserPromptParameters") +@dataclass(frozen=True) +class HandleUserPromptParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + accept: bool | UnsetType = field(default=UNSET, metadata=meta("accept", primitive="bool")) + user_text: str | UnsetType = field(default=UNSET, metadata=meta("userText", primitive="str")) + + +@register("browsingContext.LocateNodesParameters") +@dataclass(frozen=True) +class LocateNodesParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + locator: LocatorValue = field(metadata=meta("locator", required=True, ref="browsingContext.Locator")) + max_node_count: int | UnsetType = field(default=UNSET, metadata=meta("maxNodeCount", primitive="int")) + serialization_options: SerializationOptions | UnsetType = field( + default=UNSET, + metadata=meta("serializationOptions", ref="script.SerializationOptions"), + ) + start_nodes: list[SharedReference] | UnsetType = field( + default=UNSET, + metadata=meta("startNodes", ref="script.SharedReference", is_list=True), + ) + + +@register("browsingContext.LocateNodesResult") +@dataclass(frozen=True) +class LocateNodesResult(Record): + nodes: list[NodeRemoteValue] = field( + metadata=meta("nodes", required=True, ref="script.NodeRemoteValue", is_list=True), + ) + + +@register("browsingContext.NavigateParameters") +@dataclass(frozen=True) +class NavigateParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + wait: ReadinessState | UnsetType = field( + default=UNSET, + metadata=meta("wait", enum="browsingContext.ReadinessState"), + ) + + +@register("browsingContext.NavigateResult") +@dataclass(frozen=True) +class NavigateResult(Record): + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + + +@register("browsingContext.PrintParameters") +@dataclass(frozen=True) +class PrintParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + background: bool | UnsetType = field(default=UNSET, metadata=meta("background", primitive="bool")) + margin: PrintMarginParameters | UnsetType = field( + default=UNSET, + metadata=meta("margin", ref="browsingContext.PrintMarginParameters"), + ) + orientation: PrintParametersOrientation | UnsetType = field( + default=UNSET, + metadata=meta("orientation", enum="browsingContext.PrintParametersOrientation"), + ) + page: PrintPageParameters | UnsetType = field( + default=UNSET, + metadata=meta("page", ref="browsingContext.PrintPageParameters"), + ) + page_ranges: list[Any] | UnsetType = field(default=UNSET, metadata=meta("pageRanges", is_list=True)) + scale: float | UnsetType = field(default=UNSET, metadata=meta("scale", primitive="float")) + shrink_to_fit: bool | UnsetType = field(default=UNSET, metadata=meta("shrinkToFit", primitive="bool")) + + +@register("browsingContext.PrintMarginParameters") +@dataclass(frozen=True) +class PrintMarginParameters(Record): + bottom: float | UnsetType = field(default=UNSET, metadata=meta("bottom", primitive="float")) + left: float | UnsetType = field(default=UNSET, metadata=meta("left", primitive="float")) + right: float | UnsetType = field(default=UNSET, metadata=meta("right", primitive="float")) + top: float | UnsetType = field(default=UNSET, metadata=meta("top", primitive="float")) + + +@register("browsingContext.PrintPageParameters") +@dataclass(frozen=True) +class PrintPageParameters(Record): + height: float | UnsetType = field(default=UNSET, metadata=meta("height", primitive="float")) + width: float | UnsetType = field(default=UNSET, metadata=meta("width", primitive="float")) + + +@register("browsingContext.PrintResult") +@dataclass(frozen=True) +class PrintResult(Record): + data: str = field(metadata=meta("data", required=True, primitive="str")) + + +@register("browsingContext.ReloadParameters") +@dataclass(frozen=True) +class ReloadParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + ignore_cache: bool | UnsetType = field(default=UNSET, metadata=meta("ignoreCache", primitive="bool")) + wait: ReadinessState | UnsetType = field( + default=UNSET, + metadata=meta("wait", enum="browsingContext.ReadinessState"), + ) + + +@register("browsingContext.SetBypassCSPParameters") +@dataclass(frozen=True) +class SetBypassCSPParameters(Record): + bypass: bool | None = field(metadata=meta("bypass", required=True, nullable=True)) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("browsingContext.SetViewportParameters") +@dataclass(frozen=True) +class SetViewportParameters(Record): + context: str | UnsetType = field(default=UNSET, metadata=meta("context", primitive="str")) + viewport: Viewport | None | UnsetType = field( + default=UNSET, + metadata=meta("viewport", nullable=True, ref="browsingContext.Viewport"), + ) + device_pixel_ratio: float | None | UnsetType = field( + default=UNSET, + metadata=meta("devicePixelRatio", nullable=True, primitive="float"), + ) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("browsingContext.Viewport") +@dataclass(frozen=True) +class Viewport(Record): + width: int = field(metadata=meta("width", required=True, primitive="int")) + height: int = field(metadata=meta("height", required=True, primitive="int")) + + +@register("browsingContext.TraverseHistoryParameters") +@dataclass(frozen=True) +class TraverseHistoryParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + delta: int = field(metadata=meta("delta", required=True, primitive="int")) + + +@register("browsingContext.HistoryUpdatedParameters") +@dataclass(frozen=True) +class HistoryUpdatedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.DownloadWillBeginParams") +@dataclass(frozen=True) +class DownloadWillBeginParams(Record): + suggested_filename: str = field(metadata=meta("suggestedFilename", required=True, primitive="str")) + context: str = field(metadata=meta("context", required=True, primitive="str")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.UserPromptClosedParameters") +@dataclass(frozen=True) +class UserPromptClosedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + accepted: bool = field(metadata=meta("accepted", required=True, primitive="bool")) + type: UserPromptType = field(metadata=meta("type", required=True, enum="browsingContext.UserPromptType")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + user_text: str | UnsetType = field(default=UNSET, metadata=meta("userText", primitive="str")) + + +@register("browsingContext.UserPromptOpenedParameters") +@dataclass(frozen=True) +class UserPromptOpenedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + handler: UserPromptHandlerType = field( + metadata=meta("handler", required=True, enum="session.UserPromptHandlerType"), + ) + message: str = field(metadata=meta("message", required=True, primitive="str")) + type: UserPromptType = field(metadata=meta("type", required=True, enum="browsingContext.UserPromptType")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + default_value: str | UnsetType = field(default=UNSET, metadata=meta("defaultValue", primitive="str")) + + +@register("browsingContext.DownloadEndParams_CanceledParams") +@dataclass(frozen=True) +class DownloadEndParamsCanceledParams(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + status: str = field(default="canceled", init=False, metadata=meta("status", required=True, fixed="canceled")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.DownloadEndParams_CompleteParams") +@dataclass(frozen=True) +class DownloadEndParamsCompleteParams(Record): + filepath: str | None = field(metadata=meta("filepath", required=True, nullable=True, primitive="str")) + context: str = field(metadata=meta("context", required=True, primitive="str")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + status: str = field(default="complete", init=False, metadata=meta("status", required=True, fixed="complete")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("browsingContext.AccessibilityLocatorValue") +@dataclass(frozen=True) +class AccessibilityLocatorValue(Record): + name: str | UnsetType = field(default=UNSET, metadata=meta("name", primitive="str")) + role: str | UnsetType = field(default=UNSET, metadata=meta("role", primitive="str")) + + +@register("browsingContext.ContextLocatorValue") +@dataclass(frozen=True) +class ContextLocatorValue(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("browsingContext.Locator") +class Locator(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "accessibility": "browsingContext.AccessibilityLocator", + "css": "browsingContext.CssLocator", + "context": "browsingContext.ContextLocator", + "innerText": "browsingContext.InnerTextLocator", + "xpath": "browsingContext.XPathLocator", + } + _DISCRIMINATOR_VALUES = frozenset({"accessibility", "css", "context", "innerText", "xpath"}) + + +LocatorValue: TypeAlias = "AccessibilityLocator | CssLocator | ContextLocator | InnerTextLocator | XPathLocator" + + +@register("browsingContext.ClipRectangle") +class ClipRectangle(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "box": "browsingContext.BoxClipRectangle", + "element": "browsingContext.ElementClipRectangle", + } + _DISCRIMINATOR_VALUES = frozenset({"box", "element"}) + + +ClipRectangleValue: TypeAlias = "BoxClipRectangle | ElementClipRectangle" + + +@register("browsingContext.DownloadEndParams") +class DownloadEndParams(Union): + _DISCRIMINATOR = "status" + _VARIANTS = { + "canceled": "browsingContext.DownloadEndParams_CanceledParams", + "complete": "browsingContext.DownloadEndParams_CompleteParams", + } + _DISCRIMINATOR_VALUES = frozenset({"canceled", "complete"}) + + +DownloadEndParamsValue: TypeAlias = "DownloadEndParamsCanceledParams | DownloadEndParamsCompleteParams" + + +class BrowsingContext(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "context_created": "browsingContext.contextCreated", + "context_destroyed": "browsingContext.contextDestroyed", + "dom_content_loaded": "browsingContext.domContentLoaded", + "download_end": "browsingContext.downloadEnd", + "download_will_begin": "browsingContext.downloadWillBegin", + "fragment_navigated": "browsingContext.fragmentNavigated", + "history_updated": "browsingContext.historyUpdated", + "load": "browsingContext.load", + "navigation_aborted": "browsingContext.navigationAborted", + "navigation_committed": "browsingContext.navigationCommitted", + "navigation_failed": "browsingContext.navigationFailed", + "navigation_started": "browsingContext.navigationStarted", + "user_prompt_closed": "browsingContext.userPromptClosed", + "user_prompt_opened": "browsingContext.userPromptOpened", + } + EVENT_TYPES = { + "browsingContext.contextCreated": "browsingContext.Info", + "browsingContext.contextDestroyed": "browsingContext.Info", + "browsingContext.domContentLoaded": "browsingContext.NavigationInfo", + "browsingContext.downloadEnd": "browsingContext.DownloadEndParams", + "browsingContext.downloadWillBegin": "browsingContext.DownloadWillBeginParams", + "browsingContext.fragmentNavigated": "browsingContext.NavigationInfo", + "browsingContext.historyUpdated": "browsingContext.HistoryUpdatedParameters", + "browsingContext.load": "browsingContext.NavigationInfo", + "browsingContext.navigationAborted": "browsingContext.NavigationInfo", + "browsingContext.navigationCommitted": "browsingContext.NavigationInfo", + "browsingContext.navigationFailed": "browsingContext.NavigationInfo", + "browsingContext.navigationStarted": "browsingContext.NavigationInfo", + "browsingContext.userPromptClosed": "browsingContext.UserPromptClosedParameters", + "browsingContext.userPromptOpened": "browsingContext.UserPromptOpenedParameters", + } + + def activate(self, context: str) -> Any: + """Execute browsingContext.activate (internal, unsupported).""" + params = ActivateParameters(context=context) + return self._execute("browsingContext.activate", params=params, result=None) + + def capture_screenshot( + self, + context: str, + origin: CaptureScreenshotParametersOrigin | UnsetType = UNSET, + format: ImageFormat | UnsetType = UNSET, + clip: ClipRectangleValue | UnsetType = UNSET, + ) -> CaptureScreenshotResult: + """Execute browsingContext.captureScreenshot (internal, unsupported).""" + params = CaptureScreenshotParameters(context=context, origin=origin, format=format, clip=clip) + return self._execute("browsingContext.captureScreenshot", params=params, result=CaptureScreenshotResult) + + def close(self, context: str, prompt_unload: bool | UnsetType = UNSET) -> Any: + """Execute browsingContext.close (internal, unsupported).""" + params = CloseParameters(context=context, prompt_unload=prompt_unload) + return self._execute("browsingContext.close", params=params, result=None) + + def create( + self, + type: CreateType, + reference_context: str | UnsetType = UNSET, + background: bool | UnsetType = UNSET, + user_context: str | UnsetType = UNSET, + ) -> CreateResult: + """Execute browsingContext.create (internal, unsupported).""" + params = CreateParameters( + type=type, + reference_context=reference_context, + background=background, + user_context=user_context, + ) + return self._execute("browsingContext.create", params=params, result=CreateResult) + + def get_tree(self, max_depth: int | UnsetType = UNSET, root: str | UnsetType = UNSET) -> GetTreeResult: + """Execute browsingContext.getTree (internal, unsupported).""" + params = GetTreeParameters(max_depth=max_depth, root=root) + return self._execute("browsingContext.getTree", params=params, result=GetTreeResult) + + def handle_user_prompt( + self, + context: str, + accept: bool | UnsetType = UNSET, + user_text: str | UnsetType = UNSET, + ) -> Any: + """Execute browsingContext.handleUserPrompt (internal, unsupported).""" + params = HandleUserPromptParameters(context=context, accept=accept, user_text=user_text) + return self._execute("browsingContext.handleUserPrompt", params=params, result=None) + + def locate_nodes( + self, + context: str, + locator: LocatorValue, + max_node_count: int | UnsetType = UNSET, + serialization_options: SerializationOptions | UnsetType = UNSET, + start_nodes: list[SharedReference] | UnsetType = UNSET, + ) -> LocateNodesResult: + """Execute browsingContext.locateNodes (internal, unsupported).""" + params = LocateNodesParameters( + context=context, + locator=locator, + max_node_count=max_node_count, + serialization_options=serialization_options, + start_nodes=start_nodes, + ) + return self._execute("browsingContext.locateNodes", params=params, result=LocateNodesResult) + + def navigate(self, context: str, url: str, wait: ReadinessState | UnsetType = UNSET) -> NavigateResult: + """Execute browsingContext.navigate (internal, unsupported).""" + params = NavigateParameters(context=context, url=url, wait=wait) + return self._execute("browsingContext.navigate", params=params, result=NavigateResult) + + def print( + self, + context: str, + background: bool | UnsetType = UNSET, + margin: PrintMarginParameters | UnsetType = UNSET, + orientation: PrintParametersOrientation | UnsetType = UNSET, + page: PrintPageParameters | UnsetType = UNSET, + page_ranges: list[Any] | UnsetType = UNSET, + scale: float | UnsetType = UNSET, + shrink_to_fit: bool | UnsetType = UNSET, + ) -> PrintResult: + """Execute browsingContext.print (internal, unsupported).""" + params = PrintParameters( + context=context, + background=background, + margin=margin, + orientation=orientation, + page=page, + page_ranges=page_ranges, + scale=scale, + shrink_to_fit=shrink_to_fit, + ) + return self._execute("browsingContext.print", params=params, result=PrintResult) + + def reload( + self, + context: str, + ignore_cache: bool | UnsetType = UNSET, + wait: ReadinessState | UnsetType = UNSET, + ) -> NavigateResult: + """Execute browsingContext.reload (internal, unsupported).""" + params = ReloadParameters(context=context, ignore_cache=ignore_cache, wait=wait) + return self._execute("browsingContext.reload", params=params, result=NavigateResult) + + def set_bypass_csp( + self, + bypass: bool | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute browsingContext.setBypassCSP (internal, unsupported).""" + params = SetBypassCSPParameters(bypass=bypass, contexts=contexts, user_contexts=user_contexts) + return self._execute("browsingContext.setBypassCSP", params=params, result=None) + + def set_viewport( + self, + context: str | UnsetType = UNSET, + viewport: Viewport | None | UnsetType = UNSET, + device_pixel_ratio: float | None | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute browsingContext.setViewport (internal, unsupported).""" + params = SetViewportParameters( + context=context, + viewport=viewport, + device_pixel_ratio=device_pixel_ratio, + user_contexts=user_contexts, + ) + return self._execute("browsingContext.setViewport", params=params, result=None) + + def traverse_history(self, context: str, delta: int) -> Any: + """Execute browsingContext.traverseHistory (internal, unsupported).""" + params = TraverseHistoryParameters(context=context, delta=delta) + return self._execute("browsingContext.traverseHistory", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/emulation.py b/py/selenium/webdriver/_bidi/emulation.py new file mode 100644 index 0000000000000..fef9a53243af5 --- /dev/null +++ b/py/selenium/webdriver/_bidi/emulation.py @@ -0,0 +1,413 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("emulation.ForcedColorsModeTheme") +class ForcedColorsModeTheme(str, Enum): + LIGHT = "light" + DARK = "dark" + + +@register("emulation.ScreenOrientationNatural") +class ScreenOrientationNatural(str, Enum): + PORTRAIT = "portrait" + LANDSCAPE = "landscape" + + +@register("emulation.ScreenOrientationType") +class ScreenOrientationType(str, Enum): + PORTRAIT_PRIMARY = "portrait-primary" + PORTRAIT_SECONDARY = "portrait-secondary" + LANDSCAPE_PRIMARY = "landscape-primary" + LANDSCAPE_SECONDARY = "landscape-secondary" + + +@register("emulation.SetForcedColorsModeThemeOverrideParameters") +@dataclass(frozen=True) +class SetForcedColorsModeThemeOverrideParameters(Record): + theme: ForcedColorsModeTheme | None = field( + metadata=meta("theme", required=True, nullable=True, enum="emulation.ForcedColorsModeTheme"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.GeolocationCoordinates") +@dataclass(frozen=True) +class GeolocationCoordinates(Record): + latitude: int = field(metadata=meta("latitude", required=True, primitive="int")) + longitude: int = field(metadata=meta("longitude", required=True, primitive="int")) + accuracy: float | UnsetType = field(default=UNSET, metadata=meta("accuracy", primitive="float")) + altitude: float | None | UnsetType = field( + default=UNSET, + metadata=meta("altitude", nullable=True, primitive="float"), + ) + altitude_accuracy: float | None | UnsetType = field( + default=UNSET, + metadata=meta("altitudeAccuracy", nullable=True, primitive="float"), + ) + heading: int | None | UnsetType = field(default=UNSET, metadata=meta("heading", nullable=True, primitive="int")) + speed: float | None | UnsetType = field(default=UNSET, metadata=meta("speed", nullable=True, primitive="float")) + + +@register("emulation.GeolocationPositionError") +@dataclass(frozen=True) +class GeolocationPositionError(Record): + type: str = field( + default="positionUnavailable", + init=False, + metadata=meta("type", required=True, fixed="positionUnavailable"), + ) + + +@register("emulation.SetLocaleOverrideParameters") +@dataclass(frozen=True) +class SetLocaleOverrideParameters(Record): + locale: str | None = field(metadata=meta("locale", required=True, nullable=True, primitive="str")) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetNetworkConditionsParameters") +@dataclass(frozen=True) +class SetNetworkConditionsParameters(Record): + network_conditions: NetworkConditionsOffline | None = field( + metadata=meta("networkConditions", required=True, nullable=True, ref="emulation.NetworkConditionsOffline"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.NetworkConditionsOffline") +@dataclass(frozen=True) +class NetworkConditionsOffline(Record): + type: str = field(default="offline", init=False, metadata=meta("type", required=True, fixed="offline")) + + +@register("emulation.ScreenArea") +@dataclass(frozen=True) +class ScreenArea(Record): + width: int = field(metadata=meta("width", required=True, primitive="int")) + height: int = field(metadata=meta("height", required=True, primitive="int")) + + +@register("emulation.SetScreenSettingsOverrideParameters") +@dataclass(frozen=True) +class SetScreenSettingsOverrideParameters(Record): + screen_area: ScreenArea | None = field( + metadata=meta("screenArea", required=True, nullable=True, ref="emulation.ScreenArea"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.ScreenOrientation") +@dataclass(frozen=True) +class ScreenOrientation(Record): + natural: ScreenOrientationNatural = field( + metadata=meta("natural", required=True, enum="emulation.ScreenOrientationNatural"), + ) + type: ScreenOrientationType = field( + metadata=meta("type", required=True, enum="emulation.ScreenOrientationType"), + ) + + +@register("emulation.SetScreenOrientationOverrideParameters") +@dataclass(frozen=True) +class SetScreenOrientationOverrideParameters(Record): + screen_orientation: ScreenOrientation | None = field( + metadata=meta("screenOrientation", required=True, nullable=True, ref="emulation.ScreenOrientation"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetUserAgentOverrideParameters") +@dataclass(frozen=True) +class SetUserAgentOverrideParameters(Record): + user_agent: str | None = field(metadata=meta("userAgent", required=True, nullable=True, primitive="str")) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetScriptingEnabledParameters") +@dataclass(frozen=True) +class SetScriptingEnabledParameters(Record): + enabled: bool | None = field(metadata=meta("enabled", required=True, nullable=True)) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetScrollbarTypeOverrideParameters") +@dataclass(frozen=True) +class SetScrollbarTypeOverrideParameters(Record): + scrollbar_type: Any | None = field(metadata=meta("scrollbarType", required=True, nullable=True)) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetTimezoneOverrideParameters") +@dataclass(frozen=True) +class SetTimezoneOverrideParameters(Record): + timezone: str | None = field(metadata=meta("timezone", required=True, nullable=True, primitive="str")) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetTouchOverrideParameters") +@dataclass(frozen=True) +class SetTouchOverrideParameters(Record): + max_touch_points: int | None = field( + metadata=meta("maxTouchPoints", required=True, nullable=True, primitive="int"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetGeolocationOverrideParameters_Coordinates") +@dataclass(frozen=True) +class SetGeolocationOverrideParametersCoordinates(Record): + coordinates: GeolocationCoordinates | None = field( + metadata=meta("coordinates", required=True, nullable=True, ref="emulation.GeolocationCoordinates"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetGeolocationOverrideParameters_Error") +@dataclass(frozen=True) +class SetGeolocationOverrideParametersError(Record): + error: GeolocationPositionError = field( + metadata=meta("error", required=True, ref="emulation.GeolocationPositionError"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("emulation.SetGeolocationOverrideParameters") +class SetGeolocationOverrideParameters(Union): + _PRESENCE = ( + ("emulation.SetGeolocationOverrideParameters_Coordinates", ("coordinates",)), + ("emulation.SetGeolocationOverrideParameters_Error", ("error",)), + ) + + +SetGeolocationOverrideParametersValue: TypeAlias = ( + "SetGeolocationOverrideParametersCoordinates | SetGeolocationOverrideParametersError" +) + + +class Emulation(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def set_forced_colors_mode_theme_override( + self, + theme: ForcedColorsModeTheme | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setForcedColorsModeThemeOverride (internal, unsupported).""" + params = SetForcedColorsModeThemeOverrideParameters( + theme=theme, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setForcedColorsModeThemeOverride", params=params, result=None) + + def set_geolocation_override( + self, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + coordinates: GeolocationCoordinates | None | UnsetType = UNSET, + error: GeolocationPositionError | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setGeolocationOverride (internal, unsupported).""" + params = SetGeolocationOverrideParameters.build( + contexts=contexts, + user_contexts=user_contexts, + coordinates=coordinates, + error=error, + ) + return self._execute("emulation.setGeolocationOverride", params=params, result=None) + + def set_locale_override( + self, + locale: str | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setLocaleOverride (internal, unsupported).""" + params = SetLocaleOverrideParameters(locale=locale, contexts=contexts, user_contexts=user_contexts) + return self._execute("emulation.setLocaleOverride", params=params, result=None) + + def set_network_conditions( + self, + network_conditions: NetworkConditionsOffline | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setNetworkConditions (internal, unsupported).""" + params = SetNetworkConditionsParameters( + network_conditions=network_conditions, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setNetworkConditions", params=params, result=None) + + def set_screen_orientation_override( + self, + screen_orientation: ScreenOrientation | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setScreenOrientationOverride (internal, unsupported).""" + params = SetScreenOrientationOverrideParameters( + screen_orientation=screen_orientation, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setScreenOrientationOverride", params=params, result=None) + + def set_screen_settings_override( + self, + screen_area: ScreenArea | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setScreenSettingsOverride (internal, unsupported).""" + params = SetScreenSettingsOverrideParameters( + screen_area=screen_area, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setScreenSettingsOverride", params=params, result=None) + + def set_scripting_enabled( + self, + enabled: bool | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setScriptingEnabled (internal, unsupported).""" + params = SetScriptingEnabledParameters(enabled=enabled, contexts=contexts, user_contexts=user_contexts) + return self._execute("emulation.setScriptingEnabled", params=params, result=None) + + def set_scrollbar_type_override( + self, + scrollbar_type: Any | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setScrollbarTypeOverride (internal, unsupported).""" + params = SetScrollbarTypeOverrideParameters( + scrollbar_type=scrollbar_type, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setScrollbarTypeOverride", params=params, result=None) + + def set_timezone_override( + self, + timezone: str | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setTimezoneOverride (internal, unsupported).""" + params = SetTimezoneOverrideParameters( + timezone=timezone, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setTimezoneOverride", params=params, result=None) + + def set_touch_override( + self, + max_touch_points: int | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setTouchOverride (internal, unsupported).""" + params = SetTouchOverrideParameters( + max_touch_points=max_touch_points, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setTouchOverride", params=params, result=None) + + def set_user_agent_override( + self, + user_agent: str | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute emulation.setUserAgentOverride (internal, unsupported).""" + params = SetUserAgentOverrideParameters( + user_agent=user_agent, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("emulation.setUserAgentOverride", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/input.py b/py/selenium/webdriver/_bidi/input.py new file mode 100644 index 0000000000000..39d25dbcca18b --- /dev/null +++ b/py/selenium/webdriver/_bidi/input.py @@ -0,0 +1,321 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import TYPE_CHECKING, Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.script import SharedReference + + +@register("input.PointerType") +class PointerType(str, Enum): + MOUSE = "mouse" + PEN = "pen" + TOUCH = "touch" + + +@register("input.ElementOrigin") +@dataclass(frozen=True) +class ElementOrigin(Record): + element: SharedReference = field(metadata=meta("element", required=True, ref="script.SharedReference")) + type: str = field(default="element", init=False, metadata=meta("type", required=True, fixed="element")) + + +@register("input.PerformActionsParameters") +@dataclass(frozen=True) +class PerformActionsParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + actions: list[SourceActionsValue] = field( + metadata=meta("actions", required=True, ref="input.SourceActions", is_list=True), + ) + + +@register("input.NoneSourceActions") +@dataclass(frozen=True) +class NoneSourceActions(Record): + id: str = field(metadata=meta("id", required=True, primitive="str")) + actions: list[PauseAction] = field( + metadata=meta("actions", required=True, ref="input.PauseAction", is_list=True), + ) + type: str = field(default="none", init=False, metadata=meta("type", required=True, fixed="none")) + + +@register("input.KeySourceActions") +@dataclass(frozen=True) +class KeySourceActions(Record): + id: str = field(metadata=meta("id", required=True, primitive="str")) + actions: list[KeySourceActionValue] = field( + metadata=meta("actions", required=True, ref="input.KeySourceAction", is_list=True), + ) + type: str = field(default="key", init=False, metadata=meta("type", required=True, fixed="key")) + + +@register("input.PointerSourceActions") +@dataclass(frozen=True) +class PointerSourceActions(Record): + id: str = field(metadata=meta("id", required=True, primitive="str")) + actions: list[PointerSourceActionValue] = field( + metadata=meta("actions", required=True, ref="input.PointerSourceAction", is_list=True), + ) + type: str = field(default="pointer", init=False, metadata=meta("type", required=True, fixed="pointer")) + parameters: PointerParameters | UnsetType = field( + default=UNSET, + metadata=meta("parameters", ref="input.PointerParameters"), + ) + + +@register("input.PointerParameters") +@dataclass(frozen=True) +class PointerParameters(Record): + pointer_type: PointerType | UnsetType = field( + default=UNSET, + metadata=meta("pointerType", enum="input.PointerType"), + ) + + +@register("input.WheelSourceActions") +@dataclass(frozen=True) +class WheelSourceActions(Record): + id: str = field(metadata=meta("id", required=True, primitive="str")) + actions: list[WheelSourceActionValue] = field( + metadata=meta("actions", required=True, ref="input.WheelSourceAction", is_list=True), + ) + type: str = field(default="wheel", init=False, metadata=meta("type", required=True, fixed="wheel")) + + +@register("input.PauseAction") +@dataclass(frozen=True) +class PauseAction(Record): + type: str = field(default="pause", init=False, metadata=meta("type", required=True, fixed="pause")) + duration: int | UnsetType = field(default=UNSET, metadata=meta("duration", primitive="int")) + + +@register("input.KeyDownAction") +@dataclass(frozen=True) +class KeyDownAction(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="keyDown", init=False, metadata=meta("type", required=True, fixed="keyDown")) + + +@register("input.KeyUpAction") +@dataclass(frozen=True) +class KeyUpAction(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="keyUp", init=False, metadata=meta("type", required=True, fixed="keyUp")) + + +@register("input.PointerUpAction") +@dataclass(frozen=True) +class PointerUpAction(Record): + button: int = field(metadata=meta("button", required=True, primitive="int")) + type: str = field(default="pointerUp", init=False, metadata=meta("type", required=True, fixed="pointerUp")) + + +@register("input.PointerDownAction") +@dataclass(frozen=True) +class PointerDownAction(Record): + button: int = field(metadata=meta("button", required=True, primitive="int")) + type: str = field(default="pointerDown", init=False, metadata=meta("type", required=True, fixed="pointerDown")) + width: int | UnsetType = field(default=UNSET, metadata=meta("width", primitive="int")) + height: int | UnsetType = field(default=UNSET, metadata=meta("height", primitive="int")) + pressure: int | UnsetType = field(default=UNSET, metadata=meta("pressure", primitive="int")) + tangential_pressure: int | UnsetType = field( + default=UNSET, + metadata=meta("tangentialPressure", primitive="int"), + ) + twist: int | UnsetType = field(default=UNSET, metadata=meta("twist", primitive="int")) + altitude_angle: float | UnsetType = field(default=UNSET, metadata=meta("altitudeAngle", primitive="float")) + azimuth_angle: float | UnsetType = field(default=UNSET, metadata=meta("azimuthAngle", primitive="float")) + + +@register("input.PointerMoveAction") +@dataclass(frozen=True) +class PointerMoveAction(Record): + x: float = field(metadata=meta("x", required=True, primitive="float")) + y: float = field(metadata=meta("y", required=True, primitive="float")) + type: str = field(default="pointerMove", init=False, metadata=meta("type", required=True, fixed="pointerMove")) + duration: int | UnsetType = field(default=UNSET, metadata=meta("duration", primitive="int")) + origin: OriginValue | UnsetType = field(default=UNSET, metadata=meta("origin", ref="input.Origin")) + width: int | UnsetType = field(default=UNSET, metadata=meta("width", primitive="int")) + height: int | UnsetType = field(default=UNSET, metadata=meta("height", primitive="int")) + pressure: int | UnsetType = field(default=UNSET, metadata=meta("pressure", primitive="int")) + tangential_pressure: int | UnsetType = field( + default=UNSET, + metadata=meta("tangentialPressure", primitive="int"), + ) + twist: int | UnsetType = field(default=UNSET, metadata=meta("twist", primitive="int")) + altitude_angle: float | UnsetType = field(default=UNSET, metadata=meta("altitudeAngle", primitive="float")) + azimuth_angle: float | UnsetType = field(default=UNSET, metadata=meta("azimuthAngle", primitive="float")) + + +@register("input.WheelScrollAction") +@dataclass(frozen=True) +class WheelScrollAction(Record): + x: int = field(metadata=meta("x", required=True, primitive="int")) + y: int = field(metadata=meta("y", required=True, primitive="int")) + delta_x: int = field(metadata=meta("deltaX", required=True, primitive="int")) + delta_y: int = field(metadata=meta("deltaY", required=True, primitive="int")) + type: str = field(default="scroll", init=False, metadata=meta("type", required=True, fixed="scroll")) + duration: int | UnsetType = field(default=UNSET, metadata=meta("duration", primitive="int")) + origin: OriginValue | UnsetType = field(default=UNSET, metadata=meta("origin", ref="input.Origin")) + + +@register("input.PointerCommonProperties") +@dataclass(frozen=True) +class PointerCommonProperties(Record): + width: int | UnsetType = field(default=UNSET, metadata=meta("width", primitive="int")) + height: int | UnsetType = field(default=UNSET, metadata=meta("height", primitive="int")) + pressure: int | UnsetType = field(default=UNSET, metadata=meta("pressure", primitive="int")) + tangential_pressure: int | UnsetType = field( + default=UNSET, + metadata=meta("tangentialPressure", primitive="int"), + ) + twist: int | UnsetType = field(default=UNSET, metadata=meta("twist", primitive="int")) + altitude_angle: float | UnsetType = field(default=UNSET, metadata=meta("altitudeAngle", primitive="float")) + azimuth_angle: float | UnsetType = field(default=UNSET, metadata=meta("azimuthAngle", primitive="float")) + + +@register("input.ReleaseActionsParameters") +@dataclass(frozen=True) +class ReleaseActionsParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("input.SetFilesParameters") +@dataclass(frozen=True) +class SetFilesParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + element: SharedReference = field(metadata=meta("element", required=True, ref="script.SharedReference")) + files: list[str] = field(metadata=meta("files", required=True, is_list=True, primitive="str")) + + +@register("input.FileDialogInfo") +@dataclass(frozen=True) +class FileDialogInfo(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + multiple: bool = field(metadata=meta("multiple", required=True, primitive="bool")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + element: SharedReference | UnsetType = field( + default=UNSET, + metadata=meta("element", ref="script.SharedReference"), + ) + + +@register("input.SourceActions") +class SourceActions(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "none": "input.NoneSourceActions", + "key": "input.KeySourceActions", + "pointer": "input.PointerSourceActions", + "wheel": "input.WheelSourceActions", + } + _DISCRIMINATOR_VALUES = frozenset({"none", "key", "pointer", "wheel"}) + + +SourceActionsValue: TypeAlias = "NoneSourceActions | KeySourceActions | PointerSourceActions | WheelSourceActions" + + +@register("input.KeySourceAction") +class KeySourceAction(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "pause": "input.PauseAction", + "keyDown": "input.KeyDownAction", + "keyUp": "input.KeyUpAction", + } + _DISCRIMINATOR_VALUES = frozenset({"pause", "keyDown", "keyUp"}) + + +KeySourceActionValue: TypeAlias = "PauseAction | KeyDownAction | KeyUpAction" + + +@register("input.PointerSourceAction") +class PointerSourceAction(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "pause": "input.PauseAction", + "pointerDown": "input.PointerDownAction", + "pointerUp": "input.PointerUpAction", + "pointerMove": "input.PointerMoveAction", + } + _DISCRIMINATOR_VALUES = frozenset({"pause", "pointerDown", "pointerUp", "pointerMove"}) + + +PointerSourceActionValue: TypeAlias = "PauseAction | PointerDownAction | PointerUpAction | PointerMoveAction" + + +@register("input.WheelSourceAction") +class WheelSourceAction(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "pause": "input.PauseAction", + "scroll": "input.WheelScrollAction", + } + _DISCRIMINATOR_VALUES = frozenset({"pause", "scroll"}) + + +WheelSourceActionValue: TypeAlias = "PauseAction | WheelScrollAction" + + +@register("input.Origin") +class Origin(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "element": "input.ElementOrigin", + } + _DISCRIMINATOR_VALUES = frozenset({"element"}) + + +OriginValue: TypeAlias = "ElementOrigin" + + +class Input(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "file_dialog_opened": "input.fileDialogOpened", + } + EVENT_TYPES = { + "input.fileDialogOpened": "input.FileDialogInfo", + } + + def perform_actions(self, context: str, actions: list[SourceActionsValue]) -> Any: + """Execute input.performActions (internal, unsupported).""" + params = PerformActionsParameters(context=context, actions=actions) + return self._execute("input.performActions", params=params, result=None) + + def release_actions(self, context: str) -> Any: + """Execute input.releaseActions (internal, unsupported).""" + params = ReleaseActionsParameters(context=context) + return self._execute("input.releaseActions", params=params, result=None) + + def set_files(self, context: str, element: SharedReference, files: list[str]) -> Any: + """Execute input.setFiles (internal, unsupported).""" + params = SetFilesParameters(context=context, element=element, files=files) + return self._execute("input.setFiles", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/log.py b/py/selenium/webdriver/_bidi/log.py new file mode 100644 index 0000000000000..acf14e2acdc2f --- /dev/null +++ b/py/selenium/webdriver/_bidi/log.py @@ -0,0 +1,112 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import TYPE_CHECKING, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.script import RemoteValueValue, Source, StackTrace + + +@register("log.Level") +class Level(str, Enum): + DEBUG = "debug" + INFO = "info" + WARN = "warn" + ERROR = "error" + + +@register("log.BaseLogEntry") +@dataclass(frozen=True) +class BaseLogEntry(Record): + level: Level = field(metadata=meta("level", required=True, enum="log.Level")) + source: Source = field(metadata=meta("source", required=True, ref="script.Source")) + text: str | None = field(metadata=meta("text", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + stack_trace: StackTrace | UnsetType = field(default=UNSET, metadata=meta("stackTrace", ref="script.StackTrace")) + + +@register("log.GenericLogEntry") +@dataclass(frozen=True) +class GenericLogEntry(Record): + level: Level = field(metadata=meta("level", required=True, enum="log.Level")) + source: Source = field(metadata=meta("source", required=True, ref="script.Source")) + text: str | None = field(metadata=meta("text", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + type: str = field(metadata=meta("type", required=True, primitive="str")) + stack_trace: StackTrace | UnsetType = field(default=UNSET, metadata=meta("stackTrace", ref="script.StackTrace")) + + +@register("log.ConsoleLogEntry") +@dataclass(frozen=True) +class ConsoleLogEntry(Record): + level: Level = field(metadata=meta("level", required=True, enum="log.Level")) + source: Source = field(metadata=meta("source", required=True, ref="script.Source")) + text: str | None = field(metadata=meta("text", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + method: str = field(metadata=meta("method", required=True, primitive="str")) + args: list[RemoteValueValue] = field( + metadata=meta("args", required=True, ref="script.RemoteValue", is_list=True), + ) + type: str = field(default="console", init=False, metadata=meta("type", required=True, fixed="console")) + stack_trace: StackTrace | UnsetType = field(default=UNSET, metadata=meta("stackTrace", ref="script.StackTrace")) + + +@register("log.JavascriptLogEntry") +@dataclass(frozen=True) +class JavascriptLogEntry(Record): + level: Level = field(metadata=meta("level", required=True, enum="log.Level")) + source: Source = field(metadata=meta("source", required=True, ref="script.Source")) + text: str | None = field(metadata=meta("text", required=True, nullable=True, primitive="str")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + type: str = field(default="javascript", init=False, metadata=meta("type", required=True, fixed="javascript")) + stack_trace: StackTrace | UnsetType = field(default=UNSET, metadata=meta("stackTrace", ref="script.StackTrace")) + + +@register("log.Entry") +class Entry(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "console": "log.ConsoleLogEntry", + "javascript": "log.JavascriptLogEntry", + } + _FALLBACK = "log.GenericLogEntry" + _DISCRIMINATOR_VALUES = frozenset({"console", "javascript"}) + + +EntryValue: TypeAlias = "ConsoleLogEntry | JavascriptLogEntry | GenericLogEntry" + + +class Log(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "entry_added": "log.entryAdded", + } + EVENT_TYPES = { + "log.entryAdded": "log.Entry", + } diff --git a/py/selenium/webdriver/_bidi/network.py b/py/selenium/webdriver/_bidi/network.py new file mode 100644 index 0000000000000..6014d0f96e44e --- /dev/null +++ b/py/selenium/webdriver/_bidi/network.py @@ -0,0 +1,746 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import TYPE_CHECKING, Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.script import StackTrace + + +@register("network.CollectorType") +class CollectorType(str, Enum): + BLOB = "blob" + + +@register("network.SameSite") +class SameSite(str, Enum): + STRICT = "strict" + LAX = "lax" + NONE = "none" + DEFAULT = "default" + + +@register("network.DataType") +class DataType(str, Enum): + REQUEST = "request" + RESPONSE = "response" + + +@register("network.InterceptPhase") +class InterceptPhase(str, Enum): + BEFORE_REQUEST_SENT = "beforeRequestSent" + RESPONSE_STARTED = "responseStarted" + AUTH_REQUIRED = "authRequired" + + +@register("network.InitiatorType") +class InitiatorType(str, Enum): + PARSER = "parser" + SCRIPT = "script" + PREFLIGHT = "preflight" + OTHER = "other" + + +@register("network.ContinueWithAuthNoCredentialsAction") +class ContinueWithAuthNoCredentialsAction(str, Enum): + DEFAULT = "default" + CANCEL = "cancel" + + +@register("network.SetCacheBehaviorParametersCacheBehavior") +class SetCacheBehaviorParametersCacheBehavior(str, Enum): + DEFAULT = "default" + BYPASS = "bypass" + + +@register("network.AuthChallenge") +@dataclass(frozen=True) +class AuthChallenge(Record): + scheme: str = field(metadata=meta("scheme", required=True, primitive="str")) + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + + +@register("network.AuthCredentials") +@dataclass(frozen=True) +class AuthCredentials(Record): + username: str = field(metadata=meta("username", required=True, primitive="str")) + password: str = field(metadata=meta("password", required=True, primitive="str")) + type: str = field(default="password", init=False, metadata=meta("type", required=True, fixed="password")) + + +@register("network.BaseParameters") +@dataclass(frozen=True) +class BaseParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + + +@register("network.StringValue") +@dataclass(frozen=True) +class StringValue(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="string", init=False, metadata=meta("type", required=True, fixed="string")) + + +@register("network.Base64Value") +@dataclass(frozen=True) +class Base64Value(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="base64", init=False, metadata=meta("type", required=True, fixed="base64")) + + +@register("network.Cookie") +@dataclass(frozen=True) +class Cookie(Record): + _EXTENSIBLE = True + name: str = field(metadata=meta("name", required=True, primitive="str")) + value: BytesValueValue = field(metadata=meta("value", required=True, ref="network.BytesValue")) + domain: str = field(metadata=meta("domain", required=True, primitive="str")) + path: str = field(metadata=meta("path", required=True, primitive="str")) + size: int = field(metadata=meta("size", required=True, primitive="int")) + http_only: bool = field(metadata=meta("httpOnly", required=True, primitive="bool")) + secure: bool = field(metadata=meta("secure", required=True, primitive="bool")) + same_site: SameSite = field(metadata=meta("sameSite", required=True, enum="network.SameSite")) + expiry: int | UnsetType = field(default=UNSET, metadata=meta("expiry", primitive="int")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("network.CookieHeader") +@dataclass(frozen=True) +class CookieHeader(Record): + name: str = field(metadata=meta("name", required=True, primitive="str")) + value: BytesValueValue = field(metadata=meta("value", required=True, ref="network.BytesValue")) + + +@register("network.FetchTimingInfo") +@dataclass(frozen=True) +class FetchTimingInfo(Record): + time_origin: float = field(metadata=meta("timeOrigin", required=True, primitive="float")) + request_time: float = field(metadata=meta("requestTime", required=True, primitive="float")) + redirect_start: float = field(metadata=meta("redirectStart", required=True, primitive="float")) + redirect_end: float = field(metadata=meta("redirectEnd", required=True, primitive="float")) + fetch_start: float = field(metadata=meta("fetchStart", required=True, primitive="float")) + dns_start: float = field(metadata=meta("dnsStart", required=True, primitive="float")) + dns_end: float = field(metadata=meta("dnsEnd", required=True, primitive="float")) + connect_start: float = field(metadata=meta("connectStart", required=True, primitive="float")) + connect_end: float = field(metadata=meta("connectEnd", required=True, primitive="float")) + tls_start: float = field(metadata=meta("tlsStart", required=True, primitive="float")) + request_start: float = field(metadata=meta("requestStart", required=True, primitive="float")) + response_start: float = field(metadata=meta("responseStart", required=True, primitive="float")) + response_end: float = field(metadata=meta("responseEnd", required=True, primitive="float")) + + +@register("network.Header") +@dataclass(frozen=True) +class Header(Record): + name: str = field(metadata=meta("name", required=True, primitive="str")) + value: BytesValueValue = field(metadata=meta("value", required=True, ref="network.BytesValue")) + + +@register("network.Initiator") +@dataclass(frozen=True) +class Initiator(Record): + column_number: int | UnsetType = field(default=UNSET, metadata=meta("columnNumber", primitive="int")) + line_number: int | UnsetType = field(default=UNSET, metadata=meta("lineNumber", primitive="int")) + request: str | UnsetType = field(default=UNSET, metadata=meta("request", primitive="str")) + stack_trace: StackTrace | UnsetType = field(default=UNSET, metadata=meta("stackTrace", ref="script.StackTrace")) + type: InitiatorType | UnsetType = field(default=UNSET, metadata=meta("type", enum="network.InitiatorType")) + + +@register("network.RequestData") +@dataclass(frozen=True) +class RequestData(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + method: str = field(metadata=meta("method", required=True, primitive="str")) + headers: list[Header] = field(metadata=meta("headers", required=True, ref="network.Header", is_list=True)) + cookies: list[Cookie] = field(metadata=meta("cookies", required=True, ref="network.Cookie", is_list=True)) + headers_size: int = field(metadata=meta("headersSize", required=True, primitive="int")) + body_size: int | None = field(metadata=meta("bodySize", required=True, nullable=True, primitive="int")) + destination: str = field(metadata=meta("destination", required=True, primitive="str")) + initiator_type: str | None = field( + metadata=meta("initiatorType", required=True, nullable=True, primitive="str"), + ) + timings: FetchTimingInfo = field(metadata=meta("timings", required=True, ref="network.FetchTimingInfo")) + + +@register("network.ResponseContent") +@dataclass(frozen=True) +class ResponseContent(Record): + size: int = field(metadata=meta("size", required=True, primitive="int")) + + +@register("network.ResponseData") +@dataclass(frozen=True) +class ResponseData(Record): + url: str = field(metadata=meta("url", required=True, primitive="str")) + protocol: str = field(metadata=meta("protocol", required=True, primitive="str")) + status: int = field(metadata=meta("status", required=True, primitive="int")) + status_text: str = field(metadata=meta("statusText", required=True, primitive="str")) + from_cache: bool = field(metadata=meta("fromCache", required=True, primitive="bool")) + headers: list[Header] = field(metadata=meta("headers", required=True, ref="network.Header", is_list=True)) + mime_type: str = field(metadata=meta("mimeType", required=True, primitive="str")) + bytes_received: int = field(metadata=meta("bytesReceived", required=True, primitive="int")) + headers_size: int | None = field(metadata=meta("headersSize", required=True, nullable=True, primitive="int")) + body_size: int | None = field(metadata=meta("bodySize", required=True, nullable=True, primitive="int")) + content: ResponseContent = field(metadata=meta("content", required=True, ref="network.ResponseContent")) + auth_challenges: list[AuthChallenge] | UnsetType = field( + default=UNSET, + metadata=meta("authChallenges", ref="network.AuthChallenge", is_list=True), + ) + + +@register("network.SetCookieHeader") +@dataclass(frozen=True) +class SetCookieHeader(Record): + name: str = field(metadata=meta("name", required=True, primitive="str")) + value: BytesValueValue = field(metadata=meta("value", required=True, ref="network.BytesValue")) + domain: str | UnsetType = field(default=UNSET, metadata=meta("domain", primitive="str")) + http_only: bool | UnsetType = field(default=UNSET, metadata=meta("httpOnly", primitive="bool")) + expiry: str | UnsetType = field(default=UNSET, metadata=meta("expiry", primitive="str")) + max_age: int | UnsetType = field(default=UNSET, metadata=meta("maxAge", primitive="int")) + path: str | UnsetType = field(default=UNSET, metadata=meta("path", primitive="str")) + same_site: SameSite | UnsetType = field(default=UNSET, metadata=meta("sameSite", enum="network.SameSite")) + secure: bool | UnsetType = field(default=UNSET, metadata=meta("secure", primitive="bool")) + + +@register("network.UrlPatternPattern") +@dataclass(frozen=True) +class UrlPatternPattern(Record): + type: str = field(default="pattern", init=False, metadata=meta("type", required=True, fixed="pattern")) + protocol: str | UnsetType = field(default=UNSET, metadata=meta("protocol", primitive="str")) + hostname: str | UnsetType = field(default=UNSET, metadata=meta("hostname", primitive="str")) + port: str | UnsetType = field(default=UNSET, metadata=meta("port", primitive="str")) + pathname: str | UnsetType = field(default=UNSET, metadata=meta("pathname", primitive="str")) + search: str | UnsetType = field(default=UNSET, metadata=meta("search", primitive="str")) + + +@register("network.UrlPatternString") +@dataclass(frozen=True) +class UrlPatternString(Record): + pattern: str = field(metadata=meta("pattern", required=True, primitive="str")) + type: str = field(default="string", init=False, metadata=meta("type", required=True, fixed="string")) + + +@register("network.AddDataCollectorParameters") +@dataclass(frozen=True) +class AddDataCollectorParameters(Record): + data_types: list[DataType] = field( + metadata=meta("dataTypes", required=True, is_list=True, enum="network.DataType"), + ) + max_encoded_data_size: int = field(metadata=meta("maxEncodedDataSize", required=True, primitive="int")) + collector_type: CollectorType | UnsetType = field( + default=UNSET, + metadata=meta("collectorType", enum="network.CollectorType"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("network.AddDataCollectorResult") +@dataclass(frozen=True) +class AddDataCollectorResult(Record): + collector: str = field(metadata=meta("collector", required=True, primitive="str")) + + +@register("network.AddInterceptParameters") +@dataclass(frozen=True) +class AddInterceptParameters(Record): + phases: list[InterceptPhase] = field( + metadata=meta("phases", required=True, is_list=True, enum="network.InterceptPhase"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + url_patterns: list[UrlPatternValue] | UnsetType = field( + default=UNSET, + metadata=meta("urlPatterns", ref="network.UrlPattern", is_list=True), + ) + + +@register("network.AddInterceptResult") +@dataclass(frozen=True) +class AddInterceptResult(Record): + intercept: str = field(metadata=meta("intercept", required=True, primitive="str")) + + +@register("network.ContinueRequestParameters") +@dataclass(frozen=True) +class ContinueRequestParameters(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + body: BytesValueValue | UnsetType = field(default=UNSET, metadata=meta("body", ref="network.BytesValue")) + cookies: list[CookieHeader] | UnsetType = field( + default=UNSET, + metadata=meta("cookies", ref="network.CookieHeader", is_list=True), + ) + headers: list[Header] | UnsetType = field( + default=UNSET, + metadata=meta("headers", ref="network.Header", is_list=True), + ) + method: str | UnsetType = field(default=UNSET, metadata=meta("method", primitive="str")) + url: str | UnsetType = field(default=UNSET, metadata=meta("url", primitive="str")) + + +@register("network.ContinueResponseParameters") +@dataclass(frozen=True) +class ContinueResponseParameters(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + cookies: list[SetCookieHeader] | UnsetType = field( + default=UNSET, + metadata=meta("cookies", ref="network.SetCookieHeader", is_list=True), + ) + credentials: AuthCredentials | UnsetType = field( + default=UNSET, + metadata=meta("credentials", ref="network.AuthCredentials"), + ) + headers: list[Header] | UnsetType = field( + default=UNSET, + metadata=meta("headers", ref="network.Header", is_list=True), + ) + reason_phrase: str | UnsetType = field(default=UNSET, metadata=meta("reasonPhrase", primitive="str")) + status_code: int | UnsetType = field(default=UNSET, metadata=meta("statusCode", primitive="int")) + + +@register("network.DisownDataParameters") +@dataclass(frozen=True) +class DisownDataParameters(Record): + data_type: DataType = field(metadata=meta("dataType", required=True, enum="network.DataType")) + collector: str = field(metadata=meta("collector", required=True, primitive="str")) + request: str = field(metadata=meta("request", required=True, primitive="str")) + + +@register("network.FailRequestParameters") +@dataclass(frozen=True) +class FailRequestParameters(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + + +@register("network.GetDataParameters") +@dataclass(frozen=True) +class GetDataParameters(Record): + data_type: DataType = field(metadata=meta("dataType", required=True, enum="network.DataType")) + request: str = field(metadata=meta("request", required=True, primitive="str")) + collector: str | UnsetType = field(default=UNSET, metadata=meta("collector", primitive="str")) + disown: bool | UnsetType = field(default=UNSET, metadata=meta("disown", primitive="bool")) + + +@register("network.GetDataResult") +@dataclass(frozen=True) +class GetDataResult(Record): + bytes: BytesValueValue = field(metadata=meta("bytes", required=True, ref="network.BytesValue")) + + +@register("network.ProvideResponseParameters") +@dataclass(frozen=True) +class ProvideResponseParameters(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + body: BytesValueValue | UnsetType = field(default=UNSET, metadata=meta("body", ref="network.BytesValue")) + cookies: list[SetCookieHeader] | UnsetType = field( + default=UNSET, + metadata=meta("cookies", ref="network.SetCookieHeader", is_list=True), + ) + headers: list[Header] | UnsetType = field( + default=UNSET, + metadata=meta("headers", ref="network.Header", is_list=True), + ) + reason_phrase: str | UnsetType = field(default=UNSET, metadata=meta("reasonPhrase", primitive="str")) + status_code: int | UnsetType = field(default=UNSET, metadata=meta("statusCode", primitive="int")) + + +@register("network.RemoveDataCollectorParameters") +@dataclass(frozen=True) +class RemoveDataCollectorParameters(Record): + collector: str = field(metadata=meta("collector", required=True, primitive="str")) + + +@register("network.RemoveInterceptParameters") +@dataclass(frozen=True) +class RemoveInterceptParameters(Record): + intercept: str = field(metadata=meta("intercept", required=True, primitive="str")) + + +@register("network.SetCacheBehaviorParameters") +@dataclass(frozen=True) +class SetCacheBehaviorParameters(Record): + cache_behavior: SetCacheBehaviorParametersCacheBehavior = field( + metadata=meta("cacheBehavior", required=True, enum="network.SetCacheBehaviorParametersCacheBehavior"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + + +@register("network.SetExtraHeadersParameters") +@dataclass(frozen=True) +class SetExtraHeadersParameters(Record): + headers: list[Header] = field(metadata=meta("headers", required=True, ref="network.Header", is_list=True)) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("network.AuthRequiredParameters") +@dataclass(frozen=True) +class AuthRequiredParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + response: ResponseData = field(metadata=meta("response", required=True, ref="network.ResponseData")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + + +@register("network.BeforeRequestSentParameters") +@dataclass(frozen=True) +class BeforeRequestSentParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + initiator: Initiator | UnsetType = field(default=UNSET, metadata=meta("initiator", ref="network.Initiator")) + + +@register("network.FetchErrorParameters") +@dataclass(frozen=True) +class FetchErrorParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + error_text: str = field(metadata=meta("errorText", required=True, primitive="str")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + + +@register("network.ResponseCompletedParameters") +@dataclass(frozen=True) +class ResponseCompletedParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + response: ResponseData = field(metadata=meta("response", required=True, ref="network.ResponseData")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + + +@register("network.ResponseStartedParameters") +@dataclass(frozen=True) +class ResponseStartedParameters(Record): + context: str | None = field(metadata=meta("context", required=True, nullable=True, primitive="str")) + is_blocked: bool = field(metadata=meta("isBlocked", required=True, primitive="bool")) + navigation: str | None = field(metadata=meta("navigation", required=True, nullable=True, primitive="str")) + redirect_count: int = field(metadata=meta("redirectCount", required=True, primitive="int")) + request: RequestData = field(metadata=meta("request", required=True, ref="network.RequestData")) + timestamp: int = field(metadata=meta("timestamp", required=True, primitive="int")) + response: ResponseData = field(metadata=meta("response", required=True, ref="network.ResponseData")) + user_context: str | None | UnsetType = field( + default=UNSET, + metadata=meta("userContext", nullable=True, primitive="str"), + ) + intercepts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("intercepts", is_list=True, primitive="str"), + ) + + +@register("network.ContinueWithAuthParameters_Credentials") +@dataclass(frozen=True) +class ContinueWithAuthParametersCredentials(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + credentials: AuthCredentials = field(metadata=meta("credentials", required=True, ref="network.AuthCredentials")) + action: str = field( + default="provideCredentials", + init=False, + metadata=meta("action", required=True, fixed="provideCredentials"), + ) + + +@register("network.ContinueWithAuthParameters_NoCredentials") +@dataclass(frozen=True) +class ContinueWithAuthParametersNoCredentials(Record): + request: str = field(metadata=meta("request", required=True, primitive="str")) + action: ContinueWithAuthNoCredentialsAction = field( + metadata=meta("action", required=True, enum="network.ContinueWithAuthNoCredentialsAction"), + ) + + +@register("network.BytesValue") +class BytesValue(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "string": "network.StringValue", + "base64": "network.Base64Value", + } + _DISCRIMINATOR_VALUES = frozenset({"string", "base64"}) + + +BytesValueValue: TypeAlias = "StringValue | Base64Value" + + +@register("network.UrlPattern") +class UrlPattern(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "pattern": "network.UrlPatternPattern", + "string": "network.UrlPatternString", + } + _DISCRIMINATOR_VALUES = frozenset({"pattern", "string"}) + + +UrlPatternValue: TypeAlias = "UrlPatternPattern | UrlPatternString" + + +@register("network.ContinueWithAuthParameters") +class ContinueWithAuthParameters(Union): + _DISCRIMINATOR = "action" + _VARIANTS = { + "provideCredentials": "network.ContinueWithAuthParameters_Credentials", + } + _FALLBACK = "network.ContinueWithAuthParameters_NoCredentials" + _DISCRIMINATOR_VALUES = frozenset({"provideCredentials", "default", "cancel"}) + + +ContinueWithAuthParametersValue: TypeAlias = ( + "ContinueWithAuthParametersCredentials | ContinueWithAuthParametersNoCredentials" +) + + +class Network(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "auth_required": "network.authRequired", + "before_request_sent": "network.beforeRequestSent", + "fetch_error": "network.fetchError", + "response_completed": "network.responseCompleted", + "response_started": "network.responseStarted", + } + EVENT_TYPES = { + "network.authRequired": "network.AuthRequiredParameters", + "network.beforeRequestSent": "network.BeforeRequestSentParameters", + "network.fetchError": "network.FetchErrorParameters", + "network.responseCompleted": "network.ResponseCompletedParameters", + "network.responseStarted": "network.ResponseStartedParameters", + } + + def add_data_collector( + self, + data_types: list[DataType], + max_encoded_data_size: int, + collector_type: CollectorType | UnsetType = UNSET, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> AddDataCollectorResult: + """Execute network.addDataCollector (internal, unsupported).""" + params = AddDataCollectorParameters( + data_types=data_types, + max_encoded_data_size=max_encoded_data_size, + collector_type=collector_type, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("network.addDataCollector", params=params, result=AddDataCollectorResult) + + def add_intercept( + self, + phases: list[InterceptPhase], + contexts: list[str] | UnsetType = UNSET, + url_patterns: list[UrlPatternValue] | UnsetType = UNSET, + ) -> AddInterceptResult: + """Execute network.addIntercept (internal, unsupported).""" + params = AddInterceptParameters(phases=phases, contexts=contexts, url_patterns=url_patterns) + return self._execute("network.addIntercept", params=params, result=AddInterceptResult) + + def continue_request( + self, + request: str, + body: BytesValueValue | UnsetType = UNSET, + cookies: list[CookieHeader] | UnsetType = UNSET, + headers: list[Header] | UnsetType = UNSET, + method: str | UnsetType = UNSET, + url: str | UnsetType = UNSET, + ) -> Any: + """Execute network.continueRequest (internal, unsupported).""" + params = ContinueRequestParameters( + request=request, + body=body, + cookies=cookies, + headers=headers, + method=method, + url=url, + ) + return self._execute("network.continueRequest", params=params, result=None) + + def continue_response( + self, + request: str, + cookies: list[SetCookieHeader] | UnsetType = UNSET, + credentials: AuthCredentials | UnsetType = UNSET, + headers: list[Header] | UnsetType = UNSET, + reason_phrase: str | UnsetType = UNSET, + status_code: int | UnsetType = UNSET, + ) -> Any: + """Execute network.continueResponse (internal, unsupported).""" + params = ContinueResponseParameters( + request=request, + cookies=cookies, + credentials=credentials, + headers=headers, + reason_phrase=reason_phrase, + status_code=status_code, + ) + return self._execute("network.continueResponse", params=params, result=None) + + def continue_with_auth( + self, + request: str, + action: str, + credentials: AuthCredentials | UnsetType = UNSET, + ) -> Any: + """Execute network.continueWithAuth (internal, unsupported).""" + params = ContinueWithAuthParameters.build(request=request, action=action, credentials=credentials) + return self._execute("network.continueWithAuth", params=params, result=None) + + def disown_data(self, data_type: DataType, collector: str, request: str) -> Any: + """Execute network.disownData (internal, unsupported).""" + params = DisownDataParameters(data_type=data_type, collector=collector, request=request) + return self._execute("network.disownData", params=params, result=None) + + def fail_request(self, request: str) -> Any: + """Execute network.failRequest (internal, unsupported).""" + params = FailRequestParameters(request=request) + return self._execute("network.failRequest", params=params, result=None) + + def get_data( + self, + data_type: DataType, + request: str, + collector: str | UnsetType = UNSET, + disown: bool | UnsetType = UNSET, + ) -> GetDataResult: + """Execute network.getData (internal, unsupported).""" + params = GetDataParameters(data_type=data_type, collector=collector, disown=disown, request=request) + return self._execute("network.getData", params=params, result=GetDataResult) + + def provide_response( + self, + request: str, + body: BytesValueValue | UnsetType = UNSET, + cookies: list[SetCookieHeader] | UnsetType = UNSET, + headers: list[Header] | UnsetType = UNSET, + reason_phrase: str | UnsetType = UNSET, + status_code: int | UnsetType = UNSET, + ) -> Any: + """Execute network.provideResponse (internal, unsupported).""" + params = ProvideResponseParameters( + request=request, + body=body, + cookies=cookies, + headers=headers, + reason_phrase=reason_phrase, + status_code=status_code, + ) + return self._execute("network.provideResponse", params=params, result=None) + + def remove_data_collector(self, collector: str) -> Any: + """Execute network.removeDataCollector (internal, unsupported).""" + params = RemoveDataCollectorParameters(collector=collector) + return self._execute("network.removeDataCollector", params=params, result=None) + + def remove_intercept(self, intercept: str) -> Any: + """Execute network.removeIntercept (internal, unsupported).""" + params = RemoveInterceptParameters(intercept=intercept) + return self._execute("network.removeIntercept", params=params, result=None) + + def set_cache_behavior( + self, + cache_behavior: SetCacheBehaviorParametersCacheBehavior, + contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute network.setCacheBehavior (internal, unsupported).""" + params = SetCacheBehaviorParameters(cache_behavior=cache_behavior, contexts=contexts) + return self._execute("network.setCacheBehavior", params=params, result=None) + + def set_extra_headers( + self, + headers: list[Header], + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute network.setExtraHeaders (internal, unsupported).""" + params = SetExtraHeadersParameters(headers=headers, contexts=contexts, user_contexts=user_contexts) + return self._execute("network.setExtraHeaders", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/permissions.py b/py/selenium/webdriver/_bidi/permissions.py new file mode 100644 index 0000000000000..ad264c899ac36 --- /dev/null +++ b/py/selenium/webdriver/_bidi/permissions.py @@ -0,0 +1,77 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any + +from selenium.webdriver._bidi.serialization import UNSET, Record, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("permissions.PermissionState") +class PermissionState(str, Enum): + GRANTED = "granted" + DENIED = "denied" + PROMPT = "prompt" + + +@register("permissions.PermissionDescriptor") +@dataclass(frozen=True) +class PermissionDescriptor(Record): + name: str = field(metadata=meta("name", required=True, primitive="str")) + + +@register("permissions.SetPermissionParameters") +@dataclass(frozen=True) +class SetPermissionParameters(Record): + descriptor: PermissionDescriptor = field( + metadata=meta("descriptor", required=True, ref="permissions.PermissionDescriptor"), + ) + state: PermissionState = field(metadata=meta("state", required=True, enum="permissions.PermissionState")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + embedded_origin: str | UnsetType = field(default=UNSET, metadata=meta("embeddedOrigin", primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +class Permissions(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def set_permission( + self, + descriptor: PermissionDescriptor, + state: PermissionState, + origin: str, + embedded_origin: str | UnsetType = UNSET, + user_context: str | UnsetType = UNSET, + ) -> Any: + """Execute permissions.setPermission (internal, unsupported).""" + params = SetPermissionParameters( + descriptor=descriptor, + state=state, + origin=origin, + embedded_origin=embedded_origin, + user_context=user_context, + ) + return self._execute("permissions.setPermission", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/script.py b/py/selenium/webdriver/_bidi/script.py new file mode 100644 index 0000000000000..ac531c0b46373 --- /dev/null +++ b/py/selenium/webdriver/_bidi/script.py @@ -0,0 +1,993 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("script.SpecialNumber") +class SpecialNumber(str, Enum): + NA_N = "NaN" + NEG0 = "-0" + INFINITY = "Infinity" + NEG_INFINITY = "-Infinity" + + +@register("script.RealmType") +class RealmType(str, Enum): + WINDOW = "window" + DEDICATED_WORKER = "dedicated-worker" + SHARED_WORKER = "shared-worker" + SERVICE_WORKER = "service-worker" + WORKER = "worker" + PAINT_WORKLET = "paint-worklet" + AUDIO_WORKLET = "audio-worklet" + WORKLET = "worklet" + + +@register("script.ResultOwnership") +class ResultOwnership(str, Enum): + ROOT = "root" + NONE = "none" + + +@register("script.NodePropertiesMode") +class NodePropertiesMode(str, Enum): + OPEN = "open" + CLOSED = "closed" + + +@register("script.SerializationOptionsIncludeShadowTree") +class SerializationOptionsIncludeShadowTree(str, Enum): + NONE = "none" + OPEN = "open" + ALL = "all" + + +@register("script.ChannelValue") +@dataclass(frozen=True) +class ChannelValue(Record): + value: ChannelProperties = field(metadata=meta("value", required=True, ref="script.ChannelProperties")) + type: str = field(default="channel", init=False, metadata=meta("type", required=True, fixed="channel")) + + +@register("script.ChannelProperties") +@dataclass(frozen=True) +class ChannelProperties(Record): + channel: str = field(metadata=meta("channel", required=True, primitive="str")) + serialization_options: SerializationOptions | UnsetType = field( + default=UNSET, + metadata=meta("serializationOptions", ref="script.SerializationOptions"), + ) + ownership: ResultOwnership | UnsetType = field( + default=UNSET, + metadata=meta("ownership", enum="script.ResultOwnership"), + ) + + +@register("script.EvaluateResultSuccess") +@dataclass(frozen=True) +class EvaluateResultSuccess(Record): + result: RemoteValueValue = field(metadata=meta("result", required=True, ref="script.RemoteValue")) + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + type: str = field(default="success", init=False, metadata=meta("type", required=True, fixed="success")) + + +@register("script.EvaluateResultException") +@dataclass(frozen=True) +class EvaluateResultException(Record): + exception_details: ExceptionDetails = field( + metadata=meta("exceptionDetails", required=True, ref="script.ExceptionDetails"), + ) + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + type: str = field(default="exception", init=False, metadata=meta("type", required=True, fixed="exception")) + + +@register("script.ExceptionDetails") +@dataclass(frozen=True) +class ExceptionDetails(Record): + column_number: int = field(metadata=meta("columnNumber", required=True, primitive="int")) + exception: RemoteValueValue = field(metadata=meta("exception", required=True, ref="script.RemoteValue")) + line_number: int = field(metadata=meta("lineNumber", required=True, primitive="int")) + stack_trace: StackTrace = field(metadata=meta("stackTrace", required=True, ref="script.StackTrace")) + text: str = field(metadata=meta("text", required=True, primitive="str")) + + +@register("script.ArrayLocalValue") +@dataclass(frozen=True) +class ArrayLocalValue(Record): + value: list[LocalValueValue] = field( + metadata=meta("value", required=True, ref="script.LocalValue", is_list=True), + ) + type: str = field(default="array", init=False, metadata=meta("type", required=True, fixed="array")) + + +@register("script.DateLocalValue") +@dataclass(frozen=True) +class DateLocalValue(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="date", init=False, metadata=meta("type", required=True, fixed="date")) + + +@register("script.MapLocalValue") +@dataclass(frozen=True) +class MapLocalValue(Record): + value: list[list[Any]] = field(metadata=meta("value", required=True, ref="script.LocalValue", is_list=True)) + type: str = field(default="map", init=False, metadata=meta("type", required=True, fixed="map")) + + +@register("script.ObjectLocalValue") +@dataclass(frozen=True) +class ObjectLocalValue(Record): + value: list[list[Any]] = field(metadata=meta("value", required=True, ref="script.LocalValue", is_list=True)) + type: str = field(default="object", init=False, metadata=meta("type", required=True, fixed="object")) + + +@register("script.RegExpValue") +@dataclass(frozen=True) +class RegExpValue(Record): + pattern: str = field(metadata=meta("pattern", required=True, primitive="str")) + flags: str | UnsetType = field(default=UNSET, metadata=meta("flags", primitive="str")) + + +@register("script.RegExpLocalValue") +@dataclass(frozen=True) +class RegExpLocalValue(Record): + value: RegExpValue = field(metadata=meta("value", required=True, ref="script.RegExpValue")) + type: str = field(default="regexp", init=False, metadata=meta("type", required=True, fixed="regexp")) + + +@register("script.SetLocalValue") +@dataclass(frozen=True) +class SetLocalValue(Record): + value: list[LocalValueValue] = field( + metadata=meta("value", required=True, ref="script.LocalValue", is_list=True), + ) + type: str = field(default="set", init=False, metadata=meta("type", required=True, fixed="set")) + + +@register("script.UndefinedValue") +@dataclass(frozen=True) +class UndefinedValue(Record): + type: str = field(default="undefined", init=False, metadata=meta("type", required=True, fixed="undefined")) + + +@register("script.NullValue") +@dataclass(frozen=True) +class NullValue(Record): + type: str = field(default="null", init=False, metadata=meta("type", required=True, fixed="null")) + + +@register("script.StringValue") +@dataclass(frozen=True) +class StringValue(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="string", init=False, metadata=meta("type", required=True, fixed="string")) + + +@register("script.NumberValue") +@dataclass(frozen=True) +class NumberValue(Record): + value: Any = field(metadata=meta("value", required=True)) + type: str = field(default="number", init=False, metadata=meta("type", required=True, fixed="number")) + + +@register("script.BooleanValue") +@dataclass(frozen=True) +class BooleanValue(Record): + value: bool = field(metadata=meta("value", required=True, primitive="bool")) + type: str = field(default="boolean", init=False, metadata=meta("type", required=True, fixed="boolean")) + + +@register("script.BigIntValue") +@dataclass(frozen=True) +class BigIntValue(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="bigint", init=False, metadata=meta("type", required=True, fixed="bigint")) + + +@register("script.BaseRealmInfo") +@dataclass(frozen=True) +class BaseRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + + +@register("script.WindowRealmInfo") +@dataclass(frozen=True) +class WindowRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + context: str = field(metadata=meta("context", required=True, primitive="str")) + type: str = field(default="window", init=False, metadata=meta("type", required=True, fixed="window")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + sandbox: str | UnsetType = field(default=UNSET, metadata=meta("sandbox", primitive="str")) + + +@register("script.DedicatedWorkerRealmInfo") +@dataclass(frozen=True) +class DedicatedWorkerRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + owners: list[str] = field(metadata=meta("owners", required=True, is_list=True, primitive="str")) + type: str = field( + default="dedicated-worker", + init=False, + metadata=meta("type", required=True, fixed="dedicated-worker"), + ) + + +@register("script.SharedWorkerRealmInfo") +@dataclass(frozen=True) +class SharedWorkerRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field( + default="shared-worker", + init=False, + metadata=meta("type", required=True, fixed="shared-worker"), + ) + + +@register("script.ServiceWorkerRealmInfo") +@dataclass(frozen=True) +class ServiceWorkerRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field( + default="service-worker", + init=False, + metadata=meta("type", required=True, fixed="service-worker"), + ) + + +@register("script.WorkerRealmInfo") +@dataclass(frozen=True) +class WorkerRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field(default="worker", init=False, metadata=meta("type", required=True, fixed="worker")) + + +@register("script.PaintWorkletRealmInfo") +@dataclass(frozen=True) +class PaintWorkletRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field( + default="paint-worklet", + init=False, + metadata=meta("type", required=True, fixed="paint-worklet"), + ) + + +@register("script.AudioWorkletRealmInfo") +@dataclass(frozen=True) +class AudioWorkletRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field( + default="audio-worklet", + init=False, + metadata=meta("type", required=True, fixed="audio-worklet"), + ) + + +@register("script.WorkletRealmInfo") +@dataclass(frozen=True) +class WorkletRealmInfo(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + origin: str = field(metadata=meta("origin", required=True, primitive="str")) + type: str = field(default="worklet", init=False, metadata=meta("type", required=True, fixed="worklet")) + + +@register("script.SharedReference") +@dataclass(frozen=True) +class SharedReference(Record): + _EXTENSIBLE = True + shared_id: str = field(metadata=meta("sharedId", required=True, primitive="str")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("script.RemoteObjectReference") +@dataclass(frozen=True) +class RemoteObjectReference(Record): + _EXTENSIBLE = True + handle: str = field(metadata=meta("handle", required=True, primitive="str")) + shared_id: str | UnsetType = field(default=UNSET, metadata=meta("sharedId", primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("script.SymbolRemoteValue") +@dataclass(frozen=True) +class SymbolRemoteValue(Record): + type: str = field(default="symbol", init=False, metadata=meta("type", required=True, fixed="symbol")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.ArrayRemoteValue") +@dataclass(frozen=True) +class ArrayRemoteValue(Record): + type: str = field(default="array", init=False, metadata=meta("type", required=True, fixed="array")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[RemoteValueValue] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.ObjectRemoteValue") +@dataclass(frozen=True) +class ObjectRemoteValue(Record): + type: str = field(default="object", init=False, metadata=meta("type", required=True, fixed="object")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[list[Any]] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.FunctionRemoteValue") +@dataclass(frozen=True) +class FunctionRemoteValue(Record): + type: str = field(default="function", init=False, metadata=meta("type", required=True, fixed="function")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.RegExpRemoteValue") +@dataclass(frozen=True) +class RegExpRemoteValue(Record): + value: RegExpValue = field(metadata=meta("value", required=True, ref="script.RegExpValue")) + type: str = field(default="regexp", init=False, metadata=meta("type", required=True, fixed="regexp")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.DateRemoteValue") +@dataclass(frozen=True) +class DateRemoteValue(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="date", init=False, metadata=meta("type", required=True, fixed="date")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.MapRemoteValue") +@dataclass(frozen=True) +class MapRemoteValue(Record): + type: str = field(default="map", init=False, metadata=meta("type", required=True, fixed="map")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[list[Any]] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.SetRemoteValue") +@dataclass(frozen=True) +class SetRemoteValue(Record): + type: str = field(default="set", init=False, metadata=meta("type", required=True, fixed="set")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[RemoteValueValue] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.WeakMapRemoteValue") +@dataclass(frozen=True) +class WeakMapRemoteValue(Record): + type: str = field(default="weakmap", init=False, metadata=meta("type", required=True, fixed="weakmap")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.WeakSetRemoteValue") +@dataclass(frozen=True) +class WeakSetRemoteValue(Record): + type: str = field(default="weakset", init=False, metadata=meta("type", required=True, fixed="weakset")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.GeneratorRemoteValue") +@dataclass(frozen=True) +class GeneratorRemoteValue(Record): + type: str = field(default="generator", init=False, metadata=meta("type", required=True, fixed="generator")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.ErrorRemoteValue") +@dataclass(frozen=True) +class ErrorRemoteValue(Record): + type: str = field(default="error", init=False, metadata=meta("type", required=True, fixed="error")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.ProxyRemoteValue") +@dataclass(frozen=True) +class ProxyRemoteValue(Record): + type: str = field(default="proxy", init=False, metadata=meta("type", required=True, fixed="proxy")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.PromiseRemoteValue") +@dataclass(frozen=True) +class PromiseRemoteValue(Record): + type: str = field(default="promise", init=False, metadata=meta("type", required=True, fixed="promise")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.TypedArrayRemoteValue") +@dataclass(frozen=True) +class TypedArrayRemoteValue(Record): + type: str = field(default="typedarray", init=False, metadata=meta("type", required=True, fixed="typedarray")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.ArrayBufferRemoteValue") +@dataclass(frozen=True) +class ArrayBufferRemoteValue(Record): + type: str = field(default="arraybuffer", init=False, metadata=meta("type", required=True, fixed="arraybuffer")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.NodeListRemoteValue") +@dataclass(frozen=True) +class NodeListRemoteValue(Record): + type: str = field(default="nodelist", init=False, metadata=meta("type", required=True, fixed="nodelist")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[RemoteValueValue] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.HTMLCollectionRemoteValue") +@dataclass(frozen=True) +class HTMLCollectionRemoteValue(Record): + type: str = field( + default="htmlcollection", + init=False, + metadata=meta("type", required=True, fixed="htmlcollection"), + ) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: list[RemoteValueValue] | UnsetType = field( + default=UNSET, + metadata=meta("value", ref="script.RemoteValue", is_list=True), + ) + + +@register("script.NodeRemoteValue") +@dataclass(frozen=True) +class NodeRemoteValue(Record): + type: str = field(default="node", init=False, metadata=meta("type", required=True, fixed="node")) + shared_id: str | UnsetType = field(default=UNSET, metadata=meta("sharedId", primitive="str")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + value: NodeProperties | UnsetType = field(default=UNSET, metadata=meta("value", ref="script.NodeProperties")) + + +@register("script.NodeProperties") +@dataclass(frozen=True) +class NodeProperties(Record): + node_type: int = field(metadata=meta("nodeType", required=True, primitive="int")) + child_node_count: int = field(metadata=meta("childNodeCount", required=True, primitive="int")) + attributes: Any | UnsetType = field(default=UNSET, metadata=meta("attributes")) + children: list[NodeRemoteValue] | UnsetType = field( + default=UNSET, + metadata=meta("children", ref="script.NodeRemoteValue", is_list=True), + ) + local_name: str | UnsetType = field(default=UNSET, metadata=meta("localName", primitive="str")) + mode: NodePropertiesMode | UnsetType = field( + default=UNSET, + metadata=meta("mode", enum="script.NodePropertiesMode"), + ) + namespace_uri: str | UnsetType = field(default=UNSET, metadata=meta("namespaceURI", primitive="str")) + node_value: str | UnsetType = field(default=UNSET, metadata=meta("nodeValue", primitive="str")) + shadow_root: NodeRemoteValue | None | UnsetType = field( + default=UNSET, + metadata=meta("shadowRoot", nullable=True, ref="script.NodeRemoteValue"), + ) + + +@register("script.WindowProxyRemoteValue") +@dataclass(frozen=True) +class WindowProxyRemoteValue(Record): + value: WindowProxyProperties = field(metadata=meta("value", required=True, ref="script.WindowProxyProperties")) + type: str = field(default="window", init=False, metadata=meta("type", required=True, fixed="window")) + handle: str | UnsetType = field(default=UNSET, metadata=meta("handle", primitive="str")) + internal_id: str | UnsetType = field(default=UNSET, metadata=meta("internalId", primitive="str")) + + +@register("script.WindowProxyProperties") +@dataclass(frozen=True) +class WindowProxyProperties(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("script.SerializationOptions") +@dataclass(frozen=True) +class SerializationOptions(Record): + max_dom_depth: int | None | UnsetType = field( + default=UNSET, + metadata=meta("maxDomDepth", nullable=True, primitive="int"), + ) + max_object_depth: int | None | UnsetType = field( + default=UNSET, + metadata=meta("maxObjectDepth", nullable=True, primitive="int"), + ) + include_shadow_tree: SerializationOptionsIncludeShadowTree | UnsetType = field( + default=UNSET, + metadata=meta("includeShadowTree", enum="script.SerializationOptionsIncludeShadowTree"), + ) + + +@register("script.StackFrame") +@dataclass(frozen=True) +class StackFrame(Record): + column_number: int = field(metadata=meta("columnNumber", required=True, primitive="int")) + function_name: str = field(metadata=meta("functionName", required=True, primitive="str")) + line_number: int = field(metadata=meta("lineNumber", required=True, primitive="int")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + + +@register("script.StackTrace") +@dataclass(frozen=True) +class StackTrace(Record): + call_frames: list[StackFrame] = field( + metadata=meta("callFrames", required=True, ref="script.StackFrame", is_list=True), + ) + + +@register("script.Source") +@dataclass(frozen=True) +class Source(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + context: str | UnsetType = field(default=UNSET, metadata=meta("context", primitive="str")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + + +@register("script.RealmTarget") +@dataclass(frozen=True) +class RealmTarget(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + + +@register("script.ContextTarget") +@dataclass(frozen=True) +class ContextTarget(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + sandbox: str | UnsetType = field(default=UNSET, metadata=meta("sandbox", primitive="str")) + + +@register("script.AddPreloadScriptParameters") +@dataclass(frozen=True) +class AddPreloadScriptParameters(Record): + function_declaration: str = field(metadata=meta("functionDeclaration", required=True, primitive="str")) + arguments: list[ChannelValue] | UnsetType = field( + default=UNSET, + metadata=meta("arguments", ref="script.ChannelValue", is_list=True), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + sandbox: str | UnsetType = field(default=UNSET, metadata=meta("sandbox", primitive="str")) + + +@register("script.AddPreloadScriptResult") +@dataclass(frozen=True) +class AddPreloadScriptResult(Record): + script: str = field(metadata=meta("script", required=True, primitive="str")) + + +@register("script.DisownParameters") +@dataclass(frozen=True) +class DisownParameters(Record): + handles: list[str] = field(metadata=meta("handles", required=True, is_list=True, primitive="str")) + target: TargetValue = field(metadata=meta("target", required=True, ref="script.Target")) + + +@register("script.CallFunctionParameters") +@dataclass(frozen=True) +class CallFunctionParameters(Record): + function_declaration: str = field(metadata=meta("functionDeclaration", required=True, primitive="str")) + await_promise: bool = field(metadata=meta("awaitPromise", required=True, primitive="bool")) + target: TargetValue = field(metadata=meta("target", required=True, ref="script.Target")) + arguments: list[LocalValueValue] | UnsetType = field( + default=UNSET, + metadata=meta("arguments", ref="script.LocalValue", is_list=True), + ) + result_ownership: ResultOwnership | UnsetType = field( + default=UNSET, + metadata=meta("resultOwnership", enum="script.ResultOwnership"), + ) + serialization_options: SerializationOptions | UnsetType = field( + default=UNSET, + metadata=meta("serializationOptions", ref="script.SerializationOptions"), + ) + this: LocalValueValue | UnsetType = field(default=UNSET, metadata=meta("this", ref="script.LocalValue")) + user_activation: bool | UnsetType = field(default=UNSET, metadata=meta("userActivation", primitive="bool")) + + +@register("script.EvaluateParameters") +@dataclass(frozen=True) +class EvaluateParameters(Record): + expression: str = field(metadata=meta("expression", required=True, primitive="str")) + target: TargetValue = field(metadata=meta("target", required=True, ref="script.Target")) + await_promise: bool = field(metadata=meta("awaitPromise", required=True, primitive="bool")) + result_ownership: ResultOwnership | UnsetType = field( + default=UNSET, + metadata=meta("resultOwnership", enum="script.ResultOwnership"), + ) + serialization_options: SerializationOptions | UnsetType = field( + default=UNSET, + metadata=meta("serializationOptions", ref="script.SerializationOptions"), + ) + user_activation: bool | UnsetType = field(default=UNSET, metadata=meta("userActivation", primitive="bool")) + + +@register("script.GetRealmsParameters") +@dataclass(frozen=True) +class GetRealmsParameters(Record): + context: str | UnsetType = field(default=UNSET, metadata=meta("context", primitive="str")) + type: RealmType | UnsetType = field(default=UNSET, metadata=meta("type", enum="script.RealmType")) + + +@register("script.GetRealmsResult") +@dataclass(frozen=True) +class GetRealmsResult(Record): + realms: list[RealmInfoValue] = field( + metadata=meta("realms", required=True, ref="script.RealmInfo", is_list=True), + ) + + +@register("script.RemovePreloadScriptParameters") +@dataclass(frozen=True) +class RemovePreloadScriptParameters(Record): + script: str = field(metadata=meta("script", required=True, primitive="str")) + + +@register("script.MessageParameters") +@dataclass(frozen=True) +class MessageParameters(Record): + channel: str = field(metadata=meta("channel", required=True, primitive="str")) + data: RemoteValueValue = field(metadata=meta("data", required=True, ref="script.RemoteValue")) + source: Source = field(metadata=meta("source", required=True, ref="script.Source")) + + +@register("script.RealmDestroyedParameters") +@dataclass(frozen=True) +class RealmDestroyedParameters(Record): + realm: str = field(metadata=meta("realm", required=True, primitive="str")) + + +@register("script.EvaluateResult") +class EvaluateResult(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "success": "script.EvaluateResultSuccess", + "exception": "script.EvaluateResultException", + } + _DISCRIMINATOR_VALUES = frozenset({"success", "exception"}) + + +EvaluateResultValue: TypeAlias = "EvaluateResultSuccess | EvaluateResultException" + + +@register("script.LocalValue") +class LocalValue(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "undefined": "script.UndefinedValue", + "null": "script.NullValue", + "string": "script.StringValue", + "number": "script.NumberValue", + "boolean": "script.BooleanValue", + "bigint": "script.BigIntValue", + "channel": "script.ChannelValue", + "array": "script.ArrayLocalValue", + "date": "script.DateLocalValue", + "map": "script.MapLocalValue", + "object": "script.ObjectLocalValue", + "regexp": "script.RegExpLocalValue", + "set": "script.SetLocalValue", + } + _FALLBACK = "script.RemoteReference" + _DISCRIMINATOR_VALUES = frozenset( + { + "undefined", + "null", + "string", + "number", + "boolean", + "bigint", + "channel", + "array", + "date", + "map", + "object", + "regexp", + "set", + } + ) + + +LocalValueValue: TypeAlias = ( + "UndefinedValue | NullValue | StringValue | NumberValue | BooleanValue | BigIntValue | ChannelValue" + " | ArrayLocalValue | DateLocalValue | MapLocalValue | ObjectLocalValue | RegExpLocalValue | SetLocalValue" + " | RemoteReferenceValue" +) + + +@register("script.PrimitiveProtocolValue") +class PrimitiveProtocolValue(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "undefined": "script.UndefinedValue", + "null": "script.NullValue", + "string": "script.StringValue", + "number": "script.NumberValue", + "boolean": "script.BooleanValue", + "bigint": "script.BigIntValue", + } + _DISCRIMINATOR_VALUES = frozenset({"undefined", "null", "string", "number", "boolean", "bigint"}) + + +PrimitiveProtocolValueValue: TypeAlias = ( + "UndefinedValue | NullValue | StringValue | NumberValue | BooleanValue | BigIntValue" +) + + +@register("script.RealmInfo") +class RealmInfo(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "window": "script.WindowRealmInfo", + "dedicated-worker": "script.DedicatedWorkerRealmInfo", + "shared-worker": "script.SharedWorkerRealmInfo", + "service-worker": "script.ServiceWorkerRealmInfo", + "worker": "script.WorkerRealmInfo", + "paint-worklet": "script.PaintWorkletRealmInfo", + "audio-worklet": "script.AudioWorkletRealmInfo", + "worklet": "script.WorkletRealmInfo", + } + _DISCRIMINATOR_VALUES = frozenset( + { + "window", + "dedicated-worker", + "shared-worker", + "service-worker", + "worker", + "paint-worklet", + "audio-worklet", + "worklet", + } + ) + + +RealmInfoValue: TypeAlias = ( + "WindowRealmInfo | DedicatedWorkerRealmInfo | SharedWorkerRealmInfo | ServiceWorkerRealmInfo | WorkerRealmInfo" + " | PaintWorkletRealmInfo | AudioWorkletRealmInfo | WorkletRealmInfo" +) + + +@register("script.RemoteReference") +class RemoteReference(Union): + _PRESENCE = (("script.SharedReference", ("sharedId",)), ("script.RemoteObjectReference", ("handle",))) + + +RemoteReferenceValue: TypeAlias = "SharedReference | RemoteObjectReference" + + +@register("script.RemoteValue") +class RemoteValue(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "undefined": "script.UndefinedValue", + "null": "script.NullValue", + "string": "script.StringValue", + "number": "script.NumberValue", + "boolean": "script.BooleanValue", + "bigint": "script.BigIntValue", + "symbol": "script.SymbolRemoteValue", + "array": "script.ArrayRemoteValue", + "object": "script.ObjectRemoteValue", + "function": "script.FunctionRemoteValue", + "regexp": "script.RegExpRemoteValue", + "date": "script.DateRemoteValue", + "map": "script.MapRemoteValue", + "set": "script.SetRemoteValue", + "weakmap": "script.WeakMapRemoteValue", + "weakset": "script.WeakSetRemoteValue", + "generator": "script.GeneratorRemoteValue", + "error": "script.ErrorRemoteValue", + "proxy": "script.ProxyRemoteValue", + "promise": "script.PromiseRemoteValue", + "typedarray": "script.TypedArrayRemoteValue", + "arraybuffer": "script.ArrayBufferRemoteValue", + "nodelist": "script.NodeListRemoteValue", + "htmlcollection": "script.HTMLCollectionRemoteValue", + "node": "script.NodeRemoteValue", + "window": "script.WindowProxyRemoteValue", + } + _DISCRIMINATOR_VALUES = frozenset( + { + "undefined", + "null", + "string", + "number", + "boolean", + "bigint", + "symbol", + "array", + "object", + "function", + "regexp", + "date", + "map", + "set", + "weakmap", + "weakset", + "generator", + "error", + "proxy", + "promise", + "typedarray", + "arraybuffer", + "nodelist", + "htmlcollection", + "node", + "window", + } + ) + + +RemoteValueValue: TypeAlias = ( + "UndefinedValue | NullValue | StringValue | NumberValue | BooleanValue | BigIntValue | SymbolRemoteValue" + " | ArrayRemoteValue | ObjectRemoteValue | FunctionRemoteValue | RegExpRemoteValue | DateRemoteValue" + " | MapRemoteValue | SetRemoteValue | WeakMapRemoteValue | WeakSetRemoteValue | GeneratorRemoteValue" + " | ErrorRemoteValue | ProxyRemoteValue | PromiseRemoteValue | TypedArrayRemoteValue | ArrayBufferRemoteValue" + " | NodeListRemoteValue | HTMLCollectionRemoteValue | NodeRemoteValue | WindowProxyRemoteValue" +) + + +@register("script.Target") +class Target(Union): + _PRESENCE = (("script.ContextTarget", ("context",)), ("script.RealmTarget", ("realm",))) + + +TargetValue: TypeAlias = "ContextTarget | RealmTarget" + + +class Script(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "message": "script.message", + "realm_created": "script.realmCreated", + "realm_destroyed": "script.realmDestroyed", + } + EVENT_TYPES = { + "script.message": "script.MessageParameters", + "script.realmCreated": "script.RealmInfo", + "script.realmDestroyed": "script.RealmDestroyedParameters", + } + + def add_preload_script( + self, + function_declaration: str, + arguments: list[ChannelValue] | UnsetType = UNSET, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + sandbox: str | UnsetType = UNSET, + ) -> AddPreloadScriptResult: + """Execute script.addPreloadScript (internal, unsupported).""" + params = AddPreloadScriptParameters( + function_declaration=function_declaration, + arguments=arguments, + contexts=contexts, + user_contexts=user_contexts, + sandbox=sandbox, + ) + return self._execute("script.addPreloadScript", params=params, result=AddPreloadScriptResult) + + def call_function( + self, + function_declaration: str, + await_promise: bool, + target: TargetValue, + arguments: list[LocalValueValue] | UnsetType = UNSET, + result_ownership: ResultOwnership | UnsetType = UNSET, + serialization_options: SerializationOptions | UnsetType = UNSET, + this: LocalValueValue | UnsetType = UNSET, + user_activation: bool | UnsetType = UNSET, + ) -> EvaluateResultValue: + """Execute script.callFunction (internal, unsupported).""" + params = CallFunctionParameters( + function_declaration=function_declaration, + await_promise=await_promise, + target=target, + arguments=arguments, + result_ownership=result_ownership, + serialization_options=serialization_options, + this=this, + user_activation=user_activation, + ) + return self._execute("script.callFunction", params=params, result=EvaluateResult) + + def disown(self, handles: list[str], target: TargetValue) -> Any: + """Execute script.disown (internal, unsupported).""" + params = DisownParameters(handles=handles, target=target) + return self._execute("script.disown", params=params, result=None) + + def evaluate( + self, + expression: str, + target: TargetValue, + await_promise: bool, + result_ownership: ResultOwnership | UnsetType = UNSET, + serialization_options: SerializationOptions | UnsetType = UNSET, + user_activation: bool | UnsetType = UNSET, + ) -> EvaluateResultValue: + """Execute script.evaluate (internal, unsupported).""" + params = EvaluateParameters( + expression=expression, + target=target, + await_promise=await_promise, + result_ownership=result_ownership, + serialization_options=serialization_options, + user_activation=user_activation, + ) + return self._execute("script.evaluate", params=params, result=EvaluateResult) + + def get_realms(self, context: str | UnsetType = UNSET, type: RealmType | UnsetType = UNSET) -> GetRealmsResult: + """Execute script.getRealms (internal, unsupported).""" + params = GetRealmsParameters(context=context, type=type) + return self._execute("script.getRealms", params=params, result=GetRealmsResult) + + def remove_preload_script(self, script: str) -> Any: + """Execute script.removePreloadScript (internal, unsupported).""" + params = RemovePreloadScriptParameters(script=script) + return self._execute("script.removePreloadScript", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/serialization.py b/py/selenium/webdriver/_bidi/serialization.py new file mode 100644 index 0000000000000..85e5af1104500 --- /dev/null +++ b/py/selenium/webdriver/_bidi/serialization.py @@ -0,0 +1,364 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Serialization runtime for the generated WebDriver BiDi protocol layer. + +Hand-written support for the generated ``selenium.webdriver._bidi.*`` modules +(not itself generated). Provides the immutable value-type base (:class:`Record`), +the discriminated-union dispatch base (:class:`Union`), and the :data:`UNSET` +sentinel that distinguishes an omitted optional from an explicit wire ``null``. + +Each generated field carries its wire facts as dataclass field metadata (see +:func:`meta`), so a record's Python fields are its single source of truth — there +is no parallel field table. + +This is internal, unsupported implementation. See +https://www.selenium.dev/documentation/warnings/bidi-implementation/ +""" + +from __future__ import annotations + +import dataclasses +from collections.abc import Callable +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from selenium.common.exceptions import WebDriverException + + +class BiDiSerializationError(WebDriverException): + """A payload could not be (de)serialized against this Selenium's BiDi schema.""" + + +class UnsetType: + """Type of the :data:`UNSET` sentinel (an omitted optional, not ``None``).""" + + _instance: UnsetType | None = None + + def __new__(cls) -> UnsetType: + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __repr__(self) -> str: + return "UNSET" + + def __bool__(self) -> bool: + return False + + +UNSET: UnsetType = UnsetType() +"""Marks an omitted optional. Dropped from the wire; distinct from ``None`` (explicit null).""" + + +@dataclass(frozen=True) +class _Wire: + """Wire facts for one record field, attached as its dataclass field metadata.""" + + wire: str + required: bool = False + nullable: bool = False + ref: str | None = None + is_list: bool = False + enum: str | None = None + primitive: str | None = None + fixed: Any = UNSET + + +_META_KEY = "bidi" + + +def meta( + name: str, + *, + required: bool = False, + nullable: bool = False, + ref: str | None = None, + is_list: bool = False, + enum: str | None = None, + primitive: str | None = None, + fixed: Any = UNSET, +) -> dict: + """Dataclass field metadata carrying one field's BiDi wire facts. + + Used as ``x: T = field(metadata=meta(...))`` in the generated modules — a plain + ``dataclasses.field`` so it reads as an ordinary dataclass field. A baked + discriminator additionally sets ``default=, init=False`` on the field. + """ + descriptor = _Wire( + wire=name, + required=required, + nullable=nullable, + ref=ref, + is_list=is_list, + enum=enum, + primitive=primitive, + fixed=fixed, + ) + return {_META_KEY: descriptor} + + +# schema type name -> generated class, populated as each domain module imports. +# Refs are resolved lazily (at (de)serialization time) through this registry so +# generated modules never import each other at runtime, avoiding import cycles. +_REGISTRY: dict[str, type] = {} + + +def register(schema_name: str) -> Callable[[type], type]: + """Class decorator registering a generated class under its schema type name.""" + + def decorate(cls: type) -> type: + _REGISTRY[schema_name] = cls + return cls + + return decorate + + +def resolve(schema_name: str) -> type: + try: + return _REGISTRY[schema_name] + except KeyError: + raise BiDiSerializationError(f"unknown BiDi type {schema_name!r} (module not imported?)") from None + + +def _fields(obj: Any) -> tuple[dataclasses.Field[Any], ...]: + """``dataclasses.fields`` for a Record subclass; the ``Record`` base is not itself a dataclass.""" + return dataclasses.fields(obj) + + +def _wire_of(f: dataclasses.Field) -> _Wire: + return f.metadata[_META_KEY] + + +def _as_json(value: Any) -> Any: + if isinstance(value, Record): + return value.as_json() + if isinstance(value, Enum): + return value.value + if isinstance(value, list): + return [_as_json(item) for item in value] + if isinstance(value, dict): + return {key: _as_json(item) for key, item in value.items()} + return value + + +class Record: + """Immutable value-type base for generated params/results/event payloads. + + Subclasses are ``@dataclass(frozen=True)`` whose fields are declared with + :func:`meta`. Outbound (:meth:`as_json`) omits ``UNSET`` and emits ``null`` only + for nullable fields; inbound (:meth:`from_json`) is strict on values and + required-presence, lenient on unknown keys (the spec permits extra properties). + """ + + _EXTENSIBLE: bool = False + + def __post_init__(self) -> None: + for f in _fields(self): + if f.name == "extensions": + continue + w = _wire_of(f) + if w.fixed is not UNSET: + continue + value = getattr(self, f.name) + if value is UNSET: + continue + if value is None: + if not w.nullable: + raise BiDiSerializationError(f"{type(self).__name__}.{f.name} cannot be None") + continue + if w.enum is not None: + self._validate_enum(f.name, w, value) + + def _validate_enum(self, name: str, w: _Wire, value: Any) -> None: + enum_cls = resolve(w.enum) # type: ignore[arg-type] + for item in value if w.is_list else [value]: + if isinstance(item, enum_cls): + continue + try: + enum_cls(item) + except ValueError: + raise BiDiSerializationError( + f"{type(self).__name__}.{name}: {item!r} is not a valid {enum_cls.__name__}" + ) from None + + def as_json(self) -> dict: + payload: dict = {} + for f in _fields(self): + if f.name == "extensions": + continue + w = _wire_of(f) + value = getattr(self, f.name) + if value is UNSET: + continue + if value is None and not w.nullable: + continue + payload[w.wire] = _as_json(value) + if self._EXTENSIBLE: + payload.update(getattr(self, "extensions", None) or {}) + return payload + + @classmethod + def from_json(cls, payload: dict) -> Any: + kwargs: dict = {} + known: set[str] = set() + for f in _fields(cls): + w = _wire_of(f) + known.add(w.wire) + if w.fixed is not UNSET or f.name == "extensions": + continue + kwargs[f.name] = _read_field(cls, f.name, w, payload) + if cls._EXTENSIBLE: + kwargs["extensions"] = {k: v for k, v in payload.items() if k not in known} + return cls(**kwargs) + + +def _read_field(cls: type, name: str, w: _Wire, payload: dict) -> Any: + if w.wire not in payload: + if w.required: + raise BiDiSerializationError(f"{cls.__name__}.{name} missing required {w.wire!r}") + return UNSET + raw = payload[w.wire] + if raw is None: + if w.nullable: + return None + raise BiDiSerializationError(f"{cls.__name__}.{name} received null but is not nullable") + if w.is_list != isinstance(raw, list) and (w.is_list or w.enum or w.ref or w.primitive): + expected = "a list" if w.is_list else "a single value" + raise BiDiSerializationError(f"{cls.__name__}.{name} expected {expected}, got {raw!r}") + if w.is_list: + return [_read_scalar(cls, name, w, item) for item in raw] + return _read_scalar(cls, name, w, raw) + + +# JSON value -> the Python types a schema primitive accepts. ``integer`` accepts only an +# int (a non-integer float like 1.5 is a real mismatch, and even 5.0 is rejected under +# strict-first — relax reactively if a browser is ever seen sending it). ``number`` accepts +# an int or a float. ``bool`` is excluded from the numeric checks: it is an ``int`` subclass +# but is not a number. +_PRIMITIVE_CHECKS = { + "str": lambda v: isinstance(v, str), + "bool": lambda v: isinstance(v, bool), + "int": lambda v: isinstance(v, int) and not isinstance(v, bool), + "float": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool), +} + + +def _read_scalar(cls: type, name: str, w: _Wire, raw: Any) -> Any: + if w.enum is not None: + enum_cls = resolve(w.enum) + try: + return enum_cls(raw) + except ValueError: + raise BiDiSerializationError(f"{cls.__name__}.{name}: {raw!r} is not a valid {enum_cls.__name__}") from None + if w.ref is not None: + klass = resolve(w.ref) + # A record must arrive as an object; a union may legitimately be a bare scalar + # arm (e.g. input.Origin's "viewport"), so let its from_json handle non-objects. + if not issubclass(klass, Union) and not isinstance(raw, dict): + got = type(raw).__name__ + raise BiDiSerializationError(f"{cls.__name__}.{name}: expected an object, got {got} {raw!r}") + return klass.from_json(raw) # type: ignore[attr-defined] + if w.primitive is not None and raw is not None: + check = _PRIMITIVE_CHECKS.get(w.primitive) + if check and not check(raw): + got = type(raw).__name__ + raise BiDiSerializationError(f"{cls.__name__}.{name}: expected {w.primitive}, got {got} {raw!r}") + return raw + + +class Union: + """Dispatch base for a discriminated union; subclassed, never instantiated. + + Carries the schema's authoritative ``selector`` as class attributes: a + discriminator wire key + tag->variant table, and/or presence-ordered rules, and + an optional declared fallback. Inbound (:meth:`from_json`) resolves a payload to a + variant; outbound (:meth:`build`) selects the variant a command's kwargs describe. + """ + + _DISCRIMINATOR: str | None = None + _VARIANTS: dict[Any, str] = {} + _PRESENCE: tuple[tuple[str, tuple[str, ...]], ...] = () + _FALLBACK: str | None = None + _DISCRIMINATOR_VALUES: frozenset[Any] | None = None + + @classmethod + def from_json(cls, payload: Any) -> Any: + # A bare scalar arm (e.g. input.Origin's "viewport") has no object to + # dispatch on, so it is returned unchanged. + if not isinstance(payload, dict): + return payload + variant = cls._select(payload) + if variant is None: + raise BiDiSerializationError( + f"{cls.__name__} received a variant not in this Selenium's BiDi schema: {payload!r}" + ) + return resolve(variant).from_json(payload) # type: ignore[attr-defined] + + @classmethod + def _select(cls, payload: dict) -> str | None: + if cls._DISCRIMINATOR is not None and cls._DISCRIMINATOR in payload: + tag = payload[cls._DISCRIMINATOR] + if tag in cls._VARIANTS: + return cls._VARIANTS[tag] + for variant, keys in cls._PRESENCE: + if all(key in payload for key in keys): + return variant + return cls._FALLBACK + + @classmethod + def build(cls, **kwargs: Any) -> Any: + cls._validate_discriminator(kwargs) + variant = cls._outbound(kwargs) + if variant is None: + raise BiDiSerializationError(f"no {cls.__name__} variant matches {kwargs!r}") + klass = resolve(variant) + provided = {k: v for k, v in kwargs.items() if v is not UNSET} + fields = {f.name for f in _fields(klass)} + invalid = set(provided) - fields + if invalid: + raise BiDiSerializationError(f"invalid combination for {cls.__name__}: {', '.join(sorted(invalid))}") + # A baked discriminator (init=False) is forced to its const, so drop it from + # the constructor kwargs even though it is a valid field to have named. + settable = {f.name for f in _fields(klass) if _wire_of(f).fixed is UNSET} + return klass(**{k: v for k, v in provided.items() if k in settable}) + + # Outbound-strict (V1): a supplied discriminator must be in the union-wide allowed + # set, so an invalid value fails locally rather than routing to the fallback variant. + @classmethod + def _validate_discriminator(cls, kwargs: dict) -> None: + if cls._DISCRIMINATOR is None or cls._DISCRIMINATOR_VALUES is None: + return + value = kwargs.get(cls._DISCRIMINATOR, UNSET) + if isinstance(value, Enum): + value = value.value + if value is not UNSET and value not in cls._DISCRIMINATOR_VALUES: + raise BiDiSerializationError(f"{cls.__name__}.{cls._DISCRIMINATOR}: {value!r} is not a valid discriminator") + + @classmethod + def _outbound(cls, kwargs: dict) -> str | None: + if cls._DISCRIMINATOR is not None: + tag = kwargs.get(cls._DISCRIMINATOR, UNSET) + if isinstance(tag, Enum): + tag = tag.value + if tag is not UNSET and tag in cls._VARIANTS: + return cls._VARIANTS[tag] + for variant, keys in cls._PRESENCE: + if all(kwargs.get(key, UNSET) is not UNSET for key in keys): + return variant + return cls._FALLBACK diff --git a/py/selenium/webdriver/_bidi/session.py b/py/selenium/webdriver/_bidi/session.py new file mode 100644 index 0000000000000..078ace9c23062 --- /dev/null +++ b/py/selenium/webdriver/_bidi/session.py @@ -0,0 +1,299 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("session.UserPromptHandlerType") +class UserPromptHandlerType(str, Enum): + ACCEPT = "accept" + DISMISS = "dismiss" + IGNORE = "ignore" + + +@register("session.CapabilitiesRequest") +@dataclass(frozen=True) +class CapabilitiesRequest(Record): + always_match: CapabilityRequest | UnsetType = field( + default=UNSET, + metadata=meta("alwaysMatch", ref="session.CapabilityRequest"), + ) + first_match: list[CapabilityRequest] | UnsetType = field( + default=UNSET, + metadata=meta("firstMatch", ref="session.CapabilityRequest", is_list=True), + ) + + +@register("session.CapabilityRequest") +@dataclass(frozen=True) +class CapabilityRequest(Record): + _EXTENSIBLE = True + accept_insecure_certs: bool | UnsetType = field( + default=UNSET, + metadata=meta("acceptInsecureCerts", primitive="bool"), + ) + browser_name: str | UnsetType = field(default=UNSET, metadata=meta("browserName", primitive="str")) + browser_version: str | UnsetType = field(default=UNSET, metadata=meta("browserVersion", primitive="str")) + platform_name: str | UnsetType = field(default=UNSET, metadata=meta("platformName", primitive="str")) + proxy: ProxyConfigurationValue | UnsetType = field( + default=UNSET, + metadata=meta("proxy", ref="session.ProxyConfiguration"), + ) + unhandled_prompt_behavior: UserPromptHandler | UnsetType = field( + default=UNSET, + metadata=meta("unhandledPromptBehavior", ref="session.UserPromptHandler"), + ) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.AutodetectProxyConfiguration") +@dataclass(frozen=True) +class AutodetectProxyConfiguration(Record): + _EXTENSIBLE = True + proxy_type: str = field( + default="autodetect", + init=False, + metadata=meta("proxyType", required=True, fixed="autodetect"), + ) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.DirectProxyConfiguration") +@dataclass(frozen=True) +class DirectProxyConfiguration(Record): + _EXTENSIBLE = True + proxy_type: str = field(default="direct", init=False, metadata=meta("proxyType", required=True, fixed="direct")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.ManualProxyConfiguration") +@dataclass(frozen=True) +class ManualProxyConfiguration(Record): + _EXTENSIBLE = True + socks_proxy: str = field(metadata=meta("socksProxy", required=True, primitive="str")) + socks_version: int = field(metadata=meta("socksVersion", required=True, primitive="int")) + proxy_type: str = field(default="manual", init=False, metadata=meta("proxyType", required=True, fixed="manual")) + http_proxy: str | UnsetType = field(default=UNSET, metadata=meta("httpProxy", primitive="str")) + ssl_proxy: str | UnsetType = field(default=UNSET, metadata=meta("sslProxy", primitive="str")) + no_proxy: list[str] | UnsetType = field(default=UNSET, metadata=meta("noProxy", is_list=True, primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.SocksProxyConfiguration") +@dataclass(frozen=True) +class SocksProxyConfiguration(Record): + socks_proxy: str = field(metadata=meta("socksProxy", required=True, primitive="str")) + socks_version: int = field(metadata=meta("socksVersion", required=True, primitive="int")) + + +@register("session.PacProxyConfiguration") +@dataclass(frozen=True) +class PacProxyConfiguration(Record): + _EXTENSIBLE = True + proxy_autoconfig_url: str = field(metadata=meta("proxyAutoconfigUrl", required=True, primitive="str")) + proxy_type: str = field(default="pac", init=False, metadata=meta("proxyType", required=True, fixed="pac")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.SystemProxyConfiguration") +@dataclass(frozen=True) +class SystemProxyConfiguration(Record): + _EXTENSIBLE = True + proxy_type: str = field(default="system", init=False, metadata=meta("proxyType", required=True, fixed="system")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.UserPromptHandler") +@dataclass(frozen=True) +class UserPromptHandler(Record): + alert: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("alert", enum="session.UserPromptHandlerType"), + ) + before_unload: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("beforeUnload", enum="session.UserPromptHandlerType"), + ) + confirm: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("confirm", enum="session.UserPromptHandlerType"), + ) + default: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("default", enum="session.UserPromptHandlerType"), + ) + file: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("file", enum="session.UserPromptHandlerType"), + ) + prompt: UserPromptHandlerType | UnsetType = field( + default=UNSET, + metadata=meta("prompt", enum="session.UserPromptHandlerType"), + ) + + +@register("session.SubscribeParameters") +@dataclass(frozen=True) +class SubscribeParameters(Record): + events: list[str] = field(metadata=meta("events", required=True, is_list=True, primitive="str")) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +@register("session.UnsubscribeByIDRequest") +@dataclass(frozen=True) +class UnsubscribeByIDRequest(Record): + subscriptions: list[str] = field(metadata=meta("subscriptions", required=True, is_list=True, primitive="str")) + + +@register("session.UnsubscribeByAttributesRequest") +@dataclass(frozen=True) +class UnsubscribeByAttributesRequest(Record): + events: list[str] = field(metadata=meta("events", required=True, is_list=True, primitive="str")) + + +@register("session.StatusResult") +@dataclass(frozen=True) +class StatusResult(Record): + ready: bool = field(metadata=meta("ready", required=True, primitive="bool")) + message: str = field(metadata=meta("message", required=True, primitive="str")) + + +@register("session.NewParameters") +@dataclass(frozen=True) +class NewParameters(Record): + capabilities: CapabilitiesRequest = field( + metadata=meta("capabilities", required=True, ref="session.CapabilitiesRequest"), + ) + + +@register("session.NewResult") +@dataclass(frozen=True) +class NewResult(Record): + session_id: str = field(metadata=meta("sessionId", required=True, primitive="str")) + capabilities: NewResultCapabilities = field( + metadata=meta("capabilities", required=True, ref="session.NewResultCapabilities"), + ) + + +@register("session.SubscribeResult") +@dataclass(frozen=True) +class SubscribeResult(Record): + subscription: str = field(metadata=meta("subscription", required=True, primitive="str")) + + +@register("session.NewResultCapabilities") +@dataclass(frozen=True) +class NewResultCapabilities(Record): + _EXTENSIBLE = True + accept_insecure_certs: bool = field(metadata=meta("acceptInsecureCerts", required=True, primitive="bool")) + browser_name: str = field(metadata=meta("browserName", required=True, primitive="str")) + browser_version: str = field(metadata=meta("browserVersion", required=True, primitive="str")) + platform_name: str = field(metadata=meta("platformName", required=True, primitive="str")) + set_window_rect: bool = field(metadata=meta("setWindowRect", required=True, primitive="bool")) + user_agent: str = field(metadata=meta("userAgent", required=True, primitive="str")) + proxy: ProxyConfigurationValue | UnsetType = field( + default=UNSET, + metadata=meta("proxy", ref="session.ProxyConfiguration"), + ) + unhandled_prompt_behavior: UserPromptHandler | UnsetType = field( + default=UNSET, + metadata=meta("unhandledPromptBehavior", ref="session.UserPromptHandler"), + ) + web_socket_url: str | UnsetType = field(default=UNSET, metadata=meta("webSocketUrl", primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("session.ProxyConfiguration") +class ProxyConfiguration(Union): + _DISCRIMINATOR = "proxyType" + _VARIANTS = { + "autodetect": "session.AutodetectProxyConfiguration", + "direct": "session.DirectProxyConfiguration", + "manual": "session.ManualProxyConfiguration", + "pac": "session.PacProxyConfiguration", + "system": "session.SystemProxyConfiguration", + } + _DISCRIMINATOR_VALUES = frozenset({"autodetect", "direct", "manual", "pac", "system"}) + + +ProxyConfigurationValue: TypeAlias = ( + "AutodetectProxyConfiguration | DirectProxyConfiguration | ManualProxyConfiguration | PacProxyConfiguration" + " | SystemProxyConfiguration" +) + + +@register("session.UnsubscribeParameters") +class UnsubscribeParameters(Union): + _PRESENCE = ( + ("session.UnsubscribeByAttributesRequest", ("events",)), + ("session.UnsubscribeByIDRequest", ("subscriptions",)), + ) + + +UnsubscribeParametersValue: TypeAlias = "UnsubscribeByAttributesRequest | UnsubscribeByIDRequest" + + +class Session(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def end(self) -> Any: + """Execute session.end (internal, unsupported).""" + return self._execute("session.end", params=None, result=None) + + def new(self, capabilities: CapabilitiesRequest) -> NewResult: + """Execute session.new (internal, unsupported).""" + params = NewParameters(capabilities=capabilities) + return self._execute("session.new", params=params, result=NewResult) + + def status(self) -> StatusResult: + """Execute session.status (internal, unsupported).""" + return self._execute("session.status", params=None, result=StatusResult) + + def subscribe( + self, + events: list[str], + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> SubscribeResult: + """Execute session.subscribe (internal, unsupported).""" + params = SubscribeParameters(events=events, contexts=contexts, user_contexts=user_contexts) + return self._execute("session.subscribe", params=params, result=SubscribeResult) + + def unsubscribe( + self, + events: list[str] | UnsetType = UNSET, + subscriptions: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute session.unsubscribe (internal, unsupported).""" + params = UnsubscribeParameters.build(events=events, subscriptions=subscriptions) + return self._execute("session.unsubscribe", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/speculation.py b/py/selenium/webdriver/_bidi/speculation.py new file mode 100644 index 0000000000000..f219d1d0bc261 --- /dev/null +++ b/py/selenium/webdriver/_bidi/speculation.py @@ -0,0 +1,55 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum + +from selenium.webdriver._bidi.serialization import Record, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("speculation.PreloadingStatus") +class PreloadingStatus(str, Enum): + PENDING = "pending" + READY = "ready" + SUCCESS = "success" + FAILURE = "failure" + + +@register("speculation.PrefetchStatusUpdatedParameters") +@dataclass(frozen=True) +class PrefetchStatusUpdatedParameters(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + url: str = field(metadata=meta("url", required=True, primitive="str")) + status: PreloadingStatus = field(metadata=meta("status", required=True, enum="speculation.PreloadingStatus")) + + +class Speculation(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + EVENTS = { + "prefetch_status_updated": "speculation.prefetchStatusUpdated", + } + EVENT_TYPES = { + "speculation.prefetchStatusUpdated": "speculation.PrefetchStatusUpdatedParameters", + } diff --git a/py/selenium/webdriver/_bidi/storage.py b/py/selenium/webdriver/_bidi/storage.py new file mode 100644 index 0000000000000..6f22e4369ee8e --- /dev/null +++ b/py/selenium/webdriver/_bidi/storage.py @@ -0,0 +1,182 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, TypeAlias + +from selenium.webdriver._bidi.serialization import UNSET, Record, Union, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + +if TYPE_CHECKING: + from selenium.webdriver._bidi.network import BytesValueValue, Cookie, SameSite + + +@register("storage.PartitionKey") +@dataclass(frozen=True) +class PartitionKey(Record): + _EXTENSIBLE = True + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + source_origin: str | UnsetType = field(default=UNSET, metadata=meta("sourceOrigin", primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("storage.CookieFilter") +@dataclass(frozen=True) +class CookieFilter(Record): + _EXTENSIBLE = True + name: str | UnsetType = field(default=UNSET, metadata=meta("name", primitive="str")) + value: BytesValueValue | UnsetType = field(default=UNSET, metadata=meta("value", ref="network.BytesValue")) + domain: str | UnsetType = field(default=UNSET, metadata=meta("domain", primitive="str")) + path: str | UnsetType = field(default=UNSET, metadata=meta("path", primitive="str")) + size: int | UnsetType = field(default=UNSET, metadata=meta("size", primitive="int")) + http_only: bool | UnsetType = field(default=UNSET, metadata=meta("httpOnly", primitive="bool")) + secure: bool | UnsetType = field(default=UNSET, metadata=meta("secure", primitive="bool")) + same_site: SameSite | UnsetType = field(default=UNSET, metadata=meta("sameSite", enum="network.SameSite")) + expiry: int | UnsetType = field(default=UNSET, metadata=meta("expiry", primitive="int")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("storage.BrowsingContextPartitionDescriptor") +@dataclass(frozen=True) +class BrowsingContextPartitionDescriptor(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + type: str = field(default="context", init=False, metadata=meta("type", required=True, fixed="context")) + + +@register("storage.StorageKeyPartitionDescriptor") +@dataclass(frozen=True) +class StorageKeyPartitionDescriptor(Record): + _EXTENSIBLE = True + type: str = field(default="storageKey", init=False, metadata=meta("type", required=True, fixed="storageKey")) + user_context: str | UnsetType = field(default=UNSET, metadata=meta("userContext", primitive="str")) + source_origin: str | UnsetType = field(default=UNSET, metadata=meta("sourceOrigin", primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("storage.GetCookiesParameters") +@dataclass(frozen=True) +class GetCookiesParameters(Record): + filter: CookieFilter | UnsetType = field(default=UNSET, metadata=meta("filter", ref="storage.CookieFilter")) + partition: PartitionDescriptorValue | UnsetType = field( + default=UNSET, + metadata=meta("partition", ref="storage.PartitionDescriptor"), + ) + + +@register("storage.GetCookiesResult") +@dataclass(frozen=True) +class GetCookiesResult(Record): + cookies: list[Cookie] = field(metadata=meta("cookies", required=True, ref="network.Cookie", is_list=True)) + partition_key: PartitionKey = field(metadata=meta("partitionKey", required=True, ref="storage.PartitionKey")) + + +@register("storage.PartialCookie") +@dataclass(frozen=True) +class PartialCookie(Record): + _EXTENSIBLE = True + name: str = field(metadata=meta("name", required=True, primitive="str")) + value: BytesValueValue = field(metadata=meta("value", required=True, ref="network.BytesValue")) + domain: str = field(metadata=meta("domain", required=True, primitive="str")) + path: str | UnsetType = field(default=UNSET, metadata=meta("path", primitive="str")) + http_only: bool | UnsetType = field(default=UNSET, metadata=meta("httpOnly", primitive="bool")) + secure: bool | UnsetType = field(default=UNSET, metadata=meta("secure", primitive="bool")) + same_site: SameSite | UnsetType = field(default=UNSET, metadata=meta("sameSite", enum="network.SameSite")) + expiry: int | UnsetType = field(default=UNSET, metadata=meta("expiry", primitive="int")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("storage.SetCookieParameters") +@dataclass(frozen=True) +class SetCookieParameters(Record): + cookie: PartialCookie = field(metadata=meta("cookie", required=True, ref="storage.PartialCookie")) + partition: PartitionDescriptorValue | UnsetType = field( + default=UNSET, + metadata=meta("partition", ref="storage.PartitionDescriptor"), + ) + + +@register("storage.SetCookieResult") +@dataclass(frozen=True) +class SetCookieResult(Record): + partition_key: PartitionKey = field(metadata=meta("partitionKey", required=True, ref="storage.PartitionKey")) + + +@register("storage.DeleteCookiesParameters") +@dataclass(frozen=True) +class DeleteCookiesParameters(Record): + filter: CookieFilter | UnsetType = field(default=UNSET, metadata=meta("filter", ref="storage.CookieFilter")) + partition: PartitionDescriptorValue | UnsetType = field( + default=UNSET, + metadata=meta("partition", ref="storage.PartitionDescriptor"), + ) + + +@register("storage.DeleteCookiesResult") +@dataclass(frozen=True) +class DeleteCookiesResult(Record): + partition_key: PartitionKey = field(metadata=meta("partitionKey", required=True, ref="storage.PartitionKey")) + + +@register("storage.PartitionDescriptor") +class PartitionDescriptor(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "context": "storage.BrowsingContextPartitionDescriptor", + "storageKey": "storage.StorageKeyPartitionDescriptor", + } + _DISCRIMINATOR_VALUES = frozenset({"context", "storageKey"}) + + +PartitionDescriptorValue: TypeAlias = "BrowsingContextPartitionDescriptor | StorageKeyPartitionDescriptor" + + +class Storage(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def delete_cookies( + self, + filter: CookieFilter | UnsetType = UNSET, + partition: PartitionDescriptorValue | UnsetType = UNSET, + ) -> DeleteCookiesResult: + """Execute storage.deleteCookies (internal, unsupported).""" + params = DeleteCookiesParameters(filter=filter, partition=partition) + return self._execute("storage.deleteCookies", params=params, result=DeleteCookiesResult) + + def get_cookies( + self, + filter: CookieFilter | UnsetType = UNSET, + partition: PartitionDescriptorValue | UnsetType = UNSET, + ) -> GetCookiesResult: + """Execute storage.getCookies (internal, unsupported).""" + params = GetCookiesParameters(filter=filter, partition=partition) + return self._execute("storage.getCookies", params=params, result=GetCookiesResult) + + def set_cookie( + self, + cookie: PartialCookie, + partition: PartitionDescriptorValue | UnsetType = UNSET, + ) -> SetCookieResult: + """Execute storage.setCookie (internal, unsupported).""" + params = SetCookieParameters(cookie=cookie, partition=partition) + return self._execute("storage.setCookie", params=params, result=SetCookieResult) diff --git a/py/selenium/webdriver/_bidi/transport.py b/py/selenium/webdriver/_bidi/transport.py new file mode 100644 index 0000000000000..75acae9840916 --- /dev/null +++ b/py/selenium/webdriver/_bidi/transport.py @@ -0,0 +1,89 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""The seam between the generated BiDi protocol layer and the websocket. + +Hand-written (not generated). :class:`Transport` serializes a command's params, +sends it over the existing session websocket, and parses the reply into its +declared result type. :class:`Domain` is the base every generated domain module +subclasses; it accepts a ``WebDriver`` (reusing the session's existing BiDi +connection) or a :class:`Transport` (standalone/low-level path). + +This is internal, unsupported implementation. See +https://www.selenium.dev/documentation/warnings/bidi-implementation/ +""" + +from __future__ import annotations + +from collections.abc import Generator +from typing import Any + +from selenium.common.exceptions import WebDriverException + + +def _command(method: str, params: dict) -> Generator[dict, Any, Any]: + """A command_builder coroutine: yield the wire frame, return the raw result. + + ``WebSocketConnection.execute`` speaks this protocol (``next`` then ``send``). + Params arrive already wire-shaped (from ``Record.as_json``), so the connection's + dataclass encoder passes them through untouched — no double camelCasing. + """ + result = yield {"method": method, "params": params} + return result + + +class Transport: + """Serializes params, sends over a websocket connection, parses the reply. + + Deliberately narrow: it holds only an immutable connection reference (the id + counter and callbacks live on the shared ``WebSocketConnection``). One is + built per session and owned by the ``WebDriver`` (as ``_bidi_transport``), so + every domain over that session shares it. + """ + + def __init__(self, connection: Any) -> None: + self._connection = connection + + def execute(self, cmd: str, params: Any = None, result: Any = None) -> Any: + wire_params = params.as_json() if params is not None else {} + raw = self._connection.execute(_command(cmd, wire_params)) + return result.from_json(raw) if result is not None else raw + + +class Domain: + """Base for every generated domain module. + + ``source`` is a ``WebDriver`` — whose one shared BiDi transport is reused (the + driver starts BiDi if it hasn't already) — or a :class:`Transport` for the + standalone path. + """ + + def __init__(self, source: Any) -> None: + if isinstance(source, Transport): + self._transport = source + return + transport = getattr(source, "_bidi_transport", None) + if transport is None: + start = getattr(source, "_start_bidi", None) + if start is None: + raise WebDriverException("a WebDriver or Transport is required") + start() + transport = source._bidi_transport + self._transport = transport + + def _execute(self, cmd: str, params: Any = None, result: Any = None) -> Any: + return self._transport.execute(cmd, params=params, result=result) diff --git a/py/selenium/webdriver/_bidi/user_agent_client_hints.py b/py/selenium/webdriver/_bidi/user_agent_client_hints.py new file mode 100644 index 0000000000000..48686207a7d99 --- /dev/null +++ b/py/selenium/webdriver/_bidi/user_agent_client_hints.py @@ -0,0 +1,91 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + +from selenium.webdriver._bidi.serialization import UNSET, Record, UnsetType, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("userAgentClientHints.ClientHintsMetadata") +@dataclass(frozen=True) +class ClientHintsMetadata(Record): + brands: list[BrandVersion] | UnsetType = field( + default=UNSET, + metadata=meta("brands", ref="userAgentClientHints.BrandVersion", is_list=True), + ) + full_version_list: list[BrandVersion] | UnsetType = field( + default=UNSET, + metadata=meta("fullVersionList", ref="userAgentClientHints.BrandVersion", is_list=True), + ) + platform: str | UnsetType = field(default=UNSET, metadata=meta("platform", primitive="str")) + platform_version: str | UnsetType = field(default=UNSET, metadata=meta("platformVersion", primitive="str")) + architecture: str | UnsetType = field(default=UNSET, metadata=meta("architecture", primitive="str")) + model: str | UnsetType = field(default=UNSET, metadata=meta("model", primitive="str")) + mobile: bool | UnsetType = field(default=UNSET, metadata=meta("mobile", primitive="bool")) + bitness: str | UnsetType = field(default=UNSET, metadata=meta("bitness", primitive="str")) + wow64: bool | UnsetType = field(default=UNSET, metadata=meta("wow64", primitive="bool")) + form_factors: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("formFactors", is_list=True, primitive="str"), + ) + + +@register("userAgentClientHints.BrandVersion") +@dataclass(frozen=True) +class BrandVersion(Record): + brand: str = field(metadata=meta("brand", required=True, primitive="str")) + version: str = field(metadata=meta("version", required=True, primitive="str")) + + +@register("userAgentClientHints.SetClientHintsOverrideCommandParams") +@dataclass(frozen=True) +class SetClientHintsOverrideCommandParams(Record): + client_hints: ClientHintsMetadata | None = field( + metadata=meta("clientHints", required=True, nullable=True, ref="userAgentClientHints.ClientHintsMetadata"), + ) + contexts: list[str] | UnsetType = field(default=UNSET, metadata=meta("contexts", is_list=True, primitive="str")) + user_contexts: list[str] | UnsetType = field( + default=UNSET, + metadata=meta("userContexts", is_list=True, primitive="str"), + ) + + +class UserAgentClientHints(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def set_client_hints_override( + self, + client_hints: ClientHintsMetadata | None, + contexts: list[str] | UnsetType = UNSET, + user_contexts: list[str] | UnsetType = UNSET, + ) -> Any: + """Execute userAgentClientHints.setClientHintsOverride (internal, unsupported).""" + params = SetClientHintsOverrideCommandParams( + client_hints=client_hints, + contexts=contexts, + user_contexts=user_contexts, + ) + return self._execute("userAgentClientHints.setClientHintsOverride", params=params, result=None) diff --git a/py/selenium/webdriver/_bidi/web_extension.py b/py/selenium/webdriver/_bidi/web_extension.py new file mode 100644 index 0000000000000..d0b6c672f1d07 --- /dev/null +++ b/py/selenium/webdriver/_bidi/web_extension.py @@ -0,0 +1,98 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# This file is generated from the WebDriver BiDi specification. +# DO NOT EDIT. Regenerate with: +# bazel run //py:generate-bidi-protocol + + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, TypeAlias + +from selenium.webdriver._bidi.serialization import Record, Union, meta, register +from selenium.webdriver._bidi.transport import Domain + + +@register("webExtension.InstallParameters") +@dataclass(frozen=True) +class InstallParameters(Record): + extension_data: ExtensionDataValue = field( + metadata=meta("extensionData", required=True, ref="webExtension.ExtensionData"), + ) + + +@register("webExtension.ExtensionPath") +@dataclass(frozen=True) +class ExtensionPath(Record): + path: str = field(metadata=meta("path", required=True, primitive="str")) + type: str = field(default="path", init=False, metadata=meta("type", required=True, fixed="path")) + + +@register("webExtension.ExtensionArchivePath") +@dataclass(frozen=True) +class ExtensionArchivePath(Record): + path: str = field(metadata=meta("path", required=True, primitive="str")) + type: str = field(default="archivePath", init=False, metadata=meta("type", required=True, fixed="archivePath")) + + +@register("webExtension.ExtensionBase64Encoded") +@dataclass(frozen=True) +class ExtensionBase64Encoded(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + type: str = field(default="base64", init=False, metadata=meta("type", required=True, fixed="base64")) + + +@register("webExtension.InstallResult") +@dataclass(frozen=True) +class InstallResult(Record): + extension: str = field(metadata=meta("extension", required=True, primitive="str")) + + +@register("webExtension.UninstallParameters") +@dataclass(frozen=True) +class UninstallParameters(Record): + extension: str = field(metadata=meta("extension", required=True, primitive="str")) + + +@register("webExtension.ExtensionData") +class ExtensionData(Union): + _DISCRIMINATOR = "type" + _VARIANTS = { + "archivePath": "webExtension.ExtensionArchivePath", + "base64": "webExtension.ExtensionBase64Encoded", + "path": "webExtension.ExtensionPath", + } + _DISCRIMINATOR_VALUES = frozenset({"archivePath", "base64", "path"}) + + +ExtensionDataValue: TypeAlias = "ExtensionArchivePath | ExtensionBase64Encoded | ExtensionPath" + + +class WebExtension(Domain): + # Internal, unsupported. See https://www.selenium.dev/documentation/warnings/bidi-implementation/ + + def install(self, extension_data: ExtensionDataValue) -> InstallResult: + """Execute webExtension.install (internal, unsupported).""" + params = InstallParameters(extension_data=extension_data) + return self._execute("webExtension.install", params=params, result=InstallResult) + + def uninstall(self, extension: str) -> Any: + """Execute webExtension.uninstall (internal, unsupported).""" + params = UninstallParameters(extension=extension) + return self._execute("webExtension.uninstall", params=params, result=None) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index f3e95859276cc..0ee7e08b01c53 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -183,6 +183,7 @@ def create_matches(options: list[BaseOptions]) -> dict: if TYPE_CHECKING: + from selenium.webdriver._bidi.transport import Transport from selenium.webdriver.common.api_request_context import APIRequestContext from selenium.webdriver.common.fedcm.dialog import Dialog from selenium.webdriver.common.print_page_options import PrintOptions @@ -301,6 +302,7 @@ def __init__( self._fedcm = FedCM(self) self._websocket_connection: WebSocketConnection | None = None + self._bidi_transport: Transport | None = None self._script: Script | None = None self._network: Network | None = None self._browser: Browser | None = None @@ -642,6 +644,7 @@ def quit(self) -> None: if self._websocket_connection is not None: self._websocket_connection.close() self._websocket_connection = None + self._bidi_transport = None self.execute(Command.QUIT) finally: if self._request is not None: @@ -1219,6 +1222,10 @@ def _start_bidi(self) -> None: self.command_executor.client_config.websocket_timeout, self.command_executor.client_config.websocket_interval, ) + # Imported lazily: only BiDi sessions pay for loading the generated _bidi package. + from selenium.webdriver._bidi.transport import Transport + + self._bidi_transport = Transport(self._websocket_connection) @property def network(self) -> Network: diff --git a/py/test/selenium/webdriver/common/bidi_protocol_tests.py b/py/test/selenium/webdriver/common/bidi_protocol_tests.py new file mode 100644 index 0000000000000..8450a2934c9b6 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_protocol_tests.py @@ -0,0 +1,64 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""End-to-end checks for the generated `selenium.webdriver._bidi` layer. + +These drive the generated protocol code directly against a real browser — the +one thing the serialization unit tests structurally cannot do, since a +fixture only ever confirms our model against itself. What a browser adds is +proof that the schema-derived field names, nesting, discriminators, and types +match what the browser actually sends, and that the strict inbound deserializer +accepts real payloads. Coverage is by wire *shape*, not by domain (the machinery +is uniform): a plain command result and a deeply nested union/record result. + +Event delivery is not covered here: the layer speaks commands (request/response) +only. Routing pushed events into their generated types is the facade piece ADR +17701 keeps out of scope, so there is nothing in `_bidi` to exercise yet. +""" + +from selenium.webdriver._bidi.browsing_context import BrowsingContext, CreateResult, CreateType, GetTreeResult, Info + + +def test_create_result_round_trips(driver): + """A plain command result deserializes into its generated value object.""" + context = BrowsingContext(driver) + + result = context.create(type=CreateType.TAB) + + assert isinstance(result, CreateResult) + assert isinstance(result.context, str) + assert result.context + + context.close(result.context) + + +def test_get_tree_deserializes_nested_records(driver, pages): + """A nested list[Info] result deserializes end-to-end from the browser.""" + driver.get(pages.url("simpleTest.html")) + + tree = BrowsingContext(driver).get_tree() + + assert isinstance(tree, GetTreeResult) + assert tree.contexts, "expected at least one browsing context" + + top = tree.contexts[0] + assert isinstance(top, Info) + assert isinstance(top.context, str) + assert top.context + assert isinstance(top.url, str) + # children is required-nullable: a real browser sends a list or null, never omits it. + assert top.children is None or all(isinstance(child, Info) for child in top.children) diff --git a/py/test/unit/selenium/webdriver/common/bidi_protocol_command_tests.py b/py/test/unit/selenium/webdriver/common/bidi_protocol_command_tests.py new file mode 100644 index 0000000000000..3e0374bbf1192 --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/bidi_protocol_command_tests.py @@ -0,0 +1,151 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Unit tests for the generated BiDi command surface. + +Drives the actual generated domain classes through a stand-in transport — no +browser — to prove the schema-derived methods build and serialize their params, +validate enum/discriminator arguments before any wire call, dispatch to the +right command, and parse the reply into the generated result type. The runtime +itself is covered by bidi_serialization_tests; here the subject is the generated +code that sits on top of it. +""" + +import pytest + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver._bidi.browsing_context import BrowsingContext, CreateResult, CreateType, GetTreeResult, Info +from selenium.webdriver._bidi.network import InterceptPhase, Network +from selenium.webdriver._bidi.serialization import BiDiSerializationError +from selenium.webdriver._bidi.transport import Transport + + +class DrivingConnection: + """Drives the command coroutine with WebSocketConnection's next()/send() protocol, minus the socket. + + Records the outbound frame (or leaves it None if no command was ever sent) + and returns a canned reply. + """ + + def __init__(self, reply=None): + self.reply = reply + self.sent = None + + def execute(self, command): + self.sent = next(command) + try: + command.send(self.reply) + except StopIteration as done: + return done.value + raise AssertionError("command coroutine did not terminate") + + +def _domain(cls, reply=None): + connection = DrivingConnection(reply=reply) + return cls(Transport(connection)), connection + + +# --- dispatch + result parsing --- + + +def test_a_generated_command_sends_its_frame_and_parses_its_result(): + context, connection = _domain(BrowsingContext, reply={"context": "ctx-1"}) + + result = context.create(type=CreateType.TAB) + + assert connection.sent == {"method": "browsingContext.create", "params": {"type": "tab"}} + assert result == CreateResult(context="ctx-1") + + +def test_a_nested_union_result_is_deserialized_into_generated_types(): + reply = { + "contexts": [ + { + "children": None, + "clientWindow": "w1", + "context": "c1", + "originalOpener": None, + "url": "about:blank", + "userContext": "default", + } + ] + } + context, _ = _domain(BrowsingContext, reply=reply) + + tree = context.get_tree() + + assert isinstance(tree, GetTreeResult) + assert isinstance(tree.contexts[0], Info) + assert tree.contexts[0].context == "c1" + + +# --- argument validation happens before any wire call --- + + +def test_a_scalar_enum_argument_outside_the_set_raises_before_any_wire_call(): + context, connection = _domain(BrowsingContext) + + with pytest.raises(BiDiSerializationError, match=r"not a valid CreateType"): + context.create(type="sideways") + + assert connection.sent is None + + +def test_each_element_of_a_list_enum_argument_is_validated(): + network, connection = _domain(Network) + + with pytest.raises(BiDiSerializationError, match=r"not a valid InterceptPhase"): + network.add_intercept(phases=[InterceptPhase.AUTH_REQUIRED, "nope"]) + + assert connection.sent is None + + +def test_a_union_discriminator_outside_the_set_raises_before_any_wire_call(): + network, connection = _domain(Network) + + with pytest.raises(BiDiSerializationError, match=r"not a valid discriminator"): + network.continue_with_auth(request="r", action="bogus") + + assert connection.sent is None + + +# --- valid arguments serialize and dispatch --- + + +def test_a_valid_list_enum_argument_is_serialized_to_wire_tokens(): + network, connection = _domain(Network, reply={"intercept": "i-1"}) + + network.add_intercept(phases=[InterceptPhase.AUTH_REQUIRED]) + + assert connection.sent["method"] == "network.addIntercept" + assert connection.sent["params"]["phases"] == ["authRequired"] + + +def test_a_union_discriminator_selects_and_sends_its_variant(): + network, connection = _domain(Network, reply={}) + + network.continue_with_auth(request="r", action="cancel") + + assert connection.sent == {"method": "network.continueWithAuth", "params": {"request": "r", "action": "cancel"}} + + +# --- construction --- + + +def test_a_generated_domain_requires_a_driver_or_transport(): + with pytest.raises(WebDriverException, match=r"a WebDriver or Transport is required"): + BrowsingContext(object()) diff --git a/py/test/unit/selenium/webdriver/common/bidi_serialization_tests.py b/py/test/unit/selenium/webdriver/common/bidi_serialization_tests.py new file mode 100644 index 0000000000000..c6c4301cec925 --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/bidi_serialization_tests.py @@ -0,0 +1,396 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Unit tests for the hand-written BiDi serialization runtime. + +Exercises `selenium.webdriver._bidi.serialization` — the Record/Union/UNSET +machinery every generated module builds on — in isolation, using small value +types declared here the way the generator emits them. The generated modules are +covered by the command-surface and browser round-trip tests; the subject here is +the runtime's own rules: strict inbound validation, UNSET omit-vs-null, and union +dispatch. +""" + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any + +import pytest + +from selenium.webdriver._bidi.serialization import ( + UNSET, + BiDiSerializationError, + Record, + Union, + UnsetType, + meta, + register, + resolve, +) + +# --- fixtures: value types declared the way the generator emits them --- + + +@register("test.Color") +class Color(str, Enum): + RED = "red" + BLUE = "blue" + + +@register("test.Point") +@dataclass(frozen=True) +class Point(Record): + x: int = field(metadata=meta("x", required=True, primitive="int")) + y: int = field(metadata=meta("y", required=True, primitive="int")) + + +@register("test.Optionals") +@dataclass(frozen=True) +class Optionals(Record): + req: str = field(metadata=meta("req", required=True, primitive="str")) + nullable: str | None = field(metadata=meta("nullable", required=True, nullable=True, primitive="str")) + opt: str | UnsetType = field(default=UNSET, metadata=meta("opt", primitive="str")) + + +@register("test.Scalars") +@dataclass(frozen=True) +class Scalars(Record): + count: int = field(metadata=meta("count", required=True, primitive="int")) + ratio: float = field(metadata=meta("ratio", required=True, primitive="float")) + flag: bool = field(metadata=meta("flag", required=True, primitive="bool")) + name: str = field(metadata=meta("name", required=True, primitive="str")) + + +@register("test.Painted") +@dataclass(frozen=True) +class Painted(Record): + color: Color = field(metadata=meta("color", required=True, enum="test.Color")) + + +@register("test.Tags") +@dataclass(frozen=True) +class Tags(Record): + tags: list[str] = field(metadata=meta("tags", required=True, is_list=True, primitive="str")) + + +@register("test.Line") +@dataclass(frozen=True) +class Line(Record): + start: Point = field(metadata=meta("start", required=True, ref="test.Point")) + + +@register("test.Path") +@dataclass(frozen=True) +class Path(Record): + points: list[Point] = field(metadata=meta("points", required=True, ref="test.Point", is_list=True)) + + +@register("test.Extensible") +@dataclass(frozen=True) +class Extensible(Record): + _EXTENSIBLE = True + + known: str = field(metadata=meta("known", required=True, primitive="str")) + extensions: dict[str, Any] | UnsetType = field(default=UNSET, metadata=meta("extensions")) + + +@register("test.Cat") +@dataclass(frozen=True) +class Cat(Record): + meow: str = field(metadata=meta("meow", required=True, primitive="str")) + kind: str = field(default="cat", init=False, metadata=meta("kind", required=True, fixed="cat")) + + +@register("test.Dog") +@dataclass(frozen=True) +class Dog(Record): + bark: str = field(metadata=meta("bark", required=True, primitive="str")) + kind: str = field(default="dog", init=False, metadata=meta("kind", required=True, fixed="dog")) + + +@register("test.Animal") +class Animal(Union): + _DISCRIMINATOR = "kind" + _VARIANTS = {"cat": "test.Cat", "dog": "test.Dog"} + _DISCRIMINATOR_VALUES = frozenset({"cat", "dog"}) + + +@register("test.Fallback") +class Fallback(Union): + _DISCRIMINATOR = "kind" + _VARIANTS = {"cat": "test.Cat"} + _FALLBACK = "test.Dog" + _DISCRIMINATOR_VALUES = frozenset({"cat"}) + + +@register("test.Circle") +@dataclass(frozen=True) +class Circle(Record): + radius: int = field(metadata=meta("radius", required=True, primitive="int")) + + +@register("test.Rect") +@dataclass(frozen=True) +class Rect(Record): + width: int = field(metadata=meta("width", required=True, primitive="int")) + height: int = field(metadata=meta("height", required=True, primitive="int")) + + +@register("test.Shape") +class Shape(Union): + _PRESENCE = (("test.Circle", ("radius",)), ("test.Rect", ("width", "height"))) + + +@register("test.BareOrObject") +class BareOrObject(Union): + _DISCRIMINATOR = "type" + _VARIANTS = {} + + +# --- UNSET sentinel --- + + +def test_unset_is_a_singleton(): + assert UnsetType() is UNSET + + +def test_unset_is_falsy(): + assert not UNSET + + +def test_unset_repr(): + assert repr(UNSET) == "UNSET" + + +# --- registry --- + + +def test_resolve_returns_the_registered_class(): + assert resolve("test.Point") is Point + + +def test_resolve_unknown_raises(): + with pytest.raises(BiDiSerializationError, match=r"unknown BiDi type 'test.Nope'"): + resolve("test.Nope") + + +# --- outbound: as_json --- + + +def test_as_json_maps_field_names_to_wire_keys(): + assert Point(x=1, y=2).as_json() == {"x": 1, "y": 2} + + +def test_as_json_omits_unset_but_emits_explicit_null_for_nullable(): + assert Optionals(req="r", nullable=None).as_json() == {"req": "r", "nullable": None} + + +def test_round_trips_through_the_wire(): + assert Point.from_json(Point(x=1, y=2).as_json()) == Point(x=1, y=2) + + +# --- inbound: required / optional / null --- + + +def test_missing_required_field_raises(): + with pytest.raises(BiDiSerializationError, match=r"Point.y missing required 'y'"): + Point.from_json({"x": 1}) + + +def test_missing_optional_field_becomes_unset(): + assert Optionals.from_json({"req": "r", "nullable": "n"}).opt is UNSET + + +def test_explicit_null_for_a_nullable_field_becomes_none(): + assert Optionals.from_json({"req": "r", "nullable": None}).nullable is None + + +def test_explicit_null_for_a_non_nullable_field_raises(): + with pytest.raises(BiDiSerializationError, match=r"Point.x received null but is not nullable"): + Point.from_json({"x": None, "y": 2}) + + +# --- construction-time validation (__post_init__) --- + + +def test_constructing_with_none_for_a_non_nullable_field_raises(): + with pytest.raises(BiDiSerializationError, match=r"Point.x cannot be None"): + Point(x=None, y=2) + + +def test_constructing_with_an_invalid_enum_value_raises(): + with pytest.raises(BiDiSerializationError, match=r"is not a valid Color"): + Painted(color="green") + + +def test_constructing_with_a_valid_enum_member_is_accepted(): + assert Painted(color=Color.RED).as_json() == {"color": "red"} + + +# --- strict primitive checks (inbound) --- + + +def test_integer_rejects_a_fractional_float(): + with pytest.raises(BiDiSerializationError, match=r"expected int"): + Scalars.from_json({"count": 1.5, "ratio": 1.0, "flag": True, "name": "n"}) + + +def test_integer_rejects_even_a_whole_valued_float(): + with pytest.raises(BiDiSerializationError, match=r"expected int"): + Scalars.from_json({"count": 5.0, "ratio": 1.0, "flag": True, "name": "n"}) + + +def test_integer_rejects_a_bool(): + with pytest.raises(BiDiSerializationError, match=r"expected int"): + Scalars.from_json({"count": True, "ratio": 1.0, "flag": True, "name": "n"}) + + +def test_number_accepts_both_int_and_float(): + assert Scalars.from_json({"count": 1, "ratio": 2, "flag": True, "name": "n"}).ratio == 2 + assert Scalars.from_json({"count": 1, "ratio": 2.5, "flag": True, "name": "n"}).ratio == 2.5 + + +def test_number_rejects_a_bool(): + with pytest.raises(BiDiSerializationError, match=r"expected float"): + Scalars.from_json({"count": 1, "ratio": True, "flag": True, "name": "n"}) + + +def test_bool_accepts_a_bool_but_rejects_an_int(): + assert Scalars.from_json({"count": 1, "ratio": 1.0, "flag": False, "name": "n"}).flag is False + with pytest.raises(BiDiSerializationError, match=r"expected bool"): + Scalars.from_json({"count": 1, "ratio": 1.0, "flag": 1, "name": "n"}) + + +def test_string_rejects_a_number(): + with pytest.raises(BiDiSerializationError, match=r"expected str"): + Scalars.from_json({"count": 1, "ratio": 1.0, "flag": True, "name": 3}) + + +# --- list vs scalar shape --- + + +def test_a_scalar_for_a_list_field_raises(): + with pytest.raises(BiDiSerializationError, match=r"expected a list"): + Tags.from_json({"tags": "a"}) + + +def test_a_list_for_a_scalar_field_raises(): + with pytest.raises(BiDiSerializationError, match=r"expected a single value"): + Point.from_json({"x": [1], "y": 2}) + + +def test_a_list_of_primitives_round_trips(): + assert Tags.from_json({"tags": ["a", "b"]}).tags == ["a", "b"] + + +# --- nested refs --- + + +def test_a_nested_record_round_trips(): + line = Line.from_json({"start": {"x": 1, "y": 2}}) + assert line.start == Point(x=1, y=2) + assert line.as_json() == {"start": {"x": 1, "y": 2}} + + +def test_a_non_object_for_a_record_ref_raises(): + with pytest.raises(BiDiSerializationError, match=r"Line.start: expected an object"): + Line.from_json({"start": "nope"}) + + +def test_a_list_of_records_round_trips(): + path = Path.from_json({"points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}]}) + assert path.points == [Point(x=1, y=2), Point(x=3, y=4)] + + +# --- enum inbound --- + + +def test_an_inbound_enum_token_is_restored_to_its_member(): + assert Painted.from_json({"color": "blue"}).color is Color.BLUE + + +def test_an_inbound_enum_value_outside_the_schema_raises(): + with pytest.raises(BiDiSerializationError, match=r"is not a valid Color"): + Painted.from_json({"color": "green"}) + + +# --- extensible records --- + + +def test_an_extensible_record_captures_unknown_keys(): + assert Extensible.from_json({"known": "k", "extra": "e", "more": 1}).extensions == {"extra": "e", "more": 1} + + +def test_an_extensible_record_merges_captured_keys_back_on_serialization(): + assert Extensible(known="k", extensions={"extra": "e"}).as_json() == {"known": "k", "extra": "e"} + + +def test_a_non_extensible_record_ignores_unknown_keys(): + assert Point.from_json({"x": 1, "y": 2, "z": 3}) == Point(x=1, y=2) + + +# --- union dispatch: inbound --- + + +def test_a_discriminated_union_dispatches_by_its_tag(): + assert Animal.from_json({"kind": "cat", "meow": "hi"}) == Cat(meow="hi") + + +def test_a_structural_union_dispatches_by_which_fields_are_present(): + assert Shape.from_json({"radius": 5}) == Circle(radius=5) + assert Shape.from_json({"width": 3, "height": 4}) == Rect(width=3, height=4) + + +def test_an_unknown_discriminator_falls_back_to_the_declared_variant(): + assert Fallback.from_json({"kind": "fish", "bark": "woof"}) == Dog(bark="woof") + + +def test_a_bare_scalar_arm_is_returned_unchanged(): + assert BareOrObject.from_json("viewport") == "viewport" + + +def test_a_variant_outside_the_schema_raises_instead_of_passing_through(): + with pytest.raises(BiDiSerializationError, match=r"not in this Selenium's BiDi schema"): + Animal.from_json({"kind": "fish"}) + + +# --- union dispatch: outbound (build) --- + + +def test_build_selects_a_variant_by_its_discriminator_and_bakes_the_tag(): + animal = Animal.build(kind="cat", meow="hi") + assert animal == Cat(meow="hi") + assert animal.kind == "cat" + + +def test_build_selects_a_variant_by_which_fields_are_supplied(): + assert Shape.build(radius=5) == Circle(radius=5) + + +def test_build_rejects_a_discriminator_value_outside_the_allowed_set(): + with pytest.raises(BiDiSerializationError, match=r"not a valid discriminator"): + Animal.build(kind="fish", meow="hi") + + +def test_build_rejects_a_field_that_does_not_belong_to_the_selected_variant(): + with pytest.raises(BiDiSerializationError, match=r"invalid combination.*bark"): + Animal.build(kind="cat", bark="woof") + + +def test_build_raises_when_no_variant_matches(): + with pytest.raises(BiDiSerializationError, match=r"no Shape variant matches"): + Shape.build(depth=5) diff --git a/py/test/unit/selenium/webdriver/common/bidi_transport_tests.py b/py/test/unit/selenium/webdriver/common/bidi_transport_tests.py new file mode 100644 index 0000000000000..590afb351fbce --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/bidi_transport_tests.py @@ -0,0 +1,150 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Unit tests for the hand-written BiDi transport seam. + +Exercises `selenium.webdriver._bidi.transport` — the `Transport` that turns a +command into a wire frame and back, and the `Domain` base every generated module +subclasses. The websocket is stood in by `DrivingConnection`, which drives the +command coroutine with the exact next()/send() protocol `WebSocketConnection` +uses, so the seam is tested against the real contract rather than a mock of it. +""" + +from dataclasses import dataclass, field + +import pytest + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver._bidi.serialization import Record, meta, register +from selenium.webdriver._bidi.transport import Domain, Transport + + +@register("test_transport.Params") +@dataclass(frozen=True) +class Params(Record): + context: str = field(metadata=meta("context", required=True, primitive="str")) + + +@register("test_transport.Result") +@dataclass(frozen=True) +class Result(Record): + value: str = field(metadata=meta("value", required=True, primitive="str")) + + +class DrivingConnection: + """Drives the command coroutine with WebSocketConnection's next()/send() protocol, minus the socket. + + Records the outbound frame and returns a canned reply, so the seam is tested + against the real coroutine contract rather than a mock of it. + """ + + def __init__(self, reply=None): + self.reply = reply + self.sent = None + + def execute(self, command): + self.sent = next(command) + try: + command.send(self.reply) + except StopIteration as done: + return done.value + raise AssertionError("command coroutine did not terminate") + + +class DriverWithTransport: + def __init__(self, transport): + self._bidi_transport = transport + + +class DriverThatStartsBiDi: + def __init__(self, transport): + self._transport = transport + self._bidi_transport = None + self.started = False + + def _start_bidi(self): + self.started = True + self._bidi_transport = self._transport + + +# --- Transport --- + + +def test_execute_sends_the_method_and_serialized_params(): + connection = DrivingConnection(reply={"value": "ok"}) + + result = Transport(connection).execute("some.command", params=Params(context="c"), result=Result) + + assert connection.sent == {"method": "some.command", "params": {"context": "c"}} + assert result == Result(value="ok") + + +def test_execute_with_no_params_sends_an_empty_params_object(): + connection = DrivingConnection(reply={"value": "ok"}) + + Transport(connection).execute("some.command", result=Result) + + assert connection.sent == {"method": "some.command", "params": {}} + + +def test_execute_with_no_result_type_returns_the_raw_reply(): + connection = DrivingConnection(reply={"anything": 1}) + + result = Transport(connection).execute("some.command", params=Params(context="c")) + + assert result == {"anything": 1} + + +# --- Domain seam --- + + +def test_domain_accepts_a_transport_directly(): + connection = DrivingConnection(reply={"value": "ok"}) + + domain = Domain(Transport(connection)) + + assert domain._execute("cmd", params=Params(context="c"), result=Result) == Result(value="ok") + + +def test_domain_reuses_the_drivers_shared_transport(): + transport = Transport(DrivingConnection(reply={"value": "ok"})) + + domain = Domain(DriverWithTransport(transport)) + + assert domain._transport is transport + + +def test_domain_starts_bidi_when_the_transport_is_not_open_yet(): + transport = Transport(DrivingConnection(reply={"value": "ok"})) + driver = DriverThatStartsBiDi(transport) + + domain = Domain(driver) + + assert driver.started + assert domain._transport is transport + assert domain._execute("cmd", params=Params(context="c"), result=Result) == Result(value="ok") + + +def test_domain_without_a_transport_or_a_way_to_start_one_raises(): + with pytest.raises(WebDriverException, match=r"a WebDriver or Transport is required"): + Domain(object()) + + +def test_domains_from_the_same_driver_share_its_transport(): + driver = DriverWithTransport(Transport(DrivingConnection())) + + assert Domain(driver)._transport is Domain(driver)._transport