Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/remote/webdriver.py
1864 views
1
# Licensed to the Software Freedom Conservancy (SFC) under one
2
# or more contributor license agreements. See the NOTICE file
3
# distributed with this work for additional information
4
# regarding copyright ownership. The SFC licenses this file
5
# to you under the Apache License, Version 2.0 (the
6
# "License"); you may not use this file except in compliance
7
# with the License. You may obtain a copy of the License at
8
#
9
# http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing,
12
# software distributed under the License is distributed on an
13
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
# KIND, either express or implied. See the License for the
15
# specific language governing permissions and limitations
16
# under the License.
17
"""The WebDriver implementation."""
18
19
import base64
20
import contextlib
21
import copy
22
import os
23
import pkgutil
24
import tempfile
25
import types
26
import warnings
27
import zipfile
28
from abc import ABCMeta
29
from base64 import b64decode, urlsafe_b64encode
30
from contextlib import asynccontextmanager, contextmanager
31
from importlib import import_module
32
from typing import Any, Optional, Union, cast
33
34
from selenium.common.exceptions import (
35
InvalidArgumentException,
36
JavascriptException,
37
NoSuchCookieException,
38
NoSuchElementException,
39
WebDriverException,
40
)
41
from selenium.webdriver.common.bidi.browser import Browser
42
from selenium.webdriver.common.bidi.browsing_context import BrowsingContext
43
from selenium.webdriver.common.bidi.emulation import Emulation
44
from selenium.webdriver.common.bidi.input import Input
45
from selenium.webdriver.common.bidi.network import Network
46
from selenium.webdriver.common.bidi.permissions import Permissions
47
from selenium.webdriver.common.bidi.script import Script
48
from selenium.webdriver.common.bidi.session import Session
49
from selenium.webdriver.common.bidi.storage import Storage
50
from selenium.webdriver.common.bidi.webextension import WebExtension
51
from selenium.webdriver.common.by import By
52
from selenium.webdriver.common.fedcm.dialog import Dialog
53
from selenium.webdriver.common.options import ArgOptions, BaseOptions
54
from selenium.webdriver.common.print_page_options import PrintOptions
55
from selenium.webdriver.common.timeouts import Timeouts
56
from selenium.webdriver.common.virtual_authenticator import (
57
Credential,
58
VirtualAuthenticatorOptions,
59
required_virtual_authenticator,
60
)
61
from selenium.webdriver.support.relative_locator import RelativeBy
62
63
from .bidi_connection import BidiConnection
64
from .client_config import ClientConfig
65
from .command import Command
66
from .errorhandler import ErrorHandler
67
from .fedcm import FedCM
68
from .file_detector import FileDetector, LocalFileDetector
69
from .locator_converter import LocatorConverter
70
from .mobile import Mobile
71
from .remote_connection import RemoteConnection
72
from .script_key import ScriptKey
73
from .shadowroot import ShadowRoot
74
from .switch_to import SwitchTo
75
from .webelement import WebElement
76
from .websocket_connection import WebSocketConnection
77
78
cdp = None
79
80
81
def import_cdp():
82
global cdp
83
if not cdp:
84
cdp = import_module("selenium.webdriver.common.bidi.cdp")
85
86
87
def _create_caps(caps):
88
"""Makes a W3C alwaysMatch capabilities object.
89
90
Filters out capability names that are not in the W3C spec. Spec-compliant
91
drivers will reject requests containing unknown capability names.
92
93
Moves the Firefox profile, if present, from the old location to the new Firefox
94
options object.
95
96
Parameters:
97
-----------
98
caps : dict
99
- A dictionary of capabilities requested by the caller.
100
"""
101
caps = copy.deepcopy(caps)
102
always_match = {}
103
for k, v in caps.items():
104
always_match[k] = v
105
return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}}
106
107
108
def get_remote_connection(
109
capabilities: dict,
110
command_executor: Union[str, RemoteConnection],
111
keep_alive: bool,
112
ignore_local_proxy: bool,
113
client_config: Optional[ClientConfig] = None,
114
) -> RemoteConnection:
115
if isinstance(command_executor, str):
116
client_config = client_config or ClientConfig(remote_server_addr=command_executor)
117
client_config.remote_server_addr = command_executor
118
command_executor = RemoteConnection(client_config=client_config)
119
from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection
120
from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection
121
from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection
122
from selenium.webdriver.safari.remote_connection import SafariRemoteConnection
123
124
candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection]
125
handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection)
126
127
if hasattr(command_executor, "client_config") and command_executor.client_config:
128
remote_server_addr = command_executor.client_config.remote_server_addr
129
else:
130
remote_server_addr = command_executor
131
132
return handler(
133
remote_server_addr=remote_server_addr,
134
keep_alive=keep_alive,
135
ignore_proxy=ignore_local_proxy,
136
client_config=client_config,
137
)
138
139
140
def create_matches(options: list[BaseOptions]) -> dict:
141
capabilities: dict[str, Any] = {"capabilities": {}}
142
opts = []
143
for opt in options:
144
opts.append(opt.to_capabilities())
145
opts_size = len(opts)
146
samesies = {}
147
148
# Can not use bitwise operations on the dicts or lists due to
149
# https://bugs.python.org/issue38210
150
for i in range(opts_size):
151
min_index = i
152
if i + 1 < opts_size:
153
first_keys = opts[min_index].keys()
154
155
for kys in first_keys:
156
if kys in opts[i + 1].keys():
157
if opts[min_index][kys] == opts[i + 1][kys]:
158
samesies.update({kys: opts[min_index][kys]})
159
160
always = {}
161
for k, v in samesies.items():
162
always[k] = v
163
164
for opt_dict in opts:
165
for k in always:
166
del opt_dict[k]
167
168
capabilities["capabilities"]["alwaysMatch"] = always
169
capabilities["capabilities"]["firstMatch"] = opts
170
171
return capabilities
172
173
174
class BaseWebDriver(metaclass=ABCMeta):
175
"""Abstract Base Class for all Webdriver subtypes.
176
177
ABC's allow custom implementations of Webdriver to be registered so
178
that isinstance type checks will succeed.
179
"""
180
181
182
class WebDriver(BaseWebDriver):
183
"""Controls a browser by sending commands to a remote server. This server
184
is expected to be running the WebDriver wire protocol as defined at
185
https://www.selenium.dev/documentation/legacy/json_wire_protocol/.
186
187
Attributes:
188
-----------
189
session_id - String ID of the browser session started and controlled by this WebDriver.
190
capabilities - Dictionary of effective capabilities of this browser session as returned
191
by the remote server. See https://www.selenium.dev/documentation/legacy/desired_capabilities/
192
command_executor : str or remote_connection.RemoteConnection object used to execute commands.
193
error_handler - errorhandler.ErrorHandler object used to handle errors.
194
"""
195
196
_web_element_cls = WebElement
197
_shadowroot_cls = ShadowRoot
198
199
def __init__(
200
self,
201
command_executor: Union[str, RemoteConnection] = "http://127.0.0.1:4444",
202
keep_alive: bool = True,
203
file_detector: Optional[FileDetector] = None,
204
options: Optional[Union[BaseOptions, list[BaseOptions]]] = None,
205
locator_converter: Optional[LocatorConverter] = None,
206
web_element_cls: Optional[type[WebElement]] = None,
207
client_config: Optional[ClientConfig] = None,
208
) -> None:
209
"""Create a new driver that will issue commands using the wire
210
protocol.
211
212
Parameters:
213
-----------
214
command_executor : str or remote_connection.RemoteConnection
215
- Either a string representing the URL of the remote server or a custom
216
remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
217
keep_alive : bool (Deprecated)
218
- Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. Defaults to True.
219
file_detector : object or None
220
- Pass a custom file detector object during instantiation. If None, the default
221
LocalFileDetector() will be used.
222
options : options.Options
223
- Instance of a driver options.Options class.
224
locator_converter : object or None
225
- Custom locator converter to use. Defaults to None.
226
web_element_cls : class
227
- Custom class to use for web elements. Defaults to WebElement.
228
client_config : object or None
229
- Custom client configuration to use. Defaults to None.
230
"""
231
232
if options is None:
233
raise TypeError(
234
"missing 1 required keyword-only argument: 'options' (instance of driver `options.Options` class)"
235
)
236
elif isinstance(options, list):
237
capabilities = create_matches(options)
238
_ignore_local_proxy = False
239
else:
240
capabilities = options.to_capabilities()
241
_ignore_local_proxy = options._ignore_local_proxy
242
self.command_executor = command_executor
243
if isinstance(self.command_executor, (str, bytes)):
244
self.command_executor = get_remote_connection(
245
capabilities,
246
command_executor=command_executor,
247
keep_alive=keep_alive,
248
ignore_local_proxy=_ignore_local_proxy,
249
client_config=client_config,
250
)
251
self._is_remote = True
252
self.session_id: Optional[str] = None
253
self.caps: dict[str, Any] = {}
254
self.pinned_scripts: dict[str, Any] = {}
255
self.error_handler = ErrorHandler()
256
self._switch_to = SwitchTo(self)
257
self._mobile = Mobile(self)
258
self.file_detector = file_detector or LocalFileDetector()
259
self.locator_converter = locator_converter or LocatorConverter()
260
self._web_element_cls = web_element_cls or self._web_element_cls
261
self._authenticator_id = None
262
self.start_client()
263
self.start_session(capabilities)
264
self._fedcm = FedCM(self)
265
266
self._websocket_connection = None
267
self._script = None
268
self._network = None
269
self._browser = None
270
self._bidi_session = None
271
self._browsing_context = None
272
self._storage = None
273
self._webextension = None
274
self._permissions = None
275
self._emulation = None
276
self._input = None
277
self._devtools = None
278
279
def __repr__(self):
280
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'
281
282
def __enter__(self):
283
return self
284
285
def __exit__(
286
self,
287
exc_type: Optional[type[BaseException]],
288
exc: Optional[BaseException],
289
traceback: Optional[types.TracebackType],
290
):
291
self.quit()
292
293
@contextmanager
294
def file_detector_context(self, file_detector_class, *args, **kwargs):
295
"""Overrides the current file detector (if necessary) in limited
296
context. Ensures the original file detector is set afterwards.
297
298
Parameters:
299
-----------
300
file_detector_class : object
301
- Class of the desired file detector. If the class is different
302
from the current file_detector, then the class is instantiated with args and kwargs
303
and used as a file detector during the duration of the context manager.
304
args : tuple
305
- Optional arguments that get passed to the file detector class during instantiation.
306
kwargs : dict
307
- Keyword arguments, passed the same way as args.
308
309
Example:
310
--------
311
>>> with webdriver.file_detector_context(UselessFileDetector):
312
>>> someinput.send_keys('/etc/hosts')
313
"""
314
last_detector = None
315
if not isinstance(self.file_detector, file_detector_class):
316
last_detector = self.file_detector
317
self.file_detector = file_detector_class(*args, **kwargs)
318
try:
319
yield
320
finally:
321
if last_detector:
322
self.file_detector = last_detector
323
324
@property
325
def mobile(self) -> Mobile:
326
return self._mobile
327
328
@property
329
def name(self) -> str:
330
"""Returns the name of the underlying browser for this instance.
331
332
Example:
333
--------
334
>>> name = driver.name
335
"""
336
if "browserName" in self.caps:
337
return self.caps["browserName"]
338
raise KeyError("browserName not specified in session capabilities")
339
340
def start_client(self):
341
"""Called before starting a new session.
342
343
This method may be overridden to define custom startup behavior.
344
"""
345
pass
346
347
def stop_client(self):
348
"""Called after executing a quit command.
349
350
This method may be overridden to define custom shutdown
351
behavior.
352
"""
353
pass
354
355
def start_session(self, capabilities: dict) -> None:
356
"""Creates a new session with the desired capabilities.
357
358
Parameters:
359
-----------
360
capabilities : dict
361
- A capabilities dict to start the session with.
362
"""
363
364
caps = _create_caps(capabilities)
365
try:
366
response = self.execute(Command.NEW_SESSION, caps)["value"]
367
self.session_id = response.get("sessionId")
368
self.caps = response.get("capabilities")
369
except Exception:
370
if hasattr(self, "service") and self.service is not None:
371
self.service.stop()
372
raise
373
374
def _wrap_value(self, value):
375
if isinstance(value, dict):
376
converted = {}
377
for key, val in value.items():
378
converted[key] = self._wrap_value(val)
379
return converted
380
if isinstance(value, self._web_element_cls):
381
return {"element-6066-11e4-a52e-4f735466cecf": value.id}
382
if isinstance(value, self._shadowroot_cls):
383
return {"shadow-6066-11e4-a52e-4f735466cecf": value.id}
384
if isinstance(value, list):
385
return list(self._wrap_value(item) for item in value)
386
return value
387
388
def create_web_element(self, element_id: str) -> WebElement:
389
"""Creates a web element with the specified `element_id`."""
390
return self._web_element_cls(self, element_id)
391
392
def _unwrap_value(self, value):
393
if isinstance(value, dict):
394
if "element-6066-11e4-a52e-4f735466cecf" in value:
395
return self.create_web_element(value["element-6066-11e4-a52e-4f735466cecf"])
396
if "shadow-6066-11e4-a52e-4f735466cecf" in value:
397
return self._shadowroot_cls(self, value["shadow-6066-11e4-a52e-4f735466cecf"])
398
for key, val in value.items():
399
value[key] = self._unwrap_value(val)
400
return value
401
if isinstance(value, list):
402
return list(self._unwrap_value(item) for item in value)
403
return value
404
405
def execute_cdp_cmd(self, cmd: str, cmd_args: dict):
406
"""Execute Chrome Devtools Protocol command and get returned result The
407
command and command args should follow chrome devtools protocol
408
domains/commands, refer to link
409
https://chromedevtools.github.io/devtools-protocol/
410
411
Parameters:
412
-----------
413
cmd : str,
414
- Command name
415
416
cmd_args : dict
417
- Command args
418
- Empty dict {} if there is no command args
419
420
Returns:
421
--------
422
A dict, empty dict {} if there is no result to return.
423
- To getResponseBody: {'base64Encoded': False, 'body': 'response body string'}
424
425
Example:
426
--------
427
>>> driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": requestId})
428
429
"""
430
return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})["value"]
431
432
def execute(self, driver_command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
433
"""Sends a command to be executed by a command.CommandExecutor.
434
435
Parameters:
436
-----------
437
driver_command : str
438
- The name of the command to execute as a string.
439
440
params : dict
441
- A dictionary of named Parameters to send with the command.
442
443
Returns:
444
--------
445
dict - The command's JSON response loaded into a dictionary object.
446
"""
447
params = self._wrap_value(params)
448
449
if self.session_id:
450
if not params:
451
params = {"sessionId": self.session_id}
452
elif "sessionId" not in params:
453
params["sessionId"] = self.session_id
454
455
response = cast(RemoteConnection, self.command_executor).execute(driver_command, params)
456
457
if response:
458
self.error_handler.check_response(response)
459
response["value"] = self._unwrap_value(response.get("value", None))
460
return response
461
# If the server doesn't send a response, assume the command was
462
# a success
463
return {"success": 0, "value": None, "sessionId": self.session_id}
464
465
def get(self, url: str) -> None:
466
"""Navigate the browser to the specified URL in the current window or
467
tab.
468
469
The method does not return until the page is fully loaded (i.e. the
470
onload event has fired).
471
472
Parameters:
473
-----------
474
url : str
475
- The URL to be opened by the browser.
476
- Must include the protocol (e.g., http://, https://).
477
478
Example:
479
--------
480
>>> driver = webdriver.Chrome()
481
>>> driver.get("https://example.com")
482
"""
483
self.execute(Command.GET, {"url": url})
484
485
@property
486
def title(self) -> str:
487
"""Returns the title of the current page.
488
489
Example:
490
--------
491
>>> element = driver.find_element(By.ID, "foo")
492
>>> print(element.title())
493
"""
494
return self.execute(Command.GET_TITLE).get("value", "")
495
496
def pin_script(self, script: str, script_key=None) -> ScriptKey:
497
"""Store common javascript scripts to be executed later by a unique
498
hashable ID.
499
500
Example:
501
--------
502
>>> script = "return document.getElementById('foo').value"
503
"""
504
script_key_instance = ScriptKey(script_key)
505
self.pinned_scripts[script_key_instance.id] = script
506
return script_key_instance
507
508
def unpin(self, script_key: ScriptKey) -> None:
509
"""Remove a pinned script from storage.
510
511
Example:
512
--------
513
>>> driver.unpin(script_key)
514
"""
515
try:
516
self.pinned_scripts.pop(script_key.id)
517
except KeyError:
518
raise KeyError(f"No script with key: {script_key} existed in {self.pinned_scripts}") from None
519
520
def get_pinned_scripts(self) -> list[str]:
521
"""Return a list of all pinned scripts.
522
523
Example:
524
--------
525
>>> pinned_scripts = driver.get_pinned_scripts()
526
"""
527
return list(self.pinned_scripts)
528
529
def execute_script(self, script: str, *args):
530
"""Synchronously Executes JavaScript in the current window/frame.
531
532
Parameters:
533
-----------
534
script : str
535
- The javascript to execute.
536
537
*args : tuple
538
- Any applicable arguments for your JavaScript.
539
540
Example:
541
--------
542
>>> input_id = "username"
543
>>> input_value = "test_user"
544
>>> driver.execute_script("document.getElementById(arguments[0]).value = arguments[1];", input_id, input_value)
545
"""
546
if isinstance(script, ScriptKey):
547
try:
548
script = self.pinned_scripts[script.id]
549
except KeyError:
550
raise JavascriptException("Pinned script could not be found")
551
552
converted_args = list(args)
553
command = Command.W3C_EXECUTE_SCRIPT
554
555
return self.execute(command, {"script": script, "args": converted_args})["value"]
556
557
def execute_async_script(self, script: str, *args):
558
"""Asynchronously Executes JavaScript in the current window/frame.
559
560
Parameters:
561
-----------
562
script : str
563
- The javascript to execute.
564
565
*args : tuple
566
- Any applicable arguments for your JavaScript.
567
568
Example:
569
--------
570
>>> script = "var callback = arguments[arguments.length - 1]; "
571
... "window.setTimeout(function(){ callback('timeout') }, 3000);"
572
>>> driver.execute_async_script(script)
573
"""
574
converted_args = list(args)
575
command = Command.W3C_EXECUTE_SCRIPT_ASYNC
576
577
return self.execute(command, {"script": script, "args": converted_args})["value"]
578
579
@property
580
def current_url(self) -> str:
581
"""Gets the URL of the current page.
582
583
Example:
584
--------
585
>>> print(driver.current_url)
586
"""
587
return self.execute(Command.GET_CURRENT_URL)["value"]
588
589
@property
590
def page_source(self) -> str:
591
"""Gets the source of the current page.
592
593
Example:
594
--------
595
>>> print(driver.page_source)
596
"""
597
return self.execute(Command.GET_PAGE_SOURCE)["value"]
598
599
def close(self) -> None:
600
"""Closes the current window.
601
602
Example:
603
--------
604
>>> driver.close()
605
"""
606
self.execute(Command.CLOSE)
607
608
def quit(self) -> None:
609
"""Quits the driver and closes every associated window.
610
611
Example:
612
--------
613
>>> driver.quit()
614
"""
615
try:
616
self.execute(Command.QUIT)
617
finally:
618
self.stop_client()
619
executor = cast(RemoteConnection, self.command_executor)
620
executor.close()
621
622
@property
623
def current_window_handle(self) -> str:
624
"""Returns the handle of the current window.
625
626
Example:
627
--------
628
>>> print(driver.current_window_handle)
629
"""
630
return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)["value"]
631
632
@property
633
def window_handles(self) -> list[str]:
634
"""Returns the handles of all windows within the current session.
635
636
Example:
637
--------
638
>>> print(driver.window_handles)
639
"""
640
return self.execute(Command.W3C_GET_WINDOW_HANDLES)["value"]
641
642
def maximize_window(self) -> None:
643
"""Maximizes the current window that webdriver is using.
644
645
Example:
646
--------
647
>>> driver.maximize_window()
648
"""
649
command = Command.W3C_MAXIMIZE_WINDOW
650
self.execute(command, None)
651
652
def fullscreen_window(self) -> None:
653
"""Invokes the window manager-specific 'full screen' operation.
654
655
Example:
656
--------
657
>>> driver.fullscreen_window()
658
"""
659
self.execute(Command.FULLSCREEN_WINDOW)
660
661
def minimize_window(self) -> None:
662
"""Invokes the window manager-specific 'minimize' operation."""
663
self.execute(Command.MINIMIZE_WINDOW)
664
665
def print_page(self, print_options: Optional[PrintOptions] = None) -> str:
666
"""Takes PDF of the current page.
667
668
The driver makes a best effort to return a PDF based on the
669
provided Parameters.
670
671
Example:
672
--------
673
>>> driver.print_page()
674
"""
675
options: Union[dict[str, Any], Any] = {}
676
if print_options:
677
options = print_options.to_dict()
678
679
return self.execute(Command.PRINT_PAGE, options)["value"]
680
681
@property
682
def switch_to(self) -> SwitchTo:
683
"""Return an object containing all options to switch focus into.
684
685
Returns:
686
--------
687
SwitchTo: an object containing all options to switch focus into.
688
689
Examples:
690
--------
691
>>> element = driver.switch_to.active_element
692
>>> alert = driver.switch_to.alert
693
>>> driver.switch_to.default_content()
694
>>> driver.switch_to.frame("frame_name")
695
>>> driver.switch_to.frame(1)
696
>>> driver.switch_to.frame(driver.find_elements(By.TAG_NAME, "iframe")[0])
697
>>> driver.switch_to.parent_frame()
698
>>> driver.switch_to.window("main")
699
"""
700
return self._switch_to
701
702
# Navigation
703
def back(self) -> None:
704
"""Goes one step backward in the browser history.
705
706
Example:
707
--------
708
>>> driver.back()
709
"""
710
self.execute(Command.GO_BACK)
711
712
def forward(self) -> None:
713
"""Goes one step forward in the browser history.
714
715
Example:
716
--------
717
>>> driver.forward()
718
"""
719
self.execute(Command.GO_FORWARD)
720
721
def refresh(self) -> None:
722
"""Refreshes the current page.
723
724
Example:
725
--------
726
>>> driver.refresh()
727
"""
728
self.execute(Command.REFRESH)
729
730
# Options
731
def get_cookies(self) -> list[dict]:
732
"""Returns a set of dictionaries, corresponding to cookies visible in
733
the current session.
734
735
Returns:
736
--------
737
cookies:List[dict] : A list of dictionaries, corresponding to cookies visible in the current
738
739
Example:
740
--------
741
>>> cookies = driver.get_cookies()
742
"""
743
return self.execute(Command.GET_ALL_COOKIES)["value"]
744
745
def get_cookie(self, name) -> Optional[dict]:
746
"""Get a single cookie by name. Raises ValueError if the name is empty
747
or whitespace. Returns the cookie if found, None if not.
748
749
Example:
750
--------
751
>>> cookie = driver.get_cookie("my_cookie")
752
"""
753
if not name or name.isspace():
754
raise ValueError("Cookie name cannot be empty")
755
756
with contextlib.suppress(NoSuchCookieException):
757
return self.execute(Command.GET_COOKIE, {"name": name})["value"]
758
759
return None
760
761
def delete_cookie(self, name) -> None:
762
"""Deletes a single cookie with the given name. Raises ValueError if
763
the name is empty or whitespace.
764
765
Example:
766
--------
767
>>> driver.delete_cookie("my_cookie")
768
"""
769
770
# firefox deletes all cookies when "" is passed as name
771
if not name or name.isspace():
772
raise ValueError("Cookie name cannot be empty")
773
774
self.execute(Command.DELETE_COOKIE, {"name": name})
775
776
def delete_all_cookies(self) -> None:
777
"""Delete all cookies in the scope of the session.
778
779
Example:
780
--------
781
>>> driver.delete_all_cookies()
782
"""
783
self.execute(Command.DELETE_ALL_COOKIES)
784
785
def add_cookie(self, cookie_dict) -> None:
786
"""Adds a cookie to your current session.
787
788
Parameters:
789
-----------
790
cookie_dict : dict
791
- A dictionary object, with required keys - "name" and "value";
792
- Optional keys - "path", "domain", "secure", "httpOnly", "expiry", "sameSite"
793
794
Examples:
795
--------
796
>>> driver.add_cookie({"name": "foo", "value": "bar"})
797
>>> driver.add_cookie({"name": "foo", "value": "bar", "path": "/"})
798
>>> driver.add_cookie({"name": "foo", "value": "bar", "path": "/", "secure": True})
799
>>> driver.add_cookie({"name": "foo", "value": "bar", "sameSite": "Strict"})
800
"""
801
if "sameSite" in cookie_dict:
802
assert cookie_dict["sameSite"] in ["Strict", "Lax", "None"]
803
self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})
804
else:
805
self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})
806
807
# Timeouts
808
def implicitly_wait(self, time_to_wait: float) -> None:
809
"""Sets a sticky timeout to implicitly wait for an element to be found,
810
or a command to complete. This method only needs to be called one time
811
per session. To set the timeout for calls to execute_async_script, see
812
set_script_timeout.
813
814
Parameters:
815
-----------
816
time_to_wait : float
817
- Amount of time to wait (in seconds)
818
819
Example:
820
--------
821
>>> driver.implicitly_wait(30)
822
"""
823
self.execute(Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)})
824
825
def set_script_timeout(self, time_to_wait: float) -> None:
826
"""Set the amount of time that the script should wait during an
827
execute_async_script call before throwing an error.
828
829
Parameters:
830
-----------
831
time_to_wait : float
832
- The amount of time to wait (in seconds)
833
834
Example:
835
--------
836
>>> driver.set_script_timeout(30)
837
"""
838
self.execute(Command.SET_TIMEOUTS, {"script": int(float(time_to_wait) * 1000)})
839
840
def set_page_load_timeout(self, time_to_wait: float) -> None:
841
"""Set the amount of time to wait for a page load to complete before
842
throwing an error.
843
844
Parameters:
845
-----------
846
time_to_wait : float
847
- The amount of time to wait (in seconds)
848
849
Example:
850
--------
851
>>> driver.set_page_load_timeout(30)
852
"""
853
try:
854
self.execute(Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)})
855
except WebDriverException:
856
self.execute(Command.SET_TIMEOUTS, {"ms": float(time_to_wait) * 1000, "type": "page load"})
857
858
@property
859
def timeouts(self) -> Timeouts:
860
"""Get all the timeouts that have been set on the current session.
861
862
Returns:
863
--------
864
Timeouts: A named tuple with the following fields:
865
- implicit_wait: The time to wait for elements to be found.
866
- page_load: The time to wait for a page to load.
867
- script: The time to wait for scripts to execute.
868
869
Example:
870
--------
871
>>> driver.timeouts
872
"""
873
timeouts = self.execute(Command.GET_TIMEOUTS)["value"]
874
timeouts["implicit_wait"] = timeouts.pop("implicit") / 1000
875
timeouts["page_load"] = timeouts.pop("pageLoad") / 1000
876
timeouts["script"] = timeouts.pop("script") / 1000
877
return Timeouts(**timeouts)
878
879
@timeouts.setter
880
def timeouts(self, timeouts) -> None:
881
"""Set all timeouts for the session. This will override any previously
882
set timeouts.
883
884
Example:
885
--------
886
>>> my_timeouts = Timeouts()
887
>>> my_timeouts.implicit_wait = 10
888
>>> driver.timeouts = my_timeouts
889
"""
890
_ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"]
891
892
def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement:
893
"""Find an element given a By strategy and locator.
894
895
Parameters:
896
-----------
897
by : selenium.webdriver.common.by.By
898
The locating strategy to use. Default is `By.ID`. Supported values include:
899
- By.ID: Locate by element ID.
900
- By.NAME: Locate by the `name` attribute.
901
- By.XPATH: Locate by an XPath expression.
902
- By.CSS_SELECTOR: Locate by a CSS selector.
903
- By.CLASS_NAME: Locate by the `class` attribute.
904
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
905
- By.LINK_TEXT: Locate a link element by its exact text.
906
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
907
- RelativeBy: Locate elements relative to a specified root element.
908
909
Example:
910
--------
911
element = driver.find_element(By.ID, 'foo')
912
913
Returns:
914
-------
915
WebElement
916
The first matching `WebElement` found on the page.
917
"""
918
by, value = self.locator_converter.convert(by, value)
919
920
if isinstance(by, RelativeBy):
921
elements = self.find_elements(by=by, value=value)
922
if not elements:
923
raise NoSuchElementException(f"Cannot locate relative element with: {by.root}")
924
return elements[0]
925
926
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
927
928
def find_elements(self, by=By.ID, value: Optional[str] = None) -> list[WebElement]:
929
"""Find elements given a By strategy and locator.
930
931
Parameters:
932
-----------
933
by : selenium.webdriver.common.by.By
934
The locating strategy to use. Default is `By.ID`. Supported values include:
935
- By.ID: Locate by element ID.
936
- By.NAME: Locate by the `name` attribute.
937
- By.XPATH: Locate by an XPath expression.
938
- By.CSS_SELECTOR: Locate by a CSS selector.
939
- By.CLASS_NAME: Locate by the `class` attribute.
940
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
941
- By.LINK_TEXT: Locate a link element by its exact text.
942
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
943
- RelativeBy: Locate elements relative to a specified root element.
944
945
Example:
946
--------
947
element = driver.find_elements(By.ID, 'foo')
948
949
Returns:
950
-------
951
List[WebElement]
952
list of `WebElements` matching locator strategy found on the page.
953
"""
954
by, value = self.locator_converter.convert(by, value)
955
956
if isinstance(by, RelativeBy):
957
_pkg = ".".join(__name__.split(".")[:-1])
958
raw_data = pkgutil.get_data(_pkg, "findElements.js")
959
if raw_data is None:
960
raise FileNotFoundError(f"Could not find findElements.js in package {_pkg}")
961
raw_function = raw_data.decode("utf8")
962
find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);"
963
return self.execute_script(find_element_js, by.to_dict())
964
965
# Return empty list if driver returns null
966
# See https://github.com/SeleniumHQ/selenium/issues/4555
967
return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or []
968
969
@property
970
def capabilities(self) -> dict:
971
"""Returns the drivers current capabilities being used.
972
973
Example:
974
--------
975
>>> print(driver.capabilities)
976
"""
977
return self.caps
978
979
def get_screenshot_as_file(self, filename) -> bool:
980
"""Saves a screenshot of the current window to a PNG image file.
981
Returns False if there is any IOError, else returns True. Use full
982
paths in your filename.
983
984
Parameters:
985
-----------
986
filename : str
987
- The full path you wish to save your screenshot to. This
988
- should end with a `.png` extension.
989
990
Example:
991
--------
992
>>> driver.get_screenshot_as_file("/Screenshots/foo.png")
993
"""
994
if not str(filename).lower().endswith(".png"):
995
warnings.warn(
996
"name used for saved screenshot does not match file type. It should end with a `.png` extension",
997
UserWarning,
998
stacklevel=2,
999
)
1000
png = self.get_screenshot_as_png()
1001
try:
1002
with open(filename, "wb") as f:
1003
f.write(png)
1004
except OSError:
1005
return False
1006
finally:
1007
del png
1008
return True
1009
1010
def save_screenshot(self, filename) -> bool:
1011
"""Saves a screenshot of the current window to a PNG image file.
1012
Returns False if there is any IOError, else returns True. Use full
1013
paths in your filename.
1014
1015
Parameters:
1016
-----------
1017
filename : str
1018
- The full path you wish to save your screenshot to. This
1019
- should end with a `.png` extension.
1020
1021
Example:
1022
--------
1023
>>> driver.save_screenshot("/Screenshots/foo.png")
1024
"""
1025
return self.get_screenshot_as_file(filename)
1026
1027
def get_screenshot_as_png(self) -> bytes:
1028
"""Gets the screenshot of the current window as a binary data.
1029
1030
Example:
1031
--------
1032
>>> driver.get_screenshot_as_png()
1033
"""
1034
return b64decode(self.get_screenshot_as_base64().encode("ascii"))
1035
1036
def get_screenshot_as_base64(self) -> str:
1037
"""Gets the screenshot of the current window as a base64 encoded string
1038
which is useful in embedded images in HTML.
1039
1040
Example:
1041
--------
1042
>>> driver.get_screenshot_as_base64()
1043
"""
1044
return self.execute(Command.SCREENSHOT)["value"]
1045
1046
def set_window_size(self, width, height, windowHandle: str = "current") -> None:
1047
"""Sets the width and height of the current window. (window.resizeTo)
1048
1049
Parameters:
1050
-----------
1051
width : int
1052
- the width in pixels to set the window to
1053
1054
height : int
1055
- the height in pixels to set the window to
1056
1057
Example:
1058
--------
1059
>>> driver.set_window_size(800, 600)
1060
"""
1061
self._check_if_window_handle_is_current(windowHandle)
1062
self.set_window_rect(width=int(width), height=int(height))
1063
1064
def get_window_size(self, windowHandle: str = "current") -> dict:
1065
"""Gets the width and height of the current window.
1066
1067
Example:
1068
--------
1069
>>> driver.get_window_size()
1070
"""
1071
1072
self._check_if_window_handle_is_current(windowHandle)
1073
size = self.get_window_rect()
1074
1075
if size.get("value", None):
1076
size = size["value"]
1077
1078
return {k: size[k] for k in ("width", "height")}
1079
1080
def set_window_position(self, x: float, y: float, windowHandle: str = "current") -> dict:
1081
"""Sets the x,y position of the current window. (window.moveTo)
1082
1083
Parameters:
1084
---------
1085
x : float
1086
- The x-coordinate in pixels to set the window position
1087
1088
y : float
1089
- The y-coordinate in pixels to set the window position
1090
1091
Example:
1092
--------
1093
>>> driver.set_window_position(0, 0)
1094
"""
1095
self._check_if_window_handle_is_current(windowHandle)
1096
return self.set_window_rect(x=int(x), y=int(y))
1097
1098
def get_window_position(self, windowHandle="current") -> dict:
1099
"""Gets the x,y position of the current window.
1100
1101
Example:
1102
--------
1103
>>> driver.get_window_position()
1104
"""
1105
1106
self._check_if_window_handle_is_current(windowHandle)
1107
position = self.get_window_rect()
1108
1109
return {k: position[k] for k in ("x", "y")}
1110
1111
def _check_if_window_handle_is_current(self, windowHandle: str) -> None:
1112
"""Warns if the window handle is not equal to `current`."""
1113
if windowHandle != "current":
1114
warnings.warn("Only 'current' window is supported for W3C compatible browsers.", stacklevel=2)
1115
1116
def get_window_rect(self) -> dict:
1117
"""Gets the x, y coordinates of the window as well as height and width
1118
of the current window.
1119
1120
Example:
1121
--------
1122
>>> driver.get_window_rect()
1123
"""
1124
return self.execute(Command.GET_WINDOW_RECT)["value"]
1125
1126
def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict:
1127
"""Sets the x, y coordinates of the window as well as height and width
1128
of the current window. This method is only supported for W3C compatible
1129
browsers; other browsers should use `set_window_position` and
1130
`set_window_size`.
1131
1132
Example:
1133
--------
1134
>>> driver.set_window_rect(x=10, y=10)
1135
>>> driver.set_window_rect(width=100, height=200)
1136
>>> driver.set_window_rect(x=10, y=10, width=100, height=200)
1137
"""
1138
1139
if (x is None and y is None) and (not height and not width):
1140
raise InvalidArgumentException("x and y or height and width need values")
1141
1142
return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height})["value"]
1143
1144
@property
1145
def file_detector(self) -> FileDetector:
1146
return self._file_detector
1147
1148
@file_detector.setter
1149
def file_detector(self, detector) -> None:
1150
"""Set the file detector to be used when sending keyboard input. By
1151
default, this is set to a file detector that does nothing.
1152
1153
- see FileDetector
1154
- see LocalFileDetector
1155
- see UselessFileDetector
1156
1157
Parameters:
1158
-----------
1159
detector : Any
1160
- The detector to use. Must not be None.
1161
"""
1162
if not detector:
1163
raise WebDriverException("You may not set a file detector that is null")
1164
if not isinstance(detector, FileDetector):
1165
raise WebDriverException("Detector has to be instance of FileDetector")
1166
self._file_detector = detector
1167
1168
@property
1169
def orientation(self):
1170
"""Gets the current orientation of the device.
1171
1172
Example:
1173
--------
1174
>>> orientation = driver.orientation
1175
"""
1176
return self.execute(Command.GET_SCREEN_ORIENTATION)["value"]
1177
1178
@orientation.setter
1179
def orientation(self, value) -> None:
1180
"""Sets the current orientation of the device.
1181
1182
Parameters:
1183
-----------
1184
value : str
1185
- orientation to set it to.
1186
1187
Example:
1188
--------
1189
>>> driver.orientation = "landscape"
1190
"""
1191
allowed_values = ["LANDSCAPE", "PORTRAIT"]
1192
if value.upper() in allowed_values:
1193
self.execute(Command.SET_SCREEN_ORIENTATION, {"orientation": value})
1194
else:
1195
raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")
1196
1197
def start_devtools(self):
1198
global cdp
1199
import_cdp()
1200
if self.caps.get("se:cdp"):
1201
ws_url = self.caps.get("se:cdp")
1202
version = self.caps.get("se:cdpVersion").split(".")[0]
1203
else:
1204
version, ws_url = self._get_cdp_details()
1205
1206
if not ws_url:
1207
raise WebDriverException("Unable to find url to connect to from capabilities")
1208
1209
self._devtools = cdp.import_devtools(version)
1210
if self._websocket_connection:
1211
return self._devtools, self._websocket_connection
1212
if self.caps["browserName"].lower() == "firefox":
1213
raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.")
1214
self._websocket_connection = WebSocketConnection(ws_url)
1215
targets = self._websocket_connection.execute(self._devtools.target.get_targets())
1216
for target in targets:
1217
if target.target_id == self.current_window_handle:
1218
target_id = target.target_id
1219
break
1220
session = self._websocket_connection.execute(self._devtools.target.attach_to_target(target_id, True))
1221
self._websocket_connection.session_id = session
1222
return self._devtools, self._websocket_connection
1223
1224
@asynccontextmanager
1225
async def bidi_connection(self):
1226
global cdp
1227
import_cdp()
1228
if self.caps.get("se:cdp"):
1229
ws_url = self.caps.get("se:cdp")
1230
version = self.caps.get("se:cdpVersion").split(".")[0]
1231
else:
1232
version, ws_url = self._get_cdp_details()
1233
1234
if not ws_url:
1235
raise WebDriverException("Unable to find url to connect to from capabilities")
1236
1237
devtools = cdp.import_devtools(version)
1238
async with cdp.open_cdp(ws_url) as conn:
1239
targets = await conn.execute(devtools.target.get_targets())
1240
for target in targets:
1241
if target.target_id == self.current_window_handle:
1242
target_id = target.target_id
1243
break
1244
async with conn.open_session(target_id) as session:
1245
yield BidiConnection(session, cdp, devtools)
1246
1247
@property
1248
def script(self):
1249
if not self._websocket_connection:
1250
self._start_bidi()
1251
1252
if not self._script:
1253
self._script = Script(self._websocket_connection, self)
1254
1255
return self._script
1256
1257
def _start_bidi(self):
1258
if self.caps.get("webSocketUrl"):
1259
ws_url = self.caps.get("webSocketUrl")
1260
else:
1261
raise WebDriverException("Unable to find url to connect to from capabilities")
1262
1263
self._websocket_connection = WebSocketConnection(ws_url)
1264
1265
@property
1266
def network(self):
1267
if not self._websocket_connection:
1268
self._start_bidi()
1269
1270
if not hasattr(self, "_network") or self._network is None:
1271
self._network = Network(self._websocket_connection)
1272
1273
return self._network
1274
1275
@property
1276
def browser(self):
1277
"""Returns a browser module object for BiDi browser commands.
1278
1279
Returns:
1280
--------
1281
Browser: an object containing access to BiDi browser commands.
1282
1283
Examples:
1284
---------
1285
>>> user_context = driver.browser.create_user_context()
1286
>>> user_contexts = driver.browser.get_user_contexts()
1287
>>> client_windows = driver.browser.get_client_windows()
1288
>>> driver.browser.remove_user_context(user_context)
1289
"""
1290
if not self._websocket_connection:
1291
self._start_bidi()
1292
1293
if self._browser is None:
1294
self._browser = Browser(self._websocket_connection)
1295
1296
return self._browser
1297
1298
@property
1299
def _session(self):
1300
"""Returns the BiDi session object for the current WebDriver
1301
session."""
1302
if not self._websocket_connection:
1303
self._start_bidi()
1304
1305
if self._bidi_session is None:
1306
self._bidi_session = Session(self._websocket_connection)
1307
1308
return self._bidi_session
1309
1310
@property
1311
def browsing_context(self):
1312
"""Returns a browsing context module object for BiDi browsing context
1313
commands.
1314
1315
Returns:
1316
--------
1317
BrowsingContext: an object containing access to BiDi browsing context commands.
1318
1319
Examples:
1320
---------
1321
>>> context_id = driver.browsing_context.create(type="tab")
1322
>>> driver.browsing_context.navigate(context=context_id, url="https://www.selenium.dev")
1323
>>> driver.browsing_context.capture_screenshot(context=context_id)
1324
>>> driver.browsing_context.close(context_id)
1325
"""
1326
if not self._websocket_connection:
1327
self._start_bidi()
1328
1329
if self._browsing_context is None:
1330
self._browsing_context = BrowsingContext(self._websocket_connection)
1331
1332
return self._browsing_context
1333
1334
@property
1335
def storage(self):
1336
"""Returns a storage module object for BiDi storage commands.
1337
1338
Returns:
1339
--------
1340
Storage: an object containing access to BiDi storage commands.
1341
1342
Examples:
1343
---------
1344
>>> cookie_filter = CookieFilter(name="example")
1345
>>> result = driver.storage.get_cookies(filter=cookie_filter)
1346
>>> driver.storage.set_cookie(cookie=PartialCookie(
1347
"name", BytesValue(BytesValue.TYPE_STRING, "value"), "domain")
1348
)
1349
>>> driver.storage.delete_cookies(filter=CookieFilter(name="example"))
1350
"""
1351
if not self._websocket_connection:
1352
self._start_bidi()
1353
1354
if self._storage is None:
1355
self._storage = Storage(self._websocket_connection)
1356
1357
return self._storage
1358
1359
@property
1360
def permissions(self):
1361
"""Returns a permissions module object for BiDi permissions commands.
1362
1363
Returns:
1364
--------
1365
Permissions: an object containing access to BiDi permissions commands.
1366
1367
Examples:
1368
---------
1369
>>> from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState
1370
>>> descriptor = PermissionDescriptor("geolocation")
1371
>>> driver.permissions.set_permission(descriptor, PermissionState.GRANTED, "https://example.com")
1372
"""
1373
if not self._websocket_connection:
1374
self._start_bidi()
1375
1376
if self._permissions is None:
1377
self._permissions = Permissions(self._websocket_connection)
1378
1379
return self._permissions
1380
1381
@property
1382
def webextension(self):
1383
"""Returns a webextension module object for BiDi webextension commands.
1384
1385
Returns:
1386
--------
1387
WebExtension: an object containing access to BiDi webextension commands.
1388
1389
Examples:
1390
---------
1391
>>> extension_path = "/path/to/extension"
1392
>>> extension_result = driver.webextension.install(path=extension_path)
1393
>>> driver.webextension.uninstall(extension_result)
1394
"""
1395
if not self._websocket_connection:
1396
self._start_bidi()
1397
1398
if self._webextension is None:
1399
self._webextension = WebExtension(self._websocket_connection)
1400
1401
return self._webextension
1402
1403
@property
1404
def emulation(self):
1405
"""Returns an emulation module object for BiDi emulation commands.
1406
1407
Returns:
1408
--------
1409
Emulation: an object containing access to BiDi emulation commands.
1410
1411
Examples:
1412
---------
1413
>>> from selenium.webdriver.common.bidi.emulation import GeolocationCoordinates
1414
>>> coordinates = GeolocationCoordinates(37.7749, -122.4194)
1415
>>> driver.emulation.set_geolocation_override(coordinates=coordinates, contexts=[context_id])
1416
"""
1417
if not self._websocket_connection:
1418
self._start_bidi()
1419
1420
if self._emulation is None:
1421
self._emulation = Emulation(self._websocket_connection)
1422
1423
return self._emulation
1424
1425
@property
1426
def input(self):
1427
"""Returns an input module object for BiDi input commands.
1428
1429
Returns:
1430
--------
1431
Input: an object containing access to BiDi input commands.
1432
1433
Examples:
1434
---------
1435
>>> from selenium.webdriver.common.bidi.input import KeySourceActions, KeyDownAction, KeyUpAction
1436
>>> key_actions = KeySourceActions(id="keyboard", actions=[KeyDownAction(value="a"), KeyUpAction(value="a")])
1437
>>> driver.input.perform_actions(driver.current_window_handle, [key_actions])
1438
>>> driver.input.release_actions(driver.current_window_handle)
1439
"""
1440
if not self._websocket_connection:
1441
self._start_bidi()
1442
1443
if self._input is None:
1444
self._input = Input(self._websocket_connection)
1445
1446
return self._input
1447
1448
def _get_cdp_details(self):
1449
import json
1450
1451
import urllib3
1452
1453
http = urllib3.PoolManager()
1454
try:
1455
if self.caps.get("browserName") == "chrome":
1456
debugger_address = self.caps.get("goog:chromeOptions").get("debuggerAddress")
1457
elif self.caps.get("browserName") in ("MicrosoftEdge", "webview2"):
1458
debugger_address = self.caps.get("ms:edgeOptions").get("debuggerAddress")
1459
except AttributeError:
1460
raise WebDriverException("Can't get debugger address.")
1461
1462
res = http.request("GET", f"http://{debugger_address}/json/version")
1463
data = json.loads(res.data)
1464
1465
browser_version = data.get("Browser")
1466
websocket_url = data.get("webSocketDebuggerUrl")
1467
1468
import re
1469
1470
version = re.search(r".*/(\d+)\.", browser_version).group(1)
1471
1472
return version, websocket_url
1473
1474
# Virtual Authenticator Methods
1475
def add_virtual_authenticator(self, options: VirtualAuthenticatorOptions) -> None:
1476
"""Adds a virtual authenticator with the given options.
1477
1478
Example:
1479
--------
1480
>>> from selenium.webdriver.common.virtual_authenticator import VirtualAuthenticatorOptions
1481
>>> options = VirtualAuthenticatorOptions(protocol="u2f", transport="usb", device_id="myDevice123")
1482
>>> driver.add_virtual_authenticator(options)
1483
"""
1484
self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict())["value"]
1485
1486
@property
1487
def virtual_authenticator_id(self) -> Optional[str]:
1488
"""Returns the id of the virtual authenticator.
1489
1490
Example:
1491
--------
1492
>>> print(driver.virtual_authenticator_id)
1493
"""
1494
return self._authenticator_id
1495
1496
@required_virtual_authenticator
1497
def remove_virtual_authenticator(self) -> None:
1498
"""Removes a previously added virtual authenticator.
1499
1500
The authenticator is no longer valid after removal, so no
1501
methods may be called.
1502
1503
Example:
1504
--------
1505
>>> driver.remove_virtual_authenticator()
1506
"""
1507
self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {"authenticatorId": self._authenticator_id})
1508
self._authenticator_id = None
1509
1510
@required_virtual_authenticator
1511
def add_credential(self, credential: Credential) -> None:
1512
"""Injects a credential into the authenticator.
1513
1514
Example:
1515
--------
1516
>>> from selenium.webdriver.common.credential import Credential
1517
>>> credential = Credential(id="[email protected]", password="aPassword")
1518
>>> driver.add_credential(credential)
1519
"""
1520
self.execute(Command.ADD_CREDENTIAL, {**credential.to_dict(), "authenticatorId": self._authenticator_id})
1521
1522
@required_virtual_authenticator
1523
def get_credentials(self) -> list[Credential]:
1524
"""Returns the list of credentials owned by the authenticator.
1525
1526
Example:
1527
--------
1528
>>> credentials = driver.get_credentials()
1529
"""
1530
credential_data = self.execute(Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id})
1531
return [Credential.from_dict(credential) for credential in credential_data["value"]]
1532
1533
@required_virtual_authenticator
1534
def remove_credential(self, credential_id: Union[str, bytearray]) -> None:
1535
"""Removes a credential from the authenticator.
1536
1537
Example:
1538
--------
1539
>>> credential_id = "[email protected]"
1540
>>> driver.remove_credential(credential_id)
1541
"""
1542
# Check if the credential is bytearray converted to b64 string
1543
if isinstance(credential_id, bytearray):
1544
credential_id = urlsafe_b64encode(credential_id).decode()
1545
1546
self.execute(
1547
Command.REMOVE_CREDENTIAL, {"credentialId": credential_id, "authenticatorId": self._authenticator_id}
1548
)
1549
1550
@required_virtual_authenticator
1551
def remove_all_credentials(self) -> None:
1552
"""Removes all credentials from the authenticator.
1553
1554
Example:
1555
--------
1556
>>> driver.remove_all_credentials()
1557
"""
1558
self.execute(Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id})
1559
1560
@required_virtual_authenticator
1561
def set_user_verified(self, verified: bool) -> None:
1562
"""Sets whether the authenticator will simulate success or fail on user
1563
verification.
1564
1565
Parameters:
1566
-----------
1567
verified: True if the authenticator will pass user verification, False otherwise.
1568
1569
Example:
1570
--------
1571
>>> driver.set_user_verified(True)
1572
"""
1573
self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified})
1574
1575
def get_downloadable_files(self) -> list:
1576
"""Retrieves the downloadable files as a list of file names.
1577
1578
Example:
1579
--------
1580
>>> files = driver.get_downloadable_files()
1581
"""
1582
if "se:downloadsEnabled" not in self.capabilities:
1583
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1584
1585
return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]["names"]
1586
1587
def download_file(self, file_name: str, target_directory: str) -> None:
1588
"""Downloads a file with the specified file name to the target
1589
directory.
1590
1591
Parameters:
1592
-----------
1593
file_name : str
1594
- The name of the file to download.
1595
1596
target_directory : str
1597
- The path to the directory to save the downloaded file.
1598
1599
Example:
1600
--------
1601
>>> driver.download_file("example.zip", "/path/to/directory")
1602
"""
1603
if "se:downloadsEnabled" not in self.capabilities:
1604
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1605
1606
if not os.path.exists(target_directory):
1607
os.makedirs(target_directory)
1608
1609
contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]["contents"]
1610
1611
with tempfile.TemporaryDirectory() as tmp_dir:
1612
zip_file = os.path.join(tmp_dir, file_name + ".zip")
1613
with open(zip_file, "wb") as file:
1614
file.write(base64.b64decode(contents))
1615
1616
with zipfile.ZipFile(zip_file, "r") as zip_ref:
1617
zip_ref.extractall(target_directory)
1618
1619
def delete_downloadable_files(self) -> None:
1620
"""Deletes all downloadable files.
1621
1622
Example:
1623
--------
1624
>>> driver.delete_downloadable_files()
1625
"""
1626
if "se:downloadsEnabled" not in self.capabilities:
1627
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1628
1629
self.execute(Command.DELETE_DOWNLOADABLE_FILES)
1630
1631
@property
1632
def fedcm(self) -> FedCM:
1633
"""Returns the Federated Credential Management (FedCM) dialog object
1634
for interaction.
1635
1636
Returns:
1637
-------
1638
FedCM: an object providing access to all Federated Credential Management (FedCM) dialog commands.
1639
1640
Examples:
1641
--------
1642
>>> title = driver.fedcm.title
1643
>>> subtitle = driver.fedcm.subtitle
1644
>>> dialog_type = driver.fedcm.dialog_type
1645
>>> accounts = driver.fedcm.account_list
1646
>>> driver.fedcm.select_account(0)
1647
>>> driver.fedcm.accept()
1648
>>> driver.fedcm.dismiss()
1649
>>> driver.fedcm.enable_delay()
1650
>>> driver.fedcm.disable_delay()
1651
>>> driver.fedcm.reset_cooldown()
1652
"""
1653
return self._fedcm
1654
1655
@property
1656
def supports_fedcm(self) -> bool:
1657
"""Returns whether the browser supports FedCM capabilities.
1658
1659
Example:
1660
--------
1661
>>> print(driver.supports_fedcm)
1662
"""
1663
return self.capabilities.get(ArgOptions.FEDCM_CAPABILITY, False)
1664
1665
def _require_fedcm_support(self):
1666
"""Raises an exception if FedCM is not supported."""
1667
if not self.supports_fedcm:
1668
raise WebDriverException(
1669
"This browser does not support Federated Credential Management. "
1670
"Please ensure you're using a supported browser."
1671
)
1672
1673
@property
1674
def dialog(self):
1675
"""Returns the FedCM dialog object for interaction.
1676
1677
Example:
1678
--------
1679
>>> dialog = driver.dialog
1680
"""
1681
self._require_fedcm_support()
1682
return Dialog(self)
1683
1684
def fedcm_dialog(self, timeout=5, poll_frequency=0.5, ignored_exceptions=None):
1685
"""Waits for and returns the FedCM dialog.
1686
1687
Parameters:
1688
-----------
1689
timeout : int
1690
- How long to wait for the dialog
1691
1692
poll_frequency : floatHow
1693
- Frequently to poll
1694
1695
ignored_exceptions : Any
1696
- Exceptions to ignore while waiting
1697
1698
Returns:
1699
-------
1700
The FedCM dialog object if found
1701
1702
Raises:
1703
-------
1704
TimeoutException if dialog doesn't appear
1705
WebDriverException if FedCM not supported
1706
"""
1707
from selenium.common.exceptions import NoAlertPresentException
1708
from selenium.webdriver.support.wait import WebDriverWait
1709
1710
self._require_fedcm_support()
1711
1712
if ignored_exceptions is None:
1713
ignored_exceptions = (NoAlertPresentException,)
1714
1715
def _check_fedcm():
1716
try:
1717
dialog = Dialog(self)
1718
return dialog if dialog.type else None
1719
except NoAlertPresentException:
1720
return None
1721
1722
wait = WebDriverWait(self, timeout, poll_frequency=poll_frequency, ignored_exceptions=ignored_exceptions)
1723
return wait.until(lambda _: _check_fedcm())
1724
1725