Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions .github/actions/setup-appium-server/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ runs:
steps:
- name: Start Appium server
shell: bash
env:
LOG_FILE: ${{ inputs.log_file }}
run: |
nohup appium server \
--port=${{ inputs.port }} \
Expand All @@ -36,10 +38,12 @@ runs:
--log-timestamp \
--keep-alive-timeout 1200 \
${{ inputs.server_args }} \
2>&1 > ${{ inputs.log_file }} &
> "$LOG_FILE" 2>&1 &

- name: Wait for Appium server to start
shell: bash
env:
LOG_FILE: ${{ inputs.log_file }}
run: |
TIMEOUT_SEC=${{ inputs.timeout }}
INTERVAL_SEC=1
Expand All @@ -51,12 +55,13 @@ runs:

if nc -z ${{ inputs.host }} ${{ inputs.port }}; then
echo "Appium server is running after $elapsed seconds"
cat ${{ inputs.log_file }}
cat "$LOG_FILE"
exit 0
fi

if [[ "$elapsed" -ge "$TIMEOUT_SEC" ]]; then
echo "${elapsed} seconds timeout reached: Appium server is NOT running"
cat "$LOG_FILE"
exit 1
fi

Expand Down
84 changes: 41 additions & 43 deletions .github/workflows/functional-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ env:
APPIUM_TEST_SERVER_PORT: '4723'
APPIUM_TEST_SERVER_HOST: '127.0.0.1'
PYTHONUNBUFFERED: 1
UV_LOCKED: 'true'

jobs:
ios_test:
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -80,39 +82,37 @@ jobs:
with:
python-version: 3.14

- name: Cache uv modules
uses: actions/cache@v6
- name: Set up uv
uses: astral-sh/setup-uv@v10.1.0
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-shared-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-shared-

- name: Install uv
run: make install-uv
enable-cache: true
python-version: '3.14'

- name: Run Tests
run: |
uv run pytest -v ${{ matrix.test_targets.target}} \
--doctest-modules \
--junitxml=junit/test-results.xml \
--cov=com \
--cov=appium \
--cov-report=xml \
--cov-report=html
env:
LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }}

- name: Save server output
- name: Save test diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@master
with:
name: appium-ios-${{matrix.test_targets.name}}.log
path: appium.log
name: appium-ios-${{matrix.test_targets.name}}-diagnostics
path: |
appium.log
junit/
coverage.xml
htmlcov/


android_test:
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -189,24 +189,19 @@ jobs:
with:
python-version: 3.14

- name: Cache uv modules
uses: actions/cache@v6
- name: Set up uv
uses: astral-sh/setup-uv@v10.1.0
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-shared-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-shared-
enable-cache: true
python-version: '3.14'

