Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5d6aaa1
Fix "NoReturn" type-hinnts.
cpburnz Dec 8, 2021
b4623ab
Reflect how find_elements returns a list, not just one WebElement.
colons Dec 21, 2021
622c6aa
Reduce mypy's output to only include errors in the annotations.
colons Dec 21, 2021
ce2203e
Merge branch 'patch-1' of github.com:cpburnz/selenium into issue/make…
colons Dec 21, 2021
f3b1e2a
Merge branch 'issue/find-elements-return-type' into issue/make-mypy-o…
colons Dec 21, 2021
293e311
Fix some more incorrect `NoReturn` usage.
colons Dec 21, 2021
98aab55
Placate mypy about an attribute missing from ChromeOptions.
colons Dec 21, 2021
9d766bc
Create an `ExecuteResponse` type to use for .execute() return values.
colons Dec 22, 2021
9215532
Fix some easy-to-fix errors in existing type annotations.
colons Dec 22, 2021
0174634
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
colons Dec 30, 2021
153289d
Make send_keys behave like it did before this branch.
colons Dec 30, 2021
62911c1
Placate a few more fair mypy complaints.
colons Dec 30, 2021
0fcffcc
Annotate this 'enc' dict.
colons Dec 30, 2021
275fec4
Properly annotate ActionBuilder.devices.
colons Dec 30, 2021
049ced2
Fix an ImportError on Python 3.7 and earlier.
colons Dec 31, 2021
fb2b991
Shuffle my feet nervously.
colons Dec 31, 2021
5b1d1d1
Don't special case instances of Keys.
colons Jan 4, 2022
41b47ea
Prevent an UnboundLocalError in WebElement.send_keys().
colons Jan 26, 2022
12afeec
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
colons May 9, 2022
726d9d3
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
titusfortner May 10, 2022
a526677
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
titusfortner May 10, 2022
e94f36a
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
colons Aug 27, 2022
a09d7ba
Fix most of the new mypy complaints.
colons Aug 27, 2022
0f38f31
Remove some unnecessary type: ignore instances.
colons Aug 27, 2022
73778f1
Store alpha as a float when dealing with rgba values.
colons Aug 27, 2022
4b49f35
Placate flake8.
colons Aug 27, 2022
bc7b7b8
Fix an incorrect merge conflict resolution.
colons Aug 27, 2022
b59e684
Put the _ignore_local_proxy assignment somewhere more like where it was.
colons Aug 27, 2022
0c936ff
Complete this thought.
colons Aug 27, 2022
c75b9d1
Reflect `android_package`'s nature as a required argument.
colons Aug 27, 2022
3c0d215
Remove an errant newline that I added in a merge.
colons Aug 27, 2022
83ee29e
Set up these type aliases properly.
colons Aug 27, 2022
5100748
Enable type checking for tests and fix the problems that brought up.
colons Aug 27, 2022
5f87c65
Placate flake8.
colons Aug 27, 2022
0f83235
Don't break on Python versions that don't have TypeAlias.
colons Aug 27, 2022
576ea7b
Don't expect these attributes to magically chage their own type.
colons Aug 27, 2022
19c2125
Mention that mypy is an environment that exists.
colons Aug 27, 2022
05b96a3
Placate mypy.
colons Aug 27, 2022
33a9fc8
Annotate this chrome proxy test.
colons Aug 27, 2022
5d8f00a
Annotate common alert tests.
colons Aug 27, 2022
122ab17
Don't hand enums into the executor.
colons Aug 27, 2022
4f5d61b
Remove an accidental duplicate import.
colons Aug 27, 2022
a718ef5
Fix some WebDriver imports.
colons Aug 27, 2022
73223d3
Fix another case of handing enums into an executor.
colons Aug 27, 2022
ab2b7c7
Fix two more handing-enums-to-executors problem.
colons Aug 27, 2022
58a6fa8
Fix and annotate the construction of RelativeRoot dicts.
colons Aug 27, 2022
00c801c
Remove one more type: ignore.
colons Aug 27, 2022
cfd676c
Complain manually about non-By by arguments.
colons Aug 27, 2022
9f4636d
Fix the log constructed as part of event firing tests.
colons Aug 27, 2022
5fb2542
Restore error codes to the remaining type: ignore instances.
colons Aug 27, 2022
2b5fd5f
Fix how RelativeBy problems get surfaced.
colons Aug 27, 2022
4029f44
Fix a bad import.
colons Aug 27, 2022
b901712
Fix some more enums-to-executors stuff in the shadowroot module.
colons Aug 27, 2022
840c5b3
Annotate the by arguments in WebElement's element finding methods.
colons Aug 28, 2022
7b8a87e
Accept strings as `by` arguments, to avoid breaking compatibility.
colons Aug 29, 2022
b9843ed
Make sure this string-based element locating continues to work.
colons Aug 29, 2022
d081092
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
colons Aug 29, 2022
7f462e5
Merge branch 'trunk' into issue/make-mypy-only-complain-about-stuff-t…
colons Aug 30, 2022
aa7809c
Split testing of `by: str`-style calling into separate tests.
colons Aug 30, 2022
dc0c7e4
Cover the `by: str` methods for shadowroot, too.
colons Aug 30, 2022
d281d18
Ask Python to stop treating this annotation as code.
colons Aug 30, 2022
4f86baf
Make the mypy CI job fail when mypy complains.
colons Aug 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
pip install tox==2.4.1
- name: Test with tox
run: |
tox -c py/tox.ini -- --cobertura-xml-report ci || true
tox -c py/tox.ini -- --cobertura-xml-report ci
bash <(curl -s https://codecov.io/bash) -f py/ci/cobertura.xml
env:
TOXENV: mypy
Expand Down
3 changes: 2 additions & 1 deletion .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<dfackrell@bluehost.com> <unlearned@gmail.com>
<ebeans@google.com> <erikbeans@gmail.com>
<eran.mes@gmail.com> <eranm@google.com>
<iain@colons.co> <iain@bldm.us>
<james.h.evans.jr@gmail.com> <james.evans@salesforce.com>
<jbevan@google.com> <jennifer.bevan@gmail.com>
<jmleyba@gmail.com> <jleyba@google.com>
Expand All @@ -23,6 +22,8 @@ Andreas Tolfsen <ato@mozilla.com> Andreas Tolf Tolfsen
Andreas Tolfsen <ato@mozilla.com> <Andreas Tolfsen>
Aslak Hellesøy <aslak.hellesoy@gmail.com> Aslak Hellesoy
Chethana Paniyadi <cpaniyad@cpaniyadi-wsl.internal.salesforce.com> CHETANA PANIYADI
colons <colons@colons.co> <iain@bldm.us>
colons <colons@colons.co> <iain@colons.co>
Daniel Davison <daniel.jj.davison@gmail.com> ddavison
David Burns <david.burns@theautomatedtester.co.uk> David Burns
Eran Messeri <eran.mes@gmail.com> Eran Mes
Expand Down
13 changes: 4 additions & 9 deletions py/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@

from selenium import webdriver
from selenium.webdriver import DesiredCapabilities
from test.selenium.webdriver.common.webserver import SimpleWebServer
from selenium.webdriver.remote.webdriver import WebDriver
from test.selenium.webdriver.common.webserver import Pages, SimpleWebServer
from test.selenium.webdriver.common.network import get_lan_ip

from urllib.request import urlopen
Expand Down Expand Up @@ -185,14 +186,8 @@ def pytest_exception_interact(node, call, report):


@pytest.fixture
def pages(driver, webserver):
class Pages:
def url(self, name, localhost=False):
return webserver.where_is(name, localhost)

def load(self, name):
driver.get(self.url(name))
return Pages()
def pages(driver: WebDriver, webserver: SimpleWebServer) -> Pages:
return Pages(driver, webserver)


@pytest.fixture(autouse=True, scope='session')
Expand Down
2 changes: 1 addition & 1 deletion py/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from textwrap import dedent, indent as tw_indent
import typing

import inflection # type: ignore
import inflection


log_level = getattr(logging, os.environ.get('LOG_LEVEL', 'warning').upper())
Expand Down
16 changes: 2 additions & 14 deletions py/mypy.ini
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
[mypy]
files = selenium
ignore_missing_imports = False
warn_unused_configs = True
disallow_subclassing_any = True
disallow_any_generics = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
files = selenium,generate.py,setup.py,test
ignore_missing_imports = True
warn_unused_ignores = True
warn_return_any = True
warn_unreachable = True
5 changes: 2 additions & 3 deletions py/selenium/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

"""Selenium type definitions."""

import typing
from typing import Union


AnyKey = typing.Union[str, int, float]
WaitExcTypes = typing.Iterable[typing.Type[Exception]]
AnyKey = Union[str, int, float]
12 changes: 6 additions & 6 deletions py/selenium/webdriver/chromium/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import base64
import os
import warnings
from typing import List, Union, BinaryIO
from typing import Any, BinaryIO, Dict, List, Optional, Union

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.options import ArgOptions
Expand All @@ -30,10 +30,10 @@ class ChromiumOptions(ArgOptions):
def __init__(self) -> None:
super().__init__()
self._binary_location = ''
self._extension_files = []
self._extensions = []
self._experimental_options = {}
self._debugger_address = None
self._extension_files: List[str] = []
self._extensions: List[str] = []
self._experimental_options: Dict[str, Any] = {}
self._debugger_address: Optional[str] = None

@property
def binary_location(self) -> str:
Expand All @@ -52,7 +52,7 @@ def binary_location(self, value: str) -> None:
self._binary_location = value

@property
def debugger_address(self) -> str:
def debugger_address(self) -> Optional[str]:
"""
:Returns: The address of the remote devtools instance
"""
Expand Down
12 changes: 6 additions & 6 deletions py/selenium/webdriver/chromium/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import warnings

from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
from selenium.webdriver.remote.webdriver import ExecuteResponse, WebDriver as RemoteWebDriver

DEFAULT_PORT = 0
DEFAULT_SERVICE_LOG_PATH = None
Expand Down Expand Up @@ -63,7 +63,7 @@ def __init__(self, browser_name, vendor_prefix,
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if keep_alive != DEFAULT_KEEP_ALIVE and type(self) == __class__:
if keep_alive != DEFAULT_KEEP_ALIVE and type(self) == ChromiumDriver:
warnings.warn('keep_alive has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
else:
Expand Down Expand Up @@ -188,7 +188,7 @@ def get_issue_message(self):
"""
return self.execute('getIssueMessage')['value']

def set_sink_to_use(self, sink_name: str) -> dict:
def set_sink_to_use(self, sink_name: str) -> ExecuteResponse:
"""
Sets a specific sink, using its name, as a Cast session receiver target.

Expand All @@ -197,7 +197,7 @@ def set_sink_to_use(self, sink_name: str) -> dict:
"""
return self.execute('setSinkToUse', {'sinkName': sink_name})

def start_desktop_mirroring(self, sink_name: str) -> dict:
def start_desktop_mirroring(self, sink_name: str) -> ExecuteResponse:
"""
Starts a desktop mirroring session on a specific receiver target.

Expand All @@ -206,7 +206,7 @@ def start_desktop_mirroring(self, sink_name: str) -> dict:
"""
return self.execute('startDesktopMirroring', {'sinkName': sink_name})

def start_tab_mirroring(self, sink_name: str) -> dict:
def start_tab_mirroring(self, sink_name: str) -> ExecuteResponse:
"""
Starts a tab mirroring session on a specific receiver target.

Expand All @@ -215,7 +215,7 @@ def start_tab_mirroring(self, sink_name: str) -> dict:
"""
return self.execute('startTabMirroring', {'sinkName': sink_name})

def stop_casting(self, sink_name: str) -> dict:
def stop_casting(self, sink_name: str) -> ExecuteResponse:
"""
Stops the existing Cast session on a specific receiver target.

Expand Down
15 changes: 9 additions & 6 deletions py/selenium/webdriver/common/actions/action_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
# specific language governing permissions and limitations
# under the License.

from typing import Union, List
from typing import Any, Dict, Optional, List
from selenium.webdriver.remote.command import Command
from . import interaction
from .input_device import InputDevice
from .key_actions import KeyActions
from .key_input import KeyInput
from .pointer_actions import PointerActions
Expand All @@ -26,7 +27,9 @@
from .wheel_actions import WheelActions


class ActionBuilder:
class ActionBuilder(object):
devices: List[InputDevice]

def __init__(self, driver, mouse=None, wheel=None, keyboard=None, duration=250) -> None:
if not mouse:
mouse = PointerInput(interaction.POINTER_MOUSE, "mouse")
Expand All @@ -40,16 +43,16 @@ def __init__(self, driver, mouse=None, wheel=None, keyboard=None, duration=250)
self._wheel_action = WheelActions(wheel)
self.driver = driver

def get_device_with(self, name) -> Union["WheelInput", "PointerInput", "KeyInput"]:
def get_device_with(self, name) -> Optional[InputDevice]:
return next(filter(lambda x: x == name, self.devices), None)

@property
def pointer_inputs(self) -> List[PointerInput]:
return [device for device in self.devices if device.type == interaction.POINTER]
return [device for device in self.devices if isinstance(device, PointerInput)]

@property
def key_inputs(self) -> List[KeyInput]:
return [device for device in self.devices if device.type == interaction.KEY]
return [device for device in self.devices if isinstance(device, KeyInput)]

@property
def key_action(self) -> KeyActions:
Expand Down Expand Up @@ -79,7 +82,7 @@ def add_wheel_input(self, name) -> WheelInput:
return new_input

def perform(self) -> None:
enc = {"actions": []}
enc: Dict[str, List[Dict[str, Any]]] = {"actions": []}
for device in self.devices:
encoded = device.encode()
if encoded['actions']:
Expand Down
8 changes: 7 additions & 1 deletion py/selenium/webdriver/common/actions/input_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.

from abc import abstractmethod
from typing import Any, Dict, Union
import uuid


Expand All @@ -39,5 +41,9 @@ def add_action(self, action):
def clear_actions(self):
self.actions = []

def create_pause(self, duration=0):
def create_pause(self, duration: Union[int, float] = 0) -> None:
pass

@abstractmethod
def encode(self) -> Dict[str, Any]:
raise NotImplementedError()
4 changes: 2 additions & 2 deletions py/selenium/webdriver/common/actions/wheel_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ def create_scroll(self, x: int, y: int, delta_x: int,
"deltaY": delta_y, "duration": duration,
"origin": origin})

def create_pause(self, pause_duration: Union[int, float]) -> None:
def create_pause(self, duration: Union[int, float] = 0) -> None:
self.add_action(
{"type": "pause", "duration": int(pause_duration * 1000)})
{"type": "pause", "duration": int(duration * 1000)})
1 change: 1 addition & 0 deletions py/selenium/webdriver/common/bidi/cdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ async def connect_session(self, target_id) -> 'CdpSession':
Returns a new :class:`CdpSession` connected to the specified target.
'''
global devtools
assert devtools is not None, "Devtools have not been imported. Call import_devtools(ver) first."
session_id = await self.execute(devtools.target.attach_to_target(
target_id, True))
session = CdpSession(self.ws, session_id, target_id)
Expand Down
25 changes: 24 additions & 1 deletion py/selenium/webdriver/common/by.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
The By implementation.
"""

from __future__ import annotations

class By:
from enum import Enum

from selenium.common.exceptions import WebDriverException


class By(Enum):
"""
Set of supported locator strategies.
"""
Expand All @@ -33,3 +39,20 @@ class By:
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"

@classmethod
def from_str(cls, by_str: str) -> By:
"""
Take the string representation of a locator strategy ("id", "css
selector", etc.), and return the appropriate By enum value for it.
Throw a WebDriverException if no such By type exists.
"""

for value in cls.__members__.values():
if value.value == by_str:
return value

raise WebDriverException(
f"{by_str!r} is not a valid locator strategy. Use a member of the By enum directly or one of: "
f"{', '.join(repr(v.value) for v in cls.__members__.values())}"
)
7 changes: 5 additions & 2 deletions py/selenium/webdriver/common/desired_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"""


class DesiredCapabilities:
FIREFOX_NAME = "firefox"


class DesiredCapabilities(object):
"""
Set of default supported desired capabilities.

Expand Down Expand Up @@ -48,7 +51,7 @@ class DesiredCapabilities:
"""

FIREFOX = {
"browserName": "firefox",
"browserName": FIREFOX_NAME,
"acceptInsecureCerts": True,
"moz:debuggerAddress": True,
}
Expand Down
9 changes: 5 additions & 4 deletions py/selenium/webdriver/common/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import json
import pkgutil
from typing import Any, AsyncIterator, Dict, cast

from contextlib import asynccontextmanager
from importlib import import_module
Expand Down Expand Up @@ -47,10 +48,10 @@ def __init__(self, driver, bidi_session) -> None:
self.cdp = bidi_session.cdp
self.devtools = bidi_session.devtools
_pkg = '.'.join(__name__.split('.')[:-1])
self._mutation_listener_js = pkgutil.get_data(_pkg, 'mutation-listener.js').decode('utf8').strip()
self._mutation_listener_js = cast(bytes, pkgutil.get_data(_pkg, 'mutation-listener.js')).decode('utf8').strip()

@asynccontextmanager
async def mutation_events(self) -> dict:
async def mutation_events(self) -> AsyncIterator[Dict[str, Any]]:
"""
Listen for mutation events and emit them as they are found.

Expand All @@ -77,7 +78,7 @@ async def mutation_events(self) -> dict:
script_key = await page.execute(self.devtools.page.add_script_to_evaluate_on_new_document(self._mutation_listener_js))
self.driver.pin_script(self._mutation_listener_js, script_key)
self.driver.execute_script(f"return {self._mutation_listener_js}")
event = {}
event: Dict[str, Any] = {}
async with runtime.wait_for(self.devtools.runtime.BindingCalled) as evnt:
yield event

Expand Down Expand Up @@ -115,7 +116,7 @@ async def add_js_error_listener(self):
js_exception.exception_details = exception.value.exception_details

@asynccontextmanager
async def add_listener(self, event_type) -> dict:
async def add_listener(self, event_type) -> AsyncIterator[Dict[str, Any]]:
"""
Listen for certain events that are passed in.

Expand Down
Loading