commit 0a286614a007ed84773faf0806aaa6b15e7f9d2f
parent 069c2bb8fe5cb0d8819e84512a127c932a4bc2ee
Author: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com>
Date: Fri, 10 Oct 2025 07:49:13 +0000
Bug 1992761 [wpt PR 54351] - [wdspec] introduce `Maybe` and `Nullable` types, a=testonly
Automatic update from web-platform-tests
[wdspec] introduce `Maybe` and `Nullable` types (#54351)
* In order to avoid confusion, add `Maybe` and `Nullable` types for WebDriver BiDi parameters.
* And do not use `None` for missing parameter in `Emulation` module.
Out of scope: updating of the other modules.
Context: https://github.com/w3c/webdriver-bidi/issues/642
--
wpt-commits: 06549edf764586a7d0b9557d48f7e4a586d29140
wpt-pr: 54351
Diffstat:
4 files changed, 136 insertions(+), 97 deletions(-)
diff --git a/testing/web-platform/tests/tools/webdriver/webdriver/bidi/modules/_module.py b/testing/web-platform/tests/tools/webdriver/webdriver/bidi/modules/_module.py
@@ -102,5 +102,53 @@ def to_camelcase(name: str) -> str:
return "".join(parts)
-def remove_undefined(obj: Mapping[str, Any]) -> Mapping[str, Any]:
- return {key: value for key, value in obj.items() if value != UNDEFINED}
+def remove_undefined(obj: Any) -> Any:
+ """
+ Removes entries from a dictionary where the value is UNDEFINED. Also removes
+ UNDEFINED values from lists. Recursively processes nested dictionaries and
+ lists.
+
+ >>> from ..undefined import UNDEFINED
+ >>> remove_undefined({"a": 1, "b": UNDEFINED, "c": 3})
+ {'a': 1, 'c': 3}
+
+ >>> remove_undefined({"a": 1, "b": {"x": UNDEFINED, "y": 2}, "c": UNDEFINED})
+ {'a': 1, 'b': {'y': 2}}
+
+ >>> remove_undefined({"a": 1, "b": [1, UNDEFINED, 3], "c": UNDEFINED})
+ {'a': 1, 'b': [1, 3]}
+
+ >>> remove_undefined({"a": 1, "b": [{"x": UNDEFINED, "y": 2}], "c": UNDEFINED})
+ {'a': 1, 'b': [{'y': 2}]}
+
+ >>> remove_undefined({"a": UNDEFINED, "b": {"x": UNDEFINED}})
+ {'b': {}}
+
+ >>> remove_undefined({})
+ {}
+
+ >>> remove_undefined([])
+ []
+
+ >>> remove_undefined(1)
+ 1
+
+ >>> remove_undefined("foo")
+ 'foo'
+
+ >>> remove_undefined(None)
+
+ """
+ if isinstance(obj, Mapping):
+ new_obj = {}
+ for key, value in obj.items():
+ if value is not UNDEFINED:
+ new_obj[key] = remove_undefined(value)
+ return new_obj
+ elif isinstance(obj, list):
+ new_list = []
+ for item in obj:
+ if item is not UNDEFINED:
+ new_list.append(remove_undefined(item))
+ return new_list
+ return obj
diff --git a/testing/web-platform/tests/tools/webdriver/webdriver/bidi/modules/emulation.py b/testing/web-platform/tests/tools/webdriver/webdriver/bidi/modules/emulation.py
@@ -1,82 +1,64 @@
-from typing import Any, Dict, List, Literal, Mapping, MutableMapping, Optional, \
- Union
+from typing import Any, Dict, List, Literal, Mapping
from ._module import BidiModule, command
-from ..undefined import UNDEFINED, Undefined
+from ..undefined import UNDEFINED, Maybe, Nullable
class CoordinatesOptions(Dict[str, Any]):
def __init__(
- self,
- latitude: float,
- longitude: float,
- accuracy: Optional[float] = None,
- altitude: Optional[float] = None,
- altitude_accuracy: Optional[float] = None,
- heading: Optional[float] = None,
- speed: Optional[float] = None,
+ self,
+ latitude: float,
+ longitude: float,
+ accuracy: Maybe[float] = UNDEFINED,
+ altitude: Maybe[Nullable[float]] = UNDEFINED,
+ altitude_accuracy: Maybe[Nullable[float]] = UNDEFINED,
+ heading: Maybe[Nullable[float]] = UNDEFINED,
+ speed: Maybe[Nullable[float]] = UNDEFINED,
):
self["latitude"] = latitude
self["longitude"] = longitude
-
- if accuracy is not None:
- self["accuracy"] = accuracy
- if altitude is not None:
- self["altitude"] = altitude
- if altitude_accuracy is not None:
- self["altitudeAccuracy"] = altitude_accuracy
- if heading is not None:
- self["heading"] = heading
- if speed is not None:
- self["speed"] = speed
+ self["accuracy"] = accuracy
+ self["altitude"] = altitude
+ self["altitudeAccuracy"] = altitude_accuracy
+ self["heading"] = heading
+ self["speed"] = speed
class Emulation(BidiModule):
@command
def set_geolocation_override(
- self,
- coordinates: Union[CoordinatesOptions, Undefined] = UNDEFINED,
- error: Optional[Dict[str, Any]] = None,
- contexts: Optional[List[str]] = None,
- user_contexts: Optional[List[str]] = None,
+ self,
+ coordinates: Maybe[Nullable[CoordinatesOptions]] = UNDEFINED,
+ error: Maybe[Dict[str, Any]] = UNDEFINED,
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
- params: MutableMapping[str, Any] = {}
-
- if coordinates is not UNDEFINED:
- params["coordinates"] = coordinates
- if error is not None:
- params["error"] = error
- if contexts is not None:
- params["contexts"] = contexts
- if user_contexts is not None:
- params["userContexts"] = user_contexts
-
- return params
+ return {
+ "coordinates": coordinates,
+ "error": error,
+ "contexts": contexts,
+ "userContexts": user_contexts
+ }
@command
def set_locale_override(
- self,
- locale: Union[str, None],
- contexts: Optional[List[str]] = None,
- user_contexts: Optional[List[str]] = None,
+ self,
+ locale: Nullable[str],
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
- params: MutableMapping[str, Any] = {
- "locale": locale
+ return {
+ "locale": locale,
+ "contexts": contexts,
+ "userContexts": user_contexts
}
- if contexts is not None:
- params["contexts"] = contexts
- if user_contexts is not None:
- params["userContexts"] = user_contexts
-
- return params
-
@command
def set_scripting_enabled(
self,
- enabled: Literal[False, None],
- contexts: Union[List[str], Undefined] = UNDEFINED,
- user_contexts: Union[List[str], Undefined] = UNDEFINED,
+ enabled: Nullable[Literal[False]],
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
return {
"enabled": enabled,
@@ -86,46 +68,36 @@ class Emulation(BidiModule):
@command
def set_screen_orientation_override(
- self,
- screen_orientation:Dict[str, Any],
- contexts: Optional[List[str]] = None,
- user_contexts: Optional[List[str]] = None,
+ self,
+ screen_orientation: Nullable[Dict[str, Any]],
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
- params: MutableMapping[str, Any] = {
- "screenOrientation": screen_orientation
+ return {
+ "screenOrientation": screen_orientation,
+ "contexts": contexts,
+ "userContexts": user_contexts
}
- if contexts is not None:
- params["contexts"] = contexts
- if user_contexts is not None:
- params["userContexts"] = user_contexts
-
- return params
-
@command
def set_timezone_override(
self,
- timezone: Union[str, None],
- contexts: Optional[List[str]] = None,
- user_contexts: Optional[List[str]] = None,
+ timezone: Nullable[str],
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
- params: MutableMapping[str, Any] = {
- "timezone": timezone
+ return {
+ "timezone": timezone,
+ "contexts": contexts,
+ "userContexts": user_contexts
}
- if contexts is not None:
- params["contexts"] = contexts
- if user_contexts is not None:
- params["userContexts"] = user_contexts
-
- return params
-
@command
def set_user_agent_override(
self,
- user_agent: Union[str, None],
- contexts: Union[List[str], Undefined] = UNDEFINED,
- user_contexts: Union[List[str], Undefined] = UNDEFINED,
+ user_agent: Nullable[str],
+ contexts: Maybe[List[str]] = UNDEFINED,
+ user_contexts: Maybe[List[str]] = UNDEFINED,
) -> Mapping[str, Any]:
return {
"userAgent": user_agent,
diff --git a/testing/web-platform/tests/tools/webdriver/webdriver/bidi/undefined.py b/testing/web-platform/tests/tools/webdriver/webdriver/bidi/undefined.py
@@ -1,6 +1,25 @@
-class Undefined:
- def __init__(self) -> None:
- raise RuntimeError('Import UNDEFINED instead.')
+import enum
+from enum import Enum
+from typing import TypeVar, Union
-UNDEFINED = Undefined.__new__(Undefined)
+class Undefined(Enum):
+ """
+ Class representing special value that indicates that a property is not set.
+ """
+
+ UNDEFINED = enum.auto()
+
+
+UNDEFINED = Undefined.UNDEFINED
+"""A special value that indicates that a property is not set."""
+
+T = TypeVar("T")
+
+#: A type hint for a value that can be of a specific type or UNDEFINED.
+#: For example, ``Maybe[str]`` is equivalent to ``Union[str, Undefined]``.
+Maybe = Union[T, Undefined]
+
+#: A type hint which can have protocol values `null`. Intended to be used
+# instead of `Optional` to avoid confusion.
+Nullable = Union[T, None]
diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/asyncactions.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/asyncactions.py
@@ -176,14 +176,14 @@ class BidiEmulationSetGeolocationOverrideAction:
raise ValueError(
"Params `error` and `coordinates` are mutually exclusive")
- # If `error` is present, set it. Otherwise, do not pass it (error: None).
- # Note, unlike `coordinates`, `error` cannot be `UNDEFINED`. It's either
- # `None` and it's not passed, or some dict value which is passed.
- error = payload['error'] if 'error' in payload else None
- # If `error` is present, do not pass `coordinates` (coordinates: UNDEFINED).
- # Otherwise, remove emulation (coordinates: None).
- coordinates = payload['coordinates'] if 'coordinates' in payload else (
- None if error is None else webdriver.bidi.undefined.UNDEFINED)
+ # If `error` is present, set it. Otherwise, use `UNDEFINED`.
+ error = payload['error'] if 'error' in payload else webdriver.bidi.undefined.UNDEFINED
+ coordinates = webdriver.bidi.undefined.UNDEFINED
+ if 'coordinates' in payload:
+ coordinates = payload['coordinates']
+ elif error is webdriver.bidi.undefined.UNDEFINED:
+ # If `error` is not present, pass `coordinates` of null.
+ coordinates = None
if "contexts" not in payload:
raise ValueError("Missing required parameter: contexts")