From a1886e54d9e1ea73f0d09357b14c732472715ddc Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 9 Jan 2025 12:33:50 -0500 Subject: [PATCH 01/10] Improved WebDriverWait Docstrings --- py/selenium/webdriver/support/wait.py | 111 +++++++++++++++++++------- 1 file changed, 84 insertions(+), 27 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index f9e3815b6022f..46e894cad62f2 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -48,20 +48,29 @@ def __init__( ): """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()) + Parameters: + ---------- + 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.common.by import By + >>> from selenium.webdriver.support.wait import WebDriverWait + >>> from selenium.common.exceptions import ElementNotVisibleException + >>> + >>> # Wait until the element is no longer visible + >>> is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not( + ... lambda x: x.find_element(By.ID, "someId").is_displayed() + ... ) """ self._driver = driver self._timeout = float(timeout) @@ -81,13 +90,38 @@ 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 \ + """Wait until the method returns a value that is not False + + 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 + Parameters: + ---------- + method: callable(WebDriver) + - A callable object that takes a WebDriver instance as an argument. + message: str + - Optional message for :exc:`TimeoutException` + + Return: + ------- + object: T + - The result of the last call to `method` + + Raises: + ------- + TimeoutException + - If 'method' does not return a truthy value within the WebDriverWait object's timeout + + Example: + -------- + >>> from selenium.webdriver.common.by import By + >>> from selenium.webdriver.support.ui import WebDriverWait + >>> from selenium.webdriver.support import expected_conditions as EC + + # Wait until an element is visible on the page + >>> wait = WebDriverWait(driver, 10) + >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) + >>> print(element.text) """ screen = None stacktrace = None @@ -107,14 +141,37 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = 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 + """Wait until the method returns a value that is not False + + Calls the method provided with the driver as an argument until the + return value does not evaluate to ``False``. + + Parameters: + ---------- + method: callable(WebDriver) + - A callable object that takes a WebDriver instance as an argument. + message: str + - Optional message for :exc:`TimeoutException` + + Return: + ------- + object: T + - The result of the last call to `method` + + Raises: + ------- + TimeoutException + - If 'method' does not return False within the WebDriverWait object's timeout + + Example: + -------- + >>> from selenium.webdriver.common.by import By + >>> from selenium.webdriver.support.ui import WebDriverWait + >>> from selenium.webdriver.support import expected_conditions as EC + + # Wait until an element is visible on the page + >>> wait = WebDriverWait(driver, 10) + >>> is_disappeared = wait.until_not(EC.visibility_of_element_located((By.ID, "exampleId"))) """ end_time = time.monotonic() + self._timeout while True: From 32214b69116da53833a611ff073ed022daf21b6a Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 12 Jan 2025 12:49:59 -0500 Subject: [PATCH 02/10] format.sh --- py/generate.py | 159 +++++++++--------- py/selenium/webdriver/support/wait.py | 8 +- py/test/selenium/webdriver/common/example2.py | 3 +- .../webdriver/common/interactions_tests.py | 13 +- .../common/interactions_with_device_tests.py | 13 +- .../selenium/webdriver/common/page_loader.py | 13 +- .../selenium/webdriver/common/webserver.py | 8 +- .../webdriver/remote/subtyping_tests.py | 3 +- scripts/update_cdp.py | 6 +- 9 files changed, 114 insertions(+), 112 deletions(-) diff --git a/py/generate.py b/py/generate.py index 0763084423168..7b8d99b041f0f 100644 --- a/py/generate.py +++ b/py/generate.py @@ -89,7 +89,7 @@ def parse_json_event(json: T_JSON_DICT) -> typing.Any: def indent(s, n): - ''' A shortcut for ``textwrap.indent`` that always uses spaces. ''' + """A shortcut for ``textwrap.indent`` that always uses spaces.""" return tw_indent(s, n * ' ') @@ -97,13 +97,13 @@ def indent(s, n): def escape_backticks(docstr): - ''' - Escape backticks in a docstring by doubling them up. + """Escape backticks in a docstring by doubling them up. + This is a little tricky because RST requires a non-letter character after the closing backticks, but some CDPs docs have things like "`AxNodeId`s". If we double the backticks in that string, then it won't be valid RST. The fix is to insert an apostrophe if an "s" trails the backticks. - ''' + """ def replace_one(match): if match.group(2) == 's': return f"``{match.group(1)}``'s" @@ -119,7 +119,7 @@ def replace_one(match): def inline_doc(description): - ''' Generate an inline doc, e.g. ``#: This type is a ...`` ''' + """Generate an inline doc, e.g. ``#: This type is a ...``""" if not description: return '' @@ -129,7 +129,7 @@ def inline_doc(description): def docstring(description): - ''' Generate a docstring from a description. ''' + """Generate a docstring from a description.""" if not description: return '' @@ -138,7 +138,7 @@ def docstring(description): def is_builtin(name): - ''' Return True if ``name`` would shadow a builtin. ''' + """Return True if ``name`` would shadow a builtin.""" try: getattr(builtins, name) return True @@ -147,8 +147,11 @@ def is_builtin(name): def snake_case(name): - ''' Convert a camel case name to snake case. If the name would shadow a - Python builtin, then append an underscore. ''' + """Convert a camel case name to snake case. + + If the name would shadow a Python builtin, then append an + underscore. + """ name = inflection.underscore(name) if is_builtin(name): name += '_' @@ -156,10 +159,10 @@ def snake_case(name): def ref_to_python(ref): - ''' - Convert a CDP ``$ref`` to the name of a Python type. + """Convert a CDP ``$ref`` to the name of a Python type. + For a dotted ref, the part before the dot is snake cased. - ''' + """ if '.' in ref: domain, subtype = ref.split('.') ref = f'{snake_case(domain)}.{subtype}' @@ -167,7 +170,7 @@ def ref_to_python(ref): class CdpPrimitiveType(Enum): - ''' All of the CDP types that map directly to a Python type. ''' + """All of the CDP types that map directly to a Python type.""" boolean = 'bool' integer = 'int' number = 'float' @@ -176,14 +179,14 @@ class CdpPrimitiveType(Enum): @classmethod def get_annotation(cls, cdp_type): - ''' Return a type annotation for the CDP type. ''' + """Return a type annotation for the CDP type.""" if cdp_type == 'any': return 'typing.Any' return cls[cdp_type].value @classmethod def get_constructor(cls, cdp_type, val): - ''' Return the code to construct a value for a given CDP type. ''' + """Return the code to construct a value for a given CDP type.""" if cdp_type == 'any': return val cons = cls[cdp_type].value @@ -192,19 +195,19 @@ def get_constructor(cls, cdp_type, val): @dataclass class CdpItems: - ''' Represents the type of a repeated item. ''' + """Represents the type of a repeated item.""" type: str ref: str @classmethod def from_json(cls, type): - ''' Generate code to instantiate an item from a JSON object. ''' + """Generate code to instantiate an item from a JSON object.""" return cls(type.get('type'), type.get('$ref')) @dataclass class CdpProperty: - ''' A property belonging to a non-primitive CDP type. ''' + """A property belonging to a non-primitive CDP type.""" name: str description: Optional[str] type: Optional[str] @@ -217,12 +220,12 @@ class CdpProperty: @property def py_name(self): - ''' Get this property's Python name. ''' + """Get this property's Python name.""" return snake_case(self.name) @property def py_annotation(self): - ''' This property's Python type annotation. ''' + """This property's Python type annotation.""" if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -243,7 +246,7 @@ def py_annotation(self): @classmethod def from_json(cls, property): - ''' Instantiate a CDP property from a JSON object. ''' + """Instantiate a CDP property from a JSON object.""" return cls( property['name'], property.get('description'), @@ -257,7 +260,7 @@ def from_json(cls, property): ) def generate_decl(self): - ''' Generate the code that declares this property. ''' + """Generate the code that declares this property.""" code = inline_doc(self.description) if code: code += '\n' @@ -267,8 +270,8 @@ def generate_decl(self): return code def generate_to_json(self, dict_, use_self=True): - ''' Generate the code that exports this property to the specified JSON - dict. ''' + """Generate the code that exports this property to the specified JSON + dict.""" self_ref = 'self.' if use_self else '' assign = f"{dict_}['{self.name}'] = " if self.items: @@ -290,8 +293,8 @@ def generate_to_json(self, dict_, use_self=True): return code def generate_from_json(self, dict_): - ''' Generate the code that creates an instance from a JSON dict named - ``dict_``. ''' + """Generate the code that creates an instance from a JSON dict named + ``dict_``.""" if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -314,7 +317,7 @@ def generate_from_json(self, dict_): @dataclass class CdpType: - ''' A top-level CDP type. ''' + """A top-level CDP type.""" id: str description: Optional[str] type: str @@ -324,7 +327,7 @@ class CdpType: @classmethod def from_json(cls, type_): - ''' Instantiate a CDP type from a JSON object. ''' + """Instantiate a CDP type from a JSON object.""" return cls( type_['id'], type_.get('description'), @@ -335,7 +338,7 @@ def from_json(cls, type_): ) def generate_code(self): - ''' Generate Python code for this type. ''' + """Generate Python code for this type.""" logger.debug('Generating type %s: %s', self.id, self.type) if self.enum: return self.generate_enum_code() @@ -344,7 +347,7 @@ def generate_code(self): return self.generate_primitive_code() def generate_primitive_code(self): - ''' Generate code for a primitive type. ''' + """Generate code for a primitive type.""" if self.items: if self.items.ref: nested_type = ref_to_python(self.items.ref) @@ -381,13 +384,13 @@ def __repr__(self): return code def generate_enum_code(self): - ''' - Generate an "enum" type. + """Generate an "enum" type. + Enums are handled by making a python class that contains only class members. Each class member is upper snaked case, e.g. ``MyTypeClass.MY_ENUM_VALUE`` and is assigned a string value from the CDP metadata. - ''' + """ def_to_json = dedent('''\ def to_json(self): return self.value''') @@ -411,11 +414,11 @@ def from_json(cls, json): return code def generate_class_code(self): - ''' - Generate a class type. + """Generate a class type. + Top-level types that are defined as a CDP ``object`` are turned into Python dataclasses. - ''' + """ # children = set() code = dedent(f'''\ @dataclass @@ -463,7 +466,7 @@ def from_json(cls, json): return code def get_refs(self): - ''' Return all refs for this type. ''' + """Return all refs for this type.""" refs = set() if self.enum: # Enum types don't have refs. @@ -484,10 +487,10 @@ def get_refs(self): class CdpParameter(CdpProperty): - ''' A parameter to a CDP command. ''' + """A parameter to a CDP command.""" def generate_code(self): - ''' Generate the code for a parameter in a function call. ''' + """Generate the code for a parameter in a function call.""" if self.items: if self.items.ref: nested_type = ref_to_python(self.items.ref) @@ -509,7 +512,7 @@ def generate_code(self): return code def generate_decl(self): - ''' Generate the declaration for this parameter. ''' + """Generate the declaration for this parameter.""" if self.description: code = inline_doc(self.description) code += '\n' @@ -519,7 +522,7 @@ def generate_decl(self): return code def generate_doc(self): - ''' Generate the docstring for this parameter. ''' + """Generate the docstring for this parameter.""" doc = f':param {self.py_name}:' if self.experimental: @@ -534,18 +537,16 @@ def generate_doc(self): return doc def generate_from_json(self, dict_): - ''' - Generate the code to instantiate this parameter from a JSON dict. - ''' + """Generate the code to instantiate this parameter from a JSON dict.""" code = super().generate_from_json(dict_) return f'{self.py_name}={code}' class CdpReturn(CdpProperty): - ''' A return value from a CDP command. ''' + """A return value from a CDP command.""" @property def py_annotation(self): - ''' Return the Python type annotation for this return. ''' + """Return the Python type annotation for this return.""" if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -564,7 +565,7 @@ def py_annotation(self): return ann def generate_doc(self): - ''' Generate the docstring for this return. ''' + """Generate the docstring for this return.""" if self.description: doc = self.description.replace('\n', ' ') if self.optional: @@ -574,13 +575,13 @@ def generate_doc(self): return doc def generate_return(self, dict_): - ''' Generate code for returning this value. ''' + """Generate code for returning this value.""" return super().generate_from_json(dict_) @dataclass class CdpCommand: - ''' A CDP command. ''' + """A CDP command.""" name: str description: str experimental: bool @@ -591,12 +592,12 @@ class CdpCommand: @property def py_name(self): - ''' Get a Python name for this command. ''' + """Get a Python name for this command.""" return snake_case(self.name) @classmethod def from_json(cls, command, domain) -> 'CdpCommand': - ''' Instantiate a CDP command from a JSON object. ''' + """Instantiate a CDP command from a JSON object.""" parameters = command.get('parameters', []) returns = command.get('returns', []) @@ -611,7 +612,7 @@ def from_json(cls, command, domain) -> 'CdpCommand': ) def generate_code(self): - ''' Generate code for a CDP command. ''' + """Generate code for a CDP command.""" global current_version # Generate the function header if len(self.returns) == 0: @@ -698,7 +699,7 @@ def generate_code(self): return code def get_refs(self): - ''' Get all refs for this command. ''' + """Get all refs for this command.""" refs = set() for type_ in itertools.chain(self.parameters, self.returns): if type_.items and type_.items.ref: @@ -710,7 +711,7 @@ def get_refs(self): @dataclass class CdpEvent: - ''' A CDP event object. ''' + """A CDP event object.""" name: str description: Optional[str] deprecated: bool @@ -720,12 +721,12 @@ class CdpEvent: @property def py_name(self): - ''' Return the Python class name for this event. ''' + """Return the Python class name for this event.""" return inflection.camelize(self.name, uppercase_first_letter=True) @classmethod def from_json(cls, json: dict, domain: str): - ''' Create a new CDP event instance from a JSON dict. ''' + """Create a new CDP event instance from a JSON dict.""" return cls( json['name'], json.get('description'), @@ -737,7 +738,7 @@ def from_json(cls, json: dict, domain: str): ) def generate_code(self): - ''' Generate code for a CDP event. ''' + """Generate code for a CDP event.""" global current_version code = dedent(f'''\ @event_class('{self.domain}.{self.name}') @@ -772,7 +773,7 @@ def from_json(cls, json: T_JSON_DICT) -> {self.py_name}: return code def get_refs(self): - ''' Get all refs for this event. ''' + """Get all refs for this event.""" refs = set() for param in self.parameters: if param.items and param.items.ref: @@ -784,7 +785,7 @@ def get_refs(self): @dataclass class CdpDomain: - ''' A CDP domain contains metadata, types, commands, and events. ''' + """A CDP domain contains metadata, types, commands, and events.""" domain: str description: Optional[str] experimental: bool @@ -795,12 +796,12 @@ class CdpDomain: @property def module(self): - ''' The name of the Python module for this CDP domain. ''' + """The name of the Python module for this CDP domain.""" return snake_case(self.domain) @classmethod def from_json(cls, domain: dict): - ''' Instantiate a CDP domain from a JSON object. ''' + """Instantiate a CDP domain from a JSON object.""" types = domain.get('types', []) commands = domain.get('commands', []) events = domain.get('events', []) @@ -818,7 +819,7 @@ def from_json(cls, domain: dict): ) def generate_code(self): - ''' Generate the Python module code for a given CDP domain. ''' + """Generate the Python module code for a given CDP domain.""" exp = ' (experimental)' if self.experimental else '' code = MODULE_HEADER.format(self.domain, exp) import_code = self.generate_imports() @@ -837,14 +838,14 @@ def generate_code(self): return code def generate_imports(self): - ''' - Determine which modules this module depends on and emit the code to + """Determine which modules this module depends on and emit the code to import those modules. + Notice that CDP defines a ``dependencies`` field for each domain, but these dependencies are a subset of the modules that we actually need to import to make our Python code work correctly and type safe. So we ignore the CDP's declared dependencies and compute them ourselves. - ''' + """ refs = set() for type_ in self.types: refs |= type_.get_refs() @@ -865,9 +866,7 @@ def generate_imports(self): return code def generate_sphinx(self): - ''' - Generate a Sphinx document for this domain. - ''' + """Generate a Sphinx document for this domain.""" docs = self.domain + '\n' docs += '=' * len(self.domain) + '\n\n' if self.description: @@ -929,12 +928,12 @@ def generate_sphinx(self): def parse(json_path, output_path): - ''' - Parse JSON protocol description and return domain objects. + """Parse JSON protocol description and return domain objects. + :param Path json_path: path to a JSON CDP schema :param Path output_path: a directory path to create the modules in :returns: a list of CDP domain objects - ''' + """ global current_version with open(json_path, encoding="utf-8") as json_file: schema = json.load(json_file) @@ -948,12 +947,12 @@ def parse(json_path, output_path): def generate_init(init_path, domains): - ''' - Generate an ``__init__.py`` that exports the specified modules. - :param Path init_path: a file path to create the init file in - :param list[tuple] modules: a list of modules each represented as tuples - of (name, list_of_exported_symbols) - ''' + """Generate an ``__init__.py`` that exports the specified modules. + + :param Path init_path: a file path to create the init file in :param + list[tuple] modules: a list of modules each represented as + tuples of (name, list_of_exported_symbols) + """ with open(init_path, "w", encoding="utf-8") as init_file: init_file.write(INIT_HEADER) for domain in domains: @@ -962,9 +961,7 @@ def generate_init(init_path, domains): def generate_docs(docs_path, domains): - ''' - Generate Sphinx documents for each domain. - ''' + """Generate Sphinx documents for each domain.""" logger.info('Generating Sphinx documents') # Remove generated documents @@ -979,7 +976,7 @@ def generate_docs(docs_path, domains): def main(browser_protocol_path, js_protocol_path, output_path): - ''' Main entry point. ''' + """Main entry point.""" output_path = Path(output_path).resolve() json_paths = [ browser_protocol_path, diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 46e894cad62f2..47edf787156ec 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -90,8 +90,8 @@ 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: - """Wait until the method returns a value that is not False - + """Wait until the method returns a value that is not False. + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. @@ -141,8 +141,8 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = raise TimeoutException(message, screen, stacktrace) def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: - """Wait until the method returns a value that is not False - + """Wait until the method returns a value that is not False. + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. diff --git a/py/test/selenium/webdriver/common/example2.py b/py/test/selenium/webdriver/common/example2.py index aa31cfab4fb5b..fdc05df79d4d4 100644 --- a/py/test/selenium/webdriver/common/example2.py +++ b/py/test/selenium/webdriver/common/example2.py @@ -22,7 +22,8 @@ def test_search(driver): """This example shows how to use the page object pattern. For more information about this pattern, see: - https://github.com/SeleniumHQ/selenium/wiki/PageObjects""" + https://github.com/SeleniumHQ/selenium/wiki/PageObjects + """ google = GoogleOneBox(driver, "http://www.google.com") res = google.search_for("cheese") assert res.link_contains_match_for("Wikipedia") diff --git a/py/test/selenium/webdriver/common/interactions_tests.py b/py/test/selenium/webdriver/common/interactions_tests.py index 7ffe0e9dc4e0b..8f47d065c5aca 100644 --- a/py/test/selenium/webdriver/common/interactions_tests.py +++ b/py/test/selenium/webdriver/common/interactions_tests.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """Tests for advanced user interactions.""" import pytest @@ -145,7 +144,8 @@ def test_cannot_move_to_anull_locator(driver, pages): @pytest.mark.xfail_safari def test_clicking_on_form_elements(driver, pages): - """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from + org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("formSelectionPage.html") options = driver.find_elements(By.TAG_NAME, "option") selectThreeOptions = ( @@ -163,7 +163,8 @@ def test_clicking_on_form_elements(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_safari def test_selecting_multiple_items(driver, pages): - """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from + org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("selectableItems.html") reportingElement = driver.find_element(By.ID, "infodiv") assert "no info" == reportingElement.text @@ -208,10 +209,8 @@ def test_sending_keys_to_element(driver, pages): def test_can_send_keys_between_clicks(driver, pages): - """ - For W3C, ensures that the correct number of pauses are given to the other - input device. - """ + """For W3C, ensures that the correct number of pauses are given to the + other input device.""" pages.load("javascriptPage.html") keyup = driver.find_element(By.ID, "keyUp") keydown = driver.find_element(By.ID, "keyDown") diff --git a/py/test/selenium/webdriver/common/interactions_with_device_tests.py b/py/test/selenium/webdriver/common/interactions_with_device_tests.py index f82f3967a0776..2ba74b4211bb4 100644 --- a/py/test/selenium/webdriver/common/interactions_with_device_tests.py +++ b/py/test/selenium/webdriver/common/interactions_with_device_tests.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """Tests for advanced user interactions.""" import pytest @@ -118,7 +117,8 @@ def test_cannot_move_to_anull_locator_with_pointer(driver, pages): @pytest.mark.xfail_safari def test_clicking_on_form_elements_with_pointer(driver, pages): - """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from + org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("formSelectionPage.html") options = driver.find_elements(By.TAG_NAME, "option") mouse = PointerInput(interaction.POINTER_MOUSE, "test mouse") @@ -142,7 +142,8 @@ def test_clicking_on_form_elements_with_pointer(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_safari def test_selecting_multiple_items_with_devices(driver, pages): - """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from + org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("selectableItems.html") reportingElement = driver.find_element(By.ID, "infodiv") assert "no info" == reportingElement.text @@ -194,10 +195,8 @@ def test_sending_keys_to_element_with_keyboard(driver, pages): def test_can_send_keys_between_clicks_with_keyboard(driver, pages): - """ - For W3C, ensures that the correct number of pauses are given to the other - input device. - """ + """For W3C, ensures that the correct number of pauses are given to the + other input device.""" pages.load("javascriptPage.html") keyup = driver.find_element(By.ID, "keyUp") keydown = driver.find_element(By.ID, "keyDown") diff --git a/py/test/selenium/webdriver/common/page_loader.py b/py/test/selenium/webdriver/common/page_loader.py index c6fe18d7ce177..6c50bf3af7c8e 100644 --- a/py/test/selenium/webdriver/common/page_loader.py +++ b/py/test/selenium/webdriver/common/page_loader.py @@ -14,13 +14,14 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +"""This module contains some decorators that can be used to support the page +models. -"""This module contains some decorators that can be used to support -the page models. For example for an action that needs a page to be fully -loaded, the @require_loaded decorator will make sure the page is loaded -before the call is invoked. -This pattern is also useful for waiting for certain asynchronous events -to happen before executing certain actions.""" +For example for an action that needs a page to be fully loaded, the +@require_loaded decorator will make sure the page is loaded before the +call is invoked. This pattern is also useful for waiting for certain +asynchronous events to happen before executing certain actions. +""" def require_loaded(func): diff --git a/py/test/selenium/webdriver/common/webserver.py b/py/test/selenium/webdriver/common/webserver.py index bd108dade8278..bb3494bf62d18 100644 --- a/py/test/selenium/webdriver/common/webserver.py +++ b/py/test/selenium/webdriver/common/webserver.py @@ -14,9 +14,11 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """A simple web server for testing purpose. -It serves the testing html pages that are needed by the webdriver unit tests.""" + +It serves the testing html pages that are needed by the webdriver unit +tests. +""" import contextlib import logging import os @@ -119,7 +121,7 @@ def do_POST(self): self.send_error(500, f"Error found: {e}") def log_message(self, format, *args): - """Override default to avoid trashing stderr""" + """Override default to avoid trashing stderr.""" pass diff --git a/py/test/unit/selenium/webdriver/remote/subtyping_tests.py b/py/test/unit/selenium/webdriver/remote/subtyping_tests.py index 1162ec7031c3d..09ff54c47a4bf 100644 --- a/py/test/unit/selenium/webdriver/remote/subtyping_tests.py +++ b/py/test/unit/selenium/webdriver/remote/subtyping_tests.py @@ -21,7 +21,8 @@ def test_web_element_not_subclassed(): - """A registered subtype of WebElement should work with isinstance checks.""" + """A registered subtype of WebElement should work with isinstance + checks.""" class MyWebElement: def __init__(self, parent, id, _w3c=True): diff --git a/scripts/update_cdp.py b/scripts/update_cdp.py index a43a9bca2963f..b88248ffae3e1 100755 --- a/scripts/update_cdp.py +++ b/scripts/update_cdp.py @@ -18,8 +18,10 @@ def get_chrome_milestone(): - """This is the same method from pinned_browser. Use --chrome_channel=Beta if - using early stable release.""" + """This is the same method from pinned_browser. + + Use --chrome_channel=Beta if using early stable release. + """ parser = argparse.ArgumentParser() parser.add_argument("--chrome_channel", default="Stable", help="Set the Chrome channel") args = parser.parse_args() From 2e5c3125faf8d0de73b546da0aecd664a49c1820 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 12 Jan 2025 12:54:02 -0500 Subject: [PATCH 03/10] Revert "format.sh" extra liting done This reverts commit 32214b69116da53833a611ff073ed022daf21b6a. --- py/generate.py | 159 +++++++++--------- py/selenium/webdriver/support/wait.py | 8 +- py/test/selenium/webdriver/common/example2.py | 3 +- .../webdriver/common/interactions_tests.py | 13 +- .../common/interactions_with_device_tests.py | 13 +- .../selenium/webdriver/common/page_loader.py | 13 +- .../selenium/webdriver/common/webserver.py | 8 +- .../webdriver/remote/subtyping_tests.py | 3 +- scripts/update_cdp.py | 6 +- 9 files changed, 112 insertions(+), 114 deletions(-) diff --git a/py/generate.py b/py/generate.py index 7b8d99b041f0f..0763084423168 100644 --- a/py/generate.py +++ b/py/generate.py @@ -89,7 +89,7 @@ def parse_json_event(json: T_JSON_DICT) -> typing.Any: def indent(s, n): - """A shortcut for ``textwrap.indent`` that always uses spaces.""" + ''' A shortcut for ``textwrap.indent`` that always uses spaces. ''' return tw_indent(s, n * ' ') @@ -97,13 +97,13 @@ def indent(s, n): def escape_backticks(docstr): - """Escape backticks in a docstring by doubling them up. - + ''' + Escape backticks in a docstring by doubling them up. This is a little tricky because RST requires a non-letter character after the closing backticks, but some CDPs docs have things like "`AxNodeId`s". If we double the backticks in that string, then it won't be valid RST. The fix is to insert an apostrophe if an "s" trails the backticks. - """ + ''' def replace_one(match): if match.group(2) == 's': return f"``{match.group(1)}``'s" @@ -119,7 +119,7 @@ def replace_one(match): def inline_doc(description): - """Generate an inline doc, e.g. ``#: This type is a ...``""" + ''' Generate an inline doc, e.g. ``#: This type is a ...`` ''' if not description: return '' @@ -129,7 +129,7 @@ def inline_doc(description): def docstring(description): - """Generate a docstring from a description.""" + ''' Generate a docstring from a description. ''' if not description: return '' @@ -138,7 +138,7 @@ def docstring(description): def is_builtin(name): - """Return True if ``name`` would shadow a builtin.""" + ''' Return True if ``name`` would shadow a builtin. ''' try: getattr(builtins, name) return True @@ -147,11 +147,8 @@ def is_builtin(name): def snake_case(name): - """Convert a camel case name to snake case. - - If the name would shadow a Python builtin, then append an - underscore. - """ + ''' Convert a camel case name to snake case. If the name would shadow a + Python builtin, then append an underscore. ''' name = inflection.underscore(name) if is_builtin(name): name += '_' @@ -159,10 +156,10 @@ def snake_case(name): def ref_to_python(ref): - """Convert a CDP ``$ref`` to the name of a Python type. - + ''' + Convert a CDP ``$ref`` to the name of a Python type. For a dotted ref, the part before the dot is snake cased. - """ + ''' if '.' in ref: domain, subtype = ref.split('.') ref = f'{snake_case(domain)}.{subtype}' @@ -170,7 +167,7 @@ def ref_to_python(ref): class CdpPrimitiveType(Enum): - """All of the CDP types that map directly to a Python type.""" + ''' All of the CDP types that map directly to a Python type. ''' boolean = 'bool' integer = 'int' number = 'float' @@ -179,14 +176,14 @@ class CdpPrimitiveType(Enum): @classmethod def get_annotation(cls, cdp_type): - """Return a type annotation for the CDP type.""" + ''' Return a type annotation for the CDP type. ''' if cdp_type == 'any': return 'typing.Any' return cls[cdp_type].value @classmethod def get_constructor(cls, cdp_type, val): - """Return the code to construct a value for a given CDP type.""" + ''' Return the code to construct a value for a given CDP type. ''' if cdp_type == 'any': return val cons = cls[cdp_type].value @@ -195,19 +192,19 @@ def get_constructor(cls, cdp_type, val): @dataclass class CdpItems: - """Represents the type of a repeated item.""" + ''' Represents the type of a repeated item. ''' type: str ref: str @classmethod def from_json(cls, type): - """Generate code to instantiate an item from a JSON object.""" + ''' Generate code to instantiate an item from a JSON object. ''' return cls(type.get('type'), type.get('$ref')) @dataclass class CdpProperty: - """A property belonging to a non-primitive CDP type.""" + ''' A property belonging to a non-primitive CDP type. ''' name: str description: Optional[str] type: Optional[str] @@ -220,12 +217,12 @@ class CdpProperty: @property def py_name(self): - """Get this property's Python name.""" + ''' Get this property's Python name. ''' return snake_case(self.name) @property def py_annotation(self): - """This property's Python type annotation.""" + ''' This property's Python type annotation. ''' if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -246,7 +243,7 @@ def py_annotation(self): @classmethod def from_json(cls, property): - """Instantiate a CDP property from a JSON object.""" + ''' Instantiate a CDP property from a JSON object. ''' return cls( property['name'], property.get('description'), @@ -260,7 +257,7 @@ def from_json(cls, property): ) def generate_decl(self): - """Generate the code that declares this property.""" + ''' Generate the code that declares this property. ''' code = inline_doc(self.description) if code: code += '\n' @@ -270,8 +267,8 @@ def generate_decl(self): return code def generate_to_json(self, dict_, use_self=True): - """Generate the code that exports this property to the specified JSON - dict.""" + ''' Generate the code that exports this property to the specified JSON + dict. ''' self_ref = 'self.' if use_self else '' assign = f"{dict_}['{self.name}'] = " if self.items: @@ -293,8 +290,8 @@ def generate_to_json(self, dict_, use_self=True): return code def generate_from_json(self, dict_): - """Generate the code that creates an instance from a JSON dict named - ``dict_``.""" + ''' Generate the code that creates an instance from a JSON dict named + ``dict_``. ''' if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -317,7 +314,7 @@ def generate_from_json(self, dict_): @dataclass class CdpType: - """A top-level CDP type.""" + ''' A top-level CDP type. ''' id: str description: Optional[str] type: str @@ -327,7 +324,7 @@ class CdpType: @classmethod def from_json(cls, type_): - """Instantiate a CDP type from a JSON object.""" + ''' Instantiate a CDP type from a JSON object. ''' return cls( type_['id'], type_.get('description'), @@ -338,7 +335,7 @@ def from_json(cls, type_): ) def generate_code(self): - """Generate Python code for this type.""" + ''' Generate Python code for this type. ''' logger.debug('Generating type %s: %s', self.id, self.type) if self.enum: return self.generate_enum_code() @@ -347,7 +344,7 @@ def generate_code(self): return self.generate_primitive_code() def generate_primitive_code(self): - """Generate code for a primitive type.""" + ''' Generate code for a primitive type. ''' if self.items: if self.items.ref: nested_type = ref_to_python(self.items.ref) @@ -384,13 +381,13 @@ def __repr__(self): return code def generate_enum_code(self): - """Generate an "enum" type. - + ''' + Generate an "enum" type. Enums are handled by making a python class that contains only class members. Each class member is upper snaked case, e.g. ``MyTypeClass.MY_ENUM_VALUE`` and is assigned a string value from the CDP metadata. - """ + ''' def_to_json = dedent('''\ def to_json(self): return self.value''') @@ -414,11 +411,11 @@ def from_json(cls, json): return code def generate_class_code(self): - """Generate a class type. - + ''' + Generate a class type. Top-level types that are defined as a CDP ``object`` are turned into Python dataclasses. - """ + ''' # children = set() code = dedent(f'''\ @dataclass @@ -466,7 +463,7 @@ def from_json(cls, json): return code def get_refs(self): - """Return all refs for this type.""" + ''' Return all refs for this type. ''' refs = set() if self.enum: # Enum types don't have refs. @@ -487,10 +484,10 @@ def get_refs(self): class CdpParameter(CdpProperty): - """A parameter to a CDP command.""" + ''' A parameter to a CDP command. ''' def generate_code(self): - """Generate the code for a parameter in a function call.""" + ''' Generate the code for a parameter in a function call. ''' if self.items: if self.items.ref: nested_type = ref_to_python(self.items.ref) @@ -512,7 +509,7 @@ def generate_code(self): return code def generate_decl(self): - """Generate the declaration for this parameter.""" + ''' Generate the declaration for this parameter. ''' if self.description: code = inline_doc(self.description) code += '\n' @@ -522,7 +519,7 @@ def generate_decl(self): return code def generate_doc(self): - """Generate the docstring for this parameter.""" + ''' Generate the docstring for this parameter. ''' doc = f':param {self.py_name}:' if self.experimental: @@ -537,16 +534,18 @@ def generate_doc(self): return doc def generate_from_json(self, dict_): - """Generate the code to instantiate this parameter from a JSON dict.""" + ''' + Generate the code to instantiate this parameter from a JSON dict. + ''' code = super().generate_from_json(dict_) return f'{self.py_name}={code}' class CdpReturn(CdpProperty): - """A return value from a CDP command.""" + ''' A return value from a CDP command. ''' @property def py_annotation(self): - """Return the Python type annotation for this return.""" + ''' Return the Python type annotation for this return. ''' if self.items: if self.items.ref: py_ref = ref_to_python(self.items.ref) @@ -565,7 +564,7 @@ def py_annotation(self): return ann def generate_doc(self): - """Generate the docstring for this return.""" + ''' Generate the docstring for this return. ''' if self.description: doc = self.description.replace('\n', ' ') if self.optional: @@ -575,13 +574,13 @@ def generate_doc(self): return doc def generate_return(self, dict_): - """Generate code for returning this value.""" + ''' Generate code for returning this value. ''' return super().generate_from_json(dict_) @dataclass class CdpCommand: - """A CDP command.""" + ''' A CDP command. ''' name: str description: str experimental: bool @@ -592,12 +591,12 @@ class CdpCommand: @property def py_name(self): - """Get a Python name for this command.""" + ''' Get a Python name for this command. ''' return snake_case(self.name) @classmethod def from_json(cls, command, domain) -> 'CdpCommand': - """Instantiate a CDP command from a JSON object.""" + ''' Instantiate a CDP command from a JSON object. ''' parameters = command.get('parameters', []) returns = command.get('returns', []) @@ -612,7 +611,7 @@ def from_json(cls, command, domain) -> 'CdpCommand': ) def generate_code(self): - """Generate code for a CDP command.""" + ''' Generate code for a CDP command. ''' global current_version # Generate the function header if len(self.returns) == 0: @@ -699,7 +698,7 @@ def generate_code(self): return code def get_refs(self): - """Get all refs for this command.""" + ''' Get all refs for this command. ''' refs = set() for type_ in itertools.chain(self.parameters, self.returns): if type_.items and type_.items.ref: @@ -711,7 +710,7 @@ def get_refs(self): @dataclass class CdpEvent: - """A CDP event object.""" + ''' A CDP event object. ''' name: str description: Optional[str] deprecated: bool @@ -721,12 +720,12 @@ class CdpEvent: @property def py_name(self): - """Return the Python class name for this event.""" + ''' Return the Python class name for this event. ''' return inflection.camelize(self.name, uppercase_first_letter=True) @classmethod def from_json(cls, json: dict, domain: str): - """Create a new CDP event instance from a JSON dict.""" + ''' Create a new CDP event instance from a JSON dict. ''' return cls( json['name'], json.get('description'), @@ -738,7 +737,7 @@ def from_json(cls, json: dict, domain: str): ) def generate_code(self): - """Generate code for a CDP event.""" + ''' Generate code for a CDP event. ''' global current_version code = dedent(f'''\ @event_class('{self.domain}.{self.name}') @@ -773,7 +772,7 @@ def from_json(cls, json: T_JSON_DICT) -> {self.py_name}: return code def get_refs(self): - """Get all refs for this event.""" + ''' Get all refs for this event. ''' refs = set() for param in self.parameters: if param.items and param.items.ref: @@ -785,7 +784,7 @@ def get_refs(self): @dataclass class CdpDomain: - """A CDP domain contains metadata, types, commands, and events.""" + ''' A CDP domain contains metadata, types, commands, and events. ''' domain: str description: Optional[str] experimental: bool @@ -796,12 +795,12 @@ class CdpDomain: @property def module(self): - """The name of the Python module for this CDP domain.""" + ''' The name of the Python module for this CDP domain. ''' return snake_case(self.domain) @classmethod def from_json(cls, domain: dict): - """Instantiate a CDP domain from a JSON object.""" + ''' Instantiate a CDP domain from a JSON object. ''' types = domain.get('types', []) commands = domain.get('commands', []) events = domain.get('events', []) @@ -819,7 +818,7 @@ def from_json(cls, domain: dict): ) def generate_code(self): - """Generate the Python module code for a given CDP domain.""" + ''' Generate the Python module code for a given CDP domain. ''' exp = ' (experimental)' if self.experimental else '' code = MODULE_HEADER.format(self.domain, exp) import_code = self.generate_imports() @@ -838,14 +837,14 @@ def generate_code(self): return code def generate_imports(self): - """Determine which modules this module depends on and emit the code to + ''' + Determine which modules this module depends on and emit the code to import those modules. - Notice that CDP defines a ``dependencies`` field for each domain, but these dependencies are a subset of the modules that we actually need to import to make our Python code work correctly and type safe. So we ignore the CDP's declared dependencies and compute them ourselves. - """ + ''' refs = set() for type_ in self.types: refs |= type_.get_refs() @@ -866,7 +865,9 @@ def generate_imports(self): return code def generate_sphinx(self): - """Generate a Sphinx document for this domain.""" + ''' + Generate a Sphinx document for this domain. + ''' docs = self.domain + '\n' docs += '=' * len(self.domain) + '\n\n' if self.description: @@ -928,12 +929,12 @@ def generate_sphinx(self): def parse(json_path, output_path): - """Parse JSON protocol description and return domain objects. - + ''' + Parse JSON protocol description and return domain objects. :param Path json_path: path to a JSON CDP schema :param Path output_path: a directory path to create the modules in :returns: a list of CDP domain objects - """ + ''' global current_version with open(json_path, encoding="utf-8") as json_file: schema = json.load(json_file) @@ -947,12 +948,12 @@ def parse(json_path, output_path): def generate_init(init_path, domains): - """Generate an ``__init__.py`` that exports the specified modules. - - :param Path init_path: a file path to create the init file in :param - list[tuple] modules: a list of modules each represented as - tuples of (name, list_of_exported_symbols) - """ + ''' + Generate an ``__init__.py`` that exports the specified modules. + :param Path init_path: a file path to create the init file in + :param list[tuple] modules: a list of modules each represented as tuples + of (name, list_of_exported_symbols) + ''' with open(init_path, "w", encoding="utf-8") as init_file: init_file.write(INIT_HEADER) for domain in domains: @@ -961,7 +962,9 @@ def generate_init(init_path, domains): def generate_docs(docs_path, domains): - """Generate Sphinx documents for each domain.""" + ''' + Generate Sphinx documents for each domain. + ''' logger.info('Generating Sphinx documents') # Remove generated documents @@ -976,7 +979,7 @@ def generate_docs(docs_path, domains): def main(browser_protocol_path, js_protocol_path, output_path): - """Main entry point.""" + ''' Main entry point. ''' output_path = Path(output_path).resolve() json_paths = [ browser_protocol_path, diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 47edf787156ec..46e894cad62f2 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -90,8 +90,8 @@ 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: - """Wait until the method returns a value that is not False. - + """Wait until the method returns a value that is not False + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. @@ -141,8 +141,8 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = raise TimeoutException(message, screen, stacktrace) def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: - """Wait until the method returns a value that is not False. - + """Wait until the method returns a value that is not False + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. diff --git a/py/test/selenium/webdriver/common/example2.py b/py/test/selenium/webdriver/common/example2.py index fdc05df79d4d4..aa31cfab4fb5b 100644 --- a/py/test/selenium/webdriver/common/example2.py +++ b/py/test/selenium/webdriver/common/example2.py @@ -22,8 +22,7 @@ def test_search(driver): """This example shows how to use the page object pattern. For more information about this pattern, see: - https://github.com/SeleniumHQ/selenium/wiki/PageObjects - """ + https://github.com/SeleniumHQ/selenium/wiki/PageObjects""" google = GoogleOneBox(driver, "http://www.google.com") res = google.search_for("cheese") assert res.link_contains_match_for("Wikipedia") diff --git a/py/test/selenium/webdriver/common/interactions_tests.py b/py/test/selenium/webdriver/common/interactions_tests.py index 8f47d065c5aca..7ffe0e9dc4e0b 100644 --- a/py/test/selenium/webdriver/common/interactions_tests.py +++ b/py/test/selenium/webdriver/common/interactions_tests.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + """Tests for advanced user interactions.""" import pytest @@ -144,8 +145,7 @@ def test_cannot_move_to_anull_locator(driver, pages): @pytest.mark.xfail_safari def test_clicking_on_form_elements(driver, pages): - """Copied from - org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("formSelectionPage.html") options = driver.find_elements(By.TAG_NAME, "option") selectThreeOptions = ( @@ -163,8 +163,7 @@ def test_clicking_on_form_elements(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_safari def test_selecting_multiple_items(driver, pages): - """Copied from - org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("selectableItems.html") reportingElement = driver.find_element(By.ID, "infodiv") assert "no info" == reportingElement.text @@ -209,8 +208,10 @@ def test_sending_keys_to_element(driver, pages): def test_can_send_keys_between_clicks(driver, pages): - """For W3C, ensures that the correct number of pauses are given to the - other input device.""" + """ + For W3C, ensures that the correct number of pauses are given to the other + input device. + """ pages.load("javascriptPage.html") keyup = driver.find_element(By.ID, "keyUp") keydown = driver.find_element(By.ID, "keyDown") diff --git a/py/test/selenium/webdriver/common/interactions_with_device_tests.py b/py/test/selenium/webdriver/common/interactions_with_device_tests.py index 2ba74b4211bb4..f82f3967a0776 100644 --- a/py/test/selenium/webdriver/common/interactions_with_device_tests.py +++ b/py/test/selenium/webdriver/common/interactions_with_device_tests.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + """Tests for advanced user interactions.""" import pytest @@ -117,8 +118,7 @@ def test_cannot_move_to_anull_locator_with_pointer(driver, pages): @pytest.mark.xfail_safari def test_clicking_on_form_elements_with_pointer(driver, pages): - """Copied from - org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("formSelectionPage.html") options = driver.find_elements(By.TAG_NAME, "option") mouse = PointerInput(interaction.POINTER_MOUSE, "test mouse") @@ -142,8 +142,7 @@ def test_clicking_on_form_elements_with_pointer(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_safari def test_selecting_multiple_items_with_devices(driver, pages): - """Copied from - org.openqa.selenium.interactions.CombinedInputActionsTest.""" + """Copied from org.openqa.selenium.interactions.CombinedInputActionsTest.""" pages.load("selectableItems.html") reportingElement = driver.find_element(By.ID, "infodiv") assert "no info" == reportingElement.text @@ -195,8 +194,10 @@ def test_sending_keys_to_element_with_keyboard(driver, pages): def test_can_send_keys_between_clicks_with_keyboard(driver, pages): - """For W3C, ensures that the correct number of pauses are given to the - other input device.""" + """ + For W3C, ensures that the correct number of pauses are given to the other + input device. + """ pages.load("javascriptPage.html") keyup = driver.find_element(By.ID, "keyUp") keydown = driver.find_element(By.ID, "keyDown") diff --git a/py/test/selenium/webdriver/common/page_loader.py b/py/test/selenium/webdriver/common/page_loader.py index 6c50bf3af7c8e..c6fe18d7ce177 100644 --- a/py/test/selenium/webdriver/common/page_loader.py +++ b/py/test/selenium/webdriver/common/page_loader.py @@ -14,14 +14,13 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -"""This module contains some decorators that can be used to support the page -models. -For example for an action that needs a page to be fully loaded, the -@require_loaded decorator will make sure the page is loaded before the -call is invoked. This pattern is also useful for waiting for certain -asynchronous events to happen before executing certain actions. -""" +"""This module contains some decorators that can be used to support +the page models. For example for an action that needs a page to be fully +loaded, the @require_loaded decorator will make sure the page is loaded +before the call is invoked. +This pattern is also useful for waiting for certain asynchronous events +to happen before executing certain actions.""" def require_loaded(func): diff --git a/py/test/selenium/webdriver/common/webserver.py b/py/test/selenium/webdriver/common/webserver.py index bb3494bf62d18..bd108dade8278 100644 --- a/py/test/selenium/webdriver/common/webserver.py +++ b/py/test/selenium/webdriver/common/webserver.py @@ -14,11 +14,9 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -"""A simple web server for testing purpose. -It serves the testing html pages that are needed by the webdriver unit -tests. -""" +"""A simple web server for testing purpose. +It serves the testing html pages that are needed by the webdriver unit tests.""" import contextlib import logging import os @@ -121,7 +119,7 @@ def do_POST(self): self.send_error(500, f"Error found: {e}") def log_message(self, format, *args): - """Override default to avoid trashing stderr.""" + """Override default to avoid trashing stderr""" pass diff --git a/py/test/unit/selenium/webdriver/remote/subtyping_tests.py b/py/test/unit/selenium/webdriver/remote/subtyping_tests.py index 09ff54c47a4bf..1162ec7031c3d 100644 --- a/py/test/unit/selenium/webdriver/remote/subtyping_tests.py +++ b/py/test/unit/selenium/webdriver/remote/subtyping_tests.py @@ -21,8 +21,7 @@ def test_web_element_not_subclassed(): - """A registered subtype of WebElement should work with isinstance - checks.""" + """A registered subtype of WebElement should work with isinstance checks.""" class MyWebElement: def __init__(self, parent, id, _w3c=True): diff --git a/scripts/update_cdp.py b/scripts/update_cdp.py index b88248ffae3e1..a43a9bca2963f 100755 --- a/scripts/update_cdp.py +++ b/scripts/update_cdp.py @@ -18,10 +18,8 @@ def get_chrome_milestone(): - """This is the same method from pinned_browser. - - Use --chrome_channel=Beta if using early stable release. - """ + """This is the same method from pinned_browser. Use --chrome_channel=Beta if + using early stable release.""" parser = argparse.ArgumentParser() parser.add_argument("--chrome_channel", default="Stable", help="Set the Chrome channel") args = parser.parse_args() From 2d98e3f3a01dd114276afe5342b91767965849fe Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 12 Jan 2025 12:56:43 -0500 Subject: [PATCH 04/10] linting --- py/selenium/webdriver/support/wait.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 46e894cad62f2..47edf787156ec 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -90,8 +90,8 @@ 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: - """Wait until the method returns a value that is not False - + """Wait until the method returns a value that is not False. + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. @@ -141,8 +141,8 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = raise TimeoutException(message, screen, stacktrace) def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: - """Wait until the method returns a value that is not False - + """Wait until the method returns a value that is not False. + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. From e3d089ff002a17f38e0cde8cd3d90273a4d79d74 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sun, 12 Jan 2025 12:57:52 -0500 Subject: [PATCH 05/10] Update wait.py --- py/selenium/webdriver/support/wait.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 47edf787156ec..8884e00c90ac8 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -69,8 +69,7 @@ def __init__( >>> >>> # Wait until the element is no longer visible >>> is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not( - ... lambda x: x.find_element(By.ID, "someId").is_displayed() - ... ) + ... lambda x: x.find_element(By.ID, "someId").is_displayed()) """ self._driver = driver self._timeout = float(timeout) From a1ba8d74961ec9c1222e54e1a27bf136f4799bdc Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 12 Jan 2025 14:48:20 -0500 Subject: [PATCH 06/10] formatted again --- py/selenium/webdriver/support/wait.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 8884e00c90ac8..c936acd3c382f 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -50,14 +50,14 @@ def __init__( Parameters: ---------- - driver + driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) or a WebElement - timeout + timeout - Number of seconds before timing out - poll_frequency + poll_frequency - Sleep interval between calls - By default, it is 0.5 second. - ignored_exceptions + ignored_exceptions - Iterable structure of exception classes ignored during calls. - By default, it contains NoSuchElementException only. @@ -91,21 +91,21 @@ def __repr__(self): def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T: """Wait until the method returns a value that is not False. - Calls the method provided with the driver as an argument until the + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. Parameters: ---------- method: callable(WebDriver) - - A callable object that takes a WebDriver instance as an argument. + - A callable object that takes a WebDriver instance as an argument. message: str - Optional message for :exc:`TimeoutException` - + Return: ------- object: T - The result of the last call to `method` - + Raises: ------- TimeoutException @@ -142,21 +142,21 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Literal[True]]: """Wait until the method returns a value that is not False. - Calls the method provided with the driver as an argument until the + Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. Parameters: ---------- method: callable(WebDriver) - - A callable object that takes a WebDriver instance as an argument. + - A callable object that takes a WebDriver instance as an argument. message: str - Optional message for :exc:`TimeoutException` - + Return: ------- object: T - The result of the last call to `method` - + Raises: ------- TimeoutException From 975776585b910845546e9ece58e15fb786397fe0 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 12 Jan 2025 14:52:37 -0500 Subject: [PATCH 07/10] ugh, format.sh again? --- py/selenium/webdriver/support/wait.py | 16 +++++++---- scripts/format.sh | 40 +++++++++++++-------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index c936acd3c382f..e990111cb2d35 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -51,7 +51,8 @@ def __init__( Parameters: ---------- driver - - Instance of WebDriver (Ie, Firefox, Chrome or Remote) or a WebElement + - Instance of WebDriver (Ie, Firefox, Chrome or Remote) or + a WebElement timeout - Number of seconds before timing out poll_frequency @@ -68,8 +69,8 @@ def __init__( >>> from selenium.common.exceptions import ElementNotVisibleException >>> >>> # Wait until the element is no longer visible - >>> is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not( - ... lambda x: x.find_element(By.ID, "someId").is_displayed()) + >>> is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)) + ... .until_not(lambda x: x.find_element(By.ID, "someId").is_displayed()) """ self._driver = driver self._timeout = float(timeout) @@ -109,7 +110,8 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = Raises: ------- TimeoutException - - If 'method' does not return a truthy value within the WebDriverWait object's timeout + - If 'method' does not return a truthy value within the WebDriverWait + object's timeout Example: -------- @@ -160,7 +162,8 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit Raises: ------- TimeoutException - - If 'method' does not return False within the WebDriverWait object's timeout + - If 'method' does not return False within the WebDriverWait + object's timeout Example: -------- @@ -170,7 +173,8 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit # Wait until an element is visible on the page >>> wait = WebDriverWait(driver, 10) - >>> is_disappeared = wait.until_not(EC.visibility_of_element_located((By.ID, "exampleId"))) + >>> is_disappeared = wait.until_not(EC.visibility_of_element_located( + ... (By.ID, "exampleId"))) """ end_time = time.monotonic() + self._timeout while True: diff --git a/scripts/format.sh b/scripts/format.sh index df0156aa14efe..22ef65eed7d2d 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -10,26 +10,26 @@ WORKSPACE_ROOT="$(bazel info workspace 2>/dev/null)" GOOGLE_JAVA_FORMAT="$(bazel run --run_under=echo //scripts:google-java-format)" -section "Buildifier" -echo " buildifier" >&2 -bazel run //:buildifier - -section "Java" -echo " google-java-format" >&2 -find "$PWD/java" -type f -name '*.java' | xargs "$GOOGLE_JAVA_FORMAT" --replace - -section "Javascript" -echo " javascript/node/selenium-webdriver - prettier" >&2 -NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/node/selenium-webdriver" -bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" - -section "Ruby" -echo " rubocop" >&2 -bazel run //rb:lint - -section "Rust" -echo " rustfmt" >&2 -bazel run @rules_rust//:rustfmt +# section "Buildifier" +# echo " buildifier" >&2 +# bazel run //:buildifier + +# section "Java" +# echo " google-java-format" >&2 +# find "$PWD/java" -type f -name '*.java' | xargs "$GOOGLE_JAVA_FORMAT" --replace + +# section "Javascript" +# echo " javascript/node/selenium-webdriver - prettier" >&2 +# NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/node/selenium-webdriver" +# bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" + +# section "Ruby" +# echo " rubocop" >&2 +# bazel run //rb:lint + +# section "Rust" +# echo " rustfmt" >&2 +# bazel run @rules_rust//:rustfmt # TODO: use bazel target when rules_python supports formatting section "Python" From 4bb78f5c63c92043766c79ad6c728e1fc95f32d8 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:53:12 -0500 Subject: [PATCH 08/10] Update format.sh --- scripts/format.sh | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/format.sh b/scripts/format.sh index 22ef65eed7d2d..df0156aa14efe 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -10,26 +10,26 @@ WORKSPACE_ROOT="$(bazel info workspace 2>/dev/null)" GOOGLE_JAVA_FORMAT="$(bazel run --run_under=echo //scripts:google-java-format)" -# section "Buildifier" -# echo " buildifier" >&2 -# bazel run //:buildifier - -# section "Java" -# echo " google-java-format" >&2 -# find "$PWD/java" -type f -name '*.java' | xargs "$GOOGLE_JAVA_FORMAT" --replace - -# section "Javascript" -# echo " javascript/node/selenium-webdriver - prettier" >&2 -# NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/node/selenium-webdriver" -# bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" - -# section "Ruby" -# echo " rubocop" >&2 -# bazel run //rb:lint - -# section "Rust" -# echo " rustfmt" >&2 -# bazel run @rules_rust//:rustfmt +section "Buildifier" +echo " buildifier" >&2 +bazel run //:buildifier + +section "Java" +echo " google-java-format" >&2 +find "$PWD/java" -type f -name '*.java' | xargs "$GOOGLE_JAVA_FORMAT" --replace + +section "Javascript" +echo " javascript/node/selenium-webdriver - prettier" >&2 +NODE_WEBDRIVER="${WORKSPACE_ROOT}/javascript/node/selenium-webdriver" +bazel run //javascript:prettier -- "${NODE_WEBDRIVER}" --write "${NODE_WEBDRIVER}/.prettierrc" + +section "Ruby" +echo " rubocop" >&2 +bazel run //rb:lint + +section "Rust" +echo " rustfmt" >&2 +bazel run @rules_rust//:rustfmt # TODO: use bazel target when rules_python supports formatting section "Python" From 526d541c0ea65b884498effde0eb4052455e8711 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:55:40 -0500 Subject: [PATCH 09/10] Change Parameters to Attributes --- py/selenium/webdriver/support/wait.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index e990111cb2d35..59d1decb711d2 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -48,7 +48,7 @@ def __init__( ): """Constructor, takes a WebDriver instance and timeout in seconds. - Parameters: + Attributes: ---------- driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) or @@ -95,7 +95,7 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. - Parameters: + Attributes: ---------- method: callable(WebDriver) - A callable object that takes a WebDriver instance as an argument. @@ -147,7 +147,7 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. - Parameters: + Attributes: ---------- method: callable(WebDriver) - A callable object that takes a WebDriver instance as an argument. From 86ef9299754b0d9d54e8a079bdef54a2c93c79ec Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:59:50 -0500 Subject: [PATCH 10/10] accidentally replaced all --- py/selenium/webdriver/support/wait.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 59d1decb711d2..36cf2c0de62cc 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -95,7 +95,7 @@ def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. - Attributes: + Parameters: ---------- method: callable(WebDriver) - A callable object that takes a WebDriver instance as an argument. @@ -147,7 +147,7 @@ def until_not(self, method: Callable[[D], T], message: str = "") -> Union[T, Lit Calls the method provided with the driver as an argument until the return value does not evaluate to ``False``. - Attributes: + Parameters: ---------- method: callable(WebDriver) - A callable object that takes a WebDriver instance as an argument.