- name: Run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
arch: ${{ env.ARCH }}
script: |
make install-uv
uv run pytest -v ${{ matrix.test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
uv run pytest -v ${{ matrix.test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=appium --cov-report=xml --cov-report=html
target: google_apis
profile: Nexus 5X
disable-spellchecker: true
Expand All @@ -218,14 +213,19 @@ jobs:
IGNORE_VERSION_SKIP: true
CI: true

- name: Save server output
- name: Save test diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@master
with:
name: appium-android-${{matrix.test_targets.name}}.log
path: appium.log
name: appium-android-${{matrix.test_targets.name}}-diagnostics
path: |
appium.log
junit/
coverage.xml
htmlcov/

flutter_e2e_test:
timeout-minutes: 60
# These flutter integration driver tests are maintained by: MummanaSubramanya
strategy:
fail-fast: false
Expand Down Expand Up @@ -305,24 +305,19 @@ jobs:
server_args: '--relaxed-security'
log_file: appium-${{ matrix.e2e-tests }}.log

- name: Cache uv modules
uses: actions/cache@v6
- name: Set up uv
uses: astral-sh/setup-uv@v10.1.0
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-shared-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-shared-
enable-cache: true
python-version: '3.14'

- name: Run Android tests
if: matrix.e2e-tests == 'flutter-android'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
script: |
make install-uv
uv run pytest -v test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
uv run pytest -v test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=appium --cov-report=xml --cov-report=html
target: default
disable-spellchecker: true
disable-animations: true
Expand All @@ -349,20 +344,23 @@ jobs:
- name: Run IOS tests
if: matrix.e2e-tests == 'flutter-ios'
run: |
make install-uv
export PLATFORM=ios
uv run pytest -v test/functional/flutter_integration/*_test.py \
--doctest-modules \
--junitxml=junit/test-results.xml \
--cov=com \
--cov=appium \
--cov-report=xml \
--cov-report=html
env:
LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }}

- name: Save server output
- name: Save test diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@master
with:
name: appium-${{ matrix.e2e-tests }}.log
path: appium-${{ matrix.e2e-tests }}.log
name: appium-${{ matrix.e2e-tests }}-diagnostics
path: |
appium-${{ matrix.e2e-tests }}.log
junit/
coverage.xml
htmlcov/
31 changes: 0 additions & 31 deletions .github/workflows/lock-update.yml

This file was deleted.

45 changes: 33 additions & 12 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,36 @@ on:
pull_request:
branches: [ master ]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
UV_LOCKED: 'true'

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: '3.14'
- name: Set up uv
uses: astral-sh/setup-uv@v10.1.0
with:
enable-cache: true
python-version: '3.14'
- name: Check lint and formatting
run: |
uv run ruff check .
make check-format

test-ubuntu:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
Expand All @@ -19,18 +46,12 @@ jobs:
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Cache uv modules
uses: actions/cache@v6
- name: Set up uv
uses: astral-sh/setup-uv@v10.1.0
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-shared-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-shared-
- name: Install uv
run: make install-uv
- name: Run Checks
run: make check
enable-cache: true
python-version: ${{ matrix.python-version }}
- name: Check types
run: uv run mypy appium
- name: Run Unit Tests
run: make unittest
11 changes: 5 additions & 6 deletions appium/webdriver/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,10 @@ def _update_command_executor(self, keep_alive: bool) -> None:

if not self.caps:
raise ValueError('Driver capabilities must be defined')
if not {direct_protocol, direct_host, direct_port, direct_path}.issubset(set(self.caps)):
message = 'Direct connect capabilities from server were:\n'
for key in [direct_protocol, direct_host, direct_port, direct_path]:
message += f"{key}: '{self.caps.get(key, '')}' "
logger.debug(message)
keys = (direct_protocol, direct_host, direct_port, direct_path)
if not set(keys).issubset(self.caps):
details = ' '.join(f"{key}: '{self.caps.get(key, '')}'" for key in keys)
logger.debug(f'Direct connect capabilities from server were:\n{details} ')
return

protocol = self.caps[direct_protocol]
Expand Down Expand Up @@ -459,7 +458,7 @@ def orientation(self, value: str) -> None:
"""
allowed_values = ['LANDSCAPE', 'PORTRAIT']
if value.upper() in allowed_values:
self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value})
self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value.upper()})
else:
raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")

Expand Down
49 changes: 49 additions & 0 deletions test/unit/webdriver/webdriver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
from unittest.mock import patch

import httpretty
import pytest
import urllib3
from selenium.common.exceptions import WebDriverException

from appium import webdriver
from appium.options.android import UiAutomator2Options
Expand Down Expand Up @@ -461,6 +463,53 @@ class CustomAppiumConnection(AppiumConnection):

assert isinstance(driver.command_executor, CustomAppiumConnection)

@httpretty.activate
def test_orientation_getter(self):
driver = android_w3c_driver()
httpretty.register_uri(httpretty.GET, appium_command('/session/1234567890/orientation'), body='{"value": "LANDSCAPE"}')
assert driver.orientation == 'LANDSCAPE'

@httpretty.activate
def test_orientation_setter(self):
driver = android_w3c_driver()
httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/orientation'), body='{"value": ""}')

driver.orientation = 'LANDSCAPE'

assert get_httpretty_request_body(httpretty.last_request()) == {
'orientation': 'LANDSCAPE',
}

driver.orientation = 'PORTRAIT'

assert get_httpretty_request_body(httpretty.last_request()) == {
'orientation': 'PORTRAIT',
}

@httpretty.activate
def test_orientation_setter_normalizes_case(self):
driver = android_w3c_driver()
httpretty.register_uri(httpretty.POST, appium_command('/session/1234567890/orientation'), body='{"value": ""}')

driver.orientation = 'landscape'
assert get_httpretty_request_body(httpretty.last_request()) == {
'orientation': 'LANDSCAPE',
}

driver.orientation = 'portrait'
assert get_httpretty_request_body(httpretty.last_request()) == {
'orientation': 'PORTRAIT',
}

@httpretty.activate
def test_orientation_setter_invalid(self):
driver = android_w3c_driver()

with pytest.raises(WebDriverException) as excinfo:
driver.orientation = 'INVALID'

assert "You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'" in str(excinfo.value)

@httpretty.activate
def test_extention_command_check(self):
driver = android_w3c_driver()
Expand Down
Loading