当前位置:   article > 正文

Selenium库原代码WebDriver及WebElement方法属性总结_selenium webdriver类

selenium webdriver类

简单示例:

  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By
  3. from selenium.webdriver.support.ui import WebDriverWait
  4. from selenium.webdriver.support import expected_conditions as EC
  5. driver = webdriver.Chrome()
  6. try:
  7. driver.get("https://www.hao123.com/")
  8. # print('*****driver_type',type(driver)) # driver_type <class 'selenium.webdriver.chrome.webdriver.WebDriver'>
  9. input_elements = driver.find_elements(By.TAG_NAME, "input")
  10. for input_element in input_elements:
  11. print(input_element.get_attribute("outerHTML"))
  12. search_input = driver.find_element(By.CSS_SELECTOR, 'input[data-hook="searchInput"]')
  13. # print('*****search_input_type',type(search_input)) # search_input_type <class 'selenium.webdriver.remote.webelement.WebElement'>
  14. search_input.send_keys("Selenium库")
  15. search_button = driver.find_element(By.CSS_SELECTOR, 'input[data-hook="searchSubmit"]')
  16. search_button.click()
  17. handles = driver.window_handles
  18. print(type(handles))
  19. if len(handles) > 1:
  20. driver.switch_to.window(handles[1])
  21. # Wait for the search results to load completely
  22. WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[id="content_left"]')))
  23. finally:
  24. driver.quit()
  25. # In this updated version, we added the following imports:
  26. # from selenium.webdriver.support.ui import WebDriverWait
  27. # from selenium.webdriver.support import expected_conditions as EC
  28. # We then added a WebDriverWait statement to wait for the presence of a specific element in the search results page:
  29. # WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[class="search-list-item"]')))
  30. # This statement waits for up to 10 seconds for an element with the CSS selector 'div[class="search-list-item"]' to appear on the page.
  31. # Once the element appears, the script continues to execute.
  32. # Note that you may need to adjust the CSS selector or the wait time to suit your specific needs.
  33. # (By.CSS_SELECTOR, 'div[id="content_left"]'),

------------
Selenium 的 WebDriver 实现的源代码提供了以下基本操作和功能:

初始化浏览器会话:通过实例化 WebDriver 对象,可以初始化一个特定的浏览器会话,如Chrome、Firefox等。
执行 JavaScript:可以通过 WebDriver.execute_script() 方法执行 JavaScript 代码,实现对页面的动态操作或数据获取。
窗口管理:WebDriver 可以管理浏览器窗口,包括最大化窗口、最小化窗口、设置窗口大小等功能。
元素定位:使用 WebDriver 提供的方法,如定位元素的 find_elementfind_elements 等,可以定位页面上的元素,进行操作或获取元素信息。
截图:WebDriver 可以对当前页面进行截图,并保存为图片文件,方便后续分析或记录。
下载文件:通过 WebDriver 控制浏览器下载文件,并保存到指定的目录中。
以上是 Selenium WebDriver 实现的源代码提供的一些基本操作和功能,这些功能可以帮助用户进行自动化测试、网页操作或数据采集等任务。
selenium.webdriver.chrome.webdriver.WebDriver是 Selenium WebDriver 中用于操作 Chrome 浏览器的主要类之一,它继承自 selenium.webdriver.remote.webdriver.WebDriver。这个类提供了丰富的方法和属性,用于实现对 Chrome 浏览器的控制和页面操作。

常用方法:

  1. get(url) - 打开指定的网址
  2. find_element(by=, value=) - 根据指定条件查找单个元素
  3. find_elements(by=, value=) - 根据指定条件查找多个元素
  4. execute_script(script, *args) - 在当前页面执行 JavaScript 代码
  5. close() - 关闭当前窗口
  6. quit() - 退出浏览器驱动并关闭所有窗口
  7. maximize_window() - 最大化浏览器窗口
  8. set_window_size(width, height) - 设置浏览器窗口大小
  9. save_screenshot(filename) - 保存当前页面的截图到指定文件
  10. switch_to.frame(frame_reference) - 切换到指定的 iframe
  11. switch_to.default_content() - 切回到默认的内容上下文
  12. switch_to.window(window_name) - 切换到指定窗口
  13. back() - 在浏览器中执行回退操作
  14. forward() - 在浏览器中执行前进操作
  15. refresh() - 刷新当前页面

常用属性:

current_url: 当前页面的 URL。
title: 当前页面的标题。
page_source: 当前页面的源代码。
window_handles: 当前浏览器窗口的句柄列表。
current_window_handle: 当前窗口的句柄。
capabilities: 浏览器的配置信息和能力。
name: 浏览器实例的名称。
session_id: 浏览器会话的 ID。
desired_capabilities: 期望的浏览器配置信息。

