mirror of
https://github.com/appium/appium.git
synced 2026-10-31 14:00:08 +08:00
Compare commits
108
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35f4e2b7e4 | ||
|
|
568e6d3b19 | ||
|
|
b48a697d5b | ||
|
|
422142302f | ||
|
|
c65054c232 | ||
|
|
888d892a17 | ||
|
|
4f65cb3865 | ||
|
|
e98928a39d | ||
|
|
03c505d321 | ||
|
|
2a12b88b2f | ||
|
|
734127144d | ||
|
|
8c421220c5 | ||
|
|
0cad4b785c | ||
|
|
00e7e3f1ef | ||
|
|
75183ba38b | ||
|
|
ad1dce5545 | ||
|
|
8c86e355d2 | ||
|
|
4156b7bf6b | ||
|
|
7055a0b281 | ||
|
|
bc3f1373fd | ||
|
|
2bbac72667 | ||
|
|
f04e1171d0 | ||
|
|
696c94f1ab | ||
|
|
6f846f76c9 | ||
|
|
d8588e107c | ||
|
|
c5bbb950ed | ||
|
|
fb4f0408dc | ||
|
|
697b4427e4 | ||
|
|
6fb05b2fff | ||
|
|
7add23e418 | ||
|
|
b036dde5dd | ||
|
|
1f99c97264 | ||
|
|
df31cddbe7 | ||
|
|
9d5a737daa | ||
|
|
0ca54b7994 | ||
|
|
7942118429 | ||
|
|
0aac077e30 | ||
|
|
5ef4ae9206 | ||
|
|
a22c97dbf9 | ||
|
|
57b0ee5dbb | ||
|
|
31a3cee0bc | ||
|
|
b28a1134f0 | ||
|
|
8f7171c42a | ||
|
|
da230ca243 | ||
|
|
edde4882f0 | ||
|
|
d8323b3d3c | ||
|
|
ed07ae3046 | ||
|
|
5d1090555f | ||
|
|
4b5ab4da7b | ||
|
|
f036f0a612 | ||
|
|
79105b59e2 | ||
|
|
783989b56c | ||
|
|
49eddb4368 | ||
|
|
b4670e13f9 | ||
|
|
fe7b7b6d21 | ||
|
|
7d7b510a03 | ||
|
|
63d8f2ca2d | ||
|
|
8b33414901 | ||
|
|
949719811a | ||
|
|
47ec6af185 | ||
|
|
9ee40374f5 | ||
|
|
e9042b85d9 | ||
|
|
7bb4e07d68 | ||
|
|
098ee1932d | ||
|
|
7c59380e6b | ||
|
|
b763880779 | ||
|
|
a3fad3ed28 | ||
|
|
6055f162d8 | ||
|
|
5291ca672b | ||
|
|
23224cf002 | ||
|
|
0b5f7a4085 | ||
|
|
d2cf2ed4e3 | ||
|
|
3f3b1529ed | ||
|
|
8c8bfe824d | ||
|
|
2bb4f32329 | ||
|
|
ac2c735968 | ||
|
|
29bf77cf5a | ||
|
|
28c6204b75 | ||
|
|
e0dc5b6299 | ||
|
|
6fb1f2cc32 | ||
|
|
2e76ba9607 | ||
|
|
aab415685e | ||
|
|
d13d292c4b | ||
|
|
df250cc1e0 | ||
|
|
5565febc9d | ||
|
|
49a0fe164b | ||
|
|
94911eb59f | ||
|
|
1b4a41bce9 | ||
|
|
5d0d4f5a44 | ||
|
|
f81e2e92ef | ||
|
|
316ecca8b1 | ||
|
|
20667be9a2 | ||
|
|
48b7b05a1f | ||
|
|
2df077b08c | ||
|
|
0e195caafe | ||
|
|
9842e231c5 | ||
|
|
feeaac4713 | ||
|
|
ad7e2648c1 | ||
|
|
2cb1af4357 | ||
|
|
45b6aed22d | ||
|
|
26c84c9361 | ||
|
|
ad06a0612f | ||
|
|
fe67e4cb2f | ||
|
|
fee1c98ee9 | ||
|
|
9fea6980e5 | ||
|
|
37f71c327a | ||
|
|
172fcb9aff | ||
|
|
3993decde4 |
+1
-5
@@ -29,8 +29,6 @@ targets:
|
||||
path: ./packages/eslint-config-appium
|
||||
- type: npm
|
||||
path: ./packages/fake-driver
|
||||
- type: npm
|
||||
path: ./packages/gulp-plugins
|
||||
- type: npm
|
||||
path: ./packages/support
|
||||
- type: npm
|
||||
@@ -54,7 +52,5 @@ targets:
|
||||
- type: npm
|
||||
path: ./packages/docutils
|
||||
|
||||
|
||||
|
||||
revision:
|
||||
branch: master
|
||||
- branch: master
|
||||
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Appium
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run FOSSA Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
with:
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -10,3 +10,4 @@ package.json
|
||||
**/generated/**
|
||||
# generated
|
||||
packages/types/lib/appium-config.ts
|
||||
**/*.hbs
|
||||
|
||||
Vendored
+1
@@ -50,6 +50,7 @@
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"name": "Typedoc",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/typedoc",
|
||||
"args": ["--logLevel", "Verbose"],
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
|
||||
Vendored
+15
-2
@@ -4,11 +4,24 @@
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"group": {"kind": "build", "isDefault": true},
|
||||
"problemMatcher": ["$tsc-watch"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$tsc-watch"
|
||||
],
|
||||
"label": "npm: dev",
|
||||
"detail": "npm run dev",
|
||||
"isBackground": true
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "typedoc",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: typedoc",
|
||||
"detail": "typedoc --logLevel Verbose",
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+31
-18
@@ -3,24 +3,30 @@
|
||||
module.exports = (wallaby) => {
|
||||
return {
|
||||
compilers: {
|
||||
'**/*.js': wallaby.compilers.babel(),
|
||||
'**/*.js': wallaby.compilers.typeScript({
|
||||
allowJs: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
resolveJsonModule: true,
|
||||
isolatedModules: true,
|
||||
}),
|
||||
'**/*.ts?(x)': wallaby.compilers.typeScript(),
|
||||
},
|
||||
debug: true,
|
||||
env: {
|
||||
type: 'node',
|
||||
},
|
||||
files: [
|
||||
'./packages/**/*.js',
|
||||
'./packages/**/*.json',
|
||||
'!./packages/**/build/**',
|
||||
'!./packages/**/test/**/*-specs.js',
|
||||
'!./packages/**/test/**/*.spec.js',
|
||||
'!./packages/*/node_modules/**',
|
||||
'!./packages/*/gulpfile.js',
|
||||
'!./packages/*/scripts/**',
|
||||
'./packages/*/test/**/fixtures/**/*',
|
||||
'./babel.config.json',
|
||||
// below this are fixtures
|
||||
'./packages/*/build/**/*',
|
||||
'./packages/*/lib/**/*.(j|t)s',
|
||||
'./packages/*/test/**/*helper*.(j|t)s',
|
||||
'./packages/*/test/**/*mock*.(j|t)s',
|
||||
'./packages/*/package.json',
|
||||
'./packages/*/tsconfig*.json',
|
||||
'./packages/*/test/**/fixture?(s)/**/*',
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/typedoc-plugin-appium/resources/**/*',
|
||||
},
|
||||
{
|
||||
binary: true,
|
||||
pattern: './packages/support/test/unit/assets/sample_binary.plist',
|
||||
@@ -33,17 +39,21 @@ module.exports = (wallaby) => {
|
||||
instrument: false,
|
||||
pattern: './packages/base-driver/static/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/typedoc-plugin-appium/lib/theme/resources/**',
|
||||
},
|
||||
'!./packages/*/test/**/*-specs.js',
|
||||
'!./packages/*/test/**/*.e2e.spec.(j|t)s',
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js', '!**/local_appium_home/**'],
|
||||
tests: ['./packages/*/test/unit/**/*.spec.(j|t)s', '!**/local_appium_home/**'],
|
||||
workers: {
|
||||
restart: true,
|
||||
// restart: true,
|
||||
},
|
||||
setup() {
|
||||
// This copied out of `./test/setup.js`, which uses `@babel/register`.
|
||||
// Wallaby doesn't need `@babel/register` (and it probably makes Wallaby slow),
|
||||
// but we need the other stuff, so here it is.
|
||||
setup(wallaby) {
|
||||
// copied out of `./test/setup.js`
|
||||
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
@@ -55,6 +65,9 @@ module.exports = (wallaby) => {
|
||||
|
||||
// `should()` is only necessary when working with some `null` or `undefined` values.
|
||||
global.should = chai.should();
|
||||
|
||||
const mocha = wallaby.testFramework;
|
||||
mocha.timeout(10000);
|
||||
},
|
||||
runMode: 'onsave',
|
||||
};
|
||||
|
||||
@@ -17,11 +17,11 @@ and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sect
|
||||
|
||||
:bangbang: Major documentation revision in progress
|
||||
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://github.com/appium/appium/blob/master/packages/appium/docs/en/guides/migrating-1-to-2.md) to manage the Appium server.
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
|
||||
|
||||
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/).
|
||||
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/latest/).
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -170,9 +170,9 @@ automation, for mobile and beyond. Finally!
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium Concepts](/docs/en/about-appium/intro.md).
|
||||
|
||||
### Get Started
|
||||
### Quickstart
|
||||
|
||||
Check out our [Getting Started](http://appium.io/docs/en/about-appium/getting-started) guide
|
||||
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
@@ -180,15 +180,9 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
|
||||
always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/docs) as well.
|
||||
|
||||
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
|
||||
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
|
||||
in the appium.io repository with the documentation update.
|
||||
|
||||
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
|
||||
You can always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -201,8 +195,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](http://appium.io/history)
|
||||
* [Credits](/docs/en/contributing-to-appium/credits.md)
|
||||
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'header-max-length': [0],
|
||||
'body-leading-blank': [0],
|
||||
'body-max-line-length': [0],
|
||||
'footer-max-line-length': [0],
|
||||
'header-max-length': [0],
|
||||
'subject-case': [0],
|
||||
'subject-full-stop': [0],
|
||||
},
|
||||
};
|
||||
|
||||
Generated
+1397
-9002
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -45,6 +45,7 @@
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:staged": "lint-staged",
|
||||
"pack-all": "npm pack --workspaces --include-workspace-root",
|
||||
"prepublishOnly": "run-s rebuild",
|
||||
"reinstall": "run-s clean ci",
|
||||
"rebuild": "run-s clean:artifacts build:all",
|
||||
@@ -52,12 +53,12 @@
|
||||
"sync-pkgs": "run-p sync-pkgs:*",
|
||||
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
|
||||
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,bugs,homepage,engines",
|
||||
"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 --packages=\"packages/*\" --no-package-json LICENSE",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages\"!packages/typedoc-plugin-appium\"",
|
||||
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
|
||||
"stage-synced-pkgs": "git add -A packages/appium/README.md \"packages/*/package.json\"",
|
||||
"test": "npm-run-all -p lint build -s test:unit",
|
||||
"test:ci": "run-s test:smoke test:unit test:e2e",
|
||||
"test:e2e": "lerna run --concurrency=1 --max-buffer=128 test:e2e",
|
||||
"test:e2e": "lerna run --concurrency=2 test:e2e",
|
||||
"test:smoke": "smoker --all test:smoke",
|
||||
"test:unit": "lerna run test",
|
||||
"typedoc": "typedoc --logLevel Verbose",
|
||||
@@ -73,60 +74,59 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"@commitlint/cli": "17.3.0",
|
||||
"@commitlint/config-conventional": "17.3.0",
|
||||
"@commitlint/cli": "17.4.2",
|
||||
"@commitlint/config-conventional": "17.4.2",
|
||||
"@tsconfig/node14": "1.0.3",
|
||||
"@types/chai": "4.3.4",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.11.14",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/sinon-chai": "3.2.9",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/ws": "8.5.3",
|
||||
"@types/ws": "8.5.4",
|
||||
"asyncbox": "2.9.4",
|
||||
"chai": "4.3.7",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"cpy-cli": "4.2.0",
|
||||
"eslint": "8.29.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-import": "2.27.4",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"gulp": "4.0.2",
|
||||
"handlebars": "4.7.7",
|
||||
"husky": "8.0.2",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "11.0.2",
|
||||
"lerna": "6.1.0",
|
||||
"lerna": "6.4.0",
|
||||
"lint-staged": "13.1.0",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "2.0.0",
|
||||
"midnight-smoker": "2.0.1",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.2.0",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.8.1",
|
||||
"prettier": "2.8.2",
|
||||
"rewiremock": "3.14.4",
|
||||
"rimraf": "3.0.2",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "15.0.0",
|
||||
"sinon": "15.0.1",
|
||||
"sinon-chai": "3.7.0",
|
||||
"sync-monorepo-packages": "1.0.1",
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"typedoc": "0.23.22",
|
||||
"typedoc": "0.23.24",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.7.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.27.0",
|
||||
"ws": "8.11.0",
|
||||
"webdriverio": "7.29.1",
|
||||
"ws": "8.12.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -139,10 +139,10 @@
|
||||
"overrides": {
|
||||
"jpeg-js": "0.4.4",
|
||||
"@sidvind/better-ajv-errors": {
|
||||
"ajv": "8.11.2"
|
||||
"ajv": "8.12.0"
|
||||
},
|
||||
"ajv-formats": {
|
||||
"ajv": "8.11.2"
|
||||
"ajv": "8.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,53 @@
|
||||
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.50](https://github.com/appium/appium/compare/appium@2.0.0-beta.49...appium@2.0.0-beta.50) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.49](https://github.com/appium/appium/compare/appium@2.0.0-beta.48...appium@2.0.0-beta.49) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** inability to find automationName inside appium:options ([#17966](https://github.com/appium/appium/issues/17966)) ([23224cf](https://github.com/appium/appium/commit/23224cf002b7dd9e4e5d8426b4bbe1cb28f62605))
|
||||
* **appium:** update dependency ajv to v8.12.0 ([783989b](https://github.com/appium/appium/commit/783989b56c80ebc0eebf693c168754130627b55f))
|
||||
* **appium:** update dependency yaml to v2.2.0 ([28c6204](https://github.com/appium/appium/commit/28c6204b751ead304cce21d534c91381fa39d79d))
|
||||
* **appium:** update dependency yaml to v2.2.1 ([b763880](https://github.com/appium/appium/commit/b763880779abaea900ac2c129dfb009b0e8ed0a9))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.28.1 ([2bb4f32](https://github.com/appium/appium/commit/2bb4f32329938e15062e42b585dd760cca5ae17b))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.29.0 ([a3fad3e](https://github.com/appium/appium/commit/a3fad3ed28822b1dfaae72cbfb8d7906ec35e158))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.29.1 ([7c59380](https://github.com/appium/appium/commit/7c59380e6b97691ff16a8d98a912d67b7972593a))
|
||||
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
|
||||
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
|
||||
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.48](https://github.com/appium/appium/compare/appium@2.0.0-beta.47...appium@2.0.0-beta.48) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **appium:** ensure migration always gets applied ([20667be](https://github.com/appium/appium/commit/20667be9a21e71963ecb7e25e1b8d51864bce011))
|
||||
- **appium:** fix manifest migrations ([df250cc](https://github.com/appium/appium/commit/df250cc1e024f4971abe7e756594f7feee32cba6))
|
||||
- **execute-driver-plugin:** update dependency webdriverio to v7.28.0 ([f81e2e9](https://github.com/appium/appium/commit/f81e2e92eff25c33d36f767209f423227d288218))
|
||||
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
|
||||
|
||||
### Features
|
||||
|
||||
- **appium:** add chromium driver to list of officially supported drivers ([#17939](https://github.com/appium/appium/issues/17939)) ([fe67e4c](https://github.com/appium/appium/commit/fe67e4cb2fca977dd9994f1cfbba4a0edc61ef83))
|
||||
|
||||
# [2.0.0-beta.47](https://github.com/appium/appium/compare/appium@2.0.0-beta.46...appium@2.0.0-beta.47) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -17,11 +17,11 @@ and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sect
|
||||
|
||||
:bangbang: Major documentation revision in progress
|
||||
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://github.com/appium/appium/blob/master/packages/appium/docs/en/guides/migrating-1-to-2.md) to manage the Appium server.
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
|
||||
|
||||
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/).
|
||||
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/latest/).
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -170,9 +170,9 @@ automation, for mobile and beyond. Finally!
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium Concepts](/docs/en/about-appium/intro.md).
|
||||
|
||||
### Get Started
|
||||
### Quickstart
|
||||
|
||||
Check out our [Getting Started](http://appium.io/docs/en/about-appium/getting-started) guide
|
||||
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
@@ -180,15 +180,9 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
|
||||
always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/docs) as well.
|
||||
|
||||
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
|
||||
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
|
||||
in the appium.io repository with the documentation update.
|
||||
|
||||
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
|
||||
You can always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -201,8 +195,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](http://appium.io/history)
|
||||
* [Credits](/docs/en/contributing-to-appium/credits.md)
|
||||
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ extra:
|
||||
link: https://twitter.com/AppiumDevs
|
||||
alternate:
|
||||
- name: English
|
||||
link: /docs/en/
|
||||
link: /appium/docs/en/
|
||||
lang: en
|
||||
- name: 日本
|
||||
link: /docs/ja/
|
||||
link: /appium/docs/ja/
|
||||
lang: ja
|
||||
- name: 中文简体
|
||||
link: /docs/zh/
|
||||
link: /appium/docs/zh/
|
||||
lang: zh
|
||||
|
||||
@@ -22,6 +22,7 @@ Intro](../intro/drivers.md)
|
||||
|
||||
|Driver|Installation Key|Platform(s)|Mode(s)|
|
||||
|--|--|--|--|
|
||||
|[Chromium](https://github.com/appium/appium-chromium-driver)|`chromium`|macOS, Windows, Linux|Web|
|
||||
|[Espresso](https://github.com/appium/appium-espresso-driver)|`espresso`|Android|Native|
|
||||
|[Gecko](https://github.com/appium/appium-geckodriver)|`gecko`|macOS, Windows, Linux, Android|Web|
|
||||
|[Mac2](https://github.com/appium/appium-mac2-driver)|`mac2`|macOS|Native|
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Application Bundles Caching Logic
|
||||
---
|
||||
|
||||
Appium Base driver provides a feature which enables caching of application builds provided, for example,
|
||||
as `app` capability value or to endpoints similar to the `installApp` one. This article explains common caching
|
||||
principles, so you could create more performant and efficient test suite execution strategies.
|
||||
|
||||
## Why Caching Is Necessary
|
||||
|
||||
Mobile application bundles could reach hundreds of megabytes is size. This could become a serious
|
||||
performance issue if a test suite is executed, and it is necessary to fetch/extract the same application
|
||||
bundle for each test.
|
||||
|
||||
## What Is Cached
|
||||
|
||||
Caching could be applied to application bundles generated by
|
||||
[configureApp](../../../../../packages/base-driver/lib/basedriver/helpers.js) helper call. Inherited drivers
|
||||
can customize their caching logic by providing own `onPostProcess` property definition, but the general
|
||||
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
|
||||
first before they could be actually installed on the device under test. On iOS, for example, these are .ipa or
|
||||
.zip compressed application bundles or .aab on Android.
|
||||
|
||||
## How Remote Application Bundles Are Cached
|
||||
|
||||
In order to validate whether an app bundle downloaded from the given URL could be (re)used from the cache the following
|
||||
steps are applied:
|
||||
|
||||
1. The script is sending `HEAD` request to the given link in order to only fetch response headers. If this request fails/
|
||||
times out then no caching is applied.
|
||||
2. The following header values are being extracted: `Last-Modified`, `Cache-Control`. If no `Last-Modified` header is present
|
||||
or the header value cannot be parsed as a valid datetime then no caching is applied.
|
||||
3. The script checks if the given URL is already present in the cache. If the app is not cached yet then it gets downloaded
|
||||
and its current header values along with hashsum are added to the cache.
|
||||
4. If the URL was already in the cache then the script verifies that:
|
||||
- the current `Last-Modified` datetime is not different from the previous one
|
||||
- the difference between the current `Last-Modified` datetime and the cached one is not greater than max-age (if present)
|
||||
- the hashsum of the cached file/folder is not changed (e.g. it was not corrupted while sitting in the cache)
|
||||
5. If all validations above succeed then the cached build is returned, otherwise the currently cached entry gets
|
||||
deleted and a new one is downloaded instead.
|
||||
|
||||
## How Local Application Bundles Are Cached
|
||||
|
||||
It only makes sense to cache application bundles if they need some preprocessing before being installed on the device under test.
|
||||
For example, on iOS .ipa bundles must be unzipped, because the system installer only works with .app folders.
|
||||
|
||||
1. The script verifies if the given bundle path is already present in the cache. If the bundle was not in the cache yet
|
||||
then it gets preprocessed and added there.
|
||||
2. The script validates the hashsum of the bundle and compares it to the previously stored one. If hash sums don't match
|
||||
then the cached item gets deleted and the preprocessing of the bundle repeats.
|
||||
|
||||
## How The Cache File System Is Configured
|
||||
|
||||
The cache where the base driver keeps all application bundles is located in the system temp folder. It is configured
|
||||
on per-process basis, so each test session initialized in scope of the same Appium server process takes advantages
|
||||
of it. It is a [LRU Cache](https://www.npmjs.com/package/lru-cache) with the following limitations:
|
||||
|
||||
- Max items: 1024
|
||||
- Max time to live (TTL) for each entry: 24 hours
|
||||
- TTL is refreshed for each entry upon access
|
||||
|
||||
!!! warning
|
||||
|
||||
Note: The cache root folder is set up for automatic deletion on Appium process termination. This would only
|
||||
work if Appium server is killed with SIGINT or SIGTERM. If SIGKILL is used then no cache cleanup
|
||||
would be performed.
|
||||
@@ -2,39 +2,131 @@
|
||||
title: Appium and Selenium Grid
|
||||
---
|
||||
|
||||
## Selenium Grid 4
|
||||
## Using Selenium Grid 4+
|
||||
|
||||
The **relay** feature in Grid 4 allows you to proxy Appium requests to an Appium server instance.
|
||||
The
|
||||
[relay](https://www.selenium.dev/documentation/grid/configuration/toml_options/#relaying-commands-to-a-service-endpoint-that-supports-webdriver)
|
||||
feature in Grid 4 allows you to proxy Appium requests to an Appium server instance. Here is
|
||||
an example walkthrough of how you would connect two different Appium instances to a Selenium Grid.
|
||||
|
||||
Please check [Relaying commands to a service endpoint that supports WebDriver](https://www.selenium.dev/documentation/grid/configuration/toml_options/#relaying-commands-to-a-service-endpoint-that-supports-webdriver) and [Selenium Grid 4 and Appium together in harmony](https://www.youtube.com/watch?v=3_aP2rsqZD0) about the configuration and for more details.
|
||||
### Define the Appium configs
|
||||
|
||||
## Selenium Grid 3
|
||||
Each Appium instance should have a config file that can be easily updated. It should contain any
|
||||
information which needs to be unique to that particular server (e.g., ports its drivers should use
|
||||
that others should not). We are going to have 2 Appium servers, so we will need 2 config files:
|
||||
|
||||
You are able to register your Appium server with a local [Selenium Grid 3](https://www.selenium.dev/documentation/legacy/selenium_3/grid_3/)
|
||||
([setup docs](https://www.selenium.dev/documentation/legacy/grid_3/setting_up_your_own_grid/)) by using the
|
||||
```yaml
|
||||
# appium1.yml
|
||||
server:
|
||||
port: 4723
|
||||
use-drivers:
|
||||
- xcuitest
|
||||
default-capabilities:
|
||||
wdaLocalPort: 8100
|
||||
mjpegServerPort: 9100
|
||||
mjpegScreenshotUrl: "http://localhost:9100"
|
||||
```
|
||||
|
||||
In the above YAML config file, we specify the Appium server port, the driver used, and parameters
|
||||
for the driver that will be sent in as default capabilities. Our goal is to ensure that any other
|
||||
drivers running on this host will not compete with system ports or other resources. The second
|
||||
config file could look like the following, where we simply adjust a few ports to prevent clashes:
|
||||
|
||||
```yaml
|
||||
# appium2.yml
|
||||
server:
|
||||
port: 4733
|
||||
use-drivers:
|
||||
- xcuitest
|
||||
default-capabilities:
|
||||
wdaLocalPort: 8110
|
||||
mjpegServerPort: 9110
|
||||
mjpegScreenshotUrl: "http://localhost:9110"
|
||||
```
|
||||
|
||||
### Define the Grid node configs
|
||||
|
||||
We will be launching one Grid "node" per Appium server, to manage relaying commands and determining
|
||||
capacity and online status, etc... So we should have one config file per Grid node as well. Each
|
||||
node config should include the address of the Appium server it will target, as well as a list of
|
||||
capability "configs" it should accept to relay a session request to Appium. Here is what the config
|
||||
could look like for the two nodes:
|
||||
|
||||
```toml
|
||||
# node1.toml
|
||||
[server]
|
||||
port = 5555
|
||||
|
||||
[node]
|
||||
detect-drivers = false
|
||||
|
||||
[relay]
|
||||
url = "http://localhost:4723"
|
||||
status-endpoint = "/status"
|
||||
configs = [
|
||||
"1", "{\"platformName\": \"iOS\", \"appium:platformVersion\": \"15.5\", \"appium:deviceName\": \"iPhone 13\", \"appium:automationName\": \"XCUITest\"}"
|
||||
]
|
||||
```
|
||||
|
||||
```toml
|
||||
# node2.toml
|
||||
[server]
|
||||
port = 5565
|
||||
|
||||
[node]
|
||||
detect-drivers = false
|
||||
|
||||
[relay]
|
||||
url = "http://localhost:4733"
|
||||
status-endpoint = "/status"
|
||||
configs = [
|
||||
"1", "{\"platformName\": \"iOS\", \"appium:platformVersion\": \"15.5\", \"appium:deviceName\": \"iPhone 12\", \"appium:automationName\": \"XCUITest\"}"
|
||||
]
|
||||
```
|
||||
|
||||
Note that each node config also specifies a different port itself for the node to run on.
|
||||
|
||||
### Putting it together
|
||||
|
||||
The Grid nodes aren't enough--you'll also want a Grid "hub" that acts as a load balancer and
|
||||
manager for all the nodes. So in the end we'll have 5 processes running at once: 2 Appium servers,
|
||||
2 Grid nodes, and 1 Grid hub. It's best to run each of these in a separate terminal window to avoid
|
||||
confusion of logs. Here is how you'd start each process:
|
||||
|
||||
0. `appium --config appium1.yml`
|
||||
0. `appium --config appium2.yml`
|
||||
0. `java -jar /path/to/selenium.jar node --config node1.toml`
|
||||
0. `java -jar /path/to/selenium.jar node --config node2.toml`
|
||||
0. `java -jar /path/to/selenium.jar hub`
|
||||
|
||||
Once you wait a few moments for the nodes to detect their Appium servers, and to register with the
|
||||
hub, you'll be able to send all your Appium traffic via the single hub endpoint (defaulting to
|
||||
`http://localhost:4444`).
|
||||
|
||||
And of course, you're able to link up Appium servers and nodes running on different machines in
|
||||
your network to form a larger grid.
|
||||
|
||||
## Using Selenium Grid 3
|
||||
|
||||
It is possible to register your Appium server with a local [Selenium Grid 3](https://www.selenium.dev/documentation/legacy/selenium_3/grid_3/)
|
||||
([setup docs](https://www.selenium.dev/documentation/legacy/grid_3/setting_up_your_own_grid/)) instance by using the
|
||||
`--nodeconfig` server argument.
|
||||
|
||||
```bash
|
||||
appium server --nodeconfig /path/to/nodeconfig.json --base-path=/wd/hub
|
||||
```
|
||||
|
||||
In the node config file you have to define the `browserName`,
|
||||
`version` and `platform` and based on these parameters the grid
|
||||
will re-direct your test to the right device. You will also need to
|
||||
configure your **host** details and the **Selenium Grid** details. For
|
||||
a full list of all parameters and descriptions look
|
||||
[here](https://www.selenium.dev/documentation/legacy/selenium_3/grid_setup/)
|
||||
In the referenced config file you have to define the `browserName`, `version` and `platform`
|
||||
capabilities and based on these parameters the grid will re-direct your test to the right device.
|
||||
You will also need to configure your host details and the Selenium Grid details. For a full list of
|
||||
all parameters and descriptions see
|
||||
[here](https://www.selenium.dev/documentation/legacy/selenium_3/grid_setup/).
|
||||
|
||||
Once you start the appium server and it registers with the grid,
|
||||
you will see your device on the grid console page:
|
||||
Once you start the Appium server it will register with the grid, and you will see your device on
|
||||
the grid console page:
|
||||
|
||||
`http://**\<grid-ip-adress\>**:**\<grid-port\>**/grid/console`
|
||||
|
||||
|
||||
New session capabilities should use an `appium:` prefix for non-W3C-standard capabilities such as
|
||||
`automationName`, since the grid appends the given capabilities in both `desiredCapabilities`
|
||||
and `firstMatch` in `capabilities`.
|
||||
|
||||
|
||||
### Example Grid Node Configuration JSON
|
||||
|
||||
```json
|
||||
@@ -60,13 +152,15 @@ and `firstMatch` in `capabilities`.
|
||||
"register": true,
|
||||
"registerCycle": 5000,
|
||||
"hubPort": <grid_port>,
|
||||
"hubHost": "<Grid_host_name_or_grid_ip-address>"
|
||||
"hubHost": "<Grid_host_name_or_grid_ip-address>",
|
||||
"hubProtocol": "<Protocol_of_Grid_defaults_to_http>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `url`, `host`, and `port` are not given, the config will be auto updated
|
||||
to point to localhost:whatever-port-Appium-started-on.
|
||||
If `url`, `host`, and `port` are not given, the config will be auto updated to point to
|
||||
`localhost:<appium-port>`.
|
||||
|
||||
If your Appium server is running on a different machine to your Selenium Grid server, make sure you use an external name/IP address in your `host` & `url` docs; `localhost` and `127.0.0.1` will prevent Selenium Grid from connecting correctly.
|
||||
If your Appium server is running on a different machine to your Selenium Grid server, make sure you
|
||||
use an external name/IP address in your `host` and `url` configuration; `localhost` and `127.0.0.1`
|
||||
will prevent Selenium Grid from connecting correctly.
|
||||
|
||||
@@ -14,7 +14,20 @@ Together these do introduce a few breaking changes to how Appium is installed, h
|
||||
|
||||
## 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.
|
||||
Here we call out the breaking changes and what you need to do to account for them.
|
||||
|
||||
### :bangbang: Default server base path
|
||||
|
||||
With Appium 1.x, the server would accept commands by default on `http://localhost:4723/wd/hub`. The
|
||||
`/wd/hub` base path was a legacy convention from the days of migrating from Selenium 1 to Selenium
|
||||
2, and is no longer relevant. As such the default base path for the server is now `/`. If you want
|
||||
to retain the old behaviour, you can set the base path via a command line argument as follows:
|
||||
|
||||
```
|
||||
appium --base-path=/wd/hub
|
||||
```
|
||||
|
||||
You can also set server arguments as [Config file](./config.md) properties.
|
||||
|
||||
### :bangbang: Installing drivers during setup
|
||||
|
||||
@@ -32,7 +45,7 @@ If you're running in a CI environment or want to install Appium along with some
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall -g appium`) if you get an installation or startup error.
|
||||
|
||||
### :bangbang: Drivers installation path
|
||||
|
||||
|
||||
@@ -2,4 +2,17 @@
|
||||
title: Write a Test (Java)
|
||||
---
|
||||
|
||||
TODO
|
||||
The Appium team maintains an official [client](https://github.com/appium/java-client) for the Java programming language.
|
||||
It is built on top of [Selenium](https://github.com/SeleniumHQ/selenium).
|
||||
You can also use this client in your Kotlin projects.
|
||||
|
||||
Follow the [Add Appium java client to your test framework](https://github.com/appium/java-client#add-appium-java-client-to-your-test-framework)
|
||||
tutorial in order to connect the library to your test framework sources.
|
||||
|
||||
The Appium Java client has dedicated classes to support most of the official Appium drivers. For other drivers
|
||||
you could simply use the [AppiumDriver](https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/AppiumDriver.java) class
|
||||
or build your custom derivatives from it. Check the [Drivers Support](https://github.com/appium/java-client#drivers-support)
|
||||
article to learn more about the current driver class implementations.
|
||||
|
||||
Follow the [Usage Examples](https://github.com/appium/java-client#usage-examples) article in order understand
|
||||
how to invoke Java client features from your test framework.
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
---
|
||||
title: Appium 1.xから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.
|
||||
--->
|
||||
このドキュメントは既にAppium 1.xを利用している人がAppium 2.xに移行するための手引きです。
|
||||
破壊的変更(breaking changes)の一覧や、実行環境・テストコードをAppium 2.0互換にするための方法が含まれます。
|
||||
|
||||
<!---
|
||||
## Overview of Appium 2.0
|
||||
--->
|
||||
|
||||
## 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.
|
||||
--->
|
||||
|
||||
Appium 2.0は過去5年間におけるAppiumのリリース野中で最も大きなリリースです。Appium 2.0の主要な変更は特定のプラットフォームに対する自動化された動作に関するものでは**ありません**。Appium 2.0はAppiumをドライバー(drivers)(あるプラットフォームに対する自動化を支援するためのプロジェクト)とプラグイン(plugins)(Appiumの動作を上書き、代替、拡張、もしくは追加するためのプロジェクト)という容易に実装、共有できる**仕組みを提供するプラットフォーム**として再考しています。
|
||||
|
||||
<!---
|
||||
At the same time, the Appium project is taking the opportunity to remove many old and deprecated bits of functionality.
|
||||
--->
|
||||
|
||||
同時に、Appiumプロジェクトは多くの古くなったり非推奨となっている機能や依存を取り除こうとしています。
|
||||
|
||||
<!---
|
||||
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.
|
||||
--->
|
||||
|
||||
これらに合わせて、Appiumのインストール方法、ドライバーやフィーチャーの管理、プロトコルサポートに関していくつかの破壊的変更をが導入されます。詳細は以下になります。
|
||||
|
||||
<!---
|
||||
## 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 to account for them.
|
||||
--->
|
||||
|
||||
リリース毎の変更一覧に関しては[Appium 2.0リリースノート](https://github.com/appium/appium/releases)を確認してください。ここでは破壊的変更と何をする必要があるのかを示します。
|
||||
|
||||
<!---
|
||||
### :bangbang: Installing drivers during setup
|
||||
--->
|
||||
|
||||
### :bangbang: ドライバーのインストール
|
||||
|
||||
<!---
|
||||
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@next`), 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 1.xをインストールしたとき、全ての入手可能なドライバーは主となるAppiumサーバーと合わせてインストールされていました。Appium 2.0ではそうではありません。Appium 2.0のインストール(例えば `npm install -g appium@next`)は単にAppiumサーバーのみをインストールし、ドライバーはインストールしません。ドライバーをインストールするためには新しい[Appium拡張コマンドラインインタフェース(Appium extension CLI)](../cli/extensions.md)を使わなければいけません。例えば、最新のXCUITestとUiAutomator2ドライバーをインストールする場合、Appiumをインストールしたのちに次のコマンドを実行する必要があります。
|
||||
|
||||
```bash
|
||||
appium driver install uiautomator2 # installs the latest driver version
|
||||
appium driver install xcuitest@4.12.2 # installs a specific driver version
|
||||
```
|
||||
|
||||
<!---
|
||||
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:
|
||||
--->
|
||||
|
||||
これにより、ドライバーはインストールされ、利用可能になります。このCLIはいろいろな機能を提供しているので、CLIのドキュメントを確認してみてください。もしCI上であったり、いくつかのドライバーをAppiumと一緒にインストールしたい場合、以下のようなフラグを利用することで実現可能です。
|
||||
|
||||
```bash
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
<!---
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
--->
|
||||
|
||||
これはAppiumと2つのドライバーを一つのコマンドでインストールします。
|
||||
|
||||
### :bangbang: Drivers installation path
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server.
|
||||
The path was `/path/to/appium/node_modules`.
|
||||
For example, `appium-webdriveragent` to build WebDriverAgent manually was `/path/to/appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent`.
|
||||
|
||||
Appium 2.0 installs such dependencies in `APPIUM_HOME` environment variable. The default path is `~/.appium`.
|
||||
So, the path to `appium-webdriveragent` could be `$APPIUM_HOME/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent` after installing the XCUITest driver package.
|
||||
|
||||
### :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:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
(For a complete description of the update command, check out the [Extension
|
||||
CLI](../cli/extensions.md) doc)
|
||||
|
||||
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.
|
||||
|
||||
If you would like to update to a specific version, not the latest, please uninstall the driver and install the desired version using the `install` subcommand instead of `update`.
|
||||
|
||||
```bash
|
||||
appium driver uninstall xcuitest
|
||||
appium driver install xcuitest@4.11.1
|
||||
```
|
||||
|
||||
### :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 (at least one maintained by the Appium team), the client will add the `appium:` prefix for you on all necessary capabilities automatically. New versions of [Appium Inspector](https://github.com/appium/appium-inspector) 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. (The new `appium:options` syntax support by cloud providers may vary.)
|
||||
|
||||
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. Note that Appium Desktop 1.21 and lower versions depend on the deprecated [WD](https://github.com/admc/wd) client, and are not compatible with Appium 2.0. There is currently no Appium 2.0 support for Appium Desktop planned, now that the Inspector is a standalone app.
|
||||
|
||||
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_
|
||||
|
||||
Appium extension authors can now develop their own server plugins, which can intercept and modify
|
||||
any Appium command, or even adjust the way the underlying Appium HTTP server itself works. To learn
|
||||
more about plugins, read the new [Appium Introduction](../intro/index.md). Interested in building
|
||||
a plugin? Check out the [Building Plugins](../ecosystem/build-plugins.md) guide.
|
||||
|
||||
#### :tada: _Client Plugins_
|
||||
|
||||
TODO
|
||||
|
||||
### :tada: Install drivers and plugins from anywhere
|
||||
|
||||
You're no longer limited to the drivers that come with Appium, or that the Appium team even knows
|
||||
about! Appium extension authors can now develop custom drivers, which can be downloaded or
|
||||
installed via Appium's [Extension CLI](../cli/extensions.md) from NPM, Git, GitHub, or even the
|
||||
local filesystem. Interested in building a driver? Check out the [Building
|
||||
Drivers](../ecosystem/build-drivers.md) guide.
|
||||
|
||||
### :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!
|
||||
@@ -44,6 +44,7 @@ nav:
|
||||
- guides/event-timing.md
|
||||
- guides/log-filters.md
|
||||
- guides/grid.md
|
||||
- guides/caching.md
|
||||
- Developer Reference:
|
||||
- BaseDriver: reference/base_driver.md
|
||||
- Ecosystem:
|
||||
|
||||
@@ -32,8 +32,8 @@ nav:
|
||||
# - cli/index.md
|
||||
# - cli/args.md
|
||||
# - cli/extensions.md
|
||||
# - Guides: # 手引き?
|
||||
# - guides/migrating-1-to-2.md
|
||||
- 手引き:
|
||||
- guides/migrating-1-to-2.md
|
||||
# - guides/managing-exts.md
|
||||
# - guides/config.md
|
||||
# - guides/security.md
|
||||
|
||||
@@ -5,7 +5,6 @@ const {fs} = require('@appium/support');
|
||||
const {log, LANGS, DOCS_DIR} = require('./utils');
|
||||
const path = require('path');
|
||||
const monorepoRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
||||
const {out: typedocOut} = JSON.parse(fs.readFileSync(path.join(monorepoRoot, 'typedoc.json')));
|
||||
const {exec} = require('teen_process');
|
||||
|
||||
const typedocMap = [[['commands', 'appium_base_driver.md'], ['base_driver.md']]];
|
||||
@@ -14,12 +13,14 @@ async function main() {
|
||||
log.info('Generating typedoc reference material');
|
||||
await exec('npm', ['run', 'typedoc'], {cwd: monorepoRoot});
|
||||
|
||||
const {out: typedocOut} = JSON.parse(await fs.readFile(path.join(monorepoRoot, 'typedoc.json')));
|
||||
|
||||
for (const lang of LANGS) {
|
||||
const langRefPath = path.resolve(DOCS_DIR, lang, 'reference');
|
||||
await fs.rimraf(langRefPath);
|
||||
await fs.mkdirp(langRefPath);
|
||||
for (const [from, to] of typedocMap) {
|
||||
const sourcePath = path.resolve(typedocOut, ...from);
|
||||
const sourcePath = path.resolve(monorepoRoot, typedocOut, ...from);
|
||||
const destPath = path.resolve(langRefPath, ...to);
|
||||
log.info(`Copying ${sourcePath} to ${destPath}`);
|
||||
await fs.copyFile(sourcePath, destPath);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
CREATE_SESSION_COMMAND,
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
promoteAppiumOptions,
|
||||
} from '@appium/base-driver';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {parseCapsForInnerDriver, pullSettings} from './utils';
|
||||
@@ -240,7 +241,7 @@ class AppiumDriver extends DriverCore {
|
||||
// Parse the caps into a format that the InnerDriver will accept
|
||||
const parsedCaps = parseCapsForInnerDriver(
|
||||
jsonwpCaps,
|
||||
w3cCapabilities,
|
||||
promoteAppiumOptions(w3cCapabilities),
|
||||
this.desiredCapConstraints,
|
||||
defaultCapabilities
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mai
|
||||
/**
|
||||
* @extends {ExtensionCommand<DriverType>}
|
||||
*/
|
||||
|
||||
export default class DriverCommand extends ExtensionCommand {
|
||||
/**
|
||||
* @param {import('./extension-command').ExtensionCommandOptions<DriverType>} opts
|
||||
@@ -17,6 +18,12 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
this.knownExtensions = KNOWN_DRIVERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a driver
|
||||
*
|
||||
* @param {DriverInstallOpts} opts
|
||||
* @return {Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
async install({driver, installType, packageName}) {
|
||||
return await super._install({
|
||||
installSpec: driver,
|
||||
@@ -25,18 +32,40 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall a driver
|
||||
*
|
||||
* @param {DriverUninstallOpts} opts
|
||||
* @return {Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
async uninstall({driver}) {
|
||||
return await super._uninstall({installSpec: driver});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a driver
|
||||
*
|
||||
* @param {DriverUpdateOpts} opts
|
||||
* @return {Promise<import('./extension-command').ExtensionUpdateResult>}
|
||||
*/
|
||||
async update({driver, unsafe}) {
|
||||
return await super._update({installSpec: driver, unsafe});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DriverRunOptions} opts
|
||||
* @return {Promise<import('./extension-command').RunOutput>}
|
||||
*/
|
||||
async run({driver, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: driver, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('./extension-command').ExtensionArgs} opts
|
||||
* @returns {string}
|
||||
*/
|
||||
getPostInstallText({extName, extData}) {
|
||||
return (
|
||||
`Driver ${extName}@${extData.version} successfully installed\n`.green +
|
||||
@@ -69,6 +98,16 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef DriverCommandOptions
|
||||
* @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
|
||||
@@ -76,5 +115,34 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* Options for {@linkcode DriverCommand.install}
|
||||
* @typedef DriverInstallOpts
|
||||
* @property {string} driver - the name or spec of a driver to install
|
||||
* @property {InstallType} installType - how to install this driver. One of the INSTALL_TYPES
|
||||
* @property {string} [packageName] - for git/github installs, the driver node package name
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('appium/types').InstallType} InstallType
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.uninstall}
|
||||
* @typedef DriverUninstallOpts
|
||||
* @property {string} driver - the name or spec of a driver to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.update}
|
||||
* @typedef DriverUpdateOpts
|
||||
* @property {string} driver - the name of the driver to update
|
||||
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.run}.
|
||||
* @typedef DriverRunOptions
|
||||
* @property {string} driver - name of the driver to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,7 @@ class ExtensionCommand {
|
||||
* nor is something like `@returns {never}` which does not imply a thrown exception.
|
||||
* @param {string} message
|
||||
* @protected
|
||||
* @returns {Error}
|
||||
* @throws {Error}
|
||||
*/
|
||||
_createFatalError(message) {
|
||||
return new Error(this.log.decorate(message, 'error'));
|
||||
@@ -322,6 +322,7 @@ class ExtensionCommand {
|
||||
|
||||
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
||||
// install type is local.
|
||||
/** @type {string} */
|
||||
const extName = receipt[/** @type {string} */ (`${this.type}Name`)];
|
||||
|
||||
// check _a second time_ with the more-accurate extName
|
||||
@@ -360,7 +361,7 @@ class ExtensionCommand {
|
||||
|
||||
await this.config.addExtension(extName, extManifest);
|
||||
|
||||
// update the if we've changed the local `package.json`
|
||||
// update the hash if we've changed the local `package.json`
|
||||
if (await env.hasAppiumDependency(this.config.appiumHome)) {
|
||||
await packageDidChange(this.config.appiumHome);
|
||||
}
|
||||
@@ -782,9 +783,7 @@ export {ExtensionCommand};
|
||||
|
||||
/**
|
||||
* Possible return value for {@linkcode ExtensionCommand.list}
|
||||
* @typedef UninstalledExtensionListData
|
||||
* @property {string} pkgName
|
||||
* @property {false} installed
|
||||
* @typedef {Partial<InstalledExtensionListData> & {pkgName: string, installed: false}} UninstalledExtensionListData
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,12 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
this.knownExtensions = KNOWN_PLUGINS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin
|
||||
*
|
||||
* @param {PluginInstallOpts} opts
|
||||
* @returns {Promise<ExtRecord<PluginType>>}
|
||||
*/
|
||||
async install({plugin, installType, packageName}) {
|
||||
return await super._install({
|
||||
installSpec: plugin,
|
||||
@@ -25,26 +31,48 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall a plugin
|
||||
*
|
||||
* @param {PluginUninstallOpts} opts
|
||||
* @returns {Promise<ExtRecord<PluginType>>}
|
||||
*/
|
||||
async uninstall({plugin}) {
|
||||
return await super._uninstall({installSpec: plugin});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a plugin
|
||||
*
|
||||
* @param {PluginUpdateOpts} opts
|
||||
* @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
|
||||
*/
|
||||
async update({plugin, unsafe}) {
|
||||
return await super._update({installSpec: plugin, unsafe});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PluginRunOptions} opts
|
||||
* @returns {Promise<import('./extension-command').RunOutput>}
|
||||
*/
|
||||
async run({plugin, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: plugin, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('./extension-command').ExtensionArgs} opts
|
||||
* @returns {string}
|
||||
*/
|
||||
getPostInstallText({extName, extData}) {
|
||||
return `Plugin ${extName}@${extData.version} successfully installed`.green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates fields in `appium` field of `driverMetadata`
|
||||
* Validates fields in `appium` field of `pluginMetadata`
|
||||
*
|
||||
* For any `package.json` fields which a driver requires, validate the type of
|
||||
* For any `package.json` fields which a plugin requires, validate the type of
|
||||
* those fields on the `package.json` data, throwing an error if anything is
|
||||
* amiss.
|
||||
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
|
||||
@@ -67,5 +95,44 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.install}
|
||||
* @typedef PluginInstallOpts
|
||||
* @property {string} plugin - the name or spec of a plugin to install
|
||||
* @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
|
||||
* @property {string} [packageName] - for git/github installs, the plugin node package name
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('appium/types').InstallType} InstallType
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.uninstall}
|
||||
* @typedef PluginUninstallOpts
|
||||
* @property {string} plugin - the name or spec of a plugin to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.update}
|
||||
* @typedef PluginUpdateOpts
|
||||
* @property {string} plugin - the name of the plugin to update
|
||||
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.run}.
|
||||
* @typedef PluginRunOptions
|
||||
* @property {string} plugin - name of the plugin to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ export const KNOWN_DRIVERS = Object.freeze(
|
||||
espresso: 'appium-espresso-driver',
|
||||
safari: 'appium-safari-driver',
|
||||
gecko: 'appium-geckodriver',
|
||||
chromium: 'appium-chromium-driver',
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -33,13 +33,18 @@ const INSTALL_TYPES = new Set([
|
||||
* @template {ExtensionType} ExtType
|
||||
*/
|
||||
export class ExtensionConfig {
|
||||
/** @type {ExtType} */
|
||||
/**
|
||||
* The type of extension this class is responsible for.
|
||||
* @type {ExtType}
|
||||
*/
|
||||
extensionType;
|
||||
|
||||
/** @type {`${ExtType}s`} */
|
||||
configKey;
|
||||
|
||||
/** @type {ExtRecord<ExtType>} */
|
||||
/**
|
||||
* Manifest data for the extensions of this type.
|
||||
*
|
||||
* This data should _not_ be written to by anything but {@linkcode Manifest}.
|
||||
* @type {Readonly<ExtRecord<ExtType>>}
|
||||
*/
|
||||
installedExtensions;
|
||||
|
||||
/** @type {import('@appium/types').AppiumLogger} */
|
||||
@@ -49,9 +54,9 @@ export class ExtensionConfig {
|
||||
manifest;
|
||||
|
||||
/**
|
||||
* @type {ExtensionListData}
|
||||
* @type {ExtensionListData|undefined}
|
||||
*/
|
||||
_listDataCache;
|
||||
#listDataCache;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
@@ -60,7 +65,6 @@ export class ExtensionConfig {
|
||||
*/
|
||||
constructor(extensionType, manifest) {
|
||||
this.extensionType = extensionType;
|
||||
this.configKey = `${extensionType}s`;
|
||||
this.installedExtensions = manifest.getExtensionData(extensionType);
|
||||
this.manifest = manifest;
|
||||
}
|
||||
@@ -204,8 +208,8 @@ export class ExtensionConfig {
|
||||
if (!_.isEmpty(errorSummaries)) {
|
||||
log.error(
|
||||
`Appium encountered ${util.pluralize('error', errorMap.size, true)} while validating ${
|
||||
this.configKey
|
||||
} found in manifest ${this.manifestPath}`
|
||||
this.extensionType
|
||||
}s found in manifest ${this.manifestPath}`
|
||||
);
|
||||
for (const summary of errorSummaries) {
|
||||
log.error(summary);
|
||||
@@ -219,7 +223,7 @@ export class ExtensionConfig {
|
||||
'warning',
|
||||
warningMap.size,
|
||||
true
|
||||
)} while validating ${this.configKey} found in manifest ${this.manifestPath}`
|
||||
)} while validating ${this.extensionType}s found in manifest ${this.manifestPath}`
|
||||
);
|
||||
for (const summary of warningSummaries) {
|
||||
log.warn(summary);
|
||||
@@ -231,18 +235,20 @@ export class ExtensionConfig {
|
||||
|
||||
/**
|
||||
* Retrieves listing data for extensions via command class.
|
||||
* Caches the result in {@linkcode ExtensionConfig._listDataCache}
|
||||
*
|
||||
* This is an expensive operation, so the result is cached. Currently, there is no
|
||||
* use case for invalidating the cache.
|
||||
* @protected
|
||||
* @returns {Promise<ExtensionListData>}
|
||||
*/
|
||||
async getListData() {
|
||||
if (this._listDataCache) {
|
||||
return this._listDataCache;
|
||||
if (this.#listDataCache) {
|
||||
return this.#listDataCache;
|
||||
}
|
||||
const CommandClass = /** @type {ExtCommand<ExtType>} */ (commandClasses[this.extensionType]);
|
||||
const cmd = new CommandClass({config: this, json: true});
|
||||
const listData = await cmd.list({showInstalled: true, showUpdates: true});
|
||||
this._listDataCache = listData;
|
||||
this.#listDataCache = listData;
|
||||
return listData;
|
||||
}
|
||||
|
||||
@@ -433,7 +439,7 @@ export class ExtensionConfig {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async addExtension(extName, extManifest, {write = true} = {}) {
|
||||
this.manifest.addExtension(this.extensionType, extName, extManifest);
|
||||
this.manifest.setExtension(this.extensionType, extName, extManifest);
|
||||
if (write) {
|
||||
await this.manifest.write();
|
||||
}
|
||||
@@ -446,10 +452,10 @@ export class ExtensionConfig {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateExtension(extName, extManifest, {write = true} = {}) {
|
||||
this.installedExtensions[extName] = {
|
||||
this.manifest.setExtension(this.extensionType, extName, {
|
||||
...this.installedExtensions[extName],
|
||||
...extManifest,
|
||||
};
|
||||
});
|
||||
if (write) {
|
||||
await this.manifest.write();
|
||||
}
|
||||
@@ -463,7 +469,7 @@ export class ExtensionConfig {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async removeExtension(extName, {write = true} = {}) {
|
||||
delete this.installedExtensions[extName];
|
||||
this.manifest.deleteExtension(this.extensionType, extName);
|
||||
if (write) {
|
||||
await this.manifest.write();
|
||||
}
|
||||
@@ -477,13 +483,13 @@ export class ExtensionConfig {
|
||||
print(activeNames) {
|
||||
if (_.isEmpty(this.installedExtensions)) {
|
||||
log.info(
|
||||
`No ${this.configKey} have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
|
||||
`No ${this.extensionType}s have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
|
||||
'command to install the one(s) you want to use.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`Available ${this.configKey}:`);
|
||||
log.info(`Available ${this.extensionType}s:`);
|
||||
for (const [extName, extManifest] of /** @type {[string, ExtManifest<ExtType>][]} */ (
|
||||
_.toPairs(this.installedExtensions)
|
||||
)) {
|
||||
@@ -552,7 +558,7 @@ export class ExtensionConfig {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isInstalled(extName) {
|
||||
return _.includes(Object.keys(this.installedExtensions), extName);
|
||||
return extName in this.installedExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
||||
import log from '../logger';
|
||||
|
||||
/**
|
||||
@@ -12,7 +13,13 @@ import log from '../logger';
|
||||
const SCHEMA_REV_3 = 3;
|
||||
|
||||
/**
|
||||
* Collection of functions to migrate from one version to another
|
||||
* Collection of functions to migrate from one version to another.
|
||||
*
|
||||
* These functions _should not actually perform the migration_; rather, they
|
||||
* should return `true` if the migration should be performed. The migration
|
||||
* itself will happen within {@linkcode Manifest.syncWithInstalledExtensions}; the extensions
|
||||
* will be checked and the manifest file updated per the state of the filesystem.
|
||||
*
|
||||
* @type {{[P in keyof ManifestDataVersions]?: Migration}}
|
||||
*/
|
||||
const Migrations = {
|
||||
@@ -24,9 +31,13 @@ const Migrations = {
|
||||
*
|
||||
* @type {Migration}
|
||||
*/
|
||||
[SCHEMA_REV_3]: (data) => {
|
||||
[SCHEMA_REV_3]: (manifest) => {
|
||||
let shouldSync = false;
|
||||
const allExtData = [...Object.values(data.drivers), ...Object.values(data.plugins)];
|
||||
/** @type {Array<ExtManifest<PluginType>|ExtManifest<DriverType>>} */
|
||||
const allExtData = [
|
||||
...Object.values(manifest.getExtensionData(DRIVER_TYPE)),
|
||||
...Object.values(manifest.getExtensionData(PLUGIN_TYPE)),
|
||||
];
|
||||
for (const metadata of allExtData) {
|
||||
if (!('installPath' in metadata)) {
|
||||
shouldSync = true;
|
||||
@@ -38,14 +49,20 @@ const Migrations = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Set `schemaRev` to a specific version
|
||||
* @param {AnyManifestDataVersion} data
|
||||
* Set `schemaRev` to a specific version.
|
||||
*
|
||||
* This _does_ mutate `data` in-place, unlike functions in
|
||||
* {@linkcode Migrations}.
|
||||
*
|
||||
* Again, returning `true` means that the manifest should be--at
|
||||
* minimum--persisted to disk, since we changed it.
|
||||
* @param {Readonly<Manifest>} manifest
|
||||
* @param {keyof ManifestDataVersions} version
|
||||
* @returns {boolean} Whether the data was modified
|
||||
*/
|
||||
function setSchemaRev(data, version) {
|
||||
if (data.schemaRev ?? 0 < version) {
|
||||
data.schemaRev = version;
|
||||
function setSchemaRev(manifest, version) {
|
||||
if (manifest.schemaRev ?? 0 < version) {
|
||||
manifest.setSchemaRev(version);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -56,44 +73,48 @@ function setSchemaRev(data, version) {
|
||||
*
|
||||
* `data` is modified in-place.
|
||||
*
|
||||
* @param {Manifest} manifest
|
||||
* @param {AnyManifestDataVersion} data
|
||||
* @param {Readonly<Manifest>} manifest
|
||||
* @returns {Promise<boolean>} If `true` existing packages should be synced from disk and the manifest should be persisted.
|
||||
*/
|
||||
export async function migrate(manifest, data) {
|
||||
export async function migrate(manifest) {
|
||||
let didChange = false;
|
||||
for await (const [v, migration] of Object.entries(Migrations)) {
|
||||
const version = /** @type {keyof ManifestDataVersions} */ (Number(v));
|
||||
didChange = (await migration(data, manifest)) || didChange;
|
||||
didChange = setSchemaRev(data, version) || didChange;
|
||||
didChange = (await migration(manifest)) || didChange;
|
||||
didChange = setSchemaRev(manifest, version) || didChange;
|
||||
}
|
||||
|
||||
if (didChange) {
|
||||
// this is not _technically_ true, since we don't actually write the file here.
|
||||
log.info(`Upgraded extension manifest to schema v${data.schemaRev}`);
|
||||
log.info(`Upgraded extension manifest to schema v${manifest.schemaRev}`);
|
||||
}
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration functions take a {@linkcode Manifest} and its data and **mutates `data` in-place**.
|
||||
* A migration function. It will return `true` if a change _should be made_.
|
||||
*
|
||||
* A migration function should not modify `schemaRev`, as this is done automatically.
|
||||
*
|
||||
* A migration function can also call out to, say, {@linkcode Manifest.syncWithInstalledExtensions}, which
|
||||
* may apply the mutation itself.
|
||||
*
|
||||
* Note that at the time of writing, we're not able to determine the version of the _current_ manifest file if there is no `schemaRev` prop present (and we may not want to trust it anyway).
|
||||
* @callback Migration
|
||||
* @param {AnyManifestDataVersion} data - The raw data from `Manifest`
|
||||
* @param {Manifest} manifest - The `Manifest` instance
|
||||
* @param {Readonly<Manifest>} manifest - The `Manifest` instance
|
||||
* @returns {boolean|Promise<boolean>} If `true`, then something changed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('appium/types').AnyManifestDataVersion} AnyManifestDataVersion
|
||||
* @typedef {import('appium/types').ManifestDataVersions} ManifestDataVersions
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/types').ManifestV2.ManifestData} ManifestDataV2
|
||||
* @typedef {import('./manifest').Manifest} Manifest
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
|
||||
*/
|
||||
|
||||
@@ -38,6 +38,8 @@ const INITIAL_MANIFEST_DATA = Object.freeze({
|
||||
/**
|
||||
* Given a `package.json` return `true` if it represents an Appium Extension (either a driver or plugin).
|
||||
*
|
||||
* _This is a type guard; not a validator._
|
||||
*
|
||||
* The `package.json` must have an `appium` property which is an object.
|
||||
* @param {any} value
|
||||
* @returns {value is ExtPackageJson<ExtensionType>}
|
||||
@@ -50,32 +52,35 @@ function isExtension(value) {
|
||||
_.isString(value.version)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `package.json`, return `true` if it represents an Appium Driver.
|
||||
*
|
||||
* To be considered a driver, a `package.json` must have a fields
|
||||
* `appium.driverName`, `appium.automationName` and `appium.platformNames`.
|
||||
* _This is a type guard; not a validator._
|
||||
*
|
||||
* To be considered a driver, a `package.json` must have an `appium.driverName` field.
|
||||
*
|
||||
* Further validation of the `appium` property happens elsewhere.
|
||||
* @param {any} value - Value to test
|
||||
* @returns {value is ExtPackageJson<DriverType>}
|
||||
*/
|
||||
function isDriver(value) {
|
||||
return (
|
||||
isExtension(value) &&
|
||||
_.isString(_.get(value, 'appium.driverName')) &&
|
||||
_.isString(_.get(value, 'appium.automationName')) &&
|
||||
_.isArray(_.get(value, 'appium.platformNames'))
|
||||
);
|
||||
return isExtension(value) && 'driverName' in value.appium && _.isString(value.appium.driverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `package.json`, return `true` if it represents an Appium Plugin.
|
||||
*
|
||||
* _This is a type guard; not a validator._
|
||||
*
|
||||
* To be considered a plugin, a `package.json` must have an `appium.pluginName` field.
|
||||
*
|
||||
* Further validation of the `appium` property happens elsewhere.
|
||||
* @param {any} value - Value to test
|
||||
* @returns {value is ExtPackageJson<PluginType>}
|
||||
*/
|
||||
function isPlugin(value) {
|
||||
return isExtension(value) && _.isString(_.get(value, 'appium.pluginName'));
|
||||
return isExtension(value) && 'pluginName' in value.appium && _.isString(value.appium.pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +171,23 @@ export class Manifest {
|
||||
const onMatch = async (filepath) => {
|
||||
try {
|
||||
const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
|
||||
if (isDriver(pkg) || isPlugin(pkg)) {
|
||||
if (isExtension(pkg)) {
|
||||
const extType = isDriver(pkg) ? DRIVER_TYPE : PLUGIN_TYPE;
|
||||
/**
|
||||
* this should only be 'unknown' if the extension's `package.json` is invalid
|
||||
* @type {string}
|
||||
*/
|
||||
const name = isDriver(pkg)
|
||||
? pkg.appium.driverName
|
||||
: isPlugin(pkg)
|
||||
? pkg.appium.pluginName
|
||||
: '(unknown)';
|
||||
if (
|
||||
(isDriver(pkg) && !this.hasDriver(name)) ||
|
||||
(isPlugin(pkg) && !this.hasPlugin(name))
|
||||
) {
|
||||
log.info(`Discovered installed ${extType} "${name}"`);
|
||||
}
|
||||
const changed = this.addExtensionFromPackage(pkg, filepath);
|
||||
didChange = didChange || changed;
|
||||
}
|
||||
@@ -228,11 +249,10 @@ export class Manifest {
|
||||
/**
|
||||
* Given a path to a `package.json`, add it as either a driver or plugin to the manifest.
|
||||
*
|
||||
* Will _not_ overwrite existing entries.
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {ExtPackageJson<ExtType>} pkgJson
|
||||
* @param {string} pkgPath
|
||||
* @returns {boolean} - `true` upon success, `false` if the extension is already registered.
|
||||
* @returns {boolean} - `true` if this method did anything.
|
||||
*/
|
||||
addExtensionFromPackage(pkgJson, pkgPath) {
|
||||
const extensionPath = path.dirname(pkgPath);
|
||||
@@ -250,26 +270,28 @@ export class Manifest {
|
||||
};
|
||||
|
||||
if (isDriver(pkgJson)) {
|
||||
if (!this.hasDriver(pkgJson.appium.driverName)) {
|
||||
this.addExtension(DRIVER_TYPE, pkgJson.appium.driverName, {
|
||||
..._.omit(pkgJson.appium, 'driverName'),
|
||||
...internal,
|
||||
});
|
||||
const value = {
|
||||
..._.omit(pkgJson.appium, 'driverName'),
|
||||
...internal,
|
||||
};
|
||||
if (!_.isEqual(value, this.#data.drivers[pkgJson.appium.driverName])) {
|
||||
this.setExtension(DRIVER_TYPE, pkgJson.appium.driverName, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (isPlugin(pkgJson)) {
|
||||
if (!this.hasPlugin(pkgJson.appium.pluginName)) {
|
||||
this.addExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, {
|
||||
..._.omit(pkgJson.appium, 'pluginName'),
|
||||
...internal,
|
||||
});
|
||||
const value = {
|
||||
..._.omit(pkgJson.appium, 'pluginName'),
|
||||
...internal,
|
||||
};
|
||||
if (!_.isEqual(value, this.#data.plugins[pkgJson.appium.pluginName])) {
|
||||
this.setExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
throw new TypeError(
|
||||
`The extension in ${extensionPath} is neither a valid driver nor a valid plugin.`
|
||||
`The extension in ${extensionPath} is neither a valid ${DRIVER_TYPE} nor a valid ${PLUGIN_TYPE}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -285,12 +307,29 @@ export class Manifest {
|
||||
* @param {ExtManifest<ExtType>} extData - Extension metadata
|
||||
* @returns {ExtManifest<ExtType>} A clone of `extData`, potentially with a mutated `appiumVersion` field
|
||||
*/
|
||||
addExtension(extType, extName, extData) {
|
||||
const data = _.clone(extData);
|
||||
setExtension(extType, extName, extData) {
|
||||
const data = _.cloneDeep(extData);
|
||||
this.#data[`${extType}s`][extName] = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the schema revision
|
||||
* @param {keyof import('./manifest-migrations').ManifestDataVersions} rev
|
||||
*/
|
||||
setSchemaRev(rev) {
|
||||
this.#data.schemaRev = rev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension from the manifest.
|
||||
* @param {ExtensionType} extType
|
||||
* @param {string} extName
|
||||
*/
|
||||
deleteExtension(extType, extName) {
|
||||
delete this.#data[`${extType}s`][extName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `APPIUM_HOME` path
|
||||
*/
|
||||
@@ -305,12 +344,19 @@ export class Manifest {
|
||||
return this.#manifestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the schema rev of this manifest
|
||||
*/
|
||||
get schemaRev() {
|
||||
return this.#data.schemaRev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns extension data for a particular type.
|
||||
*
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {ExtType} extType
|
||||
* @returns {ExtRecord<ExtType>}
|
||||
* @returns {Readonly<ExtRecord<ExtType>>}
|
||||
*/
|
||||
getExtensionData(extType) {
|
||||
return this.#data[/** @type {string} */ (`${extType}s`)];
|
||||
@@ -319,11 +365,18 @@ export class Manifest {
|
||||
/**
|
||||
* Reads manifest from disk and _overwrites_ the internal data.
|
||||
*
|
||||
* If the manifest does not exist on disk, an {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created.
|
||||
* If the manifest does not exist on disk, an
|
||||
* {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created, as
|
||||
* well as its directory if needed.
|
||||
*
|
||||
* If `APPIUM_HOME` contains a `package.json` with an `appium` dependency, then a hash of the `package.json` will be taken. If this hash differs from the last hash, the contents of `APPIUM_HOME/node_modules` will be scanned for extensions that may have been installed outside of the `appium` CLI. Any found extensions will be added to the manifest file, and if so, the manifest file will be written to disk.
|
||||
* This will also, if necessary:
|
||||
* 1. perform a migration of the manifest data
|
||||
* 2. sync the manifest with extensions on-disk (kind of like "auto
|
||||
* discovery")
|
||||
* 3. write the manifest to disk.
|
||||
*
|
||||
* Only one read operation can happen at a time.
|
||||
*
|
||||
* Only one read operation should happen at a time.
|
||||
* @returns {Promise<ManifestData>} The data
|
||||
*/
|
||||
async read() {
|
||||
@@ -335,17 +388,23 @@ export class Manifest {
|
||||
this.#reading = (async () => {
|
||||
/** @type {ManifestData} */
|
||||
let data;
|
||||
let isNewFile = false;
|
||||
/**
|
||||
* This will be `true` if, after reading, we need to update the manifest data
|
||||
* and write it again to disk.
|
||||
*/
|
||||
let shouldWrite = false;
|
||||
await this.#setManifestPath();
|
||||
try {
|
||||
log.debug(`Reading ${this.#manifestPath}...`);
|
||||
const yaml = await fs.readFile(this.#manifestPath, 'utf8');
|
||||
data = YAML.parse(yaml);
|
||||
log.debug(`Parsed manifest file: ${JSON.stringify(data, null, 2)}`);
|
||||
log.debug(
|
||||
`Parsed manifest file at ${this.#manifestPath}: ${JSON.stringify(data, null, 2)}`
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
log.debug(`No manifest file found at ${this.#manifestPath}; creating`);
|
||||
data = _.cloneDeep(INITIAL_MANIFEST_DATA);
|
||||
isNewFile = true;
|
||||
shouldWrite = true;
|
||||
} else {
|
||||
if (this.#manifestPath) {
|
||||
throw new Error(
|
||||
@@ -364,27 +423,42 @@ export class Manifest {
|
||||
|
||||
this.#data = data;
|
||||
|
||||
let shouldWrite = false;
|
||||
|
||||
if ((data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
|
||||
/**
|
||||
* the only way `shouldWrite` is `true` is if we have a new file. a new
|
||||
* file will get the latest schema revision, so we can skip the migration.
|
||||
*/
|
||||
if (!shouldWrite && (data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
|
||||
log.debug(
|
||||
`Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
|
||||
);
|
||||
shouldWrite = await migrate(this, this.#data);
|
||||
shouldWrite = await migrate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* we still may want to sync with installed extensions even if we have a
|
||||
* new file. right now this is limited to the following cases:
|
||||
* 1. we have a brand new manifest file
|
||||
* 2. we have performed a migration on a manifest file
|
||||
* 3. `appium` is a dependency within `package.json`, and `package.json`
|
||||
* has changed since last time we checked.
|
||||
*
|
||||
* It may also make sense to sync with the extensions in an arbitrary
|
||||
* `APPIUM_HOME`, but we don't do that here.
|
||||
*/
|
||||
if (
|
||||
shouldWrite ||
|
||||
((await env.hasAppiumDependency(this.appiumHome)) &&
|
||||
(await packageDidChange(this.appiumHome)))
|
||||
) {
|
||||
shouldWrite = await this.syncWithInstalledExtensions();
|
||||
log.debug('Discovering newly installed extensions...');
|
||||
shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
|
||||
}
|
||||
|
||||
if (isNewFile || shouldWrite) {
|
||||
if (shouldWrite) {
|
||||
await this.write();
|
||||
}
|
||||
})();
|
||||
|
||||
try {
|
||||
await this.#reading;
|
||||
return this.#data;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.47",
|
||||
"version": "2.0.0-beta.50",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -38,7 +38,8 @@
|
||||
"support.*",
|
||||
"plugin.*",
|
||||
"scripts/autoinstall-extensions.js",
|
||||
"types"
|
||||
"types",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build:docs": "node docs/scripts/build-docs.js",
|
||||
@@ -52,17 +53,17 @@
|
||||
"postinstall": "node ./scripts/autoinstall-extensions.js",
|
||||
"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:e2e": "mocha -p --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
|
||||
"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": "^9.0.0",
|
||||
"@appium/base-plugin": "^2.0.0",
|
||||
"@appium/docutils": "^0.1.0",
|
||||
"@appium/schema": "^0.1.0",
|
||||
"@appium/support": "^3.0.0",
|
||||
"@appium/types": "^0.6.0",
|
||||
"@appium/base-driver": "^9.2.1",
|
||||
"@appium/base-plugin": "^2.1.1",
|
||||
"@appium/docutils": "^0.1.3",
|
||||
"@appium/schema": "^0.2.1",
|
||||
"@appium/support": "^3.1.1",
|
||||
"@appium/types": "^0.8.1",
|
||||
"@sidvind/better-ajv-errors": "2.1.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.38",
|
||||
@@ -70,12 +71,12 @@
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.11.2",
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.2.1",
|
||||
"axios": "1.2.2",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
@@ -90,10 +91,10 @@
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.3.0",
|
||||
"type-fest": "3.5.1",
|
||||
"winston": "3.8.2",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.1.3"
|
||||
"yaml": "2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
@@ -103,8 +104,8 @@
|
||||
"access": "public",
|
||||
"tag": "next"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
|
||||
"typedoc": {
|
||||
"entryPoint": "./build/lib/main.js"
|
||||
"entryPoint": "./lib/main.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "7.27.0"
|
||||
"webdriverio": "7.29.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import getPort from 'get-port';
|
||||
import {tempDir, fs} from '@appium/support';
|
||||
import {exec} from 'teen_process';
|
||||
import B from 'bluebird';
|
||||
import {
|
||||
APPIUM_ROOT,
|
||||
readAppiumArgErrorFixture,
|
||||
formatAppiumArgErrorOutput,
|
||||
EXECUTABLE,
|
||||
runAppiumRaw,
|
||||
} from './e2e-helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('argument parsing', function () {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let appiumHome;
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
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',
|
||||
String(await getPort()),
|
||||
],
|
||||
{
|
||||
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
|
||||
cwd: APPIUM_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 () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--port=sheep'], {
|
||||
env: {FORCE_COLOR: '1'},
|
||||
}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-color.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when color output is unsupported', function () {
|
||||
it('should output a colorless yet fancy error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--port=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user provides a value for a boolean argument', function () {
|
||||
it('should output a basic error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--relaxed-security=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-boolean.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user provides an unknown argument', function () {
|
||||
it('should output a basic error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--pigs=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-unknown.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,421 @@
|
||||
// @ts-check
|
||||
|
||||
import {exec} from 'teen_process';
|
||||
import {fs, tempDir, util} from '@appium/support';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import {
|
||||
DRIVER_TYPE,
|
||||
EXT_SUBCOMMAND_INSTALL as INSTALL,
|
||||
EXT_SUBCOMMAND_LIST as LIST,
|
||||
EXT_SUBCOMMAND_RUN as RUN,
|
||||
EXT_SUBCOMMAND_UNINSTALL as UNINSTALL,
|
||||
KNOWN_DRIVERS,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
import {installLocalExtension, runAppiumJson, runAppiumRaw} from './e2e-helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
const TEST_DRIVER_DIR = path.dirname(resolveFixture('test-driver/package.json'));
|
||||
|
||||
const TEST_DRIVER_INVALID_PEERS_DIR = path.dirname(
|
||||
resolveFixture('test-driver-invalid-peer-dep/package.json')
|
||||
);
|
||||
|
||||
describe('Driver CLI', function () {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let appiumHome;
|
||||
|
||||
/**
|
||||
* @type {(args?: string[]) => Promise<ExtensionListData>}
|
||||
*/
|
||||
let runList;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
|
||||
*/
|
||||
let runRun;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
let runInstall;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
let runUninstall;
|
||||
|
||||
async function resetAppiumHome() {
|
||||
await fs.rimraf(appiumHome);
|
||||
await fs.mkdirp(appiumHome);
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
const run = runAppiumJson(appiumHome);
|
||||
runInstall = async (args) =>
|
||||
/** @type {ReturnType<typeof runInstall>} */ (await run([DRIVER_TYPE, INSTALL, ...args]));
|
||||
runUninstall = async (args) =>
|
||||
/** @type {ReturnType<typeof runUninstall>} */ (await run([DRIVER_TYPE, UNINSTALL, ...args]));
|
||||
runList = async (args = []) =>
|
||||
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
|
||||
runRun = async (args) =>
|
||||
/** @type {ReturnType<typeof runRun>} */ (await run([DRIVER_TYPE, RUN, ...args]));
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
describe(LIST, function () {
|
||||
it('should list available drivers', async function () {
|
||||
// note this is raw, not json
|
||||
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST], {});
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
stderr.should.match(new RegExp(`${d}.+[not installed]`));
|
||||
}
|
||||
});
|
||||
|
||||
it('should list available drivers in json format', async function () {
|
||||
const driverData = await runList();
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
driverData[d].should.eql({
|
||||
installed: false,
|
||||
pkgName: KNOWN_DRIVERS[d],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow filtering by installed drivers', async function () {
|
||||
const out = await runList(['--installed']);
|
||||
out.should.eql({});
|
||||
});
|
||||
|
||||
it('should show updates for installed drivers with --updates', async function () {
|
||||
const versions = /** @type {string[]} */ (
|
||||
JSON.parse(
|
||||
(
|
||||
await exec('npm', ['view', '@appium/fake-driver', 'versions', '--json'], {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
).stdout
|
||||
)
|
||||
);
|
||||
|
||||
const penultimateFakeDriverVersionAsOfRightNow = versions[versions.length - 2];
|
||||
|
||||
await resetAppiumHome();
|
||||
await runInstall([
|
||||
`@appium/fake-driver@${penultimateFakeDriverVersionAsOfRightNow}`,
|
||||
'--source',
|
||||
'npm',
|
||||
]);
|
||||
const {fake} =
|
||||
/** @type {Record<string,import('appium/lib/cli/extension-command').InstalledExtensionListData>} */ (
|
||||
await runList(['--updates'])
|
||||
);
|
||||
const updateVersion = String(fake.updateVersion || fake.unsafeUpdateVersion);
|
||||
util.compareVersions(
|
||||
String(updateVersion),
|
||||
'>',
|
||||
penultimateFakeDriverVersionAsOfRightNow
|
||||
).should.be.true;
|
||||
// TODO: this could probably be replaced by looking at updateVersion in the JSON
|
||||
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST, '--updates'], {});
|
||||
stderr.should.match(new RegExp(`fake.+[${updateVersion} available]`));
|
||||
});
|
||||
|
||||
describe('if a driver is not published to npm', function () {
|
||||
it('should not throw an error', async function () {
|
||||
await resetAppiumHome();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, TEST_DRIVER_DIR);
|
||||
await expect(runList(['--updates'])).not.to.be.rejected;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(INSTALL, function () {
|
||||
beforeEach(async function () {
|
||||
await resetAppiumHome();
|
||||
});
|
||||
|
||||
it('should not install appium in APPIUM_HOME', async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
await fs.stat(path.join(appiumHome, 'node_modules', 'appium')).should.be.rejected;
|
||||
});
|
||||
|
||||
it('should install a driver from the list of known drivers', async function () {
|
||||
const ret = await runInstall(['uiautomator2']);
|
||||
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
|
||||
ret.uiautomator2.installType.should.eql('npm');
|
||||
ret.uiautomator2.installSpec.should.eql('uiautomator2');
|
||||
const list = await runList(['--installed']);
|
||||
// @ts-ignore
|
||||
delete list.uiautomator2.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from npm', async function () {
|
||||
const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('@appium/fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
// @ts-ignore
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from npm and a local driver', async function () {
|
||||
await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, TEST_DRIVER_DIR);
|
||||
const list = await runList(['--installed']);
|
||||
expect(list.fake).to.exist;
|
||||
expect(list.test).to.exist;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
|
||||
});
|
||||
|
||||
it('should install _two_ drivers from npm', async function () {
|
||||
await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
await runInstall(['appium-uiautomator2-driver', '--source', 'npm']);
|
||||
const list = await runList(['--installed']);
|
||||
expect(list.fake).to.exist;
|
||||
expect(list.uiautomator2).to.exist;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'appium-uiautomator2-driver')).not.to.throw;
|
||||
});
|
||||
|
||||
it('should install a driver from npm with a specific version/tag', async function () {
|
||||
const currentFakeDriverVersionAsOfRightNow = '3.0.5';
|
||||
const installSpec = `@appium/fake-driver@${currentFakeDriverVersionAsOfRightNow}`;
|
||||
const ret = await runInstall([installSpec, '--source', 'npm']);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql(installSpec);
|
||||
const list = await runList(['--installed']);
|
||||
// @ts-ignore
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from GitHub', async function () {
|
||||
const ret = await runInstall([
|
||||
'appium/appium-fake-driver',
|
||||
'--source',
|
||||
'github',
|
||||
'--package',
|
||||
'appium-fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('github');
|
||||
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
// @ts-ignore
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from a local git repo', async function () {
|
||||
const ret = await runInstall([
|
||||
FAKE_DRIVER_DIR,
|
||||
'--source',
|
||||
'git',
|
||||
'--package',
|
||||
'@appium/fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql(FAKE_DRIVER_DIR);
|
||||
const list = await runList(['--installed', '--json']);
|
||||
// @ts-ignore
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from a remote git repo', async function () {
|
||||
const ret = await runInstall([
|
||||
'git+https://github.com/appium/appium-fake-driver.git',
|
||||
'--source',
|
||||
'git',
|
||||
'--package',
|
||||
'appium-fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
// @ts-ignore
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
describe('when peer dependencies are invalid', function () {
|
||||
it('should install the driver anyway', async function () {
|
||||
const ret = await installLocalExtension(
|
||||
appiumHome,
|
||||
DRIVER_TYPE,
|
||||
TEST_DRIVER_INVALID_PEERS_DIR
|
||||
);
|
||||
ret.test.pkgName.should.equal('test-driver-invalid-peer-dep');
|
||||
const list = await runList(['--installed']);
|
||||
list.test.pkgName.should.equal('test-driver-invalid-peer-dep');
|
||||
});
|
||||
|
||||
it('should warn the user that peer deps are invalid', async function () {
|
||||
const ret = await runAppiumRaw(
|
||||
appiumHome,
|
||||
[DRIVER_TYPE, INSTALL, '--source', 'local', TEST_DRIVER_INVALID_PEERS_DIR],
|
||||
{}
|
||||
);
|
||||
ret.stderr.should.match(/may be incompatible with the current version of Appium/i);
|
||||
ret.stderr.should.match(/successfully installed/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when peer dependencies are valid', function () {
|
||||
it('should not display a warning', async function () {
|
||||
const ret = await runAppiumRaw(
|
||||
appiumHome,
|
||||
[DRIVER_TYPE, INSTALL, '--source', 'local', TEST_DRIVER_DIR],
|
||||
{}
|
||||
);
|
||||
ret.stderr.should.not.match(/may be incompatible with the current version of Appium/i);
|
||||
ret.stderr.should.match(/successfully installed/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Local ${INSTALL}`, function () {
|
||||
// NOTE: This suite is separate because we don't to run `clear()` before each test.
|
||||
|
||||
/** @type {ExtRecord<DriverType>} */
|
||||
let installResult;
|
||||
|
||||
/** @type {ExtensionListData} */
|
||||
let listResult;
|
||||
|
||||
/** @type {string} */
|
||||
let installPath;
|
||||
|
||||
before(async function () {
|
||||
await resetAppiumHome();
|
||||
// take advantage of the fact that we know we have fake driver installed as a dependency in
|
||||
// this module, so we know its local path on disk
|
||||
installResult = await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
listResult = await runList(['--installed']);
|
||||
installPath = resolveFrom(appiumHome, '@appium/fake-driver');
|
||||
});
|
||||
|
||||
it('should install a driver from a local npm module', function () {
|
||||
expect(installResult.fake).to.include({
|
||||
pkgName: '@appium/fake-driver',
|
||||
installType: 'local',
|
||||
installSpec: FAKE_DRIVER_DIR,
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the installed driver in the list of extensions', function () {
|
||||
expect(listResult.fake).to.deep.include(installResult.fake);
|
||||
});
|
||||
|
||||
// this fails in CI and I don't know why
|
||||
it.skip('should create a symlink', async function () {
|
||||
const srcStat = await fs.lstat(FAKE_DRIVER_DIR);
|
||||
const destStat = await fs.lstat(appiumHome);
|
||||
if (srcStat.dev !== destStat.dev) {
|
||||
return this.skip();
|
||||
}
|
||||
// it should be a link! this may be npm-version dependent, but it's worked
|
||||
// this way for quite awhile
|
||||
const stat = await fs.lstat(installPath);
|
||||
expect(stat.isSymbolicLink()).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstall', function () {
|
||||
beforeEach(async function () {
|
||||
await resetAppiumHome();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
it('should uninstall a driver based on its driver name', async function () {
|
||||
const uninstall = await runUninstall(['fake']);
|
||||
uninstall.should.not.have.key('fake');
|
||||
expect(fs.exists(path.join(appiumHome, 'node_modules', '@appium', 'fake-driver'))).to
|
||||
.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
const driverName = 'fake';
|
||||
|
||||
before(async function () {
|
||||
await resetAppiumHome();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
describe('when the driver and script is valid', function () {
|
||||
const scriptName = 'fake-success';
|
||||
|
||||
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/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
|
||||
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template ExtSubCommand
|
||||
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
|
||||
*/
|
||||
@@ -0,0 +1,79 @@
|
||||
// @ts-check
|
||||
|
||||
import {fs, tempDir} from '@appium/support';
|
||||
import {EXT_SUBCOMMAND_RUN as RUN, PLUGIN_TYPE} from '../../lib/constants';
|
||||
import {FAKE_PLUGIN_DIR} from '../helpers';
|
||||
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('Plugin CLI', function () {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let appiumHome;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
|
||||
*/
|
||||
let runRun;
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
const run = runAppiumJson(appiumHome);
|
||||
runRun = async (args) =>
|
||||
/** @type {ReturnType<typeof runRun>} */ (await run([PLUGIN_TYPE, RUN, ...args]));
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, PLUGIN_TYPE, FAKE_PLUGIN_DIR);
|
||||
});
|
||||
|
||||
it('should run a valid plugin, valid script, and result in success', async function () {
|
||||
const pluginName = 'fake';
|
||||
const scriptName = 'fake-success';
|
||||
const out = await runRun([pluginName, scriptName, '--json']);
|
||||
out.should.not.have.property('error');
|
||||
});
|
||||
|
||||
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
|
||||
const pluginName = 'fake';
|
||||
const out = await runRun([pluginName, 'fake-error', '--json']);
|
||||
out.should.have.property('error');
|
||||
});
|
||||
|
||||
it('should take a valid plugin, invalid script, and throw an error', async function () {
|
||||
const pluginName = 'fake';
|
||||
await expect(runRun([pluginName, 'foo', '--json'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
|
||||
it('should take an invalid plugin, invalid script, and throw an error', async function () {
|
||||
await expect(runRun(['foo', 'bar', '--json'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
|
||||
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template ExtSubCommand
|
||||
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
|
||||
*/
|
||||
@@ -1,708 +0,0 @@
|
||||
// @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';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import YAML from 'yaml';
|
||||
import {
|
||||
DRIVER_TYPE,
|
||||
EXT_SUBCOMMAND_INSTALL as INSTALL,
|
||||
EXT_SUBCOMMAND_LIST as LIST,
|
||||
EXT_SUBCOMMAND_RUN as RUN,
|
||||
EXT_SUBCOMMAND_UNINSTALL as UNINSTALL,
|
||||
PKG_HASHFILE_RELATIVE_PATH,
|
||||
KNOWN_DRIVERS,
|
||||
PLUGIN_TYPE,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, APPIUM_ROOT, resolveFixture} from '../helpers';
|
||||
import {
|
||||
installLocalExtension,
|
||||
runAppiumJson,
|
||||
runAppiumRaw,
|
||||
readAppiumArgErrorFixture,
|
||||
formatAppiumArgErrorOutput,
|
||||
EXECUTABLE,
|
||||
} from './e2e-helpers';
|
||||
|
||||
const {MANIFEST_RELATIVE_PATH} = env;
|
||||
const {expect} = chai;
|
||||
|
||||
describe('CLI behavior', function () {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let appiumHome;
|
||||
|
||||
const testDriverPath = path.dirname(resolveFixture('test-driver/package.json'));
|
||||
const testDriverInvalidPeerDepsPath = path.dirname(
|
||||
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;
|
||||
/** @type {string} */
|
||||
let manifestPath;
|
||||
/** @type {string} */
|
||||
let appiumHomePkgPath;
|
||||
|
||||
/**
|
||||
* Helper fn
|
||||
* @returns {Promise<ManifestData>}
|
||||
*/
|
||||
async function readManifest() {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
return YAML.parse(manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper fn
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function readHash() {
|
||||
return await fs.readFile(hashPath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {CliExtensionSubcommand} ExtSubcommand
|
||||
* @type {import('lodash').CurriedFunction1<CliExtArgs<ExtSubcommand> | CliArgs, Promise<unknown>>}
|
||||
*/
|
||||
let runJson;
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
describe('when the project is an extension', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
await fs.copyFile(resolveFixture('test-driver/package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
it('should automatically discover the extension', async function () {
|
||||
const res = await runJson([DRIVER_TYPE, LIST]);
|
||||
expect(res).to.have.property('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the project is not an extension', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
// an example package.json referencing appium dependency
|
||||
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
describe('without drivers installed', function () {
|
||||
it('should list no drivers', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.satisfy(
|
||||
/** @param {typeof res} value */ (value) =>
|
||||
Object.values(value).every(({installed}) => !installed)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after a driver is installed', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
it('should list the driver', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should be resolvable from the local directory', function () {
|
||||
(() => resolveFrom(appiumHome, '@appium/fake-driver/package.json')).should.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a driver is installed via npm', function () {
|
||||
before(async function () {
|
||||
// remove the hash file and the manifest to start clean.
|
||||
// this leaves any previously-installed extension in place, which should make this a little faster.
|
||||
await fs.rimraf(path.dirname(hashPath));
|
||||
await npm.exec(INSTALL, [FAKE_DRIVER_DIR], {
|
||||
json: true,
|
||||
cwd: appiumHome,
|
||||
});
|
||||
});
|
||||
|
||||
describe(LIST, function () {
|
||||
/** @type {ExtensionListData} */
|
||||
let res;
|
||||
|
||||
beforeEach(async function () {
|
||||
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
});
|
||||
|
||||
it('should list the driver', function () {
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should update the manifest', async function () {
|
||||
const manifestParsed = await readManifest();
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should update the package hash', async function () {
|
||||
(await fs.exists(hashPath)).should.be.true;
|
||||
});
|
||||
|
||||
describe('when a different driver is installed via "appium driver install"', function () {
|
||||
/** @type {string} */
|
||||
let oldHash;
|
||||
|
||||
before(async function () {
|
||||
await runJson([DRIVER_TYPE, LIST]);
|
||||
oldHash = await readHash();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
});
|
||||
|
||||
it('should update package.json', async function () {
|
||||
const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8'));
|
||||
expect(newPkg).to.have.nested.property('devDependencies.@appium/test-driver');
|
||||
});
|
||||
|
||||
it('should update the hash', async function () {
|
||||
const newHash = await readHash();
|
||||
newHash.should.not.equal(oldHash);
|
||||
});
|
||||
|
||||
it('should update the manifest with the new driver', async function () {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
/** @type {ManifestData} */
|
||||
const manifestParsed = YAML.parse(manifest);
|
||||
manifestParsed.should.have.nested.property('drivers.test');
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should actually install both drivers', function () {
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/test-driver')).not.to.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Extension Commands', function () {
|
||||
/**
|
||||
* @type {(args?: string[]) => Promise<ExtensionListData>}
|
||||
*/
|
||||
let runList;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
|
||||
*/
|
||||
let runRun;
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
async function clear() {
|
||||
await fs.rimraf(appiumHome);
|
||||
await fs.mkdirp(appiumHome);
|
||||
}
|
||||
|
||||
describe('Driver CLI', function () {
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
let runInstall;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
let runUninstall;
|
||||
|
||||
before(function () {
|
||||
const run = runAppiumJson(appiumHome);
|
||||
runInstall = async (args) =>
|
||||
/** @type {ReturnType<typeof runInstall>} */ (await run([DRIVER_TYPE, INSTALL, ...args]));
|
||||
runUninstall = async (args) =>
|
||||
/** @type {ReturnType<typeof runUninstall>} */ (
|
||||
await run([DRIVER_TYPE, UNINSTALL, ...args])
|
||||
);
|
||||
runList = async (args = []) =>
|
||||
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
|
||||
runRun = async (args) =>
|
||||
/** @type {ReturnType<typeof runRun>} */ (await run([DRIVER_TYPE, RUN, ...args]));
|
||||
});
|
||||
|
||||
describe(LIST, function () {
|
||||
it('should list available drivers', async function () {
|
||||
// note this is raw, not json
|
||||
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST], {});
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
stderr.should.match(new RegExp(`${d}.+[not installed]`));
|
||||
}
|
||||
});
|
||||
it('should list available drivers in json format', async function () {
|
||||
const driverData = await runList();
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
driverData[d].should.eql({
|
||||
installed: false,
|
||||
pkgName: KNOWN_DRIVERS[d],
|
||||
});
|
||||
}
|
||||
});
|
||||
it('should allow filtering by installed drivers', async function () {
|
||||
const out = await runList(['--installed']);
|
||||
out.should.eql({});
|
||||
});
|
||||
it('should show updates for installed drivers with --updates', async function () {
|
||||
const penultimateFakeDriverVersionAsOfRightNow = '3.0.4';
|
||||
await clear();
|
||||
await runInstall([
|
||||
`@appium/fake-driver@${penultimateFakeDriverVersionAsOfRightNow}`,
|
||||
'--source',
|
||||
'npm',
|
||||
]);
|
||||
const {fake} =
|
||||
/** @type {Record<string,import('appium/lib/cli/extension-command').InstalledExtensionListData>} */ (
|
||||
await runList(['--updates'])
|
||||
);
|
||||
util.compareVersions(
|
||||
fake.updateVersion,
|
||||
'>',
|
||||
penultimateFakeDriverVersionAsOfRightNow
|
||||
).should.be.true;
|
||||
// TODO: this could probably be replaced by looking at updateVersion in the JSON
|
||||
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 () {
|
||||
beforeEach(async function () {
|
||||
await clear();
|
||||
});
|
||||
|
||||
it('should install a driver from the list of known drivers', async function () {
|
||||
const ret = await runInstall(['uiautomator2']);
|
||||
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
|
||||
ret.uiautomator2.installType.should.eql('npm');
|
||||
ret.uiautomator2.installSpec.should.eql('uiautomator2');
|
||||
const list = await runList(['--installed']);
|
||||
delete list.uiautomator2.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from npm', async function () {
|
||||
const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('@appium/fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
it('should install a driver from npm and a local driver', async function () {
|
||||
await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
const list = await runList(['--installed']);
|
||||
expect(list.fake).to.exist;
|
||||
expect(list.test).to.exist;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
|
||||
});
|
||||
|
||||
it('should install _two_ drivers from npm', async function () {
|
||||
await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
await runInstall(['appium-uiautomator2-driver', '--source', 'npm']);
|
||||
const list = await runList(['--installed']);
|
||||
expect(list.fake).to.exist;
|
||||
expect(list.uiautomator2).to.exist;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'appium-uiautomator2-driver')).not.to.throw;
|
||||
});
|
||||
|
||||
it('should install a driver from npm with a specific version/tag', async function () {
|
||||
const currentFakeDriverVersionAsOfRightNow = '3.0.5';
|
||||
const installSpec = `@appium/fake-driver@${currentFakeDriverVersionAsOfRightNow}`;
|
||||
const ret = await runInstall([installSpec, '--source', 'npm']);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql(installSpec);
|
||||
const list = await runList(['--installed']);
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from github', async function () {
|
||||
const ret = await runInstall([
|
||||
'appium/appium-fake-driver',
|
||||
'--source',
|
||||
'github',
|
||||
'--package',
|
||||
'appium-fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('github');
|
||||
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from a local git repo', async function () {
|
||||
const ret = await runInstall([
|
||||
FAKE_DRIVER_DIR,
|
||||
'--source',
|
||||
'git',
|
||||
'--package',
|
||||
'@appium/fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql(FAKE_DRIVER_DIR);
|
||||
const list = await runList(['--installed', '--json']);
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from a remote git repo', async function () {
|
||||
const ret = await runInstall([
|
||||
'git+https://github.com/appium/appium-fake-driver.git',
|
||||
'--source',
|
||||
'git',
|
||||
'--package',
|
||||
'appium-fake-driver',
|
||||
]);
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
|
||||
const list = await runList(['--installed']);
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
|
||||
describe('when peer dependencies are invalid', function () {
|
||||
it('should install the driver anyway', async function () {
|
||||
const ret = await installLocalExtension(
|
||||
appiumHome,
|
||||
DRIVER_TYPE,
|
||||
testDriverInvalidPeerDepsPath
|
||||
);
|
||||
ret.test.pkgName.should.equal('test-driver-invalid-peer-dep');
|
||||
const list = await runList(['--installed']);
|
||||
list.test.pkgName.should.equal('test-driver-invalid-peer-dep');
|
||||
});
|
||||
|
||||
it('should warn the user that peer deps are invalid', async function () {
|
||||
const ret = await runAppiumRaw(
|
||||
appiumHome,
|
||||
[DRIVER_TYPE, INSTALL, '--source', 'local', testDriverInvalidPeerDepsPath],
|
||||
{}
|
||||
);
|
||||
ret.stderr.should.match(/may be incompatible with the current version of Appium/i);
|
||||
ret.stderr.should.match(/successfully installed/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when peer dependencies are valid', function () {
|
||||
it('should not display a warning', async function () {
|
||||
const ret = await runAppiumRaw(
|
||||
appiumHome,
|
||||
[DRIVER_TYPE, INSTALL, '--source', 'local', testDriverPath],
|
||||
{}
|
||||
);
|
||||
ret.stderr.should.not.match(/may be incompatible with the current version of Appium/i);
|
||||
ret.stderr.should.match(/successfully installed/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('local npm install', function () {
|
||||
describe('when installing from a local npm module', function () {
|
||||
/** @type {ExtRecord<DriverType>} */
|
||||
let installResult;
|
||||
/** @type {ExtensionListData} */
|
||||
let listResult;
|
||||
|
||||
/** @type {string} */
|
||||
let installPath;
|
||||
|
||||
before(async function () {
|
||||
await clear();
|
||||
// take advantage of the fact that we know we have fake driver installed as a dependency in
|
||||
// this module, so we know its local path on disk
|
||||
installResult = await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
listResult = await runList(['--installed']);
|
||||
installPath = path.join(appiumHome, 'node_modules', '@appium', 'fake-driver');
|
||||
});
|
||||
|
||||
it('should install a driver from a local npm module', function () {
|
||||
expect(installResult.fake).to.include({
|
||||
pkgName: '@appium/fake-driver',
|
||||
installType: 'local',
|
||||
installSpec: FAKE_DRIVER_DIR,
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the installed driver in the list of extensions', function () {
|
||||
expect(listResult.fake).to.deep.include(installResult.fake);
|
||||
});
|
||||
|
||||
// this fails in CI and I don't know why
|
||||
it.skip('should create a symlink', async function () {
|
||||
const srcStat = await fs.lstat(FAKE_DRIVER_DIR);
|
||||
const destStat = await fs.lstat(appiumHome);
|
||||
if (srcStat.dev !== destStat.dev) {
|
||||
return this.skip();
|
||||
}
|
||||
// it should be a link! this may be npm-version dependent, but it's worked
|
||||
// this way for quite awhile
|
||||
const stat = await fs.lstat(installPath);
|
||||
expect(stat.isSymbolicLink()).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstall', function () {
|
||||
/** @type {string} */
|
||||
let pkgName;
|
||||
|
||||
beforeEach(async function () {
|
||||
await clear();
|
||||
const result = await runInstall(['@appium/fake-driver', '--source', 'npm']);
|
||||
({pkgName} = result.fake);
|
||||
});
|
||||
it('should uninstall a driver based on its driver name', async function () {
|
||||
// XXX: incorrect if APPIUM_HOME is an extension working copy
|
||||
const installPath = resolveFrom(appiumHome, pkgName);
|
||||
let list = await runList(['--installed']);
|
||||
list.fake.installed.should.be.true;
|
||||
const uninstall = await runUninstall(['fake']);
|
||||
uninstall.should.not.have.key('fake');
|
||||
await fs.exists(installPath).should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
const driverName = 'fake';
|
||||
before(async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
describe('when the driver and script is valid', function () {
|
||||
const scriptName = 'fake-success';
|
||||
|
||||
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/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Plugin CLI', function () {
|
||||
const FAKE_PLUGIN_DIR = path.dirname(require.resolve('@appium/fake-plugin/package.json'));
|
||||
|
||||
before(function () {
|
||||
const run = runAppiumJson(appiumHome);
|
||||
runList = async (args = []) =>
|
||||
/** @type {ReturnType<typeof runList>} */ (await run([PLUGIN_TYPE, LIST, ...args]));
|
||||
runRun = async (args) =>
|
||||
/** @type {ReturnType<typeof runRun>} */ (await run([PLUGIN_TYPE, RUN, ...args]));
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
before(async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, PLUGIN_TYPE, FAKE_PLUGIN_DIR);
|
||||
});
|
||||
it('should run a valid plugin, valid script, and result in success', async function () {
|
||||
const pluginName = 'fake';
|
||||
const scriptName = 'fake-success';
|
||||
const out = await runRun([pluginName, scriptName, '--json']);
|
||||
out.should.not.have.property('error');
|
||||
});
|
||||
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
|
||||
const pluginName = 'fake';
|
||||
const out = await runRun([pluginName, 'fake-error', '--json']);
|
||||
out.should.have.property('error');
|
||||
});
|
||||
it('should take a valid plugin, invalid script, and throw an error', async function () {
|
||||
const pluginName = 'fake';
|
||||
await expect(runRun([pluginName, 'foo', '--json'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
it('should take an invalid plugin, invalid script, and throw an error', async function () {
|
||||
await expect(runRun(['foo', 'bar', '--json'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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',
|
||||
String(await getPort()),
|
||||
],
|
||||
{
|
||||
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
|
||||
cwd: APPIUM_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 () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--port=sheep'], {
|
||||
env: {FORCE_COLOR: '1'},
|
||||
}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-color.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when color output is unsupported', function () {
|
||||
it('should output a colorless yet fancy error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--port=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user provides a value for a boolean argument', function () {
|
||||
it('should output a basic error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--relaxed-security=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-boolean.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user provides an unknown argument', function () {
|
||||
it('should output a basic error message', async function () {
|
||||
const [{stderr: actual}, expected] = await B.all([
|
||||
runAppiumRaw(appiumHome, ['--pigs=sheep'], {}),
|
||||
readAppiumArgErrorFixture('cli/cli-error-output-unknown.txt'),
|
||||
]);
|
||||
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
|
||||
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template ExtSubCommand
|
||||
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
|
||||
*/
|
||||
@@ -3,6 +3,7 @@
|
||||
import {BaseDriver} from '@appium/base-driver';
|
||||
import {fs, tempDir} from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import {command} from 'webdriver';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import {createSandbox} from 'sinon';
|
||||
@@ -131,7 +132,7 @@ describe('FakeDriver via HTTP', function () {
|
||||
});
|
||||
|
||||
describe('cli args handling for empty args', function () {
|
||||
it('should not recieve user cli args if none passed in', async function () {
|
||||
it('should not receive user cli args if none passed in', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
@@ -258,6 +259,43 @@ describe('FakeDriver via HTTP', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow appium-prefixed caps sent via appium:options', async function () {
|
||||
// Try with valid capabilities and check that it returns a session ID
|
||||
const appiumOptsCaps = {
|
||||
capabilities: {
|
||||
alwaysMatch: {
|
||||
'appium:options': {
|
||||
automationName: 'Fake',
|
||||
deviceName: 'Fake',
|
||||
app: TEST_FAKE_APP,
|
||||
},
|
||||
platformName: 'Fake',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
},
|
||||
};
|
||||
|
||||
// Create the session
|
||||
const {status, value, sessionId} = (
|
||||
await axios.post(testServerBaseSessionUrl, appiumOptsCaps)
|
||||
).data;
|
||||
try {
|
||||
should.not.exist(status); // Test that it's a W3C session by checking that 'status' is not in the response
|
||||
should.not.exist(sessionId);
|
||||
value.sessionId.should.be.a.string;
|
||||
value.should.exist;
|
||||
value.capabilities.should.deep.equal({
|
||||
automationName: 'Fake',
|
||||
platformName: 'Fake',
|
||||
deviceName: 'Fake',
|
||||
app: TEST_FAKE_APP,
|
||||
});
|
||||
} finally {
|
||||
// End session
|
||||
await axios.delete(`${testServerBaseSessionUrl}/${value.sessionId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should reject invalid W3C capabilities and respond with a 400 Bad Parameters error', async function () {
|
||||
const badW3Ccaps = {
|
||||
capabilities: {
|
||||
@@ -407,6 +445,46 @@ describe('FakeDriver via HTTP', function () {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
|
||||
it('should log a single deprecation warning if a deprecated method is used and not overridden by a newMethodMap', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
try {
|
||||
driver.addCommand(
|
||||
'deprecated',
|
||||
command('POST', '/session/:sessionId/deprecated', {
|
||||
command: 'deprecated',
|
||||
description: 'Call a deprecated command',
|
||||
parameters: [],
|
||||
ref: '',
|
||||
})
|
||||
);
|
||||
driver.addCommand(
|
||||
'doubleClick',
|
||||
command('POST', '/session/:sessionId/doubleclick', {
|
||||
command: 'doubleClick',
|
||||
description: 'Global double click',
|
||||
parameters: [],
|
||||
ref: '',
|
||||
})
|
||||
);
|
||||
await driver
|
||||
.executeScript('fake: getDeprecatedCommandsCalled', [])
|
||||
.should.eventually.eql([]);
|
||||
await driver.deprecated();
|
||||
await driver.deprecated();
|
||||
await driver.shake();
|
||||
|
||||
// this call should not trigger a deprecation even though deprecated by appium because it's
|
||||
// overridden as not deprecated by fake driver
|
||||
await driver.doubleClick();
|
||||
|
||||
await driver
|
||||
.executeScript('fake: getDeprecatedCommandsCalled', [])
|
||||
.should.eventually.eql(['callDeprecatedCommand', 'mobileShake']);
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export const runAppium = _.curry(_runAppium);
|
||||
|
||||
/**
|
||||
* See {@link runAppiumRaw}.
|
||||
* @type {AppiumOptsRunner<import('teen_process').TeenProcessExecResult>}
|
||||
* @type {AppiumOptsRunner<import('teen_process').TeenProcessExecResult<string>>}
|
||||
*/
|
||||
async function _runAppiumRaw(appiumHome, args, opts) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
// @ts-check
|
||||
|
||||
import {env, fs, npm, tempDir} from '@appium/support';
|
||||
import path from 'node:path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import YAML from 'yaml';
|
||||
import {
|
||||
DRIVER_TYPE,
|
||||
EXT_SUBCOMMAND_INSTALL as INSTALL,
|
||||
EXT_SUBCOMMAND_LIST as LIST,
|
||||
PKG_HASHFILE_RELATIVE_PATH,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
|
||||
|
||||
const {MANIFEST_RELATIVE_PATH} = env;
|
||||
const {expect} = chai;
|
||||
const testDriverPath = path.dirname(resolveFixture('test-driver/package.json'));
|
||||
|
||||
describe('when Appium is a dependency of the current project', function () {
|
||||
/** @type {string} */
|
||||
let hashPath;
|
||||
/** @type {string} */
|
||||
let manifestPath;
|
||||
/** @type {string} */
|
||||
let appiumHomePkgPath;
|
||||
/** @type {string} */
|
||||
let appiumHome;
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper fn
|
||||
* @returns {Promise<ManifestData>}
|
||||
*/
|
||||
async function readManifest() {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
return YAML.parse(manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper fn
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function readHash() {
|
||||
return await fs.readFile(hashPath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {CliExtensionSubcommand} ExtSubcommand
|
||||
* @type {import('lodash').CurriedFunction1<CliExtArgs<ExtSubcommand> | CliArgs, Promise<unknown>>}
|
||||
*/
|
||||
let runJson;
|
||||
|
||||
describe('when the project is an extension', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
await fs.copyFile(resolveFixture('test-driver/package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
it('should automatically discover the extension', async function () {
|
||||
const res = await runJson([DRIVER_TYPE, LIST]);
|
||||
expect(res).to.have.property('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the project is not an extension', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
// an example package.json referencing appium dependency
|
||||
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
describe('without drivers installed', function () {
|
||||
it('should list no drivers', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.satisfy(
|
||||
/** @param {typeof res} value */ (value) =>
|
||||
Object.values(value).every(({installed}) => !installed)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after a driver is installed', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
it('should list the driver', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should be resolvable from the local directory', function () {
|
||||
(() => resolveFrom(appiumHome, '@appium/fake-driver/package.json')).should.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a driver is installed via npm', function () {
|
||||
before(async function () {
|
||||
// remove the hash file and the manifest to start clean.
|
||||
// this leaves any previously-installed extension in place, which should make this a little faster.
|
||||
await fs.rimraf(path.dirname(hashPath));
|
||||
await npm.exec(INSTALL, [FAKE_DRIVER_DIR], {
|
||||
json: true,
|
||||
cwd: appiumHome,
|
||||
});
|
||||
});
|
||||
|
||||
describe(LIST, function () {
|
||||
/** @type {ExtensionListData} */
|
||||
let res;
|
||||
|
||||
beforeEach(async function () {
|
||||
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
});
|
||||
|
||||
it('should list the driver', function () {
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should update the manifest', async function () {
|
||||
const manifestParsed = await readManifest();
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should update the package hash', async function () {
|
||||
(await fs.exists(hashPath)).should.be.true;
|
||||
});
|
||||
|
||||
describe('when a different driver is installed via "appium driver install"', function () {
|
||||
/** @type {string} */
|
||||
let oldHash;
|
||||
|
||||
before(async function () {
|
||||
await runJson([DRIVER_TYPE, LIST]);
|
||||
oldHash = await readHash();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
});
|
||||
|
||||
it('should update package.json', async function () {
|
||||
const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8'));
|
||||
expect(newPkg).to.have.nested.property('devDependencies.@appium/test-driver');
|
||||
});
|
||||
|
||||
it('should update the hash', async function () {
|
||||
const newHash = await readHash();
|
||||
newHash.should.not.equal(oldHash);
|
||||
});
|
||||
|
||||
it('should update the manifest with the new driver', async function () {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
/** @type {ManifestData} */
|
||||
const manifestParsed = YAML.parse(manifest);
|
||||
manifestParsed.should.have.nested.property('drivers.test');
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should actually install both drivers', function () {
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/test-driver')).not.to.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template ExtSubCommand
|
||||
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
|
||||
*/
|
||||
@@ -0,0 +1,106 @@
|
||||
import {fs, tempDir} from '@appium/support';
|
||||
import path from 'node:path';
|
||||
import YAML from 'yaml';
|
||||
import {
|
||||
CACHE_DIR_RELATIVE_PATH,
|
||||
CURRENT_SCHEMA_REV,
|
||||
DRIVER_TYPE,
|
||||
EXT_SUBCOMMAND_LIST as LIST,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('manifest handling', function () {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let appiumHome;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let manifestPath;
|
||||
|
||||
/**
|
||||
* @type {(args?: string[]) => Promise<ExtensionListData>}
|
||||
*/
|
||||
let runList;
|
||||
|
||||
async function resetAppiumHome() {
|
||||
await fs.rimraf(appiumHome);
|
||||
await fs.mkdirp(appiumHome);
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
manifestPath = path.join(appiumHome, CACHE_DIR_RELATIVE_PATH, 'extensions.yaml');
|
||||
const run = runAppiumJson(appiumHome);
|
||||
runList = async (args = []) =>
|
||||
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns {Promise<import('appium/types').AnyManifestDataVersion>}
|
||||
*/
|
||||
async function readManifest() {
|
||||
return YAML.parse(await fs.readFile(manifestPath, 'utf8'));
|
||||
}
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
describe('migration', function () {
|
||||
beforeEach(async function () {
|
||||
await resetAppiumHome();
|
||||
await fs.mkdirp(path.dirname(manifestPath));
|
||||
});
|
||||
|
||||
describe('schema rev update', function () {
|
||||
beforeEach(async function () {
|
||||
await fs.copyFile(resolveFixture('manifest/v2-empty.yaml'), manifestPath);
|
||||
});
|
||||
|
||||
it('should update the manifest file to the latest schema revision', async function () {
|
||||
// note: all we need to do to run the migration is invoke appium, so any command
|
||||
// would do.
|
||||
await runList();
|
||||
const manifest = await readManifest();
|
||||
expect(manifest.schemaRev).to.equal(CURRENT_SCHEMA_REV);
|
||||
});
|
||||
});
|
||||
|
||||
describe('v3', function () {
|
||||
/** @type {import('appium/types').AnyManifestDataVersion} */
|
||||
let manifest;
|
||||
// for these tests, we have to use a real extension, because the migration
|
||||
// only does anything if the extension is actually installed.
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
const list = await runList();
|
||||
expect(list.fake).to.exist;
|
||||
|
||||
// but the manifest is now at v3, since it's brand new. we will manually downgrade.
|
||||
let tmpManifest = await readManifest();
|
||||
tmpManifest.schemaRev = 2;
|
||||
delete tmpManifest.drivers.fake.installPath;
|
||||
await fs.writeFile(manifestPath, YAML.stringify(tmpManifest));
|
||||
tmpManifest = await readManifest();
|
||||
expect(tmpManifest.schemaRev).to.equal(2);
|
||||
expect(tmpManifest.drivers.fake.installPath).to.not.exist;
|
||||
await runList();
|
||||
manifest = await readManifest();
|
||||
});
|
||||
|
||||
it('should add an "installPath" field to each extension', function () {
|
||||
expect(manifest.drivers.fake?.installPath).to.be.a('string');
|
||||
});
|
||||
|
||||
it('should update the manifest file to the latest schema revision', function () {
|
||||
expect(manifest.schemaRev).to.equal(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
drivers: {}
|
||||
plugins: {}
|
||||
schemaRev: 2
|
||||
@@ -33,6 +33,8 @@ describe('ExtensionConfig', function () {
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
// avoids a warning about too many listeners, caused by an exit handler in base-driver
|
||||
process.removeAllListeners('exit');
|
||||
});
|
||||
|
||||
describe('constructor', function () {});
|
||||
@@ -134,9 +136,8 @@ describe('ExtensionConfig', function () {
|
||||
installType: 'npm',
|
||||
appiumVersion: APPIUM_VER,
|
||||
};
|
||||
manifest.addExtension(DRIVER_TYPE, extData.pkgName, extData);
|
||||
manifest.setExtension(DRIVER_TYPE, extData.pkgName, extData);
|
||||
config = new ExtensionConfig(DRIVER_TYPE, manifest);
|
||||
delete this._listDataCache;
|
||||
});
|
||||
|
||||
describe('when the extension data is missing an `installSpec` field', function () {
|
||||
|
||||
@@ -294,7 +294,7 @@ describe('Manifest', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExtension()', function () {
|
||||
describe('setExtension()', function () {
|
||||
/** @type {ExtManifest<DriverType>} */
|
||||
const extData = {
|
||||
automationName: 'derp',
|
||||
@@ -309,7 +309,7 @@ describe('Manifest', function () {
|
||||
};
|
||||
|
||||
it('should add a clone of the extension manifest to the internal data object', function () {
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
|
||||
expect(manifest.getExtensionData(DRIVER_TYPE).foo)
|
||||
.to.eql(extData)
|
||||
.and.not.to.equal(extData);
|
||||
@@ -319,7 +319,7 @@ describe('Manifest', function () {
|
||||
/** @type {ExtManifest<DriverType>} */
|
||||
|
||||
beforeEach(function () {
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
|
||||
});
|
||||
|
||||
it('should rewrite', function () {
|
||||
@@ -327,7 +327,7 @@ describe('Manifest', function () {
|
||||
...extData,
|
||||
automationName: 'BLAAHAH',
|
||||
};
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', expected);
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', expected);
|
||||
expect(manifest.getExtensionData(DRIVER_TYPE).foo).to.eql(expected);
|
||||
});
|
||||
});
|
||||
@@ -338,7 +338,7 @@ describe('Manifest', function () {
|
||||
});
|
||||
|
||||
it('should work anyway', function () {
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
|
||||
expect(manifest.getExtensionData(DRIVER_TYPE).foo).to.not.have.property('appiumVersion');
|
||||
});
|
||||
});
|
||||
@@ -359,7 +359,7 @@ describe('Manifest', function () {
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
|
||||
});
|
||||
|
||||
it('should return all extension data for an extension type', function () {
|
||||
@@ -575,7 +575,7 @@ describe('Manifest', function () {
|
||||
describe('when the driver is registered', function () {
|
||||
beforeEach(function () {
|
||||
// @ts-expect-error
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', {});
|
||||
manifest.setExtension(DRIVER_TYPE, 'foo', {});
|
||||
});
|
||||
|
||||
it('should return `true`', function () {
|
||||
@@ -594,7 +594,7 @@ describe('Manifest', function () {
|
||||
describe('when the plugin is registered', function () {
|
||||
beforeEach(function () {
|
||||
// @ts-expect-error
|
||||
manifest.addExtension(PLUGIN_TYPE, 'foo', {});
|
||||
manifest.setExtension(PLUGIN_TYPE, 'foo', {});
|
||||
});
|
||||
|
||||
it('should return `true`', function () {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.2.1](https://github.com/appium/appium/compare/@appium/base-driver@9.2.0...@appium/base-driver@9.2.1) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.2.0](https://github.com/appium/appium/compare/@appium/base-driver@9.1.0...@appium/base-driver@9.2.0) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** inability to find automationName inside appium:options ([#17966](https://github.com/appium/appium/issues/17966)) ([23224cf](https://github.com/appium/appium/commit/23224cf002b7dd9e4e5d8426b4bbe1cb28f62605))
|
||||
* **base-driver:** add missing dependency @appium/types ([edde488](https://github.com/appium/appium/commit/edde4882f0633c032a077861599c337132092daf)), closes [#18006](https://github.com/appium/appium/issues/18006)
|
||||
* **base-driver:** move setClipboard deprecation to correct place ([ad1dce5](https://github.com/appium/appium/commit/ad1dce5545acb61ce79e489783a95f2caebd753c))
|
||||
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
|
||||
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
|
||||
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.1.0](https://github.com/appium/appium/compare/@appium/base-driver@9.0.0...@appium/base-driver@9.1.0) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add 'webSocketUrl' as standard cap for bidi support ([#17936](https://github.com/appium/appium/issues/17936)) ([0e195ca](https://github.com/appium/appium/commit/0e195caafefe911586ee2f8be4ae33d402b2ba40))
|
||||
- **types:** update definitelytyped ([172fcb9](https://github.com/appium/appium/commit/172fcb9aff0afe5295650566c4fb92d0894bf879))
|
||||
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
|
||||
|
||||
### Features
|
||||
|
||||
- **base:** add get computed role and label in W3C v2 ([#17928](https://github.com/appium/appium/issues/17928)) ([316ecca](https://github.com/appium/appium/commit/316ecca8b1f8e52806867a15ba8524a504751460))
|
||||
|
||||
# [9.0.0](https://github.com/appium/appium/compare/@appium/base-driver@8.7.3...@appium/base-driver@9.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -104,6 +104,7 @@ export const STANDARD_CAPS = Object.freeze(
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
'webSocketUrl',
|
||||
])
|
||||
)
|
||||
);
|
||||
@@ -224,7 +225,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
// In the future, reject 'firstMatch' argument if its array did not have one or more entries (#3.2)
|
||||
if (allFirstMatchCaps.length === 0) {
|
||||
log.warn(
|
||||
`The firstMatch array in the given capabilities has no entries. Adding an empty entry fo rnow, ` +
|
||||
`The firstMatch array in the given capabilities has no entries. Adding an empty entry for now, ` +
|
||||
`but it will require one or more entries as W3C spec.`
|
||||
);
|
||||
allFirstMatchCaps.push({});
|
||||
@@ -348,7 +349,9 @@ function promoteAppiumOptions(originalCaps) {
|
||||
}
|
||||
|
||||
if (!_.isPlainObject(appiumOptions)) {
|
||||
throw new errors.SessionNotCreatedError(`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`);
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
|
||||
);
|
||||
}
|
||||
if (_.isEmpty(appiumOptions)) {
|
||||
return obj;
|
||||
@@ -377,7 +380,7 @@ function promoteAppiumOptions(originalCaps) {
|
||||
};
|
||||
const preprocessedOptions = _(appiumOptions)
|
||||
.mapKeys((value, key) => verifyIfAcceptable(key))
|
||||
.mapKeys((value, key) => shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key)
|
||||
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
|
||||
.value();
|
||||
// warn if we are going to overwrite any keys on the base caps object
|
||||
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
|
||||
@@ -388,8 +391,8 @@ function promoteAppiumOptions(originalCaps) {
|
||||
);
|
||||
}
|
||||
return _.cloneDeep({
|
||||
...(_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)),
|
||||
...preprocessedOptions
|
||||
..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP),
|
||||
...preprocessedOptions,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export function SessionMixin(Base) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
* @returns {Promise<SingularSessionData>}
|
||||
*/
|
||||
async getSession() {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/* eslint-disable require-await */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import {
|
||||
validateCaps,
|
||||
PREFIXED_APPIUM_OPTS_CAP,
|
||||
processCapabilities,
|
||||
promoteAppiumOptions,
|
||||
} from './capabilities';
|
||||
import {validateCaps, PREFIXED_APPIUM_OPTS_CAP, processCapabilities} from './capabilities';
|
||||
import {DriverCore} from './core';
|
||||
import {util} from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
@@ -298,7 +293,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
let caps;
|
||||
try {
|
||||
caps = processCapabilities(
|
||||
promoteAppiumOptions(originalCaps),
|
||||
originalCaps,
|
||||
this._desiredCapConstraints,
|
||||
this.shouldValidateCaps
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ export {
|
||||
processCapabilities,
|
||||
isStandardCap,
|
||||
validateCaps,
|
||||
promoteAppiumOptions,
|
||||
} from './basedriver/capabilities';
|
||||
|
||||
// Web socket helpers
|
||||
|
||||
@@ -18,6 +18,9 @@ const CREATE_SESSION_COMMAND = 'createSession';
|
||||
const DELETE_SESSION_COMMAND = 'deleteSession';
|
||||
const GET_STATUS_COMMAND = 'getStatus';
|
||||
|
||||
/** type {Set<string>} */
|
||||
const deprecatedCommandsLogged = new Set();
|
||||
|
||||
function determineProtocol(createSessionArgs) {
|
||||
return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
|
||||
}
|
||||
@@ -262,6 +265,16 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
||||
let currentProtocol = extractProtocol(driver, req.params.sessionId);
|
||||
|
||||
try {
|
||||
// if the route accessed is deprecated, log a warning
|
||||
if (spec.deprecated && !deprecatedCommandsLogged.has(spec.command)) {
|
||||
deprecatedCommandsLogged.add(spec.command);
|
||||
getLogger(driver, req.params.sessionId).warn(
|
||||
`Command '${spec.command}' has been deprecated and will be removed in a future ` +
|
||||
`version of Appium or your driver/plugin. Please use a different method or contact the ` +
|
||||
`driver/plugin author to add explicit support for the command before it is removed`
|
||||
);
|
||||
}
|
||||
|
||||
// if this is a session command but we don't have a session,
|
||||
// error out early (especially before proxying)
|
||||
if (isSessCmd && !driver.sessionExists(req.params.sessionId)) {
|
||||
@@ -512,4 +525,5 @@ export {
|
||||
CREATE_SESSION_COMMAND,
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
deprecatedCommandsLogged,
|
||||
};
|
||||
|
||||
@@ -68,10 +68,10 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/timeouts/async_script': {
|
||||
POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}},
|
||||
POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/timeouts/implicit_wait': {
|
||||
POST: {command: 'implicitWait', payloadParams: {required: ['ms']}},
|
||||
POST: {command: 'implicitWait', payloadParams: {required: ['ms']}, deprecated: true},
|
||||
},
|
||||
// JSONWP
|
||||
'/session/:sessionId/window_handle': {
|
||||
@@ -117,25 +117,25 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
GET: {command: 'getScreenshot'},
|
||||
},
|
||||
'/session/:sessionId/ime/available_engines': {
|
||||
GET: {command: 'availableIMEEngines'},
|
||||
GET: {command: 'availableIMEEngines', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/ime/active_engine': {
|
||||
GET: {command: 'getActiveIMEEngine'},
|
||||
GET: {command: 'getActiveIMEEngine', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/ime/activated': {
|
||||
GET: {command: 'isIMEActivated'},
|
||||
GET: {command: 'isIMEActivated', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/ime/deactivate': {
|
||||
POST: {command: 'deactivateIMEEngine'},
|
||||
POST: {command: 'deactivateIMEEngine', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/ime/activate': {
|
||||
POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}},
|
||||
POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/frame': {
|
||||
POST: {command: 'setFrame', payloadParams: {required: ['id']}},
|
||||
},
|
||||
'/session/:sessionId/frame/parent': {
|
||||
POST: {},
|
||||
POST: {command: 'switchToParentFrame'},
|
||||
},
|
||||
'/session/:sessionId/window': {
|
||||
GET: {command: 'getWindowHandle'},
|
||||
@@ -162,12 +162,11 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
DELETE: {command: 'closeWindow'},
|
||||
},
|
||||
'/session/:sessionId/window/:windowhandle/size': {
|
||||
GET: {command: 'getWindowSize'},
|
||||
POST: {},
|
||||
GET: {command: 'getWindowSize', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/window/:windowhandle/position': {
|
||||
POST: {},
|
||||
GET: {},
|
||||
POST: {deprecated: true},
|
||||
GET: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/window/:windowhandle/maximize': {
|
||||
POST: {command: 'maximizeWindow'},
|
||||
@@ -222,7 +221,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {command: 'click'},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/submit': {
|
||||
POST: {command: 'submit'},
|
||||
POST: {command: 'submit', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/text': {
|
||||
GET: {command: 'getText'},
|
||||
@@ -246,7 +245,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/keys': {
|
||||
POST: {command: 'keys', payloadParams: {required: ['value']}},
|
||||
POST: {command: 'keys', payloadParams: {required: ['value']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/name': {
|
||||
GET: {command: 'getName'},
|
||||
@@ -264,19 +263,19 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
GET: {command: 'getAttribute'},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/equals/:otherId': {
|
||||
GET: {command: 'equalsElement'},
|
||||
GET: {command: 'equalsElement', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/displayed': {
|
||||
GET: {command: 'elementDisplayed'},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/location': {
|
||||
GET: {command: 'getLocation'},
|
||||
GET: {command: 'getLocation', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/location_in_view': {
|
||||
GET: {command: 'getLocationInView'},
|
||||
GET: {command: 'getLocationInView', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/size': {
|
||||
GET: {command: 'getSize'},
|
||||
GET: {command: 'getSize', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/shadow': {
|
||||
GET: {command: 'elementShadowRoot'},
|
||||
@@ -303,6 +302,14 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
payloadParams: {required: ['orientation']},
|
||||
},
|
||||
},
|
||||
// w3c v2 https://www.w3.org/TR/webdriver2/#get-computed-role
|
||||
'session/:sessionId/element/:elementId/computedrole': {
|
||||
GET: {command: 'getComputedRole'},
|
||||
},
|
||||
// W3C v2 https://www.w3.org/TR/webdriver2/#get-computed-label
|
||||
'session/:sessionId/element/:elementId/computedlabel': {
|
||||
GET: {command: 'getComputedLabel'},
|
||||
},
|
||||
'/session/:sessionId/rotation': {
|
||||
GET: {command: 'getRotation'},
|
||||
POST: {command: 'setRotation', payloadParams: {required: ['x', 'y', 'z']}},
|
||||
@@ -311,34 +318,35 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {
|
||||
command: 'moveTo',
|
||||
payloadParams: {optional: ['element', 'xoffset', 'yoffset']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/click': {
|
||||
POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}},
|
||||
POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/buttondown': {
|
||||
POST: {command: 'buttonDown', payloadParams: {optional: ['button']}},
|
||||
POST: {command: 'buttonDown', payloadParams: {optional: ['button']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/buttonup': {
|
||||
POST: {command: 'buttonUp', payloadParams: {optional: ['button']}},
|
||||
POST: {command: 'buttonUp', payloadParams: {optional: ['button']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/doubleclick': {
|
||||
POST: {command: 'doubleClick'},
|
||||
POST: {command: 'doubleClick', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/click': {
|
||||
POST: {command: 'click', payloadParams: {required: ['element']}},
|
||||
POST: {command: 'click', payloadParams: {required: ['element']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/down': {
|
||||
POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}},
|
||||
POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/up': {
|
||||
POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}},
|
||||
POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/move': {
|
||||
POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}},
|
||||
POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/scroll': {
|
||||
POST: {},
|
||||
POST: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/doubleclick': {
|
||||
POST: {},
|
||||
@@ -348,7 +356,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
DELETE: {command: 'releaseActions'},
|
||||
},
|
||||
'/session/:sessionId/touch/longclick': {
|
||||
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}},
|
||||
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/touch/flick': {
|
||||
POST: {
|
||||
@@ -356,6 +364,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
payloadParams: {
|
||||
optional: ['element', 'xspeed', 'yspeed', 'xoffset', 'yoffset', 'speed'],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/location': {
|
||||
@@ -363,28 +372,28 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {command: 'setGeoLocation', payloadParams: {required: ['location']}},
|
||||
},
|
||||
'/session/:sessionId/local_storage': {
|
||||
GET: {},
|
||||
POST: {},
|
||||
DELETE: {},
|
||||
GET: {deprecated: true},
|
||||
POST: {deprecated: true},
|
||||
DELETE: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/local_storage/key/:key': {
|
||||
GET: {},
|
||||
DELETE: {},
|
||||
GET: {deprecated: true},
|
||||
DELETE: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/local_storage/size': {
|
||||
GET: {},
|
||||
GET: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/session_storage': {
|
||||
GET: {},
|
||||
POST: {},
|
||||
DELETE: {},
|
||||
GET: {deprecated: true},
|
||||
POST: {deprecated: true},
|
||||
DELETE: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/session_storage/key/:key': {
|
||||
GET: {},
|
||||
DELETE: {},
|
||||
GET: {deprecated: true},
|
||||
DELETE: {deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/session_storage/size': {
|
||||
GET: {},
|
||||
GET: {deprecated: true},
|
||||
},
|
||||
// Selenium 4 clients
|
||||
'/session/:sessionId/se/log': {
|
||||
@@ -417,7 +426,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
GET: {command: 'getContexts'},
|
||||
},
|
||||
'/session/:sessionId/element/:elementId/pageIndex': {
|
||||
GET: {command: 'getPageIndex'},
|
||||
GET: {command: 'getPageIndex', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/network_connection': {
|
||||
GET: {command: 'getNetworkConnection'},
|
||||
@@ -430,12 +439,14 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {
|
||||
command: 'performTouch',
|
||||
payloadParams: {wrap: 'actions', required: ['actions']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/touch/multi/perform': {
|
||||
POST: {
|
||||
command: 'performMultiAction',
|
||||
payloadParams: {required: ['actions'], optional: ['elementId']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/receive_async_response': {
|
||||
@@ -445,35 +456,37 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/shake': {
|
||||
POST: {command: 'mobileShake'},
|
||||
POST: {command: 'mobileShake', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/system_time': {
|
||||
GET: {command: 'getDeviceTime', payloadParams: {optional: ['format']}},
|
||||
POST: {command: 'getDeviceTime', payloadParams: {optional: ['format']}},
|
||||
},
|
||||
'/session/:sessionId/appium/device/lock': {
|
||||
POST: {command: 'lock', payloadParams: {optional: ['seconds']}},
|
||||
POST: {command: 'lock', payloadParams: {optional: ['seconds']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/unlock': {
|
||||
POST: {command: 'unlock'},
|
||||
POST: {command: 'unlock', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/is_locked': {
|
||||
POST: {command: 'isLocked'},
|
||||
POST: {command: 'isLocked', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/start_recording_screen': {
|
||||
POST: {
|
||||
command: 'startRecordingScreen',
|
||||
payloadParams: {optional: ['options']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/stop_recording_screen': {
|
||||
POST: {
|
||||
command: 'stopRecordingScreen',
|
||||
payloadParams: {optional: ['options']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/performanceData/types': {
|
||||
POST: {command: 'getPerformanceDataTypes'},
|
||||
POST: {command: 'getPerformanceDataTypes', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/getPerformanceData': {
|
||||
POST: {
|
||||
@@ -482,70 +495,68 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
required: ['packageName', 'dataType'],
|
||||
optional: ['dataReadTimeout'],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/press_keycode': {
|
||||
POST: {
|
||||
command: 'pressKeyCode',
|
||||
payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/long_press_keycode': {
|
||||
POST: {
|
||||
command: 'longPressKeyCode',
|
||||
payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/finger_print': {
|
||||
POST: {
|
||||
command: 'fingerprint',
|
||||
payloadParams: {required: ['fingerprintId']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/send_sms': {
|
||||
POST: {
|
||||
command: 'sendSMS',
|
||||
payloadParams: {required: ['phoneNumber', 'message']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/gsm_call': {
|
||||
POST: {
|
||||
command: 'gsmCall',
|
||||
payloadParams: {required: ['phoneNumber', 'action']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/gsm_signal': {
|
||||
POST: {
|
||||
command: 'gsmSignal',
|
||||
payloadParams: {
|
||||
validate: (jsonObj) =>
|
||||
!util.hasValue(jsonObj.signalStrength) &&
|
||||
!util.hasValue(jsonObj.signalStrengh) &&
|
||||
'we require one of "signalStrength" or "signalStrengh" params',
|
||||
optional: ['signalStrength', 'signalStrengh'],
|
||||
// backward-compatible. sonObj.signalStrength can be 0
|
||||
makeArgs: (jsonObj) => [
|
||||
util.hasValue(jsonObj.signalStrength) ? jsonObj.signalStrength : jsonObj.signalStrengh,
|
||||
],
|
||||
},
|
||||
payloadParams: {required: ['signalStrength']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/gsm_voice': {
|
||||
POST: {command: 'gsmVoice', payloadParams: {required: ['state']}},
|
||||
POST: {command: 'gsmVoice', payloadParams: {required: ['state']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/power_capacity': {
|
||||
POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}},
|
||||
POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/power_ac': {
|
||||
POST: {command: 'powerAC', payloadParams: {required: ['state']}},
|
||||
POST: {command: 'powerAC', payloadParams: {required: ['state']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/network_speed': {
|
||||
POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}},
|
||||
POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/keyevent': {
|
||||
POST: {
|
||||
command: 'keyevent',
|
||||
payloadParams: {required: ['keycode'], optional: ['metastate']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/rotate': {
|
||||
@@ -555,13 +566,14 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
required: ['x', 'y', 'radius', 'rotation', 'touchCount', 'duration'],
|
||||
optional: ['element'],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/current_activity': {
|
||||
GET: {command: 'getCurrentActivity'},
|
||||
GET: {command: 'getCurrentActivity', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/current_package': {
|
||||
GET: {command: 'getCurrentPackage'},
|
||||
GET: {command: 'getCurrentPackage', deprecated: true},
|
||||
},
|
||||
//region Applications Management
|
||||
'/session/:sessionId/appium/device/install_app': {
|
||||
@@ -620,6 +632,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
payloadParams: {
|
||||
required: [['appId'], ['bundleId']],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
//endregion
|
||||
@@ -642,19 +655,19 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {command: 'pullFolder', payloadParams: {required: ['path']}},
|
||||
},
|
||||
'/session/:sessionId/appium/device/toggle_airplane_mode': {
|
||||
POST: {command: 'toggleFlightMode'},
|
||||
POST: {command: 'toggleFlightMode', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/toggle_data': {
|
||||
POST: {command: 'toggleData'},
|
||||
POST: {command: 'toggleData', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/toggle_wifi': {
|
||||
POST: {command: 'toggleWiFi'},
|
||||
POST: {command: 'toggleWiFi', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/toggle_location_services': {
|
||||
POST: {command: 'toggleLocationServices'},
|
||||
POST: {command: 'toggleLocationServices', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/open_notifications': {
|
||||
POST: {command: 'openNotifications'},
|
||||
POST: {command: 'openNotifications', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/start_activity': {
|
||||
POST: {
|
||||
@@ -671,77 +684,63 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
'dontStopAppOnReset',
|
||||
],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/system_bars': {
|
||||
GET: {command: 'getSystemBars'},
|
||||
GET: {command: 'getSystemBars', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/device/display_density': {
|
||||
GET: {command: 'getDisplayDensity'},
|
||||
GET: {command: 'getDisplayDensity', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/simulator/touch_id': {
|
||||
POST: {command: 'touchId', payloadParams: {required: ['match']}},
|
||||
POST: {command: 'touchId', payloadParams: {required: ['match']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/simulator/toggle_touch_id_enrollment': {
|
||||
POST: {
|
||||
command: 'toggleEnrollTouchId',
|
||||
payloadParams: {optional: ['enabled']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/app/launch': {
|
||||
POST: {command: 'launchApp'},
|
||||
POST: {command: 'launchApp', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/app/close': {
|
||||
POST: {command: 'closeApp'},
|
||||
POST: {command: 'closeApp', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/app/reset': {
|
||||
POST: {command: 'reset'},
|
||||
POST: {command: 'reset', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/app/background': {
|
||||
POST: {command: 'background', payloadParams: {required: ['seconds']}},
|
||||
POST: {command: 'background', payloadParams: {required: ['seconds']}, deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/appium/app/end_test_coverage': {
|
||||
POST: {
|
||||
command: 'endCoverage',
|
||||
payloadParams: {required: ['intent', 'path']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/app/strings': {
|
||||
POST: {
|
||||
command: 'getStrings',
|
||||
payloadParams: {optional: ['language', 'stringFile']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/element/:elementId/value': {
|
||||
POST: {
|
||||
command: 'setValueImmediate',
|
||||
payloadParams: {
|
||||
validate: (jsonObj) =>
|
||||
!util.hasValue(jsonObj.value) &&
|
||||
!util.hasValue(jsonObj.text) &&
|
||||
'we require one of "text" or "value" params',
|
||||
optional: ['value', 'text'],
|
||||
// We want to either a value (old JSONWP) or a text (new W3C) parameter,
|
||||
// but only send one of them to the command (not both).
|
||||
// Prefer 'value' since it's more backward-compatible.
|
||||
makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text],
|
||||
},
|
||||
payloadParams: {required: ['text']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/element/:elementId/replace_value': {
|
||||
POST: {
|
||||
command: 'replaceValue',
|
||||
payloadParams: {
|
||||
validate: (jsonObj) =>
|
||||
!util.hasValue(jsonObj.value) &&
|
||||
!util.hasValue(jsonObj.text) &&
|
||||
'we require one of "text" or "value" params',
|
||||
optional: ['value', 'text'],
|
||||
// We want to either a value (old JSONWP) or a text (new W3C) parameter,
|
||||
// but only send one of them to the command (not both).
|
||||
// Prefer 'value' since it's more backward-compatible.
|
||||
makeArgs: (jsonObj) => [jsonObj.value ?? jsonObj.text ?? ''],
|
||||
},
|
||||
payloadParams: {required: ['text']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/settings': {
|
||||
@@ -752,6 +751,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {
|
||||
command: 'receiveAsyncResponse',
|
||||
payloadParams: {required: ['response']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/events': {
|
||||
@@ -849,6 +849,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
required: ['content'],
|
||||
optional: ['contentType', 'label'],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/appium/device/get_clipboard': {
|
||||
@@ -857,6 +858,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
payloadParams: {
|
||||
optional: ['contentType'],
|
||||
},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.0.0",
|
||||
"version": "9.2.1",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -32,25 +32,27 @@
|
||||
"index.d.ts",
|
||||
"lib",
|
||||
"static",
|
||||
"build"
|
||||
"build",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
"test:e2e": "mocha --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:e2e": "mocha -p --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.0.0",
|
||||
"@appium/support": "^3.1.1",
|
||||
"@appium/types": "^0.8.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.3.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/express": "4.17.15",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/serve-favicon": "2.5.3",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.2.1",
|
||||
"axios": "1.2.2",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.1",
|
||||
"es6-error": "4.1.1",
|
||||
@@ -62,7 +64,7 @@
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.3.0",
|
||||
"type-fest": "3.5.1",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -72,8 +74,8 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
|
||||
"typedoc": {
|
||||
"entryPoint": "./build/lib/index.js"
|
||||
"entryPoint": "./lib/index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import {BaseDriver, server, routeConfiguringFunction, PREFIXED_APPIUM_OPTS_CAP} from '../../../lib';
|
||||
import {
|
||||
driverE2ETestSuite,
|
||||
getTestPort,
|
||||
TEST_HOST,
|
||||
createSessionHelpers,
|
||||
} from '@appium/driver-test-support';
|
||||
|
||||
const {expect} = chai;
|
||||
import {BaseDriver} from '../../../lib';
|
||||
import {driverE2ETestSuite} from '@appium/driver-test-support';
|
||||
|
||||
const DEFAULT_CAPS = {
|
||||
platformName: 'iOS',
|
||||
@@ -15,58 +8,6 @@ const DEFAULT_CAPS = {
|
||||
|
||||
driverE2ETestSuite(BaseDriver, DEFAULT_CAPS);
|
||||
|
||||
describe('BaseDriver', function () {
|
||||
// only run this test on basedriver, not other drivers which also use these tests, since we
|
||||
// don't want them to try and start sessions with these random capabilities that are
|
||||
// necessary to test the appium options logic
|
||||
describe('special appium:options capability', function () {
|
||||
/** @type {SessionHelpers['startSession']} */
|
||||
let startSession;
|
||||
/** @type {SessionHelpers['endSession']} */
|
||||
let endSession;
|
||||
/** @type {import('@appium/types').AppiumServer} */
|
||||
let baseServer;
|
||||
/** @type {BaseDriver} */
|
||||
let d;
|
||||
|
||||
before(async function () {
|
||||
const port = await getTestPort();
|
||||
d = new BaseDriver({port, address: TEST_HOST});
|
||||
baseServer = await server({
|
||||
routeConfiguringFunction: routeConfiguringFunction(d),
|
||||
port,
|
||||
hostname: TEST_HOST,
|
||||
});
|
||||
|
||||
({startSession, endSession} = createSessionHelpers(port));
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await baseServer.close();
|
||||
});
|
||||
|
||||
it('should be able to start a session with caps held in appium:options', async function () {
|
||||
const ret = await startSession({
|
||||
capabilities: {
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
[PREFIXED_APPIUM_OPTS_CAP]: {
|
||||
platformVersion: '11.4',
|
||||
'appium:deviceName': 'iPhone 11',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
expect(d.opts.platformVersion).to.equal('11.4');
|
||||
expect(d.opts.deviceName).to.equal('iPhone 11');
|
||||
} finally {
|
||||
await endSession(ret.sessionId);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/driver-test-support').SessionHelpers} SessionHelpers
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Protocol', function () {
|
||||
}
|
||||
let hash = shasum.digest('hex').substring(0, 8);
|
||||
// Modify the hash whenever the protocol has intentionally been modified.
|
||||
hash.should.equal('736bf246');
|
||||
hash.should.equal('484810b0');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"paths": {
|
||||
@@ -10,8 +10,5 @@
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["lib"],
|
||||
"references": [
|
||||
{"path": "../support"},
|
||||
{"path": "../types"}
|
||||
]
|
||||
"references": [{"path": "../support"}, {"path": "../types"}]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,29 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.1.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.1.0...@appium/base-plugin@2.1.1) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.1.0](https://github.com/appium/appium/compare/@appium/base-plugin@2.0.1...@appium/base-plugin@2.1.0) (2023-01-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.0.0...@appium/base-plugin@2.0.1) (2022-12-21)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
# [2.0.0](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.5...@appium/base-plugin@2.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.1",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -20,7 +30,8 @@
|
||||
"files": [
|
||||
"lib",
|
||||
"build",
|
||||
"index.js"
|
||||
"index.js",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -28,27 +39,20 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.0.0"
|
||||
"@appium/support": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"typedoc": {
|
||||
"entryPoint": "./build/lib/plugin.js"
|
||||
},
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
]
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"checkJs": true,
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.3](https://github.com/appium/appium/compare/@appium/doctor@2.0.2...@appium/doctor@2.0.3) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/appium/appium/compare/@appium/doctor@2.0.1...@appium/doctor@2.0.2) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.10.20 ([29bf77c](https://github.com/appium/appium/commit/29bf77cf5a623c7417d9c996814498f76cead2a3))
|
||||
* **doctor:** update dependency appium-adb to v9.10.21 ([ac2c735](https://github.com/appium/appium/commit/ac2c73596810919dcd1c6d294e8c9496eb91e0b9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/appium/appium/compare/@appium/doctor@2.0.0...@appium/doctor@2.0.1) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **doctor:** update dependency appium-adb to v9.10.19 ([2cb1af4](https://github.com/appium/appium/commit/2cb1af4357e0e867c28c7045825140d45fa02393))
|
||||
|
||||
# [2.0.0](https://github.com/appium/appium/compare/@appium/doctor@1.16.28...@appium/doctor@2.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -2,8 +2,6 @@ appium-doctor
|
||||
===================
|
||||
Attempts to diagnose and fix common Node, iOS and Android configuration issues before starting Appium.
|
||||
|
||||
[](https://travis-ci.org/appium/appium-doctor)
|
||||
|
||||
### Install
|
||||
|
||||
```
|
||||
|
||||
@@ -7,6 +7,9 @@ import '@colors/colors';
|
||||
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';
|
||||
import log from './logger';
|
||||
|
||||
/**
|
||||
* @type {import('./factory').DoctorCheckList}
|
||||
*/
|
||||
let checks = [];
|
||||
|
||||
const javaHome = system.isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME';
|
||||
|
||||
@@ -7,6 +7,9 @@ import {DoctorCheck, FixSkippedError} from './doctor';
|
||||
import log from './logger';
|
||||
import {fixIt} from './prompt';
|
||||
|
||||
/**
|
||||
* @type {import('./factory').DoctorCheckList}
|
||||
*/
|
||||
let checks = [];
|
||||
|
||||
class DirCheck extends DoctorCheck {
|
||||
|
||||
@@ -4,6 +4,9 @@ import {fs, system} from '@appium/support';
|
||||
import path from 'path';
|
||||
import '@colors/colors';
|
||||
|
||||
/**
|
||||
* @type {import('./factory').DoctorCheckList}
|
||||
*/
|
||||
let checks = [];
|
||||
|
||||
// Check PATH binaries
|
||||
|
||||
@@ -7,15 +7,29 @@ const {version} = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
class FixSkippedError extends Error {}
|
||||
|
||||
/**
|
||||
* Create interface for other Doctors
|
||||
*/
|
||||
class DoctorCheck {
|
||||
/**
|
||||
* @param {DoctorOpts} opts
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
this.autofix = !!opts.autofix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every doctor diagnose the symptoms
|
||||
* @throws {Error}
|
||||
*/
|
||||
diagnose() {
|
||||
throw new Error('Not Implemented!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Every doctor suggest the solutions to fix the sickness
|
||||
* @throws {Error}
|
||||
*/
|
||||
fix() {
|
||||
// return string for manual fixes.
|
||||
throw new Error('Not Implemented!');
|
||||
@@ -24,17 +38,28 @@ class DoctorCheck {
|
||||
|
||||
class Doctor {
|
||||
constructor() {
|
||||
/**
|
||||
* All the sub check goes here after register
|
||||
* @type {DoctorCheck[]}
|
||||
*/
|
||||
this.checks = [];
|
||||
this.checkOptionals = [];
|
||||
this.toFix = [];
|
||||
this.toFixOptionals = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all the sub check and combine them together
|
||||
* @param {DoctorCheck[] | DoctorCheck} checks
|
||||
*/
|
||||
register(checks) {
|
||||
checks = Array.isArray(checks) ? checks : [checks];
|
||||
this.checks = this.checks.concat(checks);
|
||||
}
|
||||
|
||||
/**
|
||||
* The doctor shows the report
|
||||
*/
|
||||
async diagnose() {
|
||||
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
|
||||
this.toFix = [];
|
||||
@@ -211,3 +236,8 @@ class Doctor {
|
||||
}
|
||||
|
||||
export {Doctor, DoctorCheck, FixSkippedError};
|
||||
|
||||
/**
|
||||
* @typedef DoctorOpts
|
||||
* @property {boolean?} autofix
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,9 @@ import androidChecks from './android';
|
||||
import devChecks from './dev';
|
||||
import demoChecks from './demo';
|
||||
|
||||
/**
|
||||
* @type {DoctorGroup}
|
||||
*/
|
||||
let checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks};
|
||||
|
||||
let newDoctor = (opts) => {
|
||||
@@ -19,3 +22,16 @@ let newDoctor = (opts) => {
|
||||
};
|
||||
|
||||
export default newDoctor;
|
||||
|
||||
/**
|
||||
* @typedef {import('./doctor').DoctorCheck[]} DoctorCheckList
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef DoctorGroup - Contain a group of Doctors
|
||||
* @property {DoctorCheckList} generalChecks - Check AppiumHome, NodeBinary, NodeVersion, ffmpeg, mjpeg-consumer
|
||||
* @property {DoctorCheckList} iosChecks - Check if iOS toolchains are installed
|
||||
* @property {DoctorCheckList} androidChecks - Check if Android toolchains are installed
|
||||
* @property {DoctorCheckList} devChecks - Check Path Binary and Android SDKs
|
||||
* @property {DoctorCheckList} demoChecks - Check /tmp/appium-doctor/demo/*
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,16 @@ import {util, env} from '@appium/support';
|
||||
import {EOL} from 'os';
|
||||
import '@colors/colors';
|
||||
|
||||
/**
|
||||
* @type {import('./factory').DoctorCheckList}
|
||||
*/
|
||||
let checks = [];
|
||||
|
||||
class AppiumHomeCheck extends DoctorCheck {
|
||||
/**
|
||||
*
|
||||
* @returns {UtilsResult}
|
||||
*/
|
||||
async diagnose() {
|
||||
return ok(`APPIUM_HOME is ${await env.resolveAppiumHome()}`);
|
||||
}
|
||||
@@ -59,6 +66,10 @@ class NodeVersionCheck extends DoctorCheck {
|
||||
checks.push(new NodeVersionCheck());
|
||||
|
||||
class OptionalFfmpegCommandCheck extends DoctorCheck {
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<UtilsResult>}
|
||||
*/
|
||||
async diagnose() {
|
||||
const ffmpegPath = await resolveExecutablePath('ffmpeg');
|
||||
return ffmpegPath
|
||||
@@ -69,8 +80,11 @@ class OptionalFfmpegCommandCheck extends DoctorCheck {
|
||||
)
|
||||
: nokOptional('ffmpeg cannot be found');
|
||||
}
|
||||
// eslint-disable-next-line require-await
|
||||
async fix() {
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
fix() {
|
||||
return `${
|
||||
'ffmpeg'.bold
|
||||
} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`;
|
||||
@@ -107,3 +121,7 @@ export {
|
||||
OptionalMjpegConsumerCommandCheck,
|
||||
};
|
||||
export default checks;
|
||||
|
||||
/**
|
||||
* @typedef {import('./utils').UtilsResult} UtilsResult
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,9 @@ import {fixIt} from './prompt';
|
||||
import EnvVarAndPathCheck from './env';
|
||||
import '@colors/colors';
|
||||
|
||||
/**
|
||||
* @type {import('./factory').DoctorCheckList}
|
||||
*/
|
||||
let checks = [];
|
||||
let fixes = {};
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {inquirer} from './utils';
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let persistentResponse;
|
||||
|
||||
const fixItQuestion = {
|
||||
@@ -7,6 +10,11 @@ const fixItQuestion = {
|
||||
name: 'confirmation',
|
||||
message: 'Fix it:',
|
||||
choices: ['yes', 'no', 'always', 'never'],
|
||||
/**
|
||||
*
|
||||
* @param {string} val
|
||||
* @returns {string}
|
||||
*/
|
||||
filter(val) {
|
||||
return val.toLowerCase();
|
||||
},
|
||||
|
||||
@@ -5,15 +5,34 @@ import {fs, system} from '@appium/support';
|
||||
import {exec} from 'teen_process';
|
||||
import {isFunction} from 'lodash';
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @returns {UtilsResult}
|
||||
*/
|
||||
function ok(message) {
|
||||
return {ok: true, optional: false, message};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @returns {UtilsResult}
|
||||
*/
|
||||
function nok(message) {
|
||||
return {ok: false, optional: false, message};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @returns {UtilsResult}
|
||||
*/
|
||||
function okOptional(message) {
|
||||
return {ok: true, optional: true, message};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @returns {UtilsResult}
|
||||
*/
|
||||
function nokOptional(message) {
|
||||
return {ok: false, optional: true, message};
|
||||
}
|
||||
@@ -122,3 +141,10 @@ export {
|
||||
getNpmPackageInfo,
|
||||
resetLog,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef UtilsResult
|
||||
* @property {boolean} ok
|
||||
* @property {boolean} optional
|
||||
* @property {string} message
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.3",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -34,7 +34,8 @@
|
||||
"appium-doctor.js",
|
||||
"lib",
|
||||
"bin",
|
||||
"build"
|
||||
"build",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -42,11 +43,11 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.0.0",
|
||||
"@appium/support": "^3.1.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"appium-adb": "9.10.18",
|
||||
"appium-adb": "9.10.21",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.5",
|
||||
"lodash": "4.17.21",
|
||||
@@ -61,5 +62,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"paths": {
|
||||
"@appium/support": ["../support"],
|
||||
},
|
||||
"@appium/support": ["../support"]
|
||||
}
|
||||
},
|
||||
"include": ["lib", "bin"],
|
||||
"references": [
|
||||
{"path": "../support"}
|
||||
]
|
||||
"references": [{"path": "../support"}]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.3](https://github.com/appium/appium/compare/@appium/docutils@0.1.2...@appium/docutils@0.1.3) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.2](https://github.com/appium/appium/compare/@appium/docutils@0.1.1...@appium/docutils@0.1.2) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.1](https://github.com/appium/appium/compare/@appium/docutils@0.1.0...@appium/docutils@0.1.1) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **docutils:** package should publish the base mkdocs yml ([#17968](https://github.com/appium/appium/issues/17968)) ([aab4156](https://github.com/appium/appium/commit/aab415685ec5d5578cc8faae563f5cc5c0e4bb75))
|
||||
|
||||
# [0.1.0](https://github.com/appium/appium/compare/@appium/docutils@0.0.13...@appium/docutils@0.1.0) (2022-12-14)
|
||||
|
||||
- chore!: set engines to minimum Node.js v14.17.0 ([a1dbe6c](https://github.com/appium/appium/commit/a1dbe6c43efe76604943a607d402f4c8b864d652))
|
||||
|
||||
@@ -6,11 +6,11 @@ const DEFAULT_BRANCH = 'gh-pages';
|
||||
const MIKE_VER_STRING = 'mike 1.';
|
||||
|
||||
export class Mike {
|
||||
/** @type string */ remote;
|
||||
/** @type string */ branch;
|
||||
/** @type string? */ prefix;
|
||||
/** @type string */ configFile;
|
||||
/** @type boolean */ _mikeVerified = false;
|
||||
/** @type {string} */ remote;
|
||||
/** @type {string} */ branch;
|
||||
/** @type {string?} */ prefix;
|
||||
/** @type {string} */ configFile;
|
||||
/** @type {boolean} */ _mikeVerified = false;
|
||||
|
||||
constructor(/** @type MikeOpts */ opts) {
|
||||
this.remote = opts.remote || DEFAULT_REMOTE;
|
||||
@@ -22,7 +22,7 @@ export class Mike {
|
||||
/**
|
||||
* Throw an error if the 'mike' binary cannot be found
|
||||
*
|
||||
* @throws Error
|
||||
* @throws {Error}
|
||||
*/
|
||||
async verifyMike() {
|
||||
if (this._mikeVerified) {
|
||||
@@ -46,7 +46,7 @@ export class Mike {
|
||||
* @param {string} cmdName - the name of the mike command to run
|
||||
* @param {string[]} cmdArgs - an array of command-specific arguments
|
||||
*
|
||||
* @returns string[]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getMikeArgs(cmdName, cmdArgs) {
|
||||
return [
|
||||
@@ -70,7 +70,7 @@ export class Mike {
|
||||
* @param {string[]} [mikeArgs=[]] - the arguments to pass to the mike command
|
||||
* @param {boolean?} [verify=true] - whether to verify mike exists first
|
||||
*
|
||||
* @returns Promise<import('teen_process').ExecResult<string>>
|
||||
* @returns {Promise<import('teen_process').ExecResult<string>>}
|
||||
*/
|
||||
async exec(mikeCmd, mikeArgs = [], verify = true) {
|
||||
if (verify) {
|
||||
@@ -84,7 +84,7 @@ export class Mike {
|
||||
/**
|
||||
* Return the list of mike deploys
|
||||
*
|
||||
* @returns string[]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
async list() {
|
||||
const {stdout} = await this.exec('list');
|
||||
|
||||
@@ -22,9 +22,9 @@ export async function verifyMkdocs() {
|
||||
/**
|
||||
* Run 'mkdocs build' on a project
|
||||
*
|
||||
* @param {string} configPath path to mkdocs config yml
|
||||
* @param {string} outputDir directory mkdocs should build into
|
||||
* @param {?string} theme theme name
|
||||
* @param {string} configPath - path to mkdocs config yml
|
||||
* @param {string} outputDir - directory mkdocs should build into
|
||||
* @param {string?} theme - theme name
|
||||
*/
|
||||
export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
|
||||
await verifyMkdocs();
|
||||
@@ -34,7 +34,7 @@ export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
|
||||
/**
|
||||
* Run 'mkdocs serve' on a project
|
||||
*
|
||||
* @param {string} configPath path to mkdocs config yml
|
||||
* @param {string} configPath - path to mkdocs config yml
|
||||
*/
|
||||
export async function mkdocsServe(configPath) {
|
||||
await verifyMkdocs();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -30,8 +30,10 @@
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"base-mkdocs.yml",
|
||||
"lib",
|
||||
"build"
|
||||
"build",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -40,7 +42,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.0.0",
|
||||
"@appium/support": "^3.1.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2"
|
||||
},
|
||||
@@ -51,5 +53,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mkdocs==1.4.2
|
||||
mkdocs-material==8.5.11
|
||||
mkdocs-material==9.0.4
|
||||
mkdocs-material-extensions==1.1.1
|
||||
mike==1.1.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.3.3](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.2...@appium/driver-test-support@0.3.3) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/driver-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.2](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.1...@appium/driver-test-support@0.3.2) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
|
||||
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
|
||||
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.1](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.0...@appium/driver-test-support@0.3.1) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **test-support:** update dependency sinon to v15.0.1 ([5d0d4f5](https://github.com/appium/appium/commit/5d0d4f5a44dff362430159f97f8569a9be7e378d))
|
||||
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
|
||||
|
||||
# [0.3.0](https://github.com/appium/appium/compare/@appium/driver-test-support@0.2.1...@appium/driver-test-support@0.3.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -30,7 +30,8 @@
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"build"
|
||||
"build",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -39,16 +40,16 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.6.0",
|
||||
"@appium/types": "^0.8.1",
|
||||
"@types/lodash": "4.14.191",
|
||||
"axios": "1.2.1",
|
||||
"axios": "1.2.2",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.7",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "15.0.0",
|
||||
"sinon": "15.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.3.0"
|
||||
"type-fest": "3.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
@@ -60,5 +61,6 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
},
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"checkJs": true,
|
||||
"paths": {
|
||||
"@appium/types": ["../types"],
|
||||
"appium/driver": ["../base-driver"],
|
||||
"appium/driver": ["../base-driver"]
|
||||
},
|
||||
"types": [
|
||||
"mocha", "chai", "chai-as-promised"
|
||||
]
|
||||
"types": ["mocha", "chai", "chai-as-promised"]
|
||||
},
|
||||
"include": ["./lib"],
|
||||
"references": [
|
||||
{"path": "../types"},
|
||||
{"path": "../base-driver"}
|
||||
]
|
||||
"references": [{"path": "../types"}, {"path": "../base-driver"}]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,35 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.3](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.2...@appium/execute-driver-plugin@3.0.3) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.2](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.1...@appium/execute-driver-plugin@3.0.2) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** fix bad types in execute-child module ([697b442](https://github.com/appium/appium/commit/697b4427e4e60821943e8c05292e8f6eb43aa22f))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.28.1 ([2bb4f32](https://github.com/appium/appium/commit/2bb4f32329938e15062e42b585dd760cca5ae17b))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.29.0 ([a3fad3e](https://github.com/appium/appium/commit/a3fad3ed28822b1dfaae72cbfb8d7906ec35e158))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.29.1 ([7c59380](https://github.com/appium/appium/commit/7c59380e6b97691ff16a8d98a912d67b7972593a))
|
||||
* **execute-driver-plugin:** use correct return type annotation ([7add23e](https://github.com/appium/appium/commit/7add23e4183e6982048cd653b2a47eb052ccc38a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.1](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.0...@appium/execute-driver-plugin@3.0.1) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **execute-driver-plugin:** update dependency webdriverio to v7.28.0 ([f81e2e9](https://github.com/appium/appium/commit/f81e2e92eff25c33d36f767209f423227d288218))
|
||||
|
||||
# [3.0.0](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.6...@appium/execute-driver-plugin@3.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -5,6 +5,9 @@ import {logger, util} from 'appium/support';
|
||||
import {attach} from 'webdriverio';
|
||||
|
||||
const log = logger.getLogger('ExecuteDriver Child');
|
||||
/**
|
||||
* @type {(res: ScriptResult) => Promise<void>}
|
||||
*/
|
||||
let send;
|
||||
|
||||
// duplicate defining these keys here so we don't need to re-load a huge appium
|
||||
@@ -12,12 +15,22 @@ let send;
|
||||
export const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
|
||||
export const MJSONWP_ELEMENT_KEY = 'ELEMENT';
|
||||
|
||||
async function runScript(driverOpts, script, timeoutMs) {
|
||||
/**
|
||||
* Run the script in a VM.
|
||||
* @param {DriverScriptMessageEvent} eventParams
|
||||
* @returns {Promise<RunScriptResult>}
|
||||
* @throws {TypeError}
|
||||
*/
|
||||
async function runScript(eventParams) {
|
||||
const {driverOpts, script, timeoutMs} = eventParams;
|
||||
if (!_.isNumber(timeoutMs)) {
|
||||
throw new TypeError('Timeout parameter must be a number');
|
||||
}
|
||||
|
||||
// set up fake logger
|
||||
/**
|
||||
* set up fake logger
|
||||
* @type {string[]}
|
||||
*/
|
||||
const logLevels = ['error', 'warn', 'log'];
|
||||
const logs = {};
|
||||
const consoleFns = {};
|
||||
@@ -122,11 +135,22 @@ function coerceScriptResult(obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
async function main(driverOpts, script, timeoutMs) {
|
||||
/**
|
||||
* Entry point to runScript
|
||||
* @param {DriverScriptMessageEvent} eventParams
|
||||
*/
|
||||
async function main(eventParams) {
|
||||
/**
|
||||
* keep the response of runScript
|
||||
* @type {ScriptResult}
|
||||
*/
|
||||
let res;
|
||||
log.info('Parameters received from parent process');
|
||||
try {
|
||||
res = {success: await runScript(driverOpts, script, timeoutMs)};
|
||||
res = {success: await runScript(eventParams)};
|
||||
log.info('runScript success');
|
||||
} catch (error) {
|
||||
log.info('runScript error');
|
||||
res = {error: {message: error.message, stack: error.stack}};
|
||||
}
|
||||
await send(res);
|
||||
@@ -136,8 +160,34 @@ async function main(driverOpts, script, timeoutMs) {
|
||||
if (require.main === module && _.isFunction(process.send)) {
|
||||
send = B.promisify(process.send, {context: process});
|
||||
log.info('Running driver execution in child process');
|
||||
process.on('message', ({driverOpts, script, timeoutMs}) => {
|
||||
log.info('Parameters received from parent process');
|
||||
main(driverOpts, script, timeoutMs);
|
||||
});
|
||||
process.on('message', main);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('webdriverio').AttachOptions} AttachOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef DriverScriptMessageEvent
|
||||
* @property {AttachOptions} driverOpts - the driver options
|
||||
* @property {string} script - the javascript to execute
|
||||
* @property {number} timeoutMs - script timeout in milliseconds
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ScriptResult
|
||||
* @property {any} [success]
|
||||
* @property {ScriptResultError} [error]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ScriptResultError
|
||||
* @property {any} message
|
||||
* @property {any} stack
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef RunScriptResult
|
||||
* @property {any} result
|
||||
* @property {object} logs
|
||||
*/
|
||||
|
||||
@@ -23,15 +23,17 @@ export default class ExecuteDriverPlugin extends BasePlugin {
|
||||
* a new nodejs VM, and which has available a webdriverio driver object, having
|
||||
* already been attached to the currently running session.
|
||||
*
|
||||
* @param {function} next - standard behaviour for executeDriverScript
|
||||
* @param {BaseDriver} driver - Appium driver handling this command
|
||||
* @param {import('@appium/types').NextPluginCallback} next - standard behaviour for executeDriverScript
|
||||
* @param {import('@appium/types').ExternalDriver} driver - Appium driver handling this command
|
||||
* @param {string} script - the string representing the driver script to run
|
||||
* @param {string} [scriptType='webdriverio'] - the name of the driver script
|
||||
* library (currently only webdriverio is supported)
|
||||
* @param {number} [timeoutMs=3600000] - timeout for the script process
|
||||
*
|
||||
* @returns {Object} - a JSONifiable object representing the return value of
|
||||
* @returns {Promise<any>} - a JSONifiable object representing the return value of
|
||||
* the script
|
||||
* @type {import('@appium/types').PluginCommand<[string, string?, number?]>}
|
||||
* @throws {Error}
|
||||
*/
|
||||
async executeDriverScript(
|
||||
next,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.3",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -23,10 +23,12 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"types": "./build/lib/plugin.d.ts",
|
||||
"files": [
|
||||
"build",
|
||||
"lib",
|
||||
"index.js"
|
||||
"index.js",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -40,11 +42,15 @@
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.13",
|
||||
"webdriverio": "7.27.0"
|
||||
"webdriverio": "7.29.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -52,9 +58,8 @@
|
||||
"pluginName": "execute-driver",
|
||||
"mainClass": "ExecuteDriverPlugin"
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build"
|
||||
"outDir": "build",
|
||||
"checkJs": true
|
||||
},
|
||||
"extends": "../../config/tsconfig.plugin.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.plugin.json",
|
||||
"include": ["lib"]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.1](https://github.com/appium/appium/compare/@appium/fake-driver@5.1.0...@appium/fake-driver@5.1.1) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.0.0...@appium/fake-driver@5.1.0) (2023-01-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.2...@appium/fake-driver@5.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -26,9 +26,8 @@ export function ContextsMixin(Base) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current appium context
|
||||
*
|
||||
// do not add a description to this method's docstring
|
||||
/*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async getCurrentContext() {
|
||||
|
||||
@@ -64,6 +64,10 @@ export function ElementMixin(Base) {
|
||||
await this.setValue('', elementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This comment should be displayed instead of the one from ExternalDriver
|
||||
* @param {string} elementId
|
||||
*/
|
||||
async click(elementId) {
|
||||
this.assertNoAlert();
|
||||
let el = this.getElement(elementId);
|
||||
|
||||
@@ -78,6 +78,11 @@ export function FindMixin(Base) {
|
||||
return /** @type {Mult extends true ? Element[] : Element} */ (retval);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should override whatever's in ExternalDriver
|
||||
* @param {string} strategy Strategy
|
||||
* @param {string} selector Selector
|
||||
*/
|
||||
async findElement(strategy, selector) {
|
||||
return this.findElOrEls(strategy, selector, false);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ export function GeneralMixin(Base) {
|
||||
}
|
||||
}
|
||||
|
||||
async mobileShake() {
|
||||
this.shook = true;
|
||||
}
|
||||
|
||||
async doubleClick() {}
|
||||
|
||||
async execute(script, args) {
|
||||
return await this.executeMethod(script, args);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {GeneralMixin} from './general';
|
||||
/**
|
||||
*
|
||||
* @param {import('@appium/types').Class<import('../driver').FakeDriverCore>} Base
|
||||
* @returns
|
||||
*/
|
||||
export function FakeDriverMixin(Base) {
|
||||
return GeneralMixin(FindMixin(ElementMixin(AlertMixin(ContextsMixin(Base)))));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import B from 'bluebird';
|
||||
import {BaseDriver, errors} from 'appium/driver';
|
||||
import {deprecatedCommandsLogged} from '@appium/base-driver/build/lib/protocol/protocol';
|
||||
import {FakeApp} from './fake-app';
|
||||
import {FakeDriverMixin} from './commands';
|
||||
|
||||
@@ -37,6 +38,7 @@ export class FakeDriverCore extends BaseDriver {
|
||||
this.maxElId = 0;
|
||||
this.fakeThing = null;
|
||||
this._proxyActive = false;
|
||||
this.shook = false;
|
||||
}
|
||||
|
||||
proxyActive() {
|
||||
@@ -65,25 +67,21 @@ export class FakeDriverCore extends BaseDriver {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {W3CFakeDriverCaps} jsonwpDesiredCapabilities
|
||||
* @param {W3CFakeDriverCaps} [jsonwpRequiredCaps]
|
||||
* @param {W3CFakeDriverCaps} [w3cCapabilities]
|
||||
* @param {import('@appium/types').DriverData[]} [otherSessionData]
|
||||
* @returns {Promise<[string,FakeDriverCaps]>}
|
||||
* Comment for `createSession` in `FakeDriver`
|
||||
* @param {W3CFakeDriverCaps} w3cCapabilities1 W3C Capabilities
|
||||
* @param {W3CFakeDriverCaps} [w3cCapabilities2] W3C Capabilities
|
||||
* @param {W3CFakeDriverCaps} [w3cCapabilities3] W3C Capabilities
|
||||
* @param {import('@appium/types').DriverData[]} [driverData] Other session data
|
||||
* @override
|
||||
* @returns {Promise<[string,FakeDriverCaps]>} Session ID and normalized capabilities
|
||||
*/
|
||||
async createSession(
|
||||
jsonwpDesiredCapabilities,
|
||||
jsonwpRequiredCaps,
|
||||
w3cCapabilities,
|
||||
otherSessionData = []
|
||||
) {
|
||||
async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities3, driverData = []) {
|
||||
// TODO add validation on caps.app that we will get for free from
|
||||
// BaseDriver
|
||||
|
||||
// check to see if any other sessions have set uniqueApp. If so, emulate
|
||||
// not being able to start a session because of system resources
|
||||
for (let d of otherSessionData) {
|
||||
for (let d of driverData) {
|
||||
if (d.isUnique) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
'Cannot start session; another ' +
|
||||
@@ -93,12 +91,7 @@ export class FakeDriverCore extends BaseDriver {
|
||||
}
|
||||
|
||||
let [sessionId, caps] = /** @type {[string, FakeDriverCaps]} */ (
|
||||
await super.createSession(
|
||||
jsonwpDesiredCapabilities,
|
||||
jsonwpRequiredCaps,
|
||||
w3cCapabilities,
|
||||
otherSessionData
|
||||
)
|
||||
await super.createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities3, driverData)
|
||||
);
|
||||
this.caps = caps;
|
||||
await this.appModel.loadApp(caps.app);
|
||||
@@ -138,6 +131,25 @@ export class FakeDriverCore extends BaseDriver {
|
||||
return this.cliArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a command that will return a list of deprecated command names called
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async getDeprecatedCommandsCalled() {
|
||||
await B.delay(1);
|
||||
return Array.from(deprecatedCommandsLogged);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a command that exists just to be an example of a deprecated command
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async callDeprecatedCommand() {
|
||||
await B.delay(1);
|
||||
}
|
||||
|
||||
static newMethodMap = /** @type {const} */ ({
|
||||
'/session/:sessionId/fakedriver': {
|
||||
GET: {command: 'getFakeThing'},
|
||||
@@ -149,6 +161,13 @@ export class FakeDriverCore extends BaseDriver {
|
||||
'/session/:sessionId/fakedriverargs': {
|
||||
GET: {command: 'getFakeDriverArgs'},
|
||||
},
|
||||
'/session/:sessionId/deprecated': {
|
||||
POST: {command: 'callDeprecatedCommand', deprecated: true},
|
||||
},
|
||||
// this next one exists to override a deprecated method
|
||||
'/session/:sessionId/doubleclick': {
|
||||
POST: {command: 'doubleClick'},
|
||||
},
|
||||
});
|
||||
|
||||
static executeMethodMap = /** @type {const} */ ({
|
||||
@@ -166,6 +185,9 @@ export class FakeDriverCore extends BaseDriver {
|
||||
command: 'setFakeThing',
|
||||
params: {required: ['thing']},
|
||||
},
|
||||
'fake: getDeprecatedCommandsCalled': {
|
||||
command: 'getDeprecatedCommandsCalled',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,3 +15,8 @@ async function main() {
|
||||
}
|
||||
|
||||
export {FakeDriver, startServer, main};
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').W3CFakeDriverCaps} W3CFakeDriverCaps
|
||||
* @typedef {import('./types').FakeDriverCaps} FakeDriverCaps
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.1",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -33,7 +33,8 @@
|
||||
"index.js",
|
||||
"lib",
|
||||
"build",
|
||||
"test/fixtures"
|
||||
"test/fixtures",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "cpy lib/screen.png build",
|
||||
@@ -77,8 +78,8 @@
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./build/lib/index.js"
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"checkJs": true,
|
||||
|
||||
@@ -3,6 +3,29 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.1](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.0...@appium/fake-plugin@3.1.1) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.1.0](https://github.com/appium/appium/compare/@appium/fake-plugin@3.0.1...@appium/fake-plugin@3.1.0) (2023-01-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.1](https://github.com/appium/appium/compare/@appium/fake-plugin@3.0.0...@appium/fake-plugin@3.0.1) (2022-12-21)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
# [3.0.0](https://github.com/appium/appium/compare/@appium/fake-plugin@2.1.3...@appium/fake-plugin@3.0.0) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
{
|
||||
"name": "@appium/fake-plugin",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.1",
|
||||
"description": "A fake Appium 2.0 plugin",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -13,14 +23,14 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"main": "./build/index.js",
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"build"
|
||||
"build",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
@@ -28,8 +38,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^2.0.0",
|
||||
"@appium/support": "^3.0.0",
|
||||
"@appium/base-plugin": "^2.1.1",
|
||||
"@appium/support": "^3.1.1",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
@@ -50,18 +60,14 @@
|
||||
"fake-success": "./build/lib/scripts/fake-success.js"
|
||||
}
|
||||
},
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
]
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "build"
|
||||
},
|
||||
"extends": "../../config/tsconfig.plugin.json",
|
||||
"extends": "@appium/tsconfig/tsconfig.plugin.json",
|
||||
"include": ["lib"],
|
||||
"references": [{
|
||||
"path": "../base-plugin"
|
||||
}]
|
||||
"references": [
|
||||
{
|
||||
"path": "../base-plugin"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user