mirror of
https://github.com/appium/appium.git
synced 2026-11-02 23:33:14 +08:00
Compare commits
97
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c26af8f852 | ||
|
|
bfef2db517 | ||
|
|
4c0181fb81 | ||
|
|
49d6a81973 | ||
|
|
ce44fc0fcf | ||
|
|
5ab78297e1 | ||
|
|
3764149753 | ||
|
|
21b7aa9b93 | ||
|
|
b67d72e4f3 | ||
|
|
e916a58d93 | ||
|
|
4090b56931 | ||
|
|
d48283cdca | ||
|
|
b2ad5a49db | ||
|
|
89cf01fda3 | ||
|
|
7d7bb7abc3 | ||
|
|
337241b8f6 | ||
|
|
0c62fc00e4 | ||
|
|
f0608a5d01 | ||
|
|
04c44260ac | ||
|
|
9bf9cc70fa | ||
|
|
0c70d27679 | ||
|
|
44422a1e58 | ||
|
|
21dc828306 | ||
|
|
f3a393aed8 | ||
|
|
12cdbb7fdd | ||
|
|
d14a0c5c0b | ||
|
|
74378cca66 | ||
|
|
92f4677b94 | ||
|
|
e2b3bdb09d | ||
|
|
6820f5ec4e | ||
|
|
743c764d5e | ||
|
|
ca1480adcf | ||
|
|
8ea98b1fe9 | ||
|
|
e09e5051c8 | ||
|
|
44e5f1ba05 | ||
|
|
0f735774af | ||
|
|
61de659616 | ||
|
|
07d4a81d27 | ||
|
|
d2a92c8bc1 | ||
|
|
c37f4500c2 | ||
|
|
b6c7533513 | ||
|
|
108b8c0209 | ||
|
|
41289724dc | ||
|
|
58d359d944 | ||
|
|
cf4f533022 | ||
|
|
0eb56a055e | ||
|
|
94dd88fc98 | ||
|
|
39d5cee1b7 | ||
|
|
17a803da27 | ||
|
|
3470ec5101 | ||
|
|
f5bc702c9a | ||
|
|
0668bc45dc | ||
|
|
a815e3be9d | ||
|
|
b5947991cf | ||
|
|
afc15599c0 | ||
|
|
75a9154366 | ||
|
|
8eea316484 | ||
|
|
ef9b99feee | ||
|
|
7dbf3c2127 | ||
|
|
d3c4d40b44 | ||
|
|
6357b30051 | ||
|
|
cfdf898e04 | ||
|
|
159703744f | ||
|
|
7ae4b5cb6a | ||
|
|
887cb449e6 | ||
|
|
c58f32441d | ||
|
|
325f9900cc | ||
|
|
337ec3e7ba | ||
|
|
888aac85ff | ||
|
|
ee0a579ddc | ||
|
|
8d6ffe0932 | ||
|
|
d4b983328a | ||
|
|
ce7b4520a4 | ||
|
|
dc4835f293 | ||
|
|
dc730e7307 | ||
|
|
ec79486df0 | ||
|
|
b5ea03ae85 | ||
|
|
3c6633eaa1 | ||
|
|
bf53db9fac | ||
|
|
fd57ff03a4 | ||
|
|
167fdb18f7 | ||
|
|
50964e0690 | ||
|
|
a2385f08d5 | ||
|
|
500c08f392 | ||
|
|
4655302239 | ||
|
|
5efd62af08 | ||
|
|
27ac2fa125 | ||
|
|
d8d2382327 | ||
|
|
faff3ce347 | ||
|
|
05b981afa2 | ||
|
|
5e047485ee | ||
|
|
b7e905eaf1 | ||
|
|
47de13c0d6 | ||
|
|
edc9a5e7e7 | ||
|
|
6c36683859 | ||
|
|
6db31553db | ||
|
|
de273dd370 |
@@ -7,3 +7,4 @@ sample-code
|
||||
**/*.d.ts
|
||||
**/build-fixtures/**
|
||||
**/*.ts
|
||||
packages/appium/docs/**/assets/**
|
||||
|
||||
+22
-2
@@ -4,7 +4,7 @@
|
||||
version: 3
|
||||
|
||||
project:
|
||||
id: github.com/appium/appium
|
||||
id: appium
|
||||
name: appium
|
||||
|
||||
paths:
|
||||
@@ -35,6 +35,26 @@ targets:
|
||||
path: ./packages/support
|
||||
- type: npm
|
||||
path: ./packages/test-support
|
||||
- type: npm
|
||||
path: ./packages/types
|
||||
- type: npm
|
||||
path: ./packages/schema
|
||||
- type: npm
|
||||
path: ./packages/universal-xmlplugin
|
||||
- type: npm
|
||||
path: ./packages/opencv
|
||||
- type: npm
|
||||
path: ./packages/images-plugin
|
||||
- type: npm
|
||||
path: ./packages/base-plugin
|
||||
- type: npm
|
||||
path: ./packages/relaxed-caps-plugin
|
||||
- type: npm
|
||||
path: ./packages/fake-plugin
|
||||
- type: npm
|
||||
path: ./packages/docutils
|
||||
|
||||
|
||||
|
||||
revision:
|
||||
branch: 2.0
|
||||
branch: master
|
||||
|
||||
@@ -28,13 +28,7 @@ jobs:
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: Run smoke tests
|
||||
uses: boneskull/nodejs-production-test-action@v1
|
||||
with:
|
||||
script: test:smoke
|
||||
workspaces: true
|
||||
quiet: true
|
||||
- name: Run unit & E2E tests
|
||||
- name: Run smoke, unit & E2E tests
|
||||
run: npm run test:ci
|
||||
|
||||
lint:
|
||||
@@ -43,7 +37,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: ESLint
|
||||
run: npm run lint
|
||||
- name: Check types
|
||||
@@ -56,6 +52,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: Generate docs
|
||||
run: npm run generate-docs
|
||||
|
||||
@@ -13,11 +13,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
with:
|
||||
ref: '2.0'
|
||||
- name: fossa analyze
|
||||
run: |
|
||||
npm install
|
||||
curl -sS -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install-latest.sh | bash
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
- name: Install fossa-cli
|
||||
run: ./scripts/install-fossa.sh -d
|
||||
- name: Run FOSSA analysis
|
||||
fossa analyze
|
||||
fossa test
|
||||
env:
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ module.exports = (wallaby) => {
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js'],
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js', '!**/local_appium_home/**'],
|
||||
workers: {
|
||||
restart: true,
|
||||
},
|
||||
|
||||
@@ -13,7 +13,11 @@ Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. We support simulators (iOS), emulators
|
||||
(Android), and real devices (iOS, Android, Windows, Mac).
|
||||
|
||||
Want to skip straight to the action? Check out our [getting started](/docs/en/about-appium/getting-started.md) doc.
|
||||
:bangbang: Major documentation revision in progress
|
||||
|
||||
Appium is in the final stages of a major revision (to version 2.0). As such, the documentation
|
||||
found around the web may not be correct. The current Appium 2.0 documentation is very much in
|
||||
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/2.0/).
|
||||
|
||||
### Drivers
|
||||
|
||||
@@ -130,6 +134,7 @@ plugin then don't hesitate to create a PR and add a link to your plugin into the
|
||||
Name | Description
|
||||
--- | ---
|
||||
[appium-device-farm](https://github.com/AppiumTestDistribution/appium-device-farm) | This is an Appium plugin designed to manage and create driver session on connected android devices and iOS Simulators.
|
||||
[appium-reporter-plugin](https://github.com/AppiumTestDistribution/appium-reporter-plugin) | This Plugin generates standalone consolidated html report with screenshots. Report can be fetched from appium server, without worrying about heavy lifting such as screenshot capturing, report generation etc.
|
||||
</details>
|
||||
|
||||
### Server Command Line Interface
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
jobs:
|
||||
- job: generate_docs
|
||||
pool:
|
||||
vmImage: "macOS 10.14"
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- script: |
|
||||
# do git stuff
|
||||
npm install
|
||||
brew install hub
|
||||
hub --version
|
||||
git remote add triager https://triager:$GITHUB_TOKEN@github.com/appium/appium
|
||||
git checkout -b generate-docs-$BUILD_SOURCEVERSION
|
||||
git status
|
||||
displayName: Checkout Generate Docs Branch
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
- script: |
|
||||
# Generate the docs
|
||||
npm run generate-docs
|
||||
git add .
|
||||
|
||||
# log the status so we know what is going on, on failure
|
||||
git status -s
|
||||
|
||||
# If docs were generated, make a PR to update the docs
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
git commit -a -n -m 'Update generated docs [ci skip]'
|
||||
git push triager generate-docs-$BUILD_SOURCEVERSION
|
||||
hub pull-request -b "appium:master" -h "appium:generate-docs-$BUILD_SOURCEVERSION" -m "docs: Update auto-generated docs"
|
||||
fi
|
||||
displayName: Generate Docs and Make PR
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
@@ -1,8 +1,14 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
|
||||
parameters:
|
||||
vmImage: 'macOS-latest'
|
||||
bundleNodeJSVersion: '16.17.0'
|
||||
nodeJSVersion: '16.x'
|
||||
|
||||
jobs:
|
||||
- job: bundle_and_upload_appium_zip
|
||||
pool:
|
||||
vmImage: 'macOS 10.14'
|
||||
vmImage: ${{ parameters.vmImage }}
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
@@ -11,7 +17,7 @@ jobs:
|
||||
displayName: Print GCC Version
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
versionSpec: ${{ parameters.nodeJSVersion }}
|
||||
- script: npm install
|
||||
displayName: Install NPM Dependencies
|
||||
- script: npm run build
|
||||
@@ -19,13 +25,13 @@ jobs:
|
||||
- script: npm prune --production
|
||||
displayName: Get rid of dev dependencies
|
||||
- script: |
|
||||
curl -o node-darwin.tar.gz https://nodejs.org/dist/v12.16.1/node-v12.16.1-darwin-x64.tar.gz
|
||||
curl -o node-darwin.tar.gz https://nodejs.org/dist/v${{ parameters.bundleNodeJSVersion }}/node-v${{ parameters.bundleNodeJSVersion }}-darwin-x64.tar.gz
|
||||
mkdir node-darwin-unpacked
|
||||
tar -xzf node-darwin.tar.gz -C node-darwin-unpacked/ --strip-components 1
|
||||
rm -f node-darwin.tar.gz
|
||||
cp node-darwin-unpacked/bin/node node-darwin
|
||||
rm -rf node-darwin-unpacked
|
||||
displayName: Download NodeJS
|
||||
displayName: Download NodeJS to bundle it
|
||||
- script: npm run zip
|
||||
displayName: Create the bundle zip
|
||||
- script: npm install
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"appium": ["../packages/appium"]
|
||||
"appium": ["../packages/appium"],
|
||||
"appium/plugin": ["../packages/appium/plugin"],
|
||||
"@appium/plugin-test-support": ["../packages/plugin-test-support"]
|
||||
},
|
||||
"types": ["webdriverio/async"]
|
||||
},
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [{"path": "../packages/appium"}, {"path": "../packages/base-plugin"}, {"path": "../packages/types"}]
|
||||
"references": [
|
||||
{"path": "../packages/appium"},
|
||||
{"path": "../packages/base-plugin"},
|
||||
{"path": "../packages/types"},
|
||||
{"path": "../packages/plugin-test-support"}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
The old Appium 1.x docs used to be in this directory. Now, they are available only on the [1.x
|
||||
branch](https://github.com/appium/appium/tree/1.x/docs).
|
||||
|
||||
Appium 2.x docs are located in [packages/appium/docs](packages/appium/docs) in this repo.
|
||||
Appium 2.x docs are located in [packages/appium/docs](../packages/appium/docs) in this repo.
|
||||
|
||||
Generated
+1518
-1277
File diff suppressed because it is too large
Load Diff
+18
-15
@@ -53,9 +53,10 @@
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\"",
|
||||
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
|
||||
"test": "run-s build test:unit lint",
|
||||
"test:ci": "run-s install-fake-driver test:unit test:ci:e2e",
|
||||
"test:ci": "run-s test:smoke install-fake-driver test:unit test:ci:e2e",
|
||||
"test:ci:e2e": "lerna run test:e2e",
|
||||
"test:e2e": "lerna run --parallel --max-buffer=128 test:e2e",
|
||||
"test:smoke": "smoker --all test:smoke",
|
||||
"test:unit": "lerna run test",
|
||||
"uninstall-fake-driver": "npm start -- driver uninstall fake",
|
||||
"upload": "gulp github-upload",
|
||||
@@ -83,6 +84,7 @@
|
||||
"@appium/base-plugin": "file:packages/base-plugin",
|
||||
"@appium/doctor": "file:packages/doctor",
|
||||
"@appium/docutils": "file:packages/docutils",
|
||||
"@appium/driver-test-support": "file:packages/driver-test-support",
|
||||
"@appium/eslint-config-appium": "file:packages/eslint-config-appium",
|
||||
"@appium/execute-driver-plugin": "file:packages/execute-driver-plugin",
|
||||
"@appium/fake-driver": "file:packages/fake-driver",
|
||||
@@ -90,6 +92,7 @@
|
||||
"@appium/gulp-plugins": "file:packages/gulp-plugins",
|
||||
"@appium/images-plugin": "file:packages/images-plugin",
|
||||
"@appium/opencv": "file:packages/opencv",
|
||||
"@appium/plugin-test-support": "file:packages/plugin-test-support",
|
||||
"@appium/relaxed-caps-plugin": "file:packages/relaxed-caps-plugin",
|
||||
"@appium/schema": "file:packages/schema",
|
||||
"@appium/support": "file:packages/support",
|
||||
@@ -101,19 +104,18 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-plugin": "1.3.1",
|
||||
"@babel/cli": "7.18.9",
|
||||
"@babel/core": "7.18.9",
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@babel/plugin-transform-runtime": "7.18.9",
|
||||
"@babel/preset-env": "7.18.9",
|
||||
"@babel/cli": "7.18.10",
|
||||
"@babel/core": "7.19.0",
|
||||
"@babel/plugin-transform-runtime": "7.18.10",
|
||||
"@babel/preset-env": "7.19.0",
|
||||
"@babel/register": "7.18.9",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/chai": "4.3.1",
|
||||
"@types/chai": "4.3.3",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "16.11.46",
|
||||
"@types/semver": "7.3.10",
|
||||
"@types/node": "16.11.57",
|
||||
"@types/semver": "7.3.12",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/sinon-chai": "3.2.8",
|
||||
"@types/teen_process": "1.16.1",
|
||||
@@ -124,12 +126,12 @@
|
||||
"chai": "4.3.6",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint": "8.23.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "9.0.0",
|
||||
"eslint-plugin-promise": "6.0.0",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
"eslint-plugin-promise": "6.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
@@ -139,6 +141,7 @@
|
||||
"lerna": "4.0.0",
|
||||
"lint-staged": "13.0.3",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "1.1.1",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.0.0",
|
||||
@@ -154,13 +157,13 @@
|
||||
"through2": "4.0.2",
|
||||
"typescript": "4.7.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.20.7",
|
||||
"webdriverio": "7.24.0",
|
||||
"ws": "8.8.1",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -3,6 +3,71 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.44](https://github.com/appium/appium/compare/appium@2.0.0-beta.43...appium@2.0.0-beta.44) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** do not throw ENAMETOOLONG on long cli arg ([cfdf898](https://github.com/appium/appium/commit/cfdf898e04e94032e3dd48b595caa4b6f0828289))
|
||||
* **appium:** eat update info errors ([7d7bb7a](https://github.com/appium/appium/commit/7d7bb7abc3173d29b843b3b238465d9914eaf091)), closes [#17357](https://github.com/appium/appium/issues/17357)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* **appium:** remove test export ([6820f5e](https://github.com/appium/appium/commit/6820f5ec4e62fbb5e91ce739d8aadb7f7b7a7a74)), closes [#17398](https://github.com/appium/appium/issues/17398)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Adjust NODE_PATH so NPM could properly resolve component peer dependencies ([#17325](https://github.com/appium/appium/issues/17325)) ([39d5cee](https://github.com/appium/appium/commit/39d5cee1b71f611e810900d3faed8e0fed6e1ce0))
|
||||
* **support:** Move module root detection utility into support package ([#17427](https://github.com/appium/appium/issues/17427)) ([5ab7829](https://github.com/appium/appium/commit/5ab78297e172bc6a5751c636f81b3b202fbe2743))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **appium:** This removes the `test` export from `appium`. `import * from 'appium/test'` is no longer supported.
|
||||
|
||||
Use `@appium/test-support`, `@appium/driver-test-support` or `@appium/plugin-test-support` instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.43](https://github.com/appium/appium/compare/appium@2.0.0-beta.42...appium@2.0.0-beta.43) (2022-08-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** fix busted config file normalization algorithm ([dc4835f](https://github.com/appium/appium/commit/dc4835f293f371c36873801a0060bb22eead2cc0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,base-driver,fake-driver,fake-plugin,test-support,types:** updateServer receives cliArgs param ([d4b9833](https://github.com/appium/appium/commit/d4b983328af21d1e5c27a91e438e7934eb152ab1)), closes [#17304](https://github.com/appium/appium/issues/17304)
|
||||
* **base-driver,fake-driver,appium:** add convenience methods for defining execute script overloads ([#17321](https://github.com/appium/appium/issues/17321)) ([337ec3e](https://github.com/appium/appium/commit/337ec3e7ba216dd6f8cdc88143ecaa4c75f5d266))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.42](https://github.com/appium/appium/compare/appium@2.0.0-beta.41...appium@2.0.0-beta.42) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
* **appium:** add npm version check ([27ac2fa](https://github.com/appium/appium/commit/27ac2fa125bbfbe2e2ad5a3e7bf31d518510aa2d))
|
||||
* **appium:** partially revert 2a6a056187ce925d5776b7acc4954b10ecf9221b ([b7e905e](https://github.com/appium/appium/commit/b7e905eaf188f0f23f279620d8ce7dfba60f0b25))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** pass unknown args to extension scripts ([faff3ce](https://github.com/appium/appium/commit/faff3ce3471abaea24d2cb4c3e3b75b1af5ac3a1)), closes [#17250](https://github.com/appium/appium/issues/17250)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.41](https://github.com/appium/appium/compare/appium@2.0.0-beta.40...appium@2.0.0-beta.41) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
---
|
||||
title: Appium's Config System
|
||||
---
|
||||
|
||||
Appium 2 supports [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 2 with
|
||||
a configuration file, CLI args, or both (the args have precedence over the config file).
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
In lieu of a separate file, configuration can be embedded in a project's `package.json` using the
|
||||
`appiumConfig` property, e.g.,:
|
||||
|
||||
```json
|
||||
{
|
||||
"appiumConfig": {
|
||||
"server": {
|
||||
"port": 12345
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When an Appium server is started via the `appium` executable, the `init` function in `lib/main.js`
|
||||
will call into `lib/config-file.js` to load and/or search for a configuration file and in
|
||||
`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).
|
||||
|
||||
If a config file is found and successfully [validated](#validation), the result will be merged with
|
||||
a set of defaults and any additionall CLI arguments. CLI arguments have precedence over config
|
||||
files, and config files have precedence over defaults.
|
||||
|
||||
## 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_.
|
||||
|
||||
The _base_ schema is a [JSON Schema
|
||||
Draft-7](https://json-schema.org/draft/2020-12/json-schema-core.html)-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).
|
||||
|
||||
!!! warning
|
||||
|
||||
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
|
||||
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/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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Just as the schema defines what's allowed in a config file, it also defines what's allowed on the
|
||||
command-line.
|
||||
|
||||
#### Defining CLI Arguments via Schema
|
||||
|
||||
CLI arguments must be _defined_ before their values can be validated.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
`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`.
|
||||
|
||||
This adapter (`cli-args.js`) is where most of the mess is hidden; let's explore this rat's nest
|
||||
a bit further.
|
||||
|
||||
##### CLI & Schema Incongruities
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
|
||||
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`
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
##### Types as Functions
|
||||
|
||||
When a `type` is a function, the function performs both validation _and_ coercion (if necessary).
|
||||
So what are these functions?
|
||||
|
||||
> 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.
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
Much like the config file validation, if errors are detected, Appium nicely tells the end-user and
|
||||
the process exits w/ some help text.
|
||||
|
||||
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?
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
!!! 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 stricly
|
||||
necessary.
|
||||
|
||||
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.
|
||||
|
||||
!!! 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.
|
||||
|
||||
Properties that do not need this special treatment use `ajv` directly for validation. How this
|
||||
works requires some explanation, so that's next.
|
||||
|
||||
#### Validation of Individual Arguments via `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.
|
||||
|
||||
!!! 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.
|
||||
|
||||
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.
|
||||
|
||||
An `ArgSpec` object stores the following metadata:
|
||||
|
||||
| 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 |
|
||||
|
||||
When a schema is [finalized](#schema-finalization), the `Map` is populated with `ArgSpec` objects
|
||||
for all known arguments.
|
||||
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"$id": "my-schema.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
!!! 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!
|
||||
|
||||
Next, let's take a look at how Appium loads schemas. This actually happens _before_ any argument
|
||||
validation.
|
||||
|
||||
## Schema Loading
|
||||
|
||||
Let's ignore extensions for a moment, and start with the base 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).
|
||||
|
||||
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.
|
||||
|
||||
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`".
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 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_.
|
||||
|
||||
Previously, Appium 2.0 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.
|
||||
|
||||
Now, by providing a schema for its options, a driver or plugin can register CLI arguments and
|
||||
config file schemas with Appium.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
The corresponding property in a config file would be
|
||||
`server.<extension-type>.<extension-name>.<property-name>`, e.g.:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"fake": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "my rad schema for the cowabunga driver",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fizz": {
|
||||
"type": "string",
|
||||
"default": "buzz",
|
||||
"$comment": "corresponds to CLI --driver-cowabunga-fizz"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As written in a user's config file, this would be the `server.driver.cowabunga.fizz` property.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"driver": {
|
||||
"cowabunga": {
|
||||
"$ref": "driver-cowabunga.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
!!! 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?
|
||||
|
||||
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.
|
||||
|
||||
## Development Environment Support
|
||||
|
||||
During the flow of development, a couple extra tasks have been automated to maintain the base
|
||||
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.
|
||||
|
||||
## Custom Keyword Reference
|
||||
|
||||
Keywords are defined in `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`.
|
||||
@@ -2,4 +2,22 @@
|
||||
title: Contributing to Appium
|
||||
---
|
||||
|
||||
This section of Appium's documentation is for those who are interested in contributing to Appium's
|
||||
development. It contains developer overviews, guides, and notes. Use the navigation menu to find
|
||||
a specific topic. All the information in these guides assumes in-depth familiarity with Appium
|
||||
already.
|
||||
|
||||
!!! warning
|
||||
|
||||
Developer information may not be kept up to date as frequently as user-facing information, or
|
||||
it may be most relevant in its current form on the online repository, not in this published
|
||||
version. Always be sure to check the repo or discuss with maintainers. We're always happy to
|
||||
help new contributors get started!
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
TODO
|
||||
|
||||
## The Appium Development Process
|
||||
|
||||
TODO
|
||||
|
||||
@@ -390,11 +390,56 @@ You can add custom CLI args if you want your driver to receive data from the com
|
||||
Appium server is started (for example, ports that a server administrator should set that should not
|
||||
be passed in as capabilities.
|
||||
|
||||
To do this you define a `schema` field within the `appium` field in `package.json`, pointing to
|
||||
a JS or JSON file defining an argument schema. (See the schema in `fake-driver`, for example).
|
||||
Arguments you define there will be available as a `--driver-<name>-<arg-name>` flag on the command
|
||||
line. If you define `my-arg` in the schema, for example, and if your driver's name is `awesome`,
|
||||
then users will be able to set your arg via `--driver-awesome-my-arg`.
|
||||
To define CLI arguments (or configuration properties) for the Appium server, your extension must provide a _schema_. In
|
||||
the `appium` property of your extension's `package.json`, add a `schema` property. This will either
|
||||
a) be a schema itself, or b) be a path to a schema file.
|
||||
|
||||
The rules for these schemas:
|
||||
|
||||
- Schemas must conform to [JSON Schema Draft-07](https://ajv.js.org/json-schema.html#draft-07).
|
||||
- If the `schema` property is a path to a schema file, the file must be in JSON or JS (CommonJS) format.
|
||||
- Custom `$id` values are unsupported. To use `$ref`, provide a value relative to the schema root, e.g., `/properties/foo`.
|
||||
- Known values of the `format` keyword are likely supported, but various other keywords may be unsupported. If you find a keyword that is unsupported which you need to use, please [ask for support](https://github.com/appium/appium/issues/new) or send a PR!
|
||||
- The schema must be of type `object` (`{"type": "object"}`), containing the arguments in a `properties` keyword. Nested properties are unsupported.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"test-web-server-port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "The port to use for the test web server"
|
||||
},
|
||||
"test-web-server-host": {
|
||||
"type": "string",
|
||||
"description": "The host to use for the test web server",
|
||||
"default": "sillyhost"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above schema defines two properties which can be set via CLI argument or configuration file. If
|
||||
this extension is a _driver_ and its name is "horace", the CLI args would be
|
||||
`--driver-horace-test-web-server-port` and `--driver-horace-test-web-server-host`, respectively.
|
||||
Alternatively, a user could provide a configuration file containing:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"horace": {
|
||||
"test-web-server-port": 1234,
|
||||
"test-web-server-host": "localhorse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add driver scripts
|
||||
|
||||
@@ -492,7 +537,7 @@ behaviours that don't map to any of the existing commands, you can create new co
|
||||
two ways:
|
||||
|
||||
1. Extending the WebDriver protocol and creating client-side plugins to access the extensions
|
||||
1. Overloading the Execute Script command
|
||||
1. Overloading the Execute Script command by defining "Execute Methods"
|
||||
|
||||
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`
|
||||
@@ -521,37 +566,56 @@ create and release client-side plugins for each language you want to support (di
|
||||
examples can be found at the relevant client docs).
|
||||
|
||||
An alternative to this way of doing things is to overload a command which all WebDriver clients
|
||||
have access to already: Execute Script. A common driver pattern is to create a magic prefix which
|
||||
the driver looks for at the beginning of any Execute Script invocation, and if it's present, to
|
||||
treat the rest of the script string as the name of a custom command to execute. This works in large
|
||||
part because for most platforms beyond web browsers, executing arbitrary JavaScript isn't a thing
|
||||
that makes sense.
|
||||
|
||||
Let's say you are building a driver for stereo system called `soundz`, and you wanted to create
|
||||
a command for playing a song by name. You could expose this to your users in such a way that they
|
||||
call something like:
|
||||
have access to already: Execute Script. Appium provides some a convenient tool for making this
|
||||
easy. Let's say you are building a driver for stereo system called `soundz`, and you wanted to
|
||||
create a command for playing a song by name. You could expose this to your users in such a way that
|
||||
they call something like:
|
||||
|
||||
```js
|
||||
// webdriverio example
|
||||
// webdriverio example. Calling webdriverio's `executeScript` command is what trigger's Appium's
|
||||
// Execute Script command handler
|
||||
driver.executeScript('soundz: playSong', [{song: 'Stairway to Heaven', artist: 'Led Zeppelin'}]);
|
||||
```
|
||||
|
||||
Then in your driver code you could simply look for `soundz:` at the beginning of your
|
||||
`execute` override, and do the appropriate thing:
|
||||
Then in your driver code you can define the static property `executeMethodMap` as a mapping of
|
||||
script names to methods on your driver. It has the same basic form as `newMethodMap`, described
|
||||
above. Once `executeMethodMap` is defined, you'll also need to implement the Execute Script command
|
||||
handler, which according to Appium's routes mapping is called `execute`. The implementation can
|
||||
call a single helper function, `this.executeMethod`, which takes care of looking at the script and
|
||||
arguments the user sent in and routing it to the correct custom handler you've defined. Here's an
|
||||
example:
|
||||
|
||||
```js
|
||||
async execute(script, args) {
|
||||
if (script === 'soundz: playSong') {
|
||||
const {song, artist} = args[0];
|
||||
// play the song based on song and artist details
|
||||
return;
|
||||
static executeMethodMap = {
|
||||
'soundz: playSong', {
|
||||
command: 'soundzPlaySong',
|
||||
params: {required: ['song', 'artist'], optional: []},
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise do the normal thing, or throw errors.NotYetImplementedError if you have nothing to
|
||||
// do (see the section on WebDriver errors in this guide)
|
||||
async soundzPlaySong(song, artist) {
|
||||
// play the song based on song and artist details
|
||||
}
|
||||
|
||||
async execute(script, args) {
|
||||
return await this.executeMethod(script, args);
|
||||
}
|
||||
```
|
||||
|
||||
A couple notes about this system:
|
||||
1. The arguments array sent via the call to Execute Script must contain only zero or one element(s). The
|
||||
first item in the list is considered to be the parameters object for your method. These parameters
|
||||
will be parsed, validated, and then applied to your overload method in the order specified in
|
||||
`executeMethodMap` (the order specified in the `required` parameters list, followed by the
|
||||
`optional` parameters list). I.e., this framework assumes only a single actual argument sent in via
|
||||
Execute Script (and this argument should be an object with keys/values representing the
|
||||
parameters your execute method expects).
|
||||
1. Appium does not automatically implement `execute` (the Execute Script handler) for you. You may
|
||||
wish, for example, to only call the `executeMethod` helper function when you're not in proxy
|
||||
mode!
|
||||
1. The `executeMethod` helper will reject with an error if a script name doesn't match one of the
|
||||
script names defined as a command in `executeMethodMap`, or if there are missing parameters.
|
||||
|
||||
### Implement handling of Appium settings
|
||||
|
||||
Appium users can send parameters to your driver via CLI args as well as via capabilities. But these
|
||||
|
||||
@@ -3,3 +3,137 @@ title: Capabilities
|
||||
---
|
||||
|
||||
TODO
|
||||
|
||||
## Special Notes for Cloud Providers
|
||||
|
||||
When managing an Appium cloud, your users may wish to target various independent versions of Appium
|
||||
drivers and plugins. It is of course up to each service provider how they wish to implement the
|
||||
discovery, installation, and availability of any official or third party drivers or plugins. _But
|
||||
the Appium team makes the following recommendations in terms of the *capabilities* service
|
||||
providers support, for consistency across the industry._ This is a recommendation only, and not
|
||||
a standard, but adopting it will help users to navigate the increased complexity that working with
|
||||
Appium 2.0 in a cloud environment may bring.
|
||||
|
||||
### Suggested capabilities
|
||||
|
||||
In addition to the standard `platformName`, `appium:deviceName`, `appium:automationName`, and
|
||||
`appium:platformVersion`, we recommend adopting the capability `$cloud:appiumOptions`, where the
|
||||
label `$cloud` is not meant to be interpreted literally but instead should be replaced by your
|
||||
vendor prefix (so for HeadSpin it would be `headspin`, Sauce Labs it would be `sauce`, and
|
||||
BrowserStack it would be `browserstack`, to name just a few examples). The `$cloud:appiumOptions`
|
||||
capability would itself be a JSON object, with the following internal keys:
|
||||
|
||||
| Capability | Used for | Example |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `version` | Designating which version of the Appium server is used to host and manage drivers. If ommitted, behavior left up to the provider, but the recommendation would be to provide the latest official version. | `2.0.0` |
|
||||
| `automationVersion` | Designating which version of the specified driver should be used. | `1.55.2` |
|
||||
| `automation` | Designating a custom driver to use (see below for more info). This would override `appium:automationName` and `$cloud:automationVersion` | `{"name": "@org/custom-driver", "source": "github", "package": "custom-driver"}` |
|
||||
| `plugins` | Designating the list of plugins (and potentially versions of plugins) to be activated (see below for more info). | `["images", "universal-xml"]` |
|
||||
|
||||
### Basic example
|
||||
|
||||
Appium extensions (drivers and plugins) have a set of properties that specify where they can be
|
||||
installed from. Cloud providers are obviously under no obligation to provide support for
|
||||
arbitrarily specified extensions, seeing as these may represent untrusted code running in a managed
|
||||
environment. In the case where arbitrary extensions are not supported, the `appium:automationName`,
|
||||
`$cloud:automationVersion`, and `$cloud:appiumPlugins` capabilities should be sufficient. See the
|
||||
following JSON object representing capabilities for a session:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"appium:platformVersion": "14.4",
|
||||
"appium:deviceName": "iPhone 11",
|
||||
"appium:app": "Some-App.app.zip",
|
||||
"appium:automationName": "XCUITest",
|
||||
"$cloud:appiumOptions": {
|
||||
"appiumVersion": "2.0.0",
|
||||
"automationVersion": "3.52.0",
|
||||
"plugins": ["images"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This set of capabilities requests an Appium 2+ server supporting the XCUITest driver at version
|
||||
`3.52.0`, and the `images` plugin active. This set is easy for a cloud provider to verify. The
|
||||
cloud provider can obviously do anything it wants in response to these capabilities, including
|
||||
downloading Appium and driver and plugin packages on the fly, or erroring out if the versions
|
||||
requested are not in a supported set, or if the plugin is not supported, etc...
|
||||
|
||||
### Basic example with `appium:options`
|
||||
|
||||
The previous example still looks a bit disorganized, so of course we also recommend that cloud
|
||||
providers support the `appium:options` capability as detailed above, which could turn the previous
|
||||
set of capabilities into the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"appium:options": {
|
||||
"platformVersion": "14.4",
|
||||
"deviceName": "iPhone 11",
|
||||
"app": "Some-App.app.zip",
|
||||
"automationName": "XCUITest"
|
||||
},
|
||||
"$cloud:appiumOptions": {
|
||||
"appiumVersion": "2.0.0",
|
||||
"automationVersion": "3.52.0",
|
||||
"plugins": ["images"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Extension objects
|
||||
|
||||
Some service providers may wish to dynamically allow access to all of the features of the Appium
|
||||
2.0 CLI, including downloading arbitrary drivers and plugins. To represent these extensions, we can
|
||||
define special JSON "extension objects", with the following keys:
|
||||
|
||||
- `name`: the name of the extension. This would be an NPM package name (if downloading from NPM), or a git or GitHub spec (if downloading from a git server or GitHub).
|
||||
- `version`: the version of the extension, e.g., the NPM package version or Git SHA.
|
||||
- (optional) `source`: a denotation of where the extension can be downloaded from. Recommended to support the following values: `appium`, `npm`, `git`, `github`. Here, `appium` means "Appium's own official list", and should be the default value if this key is not included.
|
||||
- (optional) `package`: when downloading extensions from git or github, the NPM package name of the extension must also be provided. This is optional for non-git sources.
|
||||
|
||||
Since each session is handled by a single driver, the `$cloud:appiumOptions`/`$automation`
|
||||
capability could be used with an extension object value to denote this driver, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"$cloud:appiumOptions": {
|
||||
"automation": {
|
||||
"name": "git+https://some-git-host.com/custom-driver-project.git",
|
||||
"version": "some-git-sha",
|
||||
"source": "git",
|
||||
"package": "driver-npm-package-name"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And since sessions can handle multiple plugins, each value in the list of `$cloud:appiumPlugins`
|
||||
could also be an extension object rather than a string, so that specific versions could be
|
||||
requested:
|
||||
|
||||
```json
|
||||
{
|
||||
"$cloud:appiumOptions": {
|
||||
"plugins": [{
|
||||
"name": "images",
|
||||
"version": "1.1.0"
|
||||
}, {
|
||||
"name": "my-github-org/my-custom-plugin",
|
||||
"version": "a83f2e",
|
||||
"source": "github",
|
||||
"package": "custom-plugin"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These serve as illustrative examples for the recommendations here. Of course it is up to the
|
||||
service providers to implement the handling of these capabilities at their front end / load
|
||||
balancer, to perform any error checking, or to actually run any of the `appium driver` or `appium
|
||||
plugin` CLI commands that support the end user's request. This section is merely a suggestion as to
|
||||
how service providers might design their user-facing capabilities API in a way which in principle
|
||||
supports all of the capabilities Appium itself would provide to the end user if they were running
|
||||
Appium on their own.
|
||||
|
||||
@@ -2,4 +2,94 @@
|
||||
title: The Appium Config File
|
||||
---
|
||||
|
||||
TODO
|
||||
Instead of passing arguments on the command line to Appium, you may add them to a special config
|
||||
file. Appium will read values from this config file when it runs. (Please note that CLI arguments
|
||||
have _precedence_ over configuration files; if a value is set in a config file _and_ via CLI
|
||||
argument, the CLI argument is preferred.)
|
||||
|
||||
## Supported Config File Formats
|
||||
|
||||
You can store your config data in the following kinds of files:
|
||||
|
||||
- JSON
|
||||
- YAML
|
||||
- JS (a JavaScript file exporting a JS object)
|
||||
- CJS (the same as above; the extension is for common JS)
|
||||
|
||||
!!! warning
|
||||
|
||||
Note: Configuration files in ESM format are not currently supported.
|
||||
|
||||
## Supported Config File Locations
|
||||
|
||||
Configuration files can be named anything, but the following filenames will be automatically
|
||||
discovered and loaded by Appium:
|
||||
|
||||
- `.appiumrc.json` (recommended)
|
||||
- `.appiumrc.yaml`
|
||||
- `.appiumrc.yml`
|
||||
- `.appiumrc.js`
|
||||
- `.appiumrc.cjs`
|
||||
- `appium.config.js`
|
||||
- `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` property in your `package.json` and it will be automatically discovered.
|
||||
|
||||
### Config File Search
|
||||
|
||||
Appium will search _up_ the directory tree from the current working directory for one of these
|
||||
files. If it reaches the current user's home directory or filesystem root, it will stop looking.
|
||||
|
||||
To specify a _custom_ location for your config file, use `appium --config-file /path/to/config/file`.
|
||||
|
||||
#### Configuration File Format
|
||||
|
||||
First, you might want to look at some examples:
|
||||
|
||||
- [Appium Configuration - JSON](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.json)
|
||||
- [Appium Configuration - YAML](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.yaml)
|
||||
- [Appium Configuration - JS](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.js)
|
||||
|
||||
A description of the format is available, as well:
|
||||
|
||||
- [Appium Configuration File JSON Schema](https://github.com/appium/appium/blob/master/packages/schema/lib/appium-config.schema.json)
|
||||
- [TypeScript declarations for Appium Configuration](https://github.com/appium/appium/blob/master/packages/types/lib/config.ts)
|
||||
|
||||
To describe in words, the config file will have a root `server` property, and all arguments are
|
||||
child properties. For certain properties which must be supplied on the command-line as comma-delimited lists, JSON strings, and/or external filepaths, these instead will be of their "native" type. For example,
|
||||
`--use-plugins <value>` needs `<value>` to be comma-delimited string or path to a delimited file.
|
||||
However, the config file just wants an array, e.g.,:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"use-plugins": ["my-plugin", "some-other-plugin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuring extensions (drivers and plugins)
|
||||
|
||||
For `driver`-and-`plugin`-specific configuration, these live under the `server.driver` and
|
||||
`server.plugin` properties, respectively. Each driver or plugin will have its own named property,
|
||||
and the values of any specific configuration it provides are under this. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"xcuitest": {
|
||||
"webkit-debug-proxy-port": 5400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
The above configuration corresponds to the `--driver-xcuitest-webkit-debug-proxy-port` CLI argument.
|
||||
|
||||
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.
|
||||
|
||||
@@ -2,4 +2,206 @@
|
||||
title: Migrating from Appium 1.x to Appium 2.x
|
||||
---
|
||||
|
||||
This document is a guide for those who are using Appium 1.x and wish to migrate to Appium 2.x. It contains a list of breaking changes and how to migrate your environments or test suites to ensure compatibility with Appium 2.0.
|
||||
|
||||
## Overview of Appium 2.0
|
||||
|
||||
Appium 2.0 is the most major new release of Appium in over 5 years. The changes in Appium 2.0 are _not_ primarily related to changes in automation behaviors for specific platforms. Instead, Appium 2.0 reenvisions Appium as a _platform_ where "drivers" (code projects that introduce support for automation of a given platform) and "plugins" (code projects that allow for overriding, altering, extending, or adding behaviors to Appium) can be easily created and shared.
|
||||
|
||||
At the same time, the Appium project is taking the opportunity to remove many old and deprecated bits of functionality.
|
||||
|
||||
Together these do introduce a few breaking changes to how Appium is installed, how drivers and various features are managed, and protocol support. These are detailed below.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases) for the most comprehensive lists of changes. Here we call out the breaking changes and what you need to do do account for them.
|
||||
|
||||
### :bangbang: Installing drivers during setup
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
|
||||
```
|
||||
appium driver install xcuitest
|
||||
appium driver install uiautomator2
|
||||
```
|
||||
|
||||
At this point, your drivers are installed and ready. There's a lot more you can do with this CLI so be sure to check out the docs on it.
|
||||
If you're running in a CI environment or want to install Appium along with some drivers all in one step, you can do so using some special flags during install, for example:
|
||||
|
||||
```
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
|
||||
### :bangbang: Chromedriver installation flags
|
||||
|
||||
In Appium 1.x it was possible to customize the way Chromedriver was installed (as part of the UiAutomator2 driver for example), using the following command line flags:
|
||||
|
||||
* `--chromedriver-skip-install`
|
||||
* `--chromedriver-version`
|
||||
* `--chromedriver-cdnurl`
|
||||
|
||||
Because Appium 2.0 now installs drivers for you, and because these flags were implemented as NPM config flags, they will no longer work. Instead, use the following environment variables during driver installation:
|
||||
|
||||
* `APPIUM_SKIP_CHROMEDRIVER_INSTALL`
|
||||
* `CHROMEDRIVER_VERSION`
|
||||
* `CHROMEDRIVER_CDNURL`
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
APPIUM_SKIP_CHROMEDRIVER_INSTALL=1 appium driver install uiautomator2
|
||||
```
|
||||
|
||||
### :bangbang: Driver-specific command line options
|
||||
|
||||
With Appium 1.x, command-line options specific to particular drivers were all hosted on the main Appium server. So, for example, `--chromedriver-executable` was a CLI parameter you could use with Appium to set the location of a specific Chromedriver version for use with, say, the UiAutomator2 driver.
|
||||
|
||||
With Appium 2.x, all driver- and platform-specific CLI params have been moved to the drivers themselves. To access them, you'll now need to prepend the argument with the extension type (either `driver` or `plugin`) and the name of the extension. For example, `--chromedriver-executable` becomes `--driver-uiautomator2-chromedriver-executable`.
|
||||
|
||||
### :bangbang: Driver-specific automation commands
|
||||
|
||||
The definition of certain commands that pertain only to specific drivers has been moved to those
|
||||
drivers' implementations. For example, `pressKeyCode` is specific to the UiAutomator2 driver and is
|
||||
now understood only by that driver. In practice, the only breaking change here is the kind of error
|
||||
you would encounter if the appropriate driver is not installed. Previously, you would get a `501
|
||||
Not Yet Implemented` error if using a driver that didn't implement the command. Now, you will get
|
||||
a `404 Not Found` error because if a driver that doesn't know about the command is not active, the
|
||||
main Appium server will not define the route corresponding to the command.
|
||||
|
||||
### :bangbang: Driver updates
|
||||
|
||||
In the past, to get updates to your iOS or Android drivers, you'd simply wait for those updates to be rolled into a new release of Appium, and then update your Appium version. With Appium 2.x, the Appium server and the Appium drivers are versioned and released separately. This means that drivers can be on their own release cadence and that you can get driver updates as they happen, rather than waiting for a new Appium server release. The way to check for driver updates is with the CLI:
|
||||
|
||||
```bash
|
||||
appium driver list --updates
|
||||
```
|
||||
|
||||
If any updates are available, you can then run the `update` command for any given driver:
|
||||
|
||||
```bash
|
||||
appium driver update xcuitest
|
||||
```
|
||||
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install -g appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
|
||||
### :bangbang: Protocol changes
|
||||
|
||||
Appium's API is based on the [W3C WebDriver Protocol](https://www.w3.org/TR/webdriver/), and it has supported this protocol for years. Before the W3C WebDriver Protocol was designed as a web standard, several other protocols were used for both Selenium and Appium. These protocols were the "JSONWP" (JSON Wire Protocol) and "MJSONWP" (Mobile JSON Wire Protocol). The W3C Protocol differs from the (M)JSONWP protocols in a few small ways.
|
||||
|
||||
Up until Appium 2.0, Appium supported both protocols, so that older Selenium/Appium clients could still communicate with newer Appium servers. Moving forward, support for older protocols will be removed.
|
||||
|
||||
### :bangbang: _Capabilities_
|
||||
|
||||
One significant difference between old and new protocols is in the format of capabilities. Previously called "desired capabilities", and now called simply "capabilities", there is now a requirement for a so-called "vendor prefix" on any non-standard capabilities. The list of standard capabilities is given in the [WebDriver Protocol spec](https://www.w3.org/TR/webdriver/#capabilities), and includes a few commonly used capabilities such as `browserName` and `platformName`.
|
||||
|
||||
These standard capabilities continue to be used as-is. All other capabilities must include a "vendor prefix" in their name. A vendor prefix is a string followed by a colon, such as `appium:`. Most of Appium's capabilities go beyond the standard W3C capabilities and must therefore include vendor prefixes (we recommend that you use `appium:` unless directed otherwise by documentation). For example:
|
||||
|
||||
- `appium:app`
|
||||
- `appium:noReset`
|
||||
- `appium:deviceName`
|
||||
|
||||
This requirement may or may not be a breaking change for your test suites when targeting Appium 2.0. If you're using an updated Appium client, the client will add the `appium:` prefix for you on all necessary capabilities. New versions of the Appium Inspector tool will also do this. Cloud-based Appium providers may also do this. So simply be aware that if you get any messages to the effect that your capabilities lack a vendor prefix, this is how you solve that problem.
|
||||
|
||||
On a related note, it will no longer be possible to start Appium sessions using WebDriver clients that don't support the W3C protocol (see below for a comment to this effect for the WD library).
|
||||
|
||||
To make everyone's lives a bit easier, we've also introduced the option of wrapping up all Appium-related capabilities into one object capability, `appium:options`. You can bundle together anything that you would normally put an `appium:` prefix on into this one capability. Here's an example (in raw JSON) of how you might start an iOS session on the Safari browser using `appium:options`:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"browserName": "Safari",
|
||||
"appium:options": {
|
||||
"platformVersion": "14.4",
|
||||
"deviceName": "iPhone 11",
|
||||
"automationName": "XCUITest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(Of course, each client will have a different way of creating structured capabilities like `appium:options` or other ones that you might have seen such as `goog:chromeOptions`). NB: capabilities that show up in `appium:options` will overwrite capabilities of the same name that show up at the top level of the object.
|
||||
|
||||
For more information on capabilities, have a look at the [Capabilities Guide](caps.md).
|
||||
|
||||
### :bangbang: _Removed Commands_
|
||||
|
||||
In addition to commands which have been moved to driver implementations, commands which were a part of the old JSON Wire Protocol and not a part of the W3C Protocol are no longer available:
|
||||
|
||||
- TODO (these commands are being identified and removed and will be updated here when complete)
|
||||
|
||||
If you use a modern Appium or Selenium client, you should no longer have access to these anyway, so any breaking changes should appear on the client side first and foremost.
|
||||
|
||||
### :bangbang: Image analysis features moved to plugin
|
||||
|
||||
One of the design goals for Appium 2.0 is to migrate non-core features into special extensions called [plugins](../ecosystem/index.md). This allows people to opt into features which require extra time to download or extra system setup. The various image-related features of Appium (image comparison, finding elements by image, etc...) have been moved into an officially supported plugin called [images](https://github.com/appium/appium/tree/master/packages/images-plugin).
|
||||
|
||||
If you use these image-related methods, to continue accessing them you will need to do two things.
|
||||
|
||||
1. Install the plugin: `appium plugin install images`
|
||||
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=images`
|
||||
|
||||
Image-related commands will also be removed on the client side of things, which means you will need to follow the instructions on the plugin README for installing client-side plugins to access these features.
|
||||
|
||||
### :bangbang: Execute Driver Script command moved to plugin
|
||||
|
||||
If you use the advanced Execute Driver Script feature (which allows you to send in a WebdriverIO script to have it executed completely on the server instead of command-by-command from the client), this functionality has been moved to a plugin. Here's what to do to keep using it:
|
||||
|
||||
1. Install the plugin: `appium plugin install execute-driver`
|
||||
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=execute-driver`
|
||||
|
||||
### :bangbang: External Files No Longer Supported for `--nodeconfig`, `--default-capabilities`, `--allow-insecure` and `--deny-insecure`
|
||||
|
||||
These options can be provided as strings on the command line (a JSON string for `--nodeconfig` and a comma-separated list of strings for `--allow-insecure` and `--deny-insecure`). Arguments provided on the command line will likely need to be quoted or escaped.
|
||||
|
||||
The recommended method to provide these options is now via a [configuration file](#tada-configuration-files).
|
||||
|
||||
In summary, if you are using a JSON Appium config file, you can simply cut-and-paste the contents of your "nodeconfig" JSON file into the value of the `server.nodeconfig` property. Any CSV-like files you had previously provided for `--allow-insecure` and `--deny-insecure` become the values of the `server.allow-insecure` and `server.deny-insecure` properties in the Appium config files (respectively); both are arrays of strings.
|
||||
|
||||
### :bangbang: Old drivers removed
|
||||
|
||||
The old iOS and Android (UiAutomator 1) drivers and related tools (e.g., `authorize-ios`) have been removed. They haven't been relevant for many years anyway.
|
||||
|
||||
### :warning: Internal packages renamed
|
||||
|
||||
Some Appium-internal NPM packages have been renamed (for example, `appium-base-driver` is now `@appium/base-driver`). This is not a breaking change for Appium users, only for people who have built software that directly incorporates Appium's code.
|
||||
|
||||
### :warning: "WD" JavaScript client library no longer supported
|
||||
|
||||
For many years, some of Appium's authors maintained the [WD](https://github.com/admc/wd) client library. This library has been deprecated and has not been updated for use with the W3C WebDriver protocol. As such, if you're using this library you'll need to move to a more modern one. We recommend [WebdriverIO](https://webdriver.io).
|
||||
|
||||
### :warning: Appium Inspector split out from Appium Desktop
|
||||
|
||||
The inspecting portion of Appium Desktop has been moved to its own app, Appium Inspector: [github.com/appium/appium-inspector](https://github.com/appium/appium-inspector). It's fully compatible with Appium 2.0 servers. Simply download it and run it on its own. You no longer need the GUI Appium Desktop server to inspect apps. The Appium Desktop server will continue to be supported at its original site: [github.com/appium/appium-desktop](https://github.com/appium/appium-desktop). It will simply no longer bundle the Inspector with it.
|
||||
|
||||
You can also now use the Appium Inspector without downloading anything, by visiting the [web version of Appium Inspector](https://inspector.appiumpro.com). Note that to test against local servers, you'll need to start the server with `--allow-cors` so that the browser-based version of Appium Inspector can access your Appium server to start sessions.
|
||||
|
||||
## Major New Features
|
||||
|
||||
Apart from the breaking changes mentioned above, in this section is a list of some of the major new features you may wish to take advantage of with Appium 2.0.
|
||||
|
||||
### Plugins
|
||||
|
||||
#### :tada: _Server Plugins_
|
||||
|
||||
TODO
|
||||
|
||||
#### :tada: _Client Plugins_
|
||||
|
||||
TODO
|
||||
|
||||
### :tada: Install drivers and plugins from anywhere
|
||||
|
||||
TODO
|
||||
|
||||
### :tada: Configuration Files
|
||||
|
||||
Appium now supports _configuration files_ in addition to command-line arguments. In a nutshell, nearly all arguments which Appium 1.x required to be provided on the CLI are now able to be expressed via a configuration file. Configuration files may be in JSON, JS, or YAML format. See the [Config Guide](config.md) for a full explanation.
|
||||
|
||||
## Special Notes for Cloud Providers
|
||||
|
||||
The rest of this document has applied to Appium generally, but some of the architectural changes in Appium 2.0 will constitute breaking changes for Appium-related service providers, whether a cloud-based Appium host or an internal service. At the end of the day, the maintainer of the Appium server is responsible for installing and making available the various Appium drivers and plugins that end users may wish to use.
|
||||
|
||||
We encourage cloud providers to thoroughly read and understand our [recommendation for cloud
|
||||
provider capabilities](caps.md#special-notes-for-cloud-providers) in order to support user needs in
|
||||
an industry-compatible way!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/en
|
||||
edit_uri: edit/2.0/packages/appium/docs/en
|
||||
edit_uri: edit/master/packages/appium/docs/en
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: en
|
||||
@@ -50,6 +50,8 @@ nav:
|
||||
- ecosystem/build-drivers.md
|
||||
- ecosystem/build-plugins.md
|
||||
- ecosystem/build-docs.md
|
||||
- Contributing: contributing/index.md
|
||||
- Contributing:
|
||||
- contributing/index.md
|
||||
- contributing/config-system.md
|
||||
- More Appium Resources: resources.md
|
||||
- Issues: https://github.com/appium/appium/issues
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/ja
|
||||
edit_uri: edit/2.0/packages/appium/docs/ja
|
||||
edit_uri: edit/master/packages/appium/docs/ja
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: ja
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/zh
|
||||
edit_uri: edit/2.0/packages/appium/docs/zh
|
||||
edit_uri: edit/master/packages/appium/docs/zh
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: zh
|
||||
|
||||
@@ -19,7 +19,7 @@ const {version} = require('../../package.json');
|
||||
|
||||
const branch = process.env.APPIUM_DOCS_BRANCH || DOCS_BRANCH;
|
||||
const prefix = process.env.APPIUM_DOCS_PREFIX || DOCS_PREFIX;
|
||||
const remote = process.env.APPIUM_DOCS_PREFIX || DOCS_REMOTE;
|
||||
const remote = process.env.APPIUM_DOCS_REMOTE || DOCS_REMOTE;
|
||||
|
||||
const shouldPush = !!process.env.APPIUM_DOCS_PUBLISH;
|
||||
|
||||
|
||||
@@ -783,6 +783,11 @@ class AppiumDriver extends DriverCore {
|
||||
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {import('@appium/types').RouteMatcher[]}
|
||||
*/
|
||||
getProxyAvoidList(sessionId) {
|
||||
const dstSession = this.sessions[sessionId];
|
||||
return dstSession ? dstSession.getProxyAvoidList() : [];
|
||||
|
||||
@@ -214,8 +214,8 @@ function makeRunArgs(type) {
|
||||
default: null,
|
||||
type: 'str',
|
||||
help:
|
||||
`Name of the script to run from the ${type}. The script name must be cached ` +
|
||||
`inside the "scripts" field under "appium" inside the ${type}'s "package.json" file`,
|
||||
`Name of the script to run from the ${type}. The script name must be a key ` +
|
||||
`inside the "appium.scripts" field inside the ${type}'s "package.json" file`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -33,8 +33,8 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
return await super._update({installSpec: driver, unsafe});
|
||||
}
|
||||
|
||||
async run({driver, scriptName}) {
|
||||
return await super._run({installSpec: driver, scriptName});
|
||||
async run({driver, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: driver, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
getPostInstallText({extName, extData}) {
|
||||
|
||||
@@ -576,13 +576,14 @@ class ExtensionCommand {
|
||||
// this is a helper method, 'ext' is assumed to already be installed here, and of the npm
|
||||
// install type
|
||||
const {version, pkgName} = this.config.installedExtensions[ext];
|
||||
/** @type {string?} */
|
||||
let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
|
||||
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
||||
this.config.appiumHome,
|
||||
pkgName,
|
||||
version
|
||||
);
|
||||
if (!util.compareVersions(unsafeUpdate, '>', version)) {
|
||||
if (unsafeUpdate !== null && !util.compareVersions(unsafeUpdate, '>', version)) {
|
||||
// the latest version is not greater than the current version, so there's no possible update
|
||||
unsafeUpdate = null;
|
||||
safeUpdate = null;
|
||||
@@ -628,7 +629,7 @@ class ExtensionCommand {
|
||||
* @param {RunOptions} opts
|
||||
* @return {Promise<RunOutput>}
|
||||
*/
|
||||
async _run({installSpec, scriptName}) {
|
||||
async _run({installSpec, scriptName, extraArgs = []}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
||||
}
|
||||
@@ -657,7 +658,7 @@ class ExtensionCommand {
|
||||
);
|
||||
}
|
||||
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName], ...extraArgs], {
|
||||
cwd: this.config.getInstallPath(installSpec),
|
||||
});
|
||||
|
||||
@@ -755,6 +756,7 @@ export {ExtensionCommand};
|
||||
* @typedef RunOptions
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,9 @@ export const commandClasses = Object.freeze(
|
||||
* Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
|
||||
* can be represented as a JS object.
|
||||
*
|
||||
* @param {Object} args - JS object where the key is the parameter name (as defined in
|
||||
* @param {import('appium/types').Args<import('appium/types').WithExtSubcommand>} args - JS object where the key is the parameter name (as defined in
|
||||
* driver-parser.js)
|
||||
* @template {import('../extension/manifest').ExtensionType} ExtType
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {import('../extension/extension-config').ExtensionConfig<ExtType>} config - Extension config object
|
||||
*/
|
||||
async function runExtensionCommand(args, config) {
|
||||
@@ -30,6 +30,7 @@ async function runExtensionCommand(args, config) {
|
||||
throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
|
||||
}
|
||||
let {json, suppressOutput} = args;
|
||||
json = Boolean(json);
|
||||
if (suppressOutput) {
|
||||
json = true;
|
||||
}
|
||||
@@ -56,6 +57,17 @@ async function runExtensionCommand(args, config) {
|
||||
export {runExtensionCommand};
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').ExtensionType} ExtType
|
||||
* @typedef {ExtType extends import('@appium/types').DriverType ? import('@appium/types').Class<DriverCommand> : ExtType extends import('@appium/types').PluginType ? import('@appium/types').Class<PluginCommand> : never} ExtCommand
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {ExtType extends DriverType ? Class<DriverCommand> : ExtType extends PluginType ? Class<PluginCommand> : never} ExtCommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('@appium/types').Class<T>} Class
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,8 @@ import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
|
||||
import {rootDir} from '../config';
|
||||
import {getExtensionArgs, getServerArgs} from './args';
|
||||
|
||||
export const EXTRA_ARGS = 'extraArgs';
|
||||
|
||||
/**
|
||||
* If the parsed args do not contain any of these values, then we
|
||||
* will automatially inject the `server` subcommand.
|
||||
@@ -97,8 +99,17 @@ class ArgParser {
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = this.parser.parse_args(args);
|
||||
return ArgParser._transformParsedArgs(parsed);
|
||||
const parsed = this.parser.parse_known_args(args);
|
||||
const [knownArgs, unknownArgs] = parsed;
|
||||
if (
|
||||
unknownArgs?.length &&
|
||||
(knownArgs.driverCommand === 'run' || knownArgs.pluginCommand === 'run')
|
||||
) {
|
||||
return ArgParser._transformParsedArgs(knownArgs, unknownArgs);
|
||||
} else if (unknownArgs?.length) {
|
||||
throw new Error(`[ERROR] Unrecognized arguments: ${unknownArgs.join(' ')}`);
|
||||
}
|
||||
return ArgParser._transformParsedArgs(knownArgs);
|
||||
} catch (err) {
|
||||
if (this.debug) {
|
||||
throw err;
|
||||
@@ -123,10 +134,11 @@ class ArgParser {
|
||||
*
|
||||
* E.g., `{'driver-foo-bar': baz}` becomes `{driver: {foo: {bar: 'baz'}}}`
|
||||
* @param {object} args
|
||||
* @param {string[]} [unknownArgs]
|
||||
* @returns {object}
|
||||
*/
|
||||
static _transformParsedArgs(args) {
|
||||
return _.reduce(
|
||||
static _transformParsedArgs(args, unknownArgs = []) {
|
||||
const result = _.reduce(
|
||||
args,
|
||||
(unpacked, value, key) => {
|
||||
if (!_.isUndefined(value) && hasArgSpec(key)) {
|
||||
@@ -140,6 +152,8 @@ class ArgParser {
|
||||
},
|
||||
{}
|
||||
);
|
||||
result[EXTRA_ARGS] = unknownArgs;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +197,7 @@ class ArgParser {
|
||||
* @param {import('argparse').SubParser} subParsers
|
||||
*/
|
||||
static _addExtensionCommandsToParser(subParsers) {
|
||||
for (const type of [DRIVER_TYPE, PLUGIN_TYPE]) {
|
||||
for (const type of /** @type {[DriverType, PluginType]} */ ([DRIVER_TYPE, PLUGIN_TYPE])) {
|
||||
const extParser = subParsers.add_parser(type, {
|
||||
add_help: true,
|
||||
help: `Access the ${type} management CLI commands`,
|
||||
@@ -195,6 +209,9 @@ class ArgParser {
|
||||
dest: `${type}Command`,
|
||||
});
|
||||
const extensionArgs = getExtensionArgs();
|
||||
/**
|
||||
* @type { {command: import('appium/types').CliExtensionSubcommand, args: import('./args').ArgumentDefinitions, help: string}[] }
|
||||
*/
|
||||
const parserSpecs = [
|
||||
{
|
||||
command: 'list',
|
||||
@@ -254,3 +271,8 @@ function getParser(debug) {
|
||||
}
|
||||
|
||||
export {getParser, ArgParser};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
@@ -33,8 +33,8 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
return await super._update({installSpec: plugin, unsafe});
|
||||
}
|
||||
|
||||
async run({plugin, scriptName}) {
|
||||
return await super._run({installSpec: plugin, scriptName});
|
||||
async run({plugin, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: plugin, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
getPostInstallText({extName, extData}) {
|
||||
|
||||
@@ -161,7 +161,9 @@ export function normalizeConfig(config) {
|
||||
|
||||
return _.mapValues(mappedObj, (value, property) => {
|
||||
const nextSection = section ? `${section}.${property}` : property;
|
||||
return isSchemaTypeObject(value) ? normalize(config, nextSection) : value;
|
||||
return isSchemaTypeObject(schema.properties?.[property])
|
||||
? normalize(config, nextSection)
|
||||
: value;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -169,7 +171,7 @@ export function normalizeConfig(config) {
|
||||
* Returns `true` if the schema prop references an object, or if it's an object itself
|
||||
* @param {import('ajv').SchemaObject|object} schema - Referencing schema object
|
||||
*/
|
||||
const isSchemaTypeObject = (schema) => Boolean(schema.properties);
|
||||
const isSchemaTypeObject = (schema) => Boolean(schema?.properties || schema?.type === 'object');
|
||||
|
||||
return normalize(config);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import _ from 'lodash';
|
||||
import {system, fs} from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import {exec} from 'teen_process';
|
||||
import logger from './logger';
|
||||
import semver from 'semver';
|
||||
import findUp from 'find-up';
|
||||
import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
|
||||
@@ -12,6 +11,7 @@ const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
const APPIUM_VER = npmPackage.version;
|
||||
const MIN_NODE_VERSION = npmPackage.engines.node;
|
||||
const MIN_NPM_VERSION = npmPackage.engines.npm;
|
||||
|
||||
const GIT_META_ROOT = '.git';
|
||||
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
|
||||
@@ -28,6 +28,15 @@ function getNodeVersion() {
|
||||
return /** @type {import('semver').SemVer} */ (semver.coerce(process.version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns version of `npm`
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getNpmVersion() {
|
||||
const {stdout} = await exec(system.isWindows() ? 'npm.cmd' : 'npm', ['--version']);
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [useGithubApiFallback]
|
||||
*/
|
||||
@@ -133,7 +142,18 @@ function getBuildInfo() {
|
||||
function checkNodeOk() {
|
||||
const version = getNodeVersion();
|
||||
if (!semver.satisfies(version, MIN_NODE_VERSION)) {
|
||||
logger.errorAndThrow(`Node version must be ${MIN_NODE_VERSION}. Currently ${version.version}`);
|
||||
throw new Error(
|
||||
`Node version must be at least ${MIN_NODE_VERSION}; current is ${version.version}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkNpmOk() {
|
||||
const npmVersion = await getNpmVersion();
|
||||
if (!semver.satisfies(npmVersion, MIN_NPM_VERSION)) {
|
||||
throw new Error(
|
||||
`npm version must be at least ${MIN_NPM_VERSION}; current is ${npmVersion}. Run "npm install -g npm" to upgrade.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-31
@@ -4,7 +4,7 @@ import {init as logsinkInit} from './logsink'; // this import needs to come firs
|
||||
import logger from './logger'; // logger needs to remain second
|
||||
// @ts-ignore
|
||||
import {routeConfiguringFunction as makeRouter, server as baseServer} from '@appium/base-driver';
|
||||
import {logger as logFactory, util, env, fs} from '@appium/support';
|
||||
import {logger as logFactory, util, env} from '@appium/support';
|
||||
import {asyncify} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {AppiumDriver} from './appium';
|
||||
@@ -15,21 +15,20 @@ import {
|
||||
checkNodeOk,
|
||||
getGitRev,
|
||||
getNonDefaultServerArgs,
|
||||
rootDir,
|
||||
showConfig,
|
||||
showBuildInfo,
|
||||
validateTmpDir,
|
||||
warnNodeDeprecations,
|
||||
checkNpmOk,
|
||||
} from './config';
|
||||
import {readConfigFile} from './config-file';
|
||||
import {loadExtensions, getActivePlugins, getActiveDrivers} from './extension';
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from './constants';
|
||||
import registerNode from './grid-register';
|
||||
import {getDefaultsForSchema, validate} from './schema/schema';
|
||||
import {inspect} from './utils';
|
||||
import path from 'path';
|
||||
import {inspect, adjustNodePath} from './utils';
|
||||
|
||||
const {resolveAppiumHome, hasAppiumDependency} = env;
|
||||
const {resolveAppiumHome} = env;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,6 +38,7 @@ const {resolveAppiumHome, hasAppiumDependency} = env;
|
||||
async function preflightChecks(args, throwInsteadOfExit = false) {
|
||||
try {
|
||||
checkNodeOk();
|
||||
await checkNpmOk();
|
||||
if (args.longStacktrace) {
|
||||
require('longjohn').async_trace_limit = -1;
|
||||
}
|
||||
@@ -172,18 +172,7 @@ function areServerCommandArgs(args) {
|
||||
async function init(args) {
|
||||
const appiumHome = args?.appiumHome ?? (await resolveAppiumHome());
|
||||
|
||||
// this is here so extensions installed in e.g., `~/.appium` can find the peer dependency
|
||||
// of `appium`, which lives wherever it lives (see `rootDir`).
|
||||
if (!(await hasAppiumDependency(appiumHome))) {
|
||||
try {
|
||||
await fs.mkdirp(path.join(appiumHome, 'node_modules'));
|
||||
await fs.symlink(rootDir, path.join(appiumHome, 'node_modules', 'appium'), 'junction');
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw new Error(`Unable to create symlink to appium: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
adjustNodePath();
|
||||
|
||||
const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
|
||||
|
||||
@@ -222,20 +211,7 @@ async function init(args) {
|
||||
// 1. command line args
|
||||
// 2. config file
|
||||
// 3. defaults from config file.
|
||||
if (!areServerCommandArgs(preConfigArgs)) {
|
||||
// if the user has requested the 'driver' CLI, don't run the normal server,
|
||||
// but instead pass control to the driver CLI
|
||||
if (preConfigArgs.subcommand === DRIVER_TYPE) {
|
||||
await runExtensionCommand(preConfigArgs, driverConfig);
|
||||
return {};
|
||||
}
|
||||
if (preConfigArgs.subcommand === PLUGIN_TYPE) {
|
||||
await runExtensionCommand(preConfigArgs, pluginConfig);
|
||||
return {};
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return {}; // should never happen
|
||||
} else {
|
||||
if (areServerCommandArgs(preConfigArgs)) {
|
||||
const defaults = getDefaultsForSchema(false);
|
||||
|
||||
/** @type {ParsedArgs} */
|
||||
@@ -282,6 +258,22 @@ async function init(args) {
|
||||
driverConfig,
|
||||
pluginConfig,
|
||||
});
|
||||
} else {
|
||||
const extensionCommandArgs = /** @type {Args<import('appium/types').WithExtSubcommand>} */ (
|
||||
preConfigArgs
|
||||
);
|
||||
// if the user has requested the 'driver' CLI, don't run the normal server,
|
||||
// but instead pass control to the driver CLI
|
||||
if (preConfigArgs.subcommand === DRIVER_TYPE) {
|
||||
await runExtensionCommand(extensionCommandArgs, driverConfig);
|
||||
return {};
|
||||
}
|
||||
if (preConfigArgs.subcommand === PLUGIN_TYPE) {
|
||||
await runExtensionCommand(extensionCommandArgs, pluginConfig);
|
||||
return {};
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return {}; // should never happen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +306,7 @@ async function main(args) {
|
||||
const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
|
||||
const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
|
||||
|
||||
/** @type {import('@appium/base-driver').ServerOpts} */
|
||||
const serverOpts = {
|
||||
routeConfiguringFunction,
|
||||
port: parsedArgs.port,
|
||||
@@ -322,6 +315,7 @@ async function main(args) {
|
||||
basePath: parsedArgs.basePath,
|
||||
serverUpdaters,
|
||||
extraMethodMap,
|
||||
cliArgs: parsedArgs,
|
||||
};
|
||||
if (parsedArgs.keepAliveTimeout) {
|
||||
serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
|
||||
|
||||
@@ -94,8 +94,9 @@ export const transformers = {
|
||||
json = readFileSync(jsonOrPath, 'utf8');
|
||||
loadedFromFile = true;
|
||||
} catch (err) {
|
||||
// unreadable files don't count... but other problems do.
|
||||
if (err.code !== 'ENOENT') {
|
||||
// unreadable files don't count.
|
||||
// also `ENAMETOOLONG` can happen if we try to open a file that's a huge JSON string.
|
||||
if (err.code !== 'ENOENT' && err.code !== 'ENAMETOOLONG') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import {processCapabilities, PROTOCOLS} from '@appium/base-driver';
|
||||
import {inspect as dump} from 'util';
|
||||
import {node} from '@appium/support';
|
||||
import path from 'path';
|
||||
|
||||
const W3C_APPIUM_PREFIX = 'appium';
|
||||
|
||||
@@ -209,6 +211,63 @@ function getPackageVersion(pkgName) {
|
||||
return pkgInfo.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts NODE_PATH environment variable,
|
||||
* so drivers and plugins could load their peer dependencies.
|
||||
* Read https://nodejs.org/api/modules.html#loading-from-the-global-folders
|
||||
* for more details.
|
||||
* @returns {void}
|
||||
*/
|
||||
function adjustNodePath() {
|
||||
const selfRoot = node.getModuleRootSync('appium', __filename);
|
||||
if (!selfRoot || path.dirname(selfRoot).length >= selfRoot.length) {
|
||||
return;
|
||||
}
|
||||
const nodeModulesRoot = path.dirname(selfRoot);
|
||||
|
||||
const refreshRequirePaths = () => {
|
||||
try {
|
||||
// ! This hack allows us to avoid modification of import
|
||||
// ! statements in client modules. It uses a private API though,
|
||||
// ! so it could break (maybe, eventually).
|
||||
// See https://gist.github.com/branneman/8048520#7-the-hack
|
||||
// @ts-ignore
|
||||
require('module').Module._initPaths();
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.info(`Module init paths cannot be refreshed. Original error: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!process.env.NODE_PATH) {
|
||||
process.env.NODE_PATH = nodeModulesRoot;
|
||||
if (refreshRequirePaths()) {
|
||||
logger.info(`Setting NODE_PATH to '${nodeModulesRoot}'`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
delete process.env.NODE_PATH;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const nodePathParts = process.env.NODE_PATH.split(path.delimiter);
|
||||
if (nodePathParts.includes(nodeModulesRoot)) {
|
||||
logger.info(`NODE_PATH already includes '${nodeModulesRoot}'`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
return;
|
||||
}
|
||||
|
||||
nodePathParts.push(nodeModulesRoot);
|
||||
process.env.NODE_PATH = nodePathParts.join(path.delimiter);
|
||||
if (refreshRequirePaths()) {
|
||||
logger.info(`Adding '${nodeModulesRoot}' to NODE_PATH`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
process.env.NODE_PATH = _.without(nodePathParts, nodeModulesRoot).join(path.delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the initial values of Appium settings from the given capabilities argument.
|
||||
* Each setting item must satisfy the following format:
|
||||
@@ -250,6 +309,7 @@ export {
|
||||
getPackageVersion,
|
||||
pullSettings,
|
||||
removeAppiumPrefixes,
|
||||
adjustNodePath,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.41",
|
||||
"version": "2.0.0-beta.44",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -37,7 +37,6 @@
|
||||
"driver.*",
|
||||
"support.*",
|
||||
"plugin.*",
|
||||
"test.*",
|
||||
"scripts/autoinstall-extensions.js",
|
||||
"types"
|
||||
],
|
||||
@@ -54,26 +53,25 @@
|
||||
"postinstall": "node ./scripts/autoinstall-extensions.js",
|
||||
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
|
||||
"prepare": "npm run build",
|
||||
"publish:docs": "APPIUM_DOCS_PUBLISH=1 npm run build:docs",
|
||||
"publish:docs": "cross-env APPIUM_DOCS_PUBLISH=1 npm run build:docs",
|
||||
"test": "npm run test:unit",
|
||||
"test:e2e": "mocha --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:smoke": "node ./index.js --show-build-info",
|
||||
"test:smoke": "cross-env APPIUM_HOME=./local_appium_home node ./index.js driver install uiautomator2 && cross-env APPIUM_HOME=./local_appium_home node ./index.js driver list",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.6.0",
|
||||
"@appium/base-plugin": "^1.10.0",
|
||||
"@appium/docutils": "^0.0.8",
|
||||
"@appium/schema": "^0.0.8",
|
||||
"@appium/support": "^2.59.3",
|
||||
"@appium/test-support": "^1.4.0",
|
||||
"@appium/types": "^0.3.0",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/base-driver": "^8.7.1",
|
||||
"@appium/base-plugin": "^1.10.3",
|
||||
"@appium/docutils": "^0.0.11",
|
||||
"@appium/schema": "^0.0.9",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@sidvind/better-ajv-errors": "2.0.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/fancy-log": "2.0.0",
|
||||
"@types/semver": "7.3.10",
|
||||
"@types/semver": "7.3.12",
|
||||
"@types/teen_process": "1.16.1",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.11.0",
|
||||
@@ -83,6 +81,7 @@
|
||||
"asyncbox": "2.9.2",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
"lilconfig": "2.0.6",
|
||||
"lodash": "4.17.21",
|
||||
@@ -94,14 +93,14 @@
|
||||
"semver": "7.3.7",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0",
|
||||
"type-fest": "2.17.0",
|
||||
"type-fest": "2.19.0",
|
||||
"winston": "3.8.1",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
export * from '@appium/test-support';
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').SessionHelpers} SessionHelpers
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').E2ESetupOpts} E2ESetupOpts
|
||||
*/
|
||||
@@ -1,13 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* This module is here to re-export `@appium/test-support` for Appium extensions.
|
||||
*
|
||||
* @see https://npm.im/@appium/test-support
|
||||
* @example
|
||||
* const { getPort } = require('appium/test');
|
||||
*/
|
||||
|
||||
module.exports = require('@appium/test-support');
|
||||
@@ -1,5 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import getPort from 'get-port';
|
||||
import {exec} from 'teen_process';
|
||||
import {npm, env, fs, tempDir, util} from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
import path from 'path';
|
||||
@@ -15,13 +17,14 @@ import {
|
||||
KNOWN_DRIVERS,
|
||||
PLUGIN_TYPE,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
import {FAKE_DRIVER_DIR, PACKAGE_ROOT, resolveFixture} from '../helpers';
|
||||
import {
|
||||
installLocalExtension,
|
||||
runAppiumJson,
|
||||
runAppiumRaw,
|
||||
readAppiumArgErrorFixture,
|
||||
formatAppiumArgErrorOutput,
|
||||
EXECUTABLE,
|
||||
} from './e2e-helpers';
|
||||
|
||||
const {MANIFEST_RELATIVE_PATH} = env;
|
||||
@@ -38,6 +41,22 @@ describe('CLI behavior', function () {
|
||||
resolveFixture('test-driver-invalid-peer-dep/package.json')
|
||||
);
|
||||
|
||||
describe('when appium is already installed', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
it('should not duplicate it as peer dependencies for drivers', async function () {
|
||||
const run = runAppiumJson(appiumHome);
|
||||
await run([DRIVER_TYPE, INSTALL, 'appium-fake-driver', '--source', 'npm']);
|
||||
await fs.exists(path.join(appiumHome, 'node_modules', 'appium')).should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when appium is a dependency of the project in the current working directory', function () {
|
||||
/** @type {string} */
|
||||
let hashPath;
|
||||
@@ -152,7 +171,7 @@ describe('CLI behavior', function () {
|
||||
|
||||
it('should update package.json', async function () {
|
||||
const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8'));
|
||||
expect(newPkg).to.have.nested.property('devDependencies.test-driver');
|
||||
expect(newPkg).to.have.nested.property('devDependencies.@appium/test-driver');
|
||||
});
|
||||
|
||||
it('should update the hash', async function () {
|
||||
@@ -170,7 +189,7 @@ describe('CLI behavior', function () {
|
||||
|
||||
it('should actually install both drivers', function () {
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/test-driver')).not.to.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -267,6 +286,14 @@ describe('CLI behavior', function () {
|
||||
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST, '--updates'], {});
|
||||
stderr.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
|
||||
});
|
||||
|
||||
describe('if a driver is not published to npm', function () {
|
||||
it('should not throw an error', async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
await expect(runList(['--updates'])).not.to.be.rejected;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(INSTALL, function () {
|
||||
@@ -438,28 +465,49 @@ describe('CLI behavior', function () {
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
const driverName = 'fake';
|
||||
before(async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
it('should run a valid driver, valid script, and result in success', async function () {
|
||||
const driverName = 'fake';
|
||||
|
||||
describe('when the driver and script is valid', function () {
|
||||
const scriptName = 'fake-success';
|
||||
const out = await runRun([driverName, scriptName]);
|
||||
out.should.not.have.property('error');
|
||||
|
||||
describe('when the script completes successfully', function () {
|
||||
it('should result in success', async function () {
|
||||
const out = await runRun([driverName, scriptName]);
|
||||
out.should.not.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the script fails', function () {
|
||||
it('should return an error', async function () {
|
||||
const out = await runRun([driverName, 'fake-error']);
|
||||
out.should.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed extra arguments', function () {
|
||||
it('should pass them to the script', async function () {
|
||||
const out = await runRun([driverName, scriptName, '--foo', '--bar']);
|
||||
out.should.not.have.property('error');
|
||||
out.output.should.match(/--foo --bar/);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should run a valid driver, valid error prone script, and return error in json', async function () {
|
||||
const driverName = 'fake';
|
||||
const out = await runRun([driverName, 'fake-error']);
|
||||
out.should.have.property('error');
|
||||
|
||||
describe('when the driver is valid but the script is not', function () {
|
||||
it('should throw an error', async function () {
|
||||
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
it('should take a valid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'fake';
|
||||
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
it('should take an invalid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'foo';
|
||||
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
|
||||
|
||||
describe('when the driver and script are invalid', function () {
|
||||
it('should throw an error', async function () {
|
||||
const driverName = 'foo';
|
||||
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -502,7 +550,41 @@ describe('CLI behavior', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('argument error handling', function () {
|
||||
describe('argument parsing', function () {
|
||||
describe('when the user provides a very long string for an arg accepting a blob or filename', function () {
|
||||
describe('when the very long string is a JSON blob', function () {
|
||||
it('should not throw an ENAMETOOLONG exception', async function () {
|
||||
this.timeout(10000);
|
||||
const capsArg = JSON.stringify({
|
||||
'appium:platformName': 'ANDROID',
|
||||
'appium:platformVersion': '11',
|
||||
'appium:deviceName':
|
||||
'Spicy jalapeno bacon ipsum dolor amet deserunt tempor pork belly aliqua drumstick, occaecat dolor venison et labore. Rump meatball pork chop tail. Consequat adipisicing kielbasa occaecat laborum pig. Qui pork chop chicken nostrud boudin fugiat. Proident ut culpa, chuck nulla sunt pastrami ut tri-tip. Buffalo dolore adipisicing, labore venison elit beef fatback kevin burgdoggen tail pancetta filet mignon. Dolor turducken rump, anim kevin sunt exercitation ham filet mignon beef ribs ad officia eiusmod id cillum.',
|
||||
});
|
||||
|
||||
await expect(
|
||||
exec(
|
||||
process.execPath,
|
||||
[
|
||||
EXECUTABLE,
|
||||
'-pa=/wd/hub',
|
||||
'--session-override',
|
||||
'--local-timezone',
|
||||
'--relaxed-security',
|
||||
`--default-capabilities=${capsArg}`,
|
||||
'--port',
|
||||
await getPort(),
|
||||
],
|
||||
{
|
||||
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
|
||||
cwd: PACKAGE_ROOT,
|
||||
timeout: 5000,
|
||||
}
|
||||
)
|
||||
).to.be.rejectedWith(Error, /timed out/);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when the user provides an string where a number was expected', function () {
|
||||
describe('when color output is supported', function () {
|
||||
it('should output a fancy error message', async function () {
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('config file behavior', function () {
|
||||
it('should return a valid config object', async function () {
|
||||
const result = await readConfigFile(GOOD_FILEPATH);
|
||||
result.should.deep.equal({
|
||||
config: require(GOOD_FILEPATH),
|
||||
config: normalizeConfig(require(GOOD_FILEPATH)),
|
||||
filepath: GOOD_FILEPATH,
|
||||
errors: [],
|
||||
});
|
||||
@@ -74,7 +74,7 @@ describe('config file behavior', function () {
|
||||
it('should return a valid config object', async function () {
|
||||
const result = await readConfigFile(SECURITY_ARRAY_FILEPATH);
|
||||
result.should.deep.equal({
|
||||
config: require(SECURITY_ARRAY_FILEPATH),
|
||||
config: normalizeConfig(require(SECURITY_ARRAY_FILEPATH)),
|
||||
filepath: SECURITY_ARRAY_FILEPATH,
|
||||
errors: [],
|
||||
});
|
||||
|
||||
@@ -114,6 +114,14 @@ describe('FakeDriver - via HTTP', function () {
|
||||
const {data} = await axios.get(`${testServerBaseUrl}/fakedriver`);
|
||||
data.should.eql({fakedriver: 'fakeResponse'});
|
||||
});
|
||||
it('should update the server with cliArgs', async function () {
|
||||
// we don't need to check the entire object, since it's large, but we can ensure an
|
||||
// arg got through.
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/fakedriverCliArgs`)).data.should.have.property(
|
||||
'appiumHome',
|
||||
appiumHome
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli args handling for empty args', function () {
|
||||
|
||||
@@ -24,7 +24,7 @@ const wdOpts = {
|
||||
capabilities: W3C_PREFIXED_CAPS,
|
||||
};
|
||||
const FAKE_DRIVER_DIR = path.join(PROJECT_ROOT, 'packages', 'fake-driver');
|
||||
const FAKE_PLUGIN_DIR = path.join(PROJECT_ROOT, 'node_modules', '@appium', 'fake-plugin');
|
||||
const FAKE_PLUGIN_DIR = path.join(PROJECT_ROOT, 'packages', 'fake-plugin');
|
||||
|
||||
describe('FakePlugin', function () {
|
||||
/** @type {string} */
|
||||
@@ -150,9 +150,11 @@ describe('FakePlugin', function () {
|
||||
describe(`with plugin registered via type ${registrationType}`, function () {
|
||||
/** @type {AppiumServer} */
|
||||
let server;
|
||||
/** @type {import('type-fest').LiteralUnion<'all', string>[]} */
|
||||
let usePlugins;
|
||||
before(async function () {
|
||||
// then start server if we need to
|
||||
const usePlugins = registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all'];
|
||||
usePlugins = registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all'];
|
||||
const args = {
|
||||
appiumHome,
|
||||
port,
|
||||
@@ -171,7 +173,12 @@ describe('FakePlugin', function () {
|
||||
const res = {fake: 'fakeResponse'};
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/fake`)).data.should.eql(res);
|
||||
});
|
||||
|
||||
it('should update the server with cliArgs', async function () {
|
||||
const res = usePlugins;
|
||||
// we don't need to check the entire object, since it's large, but we can ensure an
|
||||
// arg got through.
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/cliArgs`)).data.usePlugins.should.eql(res);
|
||||
});
|
||||
it('should modify the method map with new commands', async function () {
|
||||
const driver = await wdio(wdOpts);
|
||||
const {sessionId} = driver;
|
||||
|
||||
@@ -1 +1 @@
|
||||
main.js: error: unrecognized arguments: --pigs=sheep
|
||||
[ERROR] Unrecognized arguments: --pigs=sheep
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "test-driver",
|
||||
"name": "@appium/test-driver",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -33,6 +33,11 @@ describe('config-file', function () {
|
||||
*/
|
||||
let formatErrors;
|
||||
|
||||
/**
|
||||
* @type {import('appium/lib/config-file').normalizeConfig}
|
||||
*/
|
||||
let normalizeConfig;
|
||||
|
||||
/**
|
||||
* Mock instance of `lilconfig` containing stubs for
|
||||
* `lilconfig#search()` and `lilconfig#load`.
|
||||
@@ -93,7 +98,7 @@ describe('config-file', function () {
|
||||
// loads the `config-file` module using the lilconfig mock.
|
||||
// we only mock lilconfig because it'd otherwise be a pain in the rear to test
|
||||
// searching for config files, and it increases the likelihood that we'd load the wrong file.
|
||||
({readConfigFile, formatErrors} = rewiremock.proxy(
|
||||
({readConfigFile, formatErrors, normalizeConfig} = rewiremock.proxy(
|
||||
() => require('../../lib/config-file'),
|
||||
mocks
|
||||
));
|
||||
@@ -114,19 +119,19 @@ describe('config-file', function () {
|
||||
|
||||
it('should support yaml', async function () {
|
||||
const {config} = await readConfigFile(GOOD_YAML_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should support json', async function () {
|
||||
const {config} = await readConfigFile(GOOD_JSON_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should support js', async function () {
|
||||
const {config} = await readConfigFile(GOOD_JS_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
@@ -188,7 +193,7 @@ describe('config-file', function () {
|
||||
|
||||
it('should resolve with an object having `config` property and empty array of errors', function () {
|
||||
expect(result).to.deep.equal({
|
||||
config: GOOD_JSON_CONFIG,
|
||||
config: normalizeConfig(GOOD_JSON_CONFIG),
|
||||
errors: [],
|
||||
filepath: GOOD_JSON_CONFIG_FILEPATH,
|
||||
});
|
||||
@@ -291,7 +296,7 @@ describe('config-file', function () {
|
||||
it('should resolve with an object having `config` property and empty array of errors', function () {
|
||||
expect(result).to.deep.equal({
|
||||
errors: [],
|
||||
config: GOOD_JSON_CONFIG,
|
||||
config: normalizeConfig(GOOD_JSON_CONFIG),
|
||||
filepath: GOOD_JSON_CONFIG_FILEPATH,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('Config', function () {
|
||||
{
|
||||
config: {
|
||||
// @ts-expect-error
|
||||
server: {callbackAddress: 'quux'},
|
||||
server: {'callback-address': 'quux'},
|
||||
},
|
||||
},
|
||||
{port: 1234},
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
pullSettings,
|
||||
removeAppiumPrefixes,
|
||||
inspect,
|
||||
adjustNodePath,
|
||||
} from '../../lib/utils';
|
||||
import {BASE_CAPS, W3C_CAPS} from '../helpers';
|
||||
import _ from 'lodash';
|
||||
import {stripColors} from '@colors/colors';
|
||||
import {createSandbox} from 'sinon';
|
||||
import logger from '../../lib/logger';
|
||||
import {fs} from '@appium/support';
|
||||
|
||||
describe('utils', function () {
|
||||
describe('parseCapsForInnerDriver()', function () {
|
||||
@@ -252,4 +254,25 @@ describe('utils', function () {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adjustNodePath()', function () {
|
||||
const prevValue = process.env.NODE_PATH;
|
||||
|
||||
beforeEach(function () {
|
||||
if (process.env.NODE_PATH) {
|
||||
delete process.env.NODE_PATH;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (prevValue) {
|
||||
process.env.NODE_PATH = prevValue;
|
||||
}
|
||||
});
|
||||
|
||||
it('should adjust NODE_PATH', async function () {
|
||||
adjustNodePath();
|
||||
(await fs.exists(process.env.NODE_PATH)).should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"@appium/base-plugin": ["../base-plugin"],
|
||||
"@appium/types": ["../types"],
|
||||
"@appium/schema": ["../schema"],
|
||||
"@appium/test-support": ["../test-support"],
|
||||
"appium": ["."]
|
||||
},
|
||||
"checkJs": true
|
||||
|
||||
@@ -92,6 +92,21 @@ export interface ExtArgs extends WithExtSubcommand {
|
||||
* Subcommands of `plugin` subcommand
|
||||
*/
|
||||
pluginCommand?: CliExtensionSubcommand;
|
||||
|
||||
/**
|
||||
* Output JSON instead of human-readable text
|
||||
*/
|
||||
json?: boolean;
|
||||
|
||||
/**
|
||||
* Output nothing
|
||||
*/
|
||||
suppressOutput?: boolean;
|
||||
|
||||
/**
|
||||
* Extra args to pass to extension scripts
|
||||
*/
|
||||
extraArgs?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,37 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.7.1](https://github.com/appium/appium/compare/@appium/base-driver@8.7.0...@appium/base-driver@8.7.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.7.0](https://github.com/appium/appium/compare/@appium/base-driver@8.6.1...@appium/base-driver@8.7.0) (2022-08-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,base-driver,fake-driver,fake-plugin,test-support,types:** updateServer receives cliArgs param ([d4b9833](https://github.com/appium/appium/commit/d4b983328af21d1e5c27a91e438e7934eb152ab1)), closes [#17304](https://github.com/appium/appium/issues/17304)
|
||||
* **base-driver,fake-driver,appium:** add convenience methods for defining execute script overloads ([#17321](https://github.com/appium/appium/issues/17321)) ([337ec3e](https://github.com/appium/appium/commit/337ec3e7ba216dd6f8cdc88143ecaa4c75f5d266))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.1](https://github.com/appium/appium/compare/@appium/base-driver@8.6.0...@appium/base-driver@8.6.1) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.6.0](https://github.com/appium/appium/compare/@appium/base-driver@8.5.7...@appium/base-driver@8.6.0) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import _ from 'lodash';
|
||||
import {errors, makeArgs, checkParams} from '../../protocol';
|
||||
|
||||
/**
|
||||
* @param {SessionBase} Base
|
||||
* @returns {ExecuteBase}
|
||||
*/
|
||||
export function ExecuteMixin(Base) {
|
||||
/**
|
||||
* @implements {IExecuteCommands}
|
||||
*/
|
||||
class ExecuteCommands extends Base {
|
||||
/**
|
||||
* @param {string} script
|
||||
* @param {[Record<string, any>]|[]} protoArgs
|
||||
*/
|
||||
async executeMethod(script, protoArgs) {
|
||||
// the w3c protocol will give us an array of arguments to apply to a javascript function.
|
||||
// that's not what we're doing. we're going to look for a JS object as the first arg, so we
|
||||
// can perform validation on it. we'll ignore everything else.
|
||||
if (!protoArgs || !_.isArray(protoArgs) || protoArgs.length > 1) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`Did not get correct format of arguments for execute method. Expected zero or one ` +
|
||||
`arguments to execute script and instead received: ${JSON.stringify(protoArgs)}`
|
||||
);
|
||||
}
|
||||
let args = protoArgs[0] ?? {};
|
||||
if (!_.isPlainObject(args)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`Did not receive an appropriate execute method parameters object. It needs to be ` +
|
||||
`deserializable as a plain JS object`
|
||||
);
|
||||
}
|
||||
|
||||
const Driver = /** @type {DriverClass} */ (this.constructor);
|
||||
const availableScripts = _.keys(Driver.executeMethodMap);
|
||||
const commandMetadata = Driver.executeMethodMap[script];
|
||||
if (!commandMetadata) {
|
||||
throw new errors.UnsupportedOperationError(
|
||||
`Unsupported execute method '${script}'. Available methods ` +
|
||||
`are: ${availableScripts.join(', ')}`
|
||||
);
|
||||
}
|
||||
let argsToApply = [];
|
||||
if (!commandMetadata.params) {
|
||||
commandMetadata.params = {required: [], optional: []};
|
||||
} else {
|
||||
commandMetadata.params.required ??= [];
|
||||
commandMetadata.params.optional ??= [];
|
||||
checkParams(commandMetadata.params, args, null);
|
||||
}
|
||||
argsToApply = makeArgs({}, args, commandMetadata.params, null);
|
||||
return await this[Driver.executeMethodMap[script].command](...argsToApply);
|
||||
}
|
||||
}
|
||||
return ExecuteCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExecuteCommands} IExecuteCommands
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('./session').SessionBase} SessionBase
|
||||
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & import('@appium/types').LogCommands & import('@appium/types').SettingsCommands & import('@appium/types').SessionCommands & IExecuteCommands>} ExecuteBase
|
||||
*/
|
||||
@@ -6,6 +6,7 @@ import {LogMixin} from './log';
|
||||
import {SessionMixin} from './session';
|
||||
import {SettingsMixin} from './settings';
|
||||
import {TimeoutMixin} from './timeout';
|
||||
import {ExecuteMixin} from './execute';
|
||||
|
||||
/**
|
||||
* Applies all the mixins to the `BaseDriverBase` class.
|
||||
@@ -19,7 +20,8 @@ export function createBaseDriverClass(Base) {
|
||||
const WithLogCommands = LogMixin(WithFindCommands);
|
||||
const WithSettingsCommands = SettingsMixin(WithLogCommands);
|
||||
const WithSessionCommands = SessionMixin(WithSettingsCommands);
|
||||
return WithSessionCommands;
|
||||
const WithExecuteCommands = ExecuteMixin(WithSessionCommands);
|
||||
return WithExecuteCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,9 @@ class DriverCore {
|
||||
*/
|
||||
static baseVersion = BASEDRIVER_VER;
|
||||
|
||||
/** @type {ExecuteMethodMap} */
|
||||
static executeMethodMap = {};
|
||||
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
@@ -348,7 +351,7 @@ class DriverCore {
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {[string, RegExp][]}
|
||||
* @returns {import('@appium/types').RouteMatcher[]}
|
||||
*/
|
||||
getProxyAvoidList(sessionId) {
|
||||
return [];
|
||||
@@ -423,6 +426,7 @@ export {DriverCore};
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').Core} Core
|
||||
* @typedef {import('@appium/types').ExecuteMethodMap} ExecuteMethodMap
|
||||
* @typedef {import('@appium/types').ServerArgs} ServerArgs
|
||||
* @typedef {import('@appium/types').EventHistory} EventHistory
|
||||
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
|
||||
|
||||
@@ -40,6 +40,7 @@ async function server(opts) {
|
||||
routeConfiguringFunction,
|
||||
port,
|
||||
hostname,
|
||||
cliArgs,
|
||||
allowCors = true,
|
||||
basePath = DEFAULT_BASE_PATH,
|
||||
extraMethodMap = {},
|
||||
@@ -71,7 +72,7 @@ async function server(opts) {
|
||||
});
|
||||
// allow extensions to update the app and http server objects
|
||||
for (const updater of serverUpdaters) {
|
||||
await updater(app, appiumServer);
|
||||
await updater(app, appiumServer, cliArgs);
|
||||
}
|
||||
|
||||
// once all configurations and updaters have been applied, make sure to set up a catchall
|
||||
@@ -292,6 +293,7 @@ export {server, configureServer, normalizeBasePath};
|
||||
* @typedef ServerOpts
|
||||
* @property {RouteConfiguringFunction} routeConfiguringFunction
|
||||
* @property {number} port
|
||||
* @property {import('@appium/types').ServerArgs} cliArgs
|
||||
* @property {string} [hostname]
|
||||
* @property {boolean} [allowCors]
|
||||
* @property {string} [basePath]
|
||||
|
||||
@@ -37,3 +37,7 @@ export {
|
||||
|
||||
// Web socket helpers
|
||||
export {DEFAULT_WS_PATHNAME_PREFIX} from './express/websocket';
|
||||
|
||||
/**
|
||||
* @typedef {import('./express/server').ServerOpts} ServerOpts
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
CREATE_SESSION_COMMAND,
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
makeArgs,
|
||||
checkParams,
|
||||
} from './protocol';
|
||||
import {NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, routeToCommandName} from './routes';
|
||||
import {errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode} from './errors';
|
||||
@@ -13,6 +15,8 @@ export {
|
||||
routeConfiguringFunction,
|
||||
errors,
|
||||
isErrorType,
|
||||
makeArgs,
|
||||
checkParams,
|
||||
errorFromMJSONWPStatusCode,
|
||||
errorFromW3CJsonCode,
|
||||
ALL_COMMANDS,
|
||||
|
||||
@@ -94,7 +94,7 @@ function unwrapParams(paramSets, jsonObj) {
|
||||
return res;
|
||||
}
|
||||
|
||||
function checkParams(paramSets, jsonObj, protocol) {
|
||||
export function checkParams(paramSets, jsonObj, protocol) {
|
||||
let requiredParams = [];
|
||||
let optionalParams = [];
|
||||
let receivedParams = _.keys(jsonObj);
|
||||
@@ -162,7 +162,7 @@ function checkParams(paramSets, jsonObj, protocol) {
|
||||
* on handling parameters. This method returns an array of arguments which will
|
||||
* be applied to a command.
|
||||
*/
|
||||
function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
||||
export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
||||
// We want to pass the "url" parameters to the commands in reverse order
|
||||
// since the command will sometimes want to ignore, say, the sessionId.
|
||||
// This has the effect of putting sessionId last, which means in JS we can
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "8.6.0",
|
||||
"version": "8.7.1",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,8 +45,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.1.5",
|
||||
"@types/bluebird": "3.5.36",
|
||||
@@ -62,17 +62,17 @@
|
||||
"express": "4.18.1",
|
||||
"http-status-codes": "2.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.13.1",
|
||||
"lru-cache": "7.14.0",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "2.17.0",
|
||||
"type-fest": "2.19.0",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getTestPort,
|
||||
TEST_HOST,
|
||||
createSessionHelpers,
|
||||
} from '@appium/test-support';
|
||||
} from '@appium/driver-test-support';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -68,5 +68,5 @@ describe('BaseDriver', function () {
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').SessionHelpers} SessionHelpers
|
||||
* @typedef {import('@appium/driver-test-support').SessionHelpers} SessionHelpers
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ import finalhandler from 'finalhandler';
|
||||
import serveStatic from 'serve-static';
|
||||
import contentDisposition from 'content-disposition';
|
||||
import B from 'bluebird';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
function getFixture(file) {
|
||||
// XXX: __dirname disallowed in native ESM
|
||||
|
||||
@@ -3,7 +3,7 @@ import {server, routeConfiguringFunction, DEFAULT_WS_PATHNAME_PREFIX} from '../.
|
||||
import {FakeDriver} from '../protocol/fake-driver';
|
||||
import WebSocket from 'ws';
|
||||
import B from 'bluebird';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
describe('Websockets (e2e)', function () {
|
||||
let baseServer;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {server} from '../../../lib';
|
||||
import axios from 'axios';
|
||||
import {createSandbox} from 'sinon';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
describe('server', function () {
|
||||
let hwServer;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {server, routeConfiguringFunction, errors, JWProxy, BaseDriver} from '../../../lib';
|
||||
import {FakeDriver} from './fake-driver';
|
||||
import axios from 'axios';
|
||||
@@ -7,7 +5,7 @@ import {createSandbox} from 'sinon';
|
||||
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
|
||||
import {createProxyServer} from './helpers';
|
||||
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../../../lib/constants';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
let port;
|
||||
let baseUrl;
|
||||
|
||||
@@ -22,6 +22,14 @@ describe('DeviceSettings', function () {
|
||||
expect(() => new DeviceSettings()).not.to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not hold on to reference of defaults in constructor', function () {
|
||||
let obj = {foo: 'bar'};
|
||||
let d1 = new DeviceSettings(obj);
|
||||
let d2 = new DeviceSettings(obj);
|
||||
d1.getSettings().foo = 'baz';
|
||||
expect(d1.getSettings()).to.not.eql(d2.getSettings());
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance method', function () {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BaseDriver from '../../../lib';
|
||||
import {driverUnitTestSuite} from '@appium/test-support';
|
||||
import {driverUnitTestSuite} from '@appium/driver-test-support';
|
||||
|
||||
driverUnitTestSuite(BaseDriver, {
|
||||
platformName: 'iOS',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {server, routeConfiguringFunction} from '../../../lib';
|
||||
import {configureServer, normalizeBasePath} from '../../../lib/express/server';
|
||||
import {createSandbox} from 'sinon';
|
||||
import {getTestPort} from '@appium/test-support';
|
||||
import {getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
const newMethodMap = {
|
||||
'/session/:sessionId/fake': {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {JWProxy} from '../../../lib';
|
||||
import request from './mock-request';
|
||||
import {isErrorType, errors} from '../../../lib/protocol/errors';
|
||||
import {getTestPort, TEST_HOST} from '@appium/test-support';
|
||||
import {getTestPort, TEST_HOST} from '@appium/driver-test-support';
|
||||
|
||||
function buildReqRes(url, method, body) {
|
||||
let req = {originalUrl: url, method, body};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {JWProxy} from '../../../lib';
|
||||
import {getTestPort, TEST_HOST, createAppiumURL} from '@appium/test-support';
|
||||
import {getTestPort, TEST_HOST, createAppiumURL} from '@appium/driver-test-support';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('JWProxy', function () {
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
"paths": {
|
||||
"@appium/base-driver": ["."],
|
||||
"@appium/support": ["../support"],
|
||||
"@appium/types": ["../types"]
|
||||
"@appium/types": ["../types"],
|
||||
"@appium/driver-test-support": ["../driver-test-support"]
|
||||
},
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["lib"],
|
||||
"references": [{"path": "../support"}, {"path": "../types"}]
|
||||
"references": [
|
||||
{"path": "../support"},
|
||||
{"path": "../types"}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.3](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.2...@appium/base-plugin@1.10.3) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.2](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.1...@appium/base-plugin@1.10.2) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.1](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.0...@appium/base-plugin@1.10.1) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.10.0](https://github.com/appium/appium/compare/@appium/base-plugin@1.9.2...@appium/base-plugin@1.10.0) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "1.10.0",
|
||||
"version": "1.10.3",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -31,16 +31,26 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3"
|
||||
"@appium/support": "^2.60.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"types": "./build/lib/plugin.d.ts",
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"homepage": "https://appium.io"
|
||||
"homepage": "https://appium.io",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.26](https://github.com/appium/appium/compare/@appium/doctor@1.16.25...@appium/doctor@1.16.26) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.25](https://github.com/appium/appium/compare/@appium/doctor@1.16.24...@appium/doctor@1.16.25) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.24](https://github.com/appium/appium/compare/@appium/doctor@1.16.23...@appium/doctor@1.16.24) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.23](https://github.com/appium/appium/compare/@appium/doctor@1.16.22...@appium/doctor@1.16.23) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "1.16.23",
|
||||
"version": "1.16.26",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -49,12 +49,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/teen_process": "1.16.1",
|
||||
"appium-adb": "9.10.1",
|
||||
"appium-adb": "9.10.9",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.4",
|
||||
"lodash": "4.17.21",
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.11](https://github.com/appium/appium/compare/@appium/docutils@0.0.10...@appium/docutils@0.0.11) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.10](https://github.com/appium/appium/compare/@appium/docutils@0.0.9...@appium/docutils@0.0.10) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.9](https://github.com/appium/appium/compare/@appium/docutils@0.0.8...@appium/docutils@0.0.9) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.8](https://github.com/appium/appium/compare/@appium/docutils@0.0.7...@appium/docutils@0.0.8) (2022-07-28)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.11",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"appium",
|
||||
"docs"
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
@@ -41,17 +47,17 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"docdash": "1.2.0",
|
||||
"jsdoc": "3.6.11",
|
||||
"jsdoc-plugin-typescript": "2.1.0",
|
||||
"jsdoc-plugin-typescript": "2.1.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# 0.2.0 (2022-09-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **driver-test-support:** create package ([4128972](https://github.com/appium/appium/commit/41289724dc860e28dbe9a34710fa2d07d4a46e61))
|
||||
@@ -0,0 +1,112 @@
|
||||
# @appium/driver-test-support
|
||||
|
||||
> Testing utilities for [Appium](https://appium.io) drivers
|
||||
|
||||
This package is for driver authors to help test their drivers.
|
||||
|
||||
[Mocha](https://mochajs.org) is the supported test framework.
|
||||
|
||||
## Usage
|
||||
|
||||
### For E2E Tests
|
||||
|
||||
The `driverE2ETestSuite` method creates a Mocha test suite which makes HTTP requests to an in-memory server leveraging your driver.
|
||||
|
||||
Note that this method must be run within a _suite callback_—not a _test callback_.
|
||||
|
||||
```js
|
||||
import {driverE2ETestSuite} from '@appium/driver-test-support';
|
||||
|
||||
const defaultW3CCapabilities = {
|
||||
// some capabilities
|
||||
};
|
||||
|
||||
describe('MyDriverClass', function() {
|
||||
driverE2ETestSuite(MyDriverClass, defaultW3CCapabilities);
|
||||
|
||||
describe('more tests', function() {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### For Unit Tests
|
||||
|
||||
The `driverUnitTestSuite` method creates a Mocha test suite which performs assertions on an isolated instance of your driver.
|
||||
|
||||
Note that this method must be run within a _suite callback_—not a _test callback_.
|
||||
|
||||
```js
|
||||
import {driverUnitTestSuite} from '@appium/driver-test-support';
|
||||
|
||||
const defaultW3CCapabilities = {
|
||||
// some capabilities
|
||||
};
|
||||
|
||||
describe('MyDriverClass', function() {
|
||||
driverUnitTestSuite(MyDriverClass, defaultW3CCapabilities);
|
||||
|
||||
describe('more tests', function() {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Helpers
|
||||
|
||||
These are just some helpers (mainly for E2E tests):
|
||||
|
||||
```js
|
||||
import {TEST_HOST, getTestPort, createAppiumURL} from '@appium/driver-test-support';
|
||||
import assert from 'node:assert';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('TEST_HOST', function() {
|
||||
it('should be localhost', function() {
|
||||
assert.strictEqual(TEST_HOST, '127.0.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTestPort()', function() {
|
||||
it('should get a free test port', async function() {
|
||||
const port = await getTestPort();
|
||||
assert.ok(port > 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAppiumURL()', function() {
|
||||
it('should create a "new session" URL', function() {
|
||||
const actual = createAppiumURL(TEST_HOST, 31337, '', 'session');
|
||||
const expected = `http://${TEST_HOST}:31337/session`;
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should create a URL to get an existing session', function() {
|
||||
const sessionId = '12345';
|
||||
const createGetSessionURL = createAppiumURL(TEST_HOST, 31337, _, 'session');
|
||||
const actual = createGetSessionURL(sessionId);
|
||||
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/session`;
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should create a URL for a command using an existing session', function() {
|
||||
const sessionId = '12345';
|
||||
const createURLWithPath = createAppiumURL('127.0.0.1', 31337, sessionId);
|
||||
const actual = createURLWithPath('moocow');
|
||||
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/moocow`;
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
`appium` and `mocha` are peer dependencies.
|
||||
|
||||
```bash
|
||||
npm install appium mocha @appium/driver-test-support --save-dev
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./build/lib');
|
||||
+2
@@ -136,6 +136,8 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
routeConfiguringFunction: routeConfiguringFunction(d),
|
||||
port,
|
||||
hostname: address,
|
||||
// @ts-expect-error
|
||||
cliArgs: {},
|
||||
});
|
||||
({startSession, getSession, endSession, newSessionURL, getCommand, postCommand} =
|
||||
createSessionHelpers(port, address));
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './e2e-suite';
|
||||
export * from './unit-suite';
|
||||
export * from './helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
||||
*/
|
||||
+1
-11
@@ -1,6 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import B from 'bluebird';
|
||||
import {DeviceSettings} from '@appium/base-driver';
|
||||
import {createSandbox} from 'sinon';
|
||||
|
||||
import chai from 'chai';
|
||||
@@ -448,6 +447,7 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
||||
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
||||
avoidStub.returns([
|
||||
['POST', /^foo/],
|
||||
// @ts-expect-error
|
||||
['BAZETE', /^bar/],
|
||||
]);
|
||||
(() => {
|
||||
@@ -581,16 +581,6 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeviceSettings', function () {
|
||||
it('should not hold on to reference of defaults in constructor', function () {
|
||||
let obj = {foo: 'bar'};
|
||||
let d1 = new DeviceSettings(obj);
|
||||
let d2 = new DeviceSettings(obj);
|
||||
d1.getSettings().foo = 'baz';
|
||||
d1.getSettings().should.not.eql(d2.getSettings());
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isFeatureEnabled', function () {
|
||||
let d;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.2.0",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appium/appium.git",
|
||||
"directory": "packages/driver-test-support"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"build"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "babel lib --root-mode=upward --out-dir=build/lib",
|
||||
"dev": "npm run build -- --watch",
|
||||
"fix": "npm run lint -- --fix",
|
||||
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
|
||||
"prepare": "npm run build",
|
||||
"test": "npm run test:unit",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha \"test/unit/**/*.spec.js\""
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/lodash": "4.14.184",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.6",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "14.0.0",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
"mocha": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {TEST_HOST, getTestPort, createAppiumURL} from '../../lib';
|
||||
import _ from 'lodash';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('TEST_HOST', function () {
|
||||
it('should be localhost', function () {
|
||||
expect(TEST_HOST).to.equal('127.0.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTestPort()', function () {
|
||||
it('should get a free test port', async function () {
|
||||
const port = await getTestPort();
|
||||
expect(port).to.be.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAppiumURL()', function () {
|
||||
it('should create a "new session" URL', function () {
|
||||
const actual = createAppiumURL(TEST_HOST, 31337, '', 'session');
|
||||
const expected = `http://${TEST_HOST}:31337/session`;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
|
||||
it('should create a URL to get an existing session', function () {
|
||||
const sessionId = '12345';
|
||||
const createGetSessionURL = createAppiumURL(TEST_HOST, 31337, _, 'session');
|
||||
const actual = createGetSessionURL(sessionId);
|
||||
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/session`;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
|
||||
it('should create a URL for a command using an existing session', function () {
|
||||
const sessionId = '12345';
|
||||
const createURLWithPath = createAppiumURL('127.0.0.1', 31337, sessionId);
|
||||
const actual = createURLWithPath('moocow');
|
||||
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/moocow`;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"checkJs": true,
|
||||
"paths": {
|
||||
"@appium/types": ["../types"],
|
||||
"appium/driver": ["../base-driver"],
|
||||
},
|
||||
"types": [
|
||||
"mocha", "chai", "chai-as-promised"
|
||||
]
|
||||
},
|
||||
"include": ["./lib"],
|
||||
"references": [
|
||||
{"path": "../types"},
|
||||
{"path": "../base-driver"}
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,41 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [7.0.0](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.4...@appium/eslint-config-appium@7.0.0) (2022-09-07)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* **eslint-config-appium:** upgrade to ESLint v8 ([887cb44](https://github.com/appium/appium/commit/887cb449e61be585c084af2f6422d2e02be5028b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **eslint-config-appium:** This change upgrades to ESLint v8 and removes `@babel/eslint-parser`, which is only necessary if we're using mid-stage TC39 proposals (which we aren't). I think `@babel/core` was a peer dep of `@babel/eslint-parser`, so I removed that too. And removed cruft from the main configuration.
|
||||
|
||||
ESLint rules _very likely_ had breaking changes, but I didn't experience any on our codebase. However, this version of ESLint seems to be incompatible with `gulp-eslint`, so `@appium/gulp-plugins` should be held back from upgrading.
|
||||
|
||||
In addition:
|
||||
|
||||
- Updated `README.md`
|
||||
- Updated some fields in `package.json`
|
||||
- Loosened `peerDependencies`, as they're supposed to be loose.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.4](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.3...@appium/eslint-config-appium@6.0.4) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.3](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.2...@appium/eslint-config-appium@6.0.3) (2022-07-28)
|
||||
|
||||
**Note:** Version bump only for package @appium/eslint-config-appium
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
## Appium ESLint Shared Configuation
|
||||
# @appium/eslint-config-appium
|
||||
|
||||
This package works to provide [eslint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) to replace the need for a local `.eslintrc` file.
|
||||
> Provides a reusable [ESLint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) for [Appium](https://github.com/appium/appium) and Appium-adjacent projects.
|
||||
|
||||
It uses [babel-eslint](https://github.com/babel/babel-eslint) as a parser.
|
||||
## Usage
|
||||
|
||||
### Usage
|
||||
|
||||
Install the package
|
||||
Install the package with **`npm` v7 or newer** (for automatic installation of peer dependencies):
|
||||
|
||||
```bash
|
||||
npm install @appium/eslint-config-appium eslint-plugin-import eslint-plugin-mocha eslint-plugin-promise -D
|
||||
npm install @appium/eslint-config-appium --save-dev
|
||||
```
|
||||
|
||||
And then, in your `.eslintrc` file extend the configuration
|
||||
And then, in your `.eslintrc` file, extend the configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -20,18 +18,21 @@ And then, in your `.eslintrc` file extend the configuration
|
||||
}
|
||||
```
|
||||
|
||||
If you are using [@appium/gulp-plugins](https://www.npmjs.com/package/@appium/gulp-plugins) you can then run
|
||||
## Peer Dependencies
|
||||
|
||||
```bash
|
||||
gulp eslint
|
||||
```
|
||||
This config requires the following packages be installed (as dev dependencies) in your project. See the `package.json` for the required versions.
|
||||
|
||||
### Peer Dependencies
|
||||
- [eslint](https://www.npmjs.com/package/eslint)
|
||||
- [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
|
||||
- [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)
|
||||
- [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
|
||||
- [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
|
||||
|
||||
This config requires the following packages be installed (as dev dependencies) in your project:
|
||||
## Notes
|
||||
|
||||
* [babel-eslint](https://www.npmjs.com/package/babel-eslint)
|
||||
* [eslint](https://www.npmjs.com/package/eslint)
|
||||
* [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)
|
||||
* [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
|
||||
* [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
|
||||
- This configuration is intended to be used alongside [Prettier](https://www.npmjs.com/package/prettier).
|
||||
- This package was previously published as `eslint-config-appium`.
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2016 OpenJS Foundation. Licensed Apache-2.0
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
module.exports = {
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
requireConfigFile: false,
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true,
|
||||
es6: true,
|
||||
es2022: true,
|
||||
},
|
||||
plugins: ['import', 'mocha', 'promise'],
|
||||
globals: {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium",
|
||||
"version": "6.0.3",
|
||||
"description": "Shared config for Appium's eslint style",
|
||||
"version": "7.0.0",
|
||||
"description": "Shared ESLint config for Appium projects",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslintconfig",
|
||||
"appium",
|
||||
"es2015"
|
||||
"appium"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
@@ -32,17 +31,15 @@
|
||||
"test:smoke": "node ./index.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "7.18.9",
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "9.0.0",
|
||||
"eslint-plugin-promise": "6.0.0"
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.5](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.4...@appium/execute-driver-plugin@2.0.5) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.4](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.3...@appium/execute-driver-plugin@2.0.4) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.3](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.2...@appium/execute-driver-plugin@2.0.3) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.5",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"appium",
|
||||
"automation",
|
||||
"webdriver"
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
@@ -35,13 +40,13 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.10",
|
||||
"webdriverio": "7.20.7"
|
||||
"vm2": "3.9.11",
|
||||
"webdriverio": "7.24.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
@@ -56,6 +61,6 @@
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import {pluginE2EHarness} from 'appium/test';
|
||||
import {pluginE2EHarness} from '@appium/plugin-test-support';
|
||||
import {remote as wdio} from 'webdriverio';
|
||||
import {W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY} from '../../lib/execute-child';
|
||||
import {fs} from 'appium/support';
|
||||
@@ -39,7 +39,7 @@ describe('ExecuteDriverPlugin', function () {
|
||||
|
||||
const basicScript = `return 'foo'`;
|
||||
/**
|
||||
* @type {import('appium/test').E2ESetupOpts}
|
||||
* @type {import('@appium/plugin-test-support').E2ESetupOpts}
|
||||
*/
|
||||
const e2eSetupOpts = {
|
||||
before,
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.2.1](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.0...@appium/fake-driver@4.2.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.2.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.1.0...@appium/fake-driver@4.2.0) (2022-08-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,base-driver,fake-driver,fake-plugin,test-support,types:** updateServer receives cliArgs param ([d4b9833](https://github.com/appium/appium/commit/d4b983328af21d1e5c27a91e438e7934eb152ab1)), closes [#17304](https://github.com/appium/appium/issues/17304)
|
||||
* **base-driver,fake-driver,appium:** add convenience methods for defining execute script overloads ([#17321](https://github.com/appium/appium/issues/17321)) ([337ec3e](https://github.com/appium/appium/commit/337ec3e7ba216dd6f8cdc88143ecaa4c75f5d266))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.1.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.0.2...@appium/fake-driver@4.1.0) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** pass unknown args to extension scripts ([faff3ce](https://github.com/appium/appium/commit/faff3ce3471abaea24d2cb4c3e3b75b1af5ac3a1)), closes [#17250](https://github.com/appium/appium/issues/17250)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.0.2](https://github.com/appium/appium/compare/@appium/fake-driver@4.0.1...@appium/fake-driver@4.0.2) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@ commands.getLog = async function getLog(type) {
|
||||
}
|
||||
};
|
||||
|
||||
commands.execute = async function execute(script, args) {
|
||||
return await this.executeMethod(script, args);
|
||||
};
|
||||
|
||||
commands.fakeAddition = async function fakeAddition(num1, num2, num3) {
|
||||
return num1 + num2 + (num3 ?? 0);
|
||||
};
|
||||
|
||||
Object.assign(extensions, commands, helpers);
|
||||
export {commands, helpers};
|
||||
export default extensions;
|
||||
|
||||
@@ -113,13 +113,30 @@ class FakeDriver extends BaseDriver {
|
||||
},
|
||||
};
|
||||
|
||||
static executeMethodMap = {
|
||||
'fake: addition': {
|
||||
command: 'fakeAddition',
|
||||
params: {required: ['num1', 'num2'], optional: ['num3']},
|
||||
},
|
||||
'fake: getThing': {
|
||||
command: 'getFakeThing',
|
||||
},
|
||||
'fake: setThing': {
|
||||
command: 'setFakeThing',
|
||||
params: {required: ['thing']},
|
||||
},
|
||||
};
|
||||
|
||||
static fakeRoute(req, res) {
|
||||
res.send(JSON.stringify({fakedriver: 'fakeResponse'}));
|
||||
}
|
||||
|
||||
static async updateServer(expressApp /*, httpServer*/) {
|
||||
static async updateServer(expressApp, httpServer, cliArgs) {
|
||||
// eslint-disable-line require-await
|
||||
expressApp.all('/fakedriver', FakeDriver.fakeRoute);
|
||||
expressApp.all('/fakedriverCliArgs', (req, res) => {
|
||||
res.send(JSON.stringify(cliArgs));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// transpile:main
|
||||
|
||||
import * as driver from './driver';
|
||||
import * as server from './server';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import log from '../logger';
|
||||
|
||||
log.info('Successfully ran the script');
|
||||
|
||||
log.info(process.argv.slice(2).join(' '));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "4.0.2",
|
||||
"version": "4.2.1",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -47,7 +47,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"asyncbox": "2.9.2",
|
||||
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -80,5 +80,6 @@
|
||||
"fake-success": "./build/lib/scripts/fake-success.js"
|
||||
}
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import {driverE2ETestSuite} from 'appium/test';
|
||||
import {driverE2ETestSuite} from '@appium/driver-test-support';
|
||||
import {FakeDriver, startServer} from '../../lib/index.js';
|
||||
import {
|
||||
BASE_CAPS,
|
||||
|
||||
@@ -106,6 +106,45 @@ function generalTests() {
|
||||
res[0].type.should.eql('pointer');
|
||||
res[0].actions.should.have.length(2);
|
||||
});
|
||||
|
||||
it('should get and set a fake thing via execute overloads', async function () {
|
||||
let thing = await driver.executeScript('fake: getThing', []);
|
||||
should.not.exist(thing);
|
||||
await driver.executeScript('fake: setThing', [{thing: 1234}]);
|
||||
thing = await driver.executeScript('fake: getThing', []);
|
||||
thing.should.eql(1234);
|
||||
});
|
||||
|
||||
it('should add 2 numbers via execute overloads', async function () {
|
||||
await driver.executeScript('fake: addition', [{num1: 2, num2: 3}]).should.eventually.eql(5);
|
||||
await driver
|
||||
.executeScript('fake: addition', [{num1: 2, num2: 3, num3: 4}])
|
||||
.should.eventually.eql(9);
|
||||
});
|
||||
|
||||
it('should throw not implemented if an execute overload isnt supported', async function () {
|
||||
await driver
|
||||
.executeScript('fake: blarg', [])
|
||||
.should.be.rejectedWith(/Unsupported execute method/);
|
||||
});
|
||||
|
||||
it('should throw an error if a required overload param is missing', async function () {
|
||||
await driver
|
||||
.executeScript('fake: addition', [{num3: 4}])
|
||||
.should.be.rejectedWith(/Parameters were incorrect/);
|
||||
});
|
||||
|
||||
it('should throw an error if sending in wrong types of params', async function () {
|
||||
await driver
|
||||
.executeScript('fake: addition', [4, 5])
|
||||
.should.be.rejectedWith(/correct format of arg/);
|
||||
await driver
|
||||
.executeScript('fake: addition', [4])
|
||||
.should.be.rejectedWith(/not receive an appropriate execute/);
|
||||
await driver
|
||||
.executeScript('fake: addition', [{num1: 2}, {extra: 'bad'}])
|
||||
.should.be.rejectedWith(/correct format of arg/);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user