Skip to content
4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 1 addition & 11 deletions appium/webdriver/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
20 changes: 20 additions & 0 deletions appium/webdriver/webelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand Down
27 changes: 27 additions & 0 deletions test/unit/webdriver/webelement_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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')