Skip to content

Commit f4ae779

Browse files
committed
add bidi script commands
1 parent 523fbc8 commit f4ae779

File tree

1 file changed

+372
-0
lines changed

1 file changed

+372
-0
lines changed

py/selenium/webdriver/common/bidi/script.py

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,207 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
from dataclasses import dataclass
19+
from typing import List, Optional
20+
21+
from selenium.webdriver.common.bidi.common import command_builder
22+
1823
from .log import LogEntryAdded
1924
from .session import Session
2025

2126

27+
class ResultOwnership:
28+
"""Represents the possible result ownership types."""
29+
30+
NONE = "none"
31+
ROOT = "root"
32+
33+
34+
class RealmType:
35+
"""Represents the possible realm types."""
36+
37+
WINDOW = "window"
38+
DEDICATED_WORKER = "dedicated-worker"
39+
SHARED_WORKER = "shared-worker"
40+
SERVICE_WORKER = "service-worker"
41+
WORKER = "worker"
42+
PAINT_WORKLET = "paint-worklet"
43+
AUDIO_WORKLET = "audio-worklet"
44+
WORKLET = "worklet"
45+
46+
47+
@dataclass
48+
class RealmInfo:
49+
"""Represents information about a realm."""
50+
51+
realm: str
52+
origin: str
53+
type: str
54+
context: Optional[str] = None
55+
sandbox: Optional[str] = None
56+
57+
@classmethod
58+
def from_json(cls, json: dict) -> "RealmInfo":
59+
"""Creates a RealmInfo instance from a dictionary.
60+
61+
Parameters:
62+
-----------
63+
json: A dictionary containing the realm information.
64+
65+
Returns:
66+
-------
67+
RealmInfo: A new instance of RealmInfo.
68+
"""
69+
return cls(
70+
realm=json.get("realm"),
71+
origin=json.get("origin"),
72+
type=json.get("type"),
73+
context=json.get("context"),
74+
sandbox=json.get("sandbox"),
75+
)
76+
77+
78+
@dataclass
79+
class Source:
80+
"""Represents the source of a script message."""
81+
82+
realm: str
83+
context: Optional[str] = None
84+
85+
@classmethod
86+
def from_json(cls, json: dict) -> "Source":
87+
"""Creates a Source instance from a dictionary.
88+
89+
Parameters:
90+
-----------
91+
json: A dictionary containing the source information.
92+
93+
Returns:
94+
-------
95+
Source: A new instance of Source.
96+
"""
97+
return cls(
98+
realm=json.get("realm"),
99+
context=json.get("context"),
100+
)
101+
102+
103+
@dataclass
104+
class EvaluateResult:
105+
"""Represents the result of script evaluation."""
106+
107+
realm: str
108+
result: Optional[dict] = None
109+
exception_details: Optional[dict] = None
110+
111+
@classmethod
112+
def from_json(cls, json: dict) -> "EvaluateResult":
113+
"""Creates an EvaluateResult instance from a dictionary.
114+
115+
Parameters:
116+
-----------
117+
json: A dictionary containing the evaluation result.
118+
119+
Returns:
120+
-------
121+
EvaluateResult: A new instance of EvaluateResult.
122+
"""
123+
return cls(
124+
realm=json.get("realm"),
125+
result=json.get("result"),
126+
exception_details=json.get("exceptionDetails"),
127+
)
128+
129+
130+
class ScriptMessage:
131+
"""Represents a script message event."""
132+
133+
event_class = "script.message"
134+
135+
def __init__(self, channel: str, data: dict, source: Source):
136+
self.channel = channel
137+
self.data = data
138+
self.source = source
139+
140+
@classmethod
141+
def from_json(cls, json: dict) -> "ScriptMessage":
142+
"""Creates a ScriptMessage instance from a dictionary.
143+
144+
Parameters:
145+
-----------
146+
json: A dictionary containing the script message.
147+
148+
Returns:
149+
-------
150+
ScriptMessage: A new instance of ScriptMessage.
151+
"""
152+
return cls(
153+
channel=json.get("channel"),
154+
data=json.get("data"),
155+
source=Source.from_json(json.get("source", {})),
156+
)
157+
158+
159+
class RealmCreated:
160+
"""Represents a realm created event."""
161+
162+
event_class = "script.realmCreated"
163+
164+
def __init__(self, realm_info: RealmInfo):
165+
self.realm_info = realm_info
166+
167+
@classmethod
168+
def from_json(cls, json: dict) -> "RealmCreated":
169+
"""Creates a RealmCreated instance from a dictionary.
170+
171+
Parameters:
172+
-----------
173+
json: A dictionary containing the realm created event.
174+
175+
Returns:
176+
-------
177+
RealmCreated: A new instance of RealmCreated.
178+
"""
179+
return cls(realm_info=RealmInfo.from_json(json))
180+
181+
182+
class RealmDestroyed:
183+
"""Represents a realm destroyed event."""
184+
185+
event_class = "script.realmDestroyed"
186+
187+
def __init__(self, realm: str):
188+
self.realm = realm
189+
190+
@classmethod
191+
def from_json(cls, json: dict) -> "RealmDestroyed":
192+
"""Creates a RealmDestroyed instance from a dictionary.
193+
194+
Parameters:
195+
-----------
196+
json: A dictionary containing the realm destroyed event.
197+
198+
Returns:
199+
-------
200+
RealmDestroyed: A new instance of RealmDestroyed.
201+
"""
202+
return cls(realm=json.get("realm"))
203+
204+
22205
class Script:
206+
"""BiDi implementation of the script module."""
207+
208+
EVENTS = {
209+
"message": "script.message",
210+
"realm_created": "script.realmCreated",
211+
"realm_destroyed": "script.realmDestroyed",
212+
}
213+
23214
def __init__(self, conn):
24215
self.conn = conn
25216
self.log_entry_subscribed = False
217+
self.subscriptions = {}
218+
self.callbacks = {}
26219