通过这些常用方法和属性,您可以更好地利用 selenium.webdriver.chrome.webdriver.WebDriver 类来控制 Chrome 浏览器进行自动化测试、网页操作等各种任务。

  1. """The WebDriver implementation."""
  2. import base64
  3. import contextlib
  4. import copy
  5. import os
  6. import pkgutil
  7. import types
  8. import typing
  9. import warnings
  10. import zipfile
  11. from abc import ABCMeta
  12. from base64 import b64decode
  13. from base64 import urlsafe_b64encode
  14. from contextlib import asynccontextmanager
  15. from contextlib import contextmanager
  16. from importlib import import_module
  17. from typing import Dict
  18. from typing import List
  19. from typing import Optional
  20. from typing import Union
  21. from selenium.common.exceptions import InvalidArgumentException
  22. from selenium.common.exceptions import JavascriptException
  23. from selenium.common.exceptions import NoSuchCookieException
  24. from selenium.common.exceptions import NoSuchElementException
  25. from selenium.common.exceptions import WebDriverException
  26. from selenium.webdriver.common.by import By
  27. from selenium.webdriver.common.options import BaseOptions
  28. from selenium.webdriver.common.print_page_options import PrintOptions
  29. from selenium.webdriver.common.timeouts import Timeouts
  30. from selenium.webdriver.common.virtual_authenticator import Credential
  31. from selenium.webdriver.common.virtual_authenticator import VirtualAuthenticatorOptions
  32. from selenium.webdriver.common.virtual_authenticator import (
  33. required_virtual_authenticator,
  34. )
  35. from selenium.webdriver.support.relative_locator import RelativeBy
  36. from .bidi_connection import BidiConnection
  37. from .command import Command
  38. from .errorhandler import ErrorHandler
  39. from .file_detector import FileDetector
  40. from .file_detector import LocalFileDetector
  41. from .mobile import Mobile
  42. from .remote_connection import RemoteConnection
  43. from .script_key import ScriptKey
  44. from .shadowroot import ShadowRoot
  45. from .switch_to import SwitchTo
  46. from .webelement import WebElement
  47. cdp = None
  48. def import_cdp():
  49. global cdp
  50. if not cdp:
  51. cdp = import_module("selenium.webdriver.common.bidi.cdp")
  52. def _create_caps(caps):
  53. """Makes a W3C alwaysMatch capabilities object.
  54. Filters out capability names that are not in the W3C spec. Spec-compliant
  55. drivers will reject requests containing unknown capability names.
  56. Moves the Firefox profile, if present, from the old location to the new Firefox
  57. options object.
  58. :Args:
  59. - caps - A dictionary of capabilities requested by the caller.
  60. """
  61. caps = copy.deepcopy(caps)
  62. always_match = {}
  63. for k, v in caps.items():
  64. always_match[k] = v
  65. return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}}
  66. def get_remote_connection(capabilities, command_executor, keep_alive, ignore_local_proxy=False):
  67. from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection
  68. from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection
  69. from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection
  70. from selenium.webdriver.safari.remote_connection import SafariRemoteConnection
  71. candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection]
  72. handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection)
  73. return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy)
  74. def create_matches(options: List[BaseOptions]) -> Dict:
  75. capabilities = {"capabilities": {}}
  76. opts = []
  77. for opt in options:
  78. opts.append(opt.to_capabilities())
  79. opts_size = len(opts)
  80. samesies = {}
  81. # Can not use bitwise operations on the dicts or lists due to
  82. # https://bugs.python.org/issue38210
  83. for i in range(opts_size):
  84. min_index = i
  85. if i + 1 < opts_size:
  86. first_keys = opts[min_index].keys()
  87. for kys in first_keys:
  88. if kys in opts[i + 1].keys():
  89. if opts[min_index][kys] == opts[i + 1][kys]:
  90. samesies.update({kys: opts[min_index][kys]})
  91. always = {}
  92. for k, v in samesies.items():
  93. always[k] = v
  94. for i in opts:
  95. for k in always:
  96. del i[k]
  97. capabilities["capabilities"]["alwaysMatch"] = always
  98. capabilities["capabilities"]["firstMatch"] = opts
  99. return capabilities
  100. class BaseWebDriver(metaclass=ABCMeta):
  101. """Abstract Base Class for all Webdriver subtypes.
  102. ABC's allow custom implementations of Webdriver to be registered so
  103. that isinstance type checks will succeed.
  104. """
  105. class WebDriver(BaseWebDriver):
  106. """Controls a browser by sending commands to a remote server. This server
  107. is expected to be running the WebDriver wire protocol as defined at
  108. https://www.selenium.dev/documentation/legacy/json_wire_protocol/.
  109. :Attributes:
  110. - session_id - String ID of the browser session started and controlled by this WebDriver.
  111. - capabilities - Dictionary of effective capabilities of this browser session as returned
  112. by the remote server. See https://www.selenium.dev/documentation/legacy/desired_capabilities/
  113. - command_executor - remote_connection.RemoteConnection object used to execute commands.
  114. - error_handler - errorhandler.ErrorHandler object used to handle errors.
  115. """
  116. _web_element_cls = WebElement
  117. _shadowroot_cls = ShadowRoot
  118. def __init__(
  119. self,
  120. command_executor="http://127.0.0.1:4444",
  121. keep_alive=True,
  122. file_detector=None,
  123. options: Union[BaseOptions, List[BaseOptions]] = None,
  124. ) -> None:
  125. """Create a new driver that will issue commands using the wire
  126. protocol.
  127. :Args:
  128. - command_executor - Either a string representing URL of the remote server or a custom
  129. remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
  130. - keep_alive - Whether to configure remote_connection.RemoteConnection to use
  131. HTTP keep-alive. Defaults to True.
  132. - file_detector - Pass custom file detector object during instantiation. If None,
  133. then default LocalFileDetector() will be used.
  134. - options - instance of a driver options.Options class
  135. """
  136. if isinstance(options, list):
  137. capabilities = create_matches(options)
  138. _ignore_local_proxy = False
  139. else:
  140. capabilities = options.to_capabilities()
  141. _ignore_local_proxy = options._ignore_local_proxy
  142. self.command_executor = command_executor
  143. if isinstance(self.command_executor, (str, bytes)):
  144. self.command_executor = get_remote_connection(
  145. capabilities,
  146. command_executor=command_executor,
  147. keep_alive=keep_alive,
  148. ignore_local_proxy=_ignore_local_proxy,
  149. )
  150. self._is_remote = True
  151. self.session_id = None
  152. self.caps = {}
  153. self.pinned_scripts = {}
  154. self.error_handler = ErrorHandler()
  155. self._switch_to = SwitchTo(self)
  156. self._mobile = Mobile(self)
  157. self.file_detector = file_detector or LocalFileDetector()
  158. self._authenticator_id = None
  159. self.start_client()
  160. self.start_session(capabilities)
  161. def __repr__(self):
  162. return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'
  163. def __enter__(self):
  164. return self
  165. def __exit__(
  166. self,
  167. exc_type: typing.Optional[typing.Type[BaseException]],
  168. exc: typing.Optional[BaseException],
  169. traceback: typing.Optional[types.TracebackType],
  170. ):
  171. self.quit()
  172. @contextmanager
  173. def file_detector_context(self, file_detector_class, *args, **kwargs):
  174. """Overrides the current file detector (if necessary) in limited
  175. context. Ensures the original file detector is set afterwards.
  176. Example::
  177. with webdriver.file_detector_context(UselessFileDetector):
  178. someinput.send_keys('/etc/hosts')
  179. :Args:
  180. - file_detector_class - Class of the desired file detector. If the class is different
  181. from the current file_detector, then the class is instantiated with args and kwargs
  182. and used as a file detector during the duration of the context manager.
  183. - args - Optional arguments that get passed to the file detector class during
  184. instantiation.
  185. - kwargs - Keyword arguments, passed the same way as args.
  186. """
  187. last_detector = None
  188. if not isinstance(self.file_detector, file_detector_class):
  189. last_detector = self.file_detector
  190. self.file_detector = file_detector_class(*args, **kwargs)
  191. try:
  192. yield
  193. finally:
  194. if last_detector:
  195. self.file_detector = last_detector
  196. @property
  197. def mobile(self) -> Mobile:
  198. return self._mobile
  199. @property
  200. def name(self) -> str:
  201. """Returns the name of the underlying browser for this instance.
  202. :Usage:
  203. ::
  204. name = driver.name
  205. """
  206. if "browserName" in self.caps:
  207. return self.caps["browserName"]
  208. raise KeyError("browserName not specified in session capabilities")
  209. def start_client(self):
  210. """Called before starting a new session.
  211. This method may be overridden to define custom startup behavior.
  212. """
  213. pass
  214. def stop_client(self):
  215. """Called after executing a quit command.
  216. This method may be overridden to define custom shutdown
  217. behavior.
  218. """
  219. pass
  220. def start_session(self, capabilities: dict) -> None:
  221. """Creates a new session with the desired capabilities.
  222. :Args:
  223. - capabilities - a capabilities dict to start the session with.
  224. """
  225. caps = _create_caps(capabilities)
  226. response = self.execute(Command.NEW_SESSION, caps)["value"]
  227. self.session_id = response.get("sessionId")
  228. self.caps = response.get("capabilities")
  229. def _wrap_value(self, value):
  230. if isinstance(value, dict):
  231. converted = {}
  232. for key, val in value.items():
  233. converted[key] = self._wrap_value(val)
  234. return converted
  235. if isinstance(value, self._web_element_cls):
  236. return {"element-6066-11e4-a52e-4f735466cecf": value.id}
  237. if isinstance(value, self._shadowroot_cls):
  238. return {"shadow-6066-11e4-a52e-4f735466cecf": value.id}
  239. if isinstance(value, list):
  240. return list(self._wrap_value(item) for item in value)
  241. return value
  242. def create_web_element(self, element_id: str) -> WebElement:
  243. """Creates a web element with the specified `element_id`."""
  244. return self._web_element_cls(self, element_id)
  245. def _unwrap_value(self, value):
  246. if isinstance(value, dict):
  247. if "element-6066-11e4-a52e-4f735466cecf" in value:
  248. return self.create_web_element(value["element-6066-11e4-a52e-4f735466cecf"])
  249. if "shadow-6066-11e4-a52e-4f735466cecf" in value:
  250. return self._shadowroot_cls(self, value["shadow-6066-11e4-a52e-4f735466cecf"])
  251. for key, val in value.items():
  252. value[key] = self._unwrap_value(val)
  253. return value
  254. if isinstance(value, list):
  255. return list(self._unwrap_value(item) for item in value)
  256. return value
  257. def execute(self, driver_command: str, params: dict = None) -> dict:
  258. """Sends a command to be executed by a command.CommandExecutor.
  259. :Args:
  260. - driver_command: The name of the command to execute as a string.
  261. - params: A dictionary of named parameters to send with the command.
  262. :Returns:
  263. The command's JSON response loaded into a dictionary object.
  264. """
  265. params = self._wrap_value(params)
  266. if self.session_id:
  267. if not params:
  268. params = {"sessionId": self.session_id}
  269. elif "sessionId" not in params:
  270. params["sessionId"] = self.session_id
  271. response = self.command_executor.execute(driver_command, params)
  272. if response:
  273. self.error_handler.check_response(response)
  274. response["value"] = self._unwrap_value(response.get("value", None))
  275. return response
  276. # If the server doesn't send a response, assume the command was
  277. # a success
  278. return {"success": 0, "value": None, "sessionId": self.session_id}
  279. def get(self, url: str) -> None:
  280. """Loads a web page in the current browser session."""
  281. self.execute(Command.GET, {"url": url})
  282. @property
  283. def title(self) -> str:
  284. """Returns the title of the current page.
  285. :Usage:
  286. ::
  287. title = driver.title
  288. """
  289. return self.execute(Command.GET_TITLE).get("value", "")
  290. def pin_script(self, script: str, script_key=None) -> ScriptKey:
  291. """Store common javascript scripts to be executed later by a unique
  292. hashable ID."""
  293. script_key_instance = ScriptKey(script_key)
  294. self.pinned_scripts[script_key_instance.id] = script
  295. return script_key_instance
  296. def unpin(self, script_key: ScriptKey) -> None:
  297. """Remove a pinned script from storage."""
  298. try:
  299. self.pinned_scripts.pop(script_key.id)
  300. except KeyError:
  301. raise KeyError(f"No script with key: {script_key} existed in {self.pinned_scripts}") from None
  302. def get_pinned_scripts(self) -> List[str]:
  303. return list(self.pinned_scripts)
  304. def execute_script(self, script, *args):
  305. """Synchronously Executes JavaScript in the current window/frame.
  306. :Args:
  307. - script: The JavaScript to execute.
  308. - \\*args: Any applicable arguments for your JavaScript.
  309. :Usage:
  310. ::
  311. driver.execute_script('return document.title;')
  312. """
  313. if isinstance(script, ScriptKey):
  314. try:
  315. script = self.pinned_scripts[script.id]
  316. except KeyError:
  317. raise JavascriptException("Pinned script could not be found")
  318. converted_args = list(args)
  319. command = Command.W3C_EXECUTE_SCRIPT
  320. return self.execute(command, {"script": script, "args": converted_args})["value"]
  321. def execute_async_script(self, script: str, *args):
  322. """Asynchronously Executes JavaScript in the current window/frame.
  323. :Args:
  324. - script: The JavaScript to execute.
  325. - \\*args: Any applicable arguments for your JavaScript.
  326. :Usage:
  327. ::
  328. script = "var callback = arguments[arguments.length - 1]; " \\
  329. "window.setTimeout(function(){ callback('timeout') }, 3000);"
  330. driver.execute_async_script(script)
  331. """
  332. converted_args = list(args)
  333. command = Command.W3C_EXECUTE_SCRIPT_ASYNC
  334. return self.execute(command, {"script": script, "args": converted_args})["value"]
  335. @property
  336. def current_url(self) -> str:
  337. """Gets the URL of the current page.
  338. :Usage:
  339. ::
  340. driver.current_url
  341. """
  342. return self.execute(Command.GET_CURRENT_URL)["value"]
  343. @property
  344. def page_source(self) -> str:
  345. """Gets the source of the current page.
  346. :Usage:
  347. ::
  348. driver.page_source
  349. """
  350. return self.execute(Command.GET_PAGE_SOURCE)["value"]
  351. def close(self) -> None:
  352. """Closes the current window.
  353. :Usage:
  354. ::
  355. driver.close()
  356. """
  357. self.execute(Command.CLOSE)
  358. def quit(self) -> None:
  359. """Quits the driver and closes every associated window.
  360. :Usage:
  361. ::
  362. driver.quit()
  363. """
  364. try:
  365. self.execute(Command.QUIT)
  366. finally:
  367. self.stop_client()
  368. self.command_executor.close()
  369. @property
  370. def current_window_handle(self) -> str:
  371. """Returns the handle of the current window.
  372. :Usage:
  373. ::
  374. driver.current_window_handle
  375. """
  376. return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)["value"]
  377. @property
  378. def window_handles(self) -> List[str]:
  379. """Returns the handles of all windows within the current session.
  380. :Usage:
  381. ::
  382. driver.window_handles
  383. """
  384. return self.execute(Command.W3C_GET_WINDOW_HANDLES)["value"]
  385. def maximize_window(self) -> None:
  386. """Maximizes the current window that webdriver is using."""
  387. command = Command.W3C_MAXIMIZE_WINDOW
  388. self.execute(command, None)
  389. def fullscreen_window(self) -> None:
  390. """Invokes the window manager-specific 'full screen' operation."""
  391. self.execute(Command.FULLSCREEN_WINDOW)
  392. def minimize_window(self) -> None:
  393. """Invokes the window manager-specific 'minimize' operation."""
  394. self.execute(Command.MINIMIZE_WINDOW)
  395. def print_page(self, print_options: Optional[PrintOptions] = None) -> str:
  396. """Takes PDF of the current page.
  397. The driver makes a best effort to return a PDF based on the
  398. provided parameters.
  399. """
  400. options = {}
  401. if print_options:
  402. options = print_options.to_dict()
  403. return self.execute(Command.PRINT_PAGE, options)["value"]
  404. @property
  405. def switch_to(self) -> SwitchTo:
  406. """
  407. :Returns:
  408. - SwitchTo: an object containing all options to switch focus into
  409. :Usage:
  410. ::
  411. element = driver.switch_to.active_element
  412. alert = driver.switch_to.alert
  413. driver.switch_to.default_content()
  414. driver.switch_to.frame('frame_name')
  415. driver.switch_to.frame(1)
  416. driver.switch_to.frame(driver.find_elements(By.TAG_NAME, "iframe")[0])
  417. driver.switch_to.parent_frame()
  418. driver.switch_to.window('main')
  419. """
  420. return self._switch_to
  421. # Navigation
  422. def back(self) -> None:
  423. """Goes one step backward in the browser history.
  424. :Usage:
  425. ::
  426. driver.back()
  427. """
  428. self.execute(Command.GO_BACK)
  429. def forward(self) -> None:
  430. """Goes one step forward in the browser history.
  431. :Usage:
  432. ::
  433. driver.forward()
  434. """
  435. self.execute(Command.GO_FORWARD)
  436. def refresh(self) -> None:
  437. """Refreshes the current page.
  438. :Usage:
  439. ::
  440. driver.refresh()
  441. """
  442. self.execute(Command.REFRESH)
  443. # Options
  444. def get_cookies(self) -> List[dict]:
  445. """Returns a set of dictionaries, corresponding to cookies visible in
  446. the current session.
  447. :Usage:
  448. ::
  449. driver.get_cookies()
  450. """
  451. return self.execute(Command.GET_ALL_COOKIES)["value"]
  452. def get_cookie(self, name) -> typing.Optional[typing.Dict]:
  453. """Get a single cookie by name. Returns the cookie if found, None if
  454. not.
  455. :Usage:
  456. ::
  457. driver.get_cookie('my_cookie')
  458. """
  459. with contextlib.suppress(NoSuchCookieException):
  460. return self.execute(Command.GET_COOKIE, {"name": name})["value"]
  461. return None
  462. def delete_cookie(self, name) -> None:
  463. """Deletes a single cookie with the given name.
  464. :Usage:
  465. ::
  466. driver.delete_cookie('my_cookie')
  467. """
  468. self.execute(Command.DELETE_COOKIE, {"name": name})
  469. def delete_all_cookies(self) -> None:
  470. """Delete all cookies in the scope of the session.
  471. :Usage:
  472. ::
  473. driver.delete_all_cookies()
  474. """
  475. self.execute(Command.DELETE_ALL_COOKIES)
  476. def add_cookie(self, cookie_dict) -> None:
  477. """Adds a cookie to your current session.
  478. :Args:
  479. - cookie_dict: A dictionary object, with required keys - "name" and "value";
  480. optional keys - "path", "domain", "secure", "httpOnly", "expiry", "sameSite"
  481. :Usage:
  482. ::
  483. driver.add_cookie({'name' : 'foo', 'value' : 'bar'})
  484. driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'})
  485. driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/', 'secure' : True})
  486. driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'sameSite' : 'Strict'})
  487. """
  488. if "sameSite" in cookie_dict:
  489. assert cookie_dict["sameSite"] in ["Strict", "Lax", "None"]
  490. self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})
  491. else:
  492. self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})
  493. # Timeouts
  494. def implicitly_wait(self, time_to_wait: float) -> None:
  495. """Sets a sticky timeout to implicitly wait for an element to be found,
  496. or a command to complete. This method only needs to be called one time
  497. per session. To set the timeout for calls to execute_async_script, see
  498. set_script_timeout.
  499. :Args:
  500. - time_to_wait: Amount of time to wait (in seconds)
  501. :Usage:
  502. ::
  503. driver.implicitly_wait(30)
  504. """
  505. self.execute(Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)})
  506. def set_script_timeout(self, time_to_wait: float) -> None:
  507. """Set the amount of time that the script should wait during an
  508. execute_async_script call before throwing an error.
  509. :Args:
  510. - time_to_wait: The amount of time to wait (in seconds)
  511. :Usage:
  512. ::
  513. driver.set_script_timeout(30)
  514. """
  515. self.execute(Command.SET_TIMEOUTS, {"script": int(float(time_to_wait) * 1000)})
  516. def set_page_load_timeout(self, time_to_wait: float) -> None:
  517. """Set the amount of time to wait for a page load to complete before
  518. throwing an error.
  519. :Args:
  520. - time_to_wait: The amount of time to wait
  521. :Usage:
  522. ::
  523. driver.set_page_load_timeout(30)
  524. """
  525. try:
  526. self.execute(Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)})
  527. except WebDriverException:
  528. self.execute(Command.SET_TIMEOUTS, {"ms": float(time_to_wait) * 1000, "type": "page load"})
  529. @property
  530. def timeouts(self) -> Timeouts:
  531. """Get all the timeouts that have been set on the current session.
  532. :Usage:
  533. ::
  534. driver.timeouts
  535. :rtype: Timeout
  536. """
  537. timeouts = self.execute(Command.GET_TIMEOUTS)["value"]
  538. timeouts["implicit_wait"] = timeouts.pop("implicit") / 1000
  539. timeouts["page_load"] = timeouts.pop("pageLoad") / 1000
  540. timeouts["script"] = timeouts.pop("script") / 1000
  541. return Timeouts(**timeouts)
  542. @timeouts.setter
  543. def timeouts(self, timeouts) -> None:
  544. """Set all timeouts for the session. This will override any previously
  545. set timeouts.
  546. :Usage:
  547. ::
  548. my_timeouts = Timeouts()
  549. my_timeouts.implicit_wait = 10
  550. driver.timeouts = my_timeouts
  551. """
  552. _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"]
  553. def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement:
  554. """Find an element given a By strategy and locator.
  555. :Usage:
  556. ::
  557. element = driver.find_element(By.ID, 'foo')
  558. :rtype: WebElement
  559. """
  560. if isinstance(by, RelativeBy):
  561. elements = self.find_elements(by=by, value=value)
  562. if not elements:
  563. raise NoSuchElementException(f"Cannot locate relative element with: {by.root}")
  564. return elements[0]
  565. if by == By.ID:
  566. by = By.CSS_SELECTOR
  567. value = f'[id="{value}"]'
  568. elif by == By.CLASS_NAME:
  569. by = By.CSS_SELECTOR
  570. value = f".{value}"
  571. elif by == By.NAME:
  572. by = By.CSS_SELECTOR
  573. value = f'[name="{value}"]'
  574. return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
  575. def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElement]:
  576. """Find elements given a By strategy and locator.
  577. :Usage:
  578. ::
  579. elements = driver.find_elements(By.CLASS_NAME, 'foo')
  580. :rtype: list of WebElement
  581. """
  582. if isinstance(by, RelativeBy):
  583. _pkg = ".".join(__name__.split(".")[:-1])
  584. raw_function = pkgutil.get_data(_pkg, "findElements.js").decode("utf8")
  585. find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);"
  586. return self.execute_script(find_element_js, by.to_dict())
  587. if by == By.ID:
  588. by = By.CSS_SELECTOR
  589. value = f'[id="{value}"]'
  590. elif by == By.CLASS_NAME:
  591. by = By.CSS_SELECTOR
  592. value = f".{value}"
  593. elif by == By.NAME:
  594. by = By.CSS_SELECTOR
  595. value = f'[name="{value}"]'
  596. # Return empty list if driver returns null
  597. # See https://github.com/SeleniumHQ/selenium/issues/4555
  598. return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or []
  599. @property
  600. def capabilities(self) -> dict:
  601. """Returns the drivers current capabilities being used."""
  602. return self.caps
  603. def get_screenshot_as_file(self, filename) -> bool:
  604. """Saves a screenshot of the current window to a PNG image file.
  605. Returns False if there is any IOError, else returns True. Use full
  606. paths in your filename.
  607. :Args:
  608. - filename: The full path you wish to save your screenshot to. This
  609. should end with a `.png` extension.
  610. :Usage:
  611. ::
  612. driver.get_screenshot_as_file('/Screenshots/foo.png')
  613. """
  614. if not str(filename).lower().endswith(".png"):
  615. warnings.warn(
  616. "name used for saved screenshot does not match file type. It should end with a `.png` extension",
  617. UserWarning,
  618. stacklevel=2,
  619. )
  620. png = self.get_screenshot_as_png()
  621. try:
  622. with open(filename, "wb") as f:
  623. f.write(png)
  624. except OSError:
  625. return False
  626. finally:
  627. del png
  628. return True
  629. def save_screenshot(self, filename) -> bool:
  630. """Saves a screenshot of the current window to a PNG image file.
  631. Returns False if there is any IOError, else returns True. Use full
  632. paths in your filename.
  633. :Args:
  634. - filename: The full path you wish to save your screenshot to. This
  635. should end with a `.png` extension.
  636. :Usage:
  637. ::
  638. driver.save_screenshot('/Screenshots/foo.png')
  639. """
  640. return self.get_screenshot_as_file(filename)
  641. def get_screenshot_as_png(self) -> bytes:
  642. """Gets the screenshot of the current window as a binary data.
  643. :Usage:
  644. ::
  645. driver.get_screenshot_as_png()
  646. """
  647. return b64decode(self.get_screenshot_as_base64().encode("ascii"))
  648. def get_screenshot_as_base64(self) -> str:
  649. """Gets the screenshot of the current window as a base64 encoded string
  650. which is useful in embedded images in HTML.
  651. :Usage:
  652. ::
  653. driver.get_screenshot_as_base64()
  654. """
  655. return self.execute(Command.SCREENSHOT)["value"]
  656. def set_window_size(self, width, height, windowHandle: str = "current") -> None:
  657. """Sets the width and height of the current window. (window.resizeTo)
  658. :Args:
  659. - width: the width in pixels to set the window to
  660. - height: the height in pixels to set the window to
  661. :Usage:
  662. ::
  663. driver.set_window_size(800,600)
  664. """
  665. self._check_if_window_handle_is_current(windowHandle)
  666. self.set_window_rect(width=int(width), height=int(height))
  667. def get_window_size(self, windowHandle: str = "current") -> dict:
  668. """Gets the width and height of the current window.
  669. :Usage:
  670. ::
  671. driver.get_window_size()
  672. """
  673. self._check_if_window_handle_is_current(windowHandle)
  674. size = self.get_window_rect()
  675. if size.get("value", None):
  676. size = size["value"]
  677. return {k: size[k] for k in ("width", "height")}
  678. def set_window_position(self, x, y, windowHandle: str = "current") -> dict:
  679. """Sets the x,y position of the current window. (window.moveTo)
  680. :Args:
  681. - x: the x-coordinate in pixels to set the window position
  682. - y: the y-coordinate in pixels to set the window position
  683. :Usage:
  684. ::
  685. driver.set_window_position(0,0)
  686. """
  687. self._check_if_window_handle_is_current(windowHandle)
  688. return self.set_window_rect(x=int(x), y=int(y))
  689. def get_window_position(self, windowHandle="current") -> dict:
  690. """Gets the x,y position of the current window.
  691. :Usage:
  692. ::
  693. driver.get_window_position()
  694. """
  695. self._check_if_window_handle_is_current(windowHandle)
  696. position = self.get_window_rect()
  697. return {k: position[k] for k in ("x", "y")}
  698. def _check_if_window_handle_is_current(self, windowHandle: str) -> None:
  699. """Warns if the window handle is not equal to `current`."""
  700. if windowHandle != "current":
  701. warnings.warn("Only 'current' window is supported for W3C compatible browsers.", stacklevel=2)
  702. def get_window_rect(self) -> dict:
  703. """Gets the x, y coordinates of the window as well as height and width
  704. of the current window.
  705. :Usage:
  706. ::
  707. driver.get_window_rect()
  708. """
  709. return self.execute(Command.GET_WINDOW_RECT)["value"]
  710. def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict:
  711. """Sets the x, y coordinates of the window as well as height and width
  712. of the current window. This method is only supported for W3C compatible
  713. browsers; other browsers should use `set_window_position` and
  714. `set_window_size`.
  715. :Usage:
  716. ::
  717. driver.set_window_rect(x=10, y=10)
  718. driver.set_window_rect(width=100, height=200)
  719. driver.set_window_rect(x=10, y=10, width=100, height=200)
  720. """
  721. if (x is None and y is None) and (not height and not width):
  722. raise InvalidArgumentException("x and y or height and width need values")
  723. return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height})["value"]
  724. @property
  725. def file_detector(self) -> FileDetector:
  726. return self._file_detector
  727. @file_detector.setter
  728. def file_detector(self, detector) -> None:
  729. """Set the file detector to be used when sending keyboard input. By
  730. default, this is set to a file detector that does nothing.
  731. see FileDetector
  732. see LocalFileDetector
  733. see UselessFileDetector
  734. :Args:
  735. - detector: The detector to use. Must not be None.
  736. """
  737. if not detector:
  738. raise WebDriverException("You may not set a file detector that is null")
  739. if not isinstance(detector, FileDetector):
  740. raise WebDriverException("Detector has to be instance of FileDetector")
  741. self._file_detector = detector
  742. @property
  743. def orientation(self):
  744. """Gets the current orientation of the device.
  745. :Usage:
  746. ::
  747. orientation = driver.orientation
  748. """
  749. return self.execute(Command.GET_SCREEN_ORIENTATION)["value"]
  750. @orientation.setter
  751. def orientation(self, value) -> None:
  752. """Sets the current orientation of the device.
  753. :Args:
  754. - value: orientation to set it to.
  755. :Usage:
  756. ::
  757. driver.orientation = 'landscape'
  758. """
  759. allowed_values = ["LANDSCAPE", "PORTRAIT"]
  760. if value.upper() in allowed_values:
  761. self.execute(Command.SET_SCREEN_ORIENTATION, {"orientation": value})
  762. else:
  763. raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")
  764. @property
  765. def log_types(self):
  766. """Gets a list of the available log types. This only works with w3c
  767. compliant browsers.
  768. :Usage:
  769. ::
  770. driver.log_types
  771. """
  772. return self.execute(Command.GET_AVAILABLE_LOG_TYPES)["value"]
  773. def get_log(self, log_type):
  774. """Gets the log for a given log type.
  775. :Args:
  776. - log_type: type of log that which will be returned
  777. :Usage:
  778. ::
  779. driver.get_log('browser')
  780. driver.get_log('driver')
  781. driver.get_log('client')
  782. driver.get_log('server')
  783. """
  784. return self.execute(Command.GET_LOG, {"type": log_type})["value"]
  785. @asynccontextmanager
  786. async def bidi_connection(self):
  787. global cdp
  788. import_cdp()
  789. if self.caps.get("se:cdp"):
  790. ws_url = self.caps.get("se:cdp")
  791. version = self.caps.get("se:cdpVersion").split(".")[0]
  792. else:
  793. version, ws_url = self._get_cdp_details()
  794. if not ws_url:
  795. raise WebDriverException("Unable to find url to connect to from capabilities")
  796. devtools = cdp.import_devtools(version)
  797. async with cdp.open_cdp(ws_url) as conn:
  798. targets = await conn.execute(devtools.target.get_targets())
  799. target_id = targets[0].target_id
  800. async with conn.open_session(target_id) as session:
  801. yield BidiConnection(session, cdp, devtools)
  802. def _get_cdp_details(self):
  803. import json
  804. import urllib3
  805. http = urllib3.PoolManager()
  806. _firefox = False
  807. if self.caps.get("browserName") == "chrome":
  808. debugger_address = self.caps.get("goog:chromeOptions").get("debuggerAddress")
  809. elif self.caps.get("browserName") == "msedge":
  810. debugger_address = self.caps.get("ms:edgeOptions").get("debuggerAddress")
  811. else:
  812. _firefox = True
  813. debugger_address = self.caps.get("moz:debuggerAddress")
  814. res = http.request("GET", f"http://{debugger_address}/json/version")
  815. data = json.loads(res.data)
  816. browser_version = data.get("Browser")
  817. websocket_url = data.get("webSocketDebuggerUrl")
  818. import re
  819. if _firefox:
  820. # Mozilla Automation Team asked to only support 85
  821. # until WebDriver Bidi is available.
  822. version = 85
  823. else:
  824. version = re.search(r".*/(\d+)\.", browser_version).group(1)
  825. return version, websocket_url
  826. # Virtual Authenticator Methods
  827. def add_virtual_authenticator(self, options: VirtualAuthenticatorOptions) -> None:
  828. """Adds a virtual authenticator with the given options."""
  829. self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict())["value"]
  830. @property
  831. def virtual_authenticator_id(self) -> str:
  832. """Returns the id of the virtual authenticator."""
  833. return self._authenticator_id
  834. @required_virtual_authenticator
  835. def remove_virtual_authenticator(self) -> None:
  836. """Removes a previously added virtual authenticator.
  837. The authenticator is no longer valid after removal, so no
  838. methods may be called.
  839. """
  840. self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {"authenticatorId": self._authenticator_id})
  841. self._authenticator_id = None
  842. @required_virtual_authenticator
  843. def add_credential(self, credential: Credential) -> None:
  844. """Injects a credential into the authenticator."""
  845. self.execute(Command.ADD_CREDENTIAL, {**credential.to_dict(), "authenticatorId": self._authenticator_id})
  846. @required_virtual_authenticator
  847. def get_credentials(self) -> List[Credential]:
  848. """Returns the list of credentials owned by the authenticator."""
  849. credential_data = self.execute(Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id})
  850. return [Credential.from_dict(credential) for credential in credential_data["value"]]
  851. @required_virtual_authenticator
  852. def remove_credential(self, credential_id: Union[str, bytearray]) -> None:
  853. """Removes a credential from the authenticator."""
  854. # Check if the credential is bytearray converted to b64 string
  855. if isinstance(credential_id, bytearray):
  856. credential_id = urlsafe_b64encode(credential_id).decode()
  857. self.execute(
  858. Command.REMOVE_CREDENTIAL, {"credentialId": credential_id, "authenticatorId": self._authenticator_id}
  859. )
  860. @required_virtual_authenticator
  861. def remove_all_credentials(self) -> None:
  862. """Removes all credentials from the authenticator."""
  863. self.execute(Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id})
  864. @required_virtual_authenticator
  865. def set_user_verified(self, verified: bool) -> None:
  866. """Sets whether the authenticator will simulate success or fail on user
  867. verification.
  868. verified: True if the authenticator will pass user verification, False otherwise.
  869. """
  870. self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified})
  871. def get_downloadable_files(self) -> dict:
  872. """Retrieves the downloadable files as a map of file names and their
  873. corresponding URLs."""
  874. if "se:downloadsEnabled" not in self.capabilities:
  875. raise WebDriverException("You must enable downloads in order to work with downloadable files.")
  876. return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]["names"]
  877. def download_file(self, file_name: str, target_directory: str) -> None:
  878. """Downloads a file with the specified file name to the target
  879. directory.
  880. file_name: The name of the file to download.
  881. target_directory: The path to the directory to save the downloaded file.
  882. """
  883. if "se:downloadsEnabled" not in self.capabilities:
  884. raise WebDriverException("You must enable downloads in order to work with downloadable files.")
  885. if not os.path.exists(target_directory):
  886. os.makedirs(target_directory)
  887. contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]["contents"]
  888. target_file = os.path.join(target_directory, file_name)
  889. with open(target_file, "wb") as file:
  890. file.write(base64.b64decode(contents))
  891. with zipfile.ZipFile(target_file, "r") as zip_ref:
  892. zip_ref.extractall(target_directory)
  893. def delete_downloadable_files(self) -> None:
  894. """Deletes all downloadable files."""
  895. if "se:downloadsEnabled" not in self.capabilities:
  896. raise WebDriverException("You must enable downloads in order to work with downloadable files.")
  897. self.execute(Command.DELETE_DOWNLOADABLE_FILES)

