diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8df3ca7f..2119bfad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,6 +11,10 @@ permissions: jobs: release: + # Releases are cut from the upstream repository only. Without this guard the + # workflow also runs on forks, where semantic-release rewrites the version in + # pyproject.toml, commits to the default branch and attempts a PyPI upload. + if: github.repository == 'appium/python-client' runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 diff --git a/appium/webdriver/webdriver.py b/appium/webdriver/webdriver.py index 35e4c270..91d2b4fe 100644 --- a/appium/webdriver/webdriver.py +++ b/appium/webdriver/webdriver.py @@ -475,17 +475,7 @@ def _add_commands(self) -> None: get_atter(self) self.command_executor.add_command(Command.GET_STATUS, 'GET', '/status') - - # TODO Move commands for element to webelement - self.command_executor.add_command(Command.CLEAR, 'POST', '/session/$sessionId/element/$id/clear') - self.command_executor.add_command( - Command.LOCATION_IN_VIEW, - 'GET', - '/session/$sessionId/element/$id/location_in_view', - ) - - # MJSONWP for Selenium v4 - self.command_executor.add_command(Command.IS_ELEMENT_DISPLAYED, 'GET', '/session/$sessionId/element/$id/displayed') + MobileWebElement.add_commands(self.command_executor) self.command_executor.add_command(Command.GET_CAPABILITIES, 'GET', '/session/$sessionId') self.command_executor.add_command(Command.GET_SCREEN_ORIENTATION, 'GET', '/session/$sessionId/orientation') diff --git a/appium/webdriver/webelement.py b/appium/webdriver/webelement.py index 183dc9a1..97e48d29 100644 --- a/appium/webdriver/webelement.py +++ b/appium/webdriver/webelement.py @@ -17,6 +17,7 @@ from selenium.webdriver.common.utils import keys_to_typing from selenium.webdriver.remote.command import Command as RemoteCommand +from selenium.webdriver.remote.remote_connection import RemoteConnection from selenium.webdriver.remote.webelement import WebElement as SeleniumWebElement from typing_extensions import Self @@ -37,6 +38,25 @@ def find_element(self, by: str | By = By.ID, value: str | dict | None = None) -> def find_elements(self, by: str | By = By.ID, value: str | dict | None = None) -> list[Self]: # type: ignore[override] ... + @staticmethod + def add_commands(command_executor: RemoteConnection) -> None: + """Registers the element endpoints Appium supports on top of Selenium's. + + Called once by the Appium WebDriver when it sets up its command executor. + """ + command_executor.add_command(Command.CLEAR, 'POST', '/session/$sessionId/element/$id/clear') + command_executor.add_command( + Command.LOCATION_IN_VIEW, + 'GET', + '/session/$sessionId/element/$id/location_in_view', + ) + # MJSONWP for Selenium v4 + command_executor.add_command( + Command.IS_ELEMENT_DISPLAYED, + 'GET', + '/session/$sessionId/element/$id/displayed', + ) + def get_attribute(self, name: str) -> str | dict | None: # type: ignore[override] """Gets the given attribute or property of the element. diff --git a/test/unit/webdriver/webelement_test.py b/test/unit/webdriver/webelement_test.py index be9c8857..f5336bbd 100644 --- a/test/unit/webdriver/webelement_test.py +++ b/test/unit/webdriver/webelement_test.py @@ -18,6 +18,7 @@ import httpretty from appium.protocols.webdriver.can_find_elements import CanFindElements +from appium.webdriver.mobilecommand import MobileCommand from appium.webdriver.webelement import WebElement as MobileWebElement from test.unit.helper.test_helper import android_w3c_driver, appium_command, get_httpretty_request_body @@ -149,3 +150,29 @@ def test_can_find_elements_protocol_conformance(self): assert isinstance(element, CanFindElements) assert isinstance(driver, CanFindElements) + + @httpretty.activate + def test_is_displayed(self): + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.GET, + appium_command('/session/1234567890/element/element_id/displayed'), + body=json.dumps({'value': True}), + ) + + element = MobileWebElement(driver, 'element_id') + assert element.is_displayed() is True + + @httpretty.activate + def test_element_commands_registered_on_driver(self): + # The element endpoints are registered with the driver, so they work before any element exists. + driver = android_w3c_driver() + httpretty.register_uri( + httpretty.POST, + appium_command('/session/1234567890/element/element_id/clear'), + body=json.dumps({'value': None}), + ) + + driver.execute(MobileCommand.CLEAR, {'id': 'element_id'}) + + assert httpretty.last_request().path.endswith('/session/1234567890/element/element_id/clear')