mirror of
https://github.com/appium/appium.git
synced 2026-11-10 03:44:40 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d31d20e9e | ||
|
|
4a04100b3b | ||
|
|
ed13ec7c9b | ||
|
|
80f69e32d8 | ||
|
|
9bf6c13163 | ||
|
|
96c275f841 | ||
|
|
e61d420d15 | ||
|
|
3d400bb6fb | ||
|
|
971fa8b58c | ||
|
|
efb52173dd | ||
|
|
236e116871 | ||
|
|
bb93a07c1c | ||
|
|
b36eb13709 | ||
|
|
84ad4014ba | ||
|
|
e85fe990d5 | ||
|
|
5e87dc85d0 | ||
|
|
1db7aa9eb7 | ||
|
|
d91b92d037 | ||
|
|
0f1a76184b | ||
|
|
9ea7718f0a | ||
|
|
75f35b828e | ||
|
|
77fab30f70 | ||
|
|
dd71f66e4a | ||
|
|
c7e3b46f1a | ||
|
|
3615d6df4c | ||
|
|
52c92d6acd | ||
|
|
34e368e666 | ||
|
|
6cc4095c59 | ||
|
|
c62ffb7ddf | ||
|
|
17421ae61f | ||
|
|
531f482d87 | ||
|
|
8e42cbc82d | ||
|
|
16c84ea203 | ||
|
|
819905beb2 | ||
|
|
8f30c8abc7 | ||
|
|
39e9512b4a | ||
|
|
2dd05e12d3 | ||
|
|
2cf7f4e6b2 | ||
|
|
7bff30ba65 | ||
|
|
dbc2125d82 | ||
|
|
9e0349f398 | ||
|
|
589611f13e | ||
|
|
6575e29fba |
@@ -16,6 +16,11 @@ inputs:
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
socket-firewall:
|
||||
description: Whether to install dependencies through Socket Firewall
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -25,6 +30,7 @@ runs:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: npm
|
||||
- name: SocketDev Security Check
|
||||
if: ${{ inputs.socket-firewall == 'true' }}
|
||||
uses: SocketDev/action@v1.3.2
|
||||
with:
|
||||
mode: firewall-free
|
||||
@@ -33,6 +39,12 @@ runs:
|
||||
run: |
|
||||
# Replace npm with sfw npm in the install command
|
||||
install_cmd="${{ inputs.install-command }}"
|
||||
|
||||
if [ "${{ inputs.socket-firewall }}" != "true" ]; then
|
||||
$install_cmd
|
||||
exit $?
|
||||
fi
|
||||
|
||||
sfw_install_cmd="${install_cmd//npm /sfw npm }"
|
||||
|
||||
# Socket Firewall can fail to reach its own backend (a transient
|
||||
@@ -72,6 +84,7 @@ runs:
|
||||
exit 1
|
||||
fi
|
||||
- name: Build packages
|
||||
if: ${{ inputs.run-build == 'true' }}
|
||||
# `npm ci` already builds through the root `prepare` script
|
||||
if: ${{ inputs.run-build == 'true' && !startsWith(inputs.install-command, 'npm ci') }}
|
||||
shell: bash
|
||||
run: npm run build
|
||||
|
||||
+42
-24
@@ -10,7 +10,10 @@ on:
|
||||
- 'packages/base-driver/test-fixtures/**/*'
|
||||
- 'packages/*/test/**'
|
||||
- 'packages/*/bin'
|
||||
- 'packages/**/*.{js,mjs,cjs,ts}'
|
||||
- 'packages/**/*.js'
|
||||
- 'packages/**/*.mjs'
|
||||
- 'packages/**/*.cjs'
|
||||
- 'packages/**/*.ts'
|
||||
- 'packages/**/*.json'
|
||||
- 'package*.json'
|
||||
- 'lerna.json'
|
||||
@@ -29,7 +32,10 @@ on:
|
||||
- 'packages/base-driver/test-fixtures/**/*'
|
||||
- 'packages/*/test/**'
|
||||
- 'packages/*/bin'
|
||||
- 'packages/**/*.{js,mjs,cjs,ts}'
|
||||
- 'packages/**/*.js'
|
||||
- 'packages/**/*.mjs'
|
||||
- 'packages/**/*.cjs'
|
||||
- 'packages/**/*.ts'
|
||||
- 'packages/**/*.json'
|
||||
- 'package*.json'
|
||||
- 'lerna.json'
|
||||
@@ -55,15 +61,19 @@ jobs:
|
||||
uses: appium/appium-workflows/.github/workflows/node-lts-matrix.yml@main
|
||||
|
||||
prepare_e2e_packages:
|
||||
needs:
|
||||
- prepare_matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
packages: ${{ steps.discover-packages.outputs.packages }}
|
||||
matrix: ${{ steps.discover-packages.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Discover packages with e2e tests
|
||||
id: discover-packages
|
||||
env:
|
||||
NODE_VERSIONS: ${{ needs.prepare_matrix.outputs.versions }}
|
||||
run: |
|
||||
packages=()
|
||||
targets='[]'
|
||||
for package_dir in packages/*/; do
|
||||
package_json="${package_dir}package.json"
|
||||
if [[ -f "$package_json" ]]; then
|
||||
@@ -83,21 +93,26 @@ jobs:
|
||||
# Use the directory path without packages/ prefix (remove trailing slash)
|
||||
package_name="${package_dir%/}"
|
||||
package_name="${package_name#packages/}"
|
||||
packages+=("$package_name")
|
||||
# Packages with a test:e2e:shards script print a JSON array of shard names
|
||||
shards='[""]'
|
||||
if node -e "process.exit(require('./$package_json').scripts?.['test:e2e:shards'] ? 0 : 1)"; then
|
||||
shards=$(cd "$package_dir" && npm run --silent test:e2e:shards)
|
||||
fi
|
||||
targets=$(jq -c --arg pkg "$package_name" --argjson shards "$shards" \
|
||||
'. + [$shards[] | {package_dir: $pkg, shard: ., name: (if . == "" then $pkg else "\($pkg) [\(.)]" end)}]' \
|
||||
<<< "$targets")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# Convert array to JSON array for GitHub Actions matrix
|
||||
if [[ ${#packages[@]} -eq 0 ]]; then
|
||||
echo "No packages with e2e tests found"
|
||||
packages_json="[]"
|
||||
else
|
||||
# Use jq to create a compact single-line JSON array
|
||||
packages_json=$(printf '%s\n' "${packages[@]}" | jq -R '.' | jq -s '.' | jq -c '.')
|
||||
fi
|
||||
echo "packages=$packages_json" >> $GITHUB_OUTPUT
|
||||
echo "Found packages with e2e tests:"
|
||||
echo "$packages_json" | jq -r '.[]' | sed 's/^/ - /'
|
||||
echo "Found e2e targets:"
|
||||
echo "$targets" | jq -r '.[].name' | sed 's/^/ - /'
|
||||
# All LTS versions of Node.js on Linux, the newest one on Windows
|
||||
matrix=$(jq -c --argjson versions "$NODE_VERSIONS" '
|
||||
[.[] as $target
|
||||
| ($versions[] | {os: "ubuntu-latest", "node-version": ., target: $target}),
|
||||
{os: "windows-latest", "node-version": ($versions | map(tostring | tonumber) | max), target: $target}]
|
||||
' <<< "$targets")
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
test-smoke:
|
||||
needs:
|
||||
@@ -158,16 +173,18 @@ jobs:
|
||||
|
||||
test-e2e:
|
||||
needs:
|
||||
- prepare_matrix
|
||||
- prepare_e2e_packages
|
||||
name: E2E Tests - ${{ matrix.package_dir }} (Node ${{ matrix.node-version }})
|
||||
name: E2E Tests - ${{ matrix.target.name }} (Node ${{ matrix.node-version }}, ${{ matrix.os }})
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: ${{ fromJSON(needs.prepare_matrix.outputs.versions) }}
|
||||
package_dir: ${{ fromJSON(needs.prepare_e2e_packages.outputs.packages) }}
|
||||
runs-on: ubuntu-latest
|
||||
include: ${{ fromJSON(needs.prepare_e2e_packages.outputs.matrix) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Setup, install and build
|
||||
@@ -176,11 +193,12 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
install-command: npm ci --foreground-scripts
|
||||
run-build: 'true'
|
||||
- name: Run E2E tests for ${{ matrix.package_dir }}
|
||||
socket-firewall: 'false'
|
||||
- name: Run E2E tests for ${{ matrix.target.name }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
cd "packages/${{ matrix.package_dir }}"
|
||||
npm run test:e2e
|
||||
cd "packages/${{ matrix.target.package_dir }}"
|
||||
npm run test:e2e -- ${{ matrix.target.shard }}
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
|
||||
@@ -49,5 +49,9 @@ jobs:
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
- name: Check llms.txt
|
||||
run: npm run docs:check-llmstxt
|
||||
- name: Build Docs
|
||||
run: npm run docs:preview
|
||||
- name: Verify llms.txt was published to the site
|
||||
run: test -s packages/appium/docs/site/en/llms.txt
|
||||
|
||||
@@ -30,3 +30,18 @@ jobs:
|
||||
run: npm run install-docs-deps
|
||||
- name: Publish docs
|
||||
run: npm run docs:publish
|
||||
# The docs are deployed by mike into docs/<lang>/<version>, but crawlers look for
|
||||
# /llms.txt at the domain root. The file's links are absolute, so the same file serves
|
||||
# both locations.
|
||||
- name: Publish llms.txt to the site root
|
||||
run: |
|
||||
cp packages/appium/docs/en/llms.txt "${RUNNER_TEMP}/llms.txt"
|
||||
git checkout -f gh-pages
|
||||
cp "${RUNNER_TEMP}/llms.txt" llms.txt
|
||||
git add llms.txt
|
||||
if git diff --cached --quiet -- llms.txt; then
|
||||
echo "Root llms.txt is already up to date"
|
||||
else
|
||||
git commit -m "docs: update root llms.txt"
|
||||
git push origin gh-pages
|
||||
fi
|
||||
|
||||
@@ -161,7 +161,7 @@ for the particular driver or whether it supports parallel sessions.
|
||||
automation, all using the WebDriver model that's already the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple cloud providers that
|
||||
support various Appium drivers (mostly targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) can be used to visually inspect
|
||||
6. [Appium Inspector](https://appium.github.io/appium-inspector/) can be used to visually inspect
|
||||
the page source of applications across different platforms, facilitating easier test development.
|
||||
|
||||
Investing in the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means you
|
||||
|
||||
Generated
+67
-81
@@ -14,7 +14,6 @@
|
||||
"devDependencies": {
|
||||
"@appium/oxc-config": "2.0.0-beta.0",
|
||||
"@tsconfig/node22": "22.0.0",
|
||||
"@types/async-lock": "1.4.2",
|
||||
"@types/express": "5.0.6",
|
||||
"@types/json-schema": "7.0.15",
|
||||
"@types/lockfile": "1.0.4",
|
||||
@@ -30,7 +29,7 @@
|
||||
"@types/which": "3.0.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"asyncbox": "6.4.3",
|
||||
"asyncbox": "6.5.0",
|
||||
"conventional-changelog-conventionalcommits": "9.3.1",
|
||||
"cpy-cli": "7.0.0",
|
||||
"cross-env": "10.1.0",
|
||||
@@ -5062,12 +5061,6 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/async-lock": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/async-lock/-/async-lock-1.4.2.tgz",
|
||||
"integrity": "sha512-HlZ6Dcr205BmNhwkdXqrg2vkFMN2PluI7Lgr8In3B3wE5PiQHhjRqtW/lGdVU9gw+sM0JcIDx2AN+cW8oSWIcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.2",
|
||||
"license": "MIT",
|
||||
@@ -5902,15 +5895,10 @@
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-lock": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
|
||||
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="
|
||||
},
|
||||
"node_modules/asyncbox": {
|
||||
"version": "6.4.3",
|
||||
"resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.4.3.tgz",
|
||||
"integrity": "sha512-s8SY4wNBvZkenRVOW2dEr3r43u40uHlqKGPTkEvr7QQIOMNMIzCZMMstKgDTWhTPRfeST57S+v/3OTXhoVsg0g==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.5.0.tgz",
|
||||
"integrity": "sha512-of3Ikvrq0hR/yhgyo+Fix7j8gpa6t1WGmqIO93Pzw1CQn1dEV3EkQo463amP75oTLc3dmJtFAFRRCcMGfTfQew==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"p-limit": "^7.2.0"
|
||||
@@ -18949,22 +18937,22 @@
|
||||
}
|
||||
},
|
||||
"packages/appium": {
|
||||
"version": "4.0.0-beta.5",
|
||||
"version": "4.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "11.0.0-beta.4",
|
||||
"@appium/base-plugin": "4.0.0-beta.4",
|
||||
"@appium/docutils": "4.0.0-beta.4",
|
||||
"@appium/http-server": "1.0.0-beta.4",
|
||||
"@appium/base-driver": "11.0.0-beta.6",
|
||||
"@appium/base-plugin": "4.0.0-beta.6",
|
||||
"@appium/docutils": "4.0.0-beta.6",
|
||||
"@appium/http-server": "1.0.0-beta.6",
|
||||
"@appium/logger": "3.0.0-beta.2",
|
||||
"@appium/schema": "2.0.0-beta.2",
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"@appium/types": "2.0.0-beta.4",
|
||||
"@appium/schema": "2.0.0-beta.3",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"@appium/types": "2.0.0-beta.5",
|
||||
"@sidvind/better-ajv-errors": "7.0.0",
|
||||
"ajv": "8.20.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"argparse": "3.0.2",
|
||||
"asyncbox": "6.4.3",
|
||||
"asyncbox": "6.5.0",
|
||||
"axios": "1.20.0",
|
||||
"lilconfig": "3.1.3",
|
||||
"lru-cache": "11.5.3",
|
||||
@@ -19114,13 +19102,12 @@
|
||||
},
|
||||
"packages/base-driver": {
|
||||
"name": "@appium/base-driver",
|
||||
"version": "11.0.0-beta.4",
|
||||
"version": "11.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "~8.0.0-beta.4",
|
||||
"@appium/types": "~2.0.0-beta.4",
|
||||
"async-lock": "^1.4.1",
|
||||
"asyncbox": "^6.4.2",
|
||||
"@appium/support": "~8.0.0-beta.6",
|
||||
"@appium/types": "~2.0.0-beta.5",
|
||||
"asyncbox": "^6.5.0",
|
||||
"axios": "^1.20.0",
|
||||
"lru-cache": "^11.5.2",
|
||||
"path-to-regexp": "^8.4.2",
|
||||
@@ -19203,12 +19190,12 @@
|
||||
},
|
||||
"packages/base-plugin": {
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "4.0.0-beta.4",
|
||||
"version": "4.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "~11.0.0-beta.4",
|
||||
"@appium/support": "~8.0.0-beta.4",
|
||||
"@appium/types": "~2.0.0-beta.4"
|
||||
"@appium/base-driver": "~11.0.0-beta.6",
|
||||
"@appium/support": "~8.0.0-beta.6",
|
||||
"@appium/types": "~2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19217,10 +19204,10 @@
|
||||
},
|
||||
"packages/docutils": {
|
||||
"name": "@appium/docutils",
|
||||
"version": "4.0.0-beta.4",
|
||||
"version": "4.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "~8.0.0-beta.4",
|
||||
"@appium/support": "~8.0.0-beta.6",
|
||||
"consola": "^3.4.2",
|
||||
"diff": "^9.0.0",
|
||||
"lilconfig": "^3.1.3",
|
||||
@@ -19234,7 +19221,7 @@
|
||||
"appium-docs": "bin/appium-docs.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "~2.0.0-beta.4"
|
||||
"@appium/types": "~2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19413,13 +19400,13 @@
|
||||
},
|
||||
"packages/driver-test-support": {
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "4.0.0-beta.4",
|
||||
"version": "4.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "~8.0.0-beta.4"
|
||||
"@appium/support": "~8.0.0-beta.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "~2.0.0-beta.4"
|
||||
"@appium/types": "~2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19428,14 +19415,14 @@
|
||||
},
|
||||
"packages/execute-driver-plugin": {
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "8.0.0-beta.5",
|
||||
"version": "8.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"webdriverio": "9.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4"
|
||||
"@appium/types": "2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19447,18 +19434,18 @@
|
||||
},
|
||||
"packages/fake-driver": {
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "7.0.0-beta.5",
|
||||
"version": "7.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/http-server": "1.0.0-beta.4",
|
||||
"@appium/http-server": "1.0.0-beta.6",
|
||||
"@xmldom/xmldom": "0.9.12",
|
||||
"asyncbox": "6.4.3",
|
||||
"asyncbox": "6.5.0",
|
||||
"xpath": "0.0.34",
|
||||
"yocto-spinner": "1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/driver-test-support": "4.0.0-beta.4",
|
||||
"@appium/types": "2.0.0-beta.4",
|
||||
"@appium/driver-test-support": "4.0.0-beta.6",
|
||||
"@appium/types": "2.0.0-beta.5",
|
||||
"@types/express": "5.0.6"
|
||||
},
|
||||
"engines": {
|
||||
@@ -19471,15 +19458,15 @@
|
||||
},
|
||||
"packages/fake-plugin": {
|
||||
"name": "@appium/fake-plugin",
|
||||
"version": "5.0.0-beta.5",
|
||||
"version": "5.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "4.0.0-beta.4",
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"asyncbox": "6.4.3"
|
||||
"@appium/base-plugin": "4.0.0-beta.6",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"asyncbox": "6.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4",
|
||||
"@appium/types": "2.0.0-beta.5",
|
||||
"@types/express": "5.0.6"
|
||||
},
|
||||
"engines": {
|
||||
@@ -19492,12 +19479,12 @@
|
||||
},
|
||||
"packages/http-server": {
|
||||
"name": "@appium/http-server",
|
||||
"version": "1.0.0-beta.4",
|
||||
"version": "1.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "~11.0.0-beta.4",
|
||||
"@appium/support": "~8.0.0-beta.4",
|
||||
"@appium/types": "~2.0.0-beta.4",
|
||||
"@appium/base-driver": "~11.0.0-beta.6",
|
||||
"@appium/support": "~8.0.0-beta.6",
|
||||
"@appium/types": "~2.0.0-beta.5",
|
||||
"@types/express": "^5.0.6",
|
||||
"body-parser": "^2.3.0",
|
||||
"express": "^5.2.1",
|
||||
@@ -19512,16 +19499,16 @@
|
||||
},
|
||||
"packages/images-plugin": {
|
||||
"name": "@appium/images-plugin",
|
||||
"version": "6.0.0-beta.5",
|
||||
"version": "6.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/opencv": "6.0.0-beta.4",
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"@appium/opencv": "6.0.0-beta.6",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"lru-cache": "11.5.3",
|
||||
"sharp": "0.35.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4"
|
||||
"@appium/types": "2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19563,14 +19550,14 @@
|
||||
},
|
||||
"packages/opencv": {
|
||||
"name": "@appium/opencv",
|
||||
"version": "6.0.0-beta.4",
|
||||
"version": "6.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"opencv-bindings": "^4.5.5",
|
||||
"sharp": "^0.35.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "~2.0.0-beta.4"
|
||||
"@appium/types": "~2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19593,14 +19580,14 @@
|
||||
},
|
||||
"packages/plugin-test-support": {
|
||||
"name": "@appium/plugin-test-support",
|
||||
"version": "4.0.0-beta.5",
|
||||
"version": "4.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"async-lock": "^1.4.1",
|
||||
"asyncbox": "^6.5.0",
|
||||
"teen_process": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "~2.0.0-beta.4"
|
||||
"@appium/types": "~2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19612,10 +19599,10 @@
|
||||
},
|
||||
"packages/relaxed-caps-plugin": {
|
||||
"name": "@appium/relaxed-caps-plugin",
|
||||
"version": "4.0.0-beta.5",
|
||||
"version": "4.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4"
|
||||
"@appium/types": "2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19627,7 +19614,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@appium/schema",
|
||||
"version": "2.0.0-beta.2",
|
||||
"version": "2.0.0-beta.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
@@ -19654,17 +19641,16 @@
|
||||
},
|
||||
"packages/storage-plugin": {
|
||||
"name": "@appium/storage-plugin",
|
||||
"version": "3.0.0-beta.5",
|
||||
"version": "3.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"async-lock": "1.4.1",
|
||||
"asyncbox": "6.4.3",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"asyncbox": "6.5.0",
|
||||
"lru-cache": "11.5.3",
|
||||
"ws": "8.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4"
|
||||
"@appium/types": "2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
@@ -19712,11 +19698,11 @@
|
||||
},
|
||||
"packages/support": {
|
||||
"name": "@appium/support",
|
||||
"version": "8.0.0-beta.4",
|
||||
"version": "8.0.0-beta.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/logger": "~3.0.0-beta.2",
|
||||
"@appium/types": "~2.0.0-beta.4",
|
||||
"@appium/types": "~2.0.0-beta.5",
|
||||
"archiver": "^8.0.0",
|
||||
"asyncbox": "^6.4.2",
|
||||
"axios": "^1.20.0",
|
||||
@@ -19861,10 +19847,10 @@
|
||||
},
|
||||
"packages/types": {
|
||||
"name": "@appium/types",
|
||||
"version": "2.0.0-beta.4",
|
||||
"version": "2.0.0-beta.5",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/schema": "~2.0.0-beta.2",
|
||||
"@appium/schema": "~2.0.0-beta.3",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/ws": "^8.18.1"
|
||||
},
|
||||
@@ -19882,16 +19868,16 @@
|
||||
},
|
||||
"packages/universal-xml-plugin": {
|
||||
"name": "@appium/universal-xml-plugin",
|
||||
"version": "4.0.0-beta.5",
|
||||
"version": "4.0.0-beta.7",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "8.0.0-beta.4",
|
||||
"@appium/support": "8.0.0-beta.6",
|
||||
"@xmldom/xmldom": "0.9.12",
|
||||
"fast-xml-parser": "5.11.2",
|
||||
"xpath": "0.0.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/types": "2.0.0-beta.4"
|
||||
"@appium/types": "2.0.0-beta.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
||||
|
||||
+2
-2
@@ -46,6 +46,7 @@
|
||||
"dev": "run-s build:compile \"build:workspaces\" \"build:compile -- --watch\"",
|
||||
"dev:docs": "npm run -w packages/appium dev:docs",
|
||||
"docs": "npm run -w packages/appium build:docs",
|
||||
"docs:check-llmstxt": "npm run -w packages/appium check:docs:llmstxt",
|
||||
"docs:preview": "npm run -w packages/appium build:docs:preview",
|
||||
"docs:publish": "npm run -w packages/appium publish:docs",
|
||||
"install-docs-deps": "npm run -w packages/appium install-docs-deps",
|
||||
@@ -81,7 +82,6 @@
|
||||
"devDependencies": {
|
||||
"@appium/oxc-config": "2.0.0-beta.0",
|
||||
"@tsconfig/node22": "22.0.0",
|
||||
"@types/async-lock": "1.4.2",
|
||||
"@types/express": "5.0.6",
|
||||
"@types/json-schema": "7.0.15",
|
||||
"@types/lockfile": "1.0.4",
|
||||
@@ -97,7 +97,7 @@
|
||||
"@types/which": "3.0.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"asyncbox": "6.4.3",
|
||||
"asyncbox": "6.5.0",
|
||||
"conventional-changelog-conventionalcommits": "9.3.1",
|
||||
"cpy-cli": "7.0.0",
|
||||
"cross-env": "10.1.0",
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.0.0-beta.7](https://github.com/appium/appium/compare/appium@4.0.0-beta.6...appium@4.0.0-beta.7) (2026-10-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** respect server compatibility in extension update and warn on non-npm install ([#23119](https://github.com/appium/appium/issues/23119)) ([80f69e3](https://github.com/appium/appium/commit/80f69e32d87172eaf838819845e8017517acbaf2))
|
||||
|
||||
|
||||
## [4.0.0-beta.6](https://github.com/appium/appium/compare/appium@4.0.0-beta.5...appium@4.0.0-beta.6) (2026-10-26)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **support:** ExtractAllOptions.useSystemUnzip is removed.
|
||||
* **support:** Non-UTF-8-flagged file names now default to utf8 instead of cp437, and cp437 decoding is no longer supported (modern archives use UTF-8). Such archives must be recreated or converted; fileNamesEncoding only accepts Node.js BufferEncodings.
|
||||
* **appium:** install local extensions without global npm links (#23086)
|
||||
* **base-driver:** preserve proxied command validation ordering (#23060)
|
||||
* **base-driver:** The protected BaseDriver#commandsQueueGuard beyond promise-based acquire(key, fn) - it is now an asyncbox AsyncLock, so callback-style acquire() and the internal queues field are gone (use getQueueLength() / isBusy()).
|
||||
* **base-driver:** validate every timeout capability candidate (#23062)
|
||||
* **appium:** removed the --callback-address (-ca) server argument and the server.callback-address config property
|
||||
* **appium:** removed the --callback-port (-cp) server argument and the server.callback-port config property
|
||||
* **appium:** removed the --debug-log-spacing server argument and the server.debug-log-spacing config property
|
||||
* **appium:** removed the --strict-caps server argument and the server.strict-caps config property
|
||||
* **appium:** removed the --trace-dir server argument and the server.trace-dir config property
|
||||
* **base-driver:** POST body has to be a JSON object, otherwise the server returns invalid argument error.
|
||||
|
||||
### Features
|
||||
|
||||
* **support:** Remove system unzip from extractAllTo ([#23091](https://github.com/appium/appium/issues/23091)) ([0f1a761](https://github.com/appium/appium/commit/0f1a76184b78ee3d60652b9a610c808213196f7c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** install local extensions without global npm links ([#23086](https://github.com/appium/appium/issues/23086)) ([3615d6d](https://github.com/appium/appium/commit/3615d6df4c5a785524092f8c28dc338385a4468a))
|
||||
* **appium:** preserve npm arguments on Windows ([#22957](https://github.com/appium/appium/issues/22957)) ([dd71f66](https://github.com/appium/appium/commit/dd71f66e4ac343bc48e78f5b661ca02b351553c1))
|
||||
* **appium:** remove unused server arguments ([#23063](https://github.com/appium/appium/issues/23063)) ([16c84ea](https://github.com/appium/appium/commit/16c84ea20314f605b93070de283818a78c0ed4bb))
|
||||
* **appium:** send log webhooks to ipv6 hosts ([#23061](https://github.com/appium/appium/issues/23061)) ([819905b](https://github.com/appium/appium/commit/819905beb265431446ec56776373ec15f13a0a8f))
|
||||
* **base-driver:** preserve proxied command validation ordering ([#23060](https://github.com/appium/appium/issues/23060)) ([6cc4095](https://github.com/appium/appium/commit/6cc4095c59bc7ce23b47ce768707aae978794824))
|
||||
* **base-driver:** reject command POST bodies that are not JSON objects ([#23007](https://github.com/appium/appium/issues/23007)) ([39e9512](https://github.com/appium/appium/commit/39e9512b4a4c936edae0d7a56bdd4bd613075322))
|
||||
* **base-driver:** use AsyncLock from asyncbox instead of async-lock ([#23066](https://github.com/appium/appium/issues/23066)) ([17421ae](https://github.com/appium/appium/commit/17421ae61fa9f9e74a52b116f3ed2ee3c09fbc1f))
|
||||
* **base-driver:** validate every timeout capability candidate ([#23062](https://github.com/appium/appium/issues/23062)) ([531f482](https://github.com/appium/appium/commit/531f482d8759e7c621276b212489b764a994766a))
|
||||
* **docs:** correct stale instructions in .NET quickstart ([#23049](https://github.com/appium/appium/issues/23049)) ([2dd05e1](https://github.com/appium/appium/commit/2dd05e12d3e5911203856977120813ce56b69552))
|
||||
* **docs:** X-Appium-Is-Sensitive also accepts the value 'yes' ([#23051](https://github.com/appium/appium/issues/23051)) ([2cf7f4e](https://github.com/appium/appium/commit/2cf7f4e6b21fdf36f85986187d5361d980f7cb42))
|
||||
* **support:** prevent zip extraction through symlink chains ([#23002](https://github.com/appium/appium/issues/23002)) ([8e42cbc](https://github.com/appium/appium/commit/8e42cbc82d95da690b73399782f750043464352d))
|
||||
|
||||
|
||||
## [4.0.0-beta.5](https://github.com/appium/appium/compare/appium@4.0.0-beta.4...appium@4.0.0-beta.5) (2026-10-18)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
@@ -161,7 +161,7 @@ for the particular driver or whether it supports parallel sessions.
|
||||
automation, all using the WebDriver model that's already the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple cloud providers that
|
||||
support various Appium drivers (mostly targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) can be used to visually inspect
|
||||
6. [Appium Inspector](https://appium.github.io/appium-inspector/) can be used to visually inspect
|
||||
the page source of applications across different platforms, facilitating easier test development.
|
||||
|
||||
Investing in the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means you
|
||||
|
||||
@@ -61,7 +61,7 @@ cd appium
|
||||
|
||||
!!! info
|
||||
|
||||
If you are VS Code user, you can easily check out the project using [Runme](https://runme.dev/api/runme?repository=https%3A%2F%2Fgithub.com%2Fappium%2Fappium.git&fileToOpen=packages%2Fappium%2Fdocs%2Fen%2Fcontributing%2Findex.md).
|
||||
If you are VS Code user, you can easily check out the project using [Runme](https://runme.dev/).
|
||||
|
||||
Install dependencies:
|
||||
```sh
|
||||
|
||||
@@ -62,6 +62,33 @@ them in `docs`, and add links to these files in `mkdocs.yml`.
|
||||
Refer to the [MkDocs documentation](https://www.mkdocs.org/user-guide/writing-your-docs/) for
|
||||
information on how to organize and structure your documentation.
|
||||
|
||||
### Helping LLMs Use Your Documentation
|
||||
|
||||
The [`/llms.txt` convention](https://llmstxt.org/) is a Markdown file at the root of a
|
||||
documentation site, listing its most useful pages so that large language models can navigate the
|
||||
site without crawling it. Appium's own documentation provides one at
|
||||
[appium.io/docs/en/latest/llms.txt](https://appium.io/docs/en/latest/llms.txt), and you may want to
|
||||
do the same for your extension.
|
||||
|
||||
MkDocs copies any non-Markdown file in your `docs` directory to the built site as-is, so all you
|
||||
need to do is write an `llms.txt` and place it alongside your Markdown files. No plugin or build
|
||||
configuration is required.
|
||||
|
||||
The value of this file lies in the parts a crawler could not have worked out on its own, so it is
|
||||
worth writing by hand rather than generating it from your navigation:
|
||||
|
||||
- A short description of what your extension automates, and which platforms it targets
|
||||
- The handful of things models reliably get wrong about your extension - removed capabilities,
|
||||
renamed commands, patterns from an older major version that no longer apply
|
||||
- Links to the pages that matter, with a description of each
|
||||
|
||||
Because links in `llms.txt` are read out of context, they must be absolute URLs. If you use Mike
|
||||
for versioning, point them at your version alias (for example `latest`) rather than at a pinned
|
||||
version, so that the file does not go stale. Note that a hand-written file can drift as pages are
|
||||
renamed; Appium validates its own with a
|
||||
[script](https://github.com/appium/appium/blob/master/packages/appium/docs/scripts/check-llmstxt.js)
|
||||
that runs in CI, which you are welcome to adapt.
|
||||
|
||||
### Building the Docs
|
||||
|
||||
At this point, you can use the `appium-docs` CLI tool. Run this tool with no arguments to get the
|
||||
|
||||
@@ -80,8 +80,9 @@ The required subfields are:
|
||||
capability to tell Appium to use *your* driver.
|
||||
* `platformNames`: this is an array of one or more platform names considered valid for your driver.
|
||||
When a user sends in the `platformName` capability to start a session, it must be included in
|
||||
this list for your driver to handle the session. Known platform name strings include: `iOS`,
|
||||
`tvOS`, `macOS`, `Windows`, `Android`.
|
||||
this list for your driver to handle the session. These names should generally include the
|
||||
operating systems your driver is targeting - for example, a driver targeting iOS would include
|
||||
`iOS`, while a driver targeting the Chrome browser on Windows would include `Windows`.
|
||||
* `mainClass`: this is a named export (in CommonJS style) from your `main` field. It must be a
|
||||
class which extends Appium's `BaseDriver` (see below).
|
||||
|
||||
@@ -271,8 +272,8 @@ You handle WebDriver commands by implementing functions in your driver class. Ea
|
||||
WebDriver Protocol, plus the various Appium extensions, has a corresponding function that you
|
||||
implement if you want to support that command in your driver. The best way to see which commands
|
||||
Appium supports and which method you need to implement for each command is to look at Appium's
|
||||
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
|
||||
Each route object in this file tells you the command name as well as the parameters you'd expect to
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes).
|
||||
Each route object in this directory's files tells you the command name as well as the parameters you'd expect to
|
||||
receive for that command.
|
||||
|
||||
Let's take this block for example:
|
||||
@@ -313,7 +314,7 @@ Websockets. Implementing handlers for BiDi commands works just the same as imple
|
||||
for WebDriver classic commands (described in the previous section). You simply define a method on
|
||||
your driver of the appropriate name, and it will be called when the BiDi command is requested by
|
||||
the client. To see which specific names you should use for BiDi commands, have a look at
|
||||
[bidi-commands.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.js)
|
||||
[bidi-commands.ts](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.ts)
|
||||
|
||||
You are not limited to BiDi commands that are defined in the official BiDi specification. If you
|
||||
wish to define new commands, you may do so; you just need to tell Appium about them! See
|
||||
|
||||
@@ -62,7 +62,7 @@ functionality (e.g., the `plugin` or `driver` subcommands).
|
||||
|
||||
Note that this file is the _base_ schema; this will become painfully relevant.
|
||||
|
||||
This file is is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
This file is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.
|
||||
|
||||
It is more straightforward to explain how config files are validated, so we'll start there.
|
||||
|
||||
@@ -42,7 +42,7 @@ In order to get some value in logs replaced by a generic mask it is necessary:
|
||||
[util.format](https://nodejs.org/api/util.html#utilformatformat-args) API.
|
||||
|
||||
- While sending the appropriate server request, where this log line is used and should be masked,
|
||||
add the custom header `X-Appium-Is-Sensitive` with its value set to `1` or `true` (case-insensitive).
|
||||
add the custom header `X-Appium-Is-Sensitive` with its value set to `1`, `true` or `yes` (case-insensitive).
|
||||
Without such header the above log value is not going to be masked.
|
||||
This way it is possible to conditionally mask log records depending on which
|
||||
request is being handled by the extension if the log expression is used in the
|
||||
|
||||
@@ -15,34 +15,34 @@ To learn more about drivers, check out the [Driver Intro](../intro/drivers.md).
|
||||
|
||||
These drivers are currently maintained by the Appium team:
|
||||
|
||||
### [Chromium](https://github.com/appium/appium-chromium-driver)
|
||||
### [Chromium](https://appium.github.io/appium-chromium-driver)
|
||||
|
||||
* Target: Desktop and mobile Chromium browsers (Chrome, Microsoft Edge, etc.)
|
||||
* Target: Desktop Chromium-based browsers (Chrome, Microsoft Edge, etc.)
|
||||
* Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install chromium
|
||||
```
|
||||
|
||||
### [Espresso](https://github.com/appium/appium-espresso-driver)
|
||||
### [Espresso](https://appium.github.io/appium-espresso-driver)
|
||||
|
||||
* Target: Android applications
|
||||
* Mode: Native
|
||||
* Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
* Mode: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install espresso
|
||||
```
|
||||
|
||||
### [Gecko](https://github.com/appium/appium-geckodriver)
|
||||
### [Gecko](https://appium.github.io/appium-geckodriver)
|
||||
|
||||
* Target: Desktop and mobile Gecko browsers (Firefox)
|
||||
* Target: Desktop and mobile Gecko-based browsers (Firefox, etc.)
|
||||
* Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install gecko
|
||||
```
|
||||
|
||||
### [Mac2](https://github.com/appium/appium-mac2-driver)
|
||||
### [Mac2](https://appium.github.io/appium-mac2-driver)
|
||||
|
||||
* Target: macOS applications
|
||||
* Mode: Native
|
||||
@@ -51,7 +51,7 @@ appium driver install gecko
|
||||
appium driver install mac2
|
||||
```
|
||||
|
||||
### [Safari](https://github.com/appium/appium-safari-driver)
|
||||
### [Safari](https://appium.github.io/appium-safari-driver)
|
||||
|
||||
* Target: Desktop and mobile Safari browsers
|
||||
* Mode: Web
|
||||
@@ -60,16 +60,16 @@ appium driver install mac2
|
||||
appium driver install safari
|
||||
```
|
||||
|
||||
### [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver)
|
||||
### [UiAutomator2](https://appium.github.io/appium-uiautomator2-driver)
|
||||
|
||||
* Target: Android, Android TV, Android Wear applications
|
||||
* Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
* Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install uiautomator2
|
||||
```
|
||||
|
||||
### [WinCore](https://github.com/appium/appium-wincore-driver)
|
||||
### [WinCore](https://appium.github.io/appium-wincore-driver)
|
||||
|
||||
* Target: Windows applications
|
||||
* Mode: Native
|
||||
@@ -80,7 +80,7 @@ appium driver install wincore
|
||||
|
||||
### [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
|
||||
* Target: iOS, iPadOS, tvOS applications
|
||||
* Target: iOS, iPadOS, tvOS, watchOS applications
|
||||
* Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
@@ -151,7 +151,7 @@ appium driver install --source=npm @itsmeaj/appium-linux-driver
|
||||
### [NovaWindows](https://github.com/AutomateThePlanet/appium-novawindows-driver)
|
||||
|
||||
* Target: Windows applications
|
||||
* Mode: Native
|
||||
* Mode: Native, Hybrid
|
||||
* Supported by: Community / Automate The Planet
|
||||
|
||||
```sh title="Install This Driver"
|
||||
|
||||
@@ -12,7 +12,7 @@ for more specialised automation workflows.
|
||||
|
||||
## Official Plugins
|
||||
|
||||
These plugins are are currently maintained by the Appium team:
|
||||
These plugins are currently maintained by the Appium team:
|
||||
|
||||
### [Execute Driver](https://github.com/appium/appium/tree/master/packages/execute-driver-plugin)
|
||||
|
||||
|
||||
@@ -104,3 +104,44 @@ Supported by: `@AppiumTestDistribution`
|
||||
```sh title="Install This Tool"
|
||||
npm install -g appium-installer
|
||||
```
|
||||
|
||||
### [Optics](https://github.com/mozarkai/optics-framework)
|
||||
|
||||
Optics is an open-source test automation framework that uses Appium to drive Android, iOS,
|
||||
Android TV, Samsung Tizen and LG webOS apps (and Selenium or Playwright for web). Tests are written
|
||||
as CSV or YAML keyword steps, and the same keywords are available as a Python library, a Robot
|
||||
Framework library, an HTTP API and an MCP server. Element lookup falls back from XPath to visible
|
||||
text, OCR and image matching.
|
||||
|
||||
Supported by: `@chinmayajha`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
pip install "optics-framework[mobile]"
|
||||
```
|
||||
|
||||
### [XPath to Class Chain](https://github.com/domovou69/xpath-to-class-chain)
|
||||
|
||||
XPath to Class Chain is a command-line tool for migrating iOS XPath locators in test code to
|
||||
`-ios class chain` selectors. It converts the XPaths that have an exact class chain equivalent,
|
||||
converts with a warning those that only have a close one (such as grouped positions `(//X)[n]`),
|
||||
and skips the rest with a reason (for example, sibling or parent axes).
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-class-chain
|
||||
```
|
||||
|
||||
### [XPath to UiSelector](https://github.com/domovou69/xpath-to-uiselector)
|
||||
|
||||
XPath to UiSelector is a command-line tool for migrating Android XPath locators in test code to
|
||||
`-android uiautomator` selectors. It converts the XPaths that have an exact `UiSelector`
|
||||
equivalent, converts with a warning those that only have a close one (such as `//A//B`), and skips
|
||||
the rest with a reason. The strategy call is rewritten together with the string, for example
|
||||
`AppiumBy.xpath` becomes `AppiumBy.androidUIAutomator`.
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-uiselector
|
||||
```
|
||||
|
||||
@@ -52,6 +52,6 @@ After the PR is merged there is no need to use the local plugin/driver deploymen
|
||||
to switch back to the package managed by NPM. Follow the next steps for that:
|
||||
|
||||
- Unlink the installed driver/plugin from the server by running `appium driver uninstall <driver_name>` or
|
||||
`appium plugin uninstall <driver_name>`.
|
||||
`appium plugin uninstall <plugin_name>`.
|
||||
- Delete the local source folder (`rm -rf <full_path_to_plugin_or_driver_folder_with_fetched_sources>`).
|
||||
- Install the driver or the plugin from NPM. Check the component README in order to find a proper command for that.
|
||||
|
||||
@@ -8,14 +8,14 @@ principles, so you could create more performant and efficient test suite executi
|
||||
|
||||
## Why Caching Is Necessary
|
||||
|
||||
Mobile application bundles could reach hundreds of megabytes is size. This could become a serious
|
||||
Mobile application bundles could reach hundreds of megabytes in size. This could become a serious
|
||||
performance issue if a test suite is executed, and it is necessary to fetch/extract the same application
|
||||
bundle for each test.
|
||||
|
||||
## What Is Cached
|
||||
|
||||
Caching could be applied to application bundles generated by
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L107) helper call.
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L99) helper call.
|
||||
Inherited drivers can customize their caching logic by providing own `onPostProcess`
|
||||
(or both `inDownload` and `onPostProcess`) property definition, but the general
|
||||
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
|
||||
|
||||
@@ -24,7 +24,7 @@ to launch. In order to achieve this, Appium defines its own [extension capabilit
|
||||
## Common Appium Capabilities
|
||||
|
||||
According to the WebDriver spec, extension capabilities must include a namespace prefix (signifying
|
||||
the vendor introducing the capability), and the namespace must end a colon (`:`). Appium's vendor
|
||||
the vendor introducing the capability), and the namespace must end in a colon (`:`). Appium's vendor
|
||||
prefix is `appium:`, and so any Appium-specific capabilities must include this prefix. Depending on
|
||||
your Appium client, the prefix may be added automatically or in conjunction with certain interfaces,
|
||||
but it is always a good practice to explicitly include it for clarity.
|
||||
|
||||
@@ -34,7 +34,7 @@ discovered and loaded by Appium:
|
||||
- `appium.config.cjs`
|
||||
- `.appiumrc` (which is considered to be JSON)
|
||||
|
||||
Further, _if your project uses Node.js,_ you can use store the configuration inside an `appium`
|
||||
Further, _if your project uses Node.js,_ you can store the configuration inside an `appiumConfig`
|
||||
property in your `package.json` and it will be automatically discovered.
|
||||
|
||||
### Config File Search
|
||||
@@ -95,4 +95,4 @@ and the values of any specific configuration it provides are under this. For exa
|
||||
|
||||
All properties are case-sensitive and will be in
|
||||
[kebab-case](https://en.wikipedia.org/wiki/Naming_convention_(programming)#Delimiter-separated_words).
|
||||
For example, `callback-port` is allowed, but `callbackPort` is not.
|
||||
For example, `base-path` is allowed, but `basePath` is not.
|
||||
|
||||
@@ -15,7 +15,7 @@ structure:
|
||||
|
||||
```
|
||||
{
|
||||
"<event_type>": [<occurence_timestamp_1>, ...],
|
||||
"<event_type>": [<occurrence_timestamp_1>, ...],
|
||||
"commands": [
|
||||
{
|
||||
"cmd": "<command_name>",
|
||||
@@ -27,7 +27,7 @@ structure:
|
||||
}
|
||||
```
|
||||
|
||||
In other words, the `events` property has 2 kinds of properties of its own:
|
||||
In other words, the event history object has 2 kinds of properties of its own:
|
||||
|
||||
* Properties which are the names of event types
|
||||
* The `commands` property
|
||||
@@ -61,7 +61,8 @@ generate various kinds of reports from event timings output:
|
||||
|
||||
!!! note
|
||||
|
||||
In the past, events were available as a part of `GET /session/:id` response
|
||||
In the past, when the `appium:eventTimings` capability was set to `true`, the event history
|
||||
was also returned as an `events` property of the `GET /session/:id` response
|
||||
|
||||
## Add a custom event
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ by client libraries. There are two main strategies for this:
|
||||
based client library (including all Selenium and Appium clients).
|
||||
|
||||
There are pros and cons for each strategy, but it is ultimately up to the extension author to
|
||||
decide how they wish implement new commands.
|
||||
decide how they wish to implement new commands.
|
||||
|
||||
This guide is designed to specifically help you understand the "Execute Method" strategy. This
|
||||
pattern is commonly used in official Appium drivers and other third-party extensions.
|
||||
@@ -67,7 +67,7 @@ function, but the function itself—the snippet—is always a string and is the
|
||||
In the world of Appium, we are usually not automating a web browser, which means this command is
|
||||
not particularly useful. But it *is* useful as a way to encode the name of an arbitrary command and
|
||||
to provide parameters. For example, the [XCUITest
|
||||
Driver](https://github.com/appium/appium-xcuitest-driver) has implemented a command that lets a client
|
||||
Driver](https://appium.github.io/appium-xcuitest-driver/) has implemented a command that lets a client
|
||||
terminate a running application if you know the ID (the `bundleId`) of the app. The way that the
|
||||
driver makes this command available is via the Execute Method `mobile: terminateApp`. Instead of
|
||||
providing a JavaScript function to the "Execute Script" command, the user provides a _known string_
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
---
|
||||
title: Automating Gestures
|
||||
---
|
||||
|
||||
Appium supports automating complex pointer, touch, key, and wheel interactions through the
|
||||
W3C WebDriver [Perform Actions](../reference/api/webdriver.md#performactions) API
|
||||
([W3C Specification](https://w3c.github.io/webdriver/#actions)). Alternatively, many drivers provide
|
||||
platform-native gesture [Execute Methods](./execute-methods.md) for common interactions.
|
||||
|
||||
In the W3C Actions API, interactions are defined by creating one or more virtual input sources
|
||||
(such as `pointer` with `pointerType: "touch"`, `key`, `wheel`, or `none`) and queuing a sequence
|
||||
of synchronized ticks (`pointerMove`, `pointerDown`, `pointerUp`, `pause`). See the
|
||||
[Selenium Actions API documentation](https://www.selenium.dev/documentation/webdriver/actions_api/)
|
||||
for a general overview of input sources and tick synchronization.
|
||||
|
||||
Below are examples of common single-finger and multi-touch gestures implemented using the W3C
|
||||
Actions API across client languages.
|
||||
|
||||
## Double Tap
|
||||
|
||||
Performs two quick touch taps at `(x, y)` with a short pause between them:
|
||||
|
||||
=== "JS (WebDriverIO)"
|
||||
|
||||
```js
|
||||
await driver
|
||||
.action('pointer', {parameters: {pointerType: 'touch'}})
|
||||
.move({x: 200, y: 300, duration: 0})
|
||||
.down()
|
||||
.up()
|
||||
.pause(100)
|
||||
.down()
|
||||
.up()
|
||||
.perform();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
|
||||
Sequence doubleTap = new Sequence(finger, 1)
|
||||
.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 200, 300))
|
||||
.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(new Pause(finger, Duration.ofMillis(100)))
|
||||
.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
|
||||
driver.perform(Collections.singletonList(doubleTap));
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
actions = ActionChains(driver)
|
||||
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "finger"))
|
||||
actions.w3c_actions.pointer_action.move_to_location(200, 300)
|
||||
actions.w3c_actions.pointer_action.pointer_down()
|
||||
actions.w3c_actions.pointer_action.pointer_up()
|
||||
actions.w3c_actions.pointer_action.pause(0.1)
|
||||
actions.w3c_actions.pointer_action.pointer_down()
|
||||
actions.w3c_actions.pointer_action.pointer_up()
|
||||
actions.perform()
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
f1 = driver.action.add_pointer_input(:touch, 'finger')
|
||||
f1.create_pointer_move(duration: 0, x: 200, y: 300, origin: :viewport)
|
||||
f1.create_pointer_down(:left)
|
||||
f1.create_pointer_up(:left)
|
||||
f1.create_pause(0.1)
|
||||
f1.create_pointer_down(:left)
|
||||
f1.create_pointer_up(:left)
|
||||
driver.perform_actions([f1])
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp
|
||||
var finger = new PointerInputDevice(PointerKind.Touch, "finger");
|
||||
var doubleTap = new ActionSequence(finger, 0);
|
||||
doubleTap.AddAction(finger.CreatePointerMove(CoordinateOrigin.Viewport, 200, 300, TimeSpan.Zero));
|
||||
doubleTap.AddAction(finger.CreatePointerDown(MouseButton.Touch));
|
||||
doubleTap.AddAction(finger.CreatePointerUp(MouseButton.Touch));
|
||||
doubleTap.AddAction(finger.CreatePause(TimeSpan.FromMilliseconds(100)));
|
||||
doubleTap.AddAction(finger.CreatePointerDown(MouseButton.Touch));
|
||||
doubleTap.AddAction(finger.CreatePointerUp(MouseButton.Touch));
|
||||
driver.PerformActions(new List<ActionSequence> { doubleTap });
|
||||
```
|
||||
|
||||
## Long Press
|
||||
|
||||
Presses and holds a touch pointer at `(x, y)` for 1000ms before releasing:
|
||||
|
||||
=== "JS (WebDriverIO)"
|
||||
|
||||
```js
|
||||
await driver
|
||||
.action('pointer', {parameters: {pointerType: 'touch'}})
|
||||
.move({x: 200, y: 300, duration: 0})
|
||||
.down()
|
||||
.pause(1000)
|
||||
.up()
|
||||
.perform();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
|
||||
Sequence longPress = new Sequence(finger, 1)
|
||||
.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 200, 300))
|
||||
.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(new Pause(finger, Duration.ofMillis(1000)))
|
||||
.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
|
||||
driver.perform(Collections.singletonList(longPress));
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
actions = ActionChains(driver)
|
||||
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "finger"))
|
||||
actions.w3c_actions.pointer_action.move_to_location(200, 300)
|
||||
actions.w3c_actions.pointer_action.pointer_down()
|
||||
actions.w3c_actions.pointer_action.pause(1.0)
|
||||
actions.w3c_actions.pointer_action.pointer_up()
|
||||
actions.perform()
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
f1 = driver.action.add_pointer_input(:touch, 'finger')
|
||||
f1.create_pointer_move(duration: 0, x: 200, y: 300, origin: :viewport)
|
||||
f1.create_pointer_down(:left)
|
||||
f1.create_pause(1.0)
|
||||
f1.create_pointer_up(:left)
|
||||
driver.perform_actions([f1])
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp
|
||||
var finger = new PointerInputDevice(PointerKind.Touch, "finger");
|
||||
var longPress = new ActionSequence(finger, 0);
|
||||
longPress.AddAction(finger.CreatePointerMove(CoordinateOrigin.Viewport, 200, 300, TimeSpan.Zero));
|
||||
longPress.AddAction(finger.CreatePointerDown(MouseButton.Touch));
|
||||
longPress.AddAction(finger.CreatePause(TimeSpan.FromMilliseconds(1000)));
|
||||
longPress.AddAction(finger.CreatePointerUp(MouseButton.Touch));
|
||||
driver.PerformActions(new List<ActionSequence> { longPress });
|
||||
```
|
||||
|
||||
## Drag and Drop / Swipe
|
||||
|
||||
Moves a touch pointer from `(200, 600)` to `(200, 200)` over 600ms:
|
||||
|
||||
=== "JS (WebDriverIO)"
|
||||
|
||||
```js
|
||||
await driver
|
||||
.action('pointer', {parameters: {pointerType: 'touch'}})
|
||||
.move({x: 200, y: 600, duration: 0})
|
||||
.down()
|
||||
.pause(100)
|
||||
.move({x: 200, y: 200, duration: 600})
|
||||
.up()
|
||||
.perform();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
|
||||
Sequence dragAndDrop = new Sequence(finger, 1)
|
||||
.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 200, 600))
|
||||
.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(new Pause(finger, Duration.ofMillis(100)))
|
||||
.addAction(finger.createPointerMove(Duration.ofMillis(600), PointerInput.Origin.viewport(), 200, 200))
|
||||
.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
|
||||
driver.perform(Collections.singletonList(dragAndDrop));
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
actions = ActionChains(driver)
|
||||
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "finger"))
|
||||
actions.w3c_actions.pointer_action.move_to_location(200, 600)
|
||||
actions.w3c_actions.pointer_action.pointer_down()
|
||||
actions.w3c_actions.pointer_action.pause(0.1)
|
||||
actions.w3c_actions.pointer_action.move_to_location(200, 200)
|
||||
actions.w3c_actions.pointer_action.pointer_up()
|
||||
actions.perform()
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
f1 = driver.action.add_pointer_input(:touch, 'finger')
|
||||
f1.create_pointer_move(duration: 0, x: 200, y: 600, origin: :viewport)
|
||||
f1.create_pointer_down(:left)
|
||||
f1.create_pause(0.1)
|
||||
f1.create_pointer_move(duration: 0.6, x: 200, y: 200, origin: :viewport)
|
||||
f1.create_pointer_up(:left)
|
||||
driver.perform_actions([f1])
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp
|
||||
var finger = new PointerInputDevice(PointerKind.Touch, "finger");
|
||||
var dragAndDrop = new ActionSequence(finger, 0);
|
||||
dragAndDrop.AddAction(finger.CreatePointerMove(CoordinateOrigin.Viewport, 200, 600, TimeSpan.Zero));
|
||||
dragAndDrop.AddAction(finger.CreatePointerDown(MouseButton.Touch));
|
||||
dragAndDrop.AddAction(finger.CreatePause(TimeSpan.FromMilliseconds(100)));
|
||||
dragAndDrop.AddAction(finger.CreatePointerMove(CoordinateOrigin.Viewport, 200, 200, TimeSpan.FromMilliseconds(600)));
|
||||
dragAndDrop.AddAction(finger.CreatePointerUp(MouseButton.Touch));
|
||||
driver.PerformActions(new List<ActionSequence> { dragAndDrop });
|
||||
```
|
||||
|
||||
## Pinch and Zoom (Multi-Touch)
|
||||
|
||||
Uses two concurrent `touch` pointer sequences (`finger1` and `finger2`) moving apart to zoom in (or toward each other to pinch):
|
||||
|
||||
=== "JS (WebDriverIO)"
|
||||
|
||||
```js
|
||||
await driver.performActions([
|
||||
{
|
||||
type: 'pointer',
|
||||
id: 'finger1',
|
||||
parameters: {pointerType: 'touch'},
|
||||
actions: [
|
||||
{type: 'pointerMove', duration: 0, x: 200, y: 400},
|
||||
{type: 'pointerDown', button: 0},
|
||||
{type: 'pause', duration: 100},
|
||||
{type: 'pointerMove', duration: 500, x: 200, y: 200},
|
||||
{type: 'pointerUp', button: 0},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'pointer',
|
||||
id: 'finger2',
|
||||
parameters: {pointerType: 'touch'},
|
||||
actions: [
|
||||
{type: 'pointerMove', duration: 0, x: 200, y: 450},
|
||||
{type: 'pointerDown', button: 0},
|
||||
{type: 'pause', duration: 100},
|
||||
{type: 'pointerMove', duration: 500, x: 200, y: 650},
|
||||
{type: 'pointerUp', button: 0},
|
||||
],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
PointerInput finger1 = new PointerInput(PointerInput.Kind.TOUCH, "finger1");
|
||||
Sequence seq1 = new Sequence(finger1, 1)
|
||||
.addAction(finger1.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 200, 400))
|
||||
.addAction(finger1.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(new Pause(finger1, Duration.ofMillis(100)))
|
||||
.addAction(finger1.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), 200, 200))
|
||||
.addAction(finger1.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
|
||||
|
||||
PointerInput finger2 = new PointerInput(PointerInput.Kind.TOUCH, "finger2");
|
||||
Sequence seq2 = new Sequence(finger2, 1)
|
||||
.addAction(finger2.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 200, 450))
|
||||
.addAction(finger2.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
|
||||
.addAction(new Pause(finger2, Duration.ofMillis(100)))
|
||||
.addAction(finger2.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), 200, 650))
|
||||
.addAction(finger2.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
|
||||
|
||||
driver.perform(Arrays.asList(seq1, seq2));
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
action_builder = ActionBuilder(driver)
|
||||
finger1 = action_builder.add_pointer_input(interaction.POINTER_TOUCH, "finger1")
|
||||
finger2 = action_builder.add_pointer_input(interaction.POINTER_TOUCH, "finger2")
|
||||
|
||||
finger1.create_pointer_move(duration=0, x=200, y=400)
|
||||
finger1.create_pointer_down(button=MouseButton.LEFT)
|
||||
finger1.create_pause(0.1)
|
||||
finger1.create_pointer_move(duration=500, x=200, y=200)
|
||||
finger1.create_pointer_up(button=MouseButton.LEFT)
|
||||
|
||||
finger2.create_pointer_move(duration=0, x=200, y=450)
|
||||
finger2.create_pointer_down(button=MouseButton.LEFT)
|
||||
finger2.create_pause(0.1)
|
||||
finger2.create_pointer_move(duration=500, x=200, y=650)
|
||||
finger2.create_pointer_up(button=MouseButton.LEFT)
|
||||
|
||||
action_builder.perform()
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
f1 = driver.action.add_pointer_input(:touch, 'finger1')
|
||||
f1.create_pointer_move(duration: 0, x: 200, y: 400, origin: :viewport)
|
||||
f1.create_pointer_down(:left)
|
||||
f1.create_pause(0.1)
|
||||
f1.create_pointer_move(duration: 0.5, x: 200, y: 200, origin: :viewport)
|
||||
f1.create_pointer_up(:left)
|
||||
|
||||
f2 = driver.action.add_pointer_input(:touch, 'finger2')
|
||||
f2.create_pointer_move(duration: 0, x: 200, y: 450, origin: :viewport)
|
||||
f2.create_pointer_down(:left)
|
||||
f2.create_pause(0.1)
|
||||
f2.create_pointer_move(duration: 0.5, x: 200, y: 650, origin: :viewport)
|
||||
f2.create_pointer_up(:left)
|
||||
|
||||
driver.perform_actions([f1, f2])
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp
|
||||
var finger1 = new PointerInputDevice(PointerKind.Touch, "finger1");
|
||||
var seq1 = new ActionSequence(finger1, 0);
|
||||
seq1.AddAction(finger1.CreatePointerMove(CoordinateOrigin.Viewport, 200, 400, TimeSpan.Zero));
|
||||
seq1.AddAction(finger1.CreatePointerDown(MouseButton.Touch));
|
||||
seq1.AddAction(finger1.CreatePause(TimeSpan.FromMilliseconds(100)));
|
||||
seq1.AddAction(finger1.CreatePointerMove(CoordinateOrigin.Viewport, 200, 200, TimeSpan.FromMilliseconds(500)));
|
||||
seq1.AddAction(finger1.CreatePointerUp(MouseButton.Touch));
|
||||
|
||||
var finger2 = new PointerInputDevice(PointerKind.Touch, "finger2");
|
||||
var seq2 = new ActionSequence(finger2, 0);
|
||||
seq2.AddAction(finger2.CreatePointerMove(CoordinateOrigin.Viewport, 200, 450, TimeSpan.Zero));
|
||||
seq2.AddAction(finger2.CreatePointerDown(MouseButton.Touch));
|
||||
seq2.AddAction(finger2.CreatePause(TimeSpan.FromMilliseconds(100)));
|
||||
seq2.AddAction(finger2.CreatePointerMove(CoordinateOrigin.Viewport, 200, 650, TimeSpan.FromMilliseconds(500)));
|
||||
seq2.AddAction(finger2.CreatePointerUp(MouseButton.Touch));
|
||||
|
||||
driver.PerformActions(new List<ActionSequence> { seq1, seq2 });
|
||||
```
|
||||
|
||||
@@ -18,8 +18,11 @@ Each rule is an object with a set of predefined properties. The following rule p
|
||||
supported:
|
||||
|
||||
- `pattern`: A valid Javascript RegExp pattern to replace. Must be a valid non-empty pattern.
|
||||
- `text`: A simple non-empty exact text match to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided.
|
||||
- `text`: A simple non-empty exact text to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided. Note that the text is
|
||||
matched as a whole word: special characters are escaped and word boundaries (`\b`) are added on
|
||||
the sides where the value begins or ends with a word character. For example, a rule with text
|
||||
`pass` replaces `pass` in `my pass here`, but does not match inside `password`.
|
||||
- `flags`: Regular expression flags for the given pattern. Supported flags are the same as for the
|
||||
standard JavaScript [RegExp constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2).
|
||||
Note that the `g` (global matching) flag is always enabled.
|
||||
|
||||
@@ -25,7 +25,7 @@ appium driver install xcuitest
|
||||
```
|
||||
|
||||
This command will install the latest version of the
|
||||
[XCUITest Driver](https://github.com/appium/appium-xcuitest-driver). The Extension CLI comes with a variety
|
||||
[XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/). The Extension CLI comes with a variety
|
||||
of commands and parameters; see the documentation for that command for all the specifics.
|
||||
|
||||
The all-important question when Appium is managing your extensions for you is: where are they installed?
|
||||
@@ -63,7 +63,7 @@ like any other dependency. Basically, whenever you run Appium, if you have not e
|
||||
1. Try to determine whether the _current directory_ is inside an `npm` package.
|
||||
1. If so, it will check whether `appium` is a dependency (dev, prod, or peer) in the project's
|
||||
`package.json`
|
||||
1. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will ignore load
|
||||
1. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will load
|
||||
drivers and plugins defined in that `package.json` file instead.
|
||||
|
||||
This means you are freely able to add Appium drivers and plugins as regular package dependencies or
|
||||
|
||||
@@ -102,10 +102,10 @@ and it is recommended to migrate to the following replacements:
|
||||
|
||||
|Appium 1 Package|Replacement in Appium 2|
|
||||
|--|--|
|
||||
|iOS Driver|[XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/latest/)|
|
||||
|UiAutomator Driver|[UiAutomator2](https://github.com/appium/appium-uiautomator2-driver/)|
|
||||
|iOS Driver|[XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/)|
|
||||
|UiAutomator Driver|[UiAutomator2](https://appium.github.io/appium-uiautomator2-driver/)|
|
||||
|`wd` Client|[WebdriverIO Client](https://webdriver.io/)|
|
||||
|Appium Desktop|[Appium Inspector](https://github.com/appium/appium-inspector)|
|
||||
|Appium Desktop|[Appium Inspector](https://appium.github.io/appium-inspector/)|
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
@@ -197,8 +197,12 @@ and [Mobile JSON Wire Protocol](https://github.com/SeleniumHQ/mobile-spec/blob/m
|
||||
(MJSONWP) were used instead.
|
||||
|
||||
In Appium 1, all of these protocols were supported, so that older Selenium/Appium clients could
|
||||
still communicate with newer Appium servers. Appium 2 removes support for JSONWP/MJSONWP and is now
|
||||
only compatible with the W3C WebDriver Protocol.
|
||||
still communicate with newer Appium servers. Appium 2 removes support for JSONWP/MJSONWP (including
|
||||
legacy `TouchAction` and `MultiAction` endpoints) and is now only compatible with the W3C WebDriver
|
||||
Protocol. Multi-touch and pointer gestures should be performed using the W3C
|
||||
[Perform Actions](../reference/api/webdriver.md#performactions) API (see the
|
||||
[Automating Gestures](./gestures.md) guide) or driver-specific
|
||||
[Execute Methods](./execute-methods.md).
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
|
||||
@@ -148,9 +148,9 @@ Icons are used to indicate endpoint support in either certain drivers, or in the
|
||||
|
||||
- :simple-appium:{ .lg } - Appium server
|
||||
- :material-apple:{ .lg } - [XCUITest driver](https://appium.github.io/appium-xcuitest-driver/latest/)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://github.com/appium/appium-espresso-driver)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://github.com/appium/appium-mac2-driver)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://appium.github.io/appium-espresso-driver/)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://appium.github.io/appium-mac2-driver/)
|
||||
- :material-microsoft-windows:{ .lg } - [Windows driver](https://github.com/appium/appium-windows-driver)
|
||||
|
||||
</div>
|
||||
@@ -454,9 +454,6 @@ longer accepts, as well as the parameters it continues to accept in Appium 3.
|
||||
* `POST /session/:sessionId/element/:elementId/value`
|
||||
* :octicons-x-24: `value`
|
||||
* :octicons-check-24: `text`
|
||||
* `POST /session/:sessionId/timeouts`
|
||||
* :octicons-x-24: `type`, `ms`
|
||||
* :octicons-check-24: `script`, `pageLoad`, `implicit`
|
||||
* `POST /session/:sessionId/window`
|
||||
* :octicons-x-24: `name`
|
||||
* :octicons-check-24: `handle`
|
||||
|
||||
@@ -75,6 +75,24 @@ Library callers of `@appium/http-server`'s `server()` and `configureServer()` no
|
||||
Replace `--allow-cors` with `--allow-insecure=*:cors` (or enable it via `--relaxed-security`)
|
||||
for browser clients. Library callers that need CORS must explicitly pass `allowCors: true`
|
||||
|
||||
### Unused Server Arguments Removed
|
||||
|
||||
The following server arguments had no effect for a long time and have been removed, together with
|
||||
their `server.*` config file properties:
|
||||
|
||||
* `--callback-address` (`-ca`) and `--callback-port` (`-cp`)
|
||||
* `--debug-log-spacing`
|
||||
* `--strict-caps`
|
||||
* `--trace-dir`
|
||||
|
||||
Passing any of them on the command line now fails with an unrecognized argument error (unless
|
||||
[`--allow-unknown-args`](../reference/cli/server.md) is set), and config files containing them
|
||||
fail schema validation.
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
Remove these arguments from startup scripts and config files
|
||||
|
||||
### Extension Auto-Install on `npm install` Removed
|
||||
|
||||
The `npm install -g appium --drivers=... --plugins=...` postinstall hook that automatically
|
||||
@@ -86,6 +104,19 @@ risk and newer npm versions no longer support passing custom arguments to them.
|
||||
Use `appium driver install`/`appium plugin install`, or declare extensions in your project's
|
||||
`package.json`, instead of `--drivers`/`--plugins` install-time arguments
|
||||
|
||||
### Local Extension Installs No Longer Create Global npm Links
|
||||
|
||||
`appium driver install --source=local` and `appium plugin install --source=local` now install
|
||||
directly from the supplied local directory, without first creating an intermediate global npm
|
||||
link. Local installs still link to the source directory, so source changes do not require
|
||||
reinstalling the extension (rebuild the extension first if its source needs compilation).
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
Keep using `--source=local` with the extension's directory for local development. If other
|
||||
tooling relied on the package being linked in npm's global prefix, manage that global link
|
||||
separately; Appium no longer creates it as a side effect of a local extension install.
|
||||
|
||||
### `windows` Driver Shortcut Replaced by `wincore`
|
||||
|
||||
The `windows` shortcut for `appium driver install` has been removed, and the new `wincore` shortcut
|
||||
@@ -176,6 +207,42 @@ startup, using the same value checks as Set Timeouts. Partial objects are accept
|
||||
fields keep their defaults; backend handling remains driver-specific
|
||||
([#22983](https://github.com/appium/appium/pull/22983)).
|
||||
|
||||
Timeout capability validation also runs on `alwaysMatch` and every `firstMatch` candidate before
|
||||
driver selection. A malformed timeout object or invalid standard timeout value rejects the request
|
||||
with HTTP `400` / `invalid argument`, even when an earlier candidate could match. Top-level
|
||||
`timeouts: null` is still omitted, nested null timeout values are preserved, and unknown timeout
|
||||
properties are ignored for validation.
|
||||
|
||||
### Command POST Bodies Must Be JSON Objects
|
||||
|
||||
A command `POST` body now has to be a JSON object, as required by the W3C WebDriver standard.
|
||||
A request with no body, or a body that is a JSON array such as `[]`, returns HTTP `400` with
|
||||
`invalid argument` (`HTTP request body must be a JSON object`) and is not forwarded to the driver.
|
||||
`POST` with `{}` is unchanged, including commands that take no parameters. Routes that set
|
||||
`payloadParams.wrap` can still accept a bare array.
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
Send `{}` instead of an empty body. Clients that already send a JSON object, including the
|
||||
Appium clients, need no change
|
||||
|
||||
### Plugins Intercepting Proxied Commands Receive Unvalidated Arguments
|
||||
|
||||
When a plugin intercepts a command that would otherwise be proxied to a downstream WebDriver
|
||||
server, Appium no longer validates required payload fields or command arguments before calling
|
||||
the plugin. This matches direct proxying, where the downstream remote end already owns those
|
||||
checks ([#23060](https://github.com/appium/appium/pull/23060)). Plugins that previously relied on
|
||||
Appium to validate these arguments can now receive missing or invalid values.
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
If your plugin handles such a command without calling `next()`, validate the inputs it uses.
|
||||
A pass-through plugin can call `next()` and delegate validation to the downstream remote end.
|
||||
The downstream driver's validation responsibility is unchanged.
|
||||
|
||||
Local and proxy-avoided commands retain Appium's validation. Session-existence checks and
|
||||
JSON-object request-body checks also remain in place before the plugin is called.
|
||||
|
||||
### BiDi Commands No Longer Accept Stray `sessionId`/`id` Params
|
||||
|
||||
Unlike HTTP endpoints, WebDriver BiDi commands have no URL-derived session/element id, so a stray
|
||||
@@ -536,6 +603,21 @@ The following `@appium/support` exports have been removed: `process` (`getProces
|
||||
A completed directory walk may have skipped entries; check warnings if completeness matters.
|
||||
Allow IPC consumers to finish when unsubscribed
|
||||
|
||||
### System Unzip Removed From `@appium/support`
|
||||
|
||||
`extractAllTo()` always uses the built-in JS extractor now, so the `useSystemUnzip` option has been
|
||||
removed. The extractor supports only stored and deflate entries and verifies the CRC32 of every
|
||||
entry. File names that are not flagged as UTF-8 by the archive are decoded as `utf8` by default, and
|
||||
the legacy `cp437` decoding is no longer supported. Such names get corrupted, and different names
|
||||
may collapse into the same one, so that entries overwrite each other.
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
Remove `useSystemUnzip` from `extractAllTo()` calls. Archives with `cp437` file names must be
|
||||
recreated with UTF-8 names (or converted) before extraction. Other single-byte encodings
|
||||
supported by Node.js can be selected via `fileNamesEncoding`. Archives with encrypted entries or
|
||||
other compression methods are rejected with `ZipStreamUnsupportedError`
|
||||
|
||||
### Legacy Test Pages Removed From `@appium/base-driver`
|
||||
|
||||
The built-in test fixture routes (`/welcome`, `/test/guinea-pig*`, `/produce_error`, `/crash`),
|
||||
|
||||
@@ -99,7 +99,7 @@ a driver does this by relying on platform-specific automation technologies. For
|
||||
maintains an iOS automation technology called
|
||||
[XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests). The Appium
|
||||
driver that supports iOS app automation is called the [XCUITest
|
||||
Driver](https://github.com/appium/appium-xcuitest-driver) because ultimately what it does is
|
||||
Driver](https://appium.github.io/appium-xcuitest-driver/) because ultimately what it does is
|
||||
convert the WebDriver protocol to XCUITest library calls.
|
||||
|
||||
One of the reasons that drivers are independent, pluggable modules is that they work completely
|
||||
|
||||
@@ -49,8 +49,8 @@ async setUrl(url) {
|
||||
|
||||
[^1]: You might notice that `setUrl` doesn't look anything like `Navigate To`, so how did we know
|
||||
to use it rather than some other random string? Well, Appium's WebDriver-protocol-to-method-name
|
||||
mapping is defined in a special file within the `@appium/base-driver` package called
|
||||
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
|
||||
mappings are defined in special files within the `@appium/base-driver` package's
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes) directory.
|
||||
So if you're writing a driver, this is where you would go to figure out what method names to use
|
||||
and what parameters to expect. Or you could look at the source for any of the main Appium
|
||||
drivers!
|
||||
@@ -81,7 +81,7 @@ from there is up to you, the driver author!
|
||||
## Automation mapping
|
||||
|
||||
But *typically* what driver authors want to do is to provide automation behaviours for a given
|
||||
platform(s) that are semantically very similar to the the WebDriver spec implementations for
|
||||
platform(s) that are semantically very similar to the WebDriver spec implementations for
|
||||
browsers. When you want to find an element, you should get a reference to a UI element. When you
|
||||
want to click or tap that element, the resulting behaviour should be the same as if a person were
|
||||
to click or tap on the element. And so on.
|
||||
@@ -175,7 +175,7 @@ be passed back directly to the client, without any XCUITest driver code seeing i
|
||||
This architectural pattern provides a nice bonus for driver authors who choose to deal with the
|
||||
WebDriver protocol everywhere, rather than constructing bespoke protocols. It also means that
|
||||
Appium can create wrapper drivers for any other existing WebDriver implementation very easily. If
|
||||
you look at the [Appium Safari driver](https://github.com/appium/appium-safari-driver) code, for
|
||||
you look at the [Appium Safari driver](https://appium.github.io/appium-safari-driver/) code, for
|
||||
example, you'll see that it implements basically no standard commands, because all of these are
|
||||
proxied directly to an underlying SafariDriver process.
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Appium
|
||||
|
||||
> Appium is an open-source framework for UI automation of many app platforms - mobile (iOS, Android, Tizen), browser (Chrome, Firefox, Safari), desktop (macOS, Windows), TV (Roku, tvOS, Android TV, Samsung) and more - driven over the W3C WebDriver protocol.
|
||||
|
||||
Appium is split into four parts: the Appium server, which defines the core APIs; drivers, which
|
||||
implement connectivity to a specific platform; clients, which implement Appium's API in a specific
|
||||
programming language; and plugins, which change or extend the server's behaviour. To automate
|
||||
anything you need the server, at least one driver, and a client.
|
||||
|
||||
Notes for tools generating Appium code from this documentation:
|
||||
|
||||
- Appium 3 is the current major version, and these pages describe Appium 3. Appium 2 is still
|
||||
supported but is documented separately, under its own version of this site, reachable from the
|
||||
version selector. Appium 1 is no longer supported: `DesiredCapabilities`, `MobileBy`, the
|
||||
`findElementByAccessibilityId` family of methods, and the `/wd/hub` server base path are all
|
||||
Appium 1 era patterns - the default base path has been `/` since Appium 2.
|
||||
- Drivers are not bundled with the server. Install them explicitly, for example
|
||||
`appium driver install uiautomator2` or `appium driver install xcuitest`.
|
||||
- Appium 3 requires Node.js in the range `^20.19.0 || ^22.12.0 || >=24.0.0`, and npm 10 or newer.
|
||||
- Standard W3C capabilities are written without a prefix. `platformName`, `browserName` and
|
||||
`browserVersion` are the ones used most often with Appium, and the rest of the specification's
|
||||
set - `acceptInsecureCerts`, `pageLoadStrategy`, `timeouts`, `webSocketUrl` and so on - is also
|
||||
accepted, mostly by the browser drivers. Every Appium-specific capability needs the `appium:`
|
||||
vendor prefix - `appium:automationName`, `appium:app`, `appium:udid` - or must be nested,
|
||||
unprefixed, inside an `appium:options` object.
|
||||
- Capabilities cannot be changed once a session has started. Parameters that are adjustable during
|
||||
a session are exposed through the Settings API instead.
|
||||
- Driver-specific commands are usually Execute Methods rather than new client methods: call the
|
||||
standard `Execute Script` command with a known command name such as `mobile: terminateApp`, and
|
||||
pass a *single* object whose keys are the parameter names. Do not invent client-side method names
|
||||
for these.
|
||||
- Beyond the small common set documented here, capabilities, execute methods, settings and locator
|
||||
strategies are defined by each driver and documented on that driver's own site. Refer users to
|
||||
the driver's documentation rather than guessing names.
|
||||
- Appium 3 removed many endpoints that were deprecated in Appium 2. Check the Appium 3 migration
|
||||
guide before using an endpoint carried over from older code.
|
||||
- `GET /sessions` was removed. Session discovery is now `GET /appium/sessions`, and requires
|
||||
starting the server with `--allow-insecure=*:session_discovery`.
|
||||
- In Appium 3, every `--allow-insecure` feature name must carry a scope prefix: either a driver
|
||||
name (`uiautomator2:adb_shell`) or the wildcard (`*:adb_shell`).
|
||||
- WebDriver BiDi support is opt-in per session, via the standard `webSocketUrl` capability.
|
||||
- Contexts model the difference between native and webview modes. `Get Contexts` always returns at
|
||||
least one context, conventionally named `NATIVE_APP`.
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Appium Documentation](https://appium.io/docs/en/latest/): documentation home for the Appium server and its ecosystem.
|
||||
- [Appium in a Nutshell](https://appium.io/docs/en/latest/intro/): how the server, drivers, clients and plugins fit together, and what you need to install.
|
||||
- [How Does Appium Work?](https://appium.io/docs/en/latest/intro/appium/): the goals and architecture of the Appium project.
|
||||
- [Intro to Appium Drivers](https://appium.io/docs/en/latest/intro/drivers/): what a driver is, and how Appium supports unrelated platforms through them.
|
||||
- [Intro to Appium Clients](https://appium.io/docs/en/latest/intro/clients/): Appium's client-server architecture and its relationship to the W3C WebDriver specification.
|
||||
- [Getting Started](https://appium.io/docs/en/latest/quickstart/): the quickstart path - install Appium, install a driver, install a client, run a first script.
|
||||
- [System Requirements](https://appium.io/docs/en/latest/quickstart/requirements/): supported operating systems and the required Node.js and npm versions.
|
||||
- [Install Appium](https://appium.io/docs/en/latest/quickstart/install/): installing the Appium server with npm.
|
||||
- [Install the UiAutomator2 Driver](https://appium.io/docs/en/latest/quickstart/uiauto2-driver/): installing an Android driver and its Android SDK prerequisites.
|
||||
- [Write a Test (JS)](https://appium.io/docs/en/latest/quickstart/test-js/): a first Appium script in JavaScript with WebdriverIO.
|
||||
- [Write a Test (Python)](https://appium.io/docs/en/latest/quickstart/test-py/): a first Appium script in Python.
|
||||
- [Write a Test (Java)](https://appium.io/docs/en/latest/quickstart/test-java/): a first Appium script in Java.
|
||||
- [Write a Test (Ruby)](https://appium.io/docs/en/latest/quickstart/test-rb/): a first Appium script in Ruby.
|
||||
- [Write a Test (.NET)](https://appium.io/docs/en/latest/quickstart/test-dotnet/): a first Appium script in C#.
|
||||
- [Next Steps](https://appium.io/docs/en/latest/quickstart/next-steps/): where to go after the quickstart.
|
||||
|
||||
## Drivers, clients and plugins
|
||||
|
||||
Which driver you install determines which platform you can automate, and the driver's own
|
||||
documentation is the authoritative source for its capabilities and commands.
|
||||
|
||||
- [The Appium Ecosystem](https://appium.io/docs/en/latest/ecosystem/): index of officially-supported and community-supported Appium projects.
|
||||
- [Appium Drivers](https://appium.io/docs/en/latest/ecosystem/drivers/): every known driver with its install command and documentation link. Official drivers include UiAutomator2 and Espresso (Android), XCUITest (iOS, iPadOS, tvOS, watchOS), Mac2 (macOS), Windows, Chromium, Gecko and Safari.
|
||||
- [Appium Clients](https://appium.io/docs/en/latest/ecosystem/clients/): official clients for Java, Python, Ruby and .NET, plus community clients including WebdriverIO, Nightwatch.js, Robot Framework, Rust and Swift.
|
||||
- [Appium Plugins](https://appium.io/docs/en/latest/ecosystem/plugins/): optional extensions that modify or extend server behaviour. Not required for standard automation.
|
||||
- [Appium-Related Tools](https://appium.io/docs/en/latest/ecosystem/tools/): tools for installation, test development and inspection, such as Appium Inspector, and tools for driving Appium from an AI agent, such as the Appium MCP server and the Appium agent skills.
|
||||
- [Managing Drivers and Plugins](https://appium.io/docs/en/latest/guides/managing-exts/): installing, listing, updating and removing extensions, and installing from sources other than the official index.
|
||||
|
||||
## Sessions and capabilities
|
||||
|
||||
- [Session Capabilities](https://appium.io/docs/en/latest/guides/caps/): how capabilities work, the `appium:` vendor prefix, `appium:options` grouping, BiDi opt-in via `webSocketUrl`, and always-match versus first-match.
|
||||
- [Session Properties](https://appium.io/docs/en/latest/reference/session/): the key properties of a session - capabilities, execute methods, settings and locator strategies - and which are defined by the server versus the driver.
|
||||
- [Capabilities](https://appium.io/docs/en/latest/reference/session/caps/): the full list of capabilities recognized by the Appium server and base driver, and therefore supported by every driver.
|
||||
- [Session Settings](https://appium.io/docs/en/latest/guides/settings/): parameters that, unlike capabilities, can be adjusted while a session is running.
|
||||
- [Execute Methods](https://appium.io/docs/en/latest/guides/execute-methods/): the `mobile:` command convention that drivers use to expose commands beyond the WebDriver spec, with examples in every official client language.
|
||||
- [Automating Gestures](https://appium.io/docs/en/latest/guides/gestures/): approaches for automating gestures, including several examples for various touch gestures using the W3C Actions API.
|
||||
- [Managing Contexts](https://appium.io/docs/en/latest/guides/context/): switching between native and webview modes with the Get Contexts, Get Current Context and Set Context commands.
|
||||
- [Caching of Application Bundles](https://appium.io/docs/en/latest/guides/caching/): how the base driver caches application builds, and how to use it for faster suite execution.
|
||||
- [Retrieving Event Timings](https://appium.io/docs/en/latest/guides/event-timing/): startup and command timing information, retrieved from the `POST /session/:sessionId/appium/events` endpoint. The `appium:eventTimings` capability described on that page is not required for the endpoint to work, and is being removed in Appium 4.
|
||||
|
||||
## Server configuration and the command line
|
||||
|
||||
- [Command Line Interface](https://appium.io/docs/en/latest/reference/cli/): the `appium` executable and its `server`, `driver`, `plugin` and `setup` subcommands.
|
||||
- [appium server](https://appium.io/docs/en/latest/reference/cli/server/): the complete set of server command-line arguments.
|
||||
- [appium driver/plugin](https://appium.io/docs/en/latest/reference/cli/extensions/): the extension management subcommands - `install`, `list`, `update`, `uninstall`, `run` and `doctor`.
|
||||
- [appium setup](https://appium.io/docs/en/latest/reference/cli/setup/): installing a preset bundle of extensions for mobile, browser or desktop automation.
|
||||
- [Environment Variables](https://appium.io/docs/en/latest/reference/cli/env-vars/): advanced behaviour toggled through the environment rather than CLI arguments.
|
||||
- [The Appium Config File](https://appium.io/docs/en/latest/guides/config/): supplying server arguments through a config file instead of the command line. CLI arguments take precedence.
|
||||
- [Filtering the Appium Log](https://appium.io/docs/en/latest/guides/log-filters/): redacting passwords, device identifiers and other sensitive values from server logs with `--log-filters`.
|
||||
- [Header Handling](https://appium.io/docs/en/latest/guides/headers/): request ID tracking through the `x-request-id` header.
|
||||
- [SSL/TLS/SPDY Support](https://appium.io/docs/en/latest/guides/tls/): starting the server with secure connections enabled.
|
||||
- [Appium and Selenium Grid](https://appium.io/docs/en/latest/guides/grid/): relaying commands from a Selenium Grid to an Appium server.
|
||||
|
||||
## Security
|
||||
|
||||
- [Appium Server Security](https://appium.io/docs/en/latest/guides/security/): the insecure-feature mechanism, and the `--allow-insecure`, `--deny-insecure` and `--relaxed-security` flags. In Appium 3 every feature name requires a scope prefix.
|
||||
- [Insecure Features](https://appium.io/docs/en/latest/reference/cli/insecure-features/): the features defined at the server and official-plugin level, including `session_discovery` and `execute_driver_script`. Drivers define their own in their own documentation.
|
||||
|
||||
## API endpoints
|
||||
|
||||
- [Reference](https://appium.io/docs/en/latest/reference/): index of the core interfaces supported by the Appium server.
|
||||
- [API Endpoints](https://appium.io/docs/en/latest/reference/api/): endpoints exposed by the base driver, and therefore inherited by every driver.
|
||||
- [WebDriver Protocol](https://appium.io/docs/en/latest/reference/api/webdriver/): the W3C WebDriver endpoints Appium supports.
|
||||
- [WebDriver BiDi Protocol](https://appium.io/docs/en/latest/reference/api/bidi/): the W3C WebDriver BiDi commands Appium supports. These are commands over a WebSocket, not URL endpoints, and require the `webSocketUrl` capability.
|
||||
- [Appium Protocol](https://appium.io/docs/en/latest/reference/api/appium/): endpoints defined by Appium's own extension of the WebDriver protocol.
|
||||
- [Plugin Endpoints](https://appium.io/docs/en/latest/reference/api/plugins/): endpoints added or modified by the official plugins.
|
||||
- [Other Protocols](https://appium.io/docs/en/latest/reference/api/others/): additional supported endpoints, including the Chromedriver protocol extension.
|
||||
|
||||
## Migrating between major versions
|
||||
|
||||
- [Migrating to Appium 4](https://appium.io/docs/en/latest/guides/migrating-3-to-4/): breaking changes from Appium 3 to 4 - the Node 22 minimum, ESM-only requirement, removed endpoints, the Windows driver replacement, and more.
|
||||
- [Migrating to Appium 3](https://appium.io/docs/en/latest/guides/migrating-2-to-3/): every breaking change from Appium 2 to 3 - the Node 20 minimum, removed and modified endpoints, the mandatory `--allow-insecure` scope prefix, and the move from `GET /sessions` to `GET /appium/sessions`.
|
||||
- [Migrating to Appium 2](https://appium.io/docs/en/latest/guides/migrating-1-to-2/): the Appium 1 to 2 changes, including drivers becoming separately installed extensions and the default base path changing from `/wd/hub` to `/`.
|
||||
|
||||
## Building drivers and plugins
|
||||
|
||||
- [Developing Appium Extensions](https://appium.io/docs/en/latest/developing/): overview of building your own driver or plugin against Appium's modular structure.
|
||||
- [Building Drivers](https://appium.io/docs/en/latest/developing/build-drivers/): writing a driver, including command definitions, capability constraints and execute methods.
|
||||
- [Building Plugins](https://appium.io/docs/en/latest/developing/build-plugins/): writing a plugin that modifies or extends server behaviour.
|
||||
- [Building Doctor Checks](https://appium.io/docs/en/latest/developing/build-doctor-checks/): shipping preconditions checks that `appium driver doctor` can run for your extension.
|
||||
- [Building Documentation](https://appium.io/docs/en/latest/developing/build-docs/): using `@appium/docutils` to build an MkDocs site for your extension.
|
||||
- [Masking Sensitive Log Data](https://appium.io/docs/en/latest/developing/sensitive/): marking values as sensitive so they are redacted from extension logs.
|
||||
- [Appium's Config System](https://appium.io/docs/en/latest/developing/config-system/): how the schema-driven config file and CLI argument system works internally.
|
||||
- [Local Validation Of Extension PRs](https://appium.io/docs/en/latest/guides/branch-testing/): installing a driver or plugin from an unmerged branch to test it locally.
|
||||
|
||||
## Contributing
|
||||
|
||||
- [Contributing to Appium](https://appium.io/docs/en/latest/contributing/): how to contribute code, documentation, maintenance and support to the project.
|
||||
|
||||
## Optional
|
||||
|
||||
- [Appium Blog](https://appium.io/docs/en/latest/blog/): release announcements and project news.
|
||||
- [Additional Resources](https://appium.io/docs/en/latest/resources/): third-party courses, books, talks and tutorials.
|
||||
- [Appium Project History](https://appium.io/docs/en/latest/intro/history/): how the project evolved since 2012, of interest mostly for background.
|
||||
- [Sponsors & Backers](https://appium.io/docs/en/latest/sponsors/): the project's sponsorship program.
|
||||
- [Source repository](https://github.com/appium/appium): the Appium server implementation.
|
||||
- [JSON Wire Protocol](https://appium.io/docs/en/latest/reference/api/jsonwp/): legacy pre-W3C protocol, listed for historical reference. Do not base new code on it.
|
||||
- [Mobile JSON Wire Protocol](https://appium.io/docs/en/latest/reference/api/mjsonwp/): legacy pre-W3C mobile protocol extension, listed for historical reference. Do not base new code on it.
|
||||
@@ -13,7 +13,7 @@ The basic plan for this quickstart is as follows:
|
||||
|
||||
1. Install Appium
|
||||
1. Install an Appium driver and its dependencies
|
||||
- This guide provides instructions for the [UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver)
|
||||
- This guide provides instructions for the [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver)
|
||||
1. Install an Appium client library in your language of choice
|
||||
- This guide contains options for JavaScript, Python, Java, Ruby, and .NET
|
||||
1. Write and run a simple Appium automation script using a sample application
|
||||
|
||||
@@ -14,7 +14,7 @@ reference materials especially for beginners:
|
||||
- [Capabilities](../guides/caps.md)
|
||||
- [Settings](../guides/settings.md)
|
||||
|
||||
You'll also find that the [Appium Inspector](https://github.com/appium/appium-inspector) is an
|
||||
You'll also find that the [Appium Inspector](https://appium.github.io/appium-inspector/) is an
|
||||
indispensable tool for writing Appium tests, as it enables visual inspection of apps and
|
||||
helps you to discover element locators for use in your test scripts.
|
||||
|
||||
|
||||
@@ -8,15 +8,14 @@ title: Write a Test (.NET)
|
||||
The [Appium .NET Client](https://github.com/appium/dotnet-client/) is
|
||||
an official Appium client in C#. This driver is an extension of the Selenium C# client. It has all the functionalities of the regular driver, but add Appium-specific methods on top of this. The driver is available on the public NuGet Gallery as [Appium.WebDriver](https://www.nuget.org/packages/Appium.WebDriver/).
|
||||
|
||||
Now, we get inside the directory and create a new [NUnit](https://nunit.org/) project. We will also add the references to the Appium.Net driver, and other dependencies.
|
||||
Now, create a new [NUnit](https://nunit.org/) project. We will also add the references to the Appium.Net driver, and other dependencies.
|
||||
|
||||
```bash
|
||||
cd dotnet-client
|
||||
dotnet new nunit --name appiumtest
|
||||
|
||||
cd appiumtest
|
||||
|
||||
# This will install the latest 5.x version
|
||||
# This will install the latest version
|
||||
dotnet add package Appium.WebDriver --prerelease
|
||||
dotnet add package Newtonsoft.Json --version 13.0.3
|
||||
```
|
||||
@@ -47,7 +46,7 @@ public class Tests
|
||||
|
||||
driverOptions.AddAdditionalAppiumOption("appPackage", "com.android.settings");
|
||||
driverOptions.AddAdditionalAppiumOption("appActivity", ".Settings");
|
||||
// NoReset assumes the app com.google.android is preinstalled on the emulator
|
||||
// NoReset assumes the app com.android.settings is preinstalled on the emulator
|
||||
driverOptions.AddAdditionalAppiumOption("noReset", true);
|
||||
|
||||
_driver = new AndroidDriver(serverUri, driverOptions, TimeSpan.FromSeconds(180));
|
||||
|
||||
@@ -39,7 +39,7 @@ The `appium_lib_core` is the main part as an Appium client.
|
||||
`appium_lib_core` does not have such a global variable.
|
||||
|
||||
This example is by the `appium_lib_core` with `test-unit` gem module.
|
||||
Tes code in `appium_lib` should be similar.
|
||||
Test code in `appium_lib` should be similar.
|
||||
|
||||
```ruby title="test.rb"
|
||||
--8<-- "./sample-code/quickstarts/rb/test.rb"
|
||||
|
||||
@@ -12,7 +12,7 @@ interface that allows Appium to automate a particular platform.
|
||||
(whereas the iOS driver, for example, requires you to be using macOS).
|
||||
|
||||
The driver we're going to use is called the [UiAutomator2
|
||||
Driver](https://github.com/appium/appium-uiautomator2-driver). It's worth visiting that driver's
|
||||
Driver](https://appium.github.io/appium-uiautomator2-driver/). It's worth visiting that driver's
|
||||
documentation and bookmarking it, because it will be an invaluable reference down the line.
|
||||
|
||||
## Set up Android automation requirements
|
||||
@@ -105,7 +105,7 @@ appium setup
|
||||
```
|
||||
|
||||
Running this will install Appium's mobile-specific drivers: UiAutomator2, [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
(only if running macOS), and [Espresso](https://github.com/appium/appium-espresso-driver).
|
||||
(only if running macOS), and [Espresso](https://appium.github.io/appium-espresso-driver/).
|
||||
|
||||
You can also use this command to batch install drivers for desktop applications or desktop browsers.
|
||||
For more details on this, refer to the [Setup command documentation](../reference/cli/setup.md).
|
||||
|
||||
@@ -89,7 +89,7 @@ Retrieves the URL endpoints and WebDriver BiDi commands supported in the current
|
||||
#### Response
|
||||
|
||||
`ListCommandsResponse` - an object containing all supported endpoints and BiDi commands, grouped by
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### listExtensions
|
||||
@@ -103,7 +103,7 @@ Retrieves the [execute methods](../../guides/execute-methods.md) supported in th
|
||||
#### Response
|
||||
|
||||
`ListExtensionsResponse` - an object containing all supported execute methods, grouped by their
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### getCurrentAppiumContext
|
||||
|
||||
@@ -956,7 +956,7 @@ POST /session/:sessionId/actions
|
||||
|
||||
> WebDriver documentation: [Perform Actions](https://w3c.github.io/webdriver/#perform-actions)
|
||||
|
||||
Performs a sequence of [actions](https://w3c.github.io/webdriver/#actions).
|
||||
Performs a sequence of [actions](https://w3c.github.io/webdriver/#actions). See the [Automating Gestures](../../guides/gestures.md) guide for multi-language examples.
|
||||
|
||||
#### Parameters
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ appium {driver|plugin} install <install-spec>
|
||||
|--|--|
|
||||
|None|The short name of an official extension, with optional modifiers as supported by `npm install` (e.g. version or tag)|
|
||||
|`git`|The Git URL of the extension|
|
||||
|`github`|The GitHub repository URL of the extension|
|
||||
|`github`|The GitHub repository slug of the extension, in the form `<org>/<repo>`|
|
||||
|`local`|The local path to the extension containing its `package.json` file|
|
||||
|`npm`|The name of the `npm` package, with optional modifiers as supported by `npm install` (e.g. version or tag)|
|
||||
|
||||
@@ -114,7 +114,7 @@ appium {driver|plugin} install <install-spec>
|
||||
- Install the XCUITest driver from GitHub:
|
||||
|
||||
```
|
||||
appium driver install https://github.com/appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
appium driver install appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
```
|
||||
|
||||
- Install the XCUITest driver using a Git URL:
|
||||
|
||||
@@ -26,15 +26,12 @@ appium
|
||||
|
||||
|<div style="width:15em">Argument</div>|Description|Type|<div style="width:7em">Default</div>|
|
||||
|--|--|--|--|
|
||||
|`--address`, `-a`|IPv4/IPv6 address to listen on|string|`0.0.0.0`|
|
||||
|`--address`, `-a`|IPv4/IPv6 address or a hostname or a hostname or a hostname or a hostname to listen on|string|`0.0.0.0`|
|
||||
|`--allow-insecure`|List of [insecure features](../../guides/security.md) that should be allowed in this server's sessions. Individual features can be overridden by `--deny-insecure`. Has no effect in combination with `--relaxed-security`.|array<string>|`[]`|
|
||||
|`--allow-unknown-args`|Do not exit if unrecognized command-line arguments are passed to the server; ignore them instead. Useful when the Appium CLI is wrapped by external tooling that appends extra flags.|boolean|`false`|
|
||||
|`--app-url-rules`|Rules that remote application URLs (e.g. in the `appium:app` capability) must satisfy before the server downloads them. See the [Restricting Remote App URLs](../../guides/security.md#restricting-remote-app-urls) guide for details. Can be a path to a JSON file or raw JSON.|object||
|
||||
|`--base-path`, `-pa`|Base path to use as the prefix for all webdriver routes running on the server|string|`""`|
|
||||
|`--callback-address`, `-ca`|Callback IP address|string|`0.0.0.0`|
|
||||
|`--callback-port`, `-cp`|Callback port|integer|`4723`|
|
||||
|`--config`|Path to an [Appium configuration JSON file](../../guides/config.md)|string||
|
||||
|`--debug-log-spacing`|Add exaggerated spacing in logs to help with visual inspection|boolean|`false`|
|
||||
|`--default-capabilities`, `-dc`|Capabilities that will be used for each session, unless overridden by received capabilities. See [Default Capabilities](#default-capabilities) for how they are applied.|object||
|
||||
|`--deny-insecure`|List of [insecure features](../../guides/security.md) that should be disabled in this server's sessions. Since all insecure features are disabled by default, this argument has no effect without either `--allow-insecure` or `--relaxed-security`, and is applied after both.|array<string>|`[]`|
|
||||
|`--driver`|Driver-specific configuration. Keys should correspond to driver package names|object||
|
||||
@@ -60,7 +57,6 @@ appium
|
||||
|`--shutdown-timeout`|Timeout (in milliseconds) for waiting on all active connections to close, when shutting down the server|number|`5000`|
|
||||
|`--ssl-cert-path`|Absolute path to the `.cert` file if TLS is used. Must be provided together with `--ssl-key-path`. See the [SSL/TLS/SPDY Support guide](../../guides/tls.md) for details.|string||
|
||||
|`--ssl-key-path`|Absolute path to the `.key` file if TLS is used. Must be provided together with `--ssl-cert-path`. See the [SSL/TLS/SPDY Support guide](../../guides/tls.md) for details.|string||
|
||||
|`--strict-caps`|Prevent creation of new client sessions that use unsupported capabilities|boolean|`false`|
|
||||
|`--tmp`|Absolute path to the directory used for temporary files|string|[`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir)|
|
||||
|`--use-drivers`|List of drivers to activate. By default, all installed drivers are activated.|array<string>|`[]`|
|
||||
|`--use-plugins`|List of plugins to activate. By default, no plugins are activated. Set to `["all"]` to activate all installed plugins.|array<string>|`[]`|
|
||||
|
||||
@@ -33,7 +33,7 @@ default value.
|
||||
|--|--|--|--|
|
||||
| `webSocketUrl` | Toggle support of the WebDriver BiDi protocol | `boolean` ||
|
||||
| `appium:newCommandTimeout` | Number of seconds the Appium server should wait for clients to send commands before stopping the session. A value of `0` disables the timeout. | `number` | `60` |
|
||||
| `appium:printPageSourceOnFindFailure` | Toggle retrieval of the the page source and its printing to the Appium log, whenever a request to find an element fails | `boolean` ||
|
||||
| `appium:printPageSourceOnFindFailure` | Toggle retrieval of the page source and its printing to the Appium log, whenever a request to find an element fails | `boolean` ||
|
||||
|
||||
### Validated Capabilities
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ cd appium
|
||||
|
||||
!!! info
|
||||
|
||||
If you are VS Code user, you can easily check out the project using [Runme](https://runme.dev/api/runme?repository=https%3A%2F%2Fgithub.com%2Fappium%2Fappium.git&fileToOpen=packages%2Fappium%2Fdocs%2Fen%2Fcontributing%2Findex.md).
|
||||
If you are VS Code user, you can easily check out the project using [Runme](https://runme.dev/).
|
||||
|
||||
Install dependencies:
|
||||
|
||||
@@ -109,6 +109,27 @@ export APPIUM_WORKSPACE=@appium/base-driver
|
||||
npm run test:unit -w $APPIUM_WORKSPACE
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
#### JSDoc
|
||||
|
||||
Add concise JSDoc to all public class methods and exported functions.
|
||||
|
||||
#### Module Ordering
|
||||
|
||||
Place exported functions and classes before non-exported functions and classes in each module.
|
||||
This rule does not apply to constants or variables.
|
||||
|
||||
#### Class Member Ordering
|
||||
|
||||
When adding or moving class methods, follow these conventions:
|
||||
|
||||
- Order methods by visibility: public → protected → private. Methods without an explicit visibility
|
||||
modifier belong to the public group.
|
||||
- Place private helper methods at the end of the class.
|
||||
- Preserve existing order within each visibility group.
|
||||
- Before finishing, check the ordering of methods you added or moved.
|
||||
|
||||
### Documentation
|
||||
|
||||
The documentation for this project is [available in the project repository itself](https://github.com/appium/appium/tree/master/packages/appium/docs).
|
||||
|
||||
@@ -62,6 +62,33 @@ them in `docs`, and add links to these files in `mkdocs.yml`.
|
||||
Refer to the [MkDocs documentation](https://www.mkdocs.org/user-guide/writing-your-docs/) for
|
||||
information on how to organize and structure your documentation.
|
||||
|
||||
### Helping LLMs Use Your Documentation
|
||||
|
||||
The [`/llms.txt` convention](https://llmstxt.org/) is a Markdown file at the root of a
|
||||
documentation site, listing its most useful pages so that large language models can navigate the
|
||||
site without crawling it. Appium's own documentation provides one at
|
||||
[appium.io/docs/en/latest/llms.txt](https://appium.io/docs/en/latest/llms.txt), and you may want to
|
||||
do the same for your extension.
|
||||
|
||||
MkDocs copies any non-Markdown file in your `docs` directory to the built site as-is, so all you
|
||||
need to do is write an `llms.txt` and place it alongside your Markdown files. No plugin or build
|
||||
configuration is required.
|
||||
|
||||
The value of this file lies in the parts a crawler could not have worked out on its own, so it is
|
||||
worth writing by hand rather than generating it from your navigation:
|
||||
|
||||
- A short description of what your extension automates, and which platforms it targets
|
||||
- The handful of things models reliably get wrong about your extension - removed capabilities,
|
||||
renamed commands, patterns from an older major version that no longer apply
|
||||
- Links to the pages that matter, with a description of each
|
||||
|
||||
Because links in `llms.txt` are read out of context, they must be absolute URLs. If you use Mike
|
||||
for versioning, point them at your version alias (for example `latest`) rather than at a pinned
|
||||
version, so that the file does not go stale. Note that a hand-written file can drift as pages are
|
||||
renamed; Appium validates its own with a
|
||||
[script](https://github.com/appium/appium/blob/master/packages/appium/docs/scripts/check-llmstxt.js)
|
||||
that runs in CI, which you are welcome to adapt.
|
||||
|
||||
### Building the Docs
|
||||
|
||||
At this point, you can use the `appium-docs` CLI tool. Run this tool with no arguments to get the
|
||||
|
||||
@@ -80,8 +80,9 @@ The required subfields are:
|
||||
capability to tell Appium to use _your_ driver.
|
||||
- `platformNames`: this is an array of one or more platform names considered valid for your driver.
|
||||
When a user sends in the `platformName` capability to start a session, it must be included in
|
||||
this list for your driver to handle the session. Known platform name strings include: `iOS`,
|
||||
`tvOS`, `macOS`, `Windows`, `Android`.
|
||||
this list for your driver to handle the session. These names should generally include the
|
||||
operating systems your driver is targeting - for example, a driver targeting iOS would include
|
||||
`iOS`, while a driver targeting the Chrome browser on Windows would include `Windows`.
|
||||
- `mainClass`: this is a named export (in CommonJS style) from your `main` field. It must be a
|
||||
class which extends Appium's `BaseDriver` (see below).
|
||||
|
||||
@@ -283,8 +284,8 @@ You handle WebDriver commands by implementing functions in your driver class. Ea
|
||||
WebDriver Protocol, plus the various Appium extensions, has a corresponding function that you
|
||||
implement if you want to support that command in your driver. The best way to see which commands
|
||||
Appium supports and which method you need to implement for each command is to look at Appium's
|
||||
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
|
||||
Each route object in this file tells you the command name as well as the parameters you'd expect to
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes).
|
||||
Each route object in this directory's files tells you the command name as well as the parameters you'd expect to
|
||||
receive for that command.
|
||||
|
||||
Let's take this block for example:
|
||||
@@ -327,7 +328,7 @@ Websockets. Implementing handlers for BiDi commands works just the same as imple
|
||||
for WebDriver classic commands (described in the previous section). You simply define a method on
|
||||
your driver of the appropriate name, and it will be called when the BiDi command is requested by
|
||||
the client. To see which specific names you should use for BiDi commands, have a look at
|
||||
[bidi-commands.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.js)
|
||||
[bidi-commands.ts](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.ts)
|
||||
|
||||
You are not limited to BiDi commands that are defined in the official BiDi specification. If you
|
||||
wish to define new commands, you may do so; you just need to tell Appium about them! See
|
||||
|
||||
@@ -482,7 +482,7 @@ capability.
|
||||
|
||||
You probably don't normally need to update the Appium server object (which is an
|
||||
[Express](https://expressjs.com/) server having already been
|
||||
[configured](https://github.com/appium/appium/blob/master/packages/base-driver/lib/express/server.js)
|
||||
[configured](https://github.com/appium/appium/blob/master/packages/base-driver/lib/express/server.ts)
|
||||
in a variety of ways). But, for example, you could add new routes to the server to support your
|
||||
plugin's requirements. To update the server you must implement the `static async updateServer`
|
||||
method in your class. This method takes three parameters:
|
||||
|
||||
@@ -65,7 +65,7 @@ functionality (e.g., the `plugin` or `driver` subcommands).
|
||||
Note that this file is the _base_ schema; this will become painfully relevant.
|
||||
```
|
||||
|
||||
This file is is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
This file is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.
|
||||
|
||||
It is more straightforward to explain how config files are validated, so we'll start there.
|
||||
|
||||
@@ -17,34 +17,34 @@ If you maintain an Appium driver that you would like to be listed here, feel fre
|
||||
|
||||
These drivers are currently maintained by the Appium team:
|
||||
|
||||
### [Chromium](https://github.com/appium/appium-chromium-driver)
|
||||
### [Chromium](https://appium.github.io/appium-chromium-driver)
|
||||
|
||||
- Target: Desktop and mobile Chromium browsers (Chrome, Microsoft Edge, etc.)
|
||||
- Target: Desktop Chromium-based browsers (Chrome, Microsoft Edge, etc.)
|
||||
- Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install chromium
|
||||
```
|
||||
|
||||
### [Espresso](https://github.com/appium/appium-espresso-driver)
|
||||
### [Espresso](https://appium.github.io/appium-espresso-driver)
|
||||
|
||||
- Target: Android applications
|
||||
- Mode: Native
|
||||
- Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
- Mode: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install espresso
|
||||
```
|
||||
|
||||
### [Gecko](https://github.com/appium/appium-geckodriver)
|
||||
### [Gecko](https://appium.github.io/appium-geckodriver)
|
||||
|
||||
- Target: Desktop and mobile Gecko browsers (Firefox)
|
||||
- Target: Desktop and mobile Gecko-based browsers (Firefox, etc.)
|
||||
- Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install gecko
|
||||
```
|
||||
|
||||
### [Mac2](https://github.com/appium/appium-mac2-driver)
|
||||
### [Mac2](https://appium.github.io/appium-mac2-driver)
|
||||
|
||||
- Target: macOS applications
|
||||
- Mode: Native
|
||||
@@ -53,7 +53,7 @@ appium driver install gecko
|
||||
appium driver install mac2
|
||||
```
|
||||
|
||||
### [Safari](https://github.com/appium/appium-safari-driver)
|
||||
### [Safari](https://appium.github.io/appium-safari-driver)
|
||||
|
||||
- Target: Desktop and mobile Safari browsers
|
||||
- Mode: Web
|
||||
@@ -62,9 +62,9 @@ appium driver install mac2
|
||||
appium driver install safari
|
||||
```
|
||||
|
||||
### [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver)
|
||||
### [UiAutomator2](https://appium.github.io/appium-uiautomator2-driver)
|
||||
|
||||
- Target: Android, Android TV, Android Wear applications
|
||||
- Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
- Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
@@ -89,7 +89,7 @@ appium driver install windows
|
||||
|
||||
### [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
|
||||
- Target: iOS, iPadOS, tvOS applications
|
||||
- Target: iOS, iPadOS, tvOS, watchOS applications
|
||||
- Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
@@ -169,7 +169,7 @@ This driver is recommended as a drop-in replacement for the partially unmaintain
|
||||
```
|
||||
|
||||
- Target: Windows applications
|
||||
- Mode: Native
|
||||
- Mode: Native, Hybrid
|
||||
- Supported by: Community / Automate The Planet
|
||||
|
||||
```sh title="Install This Driver"
|
||||
|
||||
@@ -14,7 +14,7 @@ If you maintain an Appium plugin that you would like to be listed here, feel fre
|
||||
|
||||
## Official Plugins
|
||||
|
||||
These plugins are are currently maintained by the Appium team:
|
||||
These plugins are currently maintained by the Appium team:
|
||||
|
||||
### [Execute Driver](https://github.com/appium/appium/tree/master/packages/execute-driver-plugin)
|
||||
|
||||
|
||||
@@ -113,3 +113,44 @@ Supported by: `@AppiumTestDistribution`
|
||||
```sh title="Install This Tool"
|
||||
npm install -g appium-installer
|
||||
```
|
||||
|
||||
### [Optics](https://github.com/mozarkai/optics-framework)
|
||||
|
||||
Optics is an open-source test automation framework that uses Appium to drive Android, iOS,
|
||||
Android TV, Samsung Tizen and LG webOS apps (and Selenium or Playwright for web). Tests are written
|
||||
as CSV or YAML keyword steps, and the same keywords are available as a Python library, a Robot
|
||||
Framework library, an HTTP API and an MCP server. Element lookup falls back from XPath to visible
|
||||
text, OCR and image matching.
|
||||
|
||||
Supported by: `@chinmayajha`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
pip install "optics-framework[mobile]"
|
||||
```
|
||||
|
||||
### [XPath to Class Chain](https://github.com/domovou69/xpath-to-class-chain)
|
||||
|
||||
XPath to Class Chain is a command-line tool for migrating iOS XPath locators in test code to
|
||||
`-ios class chain` selectors. It converts the XPaths that have an exact class chain equivalent,
|
||||
converts with a warning those that only have a close one (such as grouped positions `(//X)[n]`),
|
||||
and skips the rest with a reason (for example, sibling or parent axes).
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-class-chain
|
||||
```
|
||||
|
||||
### [XPath to UiSelector](https://github.com/domovou69/xpath-to-uiselector)
|
||||
|
||||
XPath to UiSelector is a command-line tool for migrating Android XPath locators in test code to
|
||||
`-android uiautomator` selectors. It converts the XPaths that have an exact `UiSelector`
|
||||
equivalent, converts with a warning those that only have a close one (such as `//A//B`), and skips
|
||||
the rest with a reason. The strategy call is rewritten together with the string, for example
|
||||
`AppiumBy.xpath` becomes `AppiumBy.androidUIAutomator`.
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-uiselector
|
||||
```
|
||||
|
||||
@@ -52,6 +52,6 @@ After the PR is merged there is no need to use the local plugin/driver deploymen
|
||||
to switch back to the package managed by NPM. Follow the next steps for that:
|
||||
|
||||
- Unlink the installed driver/plugin from the server by running `appium driver uninstall <driver_name>` or
|
||||
`appium plugin uninstall <driver_name>`.
|
||||
`appium plugin uninstall <plugin_name>`.
|
||||
- Delete the local source folder (`rm -rf <full_path_to_plugin_or_driver_folder_with_fetched_sources>`).
|
||||
- Install the driver or the plugin from NPM. Check the component README in order to find a proper command for that.
|
||||
|
||||
@@ -8,14 +8,14 @@ principles, so you could create more performant and efficient test suite executi
|
||||
|
||||
## Why Caching Is Necessary
|
||||
|
||||
Mobile application bundles could reach hundreds of megabytes is size. This could become a serious
|
||||
Mobile application bundles could reach hundreds of megabytes in size. This could become a serious
|
||||
performance issue if a test suite is executed, and it is necessary to fetch/extract the same application
|
||||
bundle for each test.
|
||||
|
||||
## What Is Cached
|
||||
|
||||
Caching could be applied to application bundles generated by
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L107) helper call.
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L99) helper call.
|
||||
Inherited drivers can customize their caching logic by providing own `onPostProcess`
|
||||
(or both `inDownload` and `onPostProcess`) property definition, but the general
|
||||
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
|
||||
|
||||
@@ -24,7 +24,7 @@ to launch. In order to achieve this, Appium defines its own [extension capabilit
|
||||
## 一般的なAppiumのCapabilities
|
||||
|
||||
According to the WebDriver spec, extension capabilities must include a namespace prefix (signifying
|
||||
the vendor introducing the capability), and the namespace must end a colon (`:`). Appium's vendor
|
||||
the vendor introducing the capability), and the namespace must end in a colon (`:`). Appium's vendor
|
||||
prefix is `appium:`, and so any Appium-specific capabilities must include this prefix. Depending on
|
||||
your Appium client, the prefix may be added automatically or in conjunction with certain interfaces,
|
||||
but it is always a good practice to explicitly include it for clarity.
|
||||
|
||||
@@ -36,7 +36,7 @@ discovered and loaded by Appium:
|
||||
- `appium.config.cjs`
|
||||
- `.appiumrc` (which is considered to be JSON)
|
||||
|
||||
Further, _if your project uses Node.js,_ you can use store the configuration inside an `appium`
|
||||
Further, _if your project uses Node.js,_ you can store the configuration inside an `appiumConfig`
|
||||
property in your `package.json` and it will be automatically discovered.
|
||||
|
||||
### Config File Search
|
||||
|
||||
@@ -17,7 +17,7 @@ property:
|
||||
|
||||
```
|
||||
{
|
||||
"<event_type>": [<occurence_timestamp_1>, ...],
|
||||
"<event_type>": [<occurrence_timestamp_1>, ...],
|
||||
"commands": [
|
||||
{
|
||||
"cmd": "<command_name>",
|
||||
|
||||
@@ -16,7 +16,7 @@ by client libraries. There are two main strategies for this:
|
||||
based client library (including all Selenium and Appium clients).
|
||||
|
||||
There are pros and cons for each strategy, but it is ultimately up to the extension author to
|
||||
decide how they wish implement new commands.
|
||||
decide how they wish to implement new commands.
|
||||
|
||||
This guide is designed to specifically help you understand the "Execute Method" strategy. This
|
||||
pattern is commonly used in official Appium drivers and other third-party extensions.
|
||||
|
||||
@@ -18,8 +18,11 @@ Each rule is an object with a set of predefined properties. The following rule p
|
||||
supported:
|
||||
|
||||
- `pattern`: A valid Javascript RegExp pattern to replace. Must be a valid non-empty pattern.
|
||||
- `text`: A simple non-empty exact text match to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided.
|
||||
- `text`: A simple non-empty exact text to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided. Note that the text is
|
||||
matched as a whole word: special characters are escaped and word boundaries (`\b`) are added on
|
||||
the sides where the value begins or ends with a word character. For example, a rule with text
|
||||
`pass` replaces `pass` in `my pass here`, but does not match inside `password`.
|
||||
- `flags`: Regular expression flags for the given pattern. Supported flags are the same as for the
|
||||
standard JavaScript [RegExp constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2).
|
||||
Note that the `g` (global matching) flag is always enabled.
|
||||
|
||||
@@ -27,7 +27,7 @@ appium driver install xcuitest
|
||||
```
|
||||
|
||||
This command will install the latest version of the
|
||||
[XCUITest Driver](https://github.com/appium/appium-xcuitest-driver). The Extension CLI comes with a variety
|
||||
[XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/). The Extension CLI comes with a variety
|
||||
of commands and parameters; see the documentation for that command for all the specifics.
|
||||
|
||||
The all-important question when Appium is managing your extensions for you is: where are they installed?
|
||||
@@ -65,7 +65,7 @@ like any other dependency. Basically, whenever you run Appium, if you have not e
|
||||
1. Try to determine whether the _current directory_ is inside an `npm` package.
|
||||
2. If so, it will check whether `appium` is a dependency (dev, prod, or peer) in the project's
|
||||
`package.json`
|
||||
3. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will ignore load
|
||||
3. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will load
|
||||
drivers and plugins defined in that `package.json` file instead.
|
||||
|
||||
This means you are freely able to add Appium drivers and plugins as regular package dependencies or
|
||||
|
||||
@@ -111,12 +111,12 @@ The Appium 1 ecosystem included several drivers, clients and other packages that
|
||||
deprecated and replaced with newer packages. Appium 2 no longer includes support for these packages,
|
||||
and it is recommended to migrate to the following replacements:
|
||||
|
||||
| Appium 1 Package | Replacement in Appium 2 |
|
||||
| ------------------ | -------------------------------------------------------------------------- |
|
||||
| iOS Driver | [XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/latest/) |
|
||||
| UiAutomator Driver | [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver/) |
|
||||
| `wd` Client | [WebdriverIO Client](https://webdriver.io/) |
|
||||
| Appium Desktop | [Appium Inspector](https://github.com/appium/appium-inspector) |
|
||||
| Appium 1 Package | Replacement in Appium 2 |
|
||||
| ------------------ | -------------------------------------------------------------------- |
|
||||
| iOS Driver | [XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/) |
|
||||
| UiAutomator Driver | [UiAutomator2](https://appium.github.io/appium-uiautomator2-driver/) |
|
||||
| `wd` Client | [WebdriverIO Client](https://webdriver.io/) |
|
||||
| Appium Desktop | [Appium Inspector](https://appium.github.io/appium-inspector/) |
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
|
||||
@@ -142,9 +142,9 @@ Icons are used to indicate endpoint support in either certain drivers, or in the
|
||||
|
||||
- :simple-appium:{ .lg } - Appium server
|
||||
- :material-apple:{ .lg } - [XCUITest driver](https://appium.github.io/appium-xcuitest-driver/latest/)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://github.com/appium/appium-espresso-driver)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://github.com/appium/appium-mac2-driver)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://appium.github.io/appium-espresso-driver/)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://appium.github.io/appium-mac2-driver/)
|
||||
- :material-microsoft-windows:{ .lg } - [Windows driver](https://github.com/appium/appium-windows-driver)
|
||||
|
||||
</div>
|
||||
@@ -448,9 +448,6 @@ longer accepts, as well as the parameters it continues to accept in Appium 3.
|
||||
- `POST /session/:sessionId/element/:elementId/value`
|
||||
- :octicons-x-24: `value`
|
||||
- :octicons-check-24: `text`
|
||||
- `POST /session/:sessionId/timeouts`
|
||||
- :octicons-x-24: `type`, `ms`
|
||||
- :octicons-check-24: `script`, `pageLoad`, `implicit`
|
||||
- `POST /session/:sessionId/window`
|
||||
- :octicons-x-24: `name`
|
||||
- :octicons-check-24: `handle`
|
||||
|
||||
@@ -49,8 +49,8 @@ async setUrl(url) {
|
||||
|
||||
[^1]: You might notice that `setUrl` doesn't look anything like `Navigate To`, so how did we know
|
||||
to use it rather than some other random string? Well, Appium's WebDriver-protocol-to-method-name
|
||||
mapping is defined in a special file within the `@appium/base-driver` package called
|
||||
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
|
||||
mappings are defined in special files within the `@appium/base-driver` package's
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes) directory.
|
||||
So if you're writing a driver, this is where you would go to figure out what method names to use
|
||||
and what parameters to expect. Or you could look at the source for any of the main Appium
|
||||
drivers!
|
||||
@@ -81,7 +81,7 @@ from there is up to you, the driver author!
|
||||
## Automation mapping
|
||||
|
||||
But _typically_ what driver authors want to do is to provide automation behaviours for a given
|
||||
platform(s) that are semantically very similar to the the WebDriver spec implementations for
|
||||
platform(s) that are semantically very similar to the WebDriver spec implementations for
|
||||
browsers. When you want to find an element, you should get a reference to a UI element. When you
|
||||
want to click or tap that element, the resulting behaviour should be the same as if a person were
|
||||
to click or tap on the element. And so on.
|
||||
@@ -175,7 +175,7 @@ be passed back directly to the client, without any XCUITest driver code seeing i
|
||||
This architectural pattern provides a nice bonus for driver authors who choose to deal with the
|
||||
WebDriver protocol everywhere, rather than constructing bespoke protocols. It also means that
|
||||
Appium can create wrapper drivers for any other existing WebDriver implementation very easily. If
|
||||
you look at the [Appium Safari driver](https://github.com/appium/appium-safari-driver) code, for
|
||||
you look at the [Appium Safari driver](https://appium.github.io/appium-safari-driver/) code, for
|
||||
example, you'll see that it implements basically no standard commands, because all of these are
|
||||
proxied directly to an underlying SafariDriver process.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ title: Getting Started
|
||||
|
||||
1. Appiumをインストールする
|
||||
2. Appiumドライバーとその依存関係をインストールする
|
||||
- このガイドでは、[UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver) について説明します
|
||||
- This guide provides instructions for the [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver/)
|
||||
3. お好みの言語でAppiumクライアント・ライブラリをインストールする
|
||||
- このガイドでは、JavaScript、Python、Java、Ruby、.NETの例で説明します
|
||||
4. サンプルアプリケーションを使用した簡単なAppiumでの自動化スクリプトの作成と実行
|
||||
|
||||
@@ -12,7 +12,9 @@ Android自動化用のシステムの設定と簡単なテストの実行が完
|
||||
- [Capabilities](../guides/caps.md)
|
||||
- [Settings](../guides/settings.md)
|
||||
|
||||
また、[Appium Inspector](https://github.com/appium/appium-inspector) は、アプリの視覚的な検査を可能にし、テストスクリプトで使用する要素ロケーターを見つけるのに役立つ、Appium テストの作成に不可欠なツールです。
|
||||
You'll also find that the [Appium Inspector](https://appium.github.io/appium-inspector/) is an
|
||||
indispensable tool for writing Appium tests, as it enables visual inspection of apps and
|
||||
helps you to discover element locators for use in your test scripts.
|
||||
|
||||
また、多くのオンライン Appium コースもご活用いただけます。
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ The `appium_lib_core` is the main part as an Appium client.
|
||||
`appium_lib_core` does not have such a global variable.
|
||||
|
||||
This example is by the `appium_lib_core` with `test-unit` gem module.
|
||||
Tes code in `appium_lib` should be similar.
|
||||
Test code in `appium_lib` should be similar.
|
||||
|
||||
```ruby title="test.rb"
|
||||
--8<-- "./sample-code/quickstarts/rb/test.rb"
|
||||
|
||||
@@ -109,7 +109,7 @@ appium setup
|
||||
```
|
||||
|
||||
Running this will install Appium's mobile-specific drivers: UiAutomator2, [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
(only if running macOS), and [Espresso](https://github.com/appium/appium-espresso-driver).
|
||||
(only if running macOS), and [Espresso](https://appium.github.io/appium-espresso-driver/).
|
||||
|
||||
You can also use this command to batch install drivers for desktop applications or desktop browsers.
|
||||
For more details on this, refer to the [Setup command documentation](../reference/cli/setup.md).
|
||||
|
||||
@@ -90,7 +90,7 @@ Retrieves the URL endpoints and WebDriver BiDi commands supported in the current
|
||||
#### Response
|
||||
|
||||
`ListCommandsResponse` - an object containing all supported endpoints and BiDi commands, grouped by
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### listExtensions
|
||||
@@ -104,7 +104,7 @@ Retrieves the [execute methods](../../guides/execute-methods.md) supported in th
|
||||
#### Response
|
||||
|
||||
`ListExtensionsResponse` - an object containing all supported execute methods, grouped by their
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### getCurrentAppiumContext
|
||||
|
||||
@@ -47,7 +47,7 @@ Subscribes to one or more BiDi events.
|
||||
|
||||
#### Response
|
||||
|
||||
`null`
|
||||
`{subscription: string}` — a unique subscription ID that can be passed to `session.unsubscribe`.
|
||||
|
||||
### bidiUnsubscribe
|
||||
|
||||
@@ -57,15 +57,20 @@ session.unsubscribe
|
||||
|
||||
> WebDriver BiDi documentation: [session.unsubscribe](https://w3c.github.io/webdriver-bidi/#command-session-unsubscribe)
|
||||
|
||||
Unsubscribes from one or more BiDi events.
|
||||
Unsubscribes using previously returned subscription IDs, or the legacy event/context form.
|
||||
Removing a subscription ID preserves any overlapping subscriptions.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ------- |
|
||||
| `contexts?` | Contexts in which to unsubscribe from the specified events. By default, the global scope is applied. | string[] | `['']` |
|
||||
| `events` | Names of events to unsubscribe from | string[] | |
|
||||
| Name | Description | Type | Default |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------- |
|
||||
| `subscriptions?` | Non-empty list of subscription IDs returned by `session.subscribe`. Cannot be combined with `events` or `contexts`. | string[] | |
|
||||
| `contexts?` | Non-standard, only together with `events`: contexts in which to unsubscribe from the specified events. By default, the global scope is applied. | string[] | `['']` |
|
||||
| `events?` | Non-empty list of event names to unsubscribe from. Required when `subscriptions` is omitted. | string[] | |
|
||||
|
||||
All subscription IDs must be known to the current session. An unknown ID causes an `invalid argument`
|
||||
error without removing any subscriptions. Subscription IDs are cleared when the session is deleted.
|
||||
|
||||
#### Response
|
||||
|
||||
`null`
|
||||
`{}`
|
||||
|
||||
@@ -82,7 +82,7 @@ appium {driver|plugin} install <install-spec>
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| None | The short name of an official extension, with optional modifiers as supported by `npm install` (e.g. version or tag) |
|
||||
| `git` | The Git URL of the extension |
|
||||
| `github` | The GitHub repository URL of the extension |
|
||||
| `github` | The GitHub repository slug of the extension, in the form `<org>/<repo>` |
|
||||
| `local` | The local path to the extension containing its `package.json` file |
|
||||
| `npm` | The name of the `npm` package, with optional modifiers as supported by `npm install` (e.g. version or tag) |
|
||||
|
||||
@@ -115,7 +115,7 @@ appium {driver|plugin} install <install-spec>
|
||||
- Install the XCUITest driver from GitHub:
|
||||
|
||||
```
|
||||
appium driver install https://github.com/appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
appium driver install appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
```
|
||||
|
||||
- Install the XCUITest driver using a Git URL:
|
||||
|
||||
@@ -26,7 +26,7 @@ appium
|
||||
|
||||
| <div style="width:15em">Argument</div> | Description | Type | <div style="width:7em">Default</div> |
|
||||
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------------- |
|
||||
| `--address`, `-a` | IPv4/IPv6 address to listen on | string | `0.0.0.0` |
|
||||
| `--address`, `-a` | IPv4/IPv6 address or a hostname to listen on | string | `0.0.0.0` |
|
||||
| `--allow-cors` | Allow web browser connections from any host | boolean | `false` |
|
||||
| `--allow-insecure` | List of [insecure features](../../guides/security.md) that should be allowed in this server's sessions. Individual features can be overridden by `--deny-insecure`. Has no effect in combination with `--relaxed-security`. | array<string> | `[]` |
|
||||
| `--allow-unknown-args` | Do not exit if unrecognized command-line arguments are passed to the server; ignore them instead. Useful when the Appium CLI is wrapped by external tooling that appends extra flags. | boolean | `false` |
|
||||
|
||||
@@ -34,7 +34,7 @@ default value.
|
||||
| `webSocketUrl` | Toggle support of the WebDriver BiDi protocol | `boolean` | |
|
||||
| `appium:eventTimings` (deprecated) | Toggle collection of [Event Timings](../../guides/event-timing.md). This capability is deprecated - please use the [`getLogEvents`](../api/appium.md#getlogevents) endpoint instead. | `boolean` | |
|
||||
| `appium:newCommandTimeout` | Number of seconds the Appium server should wait for clients to send commands before stopping the session. A value of `0` disables the timeout. | `number` | `60` |
|
||||
| `appium:printPageSourceOnFindFailure` | Toggle retrieval of the the page source and its printing to the Appium log, whenever a request to find an element fails | `boolean` | |
|
||||
| `appium:printPageSourceOnFindFailure` | Toggle retrieval of the page source and its printing to the Appium log, whenever a request to find an element fails | `boolean` | |
|
||||
|
||||
### Validated Capabilities
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ nav:
|
||||
- guides/caps.md
|
||||
- guides/settings.md
|
||||
- guides/execute-methods.md
|
||||
- guides/gestures.md
|
||||
- guides/context.md
|
||||
- guides/event-timing.md
|
||||
- More Resources:
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Validates `docs/en/llms.txt` against the documentation sources.
|
||||
*
|
||||
* `llms.txt` is hand-written, so nothing regenerates it when a page is added, renamed or removed.
|
||||
* This check fails the build when it points at a page that no longer exists, and when a page has
|
||||
* been added without being listed.
|
||||
*
|
||||
* For simplicity this file is not transpiled and is run directly via an npm script.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
|
||||
const DOCS_EN_DIR = path.resolve(__dirname, '..', 'en');
|
||||
const LLMS_TXT_PATH = path.join(DOCS_EN_DIR, 'llms.txt');
|
||||
|
||||
/**
|
||||
* Base URL that every link to our own documentation must use. `llms.txt` is served from a versioned
|
||||
* directory but is consumed out of context, so the links inside it have to be absolute, and they
|
||||
* have to point at the `latest` alias rather than at a pinned version.
|
||||
*/
|
||||
const SITE_BASE_URL = 'https://appium.io/docs/en/latest';
|
||||
|
||||
/**
|
||||
* Pages that are deliberately absent from `llms.txt`, and should not be warned about.
|
||||
*/
|
||||
const UNLISTED_PAGES = [
|
||||
// individual blog posts; the blog index is listed instead
|
||||
/^blog\/posts\//,
|
||||
];
|
||||
|
||||
/**
|
||||
* Extracts the target of every Markdown link in `contents`.
|
||||
* @param {string} contents
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function extractLinks(contents) {
|
||||
return [...contents.matchAll(/\[[^\]]+\]\(([^)\s]+)\)/g)].map(([, url]) => url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a documentation URL back to the source files that could have produced it.
|
||||
*
|
||||
* MkDocs builds directory URLs by default, so `guides/caps.md` is served at `guides/caps/`, and
|
||||
* `intro/index.md` is served at `intro/`. Both spellings are accepted here.
|
||||
*
|
||||
* @param {string} url an URL underneath {@linkcode SITE_BASE_URL}
|
||||
* @returns {string[]} candidate paths, relative to the `en` docs dir
|
||||
*/
|
||||
function candidateSourcePaths(url) {
|
||||
const withoutBase = url.slice(SITE_BASE_URL.length);
|
||||
const pagePath = withoutBase.replace(/#.*$/, '').replace(/^\//, '').replace(/\/$/, '');
|
||||
if (!pagePath) {
|
||||
return ['index.md'];
|
||||
}
|
||||
return [`${pagePath}.md`, path.posix.join(pagePath, 'index.md')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds every Markdown page in the English docs, as paths relative to the `en` docs dir.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function findAllPages() {
|
||||
const entries = await fs.readdir(DOCS_EN_DIR, {recursive: true});
|
||||
return entries
|
||||
.map((entry) => entry.split(path.sep).join(path.posix.sep))
|
||||
.filter((entry) => entry.endsWith('.md'))
|
||||
.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that every documentation link in `llms.txt` resolves to a source file.
|
||||
* @param {string[]} links
|
||||
* @returns {Promise<string[]>} error messages
|
||||
*/
|
||||
async function findBrokenLinks(links) {
|
||||
const errors = [];
|
||||
for (const link of links) {
|
||||
const isOurDocs = link.includes('appium.io/docs/') || link.startsWith('/docs/');
|
||||
// relative and site-relative links do not survive being read out of context, and a link to our
|
||||
// docs that is not under the `latest` alias goes stale as soon as a new version is released
|
||||
const isAbsolute = /^[a-z][a-z\d+.-]*:/i.test(link);
|
||||
if ((!isAbsolute || isOurDocs) && !link.startsWith(SITE_BASE_URL)) {
|
||||
errors.push(`${link}\n must be an absolute link under ${SITE_BASE_URL}`);
|
||||
continue;
|
||||
}
|
||||
if (!link.startsWith(SITE_BASE_URL)) {
|
||||
// external link; not ours to validate
|
||||
continue;
|
||||
}
|
||||
const candidates = candidateSourcePaths(link);
|
||||
const found = await Promise.all(
|
||||
candidates.map(async (candidate) => {
|
||||
try {
|
||||
await fs.access(path.join(DOCS_EN_DIR, candidate));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (!found.includes(true)) {
|
||||
errors.push(`${link}\n no such page (looked for ${candidates.join(' and ')})`);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds pages that exist but are not linked from `llms.txt`.
|
||||
* @param {string[]} links
|
||||
* @param {string[]} pages
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function findUnlistedPages(links, pages) {
|
||||
const linked = new Set(
|
||||
links
|
||||
.filter((link) => link.startsWith(SITE_BASE_URL))
|
||||
.flatMap((link) => candidateSourcePaths(link))
|
||||
);
|
||||
return pages.filter(
|
||||
(page) => !linked.has(page) && !UNLISTED_PAGES.some((pattern) => pattern.test(page))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates `llms.txt`. Both broken links and unlisted pages fail the build.
|
||||
*/
|
||||
async function main() {
|
||||
const contents = await fs.readFile(LLMS_TXT_PATH, 'utf-8');
|
||||
const links = extractLinks(contents);
|
||||
const pages = await findAllPages();
|
||||
|
||||
const brokenLinks = await findBrokenLinks(links);
|
||||
const unlistedPages = findUnlistedPages(links, pages);
|
||||
|
||||
if (unlistedPages.length) {
|
||||
console.error(
|
||||
`\nERROR: ${unlistedPages.length} page(s) are not listed in llms.txt. Add them, or add ` +
|
||||
`them to UNLISTED_PAGES in this script if they are intentionally omitted:`
|
||||
);
|
||||
for (const page of unlistedPages) {
|
||||
console.error(` - ${page}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (brokenLinks.length) {
|
||||
console.error(`\nERROR: ${brokenLinks.length} link(s) in llms.txt do not resolve:`);
|
||||
for (const error of brokenLinks) {
|
||||
console.error(` - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (brokenLinks.length || unlistedPages.length) {
|
||||
throw new Error(
|
||||
`llms.txt has ${brokenLinks.length} broken link(s) and is missing ` +
|
||||
`${unlistedPages.length} page(s)`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`\nllms.txt is valid: ${links.length} link(s) checked, ${pages.length} page(s) found`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = main;
|
||||
@@ -1,101 +0,0 @@
|
||||
// for simplicity this file is not transpiled and is run directly via an npm script
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable promise/prefer-await-to-then */
|
||||
|
||||
import {realpathSync} from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
import {AppiumConfigJsonSchema} from '@appium/schema';
|
||||
import {fs} from '@appium/support';
|
||||
|
||||
const DOC_PATH = path.resolve(import.meta.dirname, '..', 'en', 'cli', 'args.md');
|
||||
|
||||
const START_TAG = `<!-- AUTOGEN-START -->`;
|
||||
const STOP_TAG = `<!-- AUTOGEN-STOP -->`;
|
||||
|
||||
/**
|
||||
* Regenerates the CLI arguments table in docs.
|
||||
*/
|
||||
async function main() {
|
||||
console.log(`Gathering current cli doc file contents...`);
|
||||
const [preStart, postEnd] = await getDocParts();
|
||||
console.log(`Generating new cli arg table...`);
|
||||
const argTable = getCliArgTable();
|
||||
console.log(`Writing new doc file...`);
|
||||
await writeNewDoc(preStart, argTable, postEnd);
|
||||
console.log(`...done!`);
|
||||
}
|
||||
|
||||
async function getDocParts() {
|
||||
if (!(await fs.exists(DOC_PATH))) {
|
||||
throw new Error(`Cannot find args.md at ${DOC_PATH}`);
|
||||
}
|
||||
|
||||
const curContents = await fs.readFile(DOC_PATH, 'utf-8');
|
||||
|
||||
// attempt to verify the start/stop tags
|
||||
const [preStart, postStart] = curContents.split(START_TAG);
|
||||
if (!postStart) {
|
||||
throw new Error(`Could not find autogen start tag '${START_TAG}'. Make sure it's in place!`);
|
||||
}
|
||||
const [, postEnd] = postStart.split(STOP_TAG);
|
||||
if (!postEnd) {
|
||||
throw new Error(`Could not find autogen stop tag '${STOP_TAG}'. Make sure it's in place!`);
|
||||
}
|
||||
|
||||
return [preStart, postEnd];
|
||||
}
|
||||
|
||||
function getCliArgTable() {
|
||||
const pad = ' '.repeat(30);
|
||||
const header = `|Argument${pad}|Description|Type|Default|Aliases|\n` + `|--|--|--|--|--|`;
|
||||
const serverProps = AppiumConfigJsonSchema.properties.server.properties;
|
||||
const args = Object.keys(serverProps).map((arg) => {
|
||||
let {appiumCliAliases = [], description, type, items, maximum, minimum} = serverProps[arg];
|
||||
let def = serverProps[arg].default;
|
||||
let enums = serverProps[arg].enum;
|
||||
if (type === 'array' || type === 'object' || (type === 'string' && def === '')) {
|
||||
def = JSON.stringify(def);
|
||||
}
|
||||
if (type === 'array' && items.type) {
|
||||
type = `array<${items.type}>`;
|
||||
}
|
||||
if (type === 'integer' && typeof minimum !== 'undefined' && typeof maximum !== 'undefined') {
|
||||
description = `${description} (Value must be between \`${minimum}\` and \`${maximum}\`)`;
|
||||
}
|
||||
if (enums) {
|
||||
const enumsFormatted = enums.map((e) => `\`${e}\``).join(', ');
|
||||
description = `${description} (Value must be one of: ${enumsFormatted})`;
|
||||
}
|
||||
return {
|
||||
arg: `--${arg}`,
|
||||
aliases: appiumCliAliases.map((a) => `-${a}`),
|
||||
type,
|
||||
def,
|
||||
description,
|
||||
};
|
||||
});
|
||||
const body = args
|
||||
.map(({arg, aliases, type, def, description}) => {
|
||||
const aliasStr = aliases.map((a) => `\`${a}\``).join(', ');
|
||||
def = typeof def !== 'undefined' ? `\`${def}\`` : '';
|
||||
return `|\`${arg}\`|${description}|${type}|${def}|${aliasStr}|`;
|
||||
})
|
||||
.join('\n');
|
||||
return `${header}\n${body}`;
|
||||
}
|
||||
|
||||
async function writeNewDoc(preStart, argTable, postEnd) {
|
||||
const newContents = `${preStart}${START_TAG}\n${argTable}\n${STOP_TAG}${postEnd}`;
|
||||
await fs.writeFile(DOC_PATH, newContents);
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) {
|
||||
main().catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export default main;
|
||||
@@ -48,7 +48,7 @@ cd appium
|
||||
|
||||
!!! info
|
||||
|
||||
如果您是 VS Code 用户,您可以使用 [Runme](https://runme.dev/api/runme?repository=https%3A%2F%2Fgithub.com%2Fappium%2Fappium.git&fileToOpen=packages%2Fappium%2Fdocs%2Fen%2Fcontributing%2Findex.md) 轻松地查看项目。
|
||||
If you are VS Code user, you can easily check out the project using [Runme](https://runme.dev/).
|
||||
|
||||
安装依赖:
|
||||
|
||||
@@ -95,6 +95,27 @@ export APPIUM_WORKSPACE=@appium/base-driver
|
||||
npm run test:unit -w $APPIUM_WORKSPACE
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
#### JSDoc
|
||||
|
||||
Add concise JSDoc to all public class methods and exported functions.
|
||||
|
||||
#### Module Ordering
|
||||
|
||||
Place exported functions and classes before non-exported functions and classes in each module.
|
||||
This rule does not apply to constants or variables.
|
||||
|
||||
#### Class Member Ordering
|
||||
|
||||
When adding or moving class methods, follow these conventions:
|
||||
|
||||
- Order methods by visibility: public → protected → private. Methods without an explicit visibility
|
||||
modifier belong to the public group.
|
||||
- Place private helper methods at the end of the class.
|
||||
- Preserve existing order within each visibility group.
|
||||
- Before finishing, check the ordering of methods you added or moved.
|
||||
|
||||
### 文档
|
||||
|
||||
此项目的文档位于项目[仓库本身](https://github.com/appium/appium/tree/master/packages/appium/docs)中。
|
||||
|
||||
@@ -46,6 +46,33 @@ npx appium-docs init
|
||||
|
||||
请参考 [MkDocs 文档](https://www.mkdocs.org/user-guide/writing-your-docs/) 来了解如何组织与构建你的文档结构。
|
||||
|
||||
### 帮助LLMs 使用您的文档
|
||||
|
||||
The [`/llms.txt` convention](https://llmstxt.org/) is a Markdown file at the root of a
|
||||
documentation site, listing its most useful pages so that large language models can navigate the
|
||||
site without crawling it. Appium's own documentation provides one at
|
||||
[appium.io/docs/en/latest/llms.txt](https://appium.io/docs/en/latest/llms.txt), and you may want to
|
||||
do the same for your extension.
|
||||
|
||||
MkDocs copies any non-Markdown file in your `docs` directory to the built site as-is, so all you
|
||||
need to do is write an `llms.txt` and place it alongside your Markdown files. No plugin or build
|
||||
configuration is required.
|
||||
|
||||
The value of this file lies in the parts a crawler could not have worked out on its own, so it is
|
||||
worth writing by hand rather than generating it from your navigation:
|
||||
|
||||
- A short description of what your extension automates, and which platforms it targets
|
||||
- The handful of things models reliably get wrong about your extension - removed capabilities,
|
||||
renamed commands, patterns from an older major version that no longer apply
|
||||
- Links to the pages that matter, with a description of each
|
||||
|
||||
Because links in `llms.txt` are read out of context, they must be absolute URLs. If you use Mike
|
||||
for versioning, point them at your version alias (for example `latest`) rather than at a pinned
|
||||
version, so that the file does not go stale. Note that a hand-written file can drift as pages are
|
||||
renamed; Appium validates its own with a
|
||||
[script](https://github.com/appium/appium/blob/master/packages/appium/docs/scripts/check-llmstxt.js)
|
||||
that runs in CI, which you are welcome to adapt.
|
||||
|
||||
### 构建文档
|
||||
|
||||
在这个阶段,你可以使用 `appium-docs` CLI 工具。 运行这个工具时不传任何参数,可以看到完整的帮助信息,以及所有可用的子命令与参数。 下面是几个常见用法示例:
|
||||
|
||||
@@ -80,8 +80,9 @@ The required subfields are:
|
||||
capability to tell Appium to use _your_ driver.
|
||||
- `platformNames`: this is an array of one or more platform names considered valid for your driver.
|
||||
When a user sends in the `platformName` capability to start a session, it must be included in
|
||||
this list for your driver to handle the session. Known platform name strings include: `iOS`,
|
||||
`tvOS`, `macOS`, `Windows`, `Android`.
|
||||
this list for your driver to handle the session. These names should generally include the
|
||||
operating systems your driver is targeting - for example, a driver targeting iOS would include
|
||||
`iOS`, while a driver targeting the Chrome browser on Windows would include `Windows`.
|
||||
- `mainClass`: this is a named export (in CommonJS style) from your `main` field. It must be a
|
||||
class which extends Appium's `BaseDriver` (see below).
|
||||
|
||||
@@ -283,8 +284,8 @@ You handle WebDriver commands by implementing functions in your driver class. Ea
|
||||
WebDriver Protocol, plus the various Appium extensions, has a corresponding function that you
|
||||
implement if you want to support that command in your driver. The best way to see which commands
|
||||
Appium supports and which method you need to implement for each command is to look at Appium's
|
||||
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
|
||||
Each route object in this file tells you the command name as well as the parameters you'd expect to
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes).
|
||||
Each route object in this directory's files tells you the command name as well as the parameters you'd expect to
|
||||
receive for that command.
|
||||
|
||||
Let's take this block for example:
|
||||
@@ -327,7 +328,7 @@ Websockets. Implementing handlers for BiDi commands works just the same as imple
|
||||
for WebDriver classic commands (described in the previous section). You simply define a method on
|
||||
your driver of the appropriate name, and it will be called when the BiDi command is requested by
|
||||
the client. To see which specific names you should use for BiDi commands, have a look at
|
||||
[bidi-commands.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.js)
|
||||
[bidi-commands.ts](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.ts)
|
||||
|
||||
You are not limited to BiDi commands that are defined in the official BiDi specification. If you
|
||||
wish to define new commands, you may do so; you just need to tell Appium about them! See
|
||||
|
||||
@@ -1,51 +1,29 @@
|
||||
---
|
||||
title: Building Plugins
|
||||
title: 开发插件
|
||||
---
|
||||
|
||||
This is a high-level guide for developing Appium plugins, which is not something most Appium users
|
||||
need to know or care about. If you are not familiar with Appium plugins yet from a user
|
||||
perspective, check out the [list of plugins](../ecosystem/plugins.md) to play around with some and get
|
||||
an idea of the sorts of things that plugins can do. Plugins are a powerful system for augmenting
|
||||
Appium's functionality or changing the way Appium works. They can be distributed to other Appium
|
||||
users and can extend Appium's ecosystem in all kinds of interesting ways! (There is also
|
||||
a significant amount of overlap here with developing Appium drivers, so you may also want to check
|
||||
out the [building drivers](./build-drivers.md) guide for further inspiration.)
|
||||
这是一份开发 Appium 插件的高层指南——大多数 Appium 用户并不需要知道或关心这些内容。如果你还不太熟悉面向用户视角的 Appium 插件,可以先看看[插件列表](../ecosystem/plugins.md),动手试用几个,
|
||||
了解插件大致能做哪些事情。插件是一套强大的机制,用来增强 Appium 的功能或改变 Appium 的运行方式。它们可以分发给其他 Appium 用户,并以各种有趣的方式扩展 Appium 的生态!(此外,开发插件与开发Appium 驱动有很大重叠,所以你也不妨看看[开发驱动](./build-drivers.md)指南获取更多灵感。)
|
||||
|
||||
## Before you create your plugin
|
||||
## 创建插件之前
|
||||
|
||||
Before creating your plugin, it's good to have a general idea of what you want your plugin to
|
||||
accomplish and whether it will be possible to implement it given the restrictions of the Appium
|
||||
platform. Reading through this guide will help you understand what's possible. In general, Appium's
|
||||
plugin system is extremely powerful and no attempts have been made to artificially limit what's
|
||||
possible with them (which is a main reason that all plugins are opt-in by the system administrator
|
||||
responsible for starting the Appium server---plugins are powerful and should only be used when
|
||||
explicitly trusted!).
|
||||
在创建插件之前,最好对你希望插件完成什么、以及在 Appium 平台的限制下是否可能实现有一个大致的判断。通读本指南能帮你理解哪些事情是可行的。总体而言,Appium 的插件系统非常强大,我们并没有人为地去限制插件能做什么(这也是所有插件都必须由负责启动 Appium 服务器的系统管理员显式启用的主要原因——插件很强大,只应在你明确信任它时才使用!)
|
||||
|
||||
## Other plugins to reference
|
||||
## 可供参考的其他插件
|
||||
|
||||
There are a wide variety of open source Appium plugins available for perusal. It's definitely
|
||||
recommended to explore the code for some other plugins before embarking on writing your own. The
|
||||
Appium team maintains a set of official plugins in the Appium GitHub
|
||||
repo. Links to other open source plugins can be found in the
|
||||
[Plugin list](../ecosystem/plugins.md)
|
||||
有大量开源的 Appium 插件可供查阅。我们强烈建议在动手写自己的插件之前,先看看其他插件的代码。 Appium 团队在 [Appium 的 GitHub 仓库](https://github.com/appium/appium)中维护了一套官方插件。其他开源插件的链接可以在[插件列表](../ecosystem/plugins.md)中找到。
|
||||
|
||||
## Basic requirements for plugins
|
||||
## 插件的基本要求
|
||||
|
||||
These are the things your plugin _must_ do (or be), if you want it to be a valid Appium plugin.
|
||||
如果你想让某个东西成为一个合法的 Appium 插件,以下是它_必须_做到(或满足)的事情。
|
||||
|
||||
### Node.js package with Appium extension metadata
|
||||
### 带 Appium 扩展元数据的 Node.js 包
|
||||
|
||||
All Appium plugins are fundamentally Node.js packages, and therefore must have a valid
|
||||
`package.json`. Your driver is not _limited_ to Node.js, but it must provide an adapter written in
|
||||
Node.js so it can be loaded by Appium.
|
||||
所有 Appium 插件本质上都是 Node.js 包,因此必须有一个合法的 `package.json`。你的插件并不_局限_于用 Node.js 编写,但它必须提供一个用 Node.js 编写的适配器,以便 Appium 能够加载它。
|
||||
|
||||
Your `package.json` must include `appium` as a `peerDependency`. The requirements for the
|
||||
dependency versions should be as loose as possible (unless you happen to know your plugin will only
|
||||
work with certain versions of Appium). For Appium 2, for example, this would look something like
|
||||
`^2.0.0`, declaring that your plugin works with any version of Appium that starts with 2.x.
|
||||
你的 `package.json` 必须将 `appium` 声明为 `peerDependency`。对依赖版本的要求应尽量宽松(除非你明确知道你的插件只能与某些特定版本的 Appium 配合工作)。以 Appium 2 为例,写成 `^2.0.0` 即可,表示你的插件兼容所有 2.x 版本的 Appium。
|
||||
|
||||
Your `package.json` must contain an `appium` field, like this (we call this the 'Appium extension
|
||||
metadata'):
|
||||
你的 `package.json` 必须包含一个 `appium` 字段,如下所示(我们称之为"Appium 扩展元数据"):
|
||||
|
||||
````
|
||||
```json
|
||||
@@ -60,19 +38,14 @@ metadata'):
|
||||
```
|
||||
````
|
||||
|
||||
The required subfields are:
|
||||
必需的子字段如下:
|
||||
|
||||
- `pluginName`: this should be a short name for your plugin.
|
||||
- `mainClass`: this is a named export (in CommonJS style) from your `main` field. It must be a
|
||||
class which extends Appium's `BasePlugin` (see below).
|
||||
- `pluginName`:你的插件的简短名称。
|
||||
- `mainClass`:从 `main` 字段具名导出(CommonJS 风格)的类。它必须是继承自 Appium 的 `BasePlugin` 的类(见下文)。
|
||||
|
||||
### Extend Appium's `BasePlugin` class
|
||||
### 继承 Appium 的 `BasePlugin` 类
|
||||
|
||||
Ultimately, your plugin is much easier to write because most of the hard work of defining patterns
|
||||
for overriding commands is done for you. This is
|
||||
all encoded up as a class which Appium exports for you to use, called `BasePlugin`. It is exported
|
||||
from `appium/plugin`, so you can use one of these styles to import it and create your _own_ class
|
||||
that extends it:
|
||||
归根结底,你的插件之所以好写,是因为定义"重写命令"的各种模式所需的繁重工作已经替你完成了。这一切都封装在一个 Appium 导出的类中,名为 `BasePlugin`。它从 `appium/plugin` 导出,你可以用下面任一方式导入它,并创建继承它的_你自己的_类:
|
||||
|
||||
```js
|
||||
import {BasePlugin} from 'appium/plugin';
|
||||
@@ -83,48 +56,38 @@ export class MyPlugin extends BasePlugin {
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
In all the code samples below, whenever we reference an example method, it is assumed
|
||||
that it is defined _within_ the class, though this is not explicitly written, for the sake of
|
||||
clarity and space.
|
||||
在下面所有代码示例中,凡是给出示例方法的地方,都默认它是定义在类_内部_的;为了简洁和省篇幅,文中没有显式写出这一点。
|
||||
```
|
||||
|
||||
### Make your plugin available
|
||||
### 让你的插件可用
|
||||
|
||||
That's basically it! With a Node.js package exporting a plugin class and with correct Appium
|
||||
extension metadata, you've got yourself an Appium plugin! Now it doesn't _do_ anything, but you can
|
||||
load it up in Appium, activate it, etc...
|
||||
基本就是这样!一个导出插件类的 Node.js 包,再加上正确的 Appium 扩展元数据,你就拥有了一个 Appium 插件!虽然它还什么都不_做_,但你已经可以在 Appium 中加载它、启用它了……
|
||||
|
||||
To make it available to users, you could publish it via NPM. When you do so, your plugin will be
|
||||
installable via the Appium CLI:
|
||||
要让其他用户能用你的插件,你可以通过 NPM 发布它。发布之后,用户就可以通过 Appium CLI 安装它:
|
||||
|
||||
```
|
||||
appium plugin install --source=npm <plugin-package-on-npm>
|
||||
```
|
||||
|
||||
It's a good idea to test your plugin first, of course. One way to see how it works within Appium is
|
||||
to install it locally first:
|
||||
当然,最好先测试你的插件。观察它在 Appium 中如何工作的一个方式,是先以本地方式安装它:
|
||||
|
||||
```
|
||||
appium plugin install --source=local /path/to/your/plugin
|
||||
```
|
||||
|
||||
And of course, plugins must be "activated" during Appium server start, so make sure you direct your
|
||||
users to do so:
|
||||
另外别忘了,启动 Appium 服务器之前,要先启用插件,所以请务必告知用户这一点:
|
||||
|
||||
```
|
||||
appium --use-plugins=plugin-name
|
||||
```
|
||||
|
||||
### Developing your plugin
|
||||
### 插件的开发流程
|
||||
|
||||
How you develop your plugin is up to you. It is convenient, however, to run it from within Appium
|
||||
without having to do lots of publishing and installing. The most straightforward way to do this is
|
||||
to include the most recent version of Appium as a `devDependency` (although its being already
|
||||
included as a `peerDependency` is sufficient in newer versions of NPM), and then also your own plugin,
|
||||
like this:
|
||||
如何开发你的插件完全由你决定。不过,直接在 Appium 内部运行会很方便,无需进行繁琐的发布与安装操作。最直接的做法是把最新版本的 Appium 作为 `devDependency`(在较新版本的 NPM 中,它已经作为
|
||||
`peerDependency` 被引入,也足够了),同时把你的插件也作为依赖引入,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -137,26 +100,19 @@ like this:
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can run Appium locally (`npm exec appium` or `npx appium`), and because your plugin is
|
||||
listed as a dependency alongside it, it will be automatically "installed" and available. You can
|
||||
design your e2e tests this way, or if you're writing them in Node.js, you can simply import
|
||||
Appium's start server methods to handle starting and stopping the Appium server in Node.
|
||||
这样,你就可以在本地运行 Appium(`npm exec appium` 或 `npx appium`),并且由于你的插件与它并列列为依赖,它会被自动"安装"并可用。你可以按这种方式设计你的 e2e 测试;如果用 Node.js 编写测试,你还可以直接导入 Appium 的启动服务方法,在 Node 中启停 Appium 服务器。
|
||||
|
||||
Of course, you can always install it locally as described above as well.
|
||||
当然,你也随时可以按上文所说的方式以本地源安装。
|
||||
|
||||
Anytime you make changes to your plugin code, you'll need to restart the Appium server to make sure
|
||||
it picks up the latest code. As with drivers, you can set the `APPIUM_RELOAD_EXTENSIONS`
|
||||
environment variable if you wish Appium to try to re-require your plugin module when a new session
|
||||
starts.
|
||||
每次修改插件代码后,你都需要重启 Appium 服务器,才能确保它加载最新的代码。与驱动一样,如果你希望Appium 在新会话启动时尝试重新 require 你的插件模块,可以设置`APPIUM_RELOAD_EXTENSIONS`环境变量。
|
||||
|
||||
## Standard plugin implementation ideas
|
||||
## 插件实现的标准思路
|
||||
|
||||
These are things you will probably find yourself wanting to do when creating a plugin.
|
||||
以下是创建插件时你多半会想做的事情。
|
||||
|
||||
### Set up state in a constructor
|
||||
### 在构造函数中初始化状态
|
||||
|
||||
If you define your own constructor, you'll need to call `super` to make sure all the standard state
|
||||
is set up correctly:
|
||||
如果你定义了自己的构造函数,需要调用 `super`,确保所有标准状态被正确初始化:
|
||||
|
||||
```js
|
||||
constructor(...args) {
|
||||
@@ -165,28 +121,20 @@ constructor(...args) {
|
||||
}
|
||||
```
|
||||
|
||||
The `args` parameter here is the object containing all the CLI args used to start the Appium
|
||||
server.
|
||||
这里的 `args` 参数是包含启动 Appium 服务器所用全部 CLI 参数的对象。
|
||||
|
||||
### Intercept and handle specific Appium commands
|
||||
### 拦截并处理特定的 Appium 命令
|
||||
|
||||
This is the most normal behavior for Appium plugins -- to modify or replace the execution of one or
|
||||
more commands that would normally be handled by the active driver. To override the default command
|
||||
handling, you need to implement `async` methods in your class with the same name as the Appium
|
||||
commands to be handled (just exactly how drivers themselves are
|
||||
implemented). Curious what command names there
|
||||
are? They are defined in the Appium base driver's
|
||||
[routes.js](https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js) file,
|
||||
and of course you can add more as defined in the next section.
|
||||
这是 Appium 插件最常见的用法——修改或替换本应由当前驱动处理的一个或多个命令的执行。要覆盖默认的命令处理逻辑,你需要在类中实现与所要处理的 Appium 命令同名的 `async` 方法(和[驱动本身的实现方式](./build-drivers.md#implement-webdriver-classic-commands)完全一致)。想知道都有哪些命令名?它们定义在 Appium base driver 的
|
||||
[routes.js](https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js) 文件中,当然你还可以按下一节介绍的方式添加更多命令。
|
||||
|
||||
Each command method is sent the following arguments:
|
||||
每个命令方法都会收到以下参数:
|
||||
|
||||
1. `next`: This is a reference to an `async` function which encapsulates the chain of behaviors which would take place if this plugin were not handling the command. You can choose to call the next behavior in the chain at any point in your logic (by making sure to include `await next()` somewhere), or not. If you don't, it means the default behavior (or any plugins registered after this one) won't be run.
|
||||
2. `driver`: This is the object representing the driver handling the current session. You have access to it for any work you need to do, for example calling other driver methods, checking capabilities or settings, etc...
|
||||
3. `...args`: A spread array with any arguments that have been applied to the command by the user.
|
||||
1. `next`:指向一个 `async` 函数的引用,该函数封装了一系列行为链——如果当前插件未处理该命令,这些行为便会随之发生。你可以在逻辑中的任意位置选择调用链中的下一个行为(只需确保在某处包含 `await next()` 即可),也可以选择不调用。如果不调用,就意味着默认行为(或注册在该插件之后的其他插件)不会被执行。
|
||||
2. `driver`:该对象代表负责处理当前会话的驱动程序。您可以根据需要使用它来执行各项操作,例如调用驱动程序的其他方法、检查功能配置或设置等。
|
||||
3. `...args`:一个包含用户在调用命令时所传入的所有参数的数组(通过展开语法获取)。
|
||||
|
||||
For example, if we wanted to override the `setUrl` command to simply add some extra logging on top,
|
||||
we would implement as follows:
|
||||
例如,如果我们想覆盖 `setUrl` 命令,只在原有行为上追加一些日志,可以这样实现:
|
||||
|
||||
```js
|
||||
async setUrl(next, driver, url) {
|
||||
@@ -198,20 +146,16 @@ async setUrl(next, driver, url) {
|
||||
}
|
||||
```
|
||||
|
||||
### Intercept and handle _all_ Appium commands
|
||||
### 拦截并处理_所有_ Appium 命令
|
||||
|
||||
You might find yourself in a position where you want to handle _all_ commands, in order to inspect
|
||||
payloads to determine whether or not to act in some way. If so, you can implement `async handle`,
|
||||
and any command that is not handled by one of your named methods will be handled by this method
|
||||
instead. It takes the following parameters (with all the same semantics as above):
|
||||
你可能会遇到这样一种情况:需要处理_所有_命令,以便检查其中的有效载荷,从而决定是否要采取某种行动。这种情况下,你可以实现 `async handle`,任何没有被你具名方法处理的命令都会交给这个方法处理。它接收以下参数(语义与上文完全一致):
|
||||
|
||||
1. `next`
|
||||
2. `driver`
|
||||
3. `cmdName` - string representing the command being run
|
||||
3. `cmdName` —— 表示正在运行的命令的字符串
|
||||
4. `...args`
|
||||
|
||||
For example, let's say we want to log timing for all Appium commands as part of a plugin. We could
|
||||
do this by implementing `handle` in our plugin class as follows:
|
||||
例如我们想在一个插件中记录所有 Appium 命令的耗时。我们可以这样在插件类中实现 `handle`:
|
||||
|
||||
```js
|
||||
async handle(next, driver, cmdName, ...args) {
|
||||
@@ -226,31 +170,17 @@ async handle(next, driver, cmdName, ...args) {
|
||||
}
|
||||
```
|
||||
|
||||
### Working with driver proxies
|
||||
### 与驱动代理打交道
|
||||
|
||||
There is a bit of a gotcha with handling Appium commands. Appium drivers have the ability to turn
|
||||
on a special 'proxy' mode, wherein the Appium server process takes a look at incoming URLs, and
|
||||
decides whether to forward them on to some upstream WebDriver server. It could happen that
|
||||
a command which a plugin wants to handle is designated as a command which is being proxied to an
|
||||
upstream server. In this case, we might run into a problem, because the plugin never gets a chance to
|
||||
handle that command! The way Appium handles this is as follows:
|
||||
在处理 Appium 命令时有一个坑。 Appium 驱动可以开启一种特殊的"代理"模式:在该模式下,Appium 服务器进程会查看进来的 URL,并决定是否把它们转发给某个上游 WebDriver 服务器。可能出现这样的情况:插件想要处理的某个命令,恰好属于被代理到上游服务器的命令。这时就会有问题——插件根本得不到处理该命令的机会! Appium 对这种情况的处理方式如下:
|
||||
|
||||
1. When a command comes in, before deciding whether to proxy the command, the main protocol handler
|
||||
checks to see whether a plugin would handle the command.
|
||||
2. If a plugin would _not_ handle the command--all proceeds as normal, and the request is either
|
||||
proxied or not, based on the driver's proxy mode.
|
||||
3. If a plugin _would_ handle the command, then the proxy behavior is skipped for the time being,
|
||||
and wrapped up as the `next` function sent to the plugin. So if you have a command handler in
|
||||
your plugin, and you want to be sure that the default driver proxying _does_ take place, then
|
||||
simply `await next()` instead of (or in addition to) whatever your plugin handler is doing.
|
||||
1. 当一个命令进来时,主协议处理器在决定是否代理该命令之前,会先检查是否有插件要处理它。
|
||||
2. 如果_没有_插件要处理该命令——一切照旧,请求是否被代理取决于驱动的代理模式。
|
||||
3. 如果_有_插件要处理该命令,那么代理行为此时会被跳过,并被包装成传给插件的 `next` 函数。所以,如果你的插件里有该命令的处理器,又希望确保驱动的默认代理行为_确实_发生,只需在你插件处理器所做的事情之外(或之中)`await next()` 即可。
|
||||
|
||||
### Throw WebDriver-specific errors
|
||||
### 抛出 WebDriver 专用错误
|
||||
|
||||
The WebDriver spec defines a set of error
|
||||
codes to accompany command responses if an
|
||||
error occurred. Appium has created error classes for each of these codes, so you can throw the
|
||||
appropriate error from inside a command, and it will do the right thing in terms of the protocol
|
||||
response to the user. To get access to these error classes, import them from `appium/driver`:
|
||||
WebDriver 规范定义了一组错误代码,以便在发生错误时随命令响应一并返回。 Appium 为每个错误码都创建了对应的错误类,因此你可以在命令内部抛出相应的错误,Appium 会按照协议规范把它正确地响应给用户。要获得这些错误类,从 `appium/driver` 导入即可:
|
||||
|
||||
```js
|
||||
import {errors} from 'appium/driver';
|
||||
@@ -258,39 +188,30 @@ import {errors} from 'appium/driver';
|
||||
throw new errors.NoSuchElementError();
|
||||
```
|
||||
|
||||
### Log messages to the Appium log
|
||||
### 向 Appium 日志写消息
|
||||
|
||||
You can always use `console.log`, of course, but Appium provides a nice logger for you as
|
||||
`this.logger` (it has `.info`, `.debug`, `.log`, `.warn`, `.error` methods on it for differing log
|
||||
levels). If you want to create an Appium logger outside of a plugin context (say in a script or
|
||||
helper file), you can always construct your own too:
|
||||
你当然可以随时用 `console.log`,但 Appium 为你提供了一个更贴心的日志器 `this.logger`(它有 `.info`、`.debug`、`.log`、`.warn`、`.error` 等不同日志级别的方法)。如果你想在插件上下文之外(比如脚本或辅助文件中)创建 Appium 日志器,也可以自行构造:
|
||||
|
||||
```js
|
||||
import {logging} from 'appium/support';
|
||||
const log = logging.getLogger('MyPlugin');
|
||||
```
|
||||
|
||||
## Further possibilities for Appium plugins
|
||||
## Appium 插件的进阶能力
|
||||
|
||||
These are things your plugin _can_ do to take advantage of extra plugin features or do its job more
|
||||
conveniently.
|
||||
这些是你的插件_可以_实现的功能,旨在利用插件的额外特性,或让插件能更便捷地完成其任务。
|
||||
|
||||
### Add a schema for custom command line arguments
|
||||
### 为自定义命令行参数添加 schema
|
||||
|
||||
You can add custom CLI args if you want your plugin to receive data from the command line when the
|
||||
Appium server is started (for example, ports that a server administrator should set that should not
|
||||
be passed in as capabilities).
|
||||
如果你希望插件在 Appium 服务器启动时从命令行接收数据,就可以添加自定义 CLI 参数(例如由服务器
|
||||
管理员设定的端口,这类参数不适合通过能力传入)。
|
||||
|
||||
This works largely the same for plugins as it does for drivers, so for more details have a look at
|
||||
the equivalent section in the building drivers
|
||||
doc.
|
||||
插件的这部分机制与驱动基本一致,更多细节请参见[开发驱动文档](./build-drivers.md#add-a-schema-for-custom-command-line-arguments)中的对应章节。
|
||||
|
||||
The only difference is that to construct the CLI argument name, you prefix it with
|
||||
`--plugin-<name>`. So for example, if you have a plugin named `pluggo` and a CLI arg defined with
|
||||
the name `electro-port`, you can set it when starting Appium via `--plugin-pluggo-electro-port`.
|
||||
唯一的区别是:构造 CLI 参数名时,前缀要用 `--plugin-<name>`。例如,你的插件名为 `pluggo`,其中定义了一个名为 `electro-port` 的 CLI 参数,那么在启动 Appium 时可以通过
|
||||
`--plugin-pluggo-electro-port` 来设置它。
|
||||
|
||||
Setting args via a configuration file is also supported, as it is for drivers, but under the
|
||||
`plugin` field instead. For example:
|
||||
通过配置文件设置参数同样是支持的,与驱动一样,只是要写在 `plugin` 字段下。例如
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -304,13 +225,9 @@ Setting args via a configuration file is also supported, as it is for drivers, b
|
||||
}
|
||||
```
|
||||
|
||||
### Add plugin scripts
|
||||
### 添加插件脚本
|
||||
|
||||
Sometimes you might want users of your plugin to be able to run scripts outside the context of
|
||||
a session (for example, to run a script that pre-builds aspects of your plugin). To support this,
|
||||
you can add a map of script names and JS files to the `scripts` field within your Appium extension
|
||||
metadata. So let's say you've created a script in your project that lives in a `scripts` directory
|
||||
in your project, named `plugin-prebuild.js`. Then you could add a `scripts` field like this:
|
||||
有时你可能希望插件用户能够在会话上下文之外运行脚本(例如运行一个预先构建插件某些部分的脚本)。为支持这一点,你可以在 Appium 扩展元数据的 `scripts` 字段中添加一个"脚本名 → JS 文件"的映射。假设你在项目的 `scripts` 目录下创建了一个名为 `plugin-prebuild.js` 的脚本,那么可以像这样添加 `scripts` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -320,25 +237,19 @@ in your project, named `plugin-prebuild.js`. Then you could add a `scripts` fiel
|
||||
}
|
||||
```
|
||||
|
||||
Now, assuming your plugin is named `myplugin`, users of your plugin can run `appium plugin run
|
||||
myplugin prebuild`, and your script will execute.
|
||||
假设你的插件名为 `myplugin`,现在,插件用户就可以运行 `appium plugin run myplugin prebuild`,你的脚本便会执行。
|
||||
|
||||
### Add new Appium commands
|
||||
### 添加新的 Appium 命令
|
||||
|
||||
If you want to offer functionality that doesn't map to any of the existing commands supported by
|
||||
drivers, you can create new commands in one of two ways, just as is possible for drivers:
|
||||
如果您希望提供某种功能,而该功能无法对应到驱动程序所支持的现有命令中,那么您可以采用以下两种方式之一来创建新命令——这与驱动程序中的做法相同:
|
||||
|
||||
1. Extending the WebDriver protocol and creating client-side plugins to access the extensions
|
||||
2. Extending the WebDriver BiDi protocol with new modules and methods, accessed from a client via the BiDi interface
|
||||
3. Overloading the Execute Script command by defining Execute
|
||||
Methods
|
||||
1. 扩展 WebDriver 协议,并编写客户端插件来访问这些扩展
|
||||
2. 用新的模块和方法扩展 WebDriver BiDi 协议,客户端通过 BiDi 接口访问
|
||||
3. 通过定义 [Execute Method](../guides/execute-methods.md) 来重载 Execute Script 命令
|
||||
|
||||
#### Extending the HTTP Protocol
|
||||
#### 扩展 HTTP 协议
|
||||
|
||||
If you want to follow the first path, you can direct Appium to recognize new methods and add them
|
||||
to its set of allowed HTTP routes and command names. You do this by assigning the `newMethodMap`
|
||||
static variable in your driver class to an object of the same form as Appium's `routes.js` object.
|
||||
For example, here is part of the `newMethodMap` for the `FakePlugin` example driver:
|
||||
如果你想采用第一种方式,可以引导 Appium 识别新方法,并将它们添加到其允许的 HTTP 路由和命令名称集合中。具体做法是:在你的驱动类中,将静态变量 `newMethodMap` 赋值为一个对象,该对象的结构应与 Appium 中 `routes.js` 所定义的对象一致。例如,以下是 `FakePlugin` 示例驱动程序中 `newMethodMap` 的部分内容:
|
||||
|
||||
```js
|
||||
static newMethodMap = {
|
||||
@@ -356,38 +267,23 @@ static newMethodMap = {
|
||||
};
|
||||
```
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
If you're using TypeScript, static member objects like these should be defined `as const`.
|
||||
如果你使用 TypeScript,像这样的静态成员对象应当用 `as const` 来定义。
|
||||
```
|
||||
|
||||
In this example we're adding a few new routes and a total of 3 new commands. For more examples of
|
||||
how to define commands in this way, it's best to have a look through `routes.js`. Now all you need
|
||||
to do is implement the command handlers in the same way you'd implement any other Appium command.
|
||||
示例中,我们添加了几个新路由,总共新增了 3 条命令。若想查看更多以这种方式定义命令的示例,建议查阅 `routes.js` 文件。接下来,你只需按照实现其他任何 Appium 命令的方式,去实现这些命令的处理程序即可。
|
||||
|
||||
Note also the special `neverProxy` key for commands; this is generally a good idea to set to `true`
|
||||
for plugins, since your plugin might be active for a driver that is put into proxy mode but hasn't
|
||||
bothered to decline proxying for these (new and therefore unknown) commands. Setting `neverProxy`
|
||||
to `true` here will cause Appium to never proxy these routes and therefore ensure your plugin
|
||||
handles them, even if a driver is in proxy mode.
|
||||
此外,请注意针对命令的特殊 `neverProxy` 键;对于插件而言,通常建议将其设置为 `true`。这是因为,当驱动程序处于“代理模式”时,它可能并未明确拒绝代理某些命令(由于是新增的,因而未知的)。将 `neverProxy` 设置为 `true`,可确保 Appium 绝不会代理这些请求路径,从而保证它们始终由您的插件来处理——即便驱动程序正处于代理模式下,也不例外。
|
||||
|
||||
The downside of adding new commands via `newMethodMap` is that people using the standard Appium
|
||||
clients won't have nice client-side functions designed to target these endpoints. So you would need
|
||||
to create and release client-side plugins for each language you want to support (directions or
|
||||
examples can be found at the relevant client docs).
|
||||
通过 `newMethodMap` 添加新命令的不足之处在于,使用标准 Appium 客户端的用户将无法直接调用专为这些端点设计的、便捷的客户端函数。因此,针对你想要支持的每种编程语言,你都需要创建并发布相应的客户端插件(相关说明或示例可在各客户端的文档中找到)。
|
||||
|
||||
#### Extending the BiDi Protocol
|
||||
#### 扩展 BiDi 协议
|
||||
|
||||
You can also make new commands accessible via the WebDriver BiDi (WebSocket-based) protocol. BiDi
|
||||
commands come in two parts: a "module", which is basically a container or namespace, and
|
||||
a "command", which is the name of your new command.
|
||||
您还可以通过 WebDriver BiDi(基于 WebSocket 的协议)来提供新的命令。 BiDi 命令由两部分组成:“模块”(本质上是一个容器或命名空间)以及“命令”(即您所定义的新命令的名称)。
|
||||
|
||||
As with the first method, you teach Appium to recognize your new BiDi commands by adding a static
|
||||
field to your driver class, called `newBidiCommands`. It has a format similar to `newMethodMap`.
|
||||
Basically it encapsulates information about the BiDi module, BiDi command name, reference to your
|
||||
driver instance method that will handle the command, and required and optional parameters. Here's
|
||||
an example of a `newBidiCommands` as implemented on an imaginary driver:
|
||||
与第一种方法类似,你可以通过在驱动类中添加一个名为 `newBidiCommands` 的静态字段,来让 Appium 识别你新增的 BiDi 命令。该字段的格式与 `newMethodMap` 类似。它主要封装了以下信息:BiDi 模块、BiDi 命令名称、指向驱动实例中负责处理该命令的方法的引用,以及该命令所需的必选参数和可选参数。以下是在一个虚构驱动程序中实现 `newBidiCommands` 的示例:
|
||||
|
||||
```js
|
||||
static newBidiCommands = {
|
||||
@@ -406,35 +302,20 @@ static newBidiCommands = {
|
||||
};
|
||||
```
|
||||
|
||||
In this imaginary example, we have defined two new BiDi commands: `appium:video.startFramerateCapture` and
|
||||
`appium:video.stopFramerateCapture`. The first command takes a required and an optional parameter, and the
|
||||
second does not. When you have implemented the `startFrameCap` and `stopFrameCap` methods on your
|
||||
plugin class, they will be called whenever the BiDi commands are triggered by a client. The
|
||||
signatures for these methods would look as follows:
|
||||
在这个虚构的示例中,我们定义了两个新的 BiDi 命令:`appium:video.startFramerateCapture` 和 `appium:video.stopFramerateCapture`。第一个命令包含一个必选参数和一个可选参数,而第二个命令则不需要任何参数。当你在插件类中实现了 `startFrameCap` 和 `stopFrameCap` 方法后,每当客户端触发这些 BiDi 命令时,相应的方法就会被调用。这些方法的签名如下所示:
|
||||
|
||||
```js
|
||||
async startFrameCap(next: () => Promise<any>, driver: DriverClass, videoSource: string, showOnScreen: boolean): Promise<any>;
|
||||
async stopFrameCap(next: () => Promise<any>, driver: DriverClass): Promise<any>;
|
||||
```
|
||||
|
||||
As with adding or overriding existing HTTP protocol commands, these methods are injected with
|
||||
a `next` parameter and a `driver` parameter. The `driver` object represents the driver currently
|
||||
owning the session, and calling `await next()` will execute/return the behavior that would execute
|
||||
were the plugin not active (i.e., the driver's own handling of that method, or the chain of
|
||||
commands executed with the same name by other active plugins).
|
||||
与添加或重写现有的 HTTP 协议命令类似,这些方法在注入时会包含 `next` 和 `driver` 两个参数。 `driver` 对象代表当前拥有该会话的驱动程序;调用 `await next()` 则会执行(或返回)在插件未激活时本应执行的行为——即驱动程序自身对该方法的处理逻辑,或者是其他已激活插件中同名命令的执行链。
|
||||
|
||||
Note that, currently, if a driver has BiDi proxying turned on, plugins will not be able to override
|
||||
BiDi methods handled by the proxy. Also note that since these are custom BiDi commands, our module
|
||||
name should include a vendor prefix (we chose `appium:`, but you can/should choose something that
|
||||
makes sense for your extension).
|
||||
请注意,目前如果启用了 BiDi 代理,插件将无法覆盖由该代理处理的 BiDi 方法。此外,由于这些属于自定义 BiDi 命令,我们的模块名称中应包含厂商前缀(我们选择了 `appium:`,您可以而且应该,根据您的扩展功能,选择一个合适的名称)。
|
||||
|
||||
#### Overloading Execute Script
|
||||
#### 重载 Execute Script
|
||||
|
||||
An alternative to these ways of doing things is to overload a command which all WebDriver clients
|
||||
have access to already: Execute Script. Make sure to read the section on adding new
|
||||
commands in
|
||||
the Building Drivers guide to understand the way this works in general. The way it works with
|
||||
plugins is only slightly different. Let's look at an example taken from Appium's `fake-plugin`:
|
||||
除了上述做法外,还有另一种选择:重载一个所有 WebDriver 客户端均可调用的命令——“执行脚本”(Execute Script)。请务必阅读“构建驱动程序”指南中关于[添加新命令](./build-drivers.md#extend-the-existing-protocol-with-new-commands)的章节,以了解其基本运作机制。在插件场景下,其运作方式仅有细微差别。让我们来看一个取自 Appium “模拟插件”(fake-plugin)的示例:
|
||||
|
||||
```js
|
||||
static executeMethodMap = {
|
||||
@@ -453,60 +334,36 @@ async execute(next, driver, script, args) {
|
||||
}
|
||||
```
|
||||
|
||||
We have three important components shown here which make this system work, all of which are defined
|
||||
inside the plugin class:
|
||||
这里有三个让这套机制运转起来的关键组件,它们都定义在插件类内部:
|
||||
|
||||
1. The `executeMethodMap`, defined in just the same way as for drivers
|
||||
2. The implementation of the command method as defined in `executeMethodMap` (in this case,
|
||||
`plugMeIn`)
|
||||
3. The overriding/handling of the `execute` command. Just like any plugin command handlers, the
|
||||
first two arguments are `next` and `driver`, followed by the script name and args. `BasePlugin`
|
||||
implements a helper method which we can simply call with all of these arguments.
|
||||
1. `executeMethodMap`,定义方式与驱动完全相同
|
||||
2. `executeMethodMap` 中所声明命令方法的实现(本例中为 `plugMeIn`)
|
||||
3. 覆盖/处理 `execute` 命令。与其他插件命令处理器一样,前两个参数是 `next` 和 `driver`,随后是脚本名和参数。 `BasePlugin` 提供了一个辅助方法,我们可以把这些参数原样传给它调用。
|
||||
|
||||
Overriding Execute Methods from drivers works as you'd expect: if your plugin defines an Execute
|
||||
Method with the same name as that of a driver, your command (in this case `plugMeIn`) will be
|
||||
called first. You can choose to run the driver's original behaviour via `next` if you want.
|
||||
覆盖驱动定义的 Execute Method 的效果符合直觉:如果你的插件定义了与驱动同名的 Execute Method,会先调用你的命令(本例中为 `plugMeIn`)。如果需要,你可以通过 `next` 执行驱动的原有行为。
|
||||
|
||||
### Emit BiDi Events
|
||||
### 触发 BiDi 事件
|
||||
|
||||
Your plugin can emit custom BiDi events in exactly the same way as Appium
|
||||
drivers.
|
||||
你的插件触发自定义 BiDi 事件的方式,与 Appium 驱动完全一致。
|
||||
|
||||
### Build Appium Doctor checks
|
||||
### 构建 Appium Doctor 检查
|
||||
|
||||
Your users can run `appium plugin doctor <pluginName>` to run installation and health checks. Visit
|
||||
the [Building Doctor Checks](./build-doctor-checks.md) guide for more information on this
|
||||
capability.
|
||||
你的用户可以运行 `appium plugin doctor <pluginName>` 来执行安装与健康检查。关于这一能力的更多信息,请访问[构建 Doctor 检查](./build-doctor-checks.md)指南。
|
||||
|
||||
### Update the Appium server object
|
||||
### 更新 Appium 服务器对象
|
||||
|
||||
You probably don't normally need to update the Appium server object (which is an
|
||||
[Express](https://expressjs.com/) server having already been
|
||||
[configured](https://github.com/appium/appium/blob/master/packages/base-driver/lib/express/server.js)
|
||||
in a variety of ways). But, for example, you could add new routes to the server to support your
|
||||
plugin's requirements. To update the server you must implement the `static async updateServer`
|
||||
method in your class. This method takes three parameters:
|
||||
[configured](https://github.com/appium/appium/blob/master/packages/base-driver/lib/express/server.ts)
|
||||
in a variety of ways). 但你可以为服务器添加新的路由来支撑你的插件的需求。要更新服务器,你必须在类中实现 `static async updateServer` 方法。该方法接收三个参数:
|
||||
|
||||
- `expressApp`: the Express app object
|
||||
- `httpServer`: the Node HTTP server object
|
||||
- `cliArgs`: a map of the CLI args used to start the Appium server
|
||||
- `expressApp`:Express app 对象
|
||||
- `httpServer`:Node HTTP 服务器对象
|
||||
- `cliArgs`:启动 Appium 服务器所用 CLI 参数的映射
|
||||
|
||||
You can do whatever you want with them inside the `updateServer` method. You might want to
|
||||
reference how these objects are created and worked with in the BaseDriver code, so that you know
|
||||
you're not undoing or overriding anything standard and important. But if you insist, you can, with
|
||||
results you'll need to test! Warning: this should be considered an advanced feature and requires
|
||||
knowledge of Express, as well as the care not to do anything that could affect the operation of
|
||||
other parts of the Appium server!
|
||||
在 `updateServer` 方法中,你可以随心所欲地处理这些对象。建议参考 `BaseDriver` 代码中创建及使用这些对象的方式,以确保不会破坏或覆盖任何标准且关键的功能。当然,如果你执意要这样做也是可以的,但务必进行充分测试以验证结果!警告:这属于高级功能,需要你具备 Express 相关知识;同时请务必小心,确保所做的修改不会影响 Appium 服务器其他部分的正常运行!
|
||||
|
||||
`updateServer` runs _after_ Appium's own routes are registered, so `expressApp.use(...)` here
|
||||
won't see requests to paths Appium already owns — Express matches middleware and routes in
|
||||
registration order. For middleware that must see requests to those routes too (e.g. logging or
|
||||
auth), use `httpServer.frontRouter` instead: Appium mounts this
|
||||
[Router](https://expressjs.com/en/5x/api/router/) before any route is registered, so anything
|
||||
attached to it — even from inside `updateServer` — still runs ahead of route matching. Note this
|
||||
is still _after_ Appium's own baseline middleware (CORS, WebSocket upgrade handling, body
|
||||
parsing, etc.), so it won't see requests Appium itself has already rejected, e.g. a CORS
|
||||
preflight failure or a body that failed to parse:
|
||||
`updateServer` 在 Appium 自身的路由注册完成后执行,因此此处通过 `expressApp.use(...)` 添加的中间件无法捕获那些已被 Appium 占用的路径请求——Express 是按照注册顺序来匹配中间件和路由的。如果某些中间件必须能够处理这些路径的请求(例如日志记录或身份验证),请改用 `httpServer.frontRouter`:Appium 会在所有路由注册之前挂载该[路由](https://expressjs.com/en/5x/api/router/),因此挂载到它上面的任何内容(即使是在 `updateServer` 内部添加的)也都会在路由匹配之前执行。需要注意的是,这仍然发生在 Appium 基础中间件(如 CORS 处理、WebSocket 升级处理、请求体解析等)之后;因此,如果请求已被 Appium 本身拒绝(例如 CORS 预检失败或请求体解析失败),这些中间件将无法捕获到此类请求。
|
||||
|
||||
```js
|
||||
static async updateServer(expressApp, httpServer, cliArgs) {
|
||||
@@ -517,16 +374,9 @@ static async updateServer(expressApp, httpServer, cliArgs) {
|
||||
}
|
||||
```
|
||||
|
||||
### Handle unexpected session shutdown
|
||||
### 处理会话意外关闭
|
||||
|
||||
When developing a plugin you may want to add some cleanup logic for when a session ends. You would
|
||||
naturally do this by adding a handler for `deleteSession`. This works in most cases, except when
|
||||
the session does not finish cleanly. Appium sometimes determines that a session has finished
|
||||
unexpectedly, and in these situations, Appium will look for a method called `onUnexpectedShutdown`
|
||||
in your plugin class, which will be called (passing the current session driver as the first
|
||||
parameter, and the error object representing the cause of the shutdown as the second), giving you
|
||||
an opportunity to take any steps that might be necessary to clean up from the session. For example,
|
||||
keeping in mind that the function is not `await`ed you could implement something like this:
|
||||
在开发插件时,你可能需要在会话结束时执行一些清理逻辑。通常,你可以通过为 `deleteSession` 添加处理程序来实现这一点。这种方法在大多数情况下都有效,但如果会话未能正常结束,则不适用。 Appium 有时会判定会话已异常终止;在这种情况下,Appium 会在你的插件类中查找名为 `onUnexpectedShutdown` 的方法并将其调用(该方法接收两个参数:第一个是当前的会话驱动程序,第二个是导致终止的错误对象)。这让你有机会执行必要的清理操作。考虑到该函数不会被 `await`(即不会等待其执行完成),你可以按如下方式进行实现:
|
||||
|
||||
```js
|
||||
async onUnexpectedShutdown(driver, cause) {
|
||||
@@ -538,11 +388,6 @@ async onUnexpectedShutdown(driver, cause) {
|
||||
}
|
||||
```
|
||||
|
||||
### Send messages to the driver or other plugins running on the same session
|
||||
### 向同一会话中运行的驱动程序或其他插件发送消息
|
||||
|
||||
Even though drivers and plugins don't know anything about each other, it's possible for them to
|
||||
send messages that other drivers or plugins can listen for, within the context of the same session,
|
||||
using an IPC channel. Given that the developer instructions for this feature are the same for
|
||||
drivers and plugins, please head over to the IPC development docs for
|
||||
drivers at the driver
|
||||
development guide.
|
||||
尽管驱动程序和插件彼此并不了解,但它们仍可在同一会话的上下文中,通过 IPC(进程间通信)通道发送消息,供其他驱动程序或插件接收。针对此功能的开发说明对于驱动程序和插件而言是相同的,请参阅“驱动程序开发指南”中有关[驱动程序](./build-drivers.md#send-messages-to-plugins-running-on-the-same-session) IPC 开发的文档。
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
---
|
||||
title: Appium's Config System
|
||||
title: Appium 的配置系统
|
||||
---
|
||||
|
||||
Appium includes support for [configuration files](../guides/config.md). A configuration file is
|
||||
intended to have (nearly) 1:1 parity with command-line arguments. An end user can supply Appium with
|
||||
a configuration file, CLI args, or both (the args have precedence over the config file).
|
||||
Appium 支持[配置文件](../guides/config.md)。配置文件的目标是与命令行参数做到(近乎)1:1 对应。最终用户可以为 Appium 提供配置文件、CLI 参数,或两者同时提供(参数优先级高于配置文件)。
|
||||
|
||||
This document will be a technical overview of how the configuration system works. It is intended
|
||||
for Appium contributors, but will also explain the system's fundamental features.
|
||||
本文是配置系统工作原理的技术性概览。它面向 Appium 贡献者,但也会说明该系统的基本特性。
|
||||
|
||||
## Reading a Config File
|
||||
## 读取配置文件
|
||||
|
||||
A config file is a JSON, JavaScript, or YAML file which can be validated against a schema. By
|
||||
default, this file will be named `.appiumrc.{json,js,yaml,yml}` and should be in the root of the
|
||||
project which depends upon `appium`. Other filenames and locations are supported via the `--config <file>` flag. For obvious reasons, the `config` argument is disallowed within config files.
|
||||
配置文件是一个可以按 schema 校验的 JSON、JavaScript 或 YAML 文件。默认情况下,该文件名为
|
||||
`.appiumrc.{json,js,yaml,yml}`,应放在依赖 `appium` 的那个项目的根目录中。其他文件名和位置也支持,需通过 `--config <file>` 标志指定。显而易见,配置文件内部不允许出现 `config` 参数。
|
||||
|
||||
In lieu of a separate file, configuration can be embedded in a project's `package.json` using the
|
||||
`appiumConfig` property, e.g.,:
|
||||
除了独立文件之外,还可以把配置内嵌在项目的 `package.json` 中,使用 `appiumConfig` 属性,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -28,248 +23,173 @@ In lieu of a separate file, configuration can be embedded in a project's `packag
|
||||
}
|
||||
```
|
||||
|
||||
When an Appium server is started via the `appium` executable, the `init` function in `lib/main.js`
|
||||
will call into `lib/bootstrap/config-file.js` to load and/or search for a configuration file and in
|
||||
`package.json`.
|
||||
当通过 `appium` 可执行文件启动 Appium 服务器时,`lib/main.js` 中的 `init` 函数会调用`lib/bootstrap/config-file.js`,去加载和/或查找配置文件以及 `package.json` 中的配置。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
It is not an error if configuration isn't found!
|
||||
没有找到配置信息并不是错误!
|
||||
```
|
||||
|
||||
The [`lilconfig`](https://npm.im/lilconfig) package provides the search & load functionality; refer
|
||||
to its documentation for more information about the search paths. Additionally, Appium provides
|
||||
support for config files written in YAML via the package [`yaml`](https://npm.im/yaml).
|
||||
[`lilconfig`](https://npm.im/lilconfig) 包提供了查找与加载功能;关于搜索路径的更多信息请参考它的文档。此外,Appium 通过 [`yaml`](https://npm.im/yaml) 包支持用 YAML 编写的配置文件。
|
||||
|
||||
If a config file is found and successfully [validated](#validation), the result will be merged with
|
||||
a set of defaults and any additional CLI arguments. CLI arguments have precedence over config
|
||||
files, and config files have precedence over defaults.
|
||||
如果找到配置文件并成功[通过校验](#校验),其结果会与一组默认值以及额外的 CLI 参数合并。 CLI 参数的优先级高于配置文件,配置文件的优先级高于默认值。
|
||||
|
||||
## Validation
|
||||
## 校验
|
||||
|
||||
The same system is used for _both_ validation of config files _and_ command-line arguments.
|
||||
用于校验配置文件和命令行参数的是同一套系统。
|
||||
|
||||
The package [`ajv`](https://npm.im/ajv) provides validation. Of course, to make `ajv` validate
|
||||
anything, it must be provided a _schema_.
|
||||
校验由 [`ajv`](https://npm.im/ajv) 包提供。当然,要让 `ajv` 校验任何东西,必须先给它一个 _schema_。
|
||||
|
||||
The _base_ schema is a JSON Schema
|
||||
Draft-7-compliant object exported by
|
||||
`lib/schema/appium-config-schema.js`. This schema defines configuration _native to Appium_, and
|
||||
only concerns its behavior as a _server_; it does not define configuration for any other
|
||||
functionality (e.g., the `plugin` or `driver` subcommands).
|
||||
_基础_ schema 是由 `lib/schema/appium-config-schema.js` 导出的一个符合 [JSON Schema Draft-7](https://json-schema.org/draft/2020-12/json-schema-core.html) 的对象。该 schema 定义的是 _Appium 原生的_配置,并且只涉及它作为_服务器_的行为;它不定义其他任何功能(例如 `plugin` 或 `driver` 子命令)的配置。
|
||||
|
||||
!!! warning
|
||||
!!! 警告
|
||||
|
||||
```
|
||||
Note that this file is the _base_ schema; this will become painfully relevant.
|
||||
请注意这个文件只是基础 schema;这一点后面会体现得很"刻骨铭心"。
|
||||
```
|
||||
|
||||
This file is is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.
|
||||
This file is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.。
|
||||
|
||||
It is more straightforward to explain how config files are validated, so we'll start there.
|
||||
解释配置文件的校验流程更直接一些,所以我们从那里开始。
|
||||
|
||||
### Validating Config Files
|
||||
### 校验配置文件
|
||||
|
||||
When a config file is found (`lib/bootstrap/config-file.js`), it will call the `validate` function exported
|
||||
from `lib/schema/schema.js` with the contents of the config file. In turn, this asks `ajv` to
|
||||
validate the data against the schema that Appium has provided it.
|
||||
当找到配置文件后(`lib/bootstrap/config-file.js`),它会把配置文件的内容传给 `lib/schema/schema.js` 导出的 `validate` 函数。这一步请求 `ajv` 按 Appium 提供的 schema 来校验数据。
|
||||
|
||||
If the config file is invalid, errors will be generated to be displayed to the user. Finally, the
|
||||
`init` function will detect these errors, display them, and the process will exit.
|
||||
如果配置文件非法,就会生成用于展示给用户的错误。最终 `init` 函数会检测到这些错误并把它们显示出来,随后进程退出。
|
||||
|
||||
I hope that made sense, because this is the easy part.
|
||||
我希望这段讲清楚了,因为这只是简单的部分。
|
||||
|
||||
### Validating CLI Arguments
|
||||
### 校验命令行参数
|
||||
|
||||
As mentioned earlier, the same system is used for validating both config files and CLI arguments.
|
||||
如前所述,校验配置文件和校验 CLI 参数用的是同一套系统。
|
||||
|
||||
Totally not judging, but Appium uses [`argparse`](https://npm.im/argparse) for its CLI argument
|
||||
parsing. This package, and others like it, provides an API to define the arguments a command-line
|
||||
Node.js script accepts, and will ultimately return an object representation of the user-supplied
|
||||
arguments.
|
||||
无意评判谁,Appium 用 [`argparse`](https://npm.im/argparse) 解析命令行参数。这类包提供了一套API 来定义一个命令行 Node.js 脚本可接受的参数,并最终返回用户所给参数的对象表示。
|
||||
|
||||
Just as the schema defines what's allowed in a config file, it also defines what's allowed on the
|
||||
command-line.
|
||||
正如 schema 定义了配置文件中允许出现什么,它也定义了命令行上允许出现什么。
|
||||
|
||||
#### Defining CLI Arguments via Schema
|
||||
#### 通过 schema 定义 CLI 参数
|
||||
|
||||
CLI arguments must be _defined_ before their values can be validated.
|
||||
必须先_定义_ CLI 参数,才能校验它们的值。
|
||||
|
||||
A JSON schema isn't a natural fit for defining CLI args--it needs some grease to make it work--but
|
||||
it's close enough that we can do so with an adapter and some custom metadata.
|
||||
JSON schema 并不是天然适合定义 CLI 参数的格式——得加点"润滑油"才能跑得起来——不过它足够接近,我们借助一个适配层和一些自定义元数据就能做到。
|
||||
|
||||
In `lib/cli/parser.js`, there's a wrapper around `argparse`'s `ArgumentParser`; it's called (wait
|
||||
for it)... `ArgParser`. The wrapper exists because we're doing some custom things with `argparse`,
|
||||
but is has nothing to do with the schema directly.
|
||||
在 `lib/cli/parser.js` 中有一个对 `argparse` 的 `ArgumentParser` 的包装类;它叫.. `ArgParser`。之所以有这个包装,是因为我们对 `argparse` 做了一些自定义处理,但它与 schema 本身
|
||||
没有直接关系。
|
||||
|
||||
An `ArgParser` instance is created and its `parseArgs()` method is called with the raw CLI
|
||||
arguments. The definition of the accepted arguments comes from `lib/cli/args.js` in part--here, all
|
||||
of the arguments _not_ intended for use with the `server` subcommand are hard-coded (e.g., the
|
||||
`driver` subcommand and _its_ subcommands). `args.js` also contains a function `getServerArgs()`,
|
||||
which in turn calls into `toParserArgs` in `lib/schema/cli-args.js`. `lib/schema/cli-args.js` can
|
||||
be considered the "adapter" layer between `argparse` and the schema.
|
||||
系统会创建一个 `ArgParser` 实例,并用原始的 CLI 参数调用它的 `parseArgs()` 方法。可接受参数的定义有一部分来自 `lib/cli/args.js`——在那里,所有_不_用于 `server` 子命令的参数都是硬编码的(例如 `driver` 子命令以及_它_的子命令)。 `args.js` 还包含一个 `getServerArgs()` 函数,它会调用 `lib/schema/cli-args.js` 中的 `toParserArgs`。 `lib/schema/cli-args.js` 可以被视为 `argparse` 与 schema 之间的"适配层"。
|
||||
|
||||
`toParserArgs` uses the `flattenSchema` function exported by `lib/schema/schema.js`, which
|
||||
"squashes" the schema into a key/value representation. Then, `toParserArgs` iterates over each
|
||||
key/value pair and "converts" it into a suitable `ArgumentOption` object for final handoff to
|
||||
`ArgParser`.
|
||||
`toParserArgs` 使用 `lib/schema/schema.js` 导出的 `flattenSchema` 函数,该函数会把 schema
|
||||
"压扁"成键/值表示。然后 `toParserArgs` 遍历每一对键/值,把它"转换"成合适的 `ArgumentOption` 对象,最终交给 `ArgParser`。
|
||||
|
||||
This adapter (`cli-args.js`) is where most of the mess is hidden; let's explore this rat's nest
|
||||
a bit further.
|
||||
这个适配层(`cli-args.js`)正是藏污纳垢之处;我们再来翻翻这个老鼠窝。
|
||||
|
||||
##### CLI & Schema Incongruities
|
||||
##### CLI 与 schema 的不一致之处
|
||||
|
||||
The conversion algorithm (see function `subSchemaToArgDef` in `lib/schema/cli-args.js`) is mostly
|
||||
just hacks and special cases neatly packed into a function. Things that don't cleanly map from
|
||||
`argparse` to a JSON schema include, but are not limited to:
|
||||
转换算法(见 `lib/schema/cli-args.js` 中的 `subSchemaToArgDef` 函数)大体上就是把各种 hack 和
|
||||
特例整齐地塞进一个函数里。无法从 `argparse` 干净地映射到 JSON schema 的事情包括但不限于:
|
||||
|
||||
- A schema cannot natively express "store the value of `--foo=<value>` in a property called `bar`" in a schema (this corresponds to the `ArgumentOption['dest']` prop).
|
||||
- A schema cannot natively express aliases; e.g., `--verbose` can also be `-v`
|
||||
- A schema `enum` is not restricted to multiple types, but `argparse`'s equivalent `ArgumentOption['choices']` prop _is_
|
||||
- A schema does not know about `argparse`'s concept of "actions" (note that Appium is not currently using custom actions--though it did, and it could again).
|
||||
- `argparse` has no native type for `email`, `hostname`, `ipv4`, `uri` etc., and the schema does
|
||||
- Schema validation only _validates_, it does not perform translation, transformation, or coercion (mostly). `argparse` allows this.
|
||||
- Schemas allow the `null` type, for whatever reason. Ever pass `null` on the CLI?
|
||||
- `argparse` does not understand anything other than primitives; no objects, arrays, etc., and certainly not arrays of a particular type.
|
||||
- Schema 无法原生表达"把 `--foo=<value>` 的值存到一个名为 `bar` 的属性里"(对应 `ArgumentOption['dest']` 属性)。
|
||||
- Schema 无法原生表达别名;例如 `--verbose` 也可以写成 `-v`
|
||||
- Schema 的 `enum` 不限于同一种类型,而 `argparse` 中对应的 `ArgumentOption['choices']` 属性_却_有这个限制
|
||||
- Schema 不知道 `argparse` 的"actions"概念(注意 Appium 目前并未使用自定义 action——尽管它曾经用过,将来也可能再用)。
|
||||
- `argparse` 没有 `email`、`hostname`、`ipv4`、`uri` 等原生类型,而 schema 有
|
||||
- Schema 校验只做_校验_,不做翻译、转换或类型强制转换。而 `argparse` 允许这些。
|
||||
- Schema 允许 `null` 类型,天知道为什么。你在命令行上传过 `null` 吗?
|
||||
- `argparse` 只理解原始类型;不理解对象、数组等,更不理解"某种特定类型的数组"。
|
||||
|
||||
All of the above cases and others are handled by the adapter.
|
||||
以上所有这些情况以及其他情况都由适配层处理。
|
||||
|
||||
!!! warning
|
||||
!!! 警告
|
||||
|
||||
```
|
||||
Some decisions made in the adapter were arrived at via coin toss. If you are curious about why
|
||||
something is the way it is, it's likely that it had to do _something_.
|
||||
适配层中做出的某些决定,说白了是抛硬币定下来的。如果你好奇某处为什么是这个样子,那多半是因为它总得做点什么。
|
||||
```
|
||||
|
||||
Let's look more closely at handling types.
|
||||
我们再仔细看看类型是怎么处理的。
|
||||
|
||||
#### Argument Types via `ajv`
|
||||
#### 借助 `ajv` 实现参数类型
|
||||
|
||||
While `argparse` allows consumers, via its API, to define the _type_ of various arguments (e.g.,
|
||||
a string, number, boolean flag, etc.), Appium mostly avoids these built-in types. _Why is that?_
|
||||
Well:
|
||||
虽然 `argparse` 允许使用方通过 API 定义各种参数的_类型_(例如字符串、数字、布尔标志等),但 Appium 基本避开了这些内置类型。\*为什么呢?\*原因是:
|
||||
|
||||
1. We already know the type of an argument, because we've defined it in a schema.
|
||||
2. `ajv` provides validation against a schema.
|
||||
3. A schema allows for greater expression of types, allowed values, etc., than `argparse` can provide natively.
|
||||
4. The expressiveness of a schema allows for better error messaging.
|
||||
1. 我们已经知道参数的类型了,因为我们在 schema 里定义了它。
|
||||
2. `ajv` 提供基于 schema 的校验。
|
||||
3. 相比 `argparse` 原生能力,schema 能对类型、允许取值等做出更强的表达。
|
||||
4. schema 的表达力更强,错误提示也就更准确。
|
||||
|
||||
To that end, the adapter eschews `argparse`'s built-in types (see allowed string values of
|
||||
`ArgumentOption['type']`) and instead abuses the ability to provide a _function_ as a `type`. The
|
||||
exception is _boolean_ flags, which do not have a `type`, but rather `action: 'store_true'`. The
|
||||
world may never know why.
|
||||
为此,适配层舍弃了 `argparse` 的内置类型(参见 `ArgumentOption['type']` 允许的字符串取值),转而利用"可以把一个_函数_作为 `type`"这一能力。唯一的例外是_布尔_标志,它们没有 `type`,而是用`action: 'store_true'`。其中的原因,恐怕永远无人知晓。
|
||||
|
||||
##### Types as Functions
|
||||
##### 把函数当类型
|
||||
|
||||
When a `type` is a function, the function performs both validation _and_ coercion (if necessary).
|
||||
So what are these functions?
|
||||
当 `type` 是一个函数时,该函数同时负责校验_和_(必要时的)类型强制转换。那么这些函数到底是什么?
|
||||
|
||||
> Note: `type` is _omitted_ (and thus _not_ a function) from the `ArgumentOption` if the property
|
||||
> type is `boolean`, and is instead provided an `action` property of `store_true`. Yes, this is
|
||||
> weird. No, I don't know why.
|
||||
> 注意:如果属性的类型是 `boolean`,`ArgumentOption` 会省略 `type`(因此它不是函数),转而给出一个值为 `store_true` 的 `action` 属性。是的,这很奇怪。我也不知道为什么。
|
||||
|
||||
Well... it depends upon the schema. But generally speaking, we create a _pipeline_ of functions,
|
||||
each corresponding to a keyword in the schema. Let's take the example of the `port` argument. In
|
||||
lieu of asking the OS which ports the `appium`-running user can bind to, this argument is expected
|
||||
to be an integer between 1 and 65535. This turns out to be two functions which we combine into
|
||||
a pipeline:
|
||||
嗯……这取决于 schema。但一般而言,我们会创建一条函数_流水线_,其中每个函数对应 schema 中的一个关键字。以 `port` 参数为例。我们没有去问操作系统 `appium` 运行能绑定哪些端口,而是期望该参数是 1 到 65535 之间的整数。这最终归结为两个函数,我们把它们组合成一条流水线:
|
||||
|
||||
1. Convert the value to an integer, if possible. Because _every value in `process.argv` is a string_, we must coerce if we want a number.
|
||||
2. Use `ajv` to validate the integer against the schema for `port`. A schema lets us define a range via the `minimum` and `maximum` keywords. Read more about how this works in
|
||||
1. 尽可能把值转换为整数。因为 _`process.argv` 中的每个值都是字符串_,所以我们想要数字就必须做强制转换。
|
||||
2. 用 `ajv` 按 `port` 的 schema 校验这个整数。 Schema 允许我们通过 `minimum` 和 `maximum` 关键字定义取值范围。关于这部分如何工作的更多说明,请阅读下文。
|
||||
|
||||
Much like the config file validation, if errors are detected, Appium nicely tells the end-user and
|
||||
the process exits w/ some help text.
|
||||
与配置文件校验一样,如果检测到错误,Appium 会礼貌地告知用户,然后进程带着帮助文本退出。
|
||||
|
||||
For other arguments which are naturally of non-primitive types, things are not so straightforward.
|
||||
对于那些天然不是原始类型的其他参数,事情就没这么简单了。
|
||||
|
||||
##### Transformers
|
||||
##### 转换器
|
||||
|
||||
Remember how `argparse` doesn't understand arrays? What if the most ergonomic way to express
|
||||
a value is, in fact, an array?
|
||||
还记得 `argparse` 不理解数组这件事吗?如果表达某个值最顺手的方式恰好就是一个数组呢?
|
||||
|
||||
Well, Appium can't accept an array on the CLI, even though it can accept one in the config file.
|
||||
But Appium _can_ accept a comma-delimited string (a CSV "line"). Or a string filepath referring to
|
||||
a file which _contains_ a delimited list. Either way: by the time the value gets out of the
|
||||
argument parser, it should be an array.
|
||||
Appium 无法在命令行上接受数组,尽管它在配置文件中可以接受数组。但 Appium _可以_接受逗号分隔的字符串(一行 CSV),或者接受一个文件路径字符串,指向_包含_分隔列表的文件。无论哪种方式:当值离开参数解析器时,它应当已经是一个数组。
|
||||
|
||||
And as mentioned above, the native facilities of a JSON schema cannot express this. However, it's
|
||||
possible to define a _custom keyword_ which Appium can then detect and handle accordingly. So
|
||||
that's what Appium does.
|
||||
如前所述,JSON schema 的原生能力无法表达这一点。但可以定义_自定义关键字_,让 Appium 检测到并按相应方式处理。 Appium 正是这样做的。
|
||||
|
||||
In this case, a custom keyword `appiumCliTransformer` is registered with `ajv`. The value of
|
||||
`appiumCliTransformer` (at the time of this writing) can be `csv` or `json`. In the base schema
|
||||
file, `appium-config-schema.js`, Appium uses `appiumCliTransformer: 'csv'` if this behavior is
|
||||
desired.
|
||||
具体来说,系统向 `ajv` 注册了一个自定义关键字 `appiumCliTransformer`。 `appiumCliTransformer` 的取值(截至本文撰写时)可以是 `csv` 或 `json`。在基础 schema 文件 `appium-config-schema.js` 中,如果想要这种行为,Appium 会写 `appiumCliTransformer: 'csv'`。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
Any property defined in the schema having type `array` will _automatically_ uses the `csv`
|
||||
transformer. Likewise, a property having type `object` will use the `json` transformer. It's
|
||||
conceivable that `array` may want to use the `json` transformer, but otherwise, the presence of
|
||||
the `appiumCliTransformer` keyword on an `array`-or-`object`-typed property is not strictly
|
||||
necessary.
|
||||
schema 中任何类型为 `array` 的属性都会自动使用 `csv` 转换器。同理,类型为 `object` 的属性会使用 `json` 转换器。可以想象,`array` 也许会用得上 `json` 转换器,但除此之外,在 `array` 或 `object` 类型的属性上显式写 `appiumCliTransformer` 关键字并不是必需的。
|
||||
```
|
||||
|
||||
The adapter (remember the adapter?) creates a pipeline function including a special "CSV
|
||||
transformer" (transformers are defined in `lib/schema/cli-transformers.js`), and uses this function
|
||||
as the `type` property of the `ArgumentOption` passed into `argparse`. In this case, the `type:
|
||||
'array'` in the schema is ignored.
|
||||
适配层(还记得适配层吗?)会创建一条包含特殊"CSV 转换器"的流水线函数(转换器定义在 `lib/schema/cli-transformers.js`),并把这个函数作为传给 `argparse` 的 `ArgumentOption` 的 `type` 属性。这种情况下,schema 中的 `type: 'array'` 会被忽略。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
The config file doesn't _need_ to perform any complex transformation of values, because it
|
||||
naturally allows Appium to define exactly what it expects. So Appium does no post-processing of
|
||||
config file values.
|
||||
配置文件并不需要对值做任何复杂转换,因为它天然允许 Appium 精确声明自己想要什么。所以 Appium 不会对配置文件中的值做任何后处理。
|
||||
```
|
||||
|
||||
Properties that do not need this special treatment use `ajv` directly for validation. How this
|
||||
works requires some explanation, so that's next.
|
||||
不需要这种特殊处理的属性直接使用 `ajv` 来校验。这套机制需要一些解释,所以下面就讲。
|
||||
|
||||
#### Validation of Individual Arguments via `ajv`
|
||||
#### 借助 `ajv` 校验单个参数
|
||||
|
||||
When we think of a JSON schema, we tend to think, "I have this JSON file and I want to validate it
|
||||
against the schema". That's valid, and in fact Appium does just that with config files! However,
|
||||
Appium does not do this when validating arguments.
|
||||
说到 JSON schema,我们通常会想:"我有这么个 JSON 文件,要拿 schema 来校验它"。这没错,Appium 对配置文件干的正是这件事!但在校验参数时,Appium 并不这么做。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
During implementation, I was tempted to mash all of the arguments together into
|
||||
a config-file-like data structure and then validate it all at once. I think that would have
|
||||
been _possible_, but since an object full of CLI arguments is a flat key/value structure and
|
||||
the schema is not, this seemed like trouble.
|
||||
在实现过程中,我曾想把所有参数塞进一个类似配置文件的数据结构里,然后一次性校验。我觉得那应该可行,但装满 CLI 参数的对象是一个扁平的键/值结构,而 schema 不是,所以这看起来自找麻烦。
|
||||
```
|
||||
|
||||
Instead, Appium validates a value against a specific property _within_ the schema. To do this, it
|
||||
maintains a mapping between a CLI argument definition and its corresponding property. The mapping
|
||||
itself is a `Map` with a unique identifier for the argument as the key, and an `ArgSpec`
|
||||
(`lib/schema/arg-spec.js`) object as the value.
|
||||
相反,Appium 是针对 schema _内部_的某个具体属性来校验一个值。为此它维护了一份 CLI 参数定义与其对应属性之间的映射。这个映射本身是一个 `Map`,键是该参数的唯一标识,值是 `ArgSpec`(`lib/schema/arg-spec.js`)对象。
|
||||
|
||||
An `ArgSpec` object stores the following metadata:
|
||||
`ArgSpec` 对象保存以下元数据:
|
||||
|
||||
| Property Name | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | Canonical name of the argument, corresponding to the property name in the schema. |
|
||||
| `extType?` | `driver` or `plugin`, if appropriate |
|
||||
| `extName?` | Extension name, if appropriate |
|
||||
| `ref` | Computed `$id` of the property in the schema |
|
||||
| `arg` | Argument as accepted on CLI, without leading dashes |
|
||||
| `dest` | Property name in parsed arguments object (as returned by `argparse`'s `parse_args()`) |
|
||||
| `defaultValue?` | Value of the `default` keyword in schema, if appropriate |
|
||||
| 属性名 | 说明 |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| `name` | 参数的规范名称,对应 schema 中的属性名。 |
|
||||
| `extType?` | 若适用,值为 `driver` 或 `plugin` |
|
||||
| `extName?` | 若适用,值为扩展名称 |
|
||||
| `ref` | 该属性在 schema 中计算得到的 `$id` |
|
||||
| `arg` | 在 CLI 上接受形式,不含前导短横线 |
|
||||
| `dest` | 解析后的参数对象中的属性名(即 `argparse` 的 `parse_args()` 返回结果中的键) |
|
||||
| `defaultValue?` | 若适用,值为 schema 中 `default` 关键字定义的值 |
|
||||
|
||||
When a schema is [finalized](#schema-loading), the `Map` is populated with `ArgSpec` objects
|
||||
for all known arguments.
|
||||
当 schema 被[定稿](#schema-的加载)时,这个 `Map` 会被填上所有已知参数的 `ArgSpec` 对象。
|
||||
|
||||
So when the adapter is creating the pipeline of functions for the argument's `type`, it already has
|
||||
an `ArgSpec` for the argument. It creates a function which calls `validate(value, ref)` (in
|
||||
`lib/schema/schema.js`) where `value` is whatever the user provided, and `ref` is the `ref`
|
||||
property of the `ArgSpec`. The concept is that `ajv` can validate using _any_ `ref` it knows about;
|
||||
each property in a schema can be referenced by this `ref` whether it's defined or not. To help
|
||||
visualize, if a schema is:
|
||||
因此,当适配层为参数的 `type` 构建函数流水线时,它手上已经有该参数的 `ArgSpec` 了。它会创建一个调用 `validate(value, ref)`(位于 `lib/schema/schema.js`)的函数,其中 `value` 是用户提供的任意
|
||||
值,`ref` 是该 `ArgSpec` 的 `ref` 属性。其理念是:`ajv` 可以用它认识的_任意_ `ref` 来校验;schema中的每个属性都能被这个 `ref` 引用,无论它是否已定义。为了帮助理解,如果 schema 是:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -283,78 +203,54 @@ visualize, if a schema is:
|
||||
}
|
||||
```
|
||||
|
||||
The `ref` of `foo` would be `my-schema.json#/properties/foo`. Assuming our `Ajv` instance knows
|
||||
about this `my-schema.json`, then we can call its `getSchema(ref)` method (which has a `schema`
|
||||
property, but is a misnomer nonetheless) to get a validation function; `validate(value, ref)` in
|
||||
`schema.js` calls this validation function.
|
||||
那么 `foo` 的 `ref` 就是 `my-schema.json#/properties/foo`。假设我们的 `Ajv` 实例认识这个 `my-schema.json`,就可以调用它的 `getSchema(ref)` 方法(该方法返回的对象有个 `schema` 属性,不过方法名本身多少有点名不副实)得到一个校验函数;`schema.js` 中的 `validate(value, ref)` 调用的就是
|
||||
这个校验函数。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
The schema spec says a schema author can supply an explicit `$id` keyword to override this;
|
||||
it's unsupported by Appium at this time. If needed, extension authors must carefully use `$ref`
|
||||
without custom `$id`s. It's highly unlikely an extension would have a schema so complicated as
|
||||
to need this, however; Appium itself doesn't even use `$ref` to define its own properties!
|
||||
schema 规范说,schema 作者可以提供显式的 `$id` 关键字来覆盖这一点;Appium 目前不支持这样做。如有需要,扩展作者必须在不使用自定义 `$id` 的前提下谨慎使用 `$ref`。不过扩展的 schema 复杂到需要这种程度几乎不可能;连 Appium 自己定义属性时都没用到 `$ref`!
|
||||
```
|
||||
|
||||
Next, let's take a look at how Appium loads schemas. This actually happens _before_ any argument
|
||||
validation.
|
||||
接下来看看 Appium 是如何加载 schema 的。这一步实际上发生在任何参数校验_之前_。
|
||||
|
||||
## Schema Loading
|
||||
## Schema 的加载
|
||||
|
||||
Let's ignore extensions for a moment, and start with the base schema.
|
||||
我们先暂时抛开扩展,从基础 schema 说起。
|
||||
|
||||
When something first imports the `lib/schema/schema.js` module, an instance of an `AppiumSchema` is
|
||||
created. This is a singleton, and its methods are exported from the module (all of which are bound
|
||||
to the instance).
|
||||
当某个地方首次 import `lib/schema/schema.js` 模块时,会创建一个 `AppiumSchema` 实例。它是一个单例,模块导出的是它的各个方法(全部绑定到该实例上)。
|
||||
|
||||
The constructor does very little; it instantiates an `Ajv` instance and configures it with Appium's
|
||||
[custom keywords](#custom-keyword-reference) and adds support for the `format` keyword via the
|
||||
[ajv-formats](https://npm.im/ajv-formats) module.
|
||||
构造函数做很少的事:它实例化一个 `Ajv`,用 Appium 的[自定义关键字](#自定义关键字参考)进行配置,并通过 [ajv-formats](https://npm.im/ajv-formats) 模块加入对 `format` 关键字的支持。
|
||||
|
||||
Otherwise, the `AppiumSchema` instance does not interact with the `Ajv` instance until its
|
||||
`finalize()` method (exported as `finalizeSchema()`) is called. When this method is called, we're
|
||||
saying "we are not going to add any more schemas; go ahead and create `ArgSpec` objects and
|
||||
register schemas with `ajv`".
|
||||
除此之外,在 `AppiumSchema` 实例的 `finalize()` 方法(以 `finalizeSchema()` 之名导出)被调用之前,
|
||||
它不会与 `Ajv` 实例发生任何交互。当这个方法被调用时,等于在说"我们不会再添加更多 schema 了;请开始创建 `ArgSpec` 对象并向 `ajv` 注册 schema 吧"。
|
||||
|
||||
When does finalization happen? Well:
|
||||
那定稿什么时候发生呢?是这样的:
|
||||
|
||||
1. When the `appium` executable begins, it _checks for and configures extensions_ (hand-wave) in `APPIUM_HOME`.
|
||||
2. Only then does it start to think about arguments--it instantiates an `ArgParser`, which (as you'll recall) runs the adapter to convert the schema to arguments.
|
||||
3. _Finalization happens here_--when creating the parser. Appium need the schema(s) to be registered with `ajv` in order to create validation functions for arguments.
|
||||
4. Thereafter, Appium parses the arguments with the `ArgParser`.
|
||||
5. Finally, decides what to do with the returned object.
|
||||
1. `appium` 可执行文件启动时,会_检查并配置 `APPIUM_HOME` 中的扩展_(这里先含糊带过)。
|
||||
2. 然后它才开始考虑参数——实例化一个 `ArgParser`,而它(还记得吗)会运行适配层,把 schema 转换成参数。
|
||||
3. _定稿就发生在这里_——即创建解析器的时候。 Appium 需要这些 schema 已注册到 `ajv`,才能为参数创建校验函数。
|
||||
4. 随后,Appium 用 `ArgParser` 解析参数。
|
||||
5. 最后,决定如何处理返回的对象。
|
||||
|
||||
Without extensions, `finalize()` still knows about the Appium base schema
|
||||
(`appium-config-schema.js`), and just registers that. However, step 1. above is doing a _lot of
|
||||
work_, so let's look at how extensions come into play.
|
||||
即使没有扩展,`finalize()` 依然知道 Appium 的基础 schema(`appium-config-schema.js`),并注册它。但上面第 1 步可干了不少_重活_,所以我们来看看扩展是如何介入的。
|
||||
|
||||
## Extension Support
|
||||
## 扩展支持
|
||||
|
||||
One of the design goals of this system is the following:
|
||||
这套系统的一个设计目标是:
|
||||
|
||||
_An extension should be able to register custom CLI arguments with the Appium, and a user should be
|
||||
able to use them like any other argument_.
|
||||
_扩展应能够向 Appium 注册自定义 CLI 参数,而用户应能像使用任何其他参数一样使用它们。_
|
||||
|
||||
Previously, Appium accepted arguments in this manner (via `--driverArgs`), but validation was
|
||||
hand-rolled and required extension implementors to use a custom API. It also required the user to
|
||||
awkwardly pass a JSON string as the configuration on the command-line. Further, no contextual help
|
||||
(via `--help`) existed for these arguments.
|
||||
以前,Appium 是以另一种方式接受这些参数(通过 `--driverArgs`),但校验是手写的,还要求扩展实现方使用一套自定义 API。用户也不得不别扭地在命令行上传入一个 JSON 字符串作为配置。而且这些参数没有上下文帮助信息(通过 `--help` 看不到)。
|
||||
|
||||
Now, by providing a schema for its options, a driver or plugin can register CLI arguments and
|
||||
config file schemas with Appium.
|
||||
如今,只要为其选项提供 schema,驱动或插件就能向 Appium 注册 CLI 参数和配置文件 schema。
|
||||
|
||||
To register a schema, an extension must provide the `appium.schema` property in its `package.json`.
|
||||
The value may be a schema or a path to a schema. If the latter, the schema should be JSON or
|
||||
a CommonJS module (ESM not supported at this time, nor is YAML).
|
||||
要注册一个 schema,扩展必须在其 `package.json` 中提供 `appium.schema` 属性。其值可以是 schema 本身,也可以是指向 schema 的路径。若是后者,schema 应为 JSON 或一个 CommonJS 模块(暂不支持 ESM,也不支持 YAML)。
|
||||
|
||||
For any property in this schema, the property will appear as a CLI argument of the form
|
||||
`--<extension-type>-<extension-name>-<property-name>`. For example, if the `fake` driver provides
|
||||
a property `foo`, the argument will be `--driver-fake-foo`, and will show in `appium server --help`
|
||||
like any other CLI argument.
|
||||
对于该 schema 中的每个属性,它都会以 `--<extension-type>-<extension-name>-<property-name>` 形式的 CLI 参数出现。例如,若 `fake` 驱动提供了一个 `foo` 属性,那么参数就是 `--driver-fake-foo`,并且会像其他任何 CLI 参数一样出现在
|
||||
`appium server --help` 中。
|
||||
|
||||
The corresponding property in a config file would be
|
||||
`server.<extension-type>.<extension-name>.<property-name>`, e.g.:
|
||||
配置文件中对应的属性则是 `server.<extension-type>.<extension-name>.<property-name>`,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -368,20 +264,15 @@ The corresponding property in a config file would be
|
||||
}
|
||||
```
|
||||
|
||||
The naming convention described above avoids problems of one extension type having a name conflict
|
||||
with a different extension type.
|
||||
上面描述的命名约定避免了某一类扩展的名称与另一类扩展发生冲突的问题。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
While an extension can provide aliases via `appiumCliAliases`, "short" flags are disallowed,
|
||||
since all arguments from extensions are prefixed with `--<extension-type>-<extension-name>-`.
|
||||
The extension name and argument name will be kebab-cased for the CLI, according to [Lodash's
|
||||
rules](https://lodash.com/docs/4.17.15#kebabCase) around kebab-casing.
|
||||
虽然扩展可以通过 `appiumCliAliases` 提供别名,但不允许使用"短"标志,因为来自扩展的所有参数都以 `--<extension-type>-<extension-name>-` 为前缀。扩展名和参数名会按 [Lodash 关于kebab-case 的规则](https://lodash.com/docs/4.17.15#kebabCase)转换成 kebab-case 形式用于命令行。
|
||||
```
|
||||
|
||||
The schema object will look much like Appium's base schema, but it will only have top-level
|
||||
properties (nested properties are currently unsupported). Example:
|
||||
schema 对象看起来会与 Appium 的基础 schema 很像,但它只有顶层属性(暂不支持嵌套属性)。示例:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -397,20 +288,13 @@ properties (nested properties are currently unsupported). Example:
|
||||
}
|
||||
```
|
||||
|
||||
As written in a user's config file, this would be the `server.driver.cowabunga.fizz` property.
|
||||
写在用户的配置文件中时,它就是 `server.driver.cowabunga.fizz` 属性。
|
||||
|
||||
When extensions are loaded, the `schema` property is verified and the schema is registered with the
|
||||
`AppiumSchema` (it is _not_ registered with `Ajv` until `finalize()` is called).
|
||||
加载扩展时,系统会验证 `schema` 属性,并把该 schema 注册到 `AppiumSchema`(此时_不会_注册到 `Ajv`,直到 `finalize()` 被调用才注册)。
|
||||
|
||||
During finalization, each registered schema is added to the `Ajv` instance. The schema is assigned
|
||||
an `$id` based on the extension type and name (which overrides whatever the extension provides, if
|
||||
anything). Schemas are also forced to disallowed unknown arguments via the `additionalProperties:
|
||||
false` keyword.
|
||||
在定稿阶段,每个已注册的 schema 都会被加入 `Ajv` 实例。 schema 会被赋予一个基于扩展类型和扩展名的 `$id`(这会覆盖扩展自己提供的 `$id`,如果有的话)。 schema 还会被强制通过 `additionalProperties: false` 关键字禁止未知参数。
|
||||
|
||||
Behind the scenes, the base schema has `driver` and `plugin` properties which are objects. When
|
||||
finalized, a property is added to each--corresponding to an extension name--and the value of this
|
||||
property is a reference to the `$id` of a property in the extension schema. For example, the
|
||||
`server.driver` property will look like this:
|
||||
在幕后,基础 schema 有两个类型为对象的属性:`driver` 和 `plugin`。定稿时会向各自添加一个属性——以扩展名命名——其值是指向该扩展 schema 中某属性 `$id` 的引用。例如 `server.driver` 属性看起来会是这样:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -422,47 +306,36 @@ property is a reference to the `$id` of a property in the extension schema. For
|
||||
}
|
||||
```
|
||||
|
||||
This is why we call it the "base" schema--it is _mutated_ when extensions provide schemas. The
|
||||
extension schemas are kept separately, but the _references_ are added to the schema before it's
|
||||
ultimately added to `ajv`. This works because an `Ajv` instance understands references _from_ any
|
||||
schema it knows about _to_ any schema it knows about.
|
||||
这就是为什么我们把它称为"基础" schema——当扩展提供 schema 时它会被_改写_。扩展的 schema 单独保存,但在最终加入 `ajv` 之前,会先把_引用_添加进这个 schema。这样做行得通,是因为一个 `Ajv` 实例能够理解从它认识的任意 schema _指向_它认识的任意 schema 的引用。
|
||||
|
||||
!!! note
|
||||
!!! 注意
|
||||
|
||||
```
|
||||
This makes it impossible to provide a complete static schema for Appium _and_ the installed
|
||||
extensions (as of Nov 5 2021). A static `.json` schema _is_ generated from the base (via a Gulp
|
||||
task), but it does not contain any extension schemas. The static schema also has uses beyond
|
||||
Appium; e.g., IDEs can provide contextual error-checking of config files this way. Let's solve
|
||||
this?
|
||||
这使得无法为 Appium _以及_已安装的扩展提供一份完整的静态 schema(截至 2021 年 11 月 5 日)。静态的 `.json` schema _会_由基础 schema 生成(通过一个 Gulp 任务),但它不包含任何扩展 schema。静态 schema 也有 Appium 之外的用途;例如 IDE 可以借此为配置文件提供上下文错误检查。这个问题要不要一起来解决呢?
|
||||
```
|
||||
|
||||
Just like how we look up the reference ID of a particular argument in the base schema, validation
|
||||
of arguments from extensions happens the exact same way. If the `cowabunga` driver has the schema
|
||||
ID `driver-cowabunga.json`, then the `fizz` property can be referenced from any schema registered
|
||||
with `ajv` via `driver-cowabunga.json#/properties/fizz`. "Base" schema arguments begin with
|
||||
`appium.json#properties/` instead.
|
||||
就像我们在基础 schema 中查找某个特定参数的引用 ID 一样,来自扩展的参数的校验也走完全相同的路径。如果 `cowabunga` 驱动的 schema ID 是 `driver-cowabunga.json`,那么 `fizz` 属性就可以通过
|
||||
`driver-cowabunga.json#/properties/fizz` 被任何注册到 `ajv` 的 schema 引用。而"基础" schema 的参数则以 `appium.json#properties/` 开头。
|
||||
|
||||
## Development Environment Support
|
||||
## 开发环境支持
|
||||
|
||||
During the flow of development, a couple extra tasks have been automated to maintain the base
|
||||
schema:
|
||||
在开发流程中,有几个额外任务被自动化了,以维护基础 schema:
|
||||
|
||||
- As a post-transpilation step, a `lib/appium-config.schema.json` gets generated from
|
||||
- `lib/schema/appium-config-schema.js` (in addition to its CJS counterpart generated by Babel).
|
||||
- This file is under version control. It ends up being _copied_ to
|
||||
- `build/lib/appium-config.schema.json` in this step. A pre-commit hook (see
|
||||
- `scripts/generate-schema-declarations.js` in the root monorepo) generates
|
||||
- a `types/appium-config-schema.d.ts` from the above JSON file. The types in `types/types.d.ts`
|
||||
- depend upon this file. This file is under version control.
|
||||
- 作为转译之后的一个步骤,会由 `lib/schema/appium-config-schema.js` 生成 `lib/appium-config.schema.json`
|
||||
- (除 Babel 生成的 CJS 对应物之外)。
|
||||
- 该文件纳入版本控制。它会在这一步被 _复制_ 到本仓库的
|
||||
- `build/lib/appium-config.schema.json` 。一个 pre-commit 钩子
|
||||
- (见根 monorepo 中的 `scripts/generate-schema-declarations.js`)
|
||||
- 会由上述 JSON 文件生成 `types/appium-config-schema.d.ts`。 `types/types.d.ts` 中的类型
|
||||
- 依赖于该文件。该文件同样纳入版本控制。
|
||||
|
||||
## Custom Keyword Reference
|
||||
## 自定义关键字参考
|
||||
|
||||
Keywords are defined in `lib/schema/keywords.js`.
|
||||
关键字定义在 `lib/schema/keywords.js` 中。
|
||||
|
||||
- `appiumCliAliases`: allows a schema to express aliases (e.g., a CLI argument can be `--verbose` or `-v`). This is an array of strings. Strings shorter than three (3) characters will begin with a single dash (`-`) instead of a double-dash (`--`). Note that any argument provided by an extension will begin with a double-dash, because these are required to have the `--<extension-type>-<extension-name>-` prefix.
|
||||
- `appiumCliDest`: allows a schema to specify a custom property name in the post-`argprase` arguments objects. If not set, this becomes a camelCased string.
|
||||
- `appiumCliDescription`: allows a schema to override the description of the argument when displayed on the command-line. This is useful paired with `appiumCliTransformer` (or `array`/`object`-typed properties), since there's a substantial difference between what a CLI-using user can provide vs. what a config-file-using user can provide.
|
||||
- `appiumCliTransformer`: currently a choice between `csv` and `json`. These are custom functions which post-process a value. They are not used when loading & validating config files, but the idea should be that they result in the same object you'd get if you used whatever the config file wanted (e.g., an array of strings). `csv` is for comma-delimited strings and CSV files; `json` is for raw JSON strings and `.json` files.
|
||||
- `appiumCliIgnore`: If `true`, do not support this property on the CLI.
|
||||
- `appiumDeprecated`: If `true`, the property is considered "deprecated", and will be displayed as such to the user (e.g., in the `--help` output). Note the JSON Schema draft-2019-09 introduces a new keyword `deprecated` which we should use instead if upgrading to this metaschema. When doing so, `appiumDeprecated` should itself be marked as `deprecated`.
|
||||
- `appiumCliAliases`:允许 schema 表达别名(例如某个 CLI 参数可以写成 `--verbose` 或 `-v`)。它是一个字符串数组,长度小于三个字符的项会以单短横线(`-`)而非双短横线(`--`)开头。注意,扩展提供的任何参数都会以双短横线开头,因为它们必须带 `--<extension-type>-<extension-name>-` 前缀。
|
||||
- `appiumCliDest`:允许 schema 指定 `argparse` 解析后参数对象中的自定义属性名。若不设置,则是一个 camelCase 字符串。
|
||||
- `appiumCliDescription`:允许 schema 覆盖参数在命令行帮助中展示的描述。它与 `appiumCliTransformer`(或 `array`/`object` 类型的属性)配合使用很有价值,因为"用命令行的用户能提供什么"与"用配置文件的用户能提供什么"之间存在显著差别。
|
||||
- `appiumCliTransformer`:目前可选 `csv` 或 `json`。它们是自定义函数,用于对值做后处理。加载和校验配置文件时不会用到它们;其设计目标是让处理结果与直接使用配置文件时得到的对象一致(例如字符串数组)。 `csv` 用于逗号分隔字符串和 CSV 文件;`json` 用于原始 JSON 字符串和 `.json` 文件。
|
||||
- `appiumCliIgnore`:若为 `true`,则该属性不支持通过 CLI 提供。
|
||||
- `appiumDeprecated`:若为 `true`,该属性被视为"已废弃",并会以这种方式向用户展示(例如在 `--help` 输出中)。注意 JSON Schema draft-2019-09 引入了新关键字 `deprecated`,如果升级到该 schema,我们应该改用它;届时 `appiumDeprecated` 也应标记为 `deprecated`。
|
||||
|
||||
@@ -17,34 +17,34 @@ If you maintain an Appium driver that you would like to be listed here, feel fre
|
||||
|
||||
These drivers are currently maintained by the Appium team:
|
||||
|
||||
### [Chromium](https://github.com/appium/appium-chromium-driver)
|
||||
### [Chromium](https://appium.github.io/appium-chromium-driver)
|
||||
|
||||
- Target: Desktop and mobile Chromium browsers (Chrome, Microsoft Edge, etc.)
|
||||
- Target: Desktop Chromium-based browsers (Chrome, Microsoft Edge, etc.)
|
||||
- Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install chromium
|
||||
```
|
||||
|
||||
### [Espresso](https://github.com/appium/appium-espresso-driver)
|
||||
### [Espresso](https://appium.github.io/appium-espresso-driver)
|
||||
|
||||
- Target: Android applications
|
||||
- Mode: Native
|
||||
- Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
- Mode: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install espresso
|
||||
```
|
||||
|
||||
### [Gecko](https://github.com/appium/appium-geckodriver)
|
||||
### [Gecko](https://appium.github.io/appium-geckodriver)
|
||||
|
||||
- Target: Desktop and mobile Gecko browsers (Firefox)
|
||||
- Target: Desktop and mobile Gecko-based browsers (Firefox, etc.)
|
||||
- Mode: Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
appium driver install gecko
|
||||
```
|
||||
|
||||
### [Mac2](https://github.com/appium/appium-mac2-driver)
|
||||
### [Mac2](https://appium.github.io/appium-mac2-driver)
|
||||
|
||||
- Target: macOS applications
|
||||
- Mode: Native
|
||||
@@ -53,7 +53,7 @@ appium driver install gecko
|
||||
appium driver install mac2
|
||||
```
|
||||
|
||||
### [Safari](https://github.com/appium/appium-safari-driver)
|
||||
### [Safari](https://appium.github.io/appium-safari-driver)
|
||||
|
||||
- Target: Desktop and mobile Safari browsers
|
||||
- Mode: Web
|
||||
@@ -62,9 +62,9 @@ appium driver install mac2
|
||||
appium driver install safari
|
||||
```
|
||||
|
||||
### [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver)
|
||||
### [UiAutomator2](https://appium.github.io/appium-uiautomator2-driver)
|
||||
|
||||
- Target: Android, Android TV, Android Wear applications
|
||||
- Target: Android applications (mobile, TV, Wear, XR, Automotive)
|
||||
- Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
@@ -89,7 +89,7 @@ appium driver install windows
|
||||
|
||||
### [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
|
||||
- Target: iOS, iPadOS, tvOS applications
|
||||
- Target: iOS, iPadOS, tvOS, watchOS applications
|
||||
- Modes: Native, Hybrid, Web
|
||||
|
||||
```sh title="Install This Driver"
|
||||
@@ -169,7 +169,7 @@ This driver is recommended as a drop-in replacement for the partially unmaintain
|
||||
```
|
||||
|
||||
- Target: Windows applications
|
||||
- Mode: Native
|
||||
- Mode: Native, Hybrid
|
||||
- Supported by: Community / Automate The Planet
|
||||
|
||||
```sh title="Install This Driver"
|
||||
|
||||
@@ -14,7 +14,7 @@ If you maintain an Appium plugin that you would like to be listed here, feel fre
|
||||
|
||||
## Official Plugins
|
||||
|
||||
These plugins are are currently maintained by the Appium team:
|
||||
These plugins are currently maintained by the Appium team:
|
||||
|
||||
### [Execute Driver](https://github.com/appium/appium/tree/master/packages/execute-driver-plugin)
|
||||
|
||||
|
||||
@@ -113,3 +113,44 @@ Supported by: `@AppiumTestDistribution`
|
||||
```sh title="Install This Tool"
|
||||
npm install -g appium-installer
|
||||
```
|
||||
|
||||
### [Optics](https://github.com/mozarkai/optics-framework)
|
||||
|
||||
Optics is an open-source test automation framework that uses Appium to drive Android, iOS,
|
||||
Android TV, Samsung Tizen and LG webOS apps (and Selenium or Playwright for web). Tests are written
|
||||
as CSV or YAML keyword steps, and the same keywords are available as a Python library, a Robot
|
||||
Framework library, an HTTP API and an MCP server. Element lookup falls back from XPath to visible
|
||||
text, OCR and image matching.
|
||||
|
||||
Supported by: `@chinmayajha`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
pip install "optics-framework[mobile]"
|
||||
```
|
||||
|
||||
### [XPath to Class Chain](https://github.com/domovou69/xpath-to-class-chain)
|
||||
|
||||
XPath to Class Chain is a command-line tool for migrating iOS XPath locators in test code to
|
||||
`-ios class chain` selectors. It converts the XPaths that have an exact class chain equivalent,
|
||||
converts with a warning those that only have a close one (such as grouped positions `(//X)[n]`),
|
||||
and skips the rest with a reason (for example, sibling or parent axes).
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-class-chain
|
||||
```
|
||||
|
||||
### [XPath to UiSelector](https://github.com/domovou69/xpath-to-uiselector)
|
||||
|
||||
XPath to UiSelector is a command-line tool for migrating Android XPath locators in test code to
|
||||
`-android uiautomator` selectors. It converts the XPaths that have an exact `UiSelector`
|
||||
equivalent, converts with a warning those that only have a close one (such as `//A//B`), and skips
|
||||
the rest with a reason. The strategy call is rewritten together with the string, for example
|
||||
`AppiumBy.xpath` becomes `AppiumBy.androidUIAutomator`.
|
||||
|
||||
Supported by: `@domovou69`
|
||||
|
||||
```sh title="Install This Tool"
|
||||
npm install -g xpath-to-uiselector
|
||||
```
|
||||
|
||||
@@ -52,6 +52,6 @@ After the PR is merged there is no need to use the local plugin/driver deploymen
|
||||
to switch back to the package managed by NPM. Follow the next steps for that:
|
||||
|
||||
- Unlink the installed driver/plugin from the server by running `appium driver uninstall <driver_name>` or
|
||||
`appium plugin uninstall <driver_name>`.
|
||||
`appium plugin uninstall <plugin_name>`.
|
||||
- Delete the local source folder (`rm -rf <full_path_to_plugin_or_driver_folder_with_fetched_sources>`).
|
||||
- Install the driver or the plugin from NPM. Check the component README in order to find a proper command for that.
|
||||
|
||||
@@ -8,14 +8,14 @@ principles, so you could create more performant and efficient test suite executi
|
||||
|
||||
## Why Caching Is Necessary
|
||||
|
||||
Mobile application bundles could reach hundreds of megabytes is size. This could become a serious
|
||||
Mobile application bundles could reach hundreds of megabytes in size. This could become a serious
|
||||
performance issue if a test suite is executed, and it is necessary to fetch/extract the same application
|
||||
bundle for each test.
|
||||
|
||||
## What Is Cached
|
||||
|
||||
Caching could be applied to application bundles generated by
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L107) helper call.
|
||||
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L99) helper call.
|
||||
Inherited drivers can customize their caching logic by providing own `onPostProcess`
|
||||
(or both `inDownload` and `onPostProcess`) property definition, but the general
|
||||
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
|
||||
|
||||
@@ -24,7 +24,7 @@ to launch. In order to achieve this, Appium defines its own [extension capabilit
|
||||
## Common Appium Capabilities
|
||||
|
||||
According to the WebDriver spec, extension capabilities must include a namespace prefix (signifying
|
||||
the vendor introducing the capability), and the namespace must end a colon (`:`). Appium's vendor
|
||||
the vendor introducing the capability), and the namespace must end in a colon (`:`). Appium's vendor
|
||||
prefix is `appium:`, and so any Appium-specific capabilities must include this prefix. Depending on
|
||||
your Appium client, the prefix may be added automatically or in conjunction with certain interfaces,
|
||||
but it is always a good practice to explicitly include it for clarity.
|
||||
|
||||
@@ -36,7 +36,7 @@ discovered and loaded by Appium:
|
||||
- `appium.config.cjs`
|
||||
- `.appiumrc` (which is considered to be JSON)
|
||||
|
||||
Further, _if your project uses Node.js,_ you can use store the configuration inside an `appium`
|
||||
Further, _if your project uses Node.js,_ you can store the configuration inside an `appiumConfig`
|
||||
property in your `package.json` and it will be automatically discovered.
|
||||
|
||||
### Config File Search
|
||||
|
||||
@@ -17,7 +17,7 @@ property:
|
||||
|
||||
```
|
||||
{
|
||||
"<event_type>": [<occurence_timestamp_1>, ...],
|
||||
"<event_type>": [<occurrence_timestamp_1>, ...],
|
||||
"commands": [
|
||||
{
|
||||
"cmd": "<command_name>",
|
||||
|
||||
@@ -16,7 +16,7 @@ by client libraries. There are two main strategies for this:
|
||||
based client library (including all Selenium and Appium clients).
|
||||
|
||||
There are pros and cons for each strategy, but it is ultimately up to the extension author to
|
||||
decide how they wish implement new commands.
|
||||
decide how they wish to implement new commands.
|
||||
|
||||
This guide is designed to specifically help you understand the "Execute Method" strategy. This
|
||||
pattern is commonly used in official Appium drivers and other third-party extensions.
|
||||
|
||||
@@ -18,8 +18,11 @@ Each rule is an object with a set of predefined properties. The following rule p
|
||||
supported:
|
||||
|
||||
- `pattern`: A valid Javascript RegExp pattern to replace. Must be a valid non-empty pattern.
|
||||
- `text`: A simple non-empty exact text match to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided.
|
||||
- `text`: A simple non-empty exact text to replace. Either this property or the above one must
|
||||
be provided. `pattern` has priority over `text` if both are provided. Note that the text is
|
||||
matched as a whole word: special characters are escaped and word boundaries (`\b`) are added on
|
||||
the sides where the value begins or ends with a word character. For example, a rule with text
|
||||
`pass` replaces `pass` in `my pass here`, but does not match inside `password`.
|
||||
- `flags`: Regular expression flags for the given pattern. Supported flags are the same as for the
|
||||
standard JavaScript [RegExp constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2).
|
||||
Note that the `g` (global matching) flag is always enabled.
|
||||
|
||||
@@ -27,7 +27,7 @@ appium driver install xcuitest
|
||||
```
|
||||
|
||||
This command will install the latest version of the
|
||||
[XCUITest Driver](https://github.com/appium/appium-xcuitest-driver). The Extension CLI comes with a variety
|
||||
[XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/). The Extension CLI comes with a variety
|
||||
of commands and parameters; see the documentation for that command for all the specifics.
|
||||
|
||||
The all-important question when Appium is managing your extensions for you is: where are they installed?
|
||||
@@ -65,7 +65,7 @@ like any other dependency. Basically, whenever you run Appium, if you have not e
|
||||
1. Try to determine whether the _current directory_ is inside an `npm` package.
|
||||
2. If so, it will check whether `appium` is a dependency (dev, prod, or peer) in the project's
|
||||
`package.json`
|
||||
3. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will ignore load
|
||||
3. If so, _unless you have specified `APPIUM_HOME` in your environment_, Appium will load
|
||||
drivers and plugins defined in that `package.json` file instead.
|
||||
|
||||
This means you are freely able to add Appium drivers and plugins as regular package dependencies or
|
||||
|
||||
@@ -115,12 +115,12 @@ The Appium 1 ecosystem included several drivers, clients and other packages that
|
||||
deprecated and replaced with newer packages. Appium 2 no longer includes support for these packages,
|
||||
and it is recommended to migrate to the following replacements:
|
||||
|
||||
| Appium 1 Package | Replacement in Appium 2 |
|
||||
| ------------------ | -------------------------------------------------------------------------- |
|
||||
| iOS Driver | [XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/latest/) |
|
||||
| UiAutomator Driver | [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver/) |
|
||||
| `wd` Client | [WebdriverIO Client](https://webdriver.io/) |
|
||||
| Appium Desktop | [Appium Inspector](https://github.com/appium/appium-inspector) |
|
||||
| Appium 1 Package | Replacement in Appium 2 |
|
||||
| ------------------ | -------------------------------------------------------------------- |
|
||||
| iOS Driver | [XCUITest Driver](https://appium.github.io/appium-xcuitest-driver/) |
|
||||
| UiAutomator Driver | [UiAutomator2](https://appium.github.io/appium-uiautomator2-driver/) |
|
||||
| `wd` Client | [WebdriverIO Client](https://webdriver.io/) |
|
||||
| Appium Desktop | [Appium Inspector](https://appium.github.io/appium-inspector/) |
|
||||
|
||||
!!! info "Actions Needed"
|
||||
|
||||
|
||||
@@ -143,9 +143,9 @@ Icons are used to indicate endpoint support in either certain drivers, or in the
|
||||
|
||||
- :simple-appium:{ .lg } - Appium server
|
||||
- :material-apple:{ .lg } - [XCUITest driver](https://appium.github.io/appium-xcuitest-driver/latest/)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://github.com/appium/appium-espresso-driver)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://github.com/appium/appium-mac2-driver)
|
||||
- :material-android:{ .lg } - [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver/)
|
||||
- :material-coffee:{ .lg } - [Espresso driver](https://appium.github.io/appium-espresso-driver/)
|
||||
- :material-apple-finder:{ .lg } - [Mac2 driver](https://appium.github.io/appium-mac2-driver/)
|
||||
- :material-microsoft-windows:{ .lg } - [Windows driver](https://github.com/appium/appium-windows-driver)
|
||||
|
||||
</div>
|
||||
@@ -449,9 +449,6 @@ longer accepts, as well as the parameters it continues to accept in Appium 3.
|
||||
- `POST /session/:sessionId/element/:elementId/value`
|
||||
- :octicons-x-24: `value`
|
||||
- :octicons-check-24: `text`
|
||||
- `POST /session/:sessionId/timeouts`
|
||||
- :octicons-x-24: `type`, `ms`
|
||||
- :octicons-check-24: `script`, `pageLoad`, `implicit`
|
||||
- `POST /session/:sessionId/window`
|
||||
- :octicons-x-24: `name`
|
||||
- :octicons-check-24: `handle`
|
||||
|
||||
@@ -49,7 +49,10 @@ Appium 的最初目标是为移动应用(iOS 和 Android)开发自动化标
|
||||
|
||||
下一个问题是,Appium 如何将此协议映射到广泛平台的自动化行为? 严格来说,Appium 没有! 它将此责任留给一种称为 Appium _驱动程序_ 的软件模块。 您可以阅读整个[驱动程序](./drivers.md)的介绍,所以我们现在不会深入细节。
|
||||
|
||||
目前重要的是要理解驱动程序有点像 Appium 的可插拔模块,它赋予 Appium 自动化特定平台(或平台集,取决于驱动程序的目标)的能力。 最终,驱动程序的责任只是实现代表 WebDriver 协议的 Appium 内部接口。 它如何实现此接口完全取决于驱动程序,基于其在特定平台上实现自动化的策略。 通常,在细节上具有更多复杂性和难度,驱动程序通过依赖特定于平台的自动化技术来实现这一点。 例如,Apple 维护一种称为 [XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests) 的 iOS 自动化技术。 支持 iOS 应用自动化的 Appium 驱动程序称为 [XCUITest 驱动程序](https://github.com/appium/appium-xcuitest-driver),因为它最终所做的是将 WebDriver 协议转换为 XCUITest 库调用。
|
||||
目前重要的是要理解驱动程序有点像 Appium 的可插拔模块,它赋予 Appium 自动化特定平台(或平台集,取决于驱动程序的目标)的能力。最终,驱动程序的责任只是实现代表 WebDriver 协议的 Appium 内部接口。它如何实现此接口完全取决于驱动程序,基于其在特定平台上实现自动化的策略。通常,在细节上具有更多复杂性和难度,驱动程序通过依赖特定于平台的自动化技术来实现这一点。例如,Apple 维护一种称为 [XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests) 的 iOS 自动化技术。 The Appium
|
||||
driver that supports iOS app automation is called the XCUITest
|
||||
Driver because ultimately what it does is
|
||||
convert the WebDriver protocol to XCUITest library calls.
|
||||
|
||||
驱动程序是独立、可插拔模块的原因之一是它们彼此完全不同。 构建和使用不同平台的驱动程序的工具和要求完全不同。 因此,Appium 让您只使用您自动化任务所需的驱动程序。 选择驱动程序并安装它们以便与您的 Appium 实例一起使用非常重要,以至于 Appium 有自己的 [CLI 来管理驱动程序](../reference/cli/extensions.md)。
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@ async setUrl(url) {
|
||||
}
|
||||
```
|
||||
|
||||
[^1]: 你可能会注意到 `setUrl` 看起来与 `Navigate To` 完全不同,那么我们怎么知道使用它而不是其他随机字符串呢? 其实,Appium 的 WebDriver 协议到方法名的映射是在 `@appium/base-driver` 包中的一个特殊文件中定义的,叫做 [routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js)。
|
||||
所以如果你正在编写驱动程序,这就是你要去找出要使用什么方法名和期望什么参数的地方。 或者你可以查看任何主要 Appium 驱动程序的源代码!
|
||||
[^1]: 你可能会注意到 `setUrl` 看起来与 `Navigate To` 完全不同,那么我们怎么知道使用它而不是其他随机字符串呢? Well, Appium's WebDriver-protocol-to-method-name
|
||||
mappings are defined in special files within the `@appium/base-driver` package's
|
||||
[routes/](https://github.com/appium/appium/tree/master/packages/base-driver/lib/protocol/routes) directory.
|
||||
所以如果你正在编写驱动程序,这就是你要去找出要使用什么方法名和期望什么参数的地方。或者你可以查看任何主要 Appium 驱动程序的源代码!
|
||||
|
||||
就是这样! 我们如何实际实现该命令完全取决于我们自己,并取决于我们想要支持的平台。 以下是针对不同平台的此命令的一些不同示例实现:
|
||||
|
||||
@@ -48,7 +50,9 @@ async setUrl(url) {
|
||||
|
||||
## 自动化映射
|
||||
|
||||
但_通常_驱动程序作者想要做的是为给定的平台提供自动化行为,这些行为在语义上与浏览器的 WebDriver 规范实现非常相似。 当你想查找一个元素时,你应该得到一个 UI 元素的引用。 当你想点击或轻触该元素时,产生的行为应该与人点击或轻触该元素相同。 等等。
|
||||
But _typically_ what driver authors want to do is to provide automation behaviours for a given
|
||||
platform(s) that are semantically very similar to the WebDriver spec implementations for
|
||||
browsers. 当你想查找一个元素时,你应该得到一个 UI 元素的引用。当你想点击或轻触该元素时,产生的行为应该与人点击或轻触该元素相同。等等。
|
||||
|
||||
因此,驱动程序作者面临的真正挑战不是如何使用 WebDriver 协议(因为 `BaseDriver` 为你封装了所有这些),而是如何在目标平台上实现实际的自动化。 每个驱动程序都依赖于自己的一套底层技术。 正如[概览](index.md)中提到的,iOS 驱动程序使用 Apple 的一项名为 [XCUITest](https://developer.apple.com/documentation/xctest/xcuielement) 的技术。 这些底层自动化技术通常都有自己专有或特殊的 API。 编写驱动程序就是将 WebDriver 协议映射到这个底层 API 的任务(有时是一组不同的底层 API——例如,UiAutomator2 驱动程序不仅依赖于 Google 的 [UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator) 技术,还依赖于只能通过 [ADB](https://developer.android.com/tools/adb) 获得的功能,以及只能通过辅助应用内的 Android SDK 获得的功能)。 将所有这些整合到一个单一、可用的 WebDriver 接口中,是驱动程序开发这项非常有用(但极具挑战性)的艺术!
|
||||
|
||||
@@ -94,7 +98,10 @@ WebDriver 协议! 没错,XCUITest 驱动程序的 Objective-C 端本身就
|
||||
|
||||
如果你不熟悉"代理"的概念,在这种情况下,它只是意味着 XCUITest 驱动程序根本不会参与处理该命令。 相反,它只会在协议级别被重新打包并转发到 WebDriverAgent,WebDriverAgent 的响应同样会直接传回客户端,而不会有任何 XCUITest 驱动程序代码看到或修改它。
|
||||
|
||||
这种架构模式为选择在各处使用 WebDriver 协议而不是构建定制协议的驱动程序作者提供了一个很好的额外好处。 这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。 例如,如果你查看 [Appium Safari 驱动程序](https://github.com/appium/appium-safari-driver)代码,你会发现它基本上没有实现任何标准命令,因为所有这些命令都直接代理到底层的 SafariDriver 进程。
|
||||
这种架构模式为选择在各处使用 WebDriver 协议而不是构建定制协议的驱动程序作者提供了一个很好的额外好处。这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。 If
|
||||
you look at the [Appium Safari driver](https://appium.github.io/appium-safari-driver/) code, for
|
||||
example, you'll see that it implements basically no standard commands, because all of these are
|
||||
proxied directly to an underlying SafariDriver process.
|
||||
|
||||
了解这种代理机制有时在幕后发生是很重要的,因为如果你曾经深入研究一些开源驱动程序代码试图找出命令的实现位置,你可能会惊讶地发现 Node.js 驱动程序代码本身根本没有任何实现! 在这种情况下,你需要找出命令被代理到哪里,以便你可以在那里查找相应的实现。
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ title: 入门指南
|
||||
|
||||
1. 安装Appium
|
||||
2. 安装Appium驱动程序及其依赖项
|
||||
- 本指南提供了[UiAutomator2 驱动程序](https://github.com/appium/appium-uiautomator2-driver) 的说明
|
||||
- This guide provides instructions for the [UiAutomator2 driver](https://appium.github.io/appium-uiautomator2-driver/)
|
||||
3. 在您选择的语言中安装Appium客户端库
|
||||
- 本指南包含JavaScript、Python、Java、Ruby和.NET的选项
|
||||
4. 使用示例应用程序编写并运行简单的Appium自动化脚本
|
||||
|
||||
@@ -12,7 +12,9 @@ title: 下一步
|
||||
- [功能](../guides/caps.md)
|
||||
- [设置](../guides/settings.md)
|
||||
|
||||
您还会发现[Appium Inspector](https://github.com/appium/appium-inspector)是编写Appium测试不可或缺的工具,因为它允许对应用进行视觉检查,并帮助您发现测试脚本中使用的元素定位器。
|
||||
You'll also find that the [Appium Inspector](https://appium.github.io/appium-inspector/) is an
|
||||
indispensable tool for writing Appium tests, as it enables visual inspection of apps and
|
||||
helps you to discover element locators for use in your test scripts.
|
||||
|
||||
您还可以利用众多可用的在线Appium课程之一。
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ bundle add test-unit
|
||||
`appium_lib_core` 没有这样的全局变量。
|
||||
|
||||
此示例使用 `appium_lib_core` 和 `test-unit` gem 模块。
|
||||
在 `appium_lib` 中的测试代码应该类似。
|
||||
Test code in `appium_lib` should be similar.
|
||||
|
||||
```ruby title="test.rb"
|
||||
--8<-- "./sample-code/quickstarts/rb/test.rb"
|
||||
|
||||
@@ -10,7 +10,8 @@ title: 安装 UiAutomator2 驱动程序
|
||||
对于这个快速入门指南,我们将在 Android 平台上自动化一个应用,因为通过 Appium 进行 Android 自动化的系统要求与 Appium 本身相同(而 iOS 驱动程序,则要求您使用 macOS)。
|
||||
```
|
||||
|
||||
我们要使用的驱动程序称为 [UiAutomator2 驱动程序](https://github.com/appium/appium-uiautomator2-driver)。 请访问该驱动程序的文档并将其加入书签,因为它将成为未来宝贵的参考。
|
||||
The driver we're going to use is called the UiAutomator2
|
||||
Driver. 请访问该驱动程序的文档并将其加入书签,因为它将成为未来宝贵的参考。
|
||||
|
||||
## 设置 Android 自动化要求
|
||||
|
||||
@@ -84,7 +85,8 @@ Driver uiautomator2@2.0.5 successfully installed
|
||||
appium setup
|
||||
```
|
||||
|
||||
运行此命令将安装 Appium 的移动端特定驱动程序:UiAutomator2、[XCUITest](https://appium.github.io/appium-xcuitest-driver/)(仅在运行 macOS 时),以及 [Espresso](https://github.com/appium/appium-espresso-driver)。
|
||||
Running this will install Appium's mobile-specific drivers: UiAutomator2, [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
|
||||
(only if running macOS), and [Espresso](https://appium.github.io/appium-espresso-driver/).
|
||||
|
||||
您也可以使用此命令批量安装桌面应用程序或桌面浏览器的驱动程序。
|
||||
有关更多详细信息,请参考[设置命令文档](../reference/cli/setup.md)。
|
||||
|
||||
@@ -90,7 +90,7 @@ Retrieves the URL endpoints and WebDriver BiDi commands supported in the current
|
||||
#### Response
|
||||
|
||||
`ListCommandsResponse` - an object containing all supported endpoints and BiDi commands, grouped by
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
their origin (base Appium, driver-specific, or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### listExtensions
|
||||
@@ -104,7 +104,7 @@ Retrieves the [execute methods](../../guides/execute-methods.md) supported in th
|
||||
#### Response
|
||||
|
||||
`ListExtensionsResponse` - an object containing all supported execute methods, grouped by their
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command.ts)
|
||||
origin (driver-specific or plugin-specific). Refer to [the type definition file](https://github.com/appium/appium/blob/master/packages/types/lib/command-maps.ts)
|
||||
for a detailed structure of this object.
|
||||
|
||||
### getCurrentAppiumContext
|
||||
|
||||
@@ -47,7 +47,7 @@ Subscribes to one or more BiDi events.
|
||||
|
||||
#### Response
|
||||
|
||||
`null`
|
||||
`{subscription: string}` — a unique subscription ID that can be passed to `session.unsubscribe`.
|
||||
|
||||
### bidiUnsubscribe
|
||||
|
||||
@@ -57,15 +57,20 @@ session.unsubscribe
|
||||
|
||||
> WebDriver BiDi documentation: [session.unsubscribe](https://w3c.github.io/webdriver-bidi/#command-session-unsubscribe)
|
||||
|
||||
Unsubscribes from one or more BiDi events.
|
||||
Unsubscribes using previously returned subscription IDs, or the legacy event/context form.
|
||||
Removing a subscription ID preserves any overlapping subscriptions.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ------- |
|
||||
| `contexts?` | Contexts in which to unsubscribe from the specified events. By default, the global scope is applied. | string[] | `['']` |
|
||||
| `events` | Names of events to unsubscribe from | string[] | |
|
||||
| Name | Description | Type | Default |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------- |
|
||||
| `subscriptions?` | Non-empty list of subscription IDs returned by `session.subscribe`. Cannot be combined with `events` or `contexts`. | string[] | |
|
||||
| `contexts?` | Non-standard, only together with `events`: contexts in which to unsubscribe from the specified events. By default, the global scope is applied. | string[] | `['']` |
|
||||
| `events?` | Non-empty list of event names to unsubscribe from. Required when `subscriptions` is omitted. | string[] | |
|
||||
|
||||
All subscription IDs must be known to the current session. An unknown ID causes an `invalid argument`
|
||||
error without removing any subscriptions. Subscription IDs are cleared when the session is deleted.
|
||||
|
||||
#### Response
|
||||
|
||||
`null`
|
||||
`{}`
|
||||
|
||||
@@ -82,7 +82,7 @@ appium {driver|plugin} install <install-spec>
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| None | The short name of an official extension, with optional modifiers as supported by `npm install` (e.g. version or tag) |
|
||||
| `git` | The Git URL of the extension |
|
||||
| `github` | The GitHub repository URL of the extension |
|
||||
| `github` | The GitHub repository slug of the extension, in the form `<org>/<repo>` |
|
||||
| `local` | The local path to the extension containing its `package.json` file |
|
||||
| `npm` | The name of the `npm` package, with optional modifiers as supported by `npm install` (e.g. version or tag) |
|
||||
|
||||
@@ -115,7 +115,7 @@ appium {driver|plugin} install <install-spec>
|
||||
- Install the XCUITest driver from GitHub:
|
||||
|
||||
```
|
||||
appium driver install https://github.com/appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
appium driver install appium/appium-xcuitest-driver --source=github --package=appium-xcuitest-driver
|
||||
```
|
||||
|
||||
- Install the XCUITest driver using a Git URL:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user