------------------- 

 这段代码看起来是关于对 Web 元素进行操作的,使用了 Selenium WebDriver 库。它定义了一个 WebElement 类用于表示网页中的 DOM 元素,并提供了一系列方法来操作元素,比如点击、输入文本、获取属性、截图等。除此之外,还包括了一些私有方法和元素定位的方法。这段代码主要是基于 Python 的 Selenium 库,用于模拟浏览器操作网页元素。
selenium.webdriver.remote.webelement.WebElement是 Selenium WebDriver 中用于表示网页上的元素(如按钮、输入框、链接等)的主要类之一。通过该类,可以对网页上的元素进行查找、操作和获取属性等操作。

常用方法:
click(self) -> None: 点击元素。
submit(self): 提交表单。
clear(self) -> None: 清除文本(如果是文本输入元素)。
send_keys(self, *value) -> None: 模拟向元素输入文本。
find_element(self, by=By.ID, value=None) -> WebElement: 根据查找策略和定位器查找元素。
find_elements(self, by=By.ID, value=None) -> List[WebElement]: 根据查找策略和定位器查找多个元素。
get_attribute(name): 获取元素的指定属性值。
is_displayed(): 判断元素是否可见。
is_enabled(): 判断元素是否可操作。
is_selected(): 判断元素是否被选中(针对复选框、单选框等元素)。