27220
def add_console_message_handler(self, handler):
28221
self._subscribe_to_log_entries()
@@ -38,6 +231,185 @@ def remove_console_message_handler(self, id):
38231

39232
remove_javascript_error_handler = remove_console_message_handler
40233

234+
def add_preload_script(
235+
self,
236+
function_declaration: str,
237+
arguments: Optional[List[dict]] = None,
238+
contexts: Optional[List[str]] = None,
239+
user_contexts: Optional[List[str]] = None,
240+
sandbox: Optional[str] = None,
241+
) -> str:
242+
"""Adds a preload script.
243+
244+
Parameters:
245+
-----------
246+
function_declaration: The function declaration to preload.
247+
arguments: The arguments to pass to the function.
248+
contexts: The browsing context IDs to apply the script to.
249+
user_contexts: The user context IDs to apply the script to.
250+
sandbox: The sandbox name to apply the script to.
251+
252+
Returns:
253+
-------
254+
str: The preload script ID.
255+
256+
Raises:
257+
------
258+
ValueError: If both contexts and user_contexts are provided.
259+
"""
260+
if contexts is not None and user_contexts is not None:
261+
raise ValueError("Cannot specify both contexts and user_contexts")
262+
263+
params = {"functionDeclaration": function_declaration}
264+
265+
if arguments is not None:
266+
params["arguments"] = arguments
267+
if contexts is not None:
268+
params["contexts"] = contexts
269+
if user_contexts is not None:
270+
params["userContexts"] = user_contexts
271+
if sandbox is not None:
272+
params["sandbox"] = sandbox
273+
274+
result = self.conn.execute(command_builder("script.addPreloadScript", params))
275+
return result["script"]
276+
277+
def remove_preload_script(self, script_id: str) -> None:
278+
"""Removes a preload script.
279+
280+
Parameters:
281+
-----------
282+
script_id: The preload script ID to remove.
283+
"""
284+
params = {"script": script_id}
285+
self.conn.execute(command_builder("script.removePreloadScript", params))
286+
287+
def disown(self, handles: List[str], target: dict) -> None:
288+
"""Disowns the given handles.
289+
290+
Parameters:
291+
-----------
292+
handles: The handles to disown.
293+
target: The target realm or context.
294+
"""
295+
params = {
296+
"handles": handles,
297+
"target": target,
298+
}
299+
self.conn.execute(command_builder("script.disown", params))
300+
301+
def call_function(
302+
self,
303+
function_declaration: str,
304+
await_promise: bool,
305+
target: dict,
306+
arguments: Optional[List[dict]] = None,
307+
result_ownership: Optional[str] = None,
308+
serialization_options: Optional[dict] = None,
309+
this: Optional[dict] = None,
310+
user_activation: bool = False,
311+
) -> EvaluateResult:
312+
"""Calls a provided function with given arguments in a given realm.
313+
314+
Parameters:
315+
-----------
316+
function_declaration: The function declaration to call.
317+
await_promise: Whether to await promise resolution.
318+
target: The target realm or context.
319+
arguments: The arguments to pass to the function.
320+
result_ownership: The result ownership type.
321+
serialization_options: The serialization options.
322+
this: The 'this' value for the function call.
323+
user_activation: Whether to trigger user activation.
324+
325+
Returns:
326+
-------
327+
EvaluateResult: The result of the function call.
328+
"""
329+
params = {
330+
"functionDeclaration": function_declaration,
331+
"awaitPromise": await_promise,
332+
"target": target,
333+
"userActivation": user_activation,
334+
}
335+
336+
if arguments is not None:
337+
params["arguments"] = arguments
338+
if result_ownership is not None:
339+
params["resultOwnership"] = result_ownership
340+
if serialization_options is not None:
341+
params["serializationOptions"] = serialization_options
342+
if this is not None:
343+
params["this"] = this
344+
345+
result = self.conn.execute(command_builder("script.callFunction", params))
346+
return EvaluateResult.from_json(result)
347+
348+
def evaluate(
349+
self,
350+
expression: str,
351+
target: dict,
352+
await_promise: bool,
353+
result_ownership: Optional[str] = None,
354+
serialization_options: Optional[dict] = None,
355+
user_activation: bool = False,
356+
) -> EvaluateResult:
357+
"""Evaluates a provided script in a given realm.
358+
359+
Parameters:
360+
-----------
361+
expression: The script expression to evaluate.
362+
target: The target realm or context.
363+
await_promise: Whether to await promise resolution.
364+
result_ownership: The result ownership type.
365+
serialization_options: The serialization options.
366+
user_activation: Whether to trigger user activation.
367+
368+
Returns:
369+
-------
370+
EvaluateResult: The result of the script evaluation.
371+
"""
372+
params = {
373+
"expression": expression,
374+
"target": target,
375+
"awaitPromise": await_promise,
376+
"userActivation": user_activation,
377+
}
378+
379+
if result_ownership is not None:
380+
params["resultOwnership"] = result_ownership
381+
if serialization_options is not None:
382+
params["serializationOptions"] = serialization_options
383+
384+
result = self.conn.execute(command_builder("script.evaluate", params))
385+
return EvaluateResult.from_json(result)
386+
387+
def get_realms(
388+
self,
389+
context: Optional[str] = None,
390+
type: Optional[str] = None,
391+
) -> List[RealmInfo]:
392+
"""Returns a list of all realms, optionally filtered.
393+
394+
Parameters:
395+
-----------
396+
context: The browsing context ID to filter by.
397+
type: The realm type to filter by.
398+
399+
Returns:
400+
-------
401+
List[RealmInfo]: A list of realm information.
402+
"""
403+
params = {}
404+
405+
if context is not None:
406+
params["context"] = context
407+
if type is not None:
408+
params["type"] = type
409+
410+
result = self.conn.execute(command_builder("script.getRealms", params))
411+
return [RealmInfo.from_json(realm) for realm in result["realms"]]
412+
41413
def _subscribe_to_log_entries(self):
42414
if not self.log_entry_subscribed:
43415
session = Session(self.conn)

0 commit comments

Comments
 (0)