Updated script that can be controled by Nodejs web app
This commit is contained in:
16
lib/python3.13/site-packages/selenium/webdriver/support/__init__.py
Executable file
16
lib/python3.13/site-packages/selenium/webdriver/support/__init__.py
Executable file
@ -0,0 +1,16 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,77 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
class AbstractEventListener:
|
||||
"""Event listener must subclass and implement this fully or partially."""
|
||||
|
||||
def before_navigate_to(self, url: str, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_navigate_to(self, url: str, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_navigate_back(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_navigate_back(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_navigate_forward(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_navigate_forward(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_find(self, by, value, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_find(self, by, value, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_click(self, element, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_click(self, element, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_change_value_of(self, element, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_change_value_of(self, element, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_execute_script(self, script, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_execute_script(self, script, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_close(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_close(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def before_quit(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def after_quit(self, driver) -> None:
|
||||
pass
|
||||
|
||||
def on_exception(self, exception, driver) -> None:
|
||||
pass
|
334
lib/python3.13/site-packages/selenium/webdriver/support/color.py
Executable file
334
lib/python3.13/site-packages/selenium/webdriver/support/color.py
Executable file
@ -0,0 +1,334 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from typing import Sequence
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from re import Match
|
||||
else:
|
||||
from typing import Match
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import SupportsFloat
|
||||
from typing import SupportsIndex
|
||||
from typing import SupportsInt
|
||||
from typing import Union
|
||||
|
||||
ParseableFloat = Union[SupportsFloat, SupportsIndex, str, bytes, bytearray]
|
||||
ParseableInt = Union[SupportsInt, SupportsIndex, str, bytes]
|
||||
else:
|
||||
ParseableFloat = Any
|
||||
ParseableInt = Any
|
||||
|
||||
RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$"
|
||||
RGB_PCT_PATTERN = (
|
||||
r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$"
|
||||
)
|
||||
RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
RGBA_PCT_PATTERN = r"^\s*rgba\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
HEX_PATTERN = r"#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})"
|
||||
HEX3_PATTERN = r"#([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])"
|
||||
HSL_PATTERN = r"^\s*hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)\s*$"
|
||||
HSLA_PATTERN = r"^\s*hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
|
||||
|
||||
class Color:
|
||||
"""Color conversion support class.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
from selenium.webdriver.support.color import Color
|
||||
|
||||
print(Color.from_string('#00ff33').rgba)
|
||||
print(Color.from_string('rgb(1, 255, 3)').hex)
|
||||
print(Color.from_string('blue').rgba)
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, str_: str) -> Color:
|
||||
import re
|
||||
|
||||
class Matcher:
|
||||
match_obj: Match[str] | None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.match_obj = None
|
||||
|
||||
def match(self, pattern: str, str_: str) -> Match[str] | None:
|
||||
self.match_obj = re.match(pattern, str_)
|
||||
return self.match_obj
|
||||
|
||||
@property
|
||||
def groups(self) -> Sequence[str]:
|
||||
return () if not self.match_obj else self.match_obj.groups()
|
||||
|
||||
m = Matcher()
|
||||
|
||||
if m.match(RGB_PATTERN, str_):
|
||||
return cls(*m.groups)
|
||||
if m.match(RGB_PCT_PATTERN, str_):
|
||||
rgb = tuple(float(each) / 100 * 255 for each in m.groups)
|
||||
return cls(*rgb)
|
||||
if m.match(RGBA_PATTERN, str_):
|
||||
return cls(*m.groups)
|
||||
if m.match(RGBA_PCT_PATTERN, str_):
|
||||
rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]])
|
||||
return cls(*rgba)
|
||||
if m.match(HEX_PATTERN, str_):
|
||||
rgb = tuple(int(each, 16) for each in m.groups)
|
||||
return cls(*rgb)
|
||||
if m.match(HEX3_PATTERN, str_):
|
||||
rgb = tuple(int(each * 2, 16) for each in m.groups)
|
||||
return cls(*rgb)
|
||||
if m.match(HSL_PATTERN, str_) or m.match(HSLA_PATTERN, str_):
|
||||
return cls._from_hsl(*m.groups)
|
||||
if str_.upper() in Colors:
|
||||
return Colors[str_.upper()]
|
||||
raise ValueError("Could not convert %s into color" % str_)
|
||||
|
||||
@classmethod
|
||||
def _from_hsl(cls, h: ParseableFloat, s: ParseableFloat, light: ParseableFloat, a: ParseableFloat = 1) -> Color:
|
||||
h = float(h) / 360
|
||||
s = float(s) / 100
|
||||
_l = float(light) / 100
|
||||
|
||||
if s == 0:
|
||||
r = _l
|
||||
g = r
|
||||
b = r
|
||||
else:
|
||||
luminocity2 = _l * (1 + s) if _l < 0.5 else _l + s - _l * s
|
||||
luminocity1 = 2 * _l - luminocity2
|
||||
|
||||
def hue_to_rgb(lum1: float, lum2: float, hue: float) -> float:
|
||||
if hue < 0.0:
|
||||
hue += 1
|
||||
if hue > 1.0:
|
||||
hue -= 1
|
||||
|
||||
if hue < 1.0 / 6.0:
|
||||
return lum1 + (lum2 - lum1) * 6.0 * hue
|
||||
if hue < 1.0 / 2.0:
|
||||
return lum2
|
||||
if hue < 2.0 / 3.0:
|
||||
return lum1 + (lum2 - lum1) * ((2.0 / 3.0) - hue) * 6.0
|
||||
return lum1
|
||||
|
||||
r = hue_to_rgb(luminocity1, luminocity2, h + 1.0 / 3.0)
|
||||
g = hue_to_rgb(luminocity1, luminocity2, h)
|
||||
b = hue_to_rgb(luminocity1, luminocity2, h - 1.0 / 3.0)
|
||||
|
||||
return cls(round(r * 255), round(g * 255), round(b * 255), a)
|
||||
|
||||
def __init__(self, red: ParseableInt, green: ParseableInt, blue: ParseableInt, alpha: ParseableFloat = 1) -> None:
|
||||
self.red = int(red)
|
||||
self.green = int(green)
|
||||
self.blue = int(blue)
|
||||
self.alpha = "1" if float(alpha) == 1 else str(float(alpha) or 0)
|
||||
|
||||
@property
|
||||
def rgb(self) -> str:
|
||||
return f"rgb({self.red}, {self.green}, {self.blue})"
|
||||
|
||||
@property
|
||||
def rgba(self) -> str:
|
||||
return f"rgba({self.red}, {self.green}, {self.blue}, {self.alpha})"
|
||||
|
||||
@property
|
||||
def hex(self) -> str:
|
||||
return f"#{self.red:02x}{self.green:02x}{self.blue:02x}"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Color):
|
||||
return self.rgba == other.rgba
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
result = self.__eq__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
return not result
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.red, self.green, self.blue, self.alpha))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Color(red={self.red}, green={self.green}, blue={self.blue}, alpha={self.alpha})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Color: {self.rgba}"
|
||||
|
||||
|
||||
# Basic, extended and transparent colour keywords as defined by the W3C HTML4 spec
|
||||
# See http://www.w3.org/TR/css3-color/#html4
|
||||
Colors = {
|
||||
"TRANSPARENT": Color(0, 0, 0, 0),
|
||||
"ALICEBLUE": Color(240, 248, 255),
|
||||
"ANTIQUEWHITE": Color(250, 235, 215),
|
||||
"AQUA": Color(0, 255, 255),
|
||||
"AQUAMARINE": Color(127, 255, 212),
|
||||
"AZURE": Color(240, 255, 255),
|
||||
"BEIGE": Color(245, 245, 220),
|
||||
"BISQUE": Color(255, 228, 196),
|
||||
"BLACK": Color(0, 0, 0),
|
||||
"BLANCHEDALMOND": Color(255, 235, 205),
|
||||
"BLUE": Color(0, 0, 255),
|
||||
"BLUEVIOLET": Color(138, 43, 226),
|
||||
"BROWN": Color(165, 42, 42),
|
||||
"BURLYWOOD": Color(222, 184, 135),
|
||||
"CADETBLUE": Color(95, 158, 160),
|
||||
"CHARTREUSE": Color(127, 255, 0),
|
||||
"CHOCOLATE": Color(210, 105, 30),
|
||||
"CORAL": Color(255, 127, 80),
|
||||
"CORNFLOWERBLUE": Color(100, 149, 237),
|
||||
"CORNSILK": Color(255, 248, 220),
|
||||
"CRIMSON": Color(220, 20, 60),
|
||||
"CYAN": Color(0, 255, 255),
|
||||
"DARKBLUE": Color(0, 0, 139),
|
||||
"DARKCYAN": Color(0, 139, 139),
|
||||
"DARKGOLDENROD": Color(184, 134, 11),
|
||||
"DARKGRAY": Color(169, 169, 169),
|
||||
"DARKGREEN": Color(0, 100, 0),
|
||||
"DARKGREY": Color(169, 169, 169),
|
||||
"DARKKHAKI": Color(189, 183, 107),
|
||||
"DARKMAGENTA": Color(139, 0, 139),
|
||||
"DARKOLIVEGREEN": Color(85, 107, 47),
|
||||
"DARKORANGE": Color(255, 140, 0),
|
||||
"DARKORCHID": Color(153, 50, 204),
|
||||
"DARKRED": Color(139, 0, 0),
|
||||
"DARKSALMON": Color(233, 150, 122),
|
||||
"DARKSEAGREEN": Color(143, 188, 143),
|
||||
"DARKSLATEBLUE": Color(72, 61, 139),
|
||||
"DARKSLATEGRAY": Color(47, 79, 79),
|
||||
"DARKSLATEGREY": Color(47, 79, 79),
|
||||
"DARKTURQUOISE": Color(0, 206, 209),
|
||||
"DARKVIOLET": Color(148, 0, 211),
|
||||
"DEEPPINK": Color(255, 20, 147),
|
||||
"DEEPSKYBLUE": Color(0, 191, 255),
|
||||
"DIMGRAY": Color(105, 105, 105),
|
||||
"DIMGREY": Color(105, 105, 105),
|
||||
"DODGERBLUE": Color(30, 144, 255),
|
||||
"FIREBRICK": Color(178, 34, 34),
|
||||
"FLORALWHITE": Color(255, 250, 240),
|
||||
"FORESTGREEN": Color(34, 139, 34),
|
||||
"FUCHSIA": Color(255, 0, 255),
|
||||
"GAINSBORO": Color(220, 220, 220),
|
||||
"GHOSTWHITE": Color(248, 248, 255),
|
||||
"GOLD": Color(255, 215, 0),
|
||||
"GOLDENROD": Color(218, 165, 32),
|
||||
"GRAY": Color(128, 128, 128),
|
||||
"GREY": Color(128, 128, 128),
|
||||
"GREEN": Color(0, 128, 0),
|
||||
"GREENYELLOW": Color(173, 255, 47),
|
||||
"HONEYDEW": Color(240, 255, 240),
|
||||
"HOTPINK": Color(255, 105, 180),
|
||||
"INDIANRED": Color(205, 92, 92),
|
||||
"INDIGO": Color(75, 0, 130),
|
||||
"IVORY": Color(255, 255, 240),
|
||||
"KHAKI": Color(240, 230, 140),
|
||||
"LAVENDER": Color(230, 230, 250),
|
||||
"LAVENDERBLUSH": Color(255, 240, 245),
|
||||
"LAWNGREEN": Color(124, 252, 0),
|
||||
"LEMONCHIFFON": Color(255, 250, 205),
|
||||
"LIGHTBLUE": Color(173, 216, 230),
|
||||
"LIGHTCORAL": Color(240, 128, 128),
|
||||
"LIGHTCYAN": Color(224, 255, 255),
|
||||
"LIGHTGOLDENRODYELLOW": Color(250, 250, 210),
|
||||
"LIGHTGRAY": Color(211, 211, 211),
|
||||
"LIGHTGREEN": Color(144, 238, 144),
|
||||
"LIGHTGREY": Color(211, 211, 211),
|
||||
"LIGHTPINK": Color(255, 182, 193),
|
||||
"LIGHTSALMON": Color(255, 160, 122),
|
||||
"LIGHTSEAGREEN": Color(32, 178, 170),
|
||||
"LIGHTSKYBLUE": Color(135, 206, 250),
|
||||
"LIGHTSLATEGRAY": Color(119, 136, 153),
|
||||
"LIGHTSLATEGREY": Color(119, 136, 153),
|
||||
"LIGHTSTEELBLUE": Color(176, 196, 222),
|
||||
"LIGHTYELLOW": Color(255, 255, 224),
|
||||
"LIME": Color(0, 255, 0),
|
||||
"LIMEGREEN": Color(50, 205, 50),
|
||||
"LINEN": Color(250, 240, 230),
|
||||
"MAGENTA": Color(255, 0, 255),
|
||||
"MAROON": Color(128, 0, 0),
|
||||
"MEDIUMAQUAMARINE": Color(102, 205, 170),
|
||||
"MEDIUMBLUE": Color(0, 0, 205),
|
||||
"MEDIUMORCHID": Color(186, 85, 211),
|
||||
"MEDIUMPURPLE": Color(147, 112, 219),
|
||||
"MEDIUMSEAGREEN": Color(60, 179, 113),
|
||||
"MEDIUMSLATEBLUE": Color(123, 104, 238),
|
||||
"MEDIUMSPRINGGREEN": Color(0, 250, 154),
|
||||
"MEDIUMTURQUOISE": Color(72, 209, 204),
|
||||
"MEDIUMVIOLETRED": Color(199, 21, 133),
|
||||
"MIDNIGHTBLUE": Color(25, 25, 112),
|
||||
"MINTCREAM": Color(245, 255, 250),
|
||||
"MISTYROSE": Color(255, 228, 225),
|
||||
"MOCCASIN": Color(255, 228, 181),
|
||||
"NAVAJOWHITE": Color(255, 222, 173),
|
||||
"NAVY": Color(0, 0, 128),
|
||||
"OLDLACE": Color(253, 245, 230),
|
||||
"OLIVE": Color(128, 128, 0),
|
||||
"OLIVEDRAB": Color(107, 142, 35),
|
||||
"ORANGE": Color(255, 165, 0),
|
||||
"ORANGERED": Color(255, 69, 0),
|
||||
"ORCHID": Color(218, 112, 214),
|
||||
"PALEGOLDENROD": Color(238, 232, 170),
|
||||
"PALEGREEN": Color(152, 251, 152),
|
||||
"PALETURQUOISE": Color(175, 238, 238),
|
||||
"PALEVIOLETRED": Color(219, 112, 147),
|
||||
"PAPAYAWHIP": Color(255, 239, 213),
|
||||
"PEACHPUFF": Color(255, 218, 185),
|
||||
"PERU": Color(205, 133, 63),
|
||||
"PINK": Color(255, 192, 203),
|
||||
"PLUM": Color(221, 160, 221),
|
||||
"POWDERBLUE": Color(176, 224, 230),
|
||||
"PURPLE": Color(128, 0, 128),
|
||||
"REBECCAPURPLE": Color(128, 51, 153),
|
||||
"RED": Color(255, 0, 0),
|
||||
"ROSYBROWN": Color(188, 143, 143),
|
||||
"ROYALBLUE": Color(65, 105, 225),
|
||||
"SADDLEBROWN": Color(139, 69, 19),
|
||||
"SALMON": Color(250, 128, 114),
|
||||
"SANDYBROWN": Color(244, 164, 96),
|
||||
"SEAGREEN": Color(46, 139, 87),
|
||||
"SEASHELL": Color(255, 245, 238),
|
||||
"SIENNA": Color(160, 82, 45),
|
||||
"SILVER": Color(192, 192, 192),
|
||||
"SKYBLUE": Color(135, 206, 235),
|
||||
"SLATEBLUE": Color(106, 90, 205),
|
||||
"SLATEGRAY": Color(112, 128, 144),
|
||||
"SLATEGREY": Color(112, 128, 144),
|
||||
"SNOW": Color(255, 250, 250),
|
||||
"SPRINGGREEN": Color(0, 255, 127),
|
||||
"STEELBLUE": Color(70, 130, 180),
|
||||
"TAN": Color(210, 180, 140),
|
||||
"TEAL": Color(0, 128, 128),
|
||||
"THISTLE": Color(216, 191, 216),
|
||||
"TOMATO": Color(255, 99, 71),
|
||||
"TURQUOISE": Color(64, 224, 208),
|
||||
"VIOLET": Color(238, 130, 238),
|
||||
"WHEAT": Color(245, 222, 179),
|
||||
"WHITE": Color(255, 255, 255),
|
||||
"WHITESMOKE": Color(245, 245, 245),
|
||||
"YELLOW": Color(255, 255, 0),
|
||||
"YELLOWGREEN": Color(154, 205, 50),
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import typing
|
||||
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
from .abstract_event_listener import AbstractEventListener
|
||||
|
||||
|
||||
def _wrap_elements(result, ef_driver):
|
||||
# handle the case if another wrapper wraps EventFiringWebElement
|
||||
if isinstance(result, EventFiringWebElement):
|
||||
return result
|
||||
if isinstance(result, WebElement):
|
||||
return EventFiringWebElement(result, ef_driver)
|
||||
if isinstance(result, list):
|
||||
return [_wrap_elements(item, ef_driver) for item in result]
|
||||
return result
|
||||
|
||||
|
||||
class EventFiringWebDriver:
|
||||
"""A wrapper around an arbitrary WebDriver instance which supports firing
|
||||
events."""
|
||||
|
||||
def __init__(self, driver: WebDriver, event_listener: AbstractEventListener) -> None:
|
||||
"""Creates a new instance of the EventFiringWebDriver.
|
||||
|
||||
:Args:
|
||||
- driver : A WebDriver instance
|
||||
- event_listener : Instance of a class that subclasses AbstractEventListener and implements it fully or partially
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener
|
||||
|
||||
class MyListener(AbstractEventListener):
|
||||
def before_navigate_to(self, url, driver):
|
||||
print("Before navigate to %s" % url)
|
||||
def after_navigate_to(self, url, driver):
|
||||
print("After navigate to %s" % url)
|
||||
|
||||
driver = Firefox()
|
||||
ef_driver = EventFiringWebDriver(driver, MyListener())
|
||||
ef_driver.get("http://www.google.co.in/")
|
||||
"""
|
||||
if not isinstance(driver, WebDriver):
|
||||
raise WebDriverException("A WebDriver instance must be supplied")
|
||||
if not isinstance(event_listener, AbstractEventListener):
|
||||
raise WebDriverException("Event listener must be a subclass of AbstractEventListener")
|
||||
self._driver = driver
|
||||
self._driver._wrap_value = self._wrap_value
|
||||
self._listener = event_listener
|
||||
|
||||
@property
|
||||
def wrapped_driver(self) -> WebDriver:
|
||||
"""Returns the WebDriver instance wrapped by this
|
||||
EventsFiringWebDriver."""
|
||||
return self._driver
|
||||
|
||||
def get(self, url: str) -> None:
|
||||
self._dispatch("navigate_to", (url, self._driver), "get", (url,))
|
||||
|
||||
def back(self) -> None:
|
||||
self._dispatch("navigate_back", (self._driver,), "back", ())
|
||||
|
||||
def forward(self) -> None:
|
||||
self._dispatch("navigate_forward", (self._driver,), "forward", ())
|
||||
|
||||
def execute_script(self, script, *args):
|
||||
unwrapped_args = (script,) + self._unwrap_element_args(args)
|
||||
return self._dispatch("execute_script", (script, self._driver), "execute_script", unwrapped_args)
|
||||
|
||||
def execute_async_script(self, script, *args):
|
||||
unwrapped_args = (script,) + self._unwrap_element_args(args)
|
||||
return self._dispatch("execute_script", (script, self._driver), "execute_async_script", unwrapped_args)
|
||||
|
||||
def close(self) -> None:
|
||||
self._dispatch("close", (self._driver,), "close", ())
|
||||
|
||||
def quit(self) -> None:
|
||||
self._dispatch("quit", (self._driver,), "quit", ())
|
||||
|
||||
def find_element(self, by=By.ID, value=None) -> WebElement:
|
||||
return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))
|
||||
|
||||
def find_elements(self, by=By.ID, value=None) -> typing.List[WebElement]:
|
||||
return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))
|
||||
|
||||
def _dispatch(
|
||||
self, l_call: str, l_args: typing.Tuple[typing.Any, ...], d_call: str, d_args: typing.Tuple[typing.Any, ...]
|
||||
):
|
||||
getattr(self._listener, f"before_{l_call}")(*l_args)
|
||||
try:
|
||||
result = getattr(self._driver, d_call)(*d_args)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
getattr(self._listener, f"after_{l_call}")(*l_args)
|
||||
return _wrap_elements(result, self)
|
||||
|
||||
def _unwrap_element_args(self, args):
|
||||
if isinstance(args, EventFiringWebElement):
|
||||
return args.wrapped_element
|
||||
if isinstance(args, tuple):
|
||||
return tuple(self._unwrap_element_args(item) for item in args)
|
||||
if isinstance(args, list):
|
||||
return [self._unwrap_element_args(item) for item in args]
|
||||
return args
|
||||
|
||||
def _wrap_value(self, value):
|
||||
if isinstance(value, EventFiringWebElement):
|
||||
return WebDriver._wrap_value(self._driver, value.wrapped_element)
|
||||
return WebDriver._wrap_value(self._driver, value)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
if item.startswith("_") or not hasattr(self._driver, item):
|
||||
object.__setattr__(self, item, value)
|
||||
else:
|
||||
try:
|
||||
object.__setattr__(self._driver, item, value)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
def __getattr__(self, name):
|
||||
def _wrap(*args, **kwargs):
|
||||
try:
|
||||
result = attrib(*args, **kwargs)
|
||||
return _wrap_elements(result, self)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
try:
|
||||
attrib = getattr(self._driver, name)
|
||||
return _wrap if callable(attrib) else attrib
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
|
||||
class EventFiringWebElement:
|
||||
"""A wrapper around WebElement instance which supports firing events."""
|
||||
|
||||
def __init__(self, webelement: WebElement, ef_driver: EventFiringWebDriver) -> None:
|
||||
"""Creates a new instance of the EventFiringWebElement."""
|
||||
self._webelement = webelement
|
||||
self._ef_driver = ef_driver
|
||||
self._driver = ef_driver.wrapped_driver
|
||||
self._listener = ef_driver._listener
|
||||
|
||||
@property
|
||||
def wrapped_element(self) -> WebElement:
|
||||
"""Returns the WebElement wrapped by this EventFiringWebElement
|
||||
instance."""
|
||||
return self._webelement
|
||||
|
||||
def click(self) -> None:
|
||||
self._dispatch("click", (self._webelement, self._driver), "click", ())
|
||||
|
||||
def clear(self) -> None:
|
||||
self._dispatch("change_value_of", (self._webelement, self._driver), "clear", ())
|
||||
|
||||
def send_keys(self, *value) -> None:
|
||||
self._dispatch("change_value_of", (self._webelement, self._driver), "send_keys", value)
|
||||
|
||||
def find_element(self, by=By.ID, value=None) -> WebElement:
|
||||
return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))
|
||||
|
||||
def find_elements(self, by=By.ID, value=None) -> typing.List[WebElement]:
|
||||
return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))
|
||||
|
||||
def _dispatch(self, l_call, l_args, d_call, d_args):
|
||||
getattr(self._listener, f"before_{l_call}")(*l_args)
|
||||
try:
|
||||
result = getattr(self._webelement, d_call)(*d_args)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
getattr(self._listener, f"after_{l_call}")(*l_args)
|
||||
return _wrap_elements(result, self._ef_driver)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
if item.startswith("_") or not hasattr(self._webelement, item):
|
||||
object.__setattr__(self, item, value)
|
||||
else:
|
||||
try:
|
||||
object.__setattr__(self._webelement, item, value)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
def __getattr__(self, name):
|
||||
def _wrap(*args, **kwargs):
|
||||
try:
|
||||
result = attrib(*args, **kwargs)
|
||||
return _wrap_elements(result, self._ef_driver)
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
try:
|
||||
attrib = getattr(self._webelement, name)
|
||||
return _wrap if callable(attrib) else attrib
|
||||
except Exception as exc:
|
||||
self._listener.on_exception(exc, self._driver)
|
||||
raise
|
||||
|
||||
|
||||
# Register a virtual subclass.
|
||||
WebElement.register(EventFiringWebElement)
|
19
lib/python3.13/site-packages/selenium/webdriver/support/events.py
Executable file
19
lib/python3.13/site-packages/selenium/webdriver/support/events.py
Executable file
@ -0,0 +1,19 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from .abstract_event_listener import AbstractEventListener # noqa
|
||||
from .event_firing_webdriver import EventFiringWebDriver # noqa
|
555
lib/python3.13/site-packages/selenium/webdriver/support/expected_conditions.py
Executable file
555
lib/python3.13/site-packages/selenium/webdriver/support/expected_conditions.py
Executable file
@ -0,0 +1,555 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Tuple
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from selenium.common.exceptions import NoAlertPresentException
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import NoSuchFrameException
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.common.alert import Alert
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webdriver import WebElement
|
||||
|
||||
"""
|
||||
* Canned "Expected Conditions" which are generally useful within webdriver
|
||||
* tests.
|
||||
"""
|
||||
|
||||
D = TypeVar("D")
|
||||
T = TypeVar("T")
|
||||
|
||||
WebDriverOrWebElement = Union[WebDriver, WebElement]
|
||||
|
||||
|
||||
def title_is(title: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking the title of a page.
|
||||
|
||||
title is the expected title, which must be an exact match returns
|
||||
True if the title matches, false otherwise.
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return driver.title == title
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def title_contains(title: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking that the title contains a case-sensitive
|
||||
substring.
|
||||
|
||||
title is the fragment of title expected returns True when the title
|
||||
matches, False otherwise
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return title in driver.title
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def presence_of_element_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], WebElement]:
|
||||
"""An expectation for checking that an element is present on the DOM of a
|
||||
page. This does not necessarily mean that the element is visible.
|
||||
|
||||
locator - used to find the element
|
||||
returns the WebElement once it is located
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
return driver.find_element(*locator)
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def url_contains(url: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking that the current url contains a case-
|
||||
sensitive substring.
|
||||
|
||||
url is the fragment of url expected, returns True when the url
|
||||
matches, False otherwise
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return url in driver.current_url
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def url_matches(pattern: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking the current url.
|
||||
|
||||
pattern is the expected pattern. This finds the first occurrence of
|
||||
pattern in the current url and as such does not require an exact
|
||||
full match.
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return re.search(pattern, driver.current_url) is not None
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def url_to_be(url: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking the current url.
|
||||
|
||||
url is the expected url, which must be an exact match returns True
|
||||
if the url matches, false otherwise.
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return url == driver.current_url
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def url_changes(url: str) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking the current url.
|
||||
|
||||
url is the expected url, which must not be an exact match returns
|
||||
True if the url is different, false otherwise.
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return url != driver.current_url
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def visibility_of_element_located(
|
||||
locator: Tuple[str, str]
|
||||
) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]:
|
||||
"""An expectation for checking that an element is present on the DOM of a
|
||||
page and visible. Visibility means that the element is not only displayed
|
||||
but also has a height and width that is greater than 0.
|
||||
|
||||
locator - used to find the element
|
||||
returns the WebElement once it is located and visible
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
return _element_if_visible(driver.find_element(*locator))
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def visibility_of(element: WebElement) -> Callable[[Any], Union[Literal[False], WebElement]]:
|
||||
"""An expectation for checking that an element, known to be present on the
|
||||
DOM of a page, is visible.
|
||||
|
||||
Visibility means that the element is not only displayed but also has
|
||||
a height and width that is greater than 0. element is the WebElement
|
||||
returns the (same) WebElement once it is visible
|
||||
"""
|
||||
|
||||
def _predicate(_):
|
||||
return _element_if_visible(element)
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[Literal[False], WebElement]:
|
||||
return element if element.is_displayed() == visibility else False
|
||||
|
||||
|
||||
def presence_of_all_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], List[WebElement]]:
|
||||
"""An expectation for checking that there is at least one element present
|
||||
on a web page.
|
||||
|
||||
locator is used to find the element returns the list of WebElements
|
||||
once they are located
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
return driver.find_elements(*locator)
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def visibility_of_any_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], List[WebElement]]:
|
||||
"""An expectation for checking that there is at least one element visible
|
||||
on a web page.
|
||||
|
||||
locator is used to find the element returns the list of WebElements
|
||||
once they are located
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
return [element for element in driver.find_elements(*locator) if _element_if_visible(element)]
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def visibility_of_all_elements_located(
|
||||
locator: Tuple[str, str]
|
||||
) -> Callable[[WebDriverOrWebElement], Union[List[WebElement], Literal[False]]]:
|
||||
"""An expectation for checking that all elements are present on the DOM of
|
||||
a page and visible. Visibility means that the elements are not only
|
||||
displayed but also has a height and width that is greater than 0.
|
||||
|
||||
locator - used to find the elements
|
||||
returns the list of WebElements once they are located and visible
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
elements = driver.find_elements(*locator)
|
||||
for element in elements:
|
||||
if _element_if_visible(element, visibility=False):
|
||||
return False
|
||||
return elements
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def text_to_be_present_in_element(locator: Tuple[str, str], text_: str) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation for checking if the given text is present in the
|
||||
specified element.
|
||||
|
||||
locator, text
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
element_text = driver.find_element(*locator).text
|
||||
return text_ in element_text
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def text_to_be_present_in_element_value(
|
||||
locator: Tuple[str, str], text_: str
|
||||
) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation for checking if the given text is present in the
|
||||
element's value.
|
||||
|
||||
locator, text
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
element_text = driver.find_element(*locator).get_attribute("value")
|
||||
return text_ in element_text
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def text_to_be_present_in_element_attribute(
|
||||
locator: Tuple[str, str], attribute_: str, text_: str
|
||||
) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation for checking if the given text is present in the
|
||||
element's attribute.
|
||||
|
||||
locator, attribute, text
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
element_text = driver.find_element(*locator).get_attribute(attribute_)
|
||||
if element_text is None:
|
||||
return False
|
||||
return text_ in element_text
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for checking whether the given frame is available to
|
||||
switch to.
|
||||
|
||||
If the frame is available it switches the given driver to the
|
||||
specified frame.
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
try:
|
||||
if isinstance(locator, Iterable) and not isinstance(locator, str):
|
||||
driver.switch_to.frame(driver.find_element(*locator))
|
||||
else:
|
||||
driver.switch_to.frame(locator)
|
||||
return True
|
||||
except NoSuchFrameException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def invisibility_of_element_located(
|
||||
locator: Union[WebElement, Tuple[str, str]]
|
||||
) -> Callable[[WebDriverOrWebElement], Union[WebElement, bool]]:
|
||||
"""An Expectation for checking that an element is either invisible or not
|
||||
present on the DOM.
|
||||
|
||||
locator used to find the element
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
target = locator
|
||||
if not isinstance(target, WebElement):
|
||||
target = driver.find_element(*target)
|
||||
return _element_if_visible(target, visibility=False)
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
# In the case of NoSuchElement, returns true because the element is
|
||||
# not present in DOM. The try block checks if the element is present
|
||||
# but is invisible.
|
||||
# In the case of StaleElementReference, returns true because stale
|
||||
# element reference implies that element is no longer visible.
|
||||
return True
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def invisibility_of_element(
|
||||
element: Union[WebElement, Tuple[str, str]]
|
||||
) -> Callable[[WebDriverOrWebElement], Union[WebElement, bool]]:
|
||||
"""An Expectation for checking that an element is either invisible or not
|
||||
present on the DOM.
|
||||
|
||||
element is either a locator (text) or an WebElement
|
||||
"""
|
||||
return invisibility_of_element_located(element)
|
||||
|
||||
|
||||
def element_to_be_clickable(
|
||||
mark: Union[WebElement, Tuple[str, str]]
|
||||
) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]:
|
||||
"""An Expectation for checking an element is visible and enabled such that
|
||||
you can click it.
|
||||
|
||||
element is either a locator (text) or an WebElement
|
||||
"""
|
||||
|
||||
# renamed argument to 'mark', to indicate that both locator
|
||||
# and WebElement args are valid
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
target = mark
|
||||
if not isinstance(target, WebElement): # if given locator instead of WebElement
|
||||
target = driver.find_element(*target) # grab element at locator
|
||||
element = visibility_of(target)(driver)
|
||||
if element and element.is_enabled():
|
||||
return element
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def staleness_of(element: WebElement) -> Callable[[Any], bool]:
|
||||
"""Wait until an element is no longer attached to the DOM.
|
||||
|
||||
element is the element to wait for. returns False if the element is
|
||||
still attached to the DOM, true otherwise.
|
||||
"""
|
||||
|
||||
def _predicate(_):
|
||||
try:
|
||||
# Calling any method forces a staleness check
|
||||
element.is_enabled()
|
||||
return False
|
||||
except StaleElementReferenceException:
|
||||
return True
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def element_to_be_selected(element: WebElement) -> Callable[[Any], bool]:
|
||||
"""An expectation for checking the selection is selected.
|
||||
|
||||
element is WebElement object
|
||||
"""
|
||||
|
||||
def _predicate(_):
|
||||
return element.is_selected()
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def element_located_to_be_selected(locator: Tuple[str, str]) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation for the element to be located is selected.
|
||||
|
||||
locator is a tuple of (by, path)
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
return driver.find_element(*locator).is_selected()
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def element_selection_state_to_be(element: WebElement, is_selected: bool) -> Callable[[Any], bool]:
|
||||
"""An expectation for checking if the given element is selected.
|
||||
|
||||
element is WebElement object is_selected is a Boolean.
|
||||
"""
|
||||
|
||||
def _predicate(_):
|
||||
return element.is_selected() == is_selected
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def element_located_selection_state_to_be(
|
||||
locator: Tuple[str, str], is_selected: bool
|
||||
) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation to locate an element and check if the selection state
|
||||
specified is in that state.
|
||||
|
||||
locator is a tuple of (by, path) is_selected is a boolean
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
element = driver.find_element(*locator)
|
||||
return element.is_selected() == is_selected
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def number_of_windows_to_be(num_windows: int) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation for the number of windows to be a certain value."""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return len(driver.window_handles) == num_windows
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def new_window_is_opened(current_handles: List[str]) -> Callable[[WebDriver], bool]:
|
||||
"""An expectation that a new window will be opened and have the number of
|
||||
windows handles increase."""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
return len(driver.window_handles) > len(current_handles)
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def alert_is_present() -> Callable[[WebDriver], Union[Alert, Literal[False]]]:
|
||||
"""An expectation for checking if an alert is currently present and
|
||||
switching to it."""
|
||||
|
||||
def _predicate(driver: WebDriver):
|
||||
try:
|
||||
return driver.switch_to.alert
|
||||
except NoAlertPresentException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def element_attribute_to_include(locator: Tuple[str, str], attribute_: str) -> Callable[[WebDriverOrWebElement], bool]:
|
||||
"""An expectation for checking if the given attribute is included in the
|
||||
specified element.
|
||||
|
||||
locator, attribute
|
||||
"""
|
||||
|
||||
def _predicate(driver: WebDriverOrWebElement):
|
||||
try:
|
||||
element_attribute = driver.find_element(*locator).get_attribute(attribute_)
|
||||
return element_attribute is not None
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
return _predicate
|
||||
|
||||
|
||||
def any_of(*expected_conditions: Callable[[D], T]) -> Callable[[D], Union[Literal[False], T]]:
|
||||
"""An expectation that any of multiple expected conditions is true.
|
||||
|
||||
Equivalent to a logical 'OR'. Returns results of the first matching
|
||||
condition, or False if none do.
|
||||
"""
|
||||
|
||||
def any_of_condition(driver: D):
|
||||
for expected_condition in expected_conditions:
|
||||
try:
|
||||
result = expected_condition(driver)
|
||||
if result:
|
||||
return result
|
||||
except WebDriverException:
|
||||
pass
|
||||
return False
|
||||
|
||||
return any_of_condition
|
||||
|
||||
|
||||
def all_of(
|
||||
*expected_conditions: Callable[[D], Union[T, Literal[False]]]
|
||||
) -> Callable[[D], Union[List[T], Literal[False]]]:
|
||||
"""An expectation that all of multiple expected conditions is true.
|
||||
|
||||
Equivalent to a logical 'AND'.
|
||||
Returns: When any ExpectedCondition is not met: False.
|
||||
When all ExpectedConditions are met: A List with each ExpectedCondition's return value.
|
||||
"""
|
||||
|
||||
def all_of_condition(driver: D):
|
||||
results: List[T] = []
|
||||
for expected_condition in expected_conditions:
|
||||
try:
|
||||
result = expected_condition(driver)
|
||||
if not result:
|
||||
return False
|
||||
results.append(result)
|
||||
except WebDriverException:
|
||||
return False
|
||||
return results
|
||||
|
||||
return all_of_condition
|
||||
|
||||
|
||||
def none_of(*expected_conditions: Callable[[D], Any]) -> Callable[[D], bool]:
|
||||
"""An expectation that none of 1 or multiple expected conditions is true.
|
||||
|
||||
Equivalent to a logical 'NOT-OR'. Returns a Boolean
|
||||
"""
|
||||
|
||||
def none_of_condition(driver: D):
|
||||
for expected_condition in expected_conditions:
|
||||
try:
|
||||
result = expected_condition(driver)
|
||||
if result:
|
||||
return False
|
||||
except WebDriverException:
|
||||
pass
|
||||
return True
|
||||
|
||||
return none_of_condition
|
191
lib/python3.13/site-packages/selenium/webdriver/support/relative_locator.py
Executable file
191
lib/python3.13/site-packages/selenium/webdriver/support/relative_locator.py
Executable file
@ -0,0 +1,191 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from typing import overload
|
||||
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.by import ByType
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
def with_tag_name(tag_name: str) -> "RelativeBy":
|
||||
"""Start searching for relative objects using a tag name.
|
||||
|
||||
Note: This method may be removed in future versions, please use
|
||||
`locate_with` instead.
|
||||
:Args:
|
||||
- tag_name: the DOM tag of element to start searching.
|
||||
:Returns:
|
||||
- RelativeBy - use this object to create filters within a
|
||||
`find_elements` call.
|
||||
"""
|
||||
if not tag_name:
|
||||
raise WebDriverException("tag_name can not be null")
|
||||
return RelativeBy({By.CSS_SELECTOR: tag_name})
|
||||
|
||||
|
||||
def locate_with(by: ByType, using: str) -> "RelativeBy":
|
||||
"""Start searching for relative objects your search criteria with By.
|
||||
|
||||
:Args:
|
||||
- by: The value from `By` passed in.
|
||||
- using: search term to find the element with.
|
||||
:Returns:
|
||||
- RelativeBy - use this object to create filters within a
|
||||
`find_elements` call.
|
||||
"""
|
||||
assert by is not None, "Please pass in a by argument"
|
||||
assert using is not None, "Please pass in a using argument"
|
||||
return RelativeBy({by: using})
|
||||
|
||||
|
||||
class RelativeBy:
|
||||
"""Gives the opportunity to find elements based on their relative location
|
||||
on the page from a root elelemt. It is recommended that you use the helper
|
||||
function to create it.
|
||||
|
||||
Example:
|
||||
lowest = driver.find_element(By.ID, "below")
|
||||
|
||||
elements = driver.find_elements(locate_with(By.CSS_SELECTOR, "p").above(lowest))
|
||||
|
||||
ids = [el.get_attribute('id') for el in elements]
|
||||
assert "above" in ids
|
||||
assert "mid" in ids
|
||||
"""
|
||||
|
||||
LocatorType = Dict[ByType, str]
|
||||
|
||||
def __init__(self, root: Optional[Dict[ByType, str]] = None, filters: Optional[List] = None):
|
||||
"""Creates a new RelativeBy object. It is preferred if you use the
|
||||
`locate_with` method as this signature could change.
|
||||
|
||||
:Args:
|
||||
root - A dict with `By` enum as the key and the search query as the value
|
||||
filters - A list of the filters that will be searched. If none are passed
|
||||
in please use the fluent API on the object to create the filters
|
||||
"""
|
||||
self.root = root
|
||||
self.filters = filters or []
|
||||
|
||||
@overload
|
||||
def above(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ...
|
||||
|
||||
@overload
|
||||
def above(self, element_or_locator: None = None) -> "NoReturn": ...
|
||||
|
||||
def above(self, element_or_locator: Union[WebElement, LocatorType, None] = None) -> "RelativeBy":
|
||||
"""Add a filter to look for elements above.
|
||||
|
||||
:Args:
|
||||
- element_or_locator: Element to look above
|
||||
"""
|
||||
if not element_or_locator:
|
||||
raise WebDriverException("Element or locator must be given when calling above method")
|
||||
|
||||
self.filters.append({"kind": "above", "args": [element_or_locator]})
|
||||
return self
|
||||
|
||||
@overload
|
||||
def below(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ...
|
||||
|
||||
@overload
|
||||
def below(self, element_or_locator: None = None) -> "NoReturn": ...
|
||||
|
||||
def below(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy":
|
||||
"""Add a filter to look for elements below.
|
||||
|
||||
:Args:
|
||||
- element_or_locator: Element to look below
|
||||
"""
|
||||
if not element_or_locator:
|
||||
raise WebDriverException("Element or locator must be given when calling below method")
|
||||
|
||||
self.filters.append({"kind": "below", "args": [element_or_locator]})
|
||||
return self
|
||||
|
||||
@overload
|
||||
def to_left_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ...
|
||||
|
||||
@overload
|
||||
def to_left_of(self, element_or_locator: None = None) -> "NoReturn": ...
|
||||
|
||||
def to_left_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy":
|
||||
"""Add a filter to look for elements to the left of.
|
||||
|
||||
:Args:
|
||||
- element_or_locator: Element to look to the left of
|
||||
"""
|
||||
if not element_or_locator:
|
||||
raise WebDriverException("Element or locator must be given when calling to_left_of method")
|
||||
|
||||
self.filters.append({"kind": "left", "args": [element_or_locator]})
|
||||
return self
|
||||
|
||||
@overload
|
||||
def to_right_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ...
|
||||
|
||||
@overload
|
||||
def to_right_of(self, element_or_locator: None = None) -> "NoReturn": ...
|
||||
|
||||
def to_right_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy":
|
||||
"""Add a filter to look for elements right of.
|
||||
|
||||
:Args:
|
||||
- element_or_locator: Element to look right of
|
||||
"""
|
||||
if not element_or_locator:
|
||||
raise WebDriverException("Element or locator must be given when calling to_right_of method")
|
||||
|
||||
self.filters.append({"kind": "right", "args": [element_or_locator]})
|
||||
return self
|
||||
|
||||
@overload
|
||||
def near(self, element_or_locator: Union[WebElement, LocatorType], distance: int = 50) -> "RelativeBy": ...
|
||||
|
||||
@overload
|
||||
def near(self, element_or_locator: None = None, distance: int = 50) -> "NoReturn": ...
|
||||
|
||||
def near(self, element_or_locator: Union[WebElement, LocatorType, None] = None, distance: int = 50) -> "RelativeBy":
|
||||
"""Add a filter to look for elements near.
|
||||
|
||||
:Args:
|
||||
- element_or_locator: Element to look near by the element or within a distance
|
||||
- distance: distance in pixel
|
||||
"""
|
||||
if not element_or_locator:
|
||||
raise WebDriverException("Element or locator must be given when calling near method")
|
||||
if distance <= 0:
|
||||
raise WebDriverException("Distance must be positive")
|
||||
|
||||
self.filters.append({"kind": "near", "args": [element_or_locator, distance]})
|
||||
return self
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Create a dict that will be passed to the driver to start searching
|
||||
for the element."""
|
||||
return {
|
||||
"relative": {
|
||||
"root": self.root,
|
||||
"filters": self.filters,
|
||||
}
|
||||
}
|
243
lib/python3.13/site-packages/selenium/webdriver/support/select.py
Executable file
243
lib/python3.13/site-packages/selenium/webdriver/support/select.py
Executable file
@ -0,0 +1,243 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from typing import List
|
||||
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import UnexpectedTagNameException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class Select:
|
||||
def __init__(self, webelement: WebElement) -> None:
|
||||
"""Constructor. A check is made that the given element is, indeed, a
|
||||
SELECT tag. If it is not, then an UnexpectedTagNameException is thrown.
|
||||
|
||||
:Args:
|
||||
- webelement - SELECT element to wrap
|
||||
|
||||
Example:
|
||||
from selenium.webdriver.support.ui import Select \n
|
||||
Select(driver.find_element(By.TAG_NAME, "select")).select_by_index(2)
|
||||
"""
|
||||
if webelement.tag_name.lower() != "select":
|
||||
raise UnexpectedTagNameException(f"Select only works on <select> elements, not on {webelement.tag_name}")
|
||||
self._el = webelement
|
||||
multi = self._el.get_dom_attribute("multiple")
|
||||
self.is_multiple = multi and multi != "false"
|
||||
|
||||
@property
|
||||
def options(self) -> List[WebElement]:
|
||||
"""Returns a list of all options belonging to this select tag."""
|
||||
return self._el.find_elements(By.TAG_NAME, "option")
|
||||
|
||||
@property
|
||||
def all_selected_options(self) -> List[WebElement]:
|
||||
"""Returns a list of all selected options belonging to this select
|
||||
tag."""
|
||||
return [opt for opt in self.options if opt.is_selected()]
|
||||
|
||||
@property
|
||||
def first_selected_option(self) -> WebElement:
|
||||
"""The first selected option in this select tag (or the currently
|
||||
selected option in a normal select)"""
|
||||
for opt in self.options:
|
||||
if opt.is_selected():
|
||||
return opt
|
||||
raise NoSuchElementException("No options are selected")
|
||||
|
||||
def select_by_value(self, value: str) -> None:
|
||||
"""Select all options that have a value matching the argument. That is,
|
||||
when given "foo" this would select an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- value - The value to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specified value in SELECT
|
||||
"""
|
||||
css = f"option[value ={self._escape_string(value)}]"
|
||||
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||||
matched = False
|
||||
for opt in opts:
|
||||
self._set_selected(opt)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException(f"Cannot locate option with value: {value}")
|
||||
|
||||
def select_by_index(self, index: int) -> None:
|
||||
"""Select the option at the given index. This is done by examining the
|
||||
"index" attribute of an element, and not merely by counting.
|
||||
|
||||
:Args:
|
||||
- index - The option at this index will be selected
|
||||
|
||||
throws NoSuchElementException If there is no option with specified index in SELECT
|
||||
"""
|
||||
match = str(index)
|
||||
for opt in self.options:
|
||||
if opt.get_attribute("index") == match:
|
||||
self._set_selected(opt)
|
||||
return
|
||||
raise NoSuchElementException(f"Could not locate element with index {index}")
|
||||
|
||||
def select_by_visible_text(self, text: str) -> None:
|
||||
"""Select all options that display text matching the argument. That is,
|
||||
when given "Bar" this would select an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- text - The visible text to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specified text in SELECT
|
||||
"""
|
||||
xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]"
|
||||
opts = self._el.find_elements(By.XPATH, xpath)
|
||||
matched = False
|
||||
for opt in opts:
|
||||
self._set_selected(opt)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
|
||||
if len(opts) == 0 and " " in text:
|
||||
sub_string_without_space = self._get_longest_token(text)
|
||||
if sub_string_without_space == "":
|
||||
candidates = self.options
|
||||
else:
|
||||
xpath = f".//option[contains(.,{self._escape_string(sub_string_without_space)})]"
|
||||
candidates = self._el.find_elements(By.XPATH, xpath)
|
||||
for candidate in candidates:
|
||||
if text == candidate.text:
|
||||
self._set_selected(candidate)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
|
||||
if not matched:
|
||||
raise NoSuchElementException(f"Could not locate element with visible text: {text}")
|
||||
|
||||
def deselect_all(self) -> None:
|
||||
"""Clear all selected entries.
|
||||
|
||||
This is only valid when the SELECT supports multiple selections.
|
||||
throws NotImplementedError If the SELECT does not support
|
||||
multiple selections
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect all options of a multi-select")
|
||||
for opt in self.options:
|
||||
self._unset_selected(opt)
|
||||
|
||||
def deselect_by_value(self, value: str) -> None:
|
||||
"""Deselect all options that have a value matching the argument. That
|
||||
is, when given "foo" this would deselect an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- value - The value to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specified value in SELECT
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
matched = False
|
||||
css = f"option[value = {self._escape_string(value)}]"
|
||||
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||||
for opt in opts:
|
||||
self._unset_selected(opt)
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException(f"Could not locate element with value: {value}")
|
||||
|
||||
def deselect_by_index(self, index: int) -> None:
|
||||
"""Deselect the option at the given index. This is done by examining
|
||||
the "index" attribute of an element, and not merely by counting.
|
||||
|
||||
:Args:
|
||||
- index - The option at this index will be deselected
|
||||
|
||||
throws NoSuchElementException If there is no option with specified index in SELECT
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
for opt in self.options:
|
||||
if opt.get_attribute("index") == str(index):
|
||||
self._unset_selected(opt)
|
||||
return
|
||||
raise NoSuchElementException(f"Could not locate element with index {index}")
|
||||
|
||||
def deselect_by_visible_text(self, text: str) -> None:
|
||||
"""Deselect all options that display text matching the argument. That
|
||||
is, when given "Bar" this would deselect an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- text - The visible text to match against
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
matched = False
|
||||
xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]"
|
||||
opts = self._el.find_elements(By.XPATH, xpath)
|
||||
for opt in opts:
|
||||
self._unset_selected(opt)
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException(f"Could not locate element with visible text: {text}")
|
||||
|
||||
def _set_selected(self, option) -> None:
|
||||
if not option.is_selected():
|
||||
if not option.is_enabled():
|
||||
raise NotImplementedError("You may not select a disabled option")
|
||||
option.click()
|
||||
|
||||
def _unset_selected(self, option) -> None:
|
||||
if option.is_selected():
|
||||
option.click()
|
||||
|
||||
def _escape_string(self, value: str) -> str:
|
||||
if '"' in value and "'" in value:
|
||||
substrings = value.split('"')
|
||||
result = ["concat("]
|
||||
for substring in substrings:
|
||||
result.append(f'"{substring}"')
|
||||
result.append(", '\"', ")
|
||||
result = result[0:-1]
|
||||
if value.endswith('"'):
|
||||
result.append(", '\"'")
|
||||
return "".join(result) + ")"
|
||||
|
||||
if '"' in value:
|
||||
return f"'{value}'"
|
||||
|
||||
return f'"{value}"'
|
||||
|
||||
def _get_longest_token(self, value: str) -> str:
|
||||
items = value.split(" ")
|
||||
longest = ""
|
||||
for item in items:
|
||||
if len(item) > len(longest):
|
||||
longest = item
|
||||
return longest
|
21
lib/python3.13/site-packages/selenium/webdriver/support/ui.py
Executable file
21
lib/python3.13/site-packages/selenium/webdriver/support/ui.py
Executable file
@ -0,0 +1,21 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from .select import Select
|
||||
from .wait import WebDriverWait
|
||||
|
||||
__all__ = ["Select", "WebDriverWait"]
|
128
lib/python3.13/site-packages/selenium/webdriver/support/wait.py
Executable file
128
lib/python3.13/site-packages/selenium/webdriver/support/wait.py
Executable file
@ -0,0 +1,128 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
import typing
|
||||
from typing import Callable
|
||||
from typing import Generic
|
||||
from typing import Literal
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
from selenium.types import WaitExcTypes
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
POLL_FREQUENCY: float = 0.5 # How long to sleep in between calls to the method
|
||||
IGNORED_EXCEPTIONS: typing.Tuple[typing.Type[Exception]] = (NoSuchElementException,) # default to be ignored.
|
||||
|
||||
D = TypeVar("D", bound=Union[WebDriver, WebElement])
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class WebDriverWait(Generic[D]):
|
||||
def __init__(
|
||||
self,
|
||||
driver: D,
|
||||
timeout: float,
|
||||
poll_frequency: float = POLL_FREQUENCY,
|
||||
ignored_exceptions: typing.Optional[WaitExcTypes] = None,
|
||||
):
|
||||
"""Constructor, takes a WebDriver instance and timeout in seconds.
|
||||
|
||||
:Args:
|
||||
- driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) or a WebElement
|
||||
- timeout - Number of seconds before timing out
|
||||
- poll_frequency - sleep interval between calls
|
||||
By default, it is 0.5 second.
|
||||
- ignored_exceptions - iterable structure of exception classes ignored during calls.
|
||||
By default, it contains NoSuchElementException only.
|
||||
|
||||
Example::
|
||||
|
||||
from selenium.webdriver.support.wait import WebDriverWait \n
|
||||
element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n
|
||||
is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n
|
||||
until_not(lambda x: x.find_element(By.ID, "someId").is_displayed())
|
||||
"""
|
||||
self._driver = driver
|
||||
self._timeout = float(timeout)
|
||||
self._poll = poll_frequency
|
||||
# avoid the divide by zero
|
||||
if self._poll == 0:
|
||||
self._poll = POLL_FREQUENCY
|
||||
exceptions = list(IGNORED_EXCEPTIONS)
|
||||
if ignored_exceptions:
|
||||
try:
|
||||
exceptions.extend(iter(ignored_exceptions))
|
||||
except TypeError: # ignored_exceptions is not iterable
|
||||
exceptions.append(ignored_exceptions)
|
||||
self._ignored_exceptions = tuple(exceptions)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__module__}.{type(self).__name__} (session="{self._driver.session_id}")>'
|
||||
|
||||
def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T:
|
||||
"""Calls the method provided with the driver as an argument until the \
|
||||
return value does not evaluate to ``False``.
|
||||
|
||||
:param method: callable(WebDriver)
|
||||
:param message: optional message for :exc:`TimeoutException`
|
||||
:returns: the result of the last call to `method`
|
||||
:raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
|
||||
"""
|
||||
screen = None
|
||||
stacktrace = None
|
||||
|
||||
end_time = time.monotonic() + self._timeout
|
||||
while True:
|
||||
try:
|
||||
value = method(self._driver)
|
||||
if value:
|
||||
return value
|
||||
except self._ignored_exceptions as exc:
|
||||
screen = getattr(exc, "screen", None)
|
||||
stacktrace = getattr(exc, "stacktrace", None)
|
||||
if time.monotonic() > end_time:
|
||||
break
|
||||
time.sleep(self._poll)
|
||||
raise TimeoutException(message, screen, stacktrace)
|
||||
|
||||
def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]:
|
||||
"""Calls the method provided with the driver as an argument until the \
|
||||
return value evaluates to ``False``.
|
||||
|
||||
:param method: callable(WebDriver)
|
||||
:param message: optional message for :exc:`TimeoutException`
|
||||
:returns: the result of the last call to `method`, or
|
||||
``True`` if `method` has raised one of the ignored exceptions
|
||||
:raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
|
||||
"""
|
||||
end_time = time.monotonic() + self._timeout
|
||||
while True:
|
||||
try:
|
||||
value = method(self._driver)
|
||||
if not value:
|
||||
return value
|
||||
except self._ignored_exceptions:
|
||||
return True
|
||||
if time.monotonic() > end_time:
|
||||
break
|
||||
time.sleep(self._poll)
|
||||
raise TimeoutException(message)
|
Reference in New Issue
Block a user