常用属性:
text - 获取元素的文本内容
tag_name - 获取元素的标签名
size - 获取元素的尺寸
location - 获取元素在页面中的位置
rect - 获取元素的位置和大小信息的字典表示


通过这些方法和属性,我们可以对网页元素执行点击、输入文本、获取属性、截图等操作,并获取元素的相关信息。这些方法和属性可以帮助我们实现自动化测试、网页数据采集等功能。

  1. from __future__ import annotations
  2. import os
  3. import pkgutil
  4. import warnings
  5. import zipfile
  6. from abc import ABCMeta
  7. from base64 import b64decode
  8. from base64 import encodebytes
  9. from hashlib import md5 as md5_hash
  10. from io import BytesIO
  11. from typing import List
  12. from selenium.common.exceptions import JavascriptException
  13. from selenium.common.exceptions import WebDriverException
  14. from selenium.webdriver.common.by import By
  15. from selenium.webdriver.common.utils import keys_to_typing
  16. from .command import Command
  17. from .shadowroot import ShadowRoot
  18. # TODO: When moving to supporting python 3.9 as the minimum version we can
  19. # use built in importlib_resources.files.
  20. getAttribute_js = None
  21. isDisplayed_js = None
  22. def _load_js():
  23. global getAttribute_js
  24. global isDisplayed_js
  25. _pkg = ".".join(__name__.split(".")[:-1])
  26. getAttribute_js = pkgutil.get_data(_pkg, "getAttribute.js").decode("utf8")
  27. isDisplayed_js = pkgutil.get_data(_pkg, "isDisplayed.js").decode("utf8")
  28. class BaseWebElement(metaclass=ABCMeta):
  29. """Abstract Base Class for WebElement.
  30. ABC's will allow custom types to be registered as a WebElement to
  31. pass type checks.
  32. """
  33. pass
  34. class WebElement(BaseWebElement):
  35. """Represents a DOM element.
  36. Generally, all interesting operations that interact with a document will be
  37. performed through this interface.
  38. All method calls will do a freshness check to ensure that the element
  39. reference is still valid. This essentially determines whether the
  40. element is still attached to the DOM. If this test fails, then an
  41. ``StaleElementReferenceException`` is thrown, and all future calls to this
  42. instance will fail.
  43. """
  44. def __init__(self, parent, id_) -> None:
  45. self._parent = parent
  46. self._id = id_
  47. def __repr__(self):
  48. return f'<{type(self).__module__}.{type(self).__name__} (session="{self._parent.session_id}", element="{self._id}")>'
  49. @property
  50. def tag_name(self) -> str:
  51. """This element's ``tagName`` property."""
  52. return self._execute(Command.GET_ELEMENT_TAG_NAME)["value"]
  53. @property
  54. def text(self) -> str:
  55. """The text of the element."""
  56. return self._execute(Command.GET_ELEMENT_TEXT)["value"]
  57. def click(self) -> None:
  58. """Clicks the element."""
  59. self._execute(Command.CLICK_ELEMENT)
  60. def submit(self):
  61. """Submits a form."""
  62. script = (
  63. "/* submitForm */var form = arguments[0];\n"
  64. 'while (form.nodeName != "FORM" && form.parentNode) {\n'
  65. " form = form.parentNode;\n"
  66. "}\n"
  67. "if (!form) { throw Error('Unable to find containing form element'); }\n"
  68. "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n"
  69. "var e = form.ownerDocument.createEvent('Event');\n"
  70. "e.initEvent('submit', true, true);\n"
  71. "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
  72. )
  73. try:
  74. self._parent.execute_script(script, self)
  75. except JavascriptException as exc:
  76. raise WebDriverException("To submit an element, it must be nested inside a form element") from exc
  77. def clear(self) -> None:
  78. """Clears the text if it's a text entry element."""
  79. self._execute(Command.CLEAR_ELEMENT)
  80. def get_property(self, name) -> str | bool | WebElement | dict:
  81. """Gets the given property of the element.
  82. :Args:
  83. - name - Name of the property to retrieve.
  84. :Usage:
  85. ::
  86. text_length = target_element.get_property("text_length")
  87. """
  88. try:
  89. return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"]
  90. except WebDriverException:
  91. # if we hit an end point that doesn't understand getElementProperty lets fake it
  92. return self.parent.execute_script("return arguments[0][arguments[1]]", self, name)
  93. def get_dom_attribute(self, name) -> str:
  94. """Gets the given attribute of the element. Unlike
  95. :func:`~selenium.webdriver.remote.BaseWebElement.get_attribute`, this
  96. method only returns attributes declared in the element's HTML markup.
  97. :Args:
  98. - name - Name of the attribute to retrieve.
  99. :Usage:
  100. ::
  101. text_length = target_element.get_dom_attribute("class")
  102. """
  103. return self._execute(Command.GET_ELEMENT_ATTRIBUTE, {"name": name})["value"]
  104. def get_attribute(self, name) -> str | None:
  105. """Gets the given attribute or property of the element.
  106. This method will first try to return the value of a property with the
  107. given name. If a property with that name doesn't exist, it returns the
  108. value of the attribute with the same name. If there's no attribute with
  109. that name, ``None`` is returned.
  110. Values which are considered truthy, that is equals "true" or "false",
  111. are returned as booleans. All other non-``None`` values are returned
  112. as strings. For attributes or properties which do not exist, ``None``
  113. is returned.
  114. To obtain the exact value of the attribute or property,
  115. use :func:`~selenium.webdriver.remote.BaseWebElement.get_dom_attribute` or
  116. :func:`~selenium.webdriver.remote.BaseWebElement.get_property` methods respectively.
  117. :Args:
  118. - name - Name of the attribute/property to retrieve.
  119. Example::
  120. # Check if the "active" CSS class is applied to an element.
  121. is_active = "active" in target_element.get_attribute("class")
  122. """
  123. if getAttribute_js is None:
  124. _load_js()
  125. attribute_value = self.parent.execute_script(
  126. f"/* getAttribute */return ({getAttribute_js}).apply(null, arguments);", self, name
  127. )
  128. return attribute_value
  129. def is_selected(self) -> bool:
  130. """Returns whether the element is selected.
  131. Can be used to check if a checkbox or radio button is selected.
  132. """
  133. return self._execute(Command.IS_ELEMENT_SELECTED)["value"]
  134. def is_enabled(self) -> bool:
  135. """Returns whether the element is enabled."""
  136. return self._execute(Command.IS_ELEMENT_ENABLED)["value"]
  137. def send_keys(self, *value) -> None:
  138. """Simulates typing into the element.
  139. :Args:
  140. - value - A string for typing, or setting form fields. For setting
  141. file inputs, this could be a local file path.
  142. Use this to send simple key events or to fill out form fields::
  143. form_textfield = driver.find_element(By.NAME, 'username')
  144. form_textfield.send_keys("admin")
  145. This can also be used to set file inputs.
  146. ::
  147. file_input = driver.find_element(By.NAME, 'profilePic')
  148. file_input.send_keys("path/to/profilepic.gif")
  149. # Generally it's better to wrap the file path in one of the methods
  150. # in os.path to return the actual path to support cross OS testing.
  151. # file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
  152. """
  153. # transfer file to another machine only if remote driver is used
  154. # the same behaviour as for java binding
  155. if self.parent._is_remote:
  156. local_files = list(
  157. map(
  158. lambda keys_to_send: self.parent.file_detector.is_local_file(str(keys_to_send)),
  159. "".join(map(str, value)).split("\n"),
  160. )
  161. )
  162. if None not in local_files:
  163. remote_files = []
  164. for file in local_files:
  165. remote_files.append(self._upload(file))
  166. value = "\n".join(remote_files)
  167. self._execute(
  168. Command.SEND_KEYS_TO_ELEMENT, {"text": "".join(keys_to_typing(value)), "value": keys_to_typing(value)}
  169. )
  170. @property
  171. def shadow_root(self) -> ShadowRoot:
  172. """Returns a shadow root of the element if there is one or an error.
  173. Only works from Chromium 96, Firefox 96, and Safari 16.4 onwards.
  174. :Returns:
  175. - ShadowRoot object or
  176. - NoSuchShadowRoot - if no shadow root was attached to element
  177. """
  178. return self._execute(Command.GET_SHADOW_ROOT)["value"]
  179. # RenderedWebElement Items
  180. def is_displayed(self) -> bool:
  181. """Whether the element is visible to a user."""
  182. # Only go into this conditional for browsers that don't use the atom themselves
  183. if isDisplayed_js is None:
  184. _load_js()
  185. return self.parent.execute_script(f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self)
  186. @property
  187. def location_once_scrolled_into_view(self) -> dict:
  188. """THIS PROPERTY MAY CHANGE WITHOUT WARNING. Use this to discover where
  189. on the screen an element is so that we can click it. This method should
  190. cause the element to be scrolled into view.
  191. Returns the top lefthand corner location on the screen, or zero
  192. coordinates if the element is not visible.
  193. """
  194. old_loc = self._execute(
  195. Command.W3C_EXECUTE_SCRIPT,
  196. {
  197. "script": "arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect()",
  198. "args": [self],
  199. },
  200. )["value"]
  201. return {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
  202. @property
  203. def size(self) -> dict:
  204. """The size of the element."""
  205. size = self._execute(Command.GET_ELEMENT_RECT)["value"]
  206. new_size = {"height": size["height"], "width": size["width"]}
  207. return new_size
  208. def value_of_css_property(self, property_name) -> str:
  209. """The value of a CSS property."""
  210. return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {"propertyName": property_name})["value"]
  211. @property
  212. def location(self) -> dict:
  213. """The location of the element in the renderable canvas."""
  214. old_loc = self._execute(Command.GET_ELEMENT_RECT)["value"]
  215. new_loc = {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
  216. return new_loc
  217. @property
  218. def rect(self) -> dict:
  219. """A dictionary with the size and location of the element."""
  220. return self._execute(Command.GET_ELEMENT_RECT)["value"]
  221. @property
  222. def aria_role(self) -> str:
  223. """Returns the ARIA role of the current web element."""
  224. return self._execute(Command.GET_ELEMENT_ARIA_ROLE)["value"]
  225. @property
  226. def accessible_name(self) -> str:
  227. """Returns the ARIA Level of the current webelement."""
  228. return self._execute(Command.GET_ELEMENT_ARIA_LABEL)["value"]
  229. @property
  230. def screenshot_as_base64(self) -> str:
  231. """Gets the screenshot of the current element as a base64 encoded
  232. string.
  233. :Usage:
  234. ::
  235. img_b64 = element.screenshot_as_base64
  236. """
  237. return self._execute(Command.ELEMENT_SCREENSHOT)["value"]
  238. @property
  239. def screenshot_as_png(self) -> bytes:
  240. """Gets the screenshot of the current element as a binary data.
  241. :Usage:
  242. ::
  243. element_png = element.screenshot_as_png
  244. """
  245. return b64decode(self.screenshot_as_base64.encode("ascii"))
  246. def screenshot(self, filename) -> bool:
  247. """Saves a screenshot of the current element to a PNG image file.
  248. Returns False if there is any IOError, else returns True. Use full
  249. paths in your filename.
  250. :Args:
  251. - filename: The full path you wish to save your screenshot to. This
  252. should end with a `.png` extension.
  253. :Usage:
  254. ::
  255. element.screenshot('/Screenshots/foo.png')
  256. """
  257. if not filename.lower().endswith(".png"):
  258. warnings.warn(
  259. "name used for saved screenshot does not match file type. It should end with a `.png` extension",
  260. UserWarning,
  261. )
  262. png = self.screenshot_as_png
  263. try:
  264. with open(filename, "wb") as f:
  265. f.write(png)
  266. except OSError:
  267. return False
  268. finally:
  269. del png
  270. return True
  271. @property
  272. def parent(self):
  273. """Internal reference to the WebDriver instance this element was found
  274. from."""
  275. return self._parent
  276. @property
  277. def id(self) -> str:
  278. """Internal ID used by selenium.
  279. This is mainly for internal use. Simple use cases such as checking if 2
  280. webelements refer to the same element, can be done using ``==``::
  281. if element1 == element2:
  282. print("These 2 are equal")
  283. """
  284. return self._id
  285. def __eq__(self, element):
  286. return hasattr(element, "id") and self._id == element.id
  287. def __ne__(self, element):
  288. return not self.__eq__(element)
  289. # Private Methods
  290. def _execute(self, command, params=None):
  291. """Executes a command against the underlying HTML element.
  292. Args:
  293. command: The name of the command to _execute as a string.
  294. params: A dictionary of named parameters to send with the command.
  295. Returns:
  296. The command's JSON response loaded into a dictionary object.
  297. """
  298. if not params:
  299. params = {}
  300. params["id"] = self._id
  301. return self._parent.execute(command, params)
  302. def find_element(self, by=By.ID, value=None) -> WebElement:
  303. """Find an element given a By strategy and locator.
  304. :Usage:
  305. ::
  306. element = element.find_element(By.ID, 'foo')
  307. :rtype: WebElement
  308. """
  309. if by == By.ID:
  310. by = By.CSS_SELECTOR
  311. value = f'[id="{value}"]'
  312. elif by == By.CLASS_NAME:
  313. by = By.CSS_SELECTOR
  314. value = f".{value}"
  315. elif by == By.NAME:
  316. by = By.CSS_SELECTOR
  317. value = f'[name="{value}"]'
  318. return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"]
  319. def find_elements(self, by=By.ID, value=None) -> List[WebElement]:
  320. """Find elements given a By strategy and locator.
  321. :Usage:
  322. ::
  323. element = element.find_elements(By.CLASS_NAME, 'foo')
  324. :rtype: list of WebElement
  325. """
  326. if by == By.ID:
  327. by = By.CSS_SELECTOR
  328. value = f'[id="{value}"]'
  329. elif by == By.CLASS_NAME:
  330. by = By.CSS_SELECTOR
  331. value = f".{value}"
  332. elif by == By.NAME:
  333. by = By.CSS_SELECTOR
  334. value = f'[name="{value}"]'
  335. return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"]
  336. def __hash__(self) -> int:
  337. return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
  338. def _upload(self, filename):
  339. fp = BytesIO()
  340. zipped = zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED)
  341. zipped.write(filename, os.path.split(filename)[1])
  342. zipped.close()
  343. content = encodebytes(fp.getvalue())
  344. if not isinstance(content, str):
  345. content = content.decode("utf-8")
  346. try:
  347. return self._execute(Command.UPLOAD_FILE, {"file": content})["value"]
  348. except WebDriverException as e:
  349. if "Unrecognized command: POST" in str(e):
  350. return filename
  351. if "Command not found: POST " in str(e):
  352. return filename
  353. if '{"status":405,"value":["GET","HEAD","DELETE"]}' in str(e):
  354. return filename
  355. raise

----------------------- 

这段代码主要是使用 Python 中的 Selenium WebDriver 库来实现网页交互操作,通过封装常见的用户操作,如点击、鼠标移动、键盘输入等。其中 ActionChains 类可以实现更复杂的用户操作,如移动到元素、点击元素、双击、拖放等。用户可以通过调用不同的方法来模拟在页面上进行各种操作,并通过 perform() 方法执行这些操作。这段代码为进行页面交互操作提供了一个基础框架,可根据具体需求进行扩展和定制。
ActionChains 类涉及到的主要方法和属性如下:
move_to_element(element):将鼠标移动到指定元素上。
click():模拟鼠标左键点击操作。
context_click():模拟鼠标右键点击操作。
double_click():模拟鼠标双击操作。
drag_and_drop(source, target):模拟拖放操作,从源元素拖动到目标元素。
key_down(key):按下指定的键。
key_up(key):释放指定的键。
send_keys(keys):向页面元素发送按键输入。
perform():执行所有排队的操作。
属性:
actions:用于存储需要执行的操作序列。
这些方法和属性可以帮助用户实现各种复杂的页面交互操作,包括鼠标操作、键盘输入等。通过组合调用这些方法,可以模拟用户在页面上的各种操作行为。

  1. """The ActionChains implementation."""
  2. from __future__ import annotations
  3. from typing import TYPE_CHECKING
  4. from typing import Union
  5. from selenium.webdriver.remote.webelement import WebElement
  6. from .actions.action_builder import ActionBuilder
  7. from .actions.key_input import KeyInput
  8. from .actions.pointer_input import PointerInput
  9. from .actions.wheel_input import ScrollOrigin
  10. from .actions.wheel_input import WheelInput
  11. from .utils import keys_to_typing
  12. if TYPE_CHECKING:
  13. from selenium.webdriver.remote.webdriver import WebDriver
  14. AnyDevice = Union[PointerInput, KeyInput, WheelInput]
  15. class ActionChains:
  16. """ActionChains are a way to automate low level interactions such as mouse
  17. movements, mouse button actions, key press, and context menu interactions.
  18. This is useful for doing more complex actions like hover over and drag and
  19. drop.
  20. Generate user actions.
  21. When you call methods for actions on the ActionChains object,
  22. the actions are stored in a queue in the ActionChains object.
  23. When you call perform(), the events are fired in the order they
  24. are queued up.
  25. ActionChains can be used in a chain pattern::
  26. menu = driver.find_element(By.CSS_SELECTOR, ".nav")
  27. hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")
  28. ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
  29. Or actions can be queued up one by one, then performed.::
  30. menu = driver.find_element(By.CSS_SELECTOR, ".nav")
  31. hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1")
  32. actions = ActionChains(driver)
  33. actions.move_to_element(menu)
  34. actions.click(hidden_submenu)
  35. actions.perform()
  36. Either way, the actions are performed in the order they are called, one after
  37. another.
  38. """
  39. def __init__(self, driver: WebDriver, duration: int = 250, devices: list[AnyDevice] | None = None) -> None:
  40. """Creates a new ActionChains.
  41. :Args:
  42. - driver: The WebDriver instance which performs user actions.
  43. - duration: override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput
  44. """
  45. self._driver = driver
  46. mouse = None
  47. keyboard = None
  48. wheel = None
  49. if devices is not None and isinstance(devices, list):
  50. for device in devices:
  51. if isinstance(device, PointerInput):
  52. mouse = device
  53. if isinstance(device, KeyInput):
  54. keyboard = device
  55. if isinstance(device, WheelInput):
  56. wheel = device
  57. self.w3c_actions = ActionBuilder(driver, mouse=mouse, keyboard=keyboard, wheel=wheel, duration=duration)
  58. def perform(self) -> None:
  59. """Performs all stored actions."""
  60. self.w3c_actions.perform()
  61. def reset_actions(self) -> None:
  62. """Clears actions that are already stored locally and on the remote
  63. end."""
  64. self.w3c_actions.clear_actions()
  65. for device in self.w3c_actions.devices:
  66. device.clear_actions()
  67. def click(self, on_element: WebElement | None = None) -> ActionChains:
  68. """Clicks an element.
  69. :Args:
  70. - on_element: The element to click.
  71. If None, clicks on current mouse position.
  72. """
  73. if on_element:
  74. self.move_to_element(on_element)
  75. self.w3c_actions.pointer_action.click()
  76. self.w3c_actions.key_action.pause()
  77. self.w3c_actions.key_action.pause()
  78. return self
  79. def click_and_hold(self, on_element: WebElement | None = None) -> ActionChains:
  80. """Holds down the left mouse button on an element.
  81. :Args:
  82. - on_element: The element to mouse down.
  83. If None, clicks on current mouse position.
  84. """
  85. if on_element:
  86. self.move_to_element(on_element)
  87. self.w3c_actions.pointer_action.click_and_hold()
  88. self.w3c_actions.key_action.pause()
  89. return self
  90. def context_click(self, on_element: WebElement | None = None) -> ActionChains:
  91. """Performs a context-click (right click) on an element.
  92. :Args:
  93. - on_element: The element to context-click.
  94. If None, clicks on current mouse position.
  95. """
  96. if on_element:
  97. self.move_to_element(on_element)
  98. self.w3c_actions.pointer_action.context_click()
  99. self.w3c_actions.key_action.pause()
  100. self.w3c_actions.key_action.pause()
  101. return self
  102. def double_click(self, on_element: WebElement | None = None) -> ActionChains:
  103. """Double-clicks an element.
  104. :Args:
  105. - on_element: The element to double-click.
  106. If None, clicks on current mouse position.
  107. """
  108. if on_element:
  109. self.move_to_element(on_element)
  110. self.w3c_actions.pointer_action.double_click()
  111. for _ in range(4):
  112. self.w3c_actions.key_action.pause()
  113. return self
  114. def drag_and_drop(self, source: WebElement, target: WebElement) -> ActionChains:
  115. """Holds down the left mouse button on the source element, then moves
  116. to the target element and releases the mouse button.
  117. :Args:
  118. - source: The element to mouse down.
  119. - target: The element to mouse up.
  120. """
  121. self.click_and_hold(source)
  122. self.release(target)
  123. return self
  124. def drag_and_drop_by_offset(self, source: WebElement, xoffset: int, yoffset: int) -> ActionChains:
  125. """Holds down the left mouse button on the source element, then moves
  126. to the target offset and releases the mouse button.
  127. :Args:
  128. - source: The element to mouse down.
  129. - xoffset: X offset to move to.
  130. - yoffset: Y offset to move to.
  131. """
  132. self.click_and_hold(source)
  133. self.move_by_offset(xoffset, yoffset)
  134. self.release()
  135. return self
  136. def key_down(self, value: str, element: WebElement | None = None) -> ActionChains:
  137. """Sends a key press only, without releasing it. Should only be used
  138. with modifier keys (Control, Alt and Shift).
  139. :Args:
  140. - value: The modifier key to send. Values are defined in `Keys` class.
  141. - element: The element to send keys.
  142. If None, sends a key to current focused element.
  143. Example, pressing ctrl+c::
  144. ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
  145. """
  146. if element:
  147. self.click(element)
  148. self.w3c_actions.key_action.key_down(value)
  149. self.w3c_actions.pointer_action.pause()
  150. return self
  151. def key_up(self, value: str, element: WebElement | None = None) -> ActionChains:
  152. """Releases a modifier key.
  153. :Args:
  154. - value: The modifier key to send. Values are defined in Keys class.
  155. - element: The element to send keys.
  156. If None, sends a key to current focused element.
  157. Example, pressing ctrl+c::
  158. ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
  159. """
  160. if element:
  161. self.click(element)
  162. self.w3c_actions.key_action.key_up(value)
  163. self.w3c_actions.pointer_action.pause()
  164. return self
  165. def move_by_offset(self, xoffset: int, yoffset: int) -> ActionChains:
  166. """Moving the mouse to an offset from current mouse position.
  167. :Args:
  168. - xoffset: X offset to move to, as a positive or negative integer.
  169. - yoffset: Y offset to move to, as a positive or negative integer.
  170. """
  171. self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
  172. self.w3c_actions.key_action.pause()
  173. return self
  174. def move_to_element(self, to_element: WebElement) -> ActionChains:
  175. """Moving the mouse to the middle of an element.
  176. :Args:
  177. - to_element: The WebElement to move to.
  178. """
  179. self.w3c_actions.pointer_action.move_to(to_element)
  180. self.w3c_actions.key_action.pause()
  181. return self
  182. def move_to_element_with_offset(self, to_element: WebElement, xoffset: int, yoffset: int) -> ActionChains:
  183. """Move the mouse by an offset of the specified element. Offsets are
  184. relative to the in-view center point of the element.
  185. :Args:
  186. - to_element: The WebElement to move to.
  187. - xoffset: X offset to move to, as a positive or negative integer.
  188. - yoffset: Y offset to move to, as a positive or negative integer.
  189. """
  190. self.w3c_actions.pointer_action.move_to(to_element, int(xoffset), int(yoffset))
  191. self.w3c_actions.key_action.pause()
  192. return self
  193. def pause(self, seconds: float | int) -> ActionChains:
  194. """Pause all inputs for the specified duration in seconds."""
  195. self.w3c_actions.pointer_action.pause(seconds)
  196. self.w3c_actions.key_action.pause(seconds)
  197. return self
  198. def release(self, on_element: WebElement | None = None) -> ActionChains:
  199. """Releasing a held mouse button on an element.
  200. :Args:
  201. - on_element: The element to mouse up.
  202. If None, releases on current mouse position.
  203. """
  204. if on_element:
  205. self.move_to_element(on_element)
  206. self.w3c_actions.pointer_action.release()
  207. self.w3c_actions.key_action.pause()
  208. return self
  209. def send_keys(self, *keys_to_send: str) -> ActionChains:
  210. """Sends keys to current focused element.
  211. :Args:
  212. - keys_to_send: The keys to send. Modifier keys constants can be found in the
  213. 'Keys' class.
  214. """
  215. typing = keys_to_typing(keys_to_send)
  216. for key in typing:
  217. self.key_down(key)
  218. self.key_up(key)
  219. return self
  220. def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> ActionChains:
  221. """Sends keys to an element.
  222. :Args:
  223. - element: The element to send keys.
  224. - keys_to_send: The keys to send. Modifier keys constants can be found in the
  225. 'Keys' class.
  226. """
  227. self.click(element)
  228. self.send_keys(*keys_to_send)
  229. return self
  230. def scroll_to_element(self, element: WebElement) -> ActionChains:
  231. """If the element is outside the viewport, scrolls the bottom of the
  232. element to the bottom of the viewport.
  233. :Args:
  234. - element: Which element to scroll into the viewport.
  235. """
  236. self.w3c_actions.wheel_action.scroll(origin=element)
  237. return self
  238. def scroll_by_amount(self, delta_x: int, delta_y: int) -> ActionChains:
  239. """Scrolls by provided amounts with the origin in the top left corner
  240. of the viewport.
  241. :Args:
  242. - delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
  243. - delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
  244. """
  245. self.w3c_actions.wheel_action.scroll(delta_x=delta_x, delta_y=delta_y)
  246. return self
  247. def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: int) -> ActionChains:
  248. """Scrolls by provided amount based on a provided origin. The scroll
  249. origin is either the center of an element or the upper left of the
  250. viewport plus any offsets. If the origin is an element, and the element
  251. is not in the viewport, the bottom of the element will first be
  252. scrolled to the bottom of the viewport.
  253. :Args:
  254. - origin: Where scroll originates (viewport or element center) plus provided offsets.
  255. - delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
  256. - delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
  257. :Raises: If the origin with offset is outside the viewport.
  258. - MoveTargetOutOfBoundsException - If the origin with offset is outside the viewport.
  259. """
  260. if not isinstance(scroll_origin, ScrollOrigin):
  261. raise TypeError(f"Expected object of type ScrollOrigin, got: {type(scroll_origin)}")
  262. self.w3c_actions.wheel_action.scroll(
  263. origin=scroll_origin.origin,
  264. x=scroll_origin.x_offset,
  265. y=scroll_origin.y_offset,
  266. delta_x=delta_x,
  267. delta_y=delta_y,
  268. )
  269. return self
  270. # Context manager so ActionChains can be used in a 'with .. as' statements.
  271. def __enter__(self) -> ActionChains:
  272. return self # Return created instance of self.
  273. def __exit__(self, _type, _value, _traceback) -> None:
  274. pass # Do nothing, does not require additional cleanup.

-----------------------

这是一段 Python 代码,它定义了一些用于 WebDriver 测试的“预期条件”(Expected Conditions)。预期条件是一种在测试中使用的断言,用于检查页面或元素的状态是否符合预期。
这段代码中定义了许多常用的预期条件函数,如检查页面标题、检查元素是否可见、检查元素是否存在、检查元素是否包含文本等。你可以根据需要选择并使用这些预期条件函数来辅助你的测试。
这段代码还使用了 Selenium 提供的异常类,如 NoAlertPresentException、NoSuchElementException 等,用于处理在测试过程中可能出现的异常情况。
总之,这段代码提供了一些方便的方法来进行基于 WebDriver 的测试,并对页面和元素的状态进行验证。你可以根据自己的需求和场景使用这些预期条件函数。
这段代码涉及了一些 Selenium WebDriver 测试中常用的预期条件方法和属性,主要包括以下内容:
Expected Conditions 方法:
title_is(title):检查页面标题是否等于给定的标题。
title_contains(title):检查页面标题是否包含给定的字符串。
presence_of_element_located(locator):检查是否存在满足定位器条件的元素。
visibility_of_element_located(locator):检查满足定位器条件的元素是否可见。
visibility_of(element):检查元素是否可见。
text_to_be_present_in_element(element, text_):检查元素是否包含指定文本。
element_to_be_clickable(locator):检查元素是否可点击。
Selenium 异常类:
NoSuchElementException:当找不到元素时抛出此异常。
TimeoutException:超时异常,用于处理等待超时的情况。
NoAlertPresentException:当尝试操作不存在的警报时抛出。
这些方法和属性能够帮助测试人员编写更健壮和可靠的测试脚本,用于验证页面和元素的状态是否符合预期。通过结合预期条件和异常处理,可以提高测试的稳定性和准确性。

  1. import re
  2. from collections.abc import Iterable
  3. from typing import Any
  4. from typing import Callable
  5. from typing import List
  6. from typing import Literal
  7. from typing import Tuple
  8. from typing import TypeVar
  9. from typing import Union
  10. from selenium.common.exceptions import NoAlertPresentException
  11. from selenium.common.exceptions import NoSuchElementException
  12. from selenium.common.exceptions import NoSuchFrameException
  13. from selenium.common.exceptions import StaleElementReferenceException
  14. from selenium.common.exceptions import WebDriverException
  15. from selenium.webdriver.common.alert import Alert
  16. from selenium.webdriver.remote.webdriver import WebDriver
  17. from selenium.webdriver.remote.webdriver import WebElement
  18. """
  19. * Canned "Expected Conditions" which are generally useful within webdriver
  20. * tests.
  21. """
  22. D = TypeVar("D")
  23. T = TypeVar("T")
  24. WebDriverOrWebElement = Union[WebDriver, WebElement]
  25. def title_is(title: str) -> Callable[[WebDriver], bool]:
  26. """An expectation for checking the title of a page.
  27. title is the expected title, which must be an exact match returns
  28. True if the title matches, false otherwise.
  29. """
  30. def _predicate(driver: WebDriver):
  31. return driver.title == title
  32. return _predicate
  33. def title_contains(title: str) -> Callable[[WebDriver], bool]:
  34. """An expectation for checking that the title contains a case-sensitive
  35. substring.
  36. title is the fragment of title expected returns True when the title
  37. matches, False otherwise
  38. """
  39. def _predicate(driver: WebDriver):
  40. return title in driver.title
  41. return _predicate
  42. def presence_of_element_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], WebElement]:
  43. """An expectation for checking that an element is present on the DOM of a
  44. page. This does not necessarily mean that the element is visible.
  45. locator - used to find the element
  46. returns the WebElement once it is located
  47. """
  48. def _predicate(driver: WebDriverOrWebElement):
  49. return driver.find_element(*locator)
  50. return _predicate
  51. def url_contains(url: str) -> Callable[[WebDriver], bool]:
  52. """An expectation for checking that the current url contains a case-
  53. sensitive substring.
  54. url is the fragment of url expected, returns True when the url
  55. matches, False otherwise
  56. """
  57. def _predicate(driver: WebDriver):
  58. return url in driver.current_url
  59. return _predicate
  60. def url_matches(pattern: str) -> Callable[[WebDriver], bool]:
  61. """An expectation for checking the current url.
  62. pattern is the expected pattern. This finds the first occurrence of
  63. pattern in the current url and as such does not require an exact
  64. full match.
  65. """
  66. def _predicate(driver: WebDriver):
  67. return re.search(pattern, driver.current_url) is not None
  68. return _predicate
  69. def url_to_be(url: str) -> Callable[[WebDriver], bool]:
  70. """An expectation for checking the current url.
  71. url is the expected url, which must be an exact match returns True
  72. if the url matches, false otherwise.
  73. """
  74. def _predicate(driver: WebDriver):
  75. return url == driver.current_url
  76. return _predicate
  77. def url_changes(url: str) -> Callable[[WebDriver], bool]:
  78. """An expectation for checking the current url.
  79. url is the expected url, which must not be an exact match returns
  80. True if the url is different, false otherwise.
  81. """
  82. def _predicate(driver: WebDriver):
  83. return url != driver.current_url
  84. return _predicate
  85. def visibility_of_element_located(
  86. locator: Tuple[str, str]
  87. ) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]:
  88. """An expectation for checking that an element is present on the DOM of a
  89. page and visible. Visibility means that the element is not only displayed
  90. but also has a height and width that is greater than 0.
  91. locator - used to find the element
  92. returns the WebElement once it is located and visible
  93. """
  94. def _predicate(driver: WebDriverOrWebElement):
  95. try:
  96. return _element_if_visible(driver.find_element(*locator))
  97. except StaleElementReferenceException:
  98. return False
  99. return _predicate
  100. def visibility_of(element: WebElement) -> Callable[[Any], Union[Literal[False], WebElement]]:
  101. """An expectation for checking that an element, known to be present on the
  102. DOM of a page, is visible.
  103. Visibility means that the element is not only displayed but also has
  104. a height and width that is greater than 0. element is the WebElement
  105. returns the (same) WebElement once it is visible
  106. """
  107. def _predicate(_):
  108. return _element_if_visible(element)
  109. return _predicate
  110. def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[Literal[False], WebElement]:
  111. return element if element.is_displayed() == visibility else False
  112. def presence_of_all_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], List[WebElement]]:
  113. """An expectation for checking that there is at least one element present
  114. on a web page.
  115. locator is used to find the element returns the list of WebElements
  116. once they are located
  117. """
  118. def _predicate(driver: WebDriverOrWebElement):
  119. return driver.find_elements(*locator)
  120. return _predicate
  121. def visibility_of_any_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], List[WebElement]]:
  122. """An expectation for checking that there is at least one element visible
  123. on a web page.
  124. locator is used to find the element returns the list of WebElements
  125. once they are located
  126. """
  127. def _predicate(driver: WebDriverOrWebElement):
  128. return [element for element in driver.find_elements(*locator) if _element_if_visible(element)]
  129. return _predicate
  130. def visibility_of_all_elements_located(
  131. locator: Tuple[str, str]
  132. ) -> Callable[[WebDriverOrWebElement], Union[List[WebElement], Literal[False]]]:
  133. """An expectation for checking that all elements are present on the DOM of
  134. a page and visible. Visibility means that the elements are not only
  135. displayed but also has a height and width that is greater than 0.
  136. locator - used to find the elements
  137. returns the list of WebElements once they are located and visible
  138. """
  139. def _predicate(driver: WebDriverOrWebElement):
  140. try:
  141. elements = driver.find_elements(*locator)
  142. for element in elements:
  143. if _element_if_visible(element, visibility=False):
  144. return False
  145. return elements
  146. except StaleElementReferenceException:
  147. return False
  148. return _predicate
  149. def text_to_be_present_in_element(locator: Tuple[str, str], text_: str) -> Callable[[WebDriverOrWebElement], bool]:
  150. """An expectation for checking if the given text is present in the
  151. specified element.
  152. locator, text
  153. """
  154. def _predicate(driver: WebDriverOrWebElement):
  155. try:
  156. element_text = driver.find_element(*locator).text
  157. return text_ in element_text
  158. except StaleElementReferenceException:
  159. return False
  160. return _predicate
  161. def text_to_be_present_in_element_value(
  162. locator: Tuple[str, str], text_: str
  163. ) -> Callable[[WebDriverOrWebElement], bool]:
  164. """An expectation for checking if the given text is present in the
  165. element's value.
  166. locator, text
  167. """
  168. def _predicate(driver: WebDriverOrWebElement):
  169. try:
  170. element_text = driver.find_element(*locator).get_attribute("value")
  171. return text_ in element_text
  172. except StaleElementReferenceException:
  173. return False
  174. return _predicate
  175. def text_to_be_present_in_element_attribute(
  176. locator: Tuple[str, str], attribute_: str, text_: str
  177. ) -> Callable[[WebDriverOrWebElement], bool]:
  178. """An expectation for checking if the given text is present in the
  179. element's attribute.
  180. locator, attribute, text
  181. """
  182. def _predicate(driver: WebDriverOrWebElement):
  183. try:
  184. element_text = driver.find_element(*locator).get_attribute(attribute_)
  185. if element_text is None:
  186. return False
  187. return text_ in element_text
  188. except StaleElementReferenceException:
  189. return False
  190. return _predicate
  191. def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) -> Callable[[WebDriver], bool]:
  192. """An expectation for checking whether the given frame is available to
  193. switch to.
  194. If the frame is available it switches the given driver to the
  195. specified frame.
  196. """
  197. def _predicate(driver: WebDriver):
  198. try:
  199. if isinstance(locator, Iterable) and not isinstance(locator, str):
  200. driver.switch_to.frame(driver.find_element(*locator))
  201. else:
  202. driver.switch_to.frame(locator)
  203. return True
  204. except NoSuchFrameException:
  205. return False
  206. return _predicate
  207. def invisibility_of_element_located(
  208. locator: Union[WebElement, Tuple[str, str]]
  209. ) -> Callable[[WebDriverOrWebElement], Union[WebElement, bool]]:
  210. """An Expectation for checking that an element is either invisible or not
  211. present on the DOM.
  212. locator used to find the element
  213. """
  214. def _predicate(driver: WebDriverOrWebElement):
  215. try:
  216. target = locator
  217. if not isinstance(target, WebElement):
  218. target = driver.find_element(*target)
  219. return _element_if_visible(target, visibility=False)
  220. except (NoSuchElementException, StaleElementReferenceException):
  221. # In the case of NoSuchElement, returns true because the element is
  222. # not present in DOM. The try block checks if the element is present
  223. # but is invisible.
  224. # In the case of StaleElementReference, returns true because stale
  225. # element reference implies that element is no longer visible.
  226. return True
  227. return _predicate
  228. def invisibility_of_element(
  229. element: Union[WebElement, Tuple[str, str]]
  230. ) -> Callable[[WebDriverOrWebElement], Union[WebElement, bool]]:
  231. """An Expectation for checking that an element is either invisible or not
  232. present on the DOM.
  233. element is either a locator (text) or an WebElement
  234. """
  235. return invisibility_of_element_located(element)
  236. def element_to_be_clickable(
  237. mark: Union[WebElement, Tuple[str, str]]
  238. ) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]:
  239. """An Expectation for checking an element is visible and enabled such that
  240. you can click it.
  241. element is either a locator (text) or an WebElement
  242. """
  243. # renamed argument to 'mark', to indicate that both locator
  244. # and WebElement args are valid
  245. def _predicate(driver: WebDriverOrWebElement):
  246. target = mark
  247. if not isinstance(target, WebElement): # if given locator instead of WebElement
  248. target = driver.find_element(*target) # grab element at locator
  249. element = visibility_of(target)(driver)
  250. if element and element.is_enabled():
  251. return element
  252. return False
  253. return _predicate
  254. def staleness_of(element: WebElement) -> Callable[[Any], bool]:
  255. """Wait until an element is no longer attached to the DOM.
  256. element is the element to wait for. returns False if the element is
  257. still attached to the DOM, true otherwise.
  258. """
  259. def _predicate(_):
  260. try:
  261. # Calling any method forces a staleness check
  262. element.is_enabled()
  263. return False
  264. except StaleElementReferenceException:
  265. return True
  266. return _predicate
  267. def element_to_be_selected(element: WebElement) -> Callable[[Any], bool]:
  268. """An expectation for checking the selection is selected.
  269. element is WebElement object
  270. """
  271. def _predicate(_):
  272. return element.is_selected()
  273. return _predicate
  274. def element_located_to_be_selected(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], bool]:
  275. """An expectation for the element to be located is selected.
  276. locator is a tuple of (by, path)
  277. """
  278. def _predicate(driver: WebDriverOrWebElement):
  279. return driver.find_element(*locator).is_selected()
  280. return _predicate
  281. def element_selection_state_to_be(element: WebElement, is_selected: bool) -> Callable[[Any], bool]:
  282. """An expectation for checking if the given element is selected.
  283. element is WebElement object is_selected is a Boolean.
  284. """
  285. def _predicate(_):
  286. return element.is_selected() == is_selected
  287. return _predicate
  288. def element_located_selection_state_to_be(
  289. locator: Tuple[str, str], is_selected: bool
  290. ) -> Callable[[WebDriverOrWebElement], bool]:
  291. """An expectation to locate an element and check if the selection state
  292. specified is in that state.
  293. locator is a tuple of (by, path) is_selected is a boolean
  294. """
  295. def _predicate(driver: WebDriverOrWebElement):
  296. try:
  297. element = driver.find_element(*locator)
  298. return element.is_selected() == is_selected
  299. except StaleElementReferenceException:
  300. return False
  301. return _predicate
  302. def number_of_windows_to_be(num_windows: int) -> Callable[[WebDriver], bool]:
  303. """An expectation for the number of windows to be a certain value."""
  304. def _predicate(driver: WebDriver):
  305. return len(driver.window_handles) == num_windows
  306. return _predicate
  307. def new_window_is_opened(current_handles: List[str]) -> Callable[[WebDriver], bool]:
  308. """An expectation that a new window will be opened and have the number of
  309. windows handles increase."""
  310. def _predicate(driver: WebDriver):
  311. return len(driver.window_handles) > len(current_handles)
  312. return _predicate
  313. def alert_is_present() -> Callable[[WebDriver], Union[Alert, Literal[False]]]:
  314. """An expectation for checking if an alert is currently present and
  315. switching to it."""
  316. def _predicate(driver: WebDriver):
  317. try:
  318. return driver.switch_to.alert
  319. except NoAlertPresentException:
  320. return False
  321. return _predicate
  322. def element_attribute_to_include(locator: Tuple[str, str], attribute_: str) -> Callable[[WebDriverOrWebElement], bool]:
  323. """An expectation for checking if the given attribute is included in the
  324. specified element.
  325. locator, attribute
  326. """
  327. def _predicate(driver: WebDriverOrWebElement):
  328. try:
  329. element_attribute = driver.find_element(*locator).get_attribute(attribute_)
  330. return element_attribute is not None
  331. except StaleElementReferenceException:
  332. return False
  333. return _predicate
  334. def any_of(*expected_conditions: Callable[[D], T]) -> Callable[[D], Union[Literal[False], T]]:
  335. """An expectation that any of multiple expected conditions is true.
  336. Equivalent to a logical 'OR'. Returns results of the first matching
  337. condition, or False if none do.
  338. """
  339. def any_of_condition(driver: D):
  340. for expected_condition in expected_conditions:
  341. try:
  342. result = expected_condition(driver)
  343. if result:
  344. return result
  345. except WebDriverException:
  346. pass
  347. return False
  348. return any_of_condition
  349. def all_of(
  350. *expected_conditions: Callable[[D], Union[T, Literal[False]]]
  351. ) -> Callable[[D], Union[List[T], Literal[False]]]:
  352. """An expectation that all of multiple expected conditions is true.
  353. Equivalent to a logical 'AND'.
  354. Returns: When any ExpectedCondition is not met: False.
  355. When all ExpectedConditions are met: A List with each ExpectedCondition's return value.
  356. """
  357. def all_of_condition(driver: D):
  358. results: List[T] = []
  359. for expected_condition in expected_conditions:
  360. try:
  361. result = expected_condition(driver)
  362. if not result:
  363. return False
  364. results.append(result)
  365. except WebDriverException:
  366. return False
  367. return results
  368. return all_of_condition
  369. def none_of(*expected_conditions: Callable[[D], Any]) -> Callable[[D], bool]:
  370. """An expectation that none of 1 or multiple expected conditions is true.
  371. Equivalent to a logical 'NOT-OR'. Returns a Boolean
  372. """
  373. def none_of_condition(driver: D):
  374. for expected_condition in expected_conditions:
  375. try:
  376. result = expected_condition(driver)
  377. if result:
  378. return False
  379. except WebDriverException:
  380. pass
  381. return True
  382. return none_of_condition

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/644384
推荐阅读
相关标签
  

闽ICP备14008679号