mirror of
https://github.com/appium/appium.git
synced 2026-11-09 19:38:26 +08:00
Compare commits
279
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0ebbaa0c6 | ||
|
|
1629f858f8 | ||
|
|
2d124323c5 | ||
|
|
901ac711c4 | ||
|
|
7982747677 | ||
|
|
26bb0f95b9 | ||
|
|
c161ec2bad | ||
|
|
1c4737c8b4 | ||
|
|
6b2f5668e9 | ||
|
|
d904477b4c | ||
|
|
da1da3fe13 | ||
|
|
307c81a24b | ||
|
|
f4c37985eb | ||
|
|
78c6794253 | ||
|
|
c416a86120 | ||
|
|
38f86900da | ||
|
|
40fc097eda | ||
|
|
0097c8e52e | ||
|
|
1ccf857a4b | ||
|
|
c34f2eb897 | ||
|
|
1d7c58ff26 | ||
|
|
d78ca63e60 | ||
|
|
4f171ee92f | ||
|
|
fd2c72fd88 | ||
|
|
3bf406015e | ||
|
|
56499eb997 | ||
|
|
bd10bffec4 | ||
|
|
b1ee41cfc7 | ||
|
|
cc7f5f4a52 | ||
|
|
a98d9c33c7 | ||
|
|
1a2260bb15 | ||
|
|
4c52fdf1d0 | ||
|
|
a959eec9cf | ||
|
|
e96724077e | ||
|
|
dfae94e7f3 | ||
|
|
0497090a90 | ||
|
|
19277f6e14 | ||
|
|
39a59c8bb3 | ||
|
|
2766dcca4c | ||
|
|
f5b112bcca | ||
|
|
5d6458b611 | ||
|
|
d47c805468 | ||
|
|
4c61d533b8 | ||
|
|
325d237207 | ||
|
|
8f63dabb5d | ||
|
|
5a555090c5 | ||
|
|
81ed72f19d | ||
|
|
f34c75c650 | ||
|
|
39d68f17a2 | ||
|
|
528dcfe324 | ||
|
|
4d7f39b5f8 | ||
|
|
8594d13bf6 | ||
|
|
a0b434dff7 | ||
|
|
19a5ae4537 | ||
|
|
fc831d2475 | ||
|
|
cb1a97fc8e | ||
|
|
a506a0a357 | ||
|
|
63a429c53b | ||
|
|
922725b4f1 | ||
|
|
f0ebe2e02f | ||
|
|
2ca1581870 | ||
|
|
6aec50b074 | ||
|
|
426830d14a | ||
|
|
9538c34131 | ||
|
|
5d24ea1ad2 | ||
|
|
db73971018 | ||
|
|
7b17754786 | ||
|
|
ba07d2a854 | ||
|
|
ee365ad168 | ||
|
|
5e351dc503 | ||
|
|
f512e596eb | ||
|
|
3eec0227a2 | ||
|
|
ea42548442 | ||
|
|
e1fa6c2133 | ||
|
|
6b245534c2 | ||
|
|
6e4d992b8b | ||
|
|
ac4d4f7262 | ||
|
|
d891c9570a | ||
|
|
37e6376d10 | ||
|
|
5ceaa5b38a | ||
|
|
cfa7b2d442 | ||
|
|
c8d707df90 | ||
|
|
40e5199caa | ||
|
|
cdfe7c3d82 | ||
|
|
3c4d3acc09 | ||
|
|
2f26c51dac | ||
|
|
155f24bb4d | ||
|
|
74f29cfd5e | ||
|
|
ac85f051a7 | ||
|
|
e68f17b3cc | ||
|
|
25fd473b98 | ||
|
|
55060f8e8f | ||
|
|
1da9de4225 | ||
|
|
6f10078591 | ||
|
|
c311c48c55 | ||
|
|
869b7aa4a2 | ||
|
|
82e2150c66 | ||
|
|
94a207fc97 | ||
|
|
c568523e01 | ||
|
|
91a6bc5925 | ||
|
|
6499eea0af | ||
|
|
add0eaa05f | ||
|
|
3d1e4af858 | ||
|
|
e7df6fd4f0 | ||
|
|
c37072e6a9 | ||
|
|
864ab11b8a | ||
|
|
4f564ef7ad | ||
|
|
b2bda2188b | ||
|
|
ddadd6977d | ||
|
|
fa681f6355 | ||
|
|
522177f1d3 | ||
|
|
ab7958c8e8 | ||
|
|
1e4bc0a273 | ||
|
|
9f8b9cef70 | ||
|
|
2fb844c8ad | ||
|
|
2796639b11 | ||
|
|
65f74d818f | ||
|
|
cf398bdfd6 | ||
|
|
ddfca214bf | ||
|
|
0f19902918 | ||
|
|
9ad7279fa7 | ||
|
|
66922279b7 | ||
|
|
61ca898c47 | ||
|
|
d3c30e5079 | ||
|
|
35b5ac72ab | ||
|
|
a4a816efeb | ||
|
|
93bddb4c73 | ||
|
|
8627d75a47 | ||
|
|
45cd43488c | ||
|
|
c85b89ecbe | ||
|
|
1f9a625f00 | ||
|
|
b9ef676b73 | ||
|
|
66459e991e | ||
|
|
5a70d7ce78 | ||
|
|
611a63f0ae | ||
|
|
e4863778c7 | ||
|
|
0ff735598a | ||
|
|
5d705851f2 | ||
|
|
6ba5c52e98 | ||
|
|
a314166585 | ||
|
|
fe96af91f2 | ||
|
|
52ae548ddc | ||
|
|
9214c15d58 | ||
|
|
11406e4879 | ||
|
|
640e0ae30f | ||
|
|
6245022be9 | ||
|
|
91abd5b598 | ||
|
|
78bc0926db | ||
|
|
59e4367cc8 | ||
|
|
c7d9a456d8 | ||
|
|
e8590c77e7 | ||
|
|
6ae86bd5c7 | ||
|
|
a9a6b834bc | ||
|
|
297f4995f8 | ||
|
|
f42374eb6d | ||
|
|
d6c70f2f52 | ||
|
|
e35bc50454 | ||
|
|
63e28a014c | ||
|
|
e9b50225dc | ||
|
|
e9426fcfec | ||
|
|
5ab2fea576 | ||
|
|
377e9825de | ||
|
|
116c9a66e2 | ||
|
|
04b542cfcd | ||
|
|
c39be81d04 | ||
|
|
efc847380a | ||
|
|
cf7b4d84af | ||
|
|
5a3666f653 | ||
|
|
5790924bf8 | ||
|
|
39b7cd5084 | ||
|
|
fd912346fa | ||
|
|
1d5070e891 | ||
|
|
42953b2add | ||
|
|
d0f479f7a8 | ||
|
|
e67890f463 | ||
|
|
ff46dfa814 | ||
|
|
afc7e34402 | ||
|
|
eef4e3447a | ||
|
|
d6c42e99c0 | ||
|
|
6cf14802b7 | ||
|
|
d5090155ad | ||
|
|
591d15bc5e | ||
|
|
00dbd28190 | ||
|
|
b808dc866d | ||
|
|
d866bddc15 | ||
|
|
825833d1ad | ||
|
|
bb11d1cfe9 | ||
|
|
11c6aaae41 | ||
|
|
822206be20 | ||
|
|
374c85e22a | ||
|
|
a11a6ede33 | ||
|
|
41eab2c938 | ||
|
|
296c29ad84 | ||
|
|
07f9d41bd2 | ||
|
|
d8b633406a | ||
|
|
b9d339eaf3 | ||
|
|
1ea309814a | ||
|
|
19ff931bc0 | ||
|
|
9d2469fdf3 | ||
|
|
47a4ddd2a3 | ||
|
|
a1723231be | ||
|
|
f4a254f204 | ||
|
|
22b943dff1 | ||
|
|
19eaaf5764 | ||
|
|
55805390b5 | ||
|
|
e94275c229 | ||
|
|
f795fe6056 | ||
|
|
bbcaec9c05 | ||
|
|
43a0d1778f | ||
|
|
d514ebdd7e | ||
|
|
8480a85ce2 | ||
|
|
ad98d38efb | ||
|
|
1358425e1a | ||
|
|
43fa0d7425 | ||
|
|
86eb495641 | ||
|
|
5485f0b9cd | ||
|
|
4974403661 | ||
|
|
deb631b456 | ||
|
|
238c489f3a | ||
|
|
3c9fa7ba73 | ||
|
|
e8e8252c52 | ||
|
|
50f1e69268 | ||
|
|
0dcd5fa371 | ||
|
|
75cbb11d00 | ||
|
|
020473afc4 | ||
|
|
497641a00d | ||
|
|
bc860c733a | ||
|
|
d774a8428a | ||
|
|
a593269f61 | ||
|
|
89fd52a602 | ||
|
|
7f061d2362 | ||
|
|
2c1e1532ad | ||
|
|
0c52b50e65 | ||
|
|
6db70f4143 | ||
|
|
1800530bf2 | ||
|
|
32a5ee91df | ||
|
|
5c0315d3bb | ||
|
|
12f4ec404c | ||
|
|
8aa39c117f | ||
|
|
017216533b | ||
|
|
499bda4533 | ||
|
|
9ef564f0be | ||
|
|
d303527c3d | ||
|
|
51a8f2fc34 | ||
|
|
c56dc98d13 | ||
|
|
454f6a5d63 | ||
|
|
87408e529c | ||
|
|
24bec8ce81 | ||
|
|
d9b47bc864 | ||
|
|
6912fa14f2 | ||
|
|
45291f71f4 | ||
|
|
409192d063 | ||
|
|
309fe4c964 | ||
|
|
34891f5657 | ||
|
|
43df77f6b6 | ||
|
|
78c6a0b991 | ||
|
|
d3e91222c8 | ||
|
|
f2dadd8f8f | ||
|
|
ffedb6e40e | ||
|
|
fdcd01ba35 | ||
|
|
bd3ecc3fb1 | ||
|
|
dbc0bf69e8 | ||
|
|
23b1096caa | ||
|
|
8805792089 | ||
|
|
ef9cbe6bcc | ||
|
|
fc19705ecd | ||
|
|
d4654138ee | ||
|
|
930a45dafe | ||
|
|
f1a8ce0bba | ||
|
|
287974a93a | ||
|
|
4c081c6723 | ||
|
|
58ae10f887 | ||
|
|
f673efcba5 | ||
|
|
503da432dc | ||
|
|
4fb7eb8643 | ||
|
|
ae303c73de | ||
|
|
ad11fbf97b | ||
|
|
9ed6711a7e | ||
|
|
fa0e853bd0 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": ["@appium/eslint-config-appium", "@appium/eslint-config-appium-ts"],
|
||||
"extends": ["@appium/eslint-config-appium-ts"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "packages/fake-driver/**/*",
|
||||
@@ -10,12 +10,6 @@
|
||||
"files": "packages/*/test/**/*",
|
||||
"rules": {"func-names": "off"}
|
||||
},
|
||||
{
|
||||
"files": ["packages/*/index.js", "packages/*/scripts/**/*.js", "test/*.js"],
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"packages/appium/support.js",
|
||||
@@ -26,9 +20,24 @@
|
||||
"sourceType": "script"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"./test/setup.js",
|
||||
"./**/scripts/**/*.js",
|
||||
"./packages/*/index.js",
|
||||
"./packages/docutils/bin/appium-docs.js"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "./packages/*/test/**/*.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-restricted-properties": [
|
||||
"error",
|
||||
{
|
||||
|
||||
+1
-1
@@ -53,4 +53,4 @@ targets:
|
||||
path: ./packages/docutils
|
||||
|
||||
revision:
|
||||
- branch: master
|
||||
branch: master
|
||||
|
||||
+12
-1
@@ -25,6 +25,7 @@ labels:
|
||||
matcher:
|
||||
commits: '^chore.*?:'
|
||||
title: '^chore.*?:'
|
||||
files: ['.github/**', '.husky/**', '.vscode/**', './.*', './*.js', '**/test/**']
|
||||
|
||||
- label: 'SemVer:Breaking'
|
||||
sync: true
|
||||
@@ -34,7 +35,7 @@ labels:
|
||||
- label: 'Dependencies'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['**/package.json', '**/package-lock.json', '**/requirements.txt']
|
||||
files: ['**/package.json', 'package-lock.json', '**/requirements.txt']
|
||||
author: 'renovate'
|
||||
|
||||
- label: 'appium core'
|
||||
@@ -52,6 +53,11 @@ labels:
|
||||
matcher:
|
||||
files: ['packages/eslint-config-appium/**']
|
||||
|
||||
- label: '@appium/eslint-config-appium-ts'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/eslint-config-appium-ts/**']
|
||||
|
||||
- label: '@appium/base-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
@@ -141,3 +147,8 @@ labels:
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/universal-xml-plugin/**']
|
||||
|
||||
- label: '@appium/strongbox'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/strongbox/**']
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Labeler
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
branches: [master]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
name: Auto-Label PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fuxingloh/multi-labeler@6704db0bcba106d07482efabbc79d3092af74fa2 # v2
|
||||
+57
-30
@@ -1,5 +1,42 @@
|
||||
name: Appium Build
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
# this list of paths could potentially impact the build
|
||||
paths:
|
||||
- '**/tsconfig*.json'
|
||||
- 'packages/base-driver/static/**/*'
|
||||
- 'packages/*/test/**'
|
||||
- 'packages/*/bin'
|
||||
- 'packages/**/*.ts'
|
||||
- 'packages/**/*.json'
|
||||
- 'packages/**/*.js'
|
||||
- 'package*.json'
|
||||
- 'lerna.json'
|
||||
- '.npmrc'
|
||||
- '.mocharc.js'
|
||||
- '.github/workflows/build.yml' # this file
|
||||
- '.eslintrc'
|
||||
- '.eslintignore'
|
||||
- '!**/sample-code/**'
|
||||
- '!packages/*/docs/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/tsconfig*.json'
|
||||
- 'packages/base-driver/static/**/*'
|
||||
- 'packages/*/test/**'
|
||||
- 'packages/*/bin'
|
||||
- 'packages/**/*.ts'
|
||||
- 'packages/**/*.json'
|
||||
- 'packages/**/*.js'
|
||||
- 'package*.json'
|
||||
- 'lerna.json'
|
||||
- '.npmrc'
|
||||
- '.mocharc.js'
|
||||
- '.github/workflows/build.yml' # this file
|
||||
- '.eslintrc'
|
||||
- '.eslintignore'
|
||||
- '!**/sample-code/**'
|
||||
- '!packages/*/docs/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -7,19 +44,31 @@ permissions:
|
||||
jobs:
|
||||
test:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
node-version: [16.x, 18.x, 20.x]
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
# TODO: Enable below envs after all tests have been verified green
|
||||
# - macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install latest npm
|
||||
- name: Install latest npm (win)
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
# for some reason there's a bug in upgrading npm on win, have to install a specific version first
|
||||
# https://github.com/actions/setup-node/issues/623
|
||||
run: |
|
||||
npm install -g npm@8.3.1
|
||||
npm install -g npm@latest
|
||||
- name: Install latest npm (non-win)
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: npm install -g npm
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
@@ -33,33 +82,11 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
install-command: npm ci
|
||||
- name: ESLint
|
||||
run: npm run lint
|
||||
- name: Validate Renovate Config
|
||||
uses: rinchsan/renovate-config-validator@1ea1e8514f6a33fdd71c40b0a5fa3512b9e7b936 # tag=v0
|
||||
with:
|
||||
pattern: '{.renovaterc.json,renovate/default.json}'
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- name: Install dependencies (node)
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
install-command: npm ci
|
||||
- name: Install dependencies (python)
|
||||
run: pip install -r packages/docutils/requirements.txt
|
||||
- name: Build Docs
|
||||
run: |
|
||||
git config user.name 'Appium Bot'
|
||||
git config user.email 'appium@appium.io'
|
||||
npm run --workspace packages/appium build:docs
|
||||
run: npm run lint:ci
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -14,8 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# node-version: [14.x, 16.x, 18.x]
|
||||
node-version: [16.x, 18.x]
|
||||
node-version: [16.x, 18.x, 20.x]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Build Docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/typedoc.json'
|
||||
- '**/tsconfig*.json'
|
||||
- '**/package*.json'
|
||||
- '**/*mkdocs*.ya?ml'
|
||||
- 'packages/docutils/requirements.txt'
|
||||
- 'packages/*/README.md'
|
||||
- 'packages/*/docs/**'
|
||||
- 'packages/*/**/*.ts'
|
||||
- 'packages/*/**/*.js'
|
||||
- '.github/workflows/docs.yml' # this file
|
||||
- '!**/test/**'
|
||||
- '!**/sample-code/**'
|
||||
push:
|
||||
paths:
|
||||
- '**/typedoc.json'
|
||||
- '**/tsconfig*.json'
|
||||
- '**/package*.json'
|
||||
- '**/*mkdocs*.ya?ml'
|
||||
- 'packages/docutils/requirements.txt'
|
||||
- 'packages/*/README.md'
|
||||
- 'packages/*/docs/**'
|
||||
- 'packages/*/**/*.ts'
|
||||
- 'packages/*/**/*.js'
|
||||
- '.github/workflows/docs.yml' # this file
|
||||
- '!**/test/**'
|
||||
- '!**/sample-code/**'
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- name: Install dependencies (Node.js)
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
install-command: npm ci
|
||||
- name: Install dependencies (Python)
|
||||
run: pip install -r packages/docutils/requirements.txt
|
||||
- name: Configure Git User
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
- name: Build Docs
|
||||
run: npm run docs:preview
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run FOSSA Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
with:
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
name: 'Labeler'
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
# Setting up permissions in the workflow to limit the scope of what it can do. Optional!
|
||||
contents: read
|
||||
issues: read # change this if/when we want to label issues
|
||||
pull-requests: write
|
||||
statuses: read
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: Labeler
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# follows semantic versioning. Lock to different version: v1, v1.5, v1.5.0 or use a commit hash.
|
||||
- uses: fuxingloh/multi-labeler@6704db0bcba106d07482efabbc79d3092af74fa2 # v2
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}} # optional, default to '${{ github.token }}'
|
||||
@@ -1,15 +1,15 @@
|
||||
name: Disallow "On Hold" PRs
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened, labeled, unlabeled, synchronize]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
label-check:
|
||||
if: github.base_ref == 'master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: danielchabr/pr-labels-checker@e1d7e79aa0933af1728011705a30528a279874fd
|
||||
- name: Check for "On Hold" label
|
||||
uses: egmacke/action-check-label@0eb29f801fb482a95c9bd2dd22834ecc8f4e8af9 # v2
|
||||
with:
|
||||
id: check-labels
|
||||
hasNone: On Hold
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
label: On Hold
|
||||
state: absent
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Validate Renovate Configs
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/renovate.yml' # this file
|
||||
- '.renovaterc.json'
|
||||
- 'renovate/default.json'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/renovate.yml' # this file
|
||||
- '.renovaterc.json'
|
||||
- 'renovate/default.json'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
name: Check Renovate Configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- name: Validate Renovate Config
|
||||
uses: rinchsan/renovate-config-validator@1ea1e8514f6a33fdd71c40b0a5fa3512b9e7b936 # tag=v0
|
||||
with:
|
||||
pattern: '{.renovaterc.json,renovate/default.json}'
|
||||
@@ -59,7 +59,6 @@ appium.zip
|
||||
*.tsbuildinfo
|
||||
packages/*/LICENSE
|
||||
local_appium_home
|
||||
jsdoc-out
|
||||
*.tgz
|
||||
typedoc-docs
|
||||
packages/appium/docs/*/reference
|
||||
|
||||
+1
-2
@@ -7,6 +7,5 @@ module.exports = {
|
||||
// forbids use of .only() in CI
|
||||
forbidOnly: Boolean(process.env.CI),
|
||||
color: true,
|
||||
// increase default timeout for CI since it can be slow
|
||||
timeout: process.env.CI ? '5s' : '2s',
|
||||
timeout: '5s',
|
||||
};
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"stateful.runme"
|
||||
]
|
||||
}
|
||||
+17
-5
@@ -17,9 +17,12 @@ module.exports = (wallaby) => {
|
||||
},
|
||||
files: [
|
||||
'./packages/*/build/**/*',
|
||||
'./packages/*/lib/**/*.(j|t)s',
|
||||
'./packages/*/test/**/*helper*.(j|t)s',
|
||||
'./packages/*/test/**/*mock*.(j|t)s',
|
||||
'./packages/*/lib/**/*.js',
|
||||
'./packages/*/lib/**/*.ts',
|
||||
'./packages/*/test/**/*helper*.js',
|
||||
'./packages/*/test/**/*helper*.ts',
|
||||
'./packages/*/test/**/*mock*.js',
|
||||
'./packages/*/test/**/*mock*.ts',
|
||||
'./packages/*/package.json',
|
||||
'./packages/*/tsconfig*.json',
|
||||
'./packages/*/test/**/fixture?(s)/**/*',
|
||||
@@ -43,12 +46,21 @@ module.exports = (wallaby) => {
|
||||
instrument: false,
|
||||
pattern: './packages/typedoc-plugin-appium/lib/theme/resources/**',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/*/test/**/__snapshots__/**/*',
|
||||
},
|
||||
'!./packages/*/test/**/*-specs.js',
|
||||
'!./packages/*/test/**/*.e2e.spec.(j|t)s',
|
||||
'!./packages/*/test/**/*.e2e.spec.js',
|
||||
'!./packages/*/test/**/*.e2e.spec.ts',
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: ['./packages/*/test/unit/**/*.spec.(j|t)s', '!**/local_appium_home/**'],
|
||||
tests: [
|
||||
'./packages/*/test/unit/**/*.spec.js',
|
||||
'./packages/*/test/unit/**/*.spec.ts',
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
workers: {
|
||||
// restart: true,
|
||||
},
|
||||
|
||||
+36
-3917
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,8 @@ The current committers are (in addition to the TC members above):
|
||||
* [@boneskull](https://github.com/boneskull)
|
||||
* [@mwakizaka](https://github.com/mwakizaka)
|
||||
* [@rerorero](https://github.com/rerorero)
|
||||
* [@tomriddly](https://github.com/tomriddly)
|
||||
* [@eglitise](https://github.com/eglitise)
|
||||
|
||||
### Contributors
|
||||
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. Initially created to automate iOS and Android mobile
|
||||
applications Appium has grown to a full-featured platform that provides [WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for the whole set of different mobile and desktop platforms.
|
||||
See [Drivers Maintained By The Appium Team](#drivers-maintained-by-the-appium-team)
|
||||
and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sections below for more details.
|
||||
See the full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers for more details.
|
||||
|
||||
: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://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) 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.io/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/latest/).
|
||||
progress. Currently, it can be found [here](https://appium.io/docs/en/latest/).
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -70,8 +71,8 @@ appium driver uninstall xcuitest
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.github.io/appium/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.github.io/appium/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
### Plugins
|
||||
@@ -103,8 +104,8 @@ appium server --use-plugins=device-farm,images
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.github.io/appium/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.github.io/appium/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
|
||||
@@ -168,11 +169,11 @@ Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
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 [Intro to Appium](https://appium.github.io/appium/docs/en/latest/intro/).
|
||||
about, please read our [Intro to Appium](https://appium.io/docs/en/latest/intro/).
|
||||
|
||||
### Quickstart
|
||||
|
||||
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
|
||||
Check out our [Quickstart](https://appium.io/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,7 +181,7 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://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/packages/appium/docs) as well.
|
||||
|
||||
@@ -195,7 +196,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
|
||||
+10
-5
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/lerna",
|
||||
"changelogPreset": "conventional-changelog-conventionalcommits",
|
||||
"command": {
|
||||
"add": {
|
||||
"exact": true
|
||||
},
|
||||
"publish": {
|
||||
"message": "chore: publish"
|
||||
},
|
||||
"run": {
|
||||
"concurrency": 8
|
||||
},
|
||||
@@ -9,13 +17,10 @@
|
||||
"version": {
|
||||
"allowBranch": "master"
|
||||
}
|
||||
},
|
||||
"add": {
|
||||
"exact": true
|
||||
}
|
||||
},
|
||||
"ignoreChanges": ["**/test/**", "**/*.md"],
|
||||
"version": "independent",
|
||||
"useNx": false,
|
||||
"useWorkspaces": true,
|
||||
"useNx": false
|
||||
"version": "independent"
|
||||
}
|
||||
|
||||
Generated
+22085
-2228
File diff suppressed because it is too large
Load Diff
+56
-41
@@ -27,43 +27,54 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"build:all": "run-s build build:packages",
|
||||
"build:packages": "lerna run build",
|
||||
"clean": "run-s clean:artifacts clean:workspaces",
|
||||
"clean:artifacts": "run-p \"clean:artifacts:*\"",
|
||||
"clean:artifacts:distfiles": "npx rimraf \"./packages/*/build\" \"./packages/*/.tsconfig.tsbuildinfo\"",
|
||||
"clean:artifacts:packages": "lerna run clean || true",
|
||||
"clean:workspaces": "lerna clean -y",
|
||||
"build": "run-s build:*",
|
||||
"build:compile": "tsc -b",
|
||||
"build:workspaces": "lerna run build",
|
||||
"ci": "npm ci",
|
||||
"dev": "run-s \"build -- --watch\"",
|
||||
"clean": "run-s clean:distfiles clean:workspaces",
|
||||
"clean:dangerously": "run-s clean:workspaces clean:force",
|
||||
"clean:deps": "lerna clean --yes",
|
||||
"clean:distfiles": "npx rimraf --glob \"./packages/*/build\" \"./packages/*/**/.tsconfig.tsbuildinfo\"; npx nx reset",
|
||||
"clean:interactive": "git clean -d -x -i",
|
||||
"clean:force": "git clean -d -x -f",
|
||||
"clean:workspaces": "lerna run clean || true",
|
||||
"dev": "run-s build:compile \"build:workspaces\" \"build:compile -- --watch\"",
|
||||
"dev:docs": "npm run -w packages/appium dev:docs",
|
||||
"docs": "npm run -w packages/appium build:docs",
|
||||
"docs:preview": "npm run -w packages/appium build:docs:preview",
|
||||
"doctor": "node ./packages/doctor",
|
||||
"husky-install": "husky install",
|
||||
"generate-docs": "npm run --workspace=packages/appium generate-docs",
|
||||
"prepare": "run-p husky-install build:all",
|
||||
"husky-install": "husky install",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:ci": "eslint --quiet .",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:staged": "lint-staged",
|
||||
"prepare": "run-p husky-install rebuild",
|
||||
"prepublishOnly": "run-s rebuild",
|
||||
"reinstall": "run-s clean ci",
|
||||
"rebuild": "run-s clean:artifacts build:all",
|
||||
"preversion": "run-s rebuild",
|
||||
"version": "run-s sync-pkgs stage-synced-pkgs",
|
||||
"rebuild": "run-s clean build",
|
||||
"reinstall": "run-s clean clean:deps ci",
|
||||
"reinstall:angry": "npm run clean:dangerously && npm ci",
|
||||
"release": "lerna publish",
|
||||
"stage-synced-pkgs": "git add -A packages/appium/README.md \"packages/*/package.json\"",
|
||||
"start": "appium",
|
||||
"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\" --packages=\"!packages/typedoc-plugin-appium\" --packages=\"!packages/eslint-config-appium-ts\" ",
|
||||
"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": "run-s test:quick",
|
||||
"test:ci": "run-s test:smoke test:unit test:types test:e2e",
|
||||
"test:e2e": "lerna run --concurrency=2 test:e2e",
|
||||
"test:quick": "run-s lint test:unit test:types",
|
||||
"test:slow": "run-s test:quick test:smoke test:e2e",
|
||||
"test:smoke": "smoker --all test:smoke",
|
||||
"test:types": "lerna run test:types",
|
||||
"test:unit": "lerna run test",
|
||||
"typedoc": "typedoc --logLevel Verbose",
|
||||
"upload": "gulp github-upload",
|
||||
"preversion": "run-s sync-pkgs stage-synced-pkgs",
|
||||
"prepack": "npm run rebuild",
|
||||
"zip": "zip -qr ./appium.zip ./packages/appium",
|
||||
"zip-and-upload": "run-s zip upload"
|
||||
},
|
||||
@@ -74,30 +85,32 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"@commitlint/cli": "17.4.4",
|
||||
"@commitlint/config-conventional": "17.4.4",
|
||||
"@commitlint/cli": "17.6.5",
|
||||
"@commitlint/config-conventional": "17.6.5",
|
||||
"@tsconfig/node14": "1.0.3",
|
||||
"@types/chai": "4.3.4",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/diff": "5.0.2",
|
||||
"@types/diff": "5.0.3",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.14.6",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/node": "18.16.16",
|
||||
"@types/semver": "7.5.0",
|
||||
"@types/sinon": "10.0.15",
|
||||
"@types/sinon-chai": "3.2.9",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/ws": "8.5.4",
|
||||
"@types/yargs": "17.0.22",
|
||||
"@typescript-eslint/eslint-plugin": "5.54.1",
|
||||
"@typescript-eslint/parser": "5.54.1",
|
||||
"@types/yargs": "17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.8",
|
||||
"@typescript-eslint/parser": "5.59.8",
|
||||
"asyncbox": "2.9.4",
|
||||
"chai": "4.3.7",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"conventional-changelog-conventionalcommits": "5.0.0",
|
||||
"cpy-cli": "4.2.0",
|
||||
"eslint": "8.35.0",
|
||||
"eslint-config-prettier": "8.7.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
@@ -106,31 +119,33 @@
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.7",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "12.0.0",
|
||||
"lerna": "6.5.1",
|
||||
"lint-staged": "13.1.2",
|
||||
"json-schema-to-typescript": "13.0.1",
|
||||
"lerna": "6.6.2",
|
||||
"lint-staged": "13.2.2",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "2.0.1",
|
||||
"midnight-smoker": "2.0.4",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-chai-jest-snapshot": "1.1.4",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.8.4",
|
||||
"prettier": "2.8.8",
|
||||
"rewiremock": "3.14.5",
|
||||
"rimraf": "3.0.2",
|
||||
"rimraf": "5.0.1",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "15.0.1",
|
||||
"sinon": "15.1.0",
|
||||
"sinon-chai": "3.7.0",
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"typedoc": "0.23.26",
|
||||
"tsd": "0.28.1",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.9.5",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.30.2",
|
||||
"ws": "8.12.1",
|
||||
"webdriverio": "7.31.1",
|
||||
"ws": "8.13.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,173 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.0-rc.1](https://github.com/appium/appium/compare/appium@2.0.0-beta.71...appium@2.0.0-rc.1) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Allow to provide ipv6 addresses and handle broadcast addresses in logs ([#18674](https://github.com/appium/appium/issues/18674)) ([8f63dab](https://github.com/appium/appium/commit/8f63dabb5dca882e522026f243e3445f5418874b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** update dependency winston to v3.9.0 ([a98d9c3](https://github.com/appium/appium/commit/a98d9c33c761af6870411721cc0d6c4491b43940))
|
||||
* **docutils:** update dependency yaml to v2.3.0 ([325d237](https://github.com/appium/appium/commit/325d2372078ee01e8fb58613ffd13417f2527151))
|
||||
* **docutils:** update dependency yaml to v2.3.1 ([1a2260b](https://github.com/appium/appium/commit/1a2260bb15d75e4f4c23551dc61529108a7b0d88))
|
||||
* ensure logServerAddress gets an actual URL ([#18754](https://github.com/appium/appium/issues/18754)) ([901ac71](https://github.com/appium/appium/commit/901ac711c4e2207f30d93ad177a332b7d6f23c38))
|
||||
* **support:** update dependency @types/semver to v7.5.0 ([528dcfe](https://github.com/appium/appium/commit/528dcfe324e75e48471cdb694448c2a09ec2d828))
|
||||
* **support:** update dependency semver to v7.5.1 ([5a55509](https://github.com/appium/appium/commit/5a555090c5322318f845415d743ee5c645fb94a0))
|
||||
* **types:** fix broken appium config schema types, temporarily ([fd2c72f](https://github.com/appium/appium/commit/fd2c72fd886ecaaf0f6588a328ff8a268a54fb55))
|
||||
* **types:** update dependency type-fest to v3.11.0 ([19277f6](https://github.com/appium/appium/commit/19277f6e14a56e52b4669d633e148ad4a3da2c7a))
|
||||
* **types:** update dependency type-fest to v3.11.1 ([56499eb](https://github.com/appium/appium/commit/56499eb997b551739bed628f057de7987674ea7f))
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.71](https://github.com/appium/appium/compare/appium@2.0.0-beta.70...appium@2.0.0-beta.71) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.70](https://github.com/appium/appium/compare/appium@2.0.0-beta.69...appium@2.0.0-beta.70) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.69](https://github.com/appium/appium/compare/appium@2.0.0-beta.68...appium@2.0.0-beta.69) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.68](https://github.com/appium/appium/compare/appium@2.0.0-beta.67...appium@2.0.0-beta.68) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.67](https://github.com/appium/appium/compare/appium@2.0.0-beta.66...appium@2.0.0-beta.67) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency yaml to v2.2.2 ([ddadd69](https://github.com/appium/appium/commit/ddadd6977d76a8a5ce1559d2a4ff4a31b58fb42f))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.31.1 ([6499eea](https://github.com/appium/appium/commit/6499eea0af6aea1bfb69b1e36c4c445e3ab05b82))
|
||||
* **support:** update dependency axios to v1.3.6 ([6692227](https://github.com/appium/appium/commit/66922279b7742a08613f472585a4a1cb70f80683))
|
||||
* **support:** update dependency axios to v1.4.0 ([91a6bc5](https://github.com/appium/appium/commit/91a6bc5925ab8ffc4ab6d05883900f7d186e49a9))
|
||||
* **support:** update dependency semver to v7.5.0 ([c568523](https://github.com/appium/appium/commit/c568523e017a8b52bdf5132a48bbfde791de704f))
|
||||
* **types:** update dependency type-fest to v3.10.0 ([3c4d3ac](https://github.com/appium/appium/commit/3c4d3acc09d2ca1ed74dc77c18c62482e4c70239))
|
||||
* **types:** update dependency type-fest to v3.9.0 ([94a207f](https://github.com/appium/appium/commit/94a207fc9718068f3657c51cc8be0ef682f16b11))
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.66](https://github.com/appium/appium/compare/appium@2.0.0-beta.65...appium@2.0.0-beta.66) (2023-04-20)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.65](https://github.com/appium/appium/compare/appium@2.0.0-beta.64...appium@2.0.0-beta.65) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.64](https://github.com/appium/appium/compare/appium@2.0.0-beta.63...appium@2.0.0-beta.64) (2023-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency semver to v7.4.0 ([6ae86bd](https://github.com/appium/appium/commit/6ae86bd5c7be879a4d8852951d73eb2f68128df3))
|
||||
|
||||
|
||||
|
||||
## [2.0.0-beta.63](https://github.com/appium/appium/compare/appium@2.0.0-beta.62...appium@2.0.0-beta.63) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.62](https://github.com/appium/appium/compare/appium@2.0.0-beta.61...appium@2.0.0-beta.62) (2023-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency axios to v1.3.5 ([6cf1480](https://github.com/appium/appium/commit/6cf14802b70a462beffc12a1134476596060c005))
|
||||
* **types:** update dependency type-fest to v3.8.0 ([d6c42e9](https://github.com/appium/appium/commit/d6c42e99c08efce0b34796d5982ce379fca044d3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** make "ls" alias of "list" ([5ab2fea](https://github.com/appium/appium/commit/5ab2fea5768ef8c67270d49c920b9515ca1ff1e4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.61](https://github.com/appium/appium/compare/appium@2.0.0-beta.60...appium@2.0.0-beta.61) (2023-04-05)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.60](https://github.com/appium/appium/compare/appium@2.0.0-beta.59...appium@2.0.0-beta.60) (2023-04-04)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.59](https://github.com/appium/appium/compare/appium@2.0.0-beta.58...appium@2.0.0-beta.59) (2023-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** update dependency package-changed to v3 ([bbcaec9](https://github.com/appium/appium/commit/bbcaec9c05e93d522ae72e30368cecc876e16db7))
|
||||
* **types:** update dependency type-fest to v3.7.2 ([5580539](https://github.com/appium/appium/commit/55805390b5a0c6aa718bb357b30f66651f3db281))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.58](https://github.com/appium/appium/compare/appium@2.0.0-beta.57...appium@2.0.0-beta.58) (2023-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,types,base-driver,fake-driver,driver-test-support:** normalize constraint defaults ([3c9fa7b](https://github.com/appium/appium/commit/3c9fa7ba73b639e610e1f3d41d239a9402845b4c))
|
||||
* **appium:** remove longjohn ([0c52b50](https://github.com/appium/appium/commit/0c52b50e6595a16287d0bb71960fb2e08278de6f)), closes [#18357](https://github.com/appium/appium/issues/18357)
|
||||
* backwards-compatible fixes for TS v5.x ([4974403](https://github.com/appium/appium/commit/49744036619ecc239e0e6255a13d38cafd709920))
|
||||
* **types:** update dependency type-fest to v3.7.0 ([6912fa1](https://github.com/appium/appium/commit/6912fa14f2a7d338f17e1bed060e959de7aba1d6))
|
||||
* **types:** update dependency type-fest to v3.7.1 ([bc860c7](https://github.com/appium/appium/commit/bc860c733a73760f0c42cbfb384e04d50c376d5e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** improvements to ext commands when running extension in development ([d9b47bc](https://github.com/appium/appium/commit/d9b47bc86434c50382221397cf39c10e661c1ea9)), closes [#18277](https://github.com/appium/appium/issues/18277)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.57](https://github.com/appium/appium/compare/appium@2.0.0-beta.56...appium@2.0.0-beta.57) (2023-03-08)
|
||||
|
||||
|
||||
|
||||
+13
-12
@@ -12,16 +12,17 @@
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. Initially created to automate iOS and Android mobile
|
||||
applications Appium has grown to a full-featured platform that provides [WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for the whole set of different mobile and desktop platforms.
|
||||
See [Drivers Maintained By The Appium Team](#drivers-maintained-by-the-appium-team)
|
||||
and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sections below for more details.
|
||||
See the full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers for more details.
|
||||
|
||||
: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://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) 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.io/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/latest/).
|
||||
progress. Currently, it can be found [here](https://appium.io/docs/en/latest/).
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -70,8 +71,8 @@ appium driver uninstall xcuitest
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.github.io/appium/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.github.io/appium/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
### Plugins
|
||||
@@ -103,8 +104,8 @@ appium server --use-plugins=device-farm,images
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.github.io/appium/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.github.io/appium/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
|
||||
@@ -168,11 +169,11 @@ Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
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 [Intro to Appium](https://appium.github.io/appium/docs/en/latest/intro/).
|
||||
about, please read our [Intro to Appium](https://appium.io/docs/en/latest/intro/).
|
||||
|
||||
### Quickstart
|
||||
|
||||
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
|
||||
Check out our [Quickstart](https://appium.io/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,7 +181,7 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://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/packages/appium/docs) as well.
|
||||
|
||||
@@ -195,7 +196,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ extra:
|
||||
link: https://twitter.com/AppiumDevs
|
||||
alternate:
|
||||
- name: English
|
||||
link: /appium/docs/en/
|
||||
link: /docs/en/
|
||||
lang: en
|
||||
- name: 日本
|
||||
link: /appium/docs/ja/
|
||||
link: /docs/ja/
|
||||
lang: ja
|
||||
- name: 中文简体
|
||||
link: /appium/docs/zh/
|
||||
link: /docs/zh/
|
||||
lang: zh
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Contributing to Appium
|
||||
cwd: ../../../../../
|
||||
---
|
||||
|
||||
This section of Appium's documentation is for those who are interested in contributing to Appium's
|
||||
@@ -16,8 +17,105 @@ already.
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
TODO
|
||||
The project offers a variety of ways to contribute, e.g.:
|
||||
|
||||
- contributing code
|
||||
- improving documentation
|
||||
- creating educational content (blog posts, tutorials, videos, etc.)
|
||||
- spreading the good word about the project (e.g. via Twitter)
|
||||
- filing bugs if you discover them while using Appium
|
||||
- making feature requests if you are missing something in the project or help triaging bugs
|
||||
- supporting users in the [Appium forum](https://discuss.appium.io/)
|
||||
|
||||
## The Appium Development Process
|
||||
|
||||
TODO
|
||||
To contribute to the Appium code base make sure to check out the Git repository.
|
||||
|
||||
!!! info
|
||||
|
||||
If you are VS Code user you can easily check out the project using [Runme](https://runme.dev/api/runme?repository=https%3A%2F%2Fgithub.com%2Fappium%2Fappium.git&fileToOpen=packages%2Fappium%2Fdocs%2Fen%2Fcontributing%2Findex.md).
|
||||
|
||||
It is advised to [fork](https://github.com/appium/appium/fork) before cloning it to your system.
|
||||
|
||||
```sh { name=clone }
|
||||
export GITHUB_USERNAME=<your-username>
|
||||
git clone git@github.com:$GITHUB_USERNAME/appium.git
|
||||
```
|
||||
|
||||
After cloning you can go ahead and install the project dependencies:
|
||||
|
||||
```sh { name=install }
|
||||
npm install
|
||||
```
|
||||
|
||||
From here on there are several things you can do.
|
||||
|
||||
### Watch Files
|
||||
|
||||
When developing Appium code we have to watch all JavaScript and TypeScript files to re-compile them after every change. You can run this watch process via:
|
||||
|
||||
```sh { name=watch, background=true }
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Start Appium in Dev-Mode
|
||||
|
||||
To test your changes you can run Appium in dev mode via:
|
||||
|
||||
```sh { name=start }
|
||||
npm start
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
The project maintains a set of different test variations you can run to verify the quality of the code.
|
||||
|
||||
#### Linting
|
||||
|
||||
Appium uses EsLint for static code analysis and linting. You can run these checks via:
|
||||
|
||||
```sh { name=test-linting }
|
||||
npm run lint
|
||||
```
|
||||
|
||||
#### Unit
|
||||
|
||||
Run via:
|
||||
|
||||
```sh { name=test-unit }
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
You can also run tests for specific workspaces, e.g.:
|
||||
|
||||
```sh { name=test-workspace }
|
||||
export APPIUM_WORKSPACE=@appium/doctor
|
||||
npm run test:unit -w $APPIUM_WORKSPACE
|
||||
```
|
||||
|
||||
#### Smoke and e2e Tests
|
||||
|
||||
Run via:
|
||||
|
||||
```sh { name=test-slow }
|
||||
npm run test:slow
|
||||
```
|
||||
|
||||
### Deploy Docs Locally
|
||||
|
||||
Our documentation system uses [MKDocs](https://www.mkdocs.org/) and therefor requires [Python](https://www.python.org/) to be installed on your system. You can run the docs by:
|
||||
|
||||
```sh
|
||||
# installing needed Python dependencies
|
||||
pip install -r packages/docutils/requirements.txt
|
||||
# build the project
|
||||
npm run build
|
||||
# run dev server
|
||||
npm run dev:docs
|
||||
```
|
||||
|
||||
You should be able to view the page at `http://127.0.0.1:8000/docs/en`:
|
||||
|
||||
```sh
|
||||
open http://127.0.0.1:8000/docs/en
|
||||
```
|
||||
|
||||
@@ -2,4 +2,241 @@
|
||||
title: Building Docs for Appium Extensions
|
||||
---
|
||||
|
||||
TODO
|
||||
Once you've [built a driver](./build-drivers.md) or [built a plugin](./build-plugins.md) for Appium,
|
||||
you will hopefully want to document how that extension works for your users. The most basic way of
|
||||
doing this is to write up a quick `README.md` and keep it in the root of your project's repository.
|
||||
However, this can involve a lot of duplication of effort, especially when documenting things like
|
||||
Appium commands.
|
||||
|
||||
Let's say your driver implements ~25 of the standard WebDriver protocol commands. You could write
|
||||
up a description of these commands, how they map to the protocol, what parameters they take, and
|
||||
what behaviour will result on your particular platform. But this information is already more or
|
||||
less stored in your code, as the command implementation (and any docstrings or comments). Having
|
||||
this information in two places creates an opportunity for the docs to get out of sync with the
|
||||
reality of the code. Wouldn't it be nice to generate command reference documentation straight from
|
||||
the code?
|
||||
|
||||
Another problem with the basic single file `README.md` approach is that many extensions might want a
|
||||
whole set of documents including longer prose guides (like this one). It might be nice to have code
|
||||
examples where you can toggle between different programming languages. It might be nice to be able
|
||||
to add a project-specific logo. And so on.
|
||||
|
||||
The Appium project has built tools to do all these things, and we've packaged up these tools so our
|
||||
ecosystem developers building drivers and plugins can _also_ use them. The best way to get going
|
||||
with these tools is probably to look at an existing Appium driver repo to see how it's done, for
|
||||
example the [XCUITest driver repo](https://github.com/appium/appium-xcuitest-driver). But this guide
|
||||
will outline the basic approach.
|
||||
|
||||
### Conceptual architecture
|
||||
|
||||
Appium settled on [MkDocs](https://www.mkdocs.org/) as a Markdown-based documentation site
|
||||
generator. It uses a Python toolchain (and not Node.js), but it turned out to be the best option
|
||||
for our purposes. You can adjust this, but by default Appium's utilities also assume that you'll be
|
||||
using the [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) theme/extension for
|
||||
MkDocs.
|
||||
|
||||
From here, building a basic docs site is as easy as collecting your Markdown files together and
|
||||
creating a sort of manifest file defining how you want them to be organized.
|
||||
|
||||
The other main piece is automatic documentation generation from your code files. Appium maintains
|
||||
a plugin for [TypeDoc](https://typedoc.org/). This plugin is incorporated into our doc utility.
|
||||
When you give it an entrypoint for you driver or plugin, it will scan and parse all your code files
|
||||
looking for Appium command implementations. A set of Markdown reference files will be generated for these
|
||||
commands, which will then be included in your docs site.
|
||||
|
||||
> Note: Implementing an extension in TypeScript is _not_ a requirement for generating documentation,
|
||||
> but for automated doc generation to work, you will need to apply TypeScript-supported JSDoc-style
|
||||
> docstrings to your JS codebase. See ["JS Projects Utilizing TypeScript"](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) for more information.
|
||||
|
||||
In order to make different versions of your docs available (one for each minor release of your
|
||||
extension, typically), we also bundle [Mike](https://github.com/jimporter/mike).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To take advantage of Appium's documentation utilities, you'll need to install:
|
||||
|
||||
- [Python v3+](https://www.python.org/downloads/)
|
||||
- [pip](https://pip.pypa.io/en/stable/installation/) (this may be installed automatically with Python)
|
||||
- The `@appium/docutils` package:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @appium/docutils
|
||||
```
|
||||
|
||||
### Initializing an Extension for Building Docs
|
||||
|
||||
To prepare your extension for generating documentation, run the following command:
|
||||
|
||||
```bash
|
||||
npx appium-docs init --entry-point <my-entry-point.js>
|
||||
```
|
||||
|
||||
...where `<my-entry-point.js>` is the **source entry point** to your extension. If you _are not_ transpiling your code via TypeScript, Babel, etc., this is typically the same as the value of the `main` property in `package.json`. If you _are_ transpiling, this is typically different. For example, your `main` property may be `dist/index.js`, but your **source entry point** is `src/index.ts`.
|
||||
|
||||
This will:
|
||||
|
||||
1. Create a `tsconfig.json` if one does not already exist. This is necessary even if your extension is not written in TypeScript.
|
||||
2. Create a `typedoc.json` with the necessary configuration for TypeDoc.
|
||||
3. Create a `mkdocs.yml` with the necessary configuration for MkDocs.
|
||||
4. Modify your `package.json` to add a `typedoc.entryPoint` property with a value of your entry point (as specified above).
|
||||
|
||||
### Documenting Your Extension
|
||||
|
||||
At this point, you can begin documenting your extension. You don't need to do this all at once, but you should make the following changes, at minimum.
|
||||
|
||||
#### `newMethodMap` and `executeMethodMap`
|
||||
|
||||
The static properties `newMethodMap` and `executeMethodMap` may be present on your extension's main class. If they are not, then you can skip to the next section. If they are, you will need to make the following changes, depending on your extension's language.
|
||||
|
||||
##### JavaScript
|
||||
|
||||
```js
|
||||
// note: this is equivalent to the TypeScript example below
|
||||
|
||||
class MyExtension {
|
||||
static newMethodMap = /** @type {const} */({
|
||||
// ...
|
||||
});
|
||||
|
||||
static executeMethodMap = /** @type {const} */({
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##### TypeScript
|
||||
|
||||
```ts
|
||||
class MyExtension {
|
||||
static newMethodMap = {
|
||||
// ...
|
||||
} as const;
|
||||
|
||||
static executeMethodMap = {
|
||||
// ...
|
||||
} as const;
|
||||
}
|
||||
```
|
||||
|
||||
#### Driver Constraints
|
||||
|
||||
> Note: Plugin authors can skip this section.
|
||||
|
||||
Your driver may have a property `desiredCapConstraints`. It should also follow the same pattern as `newMethodMap` and `executeMethodMap` above. For example:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
desiredCapConstraints = /** @type {const} */({
|
||||
myCapability: {
|
||||
presence: true,
|
||||
isString: true
|
||||
},
|
||||
myOtherCap: {
|
||||
isBoolean: true
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
(For extensions written in TypeScript, use `as const` as before.)
|
||||
|
||||
#### Commands
|
||||
|
||||
The documentation for a command, as defined in your extension, comes from multiple places. These sources are then combined as needed into the final output.
|
||||
|
||||
In Appium, new commands are defined in `newMethodMap` and execute methods are defined in `executeMethodMap`. **The value of these properties are used to build your documentation.** In particular, parameter names and optional/required status _override_ whatever method implementation does. So for example, if your `newMethodMap` contains:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
static newMethodMap = /** @type {const} */({
|
||||
'/session/:sessionId/myThing': {
|
||||
/**
|
||||
* Does my thing
|
||||
*/
|
||||
GET: {command: 'doMyThing', payloadParams: {required: ['a', 'b']}},
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
...and your `doMyThing` method implementation looks like this:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
/**
|
||||
* Doesn't do my thing
|
||||
* @param {any} a - Whatever
|
||||
* @param {number} d - Some number
|
||||
* @param {boolean} c - Some boolean
|
||||
* @returns {Promise<boolean>} Some other boolean
|
||||
*/
|
||||
async doMyThing(a, d = 1, c = false) {
|
||||
// ...
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The documentation will show that the `doMyThing` method accepts required parameters `a` and `b`. Even though `b` is named `d` in the implementation--and it's optional--it will be ignored. Likewise, since the definition in `newMethodMap` knows nothing about `c`, it too is ignored.
|
||||
|
||||
In addition, the _description_ from the docstring in `newMethodMap` overrides the description in the method implementation; it will describe the command `doMyThing` as "Does my thing".
|
||||
|
||||
The `@param` tags and `@returns` tag from the method's docstring provide information about the expected and returned types, as well as a description of each. This is not expressible via `newMethodMap` and `executeMethodMap` alone; it provides more information for your extension's users.
|
||||
|
||||
> Note for TypeScript users: while the _types_ will already be present, providing `@param` and `@returns` tags is still useful for providing descriptions.
|
||||
|
||||
All commands must be `async`, so they will return `Promise<T>` where type `T` is the type of whatever the `Promise` fulfills with. In the generated documentation, the `Promise` is ignored, and only `T` is reported. So for `doMyThing`, the return type of the command, as output in the documentation, will be `boolean`.
|
||||
|
||||
This is because while extensions must be written in JavaScript, we're documenting an API which can be called from any language; that language likely won't have a concept of a `Promise`. Likewise, `undefined` or `void` types will be output as `null` (since that is a concept that translates well to multiple languages).
|
||||
|
||||
#### Optional: `README.md`
|
||||
|
||||
If you have a `README.md`, it will be pulled in to the generated docs site automatically. This behavior can be disabled by adding the following to `typedoc.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"readme": "none"
|
||||
}
|
||||
```
|
||||
|
||||
#### Next Steps
|
||||
|
||||
Appium provides type definitions for extension authors; these are available via Appium itself and the [`@appium/types`](https://npm.im/@appium/types) package.
|
||||
|
||||
Of note, drivers should both extend `BaseDriver` _and_ implement the `ExternalDriver` interface exported by `@appium/types`. This will help ensure that your driver's implementation is correct and usable by different clients.
|
||||
|
||||
You're encouraged to look at the official test/example extensions, [`@appium/fake-driver`](https://npm.im/@appium/fake-driver) and [`@appium/fake-plugin`](https://npm.im/@appium/fake-plugin) for canonical examples of how to use these types.
|
||||
|
||||
#### Resources
|
||||
|
||||
How to type more complex return values or parameters is beyond the scope of this document. For more information, see:
|
||||
|
||||
- For extensions written in JS, the [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html)
|
||||
- For tags in addition to what TS natively recognizes, see the [TypeDoc documentation](https://typedoc.org/guides/doccomments/)
|
||||
|
||||
Likewise, refer to the [MkDocs documentation](https://www.mkdocs.org/user-guide/writing-your-docs/) for further information on how to customize your MkDocs output.
|
||||
|
||||
### Usage
|
||||
|
||||
At this point, you can use the `appium-docs` CLI tool. Run this tool with no arguments to get the
|
||||
full help output and see all the available subcommands and parameters. Here are a few usage
|
||||
examples:
|
||||
|
||||
```bash
|
||||
# Generate reference and build the mkdocs site into the site dir
|
||||
npx appium-docs build
|
||||
|
||||
# Same as build, but host the docs on a local dev server
|
||||
# and watch for changes and rebuild when files change
|
||||
npx appium-docs build --serve
|
||||
|
||||
# Build the docs and deploy them with mike versioning to the docs-site branch
|
||||
# using the included commit message and rebase strategy on the branch.
|
||||
# This is particularly useful for pushing content to a GitHub pages branch!
|
||||
npx appium-docs build \
|
||||
--deploy \
|
||||
-b docs-site \
|
||||
-m 'docs: auto-build docs for appium-xcuitest-driver@%s' \
|
||||
--rebase
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ These drivers are not maintained by the Appium team and can be used to target ad
|
||||
|
||||
## Clients
|
||||
|
||||
You need a clients to write and run Appium scripts. To learn more about clients, read our [Client
|
||||
You need clients to write and run Appium scripts. To learn more about clients, read our [Client
|
||||
Intro](../intro/clients.md). Here is the list of known Appium clients. You'll want to become very
|
||||
familiar with your client documentation (as well as the documentation of any Selenium client that
|
||||
the Appium client depends on) since that is what you will use as your primary interface to Appium.
|
||||
@@ -64,7 +64,7 @@ the Appium client depends on) since that is what you will use as your primary in
|
||||
|[Appium Java client](https://github.com/appium/java-client)|Java|Appium Team|
|
||||
|[Appium Python client](https://github.com/appium/python-client)|Python|Appium Team|
|
||||
|[Appium Ruby Core client](https://github.com/appium/ruby_lib_core) (Recommended)<br>[Appium Ruby client](https://github.com/appium/ruby_lib)|Ruby|Appium Team|
|
||||
|[WebDriverIO](https://webdriver.io)|Node.js|Community|
|
||||
|[WebDriverIO](https://webdriver.io)<br>[Nightwatch](https://nightwatchjs.org/)|Node.js|Community|
|
||||
|[Appium .NET client](https://github.com/appium/dotnet-client)|C#|Appium Team*|
|
||||
|[RobotFramework](https://github.com/serhatbolsu/robotframework-appiumlibrary)|DSL|Community|
|
||||
|
||||
@@ -97,3 +97,11 @@ are currently maintained by the Appium Team:
|
||||
If you maintain an Appium plugin that you would like to be listed in the Appium docs, feel free
|
||||
to make a PR to add it to this section with a link to the documentation for the plugin.
|
||||
|
||||
## Helper tools
|
||||
|
||||
The following tools might also be useful for Appium users:
|
||||
|
||||
|Name|Description|Supported By|
|
||||
|---|---|---|
|
||||
|[appium-installer](https://github.com/AppiumTestDistribution/appium-installer)|Help set up an Appium environment for Android and iOS|`@AppiumTestDistribution`|
|
||||
|
||||
|
||||
@@ -80,7 +80,20 @@ APPIUM_SKIP_CHROMEDRIVER_INSTALL=1 appium driver install uiautomator2
|
||||
|
||||
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`.
|
||||
With Appium 2.x, all driver- and platform-specific CLI params have been moved to the drivers themselves. To access the corresponding functionality, you'll need to refer to the driver/plugin documentation. In some cases, the extension will continue to expose CLI parameters. For example, the XCUITest driver used to expose a parameter `--webdriveragent-port`. Now, to access this parameter, it should be prefixed with `driver-xcuitest`, to differentiate it from parameters other drivers might also expose. To use this parameter, you thus need to start Appium with something like:
|
||||
|
||||
```bash
|
||||
appium --driver-xcuitest-webdriveragent-port=5000
|
||||
```
|
||||
|
||||
Some drivers have done away with CLI args entirely in favour of default capabilities instead. With
|
||||
the above-mentioned `--chromedriver-executable` param for example, you now need to take advantage
|
||||
of the `appium:chromedriverExecutable` capability supported by the UiAutomator2 driver. To set this
|
||||
capability from the command line, do the following:
|
||||
|
||||
```bash
|
||||
appium --default-capabilities '{"appium:chromedriverExecutable": "/path/to/chromedriver"}'
|
||||
```
|
||||
|
||||
### :bangbang: Driver-specific automation commands
|
||||
|
||||
@@ -231,10 +244,6 @@ any Appium command, or even adjust the way the underlying Appium HTTP server its
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@ JavaScript, Python, and Java, though [Appium clients](../ecosystem/index.md#clie
|
||||
Before getting started, make sure your system satisfies the
|
||||
[requirements](../intro/requirements.md) for running the Appium server. Additional requirements
|
||||
will be discussed in conjunction with installing the UiAutomator2 driver. The guide also assumes
|
||||
you're basic command line proficiency on your platform, for example being able to run commands, set
|
||||
you have basic command line proficiency on your platform, for example being able to run commands, set
|
||||
and persist environment variables, etc...
|
||||
|
||||
Now you're ready to get started! So head on over to [Installing Appium](./install.md).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/en
|
||||
site_url: https://appium.io/docs/en
|
||||
edit_uri: edit/master/packages/appium/docs/en
|
||||
site_description: The Appium automation project documentation
|
||||
docs_dir: en
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/ja
|
||||
site_url: https://appium.io/docs/ja
|
||||
edit_uri: edit/master/packages/appium/docs/ja
|
||||
site_description: The Appium automation project documentation
|
||||
docs_dir: ja
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/zh
|
||||
site_url: https://appium.io/docs/zh
|
||||
edit_uri: edit/master/packages/appium/docs/zh
|
||||
site_description: The Appium automation project documentation
|
||||
docs_dir: zh
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable promise/prefer-await-to-then */
|
||||
|
||||
const {buildReferenceDocs, deploy, updateNav} = require('@appium/docutils');
|
||||
const {buildReferenceDocs, deploy, updateNav, buildSite} = require('@appium/docutils');
|
||||
const {
|
||||
log,
|
||||
LANGS,
|
||||
@@ -28,9 +28,9 @@ const {version} = require('../../package.json');
|
||||
const branch = process.env.APPIUM_DOCS_BRANCH ?? DOCS_BRANCH;
|
||||
const basePrefix = process.env.APPIUM_DOCS_PREFIX ?? DOCS_PREFIX;
|
||||
const remote = process.env.APPIUM_DOCS_REMOTE ?? DOCS_REMOTE;
|
||||
const preview = Boolean(process.env.APPIUM_DOCS_PREVIEW);
|
||||
|
||||
const push = Boolean(process.env.APPIUM_DOCS_PUBLISH);
|
||||
const rebase = push;
|
||||
|
||||
async function main() {
|
||||
log.info(`Building Appium docs and committing to ${DOCS_BRANCH}`);
|
||||
@@ -51,17 +51,24 @@ async function main() {
|
||||
for (const lang of LANGS) {
|
||||
log.info(`Building docs for language '${lang}' and version ${majMinVer}`);
|
||||
const configFile = path.join(DOCS_DIR, `mkdocs-${lang}.yml`);
|
||||
await deploy({
|
||||
mkdocsYml: configFile,
|
||||
branch,
|
||||
prefix: path.join(basePrefix, lang),
|
||||
remote,
|
||||
deployVersion: majMinVer,
|
||||
push,
|
||||
alias: LATEST_ALIAS,
|
||||
rebase,
|
||||
message: `docs(appium): auto-build docs for appium@${majMinVer}, language ${lang}`,
|
||||
});
|
||||
if (preview) {
|
||||
await buildSite({
|
||||
mkdocsYml: configFile,
|
||||
siteDir: path.join('site', lang),
|
||||
});
|
||||
} else {
|
||||
await deploy({
|
||||
mkdocsYml: configFile,
|
||||
branch,
|
||||
prefix: path.join(basePrefix, lang),
|
||||
remote,
|
||||
deployVersion: majMinVer,
|
||||
push,
|
||||
alias: LATEST_ALIAS,
|
||||
rebase: true, // we always want to rebase if branch is out of sync
|
||||
message: `docs(appium): auto-build docs for appium@${majMinVer}, language ${lang}`,
|
||||
});
|
||||
}
|
||||
log.info(`Docs built`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const {baseConfig} = require('@appium/docutils');
|
||||
|
||||
const lang = process.env.APPIUM_DOCS_LANG || 'en';
|
||||
|
||||
const config = {...baseConfig};
|
||||
config.opts.source.include = [];
|
||||
config.opts.tutorials = `./docs/${lang}`;
|
||||
config.opts.readme = `${config.opts.tutorials}/README.md`;
|
||||
|
||||
module.exports = config;
|
||||
@@ -28,15 +28,12 @@ const desiredCapabilityConstraints = /** @type {const} */ ({
|
||||
isString: true,
|
||||
},
|
||||
});
|
||||
/**
|
||||
* @typedef {typeof desiredCapabilityConstraints} AppiumDriverConstraints
|
||||
*/
|
||||
|
||||
const sessionsListGuard = new AsyncLock();
|
||||
const pendingDriversGuard = new AsyncLock();
|
||||
|
||||
/**
|
||||
* @implements {SessionHandler}
|
||||
* @extends {DriverCore<AppiumDriverConstraints>}
|
||||
*/
|
||||
class AppiumDriver extends DriverCore {
|
||||
/**
|
||||
@@ -92,11 +89,11 @@ class AppiumDriver extends DriverCore {
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/** @type {DriverOpts} */
|
||||
/** @type {import('@appium/types').DriverOpts<AppiumDriverConstraints>} */
|
||||
args;
|
||||
|
||||
/**
|
||||
* @param {DriverOpts} opts
|
||||
* @param {import('@appium/types').DriverOpts<AppiumDriverConstraints>} opts
|
||||
*/
|
||||
constructor(opts) {
|
||||
// It is necessary to set `--tmp` here since it should be set to
|
||||
@@ -163,11 +160,10 @@ class AppiumDriver extends DriverCore {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async getSessions() {
|
||||
return _.toPairs(this.sessions).map(([id, driver]) => ({
|
||||
id,
|
||||
capabilities: driver.caps,
|
||||
capabilities: /** @type {import('@appium/types').DriverCaps} */ (driver.caps),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -224,12 +220,12 @@ class AppiumDriver extends DriverCore {
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} jsonwpCaps JSONWP formatted desired capabilities
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} reqCaps Required capabilities (JSONWP standard)
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} w3cCapabilities W3C capabilities
|
||||
* @param {DriverData[]} [driverData]
|
||||
* @param {W3CAppiumDriverCaps} jsonwpCaps JSONWP formatted desired capabilities
|
||||
* @param {W3CAppiumDriverCaps} [reqCaps] Required capabilities (JSONWP standard)
|
||||
* @param {W3CAppiumDriverCaps} [w3cCapabilities] W3C capabilities
|
||||
* @returns {Promise<SessionHandlerCreateResult>}
|
||||
*/
|
||||
async createSession(jsonwpCaps, reqCaps, w3cCapabilities, driverData) {
|
||||
async createSession(jsonwpCaps, reqCaps, w3cCapabilities) {
|
||||
const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
|
||||
const defaultSettings = pullSettings(defaultCapabilities);
|
||||
jsonwpCaps = _.cloneDeep(jsonwpCaps);
|
||||
@@ -247,13 +243,14 @@ class AppiumDriver extends DriverCore {
|
||||
Object.assign(w3cSettings, pullSettings(firstMatchEntry));
|
||||
}
|
||||
|
||||
/** @type {string|undefined} */
|
||||
let protocol;
|
||||
let innerSessionId, dCaps;
|
||||
try {
|
||||
// Parse the caps into a format that the InnerDriver will accept
|
||||
const parsedCaps = parseCapsForInnerDriver(
|
||||
jsonwpCaps,
|
||||
promoteAppiumOptions(w3cCapabilities),
|
||||
promoteAppiumOptions(/** @type {W3CAppiumDriverCaps} */ (w3cCapabilities)),
|
||||
this.desiredCapConstraints,
|
||||
defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
|
||||
);
|
||||
@@ -399,7 +396,7 @@ class AppiumDriver extends DriverCore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Driver} driver
|
||||
* @param {ExternalDriver} driver
|
||||
* @param {string} innerSessionId
|
||||
*/
|
||||
attachUnexpectedShutdownHandler(driver, innerSessionId) {
|
||||
@@ -442,10 +439,11 @@ class AppiumDriver extends DriverCore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DriverClass} InnerDriver
|
||||
* @param {((...args: any[]) => any)|(new(...args: any[]) => any)} InnerDriver
|
||||
* @returns {Promise<DriverData[]>}}
|
||||
* @privateRemarks The _intent_ is that `InnerDriver` is the class of a driver, but it only really
|
||||
* needs to be a function or constructor.
|
||||
*/
|
||||
// eslint-disable-next-line require-await
|
||||
async curSessionDataForDriver(InnerDriver) {
|
||||
const data = _.compact(
|
||||
_.values(this.sessions)
|
||||
@@ -588,7 +586,7 @@ class AppiumDriver extends DriverCore {
|
||||
*
|
||||
* @param {string} cmd
|
||||
* @param {...any} args
|
||||
* @returns {Promise<{value: any, error?: Error, protocol: string} | import('type-fest').AsyncReturnType<Driver['executeCommand']>>}
|
||||
* @returns {Promise<{value: any, error?: Error, protocol: string} | import('type-fest').AsyncReturnType<ExternalDriver['executeCommand']>>}
|
||||
*/
|
||||
async executeCommand(cmd, ...args) {
|
||||
// We have basically three cases for how to handle commands:
|
||||
@@ -618,6 +616,7 @@ class AppiumDriver extends DriverCore {
|
||||
let dstSession = null;
|
||||
let protocol = null;
|
||||
/** @type {this | ExternalDriver} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let driver = this;
|
||||
if (isSessionCmd) {
|
||||
sessionId = _.last(args);
|
||||
@@ -840,21 +839,38 @@ export class NoDriverProxyCommandError extends Error {
|
||||
export {AppiumDriver};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').DriverData} DriverData
|
||||
* @typedef {import('@appium/types').ServerArgs} DriverOpts
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').AppiumServer} AppiumServer
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('./extension/driver-config').DriverConfig} DriverConfig
|
||||
* @typedef {import('@appium/types').Plugin} Plugin
|
||||
* @typedef {import('@appium/types').PluginClass} PluginClass
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').SessionHandler<SessionHandlerResult<any[]>,SessionHandlerResult<void>>} SessionHandler
|
||||
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
||||
* @typedef {import('@appium/types').PluginClass} PluginClass
|
||||
* @typedef {import('@appium/types').Plugin} Plugin
|
||||
* @typedef {import('@appium/types').DriverClass<import('@appium/types').Driver>} DriverClass
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ISessionHandler<AppiumDriverConstraints,
|
||||
* SessionHandlerCreateResult, SessionHandlerDeleteResult>} AppiumSessionHandler
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {SessionHandlerResult<[innerSessionId: string, caps:
|
||||
* import('@appium/types').DriverCaps<Constraints>, protocol: string|undefined]>} SessionHandlerCreateResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Core<C>} Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {SessionHandlerResult<void>} SessionHandlerDeleteResult
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -868,11 +884,6 @@ export {AppiumDriver};
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
* @typedef {typeof desiredCapabilityConstraints} AppiumDriverConstraints
|
||||
* @typedef {import('@appium/types').W3CDriverCaps<AppiumDriverConstraints>} W3CAppiumDriverCaps
|
||||
*/
|
||||
|
||||
@@ -84,6 +84,16 @@ function makeListArgs(type) {
|
||||
dest: 'showUpdates',
|
||||
},
|
||||
],
|
||||
[
|
||||
['--verbose'],
|
||||
{
|
||||
required: false,
|
||||
default: false,
|
||||
action: 'store_true',
|
||||
help: 'Show more information about each extension',
|
||||
dest: 'verbose',
|
||||
},
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -284,5 +294,5 @@ export {getServerArgs, getExtensionArgs};
|
||||
|
||||
/**
|
||||
* A tuple of argument aliases and argument options
|
||||
* @typedef {Map<string[],import('argparse').ArgumentOptions>} ArgumentDefinitions
|
||||
* @typedef {Map<[name: string]|[name: string, alias: string],import('argparse').ArgumentOptions>} ArgumentDefinitions
|
||||
*/
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import ExtensionCommand from './extension-command';
|
||||
import ExtensionCliCommand from './extension-command';
|
||||
import {KNOWN_DRIVERS} from '../constants';
|
||||
import '@colors/colors';
|
||||
|
||||
const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mainClass'];
|
||||
|
||||
/**
|
||||
* @extends {ExtensionCommand<DriverType>}
|
||||
* @extends {ExtensionCliCommand<DriverType>}
|
||||
*/
|
||||
|
||||
export default class DriverCommand extends ExtensionCommand {
|
||||
export default class DriverCliCommand extends ExtensionCliCommand {
|
||||
/**
|
||||
* @param {import('./extension-command').ExtensionCommandOptions<DriverType>} opts
|
||||
*/
|
||||
@@ -120,7 +120,7 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.install}
|
||||
* Options for {@linkcode DriverCliCommand.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
|
||||
@@ -132,20 +132,20 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.uninstall}
|
||||
* Options for {@linkcode DriverCliCommand.uninstall}
|
||||
* @typedef DriverUninstallOpts
|
||||
* @property {string} driver - the name or spec of a driver to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCommand.update}
|
||||
* Options for {@linkcode DriverCliCommand.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}.
|
||||
* Options for {@linkcode DriverCliCommand.run}.
|
||||
* @typedef DriverRunOptions
|
||||
* @property {string} driver - name of the driver to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
INSTALL_TYPE_GIT,
|
||||
INSTALL_TYPE_GITHUB,
|
||||
INSTALL_TYPE_LOCAL,
|
||||
INSTALL_TYPE_DEV,
|
||||
} from '../extension/extension-config';
|
||||
import {SubProcess} from 'teen_process';
|
||||
import {packageDidChange} from '../extension/package-changed';
|
||||
import {spawn} from 'child_process';
|
||||
import {inspect} from 'node:util';
|
||||
|
||||
const UPDATE_ALL = 'installed';
|
||||
|
||||
@@ -32,7 +34,7 @@ function receiptToManifest(receipt) {
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
*/
|
||||
class ExtensionCommand {
|
||||
class ExtensionCliCommand {
|
||||
/**
|
||||
* This is the `DriverConfig` or `PluginConfig`, depending on `ExtType`.
|
||||
* @type {ExtensionConfig<ExtType>}
|
||||
@@ -99,41 +101,40 @@ class ExtensionCommand {
|
||||
|
||||
/**
|
||||
* List extensions
|
||||
*
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {ListOptions} opts
|
||||
* @return {Promise<ExtensionListData>} map of extension names to extension data
|
||||
* @return {Promise<ExtensionList<ExtType>>} map of extension names to extension data
|
||||
*/
|
||||
async list({showInstalled, showUpdates}) {
|
||||
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
|
||||
async list({showInstalled, showUpdates, verbose = false}) {
|
||||
let lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
|
||||
if (verbose) {
|
||||
lsMsg += ' (verbose mode)';
|
||||
}
|
||||
const installedNames = Object.keys(this.config.installedExtensions);
|
||||
const knownNames = Object.keys(this.knownExtensions);
|
||||
const exts = [...installedNames, ...knownNames].reduce(
|
||||
(acc, name) => {
|
||||
if (!acc[name]) {
|
||||
if (installedNames.includes(name)) {
|
||||
acc[name] = {
|
||||
...this.config.installedExtensions[name],
|
||||
installed: true,
|
||||
};
|
||||
} else if (!showInstalled) {
|
||||
acc[name] = {pkgName: this.knownExtensions[name], installed: false};
|
||||
}
|
||||
const listData = [...installedNames, ...knownNames].reduce((acc, name) => {
|
||||
if (!acc[name]) {
|
||||
if (installedNames.includes(name)) {
|
||||
acc[name] = {
|
||||
.../** @type {Partial<ExtManifest<ExtType>>} */ (this.config.installedExtensions[name]),
|
||||
installed: true,
|
||||
};
|
||||
} else if (!showInstalled) {
|
||||
acc[name] = /** @type {ExtensionListData<ExtType>} */ ({
|
||||
pkgName: this.knownExtensions[name],
|
||||
installed: false,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
/**
|
||||
* This accumulator contains either {@linkcode UninstalledExtensionLIstData} _or_
|
||||
* {@linkcode InstalledExtensionListData} without upgrade information (which is added by the below code block)
|
||||
* @type {Record<string,Partial<InstalledExtensionListData>|UninstalledExtensionListData>}
|
||||
*/ ({})
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, /** @type {ExtensionList<ExtType>} */ ({}));
|
||||
|
||||
// if we want to show whether updates are available, put that behind a spinner
|
||||
await spinWith(this.isJsonOutput, lsMsg, async () => {
|
||||
if (!showUpdates) {
|
||||
return;
|
||||
}
|
||||
for (const [ext, data] of _.toPairs(exts)) {
|
||||
for (const [ext, data] of _.toPairs(listData)) {
|
||||
if (!data.installed || data.installType !== INSTALL_TYPE_NPM) {
|
||||
// don't need to check for updates on exts that aren't installed
|
||||
// also don't need to check for updates on non-npm exts
|
||||
@@ -150,7 +151,12 @@ class ExtensionCommand {
|
||||
}
|
||||
});
|
||||
|
||||
const listData = /** @type {ExtensionListData} */ (exts);
|
||||
/**
|
||||
* Type guard to narrow "installed" extensions, which have more data
|
||||
* @param {any} data
|
||||
* @returns {data is InstalledExtensionListData<ExtType>}
|
||||
*/
|
||||
const extIsInstalled = (data) => Boolean(data.installed);
|
||||
|
||||
// if we're just getting the data, short circuit return here since we don't need to do any
|
||||
// formatting logic
|
||||
@@ -158,12 +164,16 @@ class ExtensionCommand {
|
||||
return listData;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
this.log.log(inspect(listData, {colors: true, depth: null}));
|
||||
return listData;
|
||||
}
|
||||
for (const [name, data] of _.toPairs(listData)) {
|
||||
let installTxt = ' [not installed]'.grey;
|
||||
let updateTxt = '';
|
||||
let upToDateTxt = '';
|
||||
let unsafeUpdateTxt = '';
|
||||
if (data.installed) {
|
||||
if (extIsInstalled(data)) {
|
||||
const {
|
||||
installType,
|
||||
installSpec,
|
||||
@@ -182,8 +192,11 @@ class ExtensionCommand {
|
||||
case INSTALL_TYPE_LOCAL:
|
||||
typeTxt = `(linked from ${installSpec})`.magenta;
|
||||
break;
|
||||
case INSTALL_TYPE_DEV:
|
||||
typeTxt = '(dev mode)';
|
||||
break;
|
||||
default:
|
||||
typeTxt = '(NPM)';
|
||||
typeTxt = '(npm)';
|
||||
}
|
||||
installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
|
||||
|
||||
@@ -411,7 +424,7 @@ class ExtensionCommand {
|
||||
* @param {ExtensionArgs} args
|
||||
* @returns {string}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getPostInstallText(args) {
|
||||
throw this._createFatalError('Must be implemented in final class');
|
||||
}
|
||||
@@ -492,7 +505,7 @@ class ExtensionCommand {
|
||||
* @param {ExtMetadata<ExtType>} extMetadata - the data in the "appium" field of `package.json` for an extension
|
||||
* @param {string} installSpec - Extension name/spec
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validateExtensionFields(extMetadata, installSpec) {
|
||||
throw this._createFatalError('Must be implemented in final class');
|
||||
}
|
||||
@@ -513,7 +526,12 @@ class ExtensionCommand {
|
||||
`Can't uninstall ${this.type} '${installSpec}'; it is not installed`
|
||||
);
|
||||
}
|
||||
const pkgName = this.config.installedExtensions[installSpec].pkgName;
|
||||
const extRecord = this.config.installedExtensions[installSpec];
|
||||
if (extRecord.installType === INSTALL_TYPE_DEV) {
|
||||
this.log.warn(`Cannot uninstall ${this.type} "${installSpec}" because it is in development!`);
|
||||
return this.config.installedExtensions;
|
||||
}
|
||||
const pkgName = extRecord.pkgName;
|
||||
await npm.uninstallPackage(this.config.appiumHome, pkgName);
|
||||
await this.config.removeExtension(installSpec);
|
||||
this.log.ok(`Successfully uninstalled ${this.type} '${installSpec}'`.green);
|
||||
@@ -771,11 +789,11 @@ class ExtensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtensionCommand;
|
||||
export {ExtensionCommand};
|
||||
export default ExtensionCliCommand;
|
||||
export {ExtensionCliCommand as ExtensionCommand};
|
||||
|
||||
/**
|
||||
* Options for the {@linkcode ExtensionCommand} constructor
|
||||
* Options for the {@linkcode ExtensionCliCommand} constructor
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef ExtensionCommandOptions
|
||||
* @property {ExtensionConfig<ExtType>} config - the `DriverConfig` or `PluginConfig` instance used for this command
|
||||
@@ -783,14 +801,15 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extra stuff about extensions; used indirectly by {@linkcode ExtensionCommand.list}.
|
||||
* Extra stuff about extensions; used indirectly by {@linkcode ExtensionCliCommand.list}.
|
||||
*
|
||||
* @typedef ExtensionMetadata
|
||||
* @typedef ExtensionListMetadata
|
||||
* @property {boolean} installed - If `true`, the extension is installed
|
||||
* @property {string?} updateVersion - If the extension is installed, the version it can be updated to
|
||||
* @property {string?} unsafeUpdateVersion - Same as above, but a major version bump
|
||||
* @property {boolean} upToDate - If the extension is installed and the latest
|
||||
* @property {string?} updateError - Update check error message (if present)
|
||||
* @property {string|null} updateVersion - If the extension is installed, the version it can be updated to
|
||||
* @property {string|null} unsafeUpdateVersion - Same as above, but a major version bump
|
||||
* @property {string} [updateError] - Update check error message (if present)
|
||||
* @property {boolean} [devMode] - If Appium is run from an extension's working copy
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -830,22 +849,24 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Possible return value for {@linkcode ExtensionCommand.list}
|
||||
* @typedef {Partial<InstalledExtensionListData> & {pkgName: string, installed: false}} UninstalledExtensionListData
|
||||
* Possible return value for {@linkcode ExtensionCliCommand.list}
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {Partial<ExtManifest<ExtType>> & Partial<ExtensionListMetadata>} ExtensionListData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Possible return value for {@linkcode ExtensionCommand.list}
|
||||
* @typedef {import('appium/types').InternalMetadata & ExtensionMetadata} InstalledExtensionListData
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {ExtManifest<ExtType> & ExtensionListMetadata} InstalledExtensionListData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return value of {@linkcode ExtensionCommand.list}.
|
||||
* @typedef {Record<string,InstalledExtensionListData|UninstalledExtensionListData>} ExtensionListData
|
||||
* Return value of {@linkcode ExtensionCliCommand.list}.
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {Record<string,ExtensionListData<ExtType>>} ExtensionList
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._run}.
|
||||
* Options for {@linkcode ExtensionCliCommand._run}.
|
||||
* @typedef RunOptions
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
@@ -854,7 +875,7 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return value of {@linkcode ExtensionCommand._run}
|
||||
* Return value of {@linkcode ExtensionCliCommand._run}
|
||||
*
|
||||
* @typedef RunOutput
|
||||
* @property {string} [error] - error message if script ran unsuccessfully, otherwise undefined
|
||||
@@ -862,41 +883,41 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._update}.
|
||||
* Options for {@linkcode ExtensionCliCommand._update}.
|
||||
* @typedef ExtensionUpdateOpts
|
||||
* @property {string} installSpec - the name of the extension to update
|
||||
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return value of {@linkcode ExtensionCommand._update}.
|
||||
* Return value of {@linkcode ExtensionCliCommand._update}.
|
||||
* @typedef ExtensionUpdateResult
|
||||
* @property {Record<string,Error>} errors - map of ext names to error objects
|
||||
* @property {Record<string,UpdateReport>} updates - map of ext names to {@linkcode UpdateReport}s
|
||||
*/
|
||||
|
||||
/**
|
||||
* Part of result of {@linkcode ExtensionCommand._update}.
|
||||
* Part of result of {@linkcode ExtensionCliCommand._update}.
|
||||
* @typedef UpdateReport
|
||||
* @property {string} from - version the extension was updated from
|
||||
* @property {string} to - version the extension was updated to
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._uninstall}.
|
||||
* Options for {@linkcode ExtensionCliCommand._uninstall}.
|
||||
* @typedef UninstallOpts
|
||||
* @property {string} installSpec - the name or spec of an extension to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used by {@linkcode ExtensionCommand.getPostInstallText}
|
||||
* Used by {@linkcode ExtensionCliCommand.getPostInstallText}
|
||||
* @typedef ExtensionArgs
|
||||
* @property {string} extName - the name of an extension
|
||||
* @property {object} extData - the data for an installed extension
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand.installViaNpm}
|
||||
* Options for {@linkcode ExtensionCliCommand.installViaNpm}
|
||||
* @typedef InstallViaNpmArgs
|
||||
* @property {string} installSpec - the name or spec of an extension to install
|
||||
* @property {string} pkgName - the NPM package name of the extension
|
||||
@@ -905,7 +926,7 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object returned by {@linkcode ExtensionCommand.checkForExtensionUpdate}
|
||||
* Object returned by {@linkcode ExtensionCliCommand.checkForExtensionUpdate}
|
||||
* @typedef PossibleUpdates
|
||||
* @property {string} current - current version
|
||||
* @property {string?} safeUpdate - version we can safely update to if it exists, or null
|
||||
@@ -913,7 +934,7 @@ export {ExtensionCommand};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._install}
|
||||
* Options for {@linkcode ExtensionCliCommand._install}
|
||||
* @typedef InstallOpts
|
||||
* @property {string} installSpec - the name or spec of an extension to install
|
||||
* @property {InstallType} installType - how to install this extension. One of the INSTALL_TYPES
|
||||
@@ -929,10 +950,11 @@ export {ExtensionCommand};
|
||||
* @typedef ListOptions
|
||||
* @property {boolean} showInstalled - whether should show only installed extensions
|
||||
* @property {boolean} showUpdates - whether should show available updates
|
||||
* @property {boolean} [verbose] - whether to show additional data from the extension
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opts for {@linkcode ExtensionCommand.getInstallationReceipt}
|
||||
* Opts for {@linkcode ExtensionCliCommand.getInstallationReceipt}
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef GetInstallationReceiptOpts
|
||||
* @property {string} installPath
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable no-console */
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
||||
import {isExtensionCommandArgs} from '../utils';
|
||||
import DriverCommand from './driver-command';
|
||||
import PluginCommand from './plugin-command';
|
||||
import DriverCliCommand from './driver-command';
|
||||
import PluginCliCommand from './plugin-command';
|
||||
import {errAndQuit, JSON_SPACES} from './utils';
|
||||
|
||||
export const commandClasses = Object.freeze(
|
||||
/** @type {const} */ ({
|
||||
[DRIVER_TYPE]: DriverCommand,
|
||||
[PLUGIN_TYPE]: PluginCommand,
|
||||
[DRIVER_TYPE]: DriverCliCommand,
|
||||
[PLUGIN_TYPE]: PluginCliCommand,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ export {runExtensionCommand};
|
||||
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {ExtType extends DriverType ? Class<DriverCommand> : ExtType extends PluginType ? Class<PluginCommand> : never} ExtCommand
|
||||
* @typedef {ExtType extends DriverType ? Class<DriverCliCommand> : ExtType extends PluginType ? Class<PluginCliCommand> : never} ExtCommand
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,6 +101,13 @@ class ArgParser {
|
||||
try {
|
||||
const parsed = this.parser.parse_known_args(args);
|
||||
const [knownArgs, unknownArgs] = parsed;
|
||||
// XXX: you'd think that argparse, when given an alias for a subcommand,
|
||||
// would set this value to the original subcommand name, but it doesn't.
|
||||
if (knownArgs?.driverCommand === 'ls') {
|
||||
knownArgs.driverCommand = 'list';
|
||||
} else if (knownArgs?.pluginCommand === 'ls') {
|
||||
knownArgs.pluginCommand = 'list';
|
||||
}
|
||||
if (
|
||||
unknownArgs?.length &&
|
||||
(knownArgs.driverCommand === 'run' || knownArgs.pluginCommand === 'run')
|
||||
@@ -210,13 +217,14 @@ class ArgParser {
|
||||
});
|
||||
const extensionArgs = getExtensionArgs();
|
||||
/**
|
||||
* @type { {command: import('appium/types').CliExtensionSubcommand, args: import('./args').ArgumentDefinitions, help: string}[] }
|
||||
* @type { {command: import('appium/types').CliExtensionSubcommand, args: import('./args').ArgumentDefinitions, help: string, aliases?: import('argparse').SubArgumentParserOptions['aliases']}[] }
|
||||
*/
|
||||
const parserSpecs = [
|
||||
{
|
||||
command: 'list',
|
||||
args: extensionArgs[type].list,
|
||||
help: `List available and installed ${type}s`,
|
||||
aliases: ['ls'],
|
||||
},
|
||||
{
|
||||
command: 'install',
|
||||
@@ -242,15 +250,18 @@ class ArgParser {
|
||||
},
|
||||
];
|
||||
|
||||
for (const {command, args, help} of parserSpecs) {
|
||||
const parser = extSubParsers.add_parser(command, {help});
|
||||
for (const {command, args, help, aliases} of parserSpecs) {
|
||||
const parser = extSubParsers.add_parser(command, {help, aliases: aliases ?? []});
|
||||
|
||||
ArgParser._patchExit(parser);
|
||||
|
||||
for (const [flagsOrNames, opts] of args) {
|
||||
// add_argument mutates params so make sure to send in copies instead
|
||||
// @ts-ignore
|
||||
parser.add_argument(...flagsOrNames, {...opts});
|
||||
if (flagsOrNames.length === 2) {
|
||||
parser.add_argument(flagsOrNames[0], flagsOrNames[1], {...opts});
|
||||
} else {
|
||||
parser.add_argument(flagsOrNames[0], {...opts});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import ExtensionCommand from './extension-command';
|
||||
import ExtensionCliCommand from './extension-command';
|
||||
import {KNOWN_PLUGINS} from '../constants';
|
||||
|
||||
const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
|
||||
|
||||
/**
|
||||
* @extends {ExtensionCommand<PluginType>}
|
||||
* @extends {ExtensionCliCommand<PluginType>}
|
||||
*/
|
||||
export default class PluginCommand extends ExtensionCommand {
|
||||
export default class PluginCliCommand extends ExtensionCliCommand {
|
||||
/**
|
||||
*
|
||||
* @param {import('./extension-command').ExtensionCommandOptions<PluginType>} opts
|
||||
@@ -110,7 +110,7 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.install}
|
||||
* Options for {@linkcode PluginCliCommand.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
|
||||
@@ -122,20 +122,20 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.uninstall}
|
||||
* Options for {@linkcode PluginCliCommand.uninstall}
|
||||
* @typedef PluginUninstallOpts
|
||||
* @property {string} plugin - the name or spec of a plugin to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCommand.update}
|
||||
* Options for {@linkcode PluginCliCommand.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}.
|
||||
* Options for {@linkcode PluginCliCommand.run}.
|
||||
* @typedef PluginRunOptions
|
||||
* @property {string} plugin - name of the plugin to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
|
||||
@@ -68,4 +68,12 @@ export const EXT_SUBCOMMAND_RUN = 'run';
|
||||
/**
|
||||
* Current revision of the manifest (`extensions.yaml`) schema
|
||||
*/
|
||||
export const CURRENT_SCHEMA_REV = 3;
|
||||
export const CURRENT_SCHEMA_REV = 4;
|
||||
|
||||
/**
|
||||
* The default number of stack frames to show in a "long" stack trace, when enabled via `--long-stacktrace`
|
||||
* @remarks This value may be increased in the future.
|
||||
* @privateRemarks A value like `Infinity` may provide to have deleterious effects on
|
||||
* memory usage, perf, and/or log output, and higher limits may be difficult to scan.
|
||||
*/
|
||||
export const LONG_STACKTRACE_LIMIT = 100;
|
||||
|
||||
@@ -212,28 +212,29 @@ export class DriverConfig extends ExtensionConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {ExtensionType} T
|
||||
* @typedef {import('appium/types').ExtMetadata<T>} ExtMetadata
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {ExtensionType} T
|
||||
* @typedef {import('appium/types').ExtManifest<T>} ExtManifest
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/types').ManifestData} ManifestData
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('./manifest').Manifest} Manifest
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {ExtensionType} T
|
||||
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {ExtensionType} T
|
||||
* @typedef {import('appium/types').ExtName<T>} ExtName
|
||||
*/
|
||||
|
||||
@@ -244,7 +245,3 @@ export class DriverConfig extends ExtensionConfig {
|
||||
* @property {string} version
|
||||
* @property {string} driverName
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import {util} from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import {satisfies} from 'semver';
|
||||
import {util} from '@appium/support';
|
||||
import {commandClasses} from '../cli/extension';
|
||||
import {APPIUM_VER} from '../config';
|
||||
import log from '../logger';
|
||||
@@ -13,17 +13,41 @@ import {
|
||||
registerSchema,
|
||||
} from '../schema/schema';
|
||||
|
||||
const INSTALL_TYPE_NPM = 'npm';
|
||||
const INSTALL_TYPE_LOCAL = 'local';
|
||||
const INSTALL_TYPE_GITHUB = 'github';
|
||||
const INSTALL_TYPE_GIT = 'git';
|
||||
/**
|
||||
* "npm" install type
|
||||
* Used when extension was installed by npm package name
|
||||
* @remarks _All_ extensions are installed _by_ `npm`, but only this one means the package name was
|
||||
* used to specify it
|
||||
*/
|
||||
export const INSTALL_TYPE_NPM = 'npm';
|
||||
/**
|
||||
* "local" install type
|
||||
* Used when extension was installed from a local path
|
||||
*/
|
||||
export const INSTALL_TYPE_LOCAL = 'local';
|
||||
/**
|
||||
* "github" install type
|
||||
* Used when extension was installed via GitHub URL
|
||||
*/
|
||||
export const INSTALL_TYPE_GITHUB = 'github';
|
||||
/**
|
||||
* "git" install type
|
||||
* Used when extensions was installed via Git URL
|
||||
*/
|
||||
export const INSTALL_TYPE_GIT = 'git';
|
||||
/**
|
||||
* "dev" install type
|
||||
* Used when automatically detected as a working copy
|
||||
*/
|
||||
export const INSTALL_TYPE_DEV = 'dev';
|
||||
|
||||
/** @type {Set<InstallType>} */
|
||||
const INSTALL_TYPES = new Set([
|
||||
export const INSTALL_TYPES = new Set([
|
||||
INSTALL_TYPE_GIT,
|
||||
INSTALL_TYPE_GITHUB,
|
||||
INSTALL_TYPE_LOCAL,
|
||||
INSTALL_TYPE_NPM,
|
||||
INSTALL_TYPE_DEV,
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -54,7 +78,7 @@ export class ExtensionConfig {
|
||||
manifest;
|
||||
|
||||
/**
|
||||
* @type {ExtensionListData|undefined}
|
||||
* @type {import('../cli/extension-command').ExtensionList<ExtType>|undefined}
|
||||
*/
|
||||
#listDataCache;
|
||||
|
||||
@@ -115,7 +139,7 @@ export class ExtensionConfig {
|
||||
* @param {ExtName<ExtType>} extName
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars,require-await
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async getConfigWarnings(extManifest, extName) {
|
||||
return [];
|
||||
}
|
||||
@@ -239,7 +263,7 @@ export class ExtensionConfig {
|
||||
* This is an expensive operation, so the result is cached. Currently, there is no
|
||||
* use case for invalidating the cache.
|
||||
* @protected
|
||||
* @returns {Promise<ExtensionListData>}
|
||||
* @returns {Promise<import('../cli/extension-command').ExtensionList<ExtType>>}
|
||||
*/
|
||||
async getListData() {
|
||||
if (this.#listDataCache) {
|
||||
@@ -300,7 +324,10 @@ export class ExtensionConfig {
|
||||
|
||||
if (_.isString(appiumVersion) && !satisfies(APPIUM_VER, appiumVersion)) {
|
||||
const listData = await this.getListData();
|
||||
const extListData = /** @type {InstalledExtensionListData} */ (listData[extName]);
|
||||
const extListData =
|
||||
/** @type {import('../cli/extension-command').ExtensionListData<ExtType>} */ (
|
||||
listData[extName]
|
||||
);
|
||||
if (extListData?.installed) {
|
||||
const {updateVersion, upToDate} = extListData;
|
||||
if (!upToDate) {
|
||||
@@ -319,7 +346,10 @@ export class ExtensionConfig {
|
||||
}
|
||||
} else if (!_.isString(appiumVersion)) {
|
||||
const listData = await this.getListData();
|
||||
const extListData = /** @type {InstalledExtensionListData} */ (listData[extName]);
|
||||
const extListData =
|
||||
/** @type {import('../cli/extension-command').InstalledExtensionListData<ExtType>} */ (
|
||||
listData[extName]
|
||||
);
|
||||
if (!extListData?.upToDate && extListData?.updateVersion) {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
@@ -391,7 +421,7 @@ export class ExtensionConfig {
|
||||
* @param {ExtName<ExtType>} extName - Extension name (from manifest)
|
||||
* @returns {ExtManifestProblem[]}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getGenericConfigProblems(extManifest, extName) {
|
||||
const {version, pkgName, mainClass} = extManifest;
|
||||
const problems = [];
|
||||
@@ -426,7 +456,7 @@ export class ExtensionConfig {
|
||||
* @param {ExtName<ExtType>} extName
|
||||
* @returns {ExtManifestProblem[]}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getConfigProblems(extManifest, extName) {
|
||||
// shoud override this method if special validation is necessary for this extension type
|
||||
return [];
|
||||
@@ -479,7 +509,7 @@ export class ExtensionConfig {
|
||||
* @param {ExtName<ExtType>[]} [activeNames]
|
||||
* @returns {void}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
print(activeNames) {
|
||||
if (_.isEmpty(this.installedExtensions)) {
|
||||
log.info(
|
||||
@@ -504,7 +534,7 @@ export class ExtensionConfig {
|
||||
* @returns {string}
|
||||
* @abstract
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
extensionDesc(extName, extManifest) {
|
||||
throw new Error('This must be implemented in a subclass');
|
||||
}
|
||||
@@ -544,6 +574,7 @@ export class ExtensionConfig {
|
||||
delete require.cache[reqResolved];
|
||||
}
|
||||
log.debug(`Requiring ${this.extensionType} at ${reqPath}`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const MainClass = require(reqPath)[mainClass];
|
||||
if (!MainClass) {
|
||||
throw new ReferenceError(
|
||||
@@ -619,8 +650,6 @@ export class ExtensionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
export {INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GITHUB, INSTALL_TYPES};
|
||||
|
||||
/**
|
||||
* An issue with the {@linkcode ExtManifest} for a particular extension.
|
||||
*
|
||||
@@ -640,8 +669,6 @@ export {INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GIT
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('./manifest').Manifest} Manifest
|
||||
* @typedef {import('../cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('../cli/extension-command').InstalledExtensionListData} InstalledExtensionListData
|
||||
* @typedef {import('appium/types').InstallType} InstallType
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
||||
import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
||||
import log from '../logger';
|
||||
|
||||
/**
|
||||
@@ -12,6 +12,11 @@ import log from '../logger';
|
||||
*/
|
||||
const SCHEMA_REV_3 = 3;
|
||||
|
||||
/**
|
||||
* Constant for v4 of the schema rev.
|
||||
*/
|
||||
const SCHEMA_REV_4 = 4;
|
||||
|
||||
/**
|
||||
* Collection of functions to migrate from one version to another.
|
||||
*
|
||||
@@ -20,7 +25,7 @@ const SCHEMA_REV_3 = 3;
|
||||
* 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}}
|
||||
* @type { {[P in keyof ManifestDataVersions]?: Migration} }
|
||||
*/
|
||||
const Migrations = {
|
||||
/**
|
||||
@@ -32,19 +37,32 @@ const Migrations = {
|
||||
* @type {Migration}
|
||||
*/
|
||||
[SCHEMA_REV_3]: (manifest) => {
|
||||
let shouldSync = false;
|
||||
/** @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;
|
||||
break;
|
||||
}
|
||||
return allExtData.some((metadata) => !('installPath' in metadata));
|
||||
},
|
||||
/**
|
||||
* Updates installed extensions to use `InstallType` of `dev` if appropriate.
|
||||
*
|
||||
* Previously, these types of extensions (automatically discovered) would use the default `InstallType` of `npm`, so we need
|
||||
* to refresh any with this install type.
|
||||
*
|
||||
* This should only happen once; we do not want to re-check everything with `npm` install type
|
||||
* every time.
|
||||
* @type {Migration}
|
||||
*/
|
||||
[SCHEMA_REV_4]: (manifest) => {
|
||||
if (manifest.schemaRev < SCHEMA_REV_4) {
|
||||
const allExtData = [
|
||||
...Object.values(manifest.getExtensionData(DRIVER_TYPE)),
|
||||
...Object.values(manifest.getExtensionData(PLUGIN_TYPE)),
|
||||
];
|
||||
return allExtData.some((metadata) => metadata.installType === 'npm');
|
||||
}
|
||||
return shouldSync;
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -61,7 +79,7 @@ const Migrations = {
|
||||
* @returns {boolean} Whether the data was modified
|
||||
*/
|
||||
function setSchemaRev(manifest, version) {
|
||||
if (manifest.schemaRev ?? 0 < version) {
|
||||
if ((manifest.schemaRev ?? 0) < version) {
|
||||
manifest.setSchemaRev(version);
|
||||
return true;
|
||||
}
|
||||
@@ -78,15 +96,13 @@ function setSchemaRev(manifest, version) {
|
||||
*/
|
||||
export async function migrate(manifest) {
|
||||
let didChange = false;
|
||||
for await (const [v, migration] of Object.entries(Migrations)) {
|
||||
const version = /** @type {keyof ManifestDataVersions} */ (Number(v));
|
||||
for await (const migration of Object.values(Migrations)) {
|
||||
didChange = (await migration(manifest)) || didChange;
|
||||
didChange = setSchemaRev(manifest, version) || didChange;
|
||||
}
|
||||
|
||||
didChange = setSchemaRev(manifest, CURRENT_SCHEMA_REV) || 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${manifest.schemaRev}`);
|
||||
log.debug(`Upgraded extension manifest to schema v${manifest.schemaRev}`);
|
||||
}
|
||||
|
||||
return didChange;
|
||||
|
||||
@@ -10,7 +10,7 @@ import path from 'path';
|
||||
import YAML from 'yaml';
|
||||
import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
||||
import log from '../logger';
|
||||
import {INSTALL_TYPE_NPM} from './extension-config';
|
||||
import {INSTALL_TYPE_NPM, INSTALL_TYPE_DEV} from './extension-config';
|
||||
import {packageDidChange} from './package-changed';
|
||||
import {migrate} from './manifest-migrations';
|
||||
|
||||
@@ -148,27 +148,33 @@ export class Manifest {
|
||||
* Returns a new or existing {@link Manifest} instance, based on the value of `appiumHome`.
|
||||
*
|
||||
* Maintains one instance per value of `appiumHome`.
|
||||
* @param {string} appiumHome - Path to `APPIUM_HOME`
|
||||
* @returns {Manifest}
|
||||
*/
|
||||
static getInstance = _.memoize(function _getInstance(appiumHome) {
|
||||
return new Manifest(appiumHome);
|
||||
});
|
||||
static getInstance = _.memoize(
|
||||
/**
|
||||
* @param {string} appiumHome - Path to `APPIUM_HOME`
|
||||
* @returns {Manifest}
|
||||
*/
|
||||
function _getInstance(appiumHome) {
|
||||
return new Manifest(appiumHome);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Searches `APPIUM_HOME` for installed extensions and adds them to the manifest.
|
||||
* @param {boolean} hasAppiumDependency - This affects whether or not the "dev" `InstallType` is used
|
||||
* @returns {Promise<boolean>} `true` if any extensions were added, `false` otherwise.
|
||||
*/
|
||||
async syncWithInstalledExtensions() {
|
||||
async syncWithInstalledExtensions(hasAppiumDependency = false) {
|
||||
// this could be parallelized, but we can't use fs.walk as an async iterator
|
||||
let didChange = false;
|
||||
|
||||
/**
|
||||
* Listener for the `match` event of a `glob` instance
|
||||
* @param {string} filepath - Path to a `package.json`
|
||||
* @param {boolean} [devType] - If `true`, this is an extension in "dev mode"
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const onMatch = async (filepath) => {
|
||||
const onMatch = async (filepath, devType = false) => {
|
||||
try {
|
||||
const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
|
||||
if (isExtension(pkg)) {
|
||||
@@ -188,7 +194,8 @@ export class Manifest {
|
||||
) {
|
||||
log.info(`Discovered installed ${extType} "${name}"`);
|
||||
}
|
||||
const changed = this.addExtensionFromPackage(pkg, filepath);
|
||||
const installType = devType && hasAppiumDependency ? INSTALL_TYPE_DEV : INSTALL_TYPE_NPM;
|
||||
const changed = this.addExtensionFromPackage(pkg, filepath, installType);
|
||||
didChange = didChange || changed;
|
||||
}
|
||||
} catch {}
|
||||
@@ -199,8 +206,9 @@ export class Manifest {
|
||||
* @type {Promise<void>[]}
|
||||
*/
|
||||
const queue = [
|
||||
// look at `package.json` in `APPIUM_HOME` only
|
||||
onMatch(path.join(this.#appiumHome, 'package.json')),
|
||||
// look at `package.json` in `APPIUM_HOME` only.
|
||||
// this causes extensions in "dev mode" to be automatically found
|
||||
onMatch(path.join(this.#appiumHome, 'package.json'), true),
|
||||
];
|
||||
|
||||
// add dependencies to the queue
|
||||
@@ -252,9 +260,10 @@ export class Manifest {
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {ExtPackageJson<ExtType>} pkgJson
|
||||
* @param {string} pkgPath
|
||||
* @param {typeof INSTALL_TYPE_NPM | typeof INSTALL_TYPE_DEV} [installType]
|
||||
* @returns {boolean} - `true` if this method did anything.
|
||||
*/
|
||||
addExtensionFromPackage(pkgJson, pkgPath) {
|
||||
addExtensionFromPackage(pkgJson, pkgPath, installType = INSTALL_TYPE_NPM) {
|
||||
const extensionPath = path.dirname(pkgPath);
|
||||
|
||||
/**
|
||||
@@ -264,7 +273,7 @@ export class Manifest {
|
||||
pkgName: pkgJson.name,
|
||||
version: pkgJson.version,
|
||||
appiumVersion: pkgJson.peerDependencies?.appium,
|
||||
installType: INSTALL_TYPE_NPM,
|
||||
installType,
|
||||
installSpec: `${pkgJson.name}@${pkgJson.version}`,
|
||||
installPath: extensionPath,
|
||||
};
|
||||
@@ -434,6 +443,8 @@ export class Manifest {
|
||||
shouldWrite = await migrate(this);
|
||||
}
|
||||
|
||||
const hasAppiumDependency = await env.hasAppiumDependency(this.appiumHome);
|
||||
|
||||
/**
|
||||
* 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:
|
||||
@@ -445,13 +456,9 @@ export class Manifest {
|
||||
* 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)))
|
||||
) {
|
||||
if (shouldWrite || (hasAppiumDependency && (await packageDidChange(this.appiumHome)))) {
|
||||
log.debug('Discovering newly installed extensions...');
|
||||
shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
|
||||
shouldWrite = (await this.syncWithInstalledExtensions(hasAppiumDependency)) || shouldWrite;
|
||||
}
|
||||
|
||||
if (shouldWrite) {
|
||||
|
||||
+89
-17
@@ -2,9 +2,8 @@
|
||||
|
||||
import {init as logsinkInit} from './logsink'; // this import needs to come first since it sets up global npmlog
|
||||
import logger from './logger'; // logger needs to remain second
|
||||
// @ts-ignore
|
||||
import {routeConfiguringFunction as makeRouter, server as baseServer} from '@appium/base-driver';
|
||||
import {logger as logFactory, util, env} from '@appium/support';
|
||||
import {logger as logFactory, util, env, fs} from '@appium/support';
|
||||
import {asyncify} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {AppiumDriver} from './appium';
|
||||
@@ -23,7 +22,7 @@ import {
|
||||
} from './config';
|
||||
import {readConfigFile} from './config-file';
|
||||
import {loadExtensions, getActivePlugins, getActiveDrivers} from './extension';
|
||||
import {SERVER_SUBCOMMAND} from './constants';
|
||||
import {SERVER_SUBCOMMAND, LONG_STACKTRACE_LIMIT} from './constants';
|
||||
import registerNode from './grid-register';
|
||||
import {getDefaultsForSchema, validate} from './schema/schema';
|
||||
import {
|
||||
@@ -33,7 +32,11 @@ import {
|
||||
isExtensionCommandArgs,
|
||||
isPluginCommandArgs,
|
||||
isServerCommandArgs,
|
||||
fetchIpAddresses,
|
||||
V4_BROADCAST_IP,
|
||||
V6_BROADCAST_IP,
|
||||
} from './utils';
|
||||
import os from 'node:os';
|
||||
|
||||
const {resolveAppiumHome} = env;
|
||||
|
||||
@@ -47,7 +50,7 @@ async function preflightChecks(args, throwInsteadOfExit = false) {
|
||||
checkNodeOk();
|
||||
await checkNpmOk();
|
||||
if (args.longStacktrace) {
|
||||
require('longjohn').async_trace_limit = -1;
|
||||
Error.stackTraceLimit = LONG_STACKTRACE_LIMIT;
|
||||
}
|
||||
if (args.showBuildInfo) {
|
||||
await showBuildInfo();
|
||||
@@ -114,17 +117,6 @@ async function logStartupInfo(args) {
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the address and port the server is listening on
|
||||
* @param {string} address - Address
|
||||
* @param {number} port - Port
|
||||
* @returns {void}
|
||||
*/
|
||||
function logServerPort(address, port) {
|
||||
let logMessage = `Appium REST http interface listener started on ` + `${address}:${port}`;
|
||||
logger.info(logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of `updateServer` functions from all extensions
|
||||
* @param {DriverNameMap} driverClasses
|
||||
@@ -150,6 +142,52 @@ function getExtraMethodMap(driverClasses, pluginClasses) {
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and validates appium home path folder
|
||||
*
|
||||
* @param {string} name The name of the appium home source (needed for error messages)
|
||||
* @param {string} appiumHome The actual value to be verified
|
||||
* @returns {Promise<string>} Same appiumHome value
|
||||
* @throws {Error} If the validation has failed
|
||||
*/
|
||||
async function prepareAppiumHome(name, appiumHome) {
|
||||
let stat;
|
||||
try {
|
||||
stat = await fs.stat(appiumHome);
|
||||
} catch (e) {
|
||||
let err = e;
|
||||
if (e.code === 'ENOENT') {
|
||||
try {
|
||||
await fs.mkdir(appiumHome, {recursive: true});
|
||||
return appiumHome;
|
||||
} catch (e1) {
|
||||
err = e1;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`The path '${appiumHome}' provided in the ${name} must point ` +
|
||||
`to a valid folder writeable for the current user account '${os.userInfo().username}'. ` +
|
||||
`Original error: ${err.message}`
|
||||
);
|
||||
}
|
||||
if (!stat.isDirectory()) {
|
||||
throw new Error(
|
||||
`The path '${appiumHome}' provided in the ${name} must point to a valid folder`
|
||||
);
|
||||
}
|
||||
try {
|
||||
await fs.access(appiumHome, fs.constants.W_OK);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`The folder path '${appiumHome}' provided in the ${name} must be ` +
|
||||
`writeable for the current user account '${os.userInfo().username}. ` +
|
||||
`Original error: ${e.message}`
|
||||
);
|
||||
}
|
||||
return appiumHome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Appium, but does not start the server.
|
||||
*
|
||||
@@ -169,6 +207,13 @@ function getExtraMethodMap(driverClasses, pluginClasses) {
|
||||
*/
|
||||
async function init(args) {
|
||||
const appiumHome = args?.appiumHome ?? (await resolveAppiumHome());
|
||||
let appiumHomeSourceName = 'autodetected appium home path';
|
||||
if (!_.isNil(args?.appiumHome)) {
|
||||
appiumHomeSourceName = 'appiumHome config value';
|
||||
} else if (process.env.APPIUM_HOME) {
|
||||
appiumHomeSourceName = 'APPIUM_HOME environment variable';
|
||||
}
|
||||
await prepareAppiumHome(appiumHomeSourceName, appiumHome);
|
||||
|
||||
adjustNodePath();
|
||||
|
||||
@@ -245,7 +290,11 @@ async function init(args) {
|
||||
}
|
||||
}
|
||||
|
||||
const appiumDriver = new AppiumDriver(serverArgs);
|
||||
const appiumDriver = new AppiumDriver(
|
||||
/** @type {import('@appium/types').DriverOpts<import('./appium').AppiumDriverConstraints>} */ (
|
||||
serverArgs
|
||||
)
|
||||
);
|
||||
// set the config on the umbrella driver so it can match drivers to caps
|
||||
appiumDriver.driverConfig = driverConfig;
|
||||
await preflightChecks(serverArgs, throwInsteadOfExit);
|
||||
@@ -271,6 +320,28 @@ async function init(args) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the actual server address and the list of URLs that
|
||||
* could be used to connect to the current server.
|
||||
* Properly replaces broadcast addresses in client URLs.
|
||||
*
|
||||
* @param {string} url The URL the server is listening on
|
||||
*/
|
||||
function logServerAddress(url) {
|
||||
logger.info(`Appium REST http interface listener started on ${url}`);
|
||||
const urlObj = new URL(url);
|
||||
if (![V4_BROADCAST_IP, V6_BROADCAST_IP].includes(urlObj.hostname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ips = fetchIpAddresses(urlObj.hostname === V4_BROADCAST_IP ? 4 : 6);
|
||||
logger.info(
|
||||
`You can provide the following ${util.pluralize('URL', ips.length, false)} ` +
|
||||
`in your client code to connect to this server:\n` +
|
||||
ips.map((x) => `\t${urlObj.href.replace(urlObj.hostname, x)}`).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Appium's config. Starts server if appropriate and resolves the
|
||||
* server instance if so; otherwise resolves w/ `undefined`.
|
||||
@@ -371,7 +442,8 @@ async function main(args) {
|
||||
});
|
||||
}
|
||||
|
||||
logServerPort(parsedArgs.address, parsedArgs.port);
|
||||
logServerAddress(`http://${parsedArgs.address}:${parsedArgs.port}${parsedArgs.basePath}`);
|
||||
|
||||
driverConfig.print();
|
||||
pluginConfig.print([...pluginClasses.values()]);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ function makeDescription(schema) {
|
||||
* as understood by `argparse`.
|
||||
* @param {AppiumJSONSchema} subSchema - JSON schema for the option
|
||||
* @param {ArgSpec} argSpec - Argument spec tuple
|
||||
* @returns {[string[], import('argparse').ArgumentOptions]} Tuple of flag and options
|
||||
* @returns {[[string]|[string, string], import('argparse').ArgumentOptions]} Tuple of flag and options
|
||||
*/
|
||||
function subSchemaToArgDef(subSchema, argSpec) {
|
||||
let {type, appiumCliAliases, appiumCliTransformer, enum: enumValues} = subSchema;
|
||||
@@ -207,7 +207,10 @@ function subSchemaToArgDef(subSchema, argSpec) {
|
||||
}
|
||||
}
|
||||
|
||||
return [aliases, argOpts];
|
||||
// TODO: argparse only accepts the command name and a single alias; any extra aliases
|
||||
// will be silently discarded.
|
||||
const finalAliases = /** @type {[string]|[string, string]} */ (aliases);
|
||||
return [finalAliases, argOpts];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +227,7 @@ export function toParserArgs() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {string|number} T
|
||||
* @typedef {import('ajv/dist/types').FormatValidator<T>} FormatValidator<T>
|
||||
*/
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export class RoachHotelMap extends Map {
|
||||
/**
|
||||
* @param {K} key
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
delete(key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,13 @@ import {inspect as dump} from 'util';
|
||||
import {node} from '@appium/support';
|
||||
import path from 'path';
|
||||
import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE} from './constants';
|
||||
import os from 'node:os';
|
||||
|
||||
const W3C_APPIUM_PREFIX = 'appium';
|
||||
const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
|
||||
export const V4_BROADCAST_IP = '0.0.0.0';
|
||||
export const V6_BROADCAST_IP = '::';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -223,7 +227,8 @@ function adjustNodePath() {
|
||||
// ! statements in client modules. It uses a private API though,
|
||||
// ! so it could break (maybe, eventually).
|
||||
// See https://gist.github.com/branneman/8048520#7-the-hack
|
||||
// @ts-ignore
|
||||
// @ts-ignore see above comment
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('module').Module._initPaths();
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -330,6 +335,28 @@ export function isPluginCommandArgs(args) {
|
||||
return args.subcommand === PLUGIN_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of IP addresses of the current host.
|
||||
*
|
||||
* @param {4|6|null} family Either 4 to include ipv4 addresses only,
|
||||
* 6 to include ipv6 addresses only, or null to include all of them
|
||||
* @returns {string[]} The list of matched IP addresses
|
||||
*/
|
||||
export function fetchIpAddresses (family = null) {
|
||||
let familyValue = null;
|
||||
// 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6
|
||||
if (family === 4) {
|
||||
familyValue = [4, 'IPv4'];
|
||||
} else if (family === 6) {
|
||||
familyValue = [6, 'IPv6'];
|
||||
}
|
||||
return _.flatMap(_.values(os.networkInterfaces()).filter(Boolean))
|
||||
// @ts-ignore The linter does not understand the above filter
|
||||
.filter(({family}) => !familyValue || familyValue && familyValue.includes(family))
|
||||
// @ts-ignore The linter does not understand the above filter
|
||||
.map(({address}) => address);
|
||||
}
|
||||
|
||||
export {
|
||||
inspect,
|
||||
parseCapsForInnerDriver,
|
||||
@@ -368,21 +395,18 @@ export {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').NSCapabilities<C, Extra>} NSCapabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').NSCapabilities<C>} NSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.57",
|
||||
"version": "2.0.0-rc.1",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,13 +44,14 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build:docs": "node docs/scripts/build-docs.js",
|
||||
"build:docs:preview": "cross-env APPIUM_DOCS_PREVIEW=1 npm run build:docs",
|
||||
"build:docs:assets": "node docs/scripts/copy-assets.js",
|
||||
"build:docs:cli": "node docs/scripts/gen-cli-args-docs.js",
|
||||
"build:docs:reference": "node docs/scripts/build-reference.js",
|
||||
"dev:docs": "npm run build:docs:assets && npm run dev:docs:en",
|
||||
"dev:docs:en": "mkdocs serve -f ./docs/mkdocs-en.yml",
|
||||
"dev:docs:ja": "mkdocs serve -f ./docs/mkdocs-ja.yml",
|
||||
"dev:docs:zh": "mkdocs serve -f ./docs/mkdocs-zh.yml",
|
||||
"dev:docs:en": "appium-docs build --serve --mkdocs-yml ./docs/mkdocs-en.yml",
|
||||
"dev:docs:ja": "appium-docs build --serve --mkdocs-yml ./docs/mkdocs-ja.yml",
|
||||
"dev:docs:zh": "appium-docs build --serve --mkdocs-yml ./docs/mkdocs-zh.yml",
|
||||
"postinstall": "node ./scripts/autoinstall-extensions.js",
|
||||
"publish:docs": "cross-env APPIUM_DOCS_PUBLISH=1 npm run build:docs",
|
||||
"test": "npm run test:unit",
|
||||
@@ -59,17 +60,17 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.3",
|
||||
"@appium/base-plugin": "^2.2.3",
|
||||
"@appium/docutils": "^0.3.0",
|
||||
"@appium/schema": "^0.2.6",
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/types": "^0.10.1",
|
||||
"@appium/base-driver": "^9.3.11",
|
||||
"@appium/base-plugin": "^2.2.11",
|
||||
"@appium/docutils": "^0.4.0",
|
||||
"@appium/schema": "^0.3.0",
|
||||
"@appium/support": "^4.0.2",
|
||||
"@appium/types": "^0.12.0",
|
||||
"@sidvind/better-ajv-errors": "2.1.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/fancy-log": "2.0.0",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/semver": "7.5.0",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.12.0",
|
||||
@@ -77,25 +78,24 @@
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.3.4",
|
||||
"axios": "1.4.0",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "8.1.0",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"longjohn": "0.2.12",
|
||||
"npmlog": "7.0.1",
|
||||
"ora": "5.4.1",
|
||||
"package-changed": "2.0.0",
|
||||
"package-changed": "3.0.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.3.8",
|
||||
"semver": "7.5.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.6.1",
|
||||
"winston": "3.8.2",
|
||||
"type-fest": "3.11.1",
|
||||
"winston": "3.9.0",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.2.1"
|
||||
"yaml": "2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
@@ -105,7 +105,7 @@
|
||||
"access": "public",
|
||||
"tag": "next"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72",
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/main.js"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "7.30.2"
|
||||
"webdriverio": "7.31.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import {exec} from 'teen_process';
|
||||
import {fs, tempDir, util} from '@appium/support';
|
||||
import {fs, system, tempDir, util} from '@appium/support';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import {
|
||||
@@ -24,6 +24,8 @@ const TEST_DRIVER_INVALID_PEERS_DIR = path.dirname(
|
||||
);
|
||||
|
||||
describe('Driver CLI', function () {
|
||||
this.timeout(90000); // some of these tests involve network and can be very slow
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
@@ -96,6 +98,10 @@ describe('Driver CLI', function () {
|
||||
});
|
||||
|
||||
it('should show updates for installed drivers with --updates', async function () {
|
||||
if (system.isWindows()) {
|
||||
// TODO figure out why this isn't working on windows
|
||||
return this.skip();
|
||||
}
|
||||
const versions = /** @type {string[]} */ (
|
||||
JSON.parse(
|
||||
(
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('Plugin CLI', function () {
|
||||
* @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('appium/lib/cli/extension-command').ExtensionList} ExtensionListData
|
||||
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
|
||||
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import {finalizeSchema, registerSchema, resetSchema} from '../../lib/schema/sche
|
||||
import extSchema from '../fixtures/driver-schema';
|
||||
import {resolveFixture} from '../helpers';
|
||||
import _ from 'lodash';
|
||||
import {system} from '@appium/support';
|
||||
|
||||
const resolveConfigFixture = _.partial(resolveFixture, 'config');
|
||||
|
||||
@@ -314,6 +315,10 @@ describe('config file behavior', function () {
|
||||
|
||||
describe('when the config file is invalid JSON', function () {
|
||||
it('should reject with a user-friendly error message', async function () {
|
||||
if (system.isWindows()) {
|
||||
// TODO figure out why this isn't working on windows
|
||||
return this.skip();
|
||||
}
|
||||
await readConfigFile(INVALID_JSON_FILEPATH).should.be.rejectedWith(
|
||||
new RegExp(`${INVALID_JSON_FILEPATH} is invalid`)
|
||||
);
|
||||
|
||||
@@ -182,7 +182,7 @@ export function formatAppiumArgErrorOutput(stderr) {
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
|
||||
* @typedef {import('appium/lib/cli/extension-command').ExtensionList} ExtensionListData
|
||||
* @typedef {import('teen_process').ExecError} ExecError
|
||||
* @typedef {import('teen_process').TeenProcessExecOptions} TeenProcessExecOptions
|
||||
* @typedef {import('teen_process').TeenProcessExecResult<string>} TeenProcessExecResult
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('manifest handling', function () {
|
||||
});
|
||||
|
||||
it('should update the manifest file to the latest schema revision', function () {
|
||||
expect(manifest.schemaRev).to.equal(3);
|
||||
expect(manifest.schemaRev).to.equal(CURRENT_SCHEMA_REV);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1
-3
@@ -11,11 +11,9 @@ export default [
|
||||
ref: 'appium.json#/properties/server/properties/address',
|
||||
},
|
||||
schema: {
|
||||
$comment: 'I think hostname covers both DNS and IPv4...could be wrong',
|
||||
appiumCliAliases: ['a'],
|
||||
default: '0.0.0.0',
|
||||
description: 'IP address to listen on',
|
||||
format: 'hostname',
|
||||
description: 'IPv4/IPv6 address or a hostname to listen on',
|
||||
title: 'address config',
|
||||
type: 'string',
|
||||
},
|
||||
|
||||
@@ -303,7 +303,7 @@ describe('ExtensionConfig', function () {
|
||||
it('should throw', function () {
|
||||
expect(() => config.require('fake')).to.throw(
|
||||
ReferenceError,
|
||||
/^could not find a driver installed at \/some\/path\/node_modules\/flotsam/i
|
||||
/^could not find a driver installed at .+flotsam/i
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import {Manifest} from '../../../lib/extension/manifest';
|
||||
import {migrate} from '../../../lib/extension/manifest-migrations';
|
||||
import {DRIVER_TYPE} from '../../../lib/constants';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('manifest-migrations', function () {
|
||||
describe('when no installPath property present in manifest', function () {
|
||||
it('should trigger refresh', async function () {
|
||||
const manifest = Manifest.getInstance(process.cwd());
|
||||
// do not explicitly set the schema rev lower here, since that will trigger
|
||||
manifest.setExtension(
|
||||
DRIVER_TYPE,
|
||||
'derp',
|
||||
// @ts-expect-error - old manifest version
|
||||
{
|
||||
version: '1.0.0',
|
||||
automationName: 'Derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
installSpec: 'derp',
|
||||
installType: 'local',
|
||||
appiumVersion: '2.0.0',
|
||||
}
|
||||
);
|
||||
|
||||
await expect(migrate(manifest)).to.eventually.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when installPath property present in manifest', function () {
|
||||
it('should not trigger refresh', async function () {
|
||||
const manifest = Manifest.getInstance(process.cwd());
|
||||
// do not explicitly set the schema rev lower here, since that will trigger
|
||||
manifest.setExtension(DRIVER_TYPE, 'derp', {
|
||||
version: '1.0.0',
|
||||
automationName: 'Derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
installPath: '/path/to/thing',
|
||||
installType: 'local',
|
||||
installSpec: 'derp',
|
||||
appiumVersion: '2.0.0',
|
||||
});
|
||||
|
||||
await expect(migrate(manifest)).to.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when an installType is "npm" and the rev is old', function () {
|
||||
it('should trigger refresh', async function () {
|
||||
const manifest = Manifest.getInstance(process.cwd());
|
||||
manifest.setSchemaRev(3); // this will trigger a refresh, but there's no way to tell _why_, which may be bad. YAGNI?
|
||||
manifest.setExtension(DRIVER_TYPE, 'derp', {
|
||||
version: '1.0.0',
|
||||
automationName: 'Derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
installType: 'npm',
|
||||
installPath: '/path/to/thing',
|
||||
installSpec: 'derp',
|
||||
appiumVersion: '2.0.0',
|
||||
});
|
||||
|
||||
await expect(migrate(manifest)).to.eventually.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no installType is "npm"', function () {
|
||||
it('should not trigger refresh', async function () {
|
||||
const manifest = Manifest.getInstance(process.cwd());
|
||||
// do not explicitly set the schema rev lower here, since that will trigger
|
||||
manifest.setExtension(DRIVER_TYPE, 'derp', {
|
||||
version: '1.0.0',
|
||||
automationName: 'Derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
installType: 'local',
|
||||
installPath: '/path/to/thing',
|
||||
installSpec: 'derp',
|
||||
appiumVersion: '2.0.0',
|
||||
});
|
||||
|
||||
await expect(migrate(manifest)).to.eventually.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -40,7 +40,10 @@ describe('Manifest', function () {
|
||||
let overrides;
|
||||
({MockPackageChanged, MockAppiumSupport, MockGlob, overrides, sandbox} = initMocks());
|
||||
MockAppiumSupport.fs.readFile.resolves(yamlFixture);
|
||||
({Manifest} = rewiremock.proxy(() => require('../../../lib/extension/manifest'), overrides));
|
||||
({Manifest} = rewiremock.proxy(() => require('../../../lib/extension/manifest'), {
|
||||
...overrides,
|
||||
'../../../lib/extension/manifest-migrations.js': {migrate: sandbox.stub().resolves()},
|
||||
}));
|
||||
|
||||
Manifest.getInstance.cache = new Map();
|
||||
});
|
||||
|
||||
@@ -59,7 +59,9 @@ describe('parser', function () {
|
||||
}).should.throw(/unrecognized arguments: --apple/i);
|
||||
});
|
||||
|
||||
it('should throw an error for an invalid value ("hostname")', function () {
|
||||
// FIXME: this test will not work until we restore the formatting restriction to the address validation
|
||||
// see #18716
|
||||
it.skip('should throw an error for an invalid value ("hostname")', function () {
|
||||
(() => {
|
||||
p.parseArgs(['--address', '-42']);
|
||||
}).should.throw(/must match format "hostname"/i);
|
||||
@@ -119,6 +121,18 @@ describe('parser', function () {
|
||||
p.parseArgs(['--allow-insecure', 'foo ,bar']).allowInsecure.should.eql(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it('should parse --address correctly', function () {
|
||||
p.parseArgs(['--address', 'localhost']).address.should.eql('localhost');
|
||||
p.parseArgs(['--address', 'appium.net']).address.should.eql('appium.net');
|
||||
p.parseArgs(['--address', '127.0.0.1']).address.should.eql('127.0.0.1');
|
||||
p.parseArgs(['--address', '10.0.0.1']).address.should.eql('10.0.0.1');
|
||||
p.parseArgs(['--address', '::']).address.should.eql('::');
|
||||
p.parseArgs(['--address', '::1']).address.should.eql('::1');
|
||||
p.parseArgs(['--address', '2a02:8888:9a80:158:2418:a474:43c6:1b78']).address.should.eql(
|
||||
'2a02:8888:9a80:158:2418:a474:43c6:1b78'
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse --deny-insecure correctly', function () {
|
||||
p.parseArgs([]).should.not.have.property('denyInsecure');
|
||||
p.parseArgs(['--deny-insecure', '']).denyInsecure.should.eql([]);
|
||||
@@ -264,6 +278,14 @@ describe('parser', function () {
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'list', '--updates']);
|
||||
args.showUpdates.should.eql(true);
|
||||
});
|
||||
it('should allow "ls" as an alias for "list"', function () {
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'ls']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('list');
|
||||
args.showInstalled.should.eql(false);
|
||||
args.showUpdates.should.eql(false);
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
});
|
||||
describe('install', function () {
|
||||
it('should not allow an empty argument list', function () {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
removeAppiumPrefixes,
|
||||
inspect,
|
||||
adjustNodePath,
|
||||
fetchIpAddresses,
|
||||
} from '../../lib/utils';
|
||||
import {BASE_CAPS, W3C_CAPS} from '../helpers';
|
||||
import _ from 'lodash';
|
||||
@@ -275,4 +276,19 @@ describe('utils', function () {
|
||||
(await fs.exists(process.env.NODE_PATH)).should.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchIpAddresses()', function () {
|
||||
it('should fetch addresses for ipv4 only', async function () {
|
||||
fetchIpAddresses(4).length.should.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should fetch addresses for ipv6 only', async function () {
|
||||
fetchIpAddresses(6).length.should.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should fetch addresses for ipv4 and ipv6', async function () {
|
||||
fetchIpAddresses().length.should.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
|
||||
import * as ManifestV2 from './base';
|
||||
import * as ManifestV3 from './v3';
|
||||
|
||||
import * as ManifestV4 from './v4';
|
||||
// add `import * as ManifestV<new-version> from './v<new-version>';` above
|
||||
|
||||
export * from './v3';
|
||||
export * from './v4';
|
||||
// replace above line with `export * from './v<new-version>';`
|
||||
|
||||
export {ManifestV2, ManifestV3};
|
||||
export {ManifestV2, ManifestV3, ManifestV4};
|
||||
|
||||
export interface ManifestDataVersions {
|
||||
2: ManifestV2.ManifestData;
|
||||
3: ManifestV3.ManifestData;
|
||||
4: ManifestV4.ManifestData;
|
||||
}
|
||||
// append to this interface your new version of `ManifestData`
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import {DriverType, ExtensionType, PluginType} from '@appium/types';
|
||||
import {SchemaObject} from 'ajv';
|
||||
import {PackageJson, SetRequired} from 'type-fest';
|
||||
|
||||
/**
|
||||
* One of the possible extension installation stratgies
|
||||
*/
|
||||
export type InstallType = 'npm' | 'git' | 'local' | 'github' | 'dev';
|
||||
|
||||
export interface InternalMetadata {
|
||||
/**
|
||||
* Package name of extension
|
||||
*
|
||||
* `name` from its `package.json`
|
||||
*/
|
||||
pkgName: string;
|
||||
/**
|
||||
* Version of extension
|
||||
*
|
||||
* `version` from its `package.json`
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* The method in which the user installed the extension (the `source` CLI arg)
|
||||
*/
|
||||
installType: InstallType;
|
||||
/**
|
||||
* Whatever the user typed as the extension to install. May be derived from `package.json`
|
||||
*/
|
||||
installSpec: string;
|
||||
/**
|
||||
* Maximum version of Appium that this extension is compatible with.
|
||||
*
|
||||
* If `undefined`, we'll try anyway.
|
||||
*/
|
||||
appiumVersion?: string;
|
||||
/**
|
||||
* Path to the extension's root directory
|
||||
*/
|
||||
installPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape of the `appium.schema` property in an extension's `package.json` (if it exists)
|
||||
*/
|
||||
export type ExtSchemaMetadata = string | (SchemaObject & {[key: number]: never});
|
||||
|
||||
/**
|
||||
* Manifest data shared by all extensions, as contained in `package.json`
|
||||
*/
|
||||
export interface CommonExtMetadata {
|
||||
/**
|
||||
* The main class of the extension.
|
||||
*
|
||||
* The extension must export this class by name.
|
||||
*/
|
||||
mainClass: string;
|
||||
|
||||
/**
|
||||
* Lookup table of scripts to run via `appium <driver|plugin> run <script>` keyed by name.
|
||||
*/
|
||||
scripts?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* Schema describing configuration options (and CLI args) for the extension.
|
||||
*
|
||||
* Can also just be a path (relative to the extension root) to an external JSON schema file.
|
||||
*/
|
||||
schema?: ExtSchemaMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver-specific manifest data as stored in a driver's `package.json`
|
||||
*/
|
||||
export interface DriverMetadata {
|
||||
/**
|
||||
* Automation name of the driver
|
||||
*/
|
||||
automationName: string;
|
||||
/**
|
||||
* Platforms the driver supports
|
||||
*/
|
||||
platformNames: string[];
|
||||
/**
|
||||
* Short name of the driver (displayed in `appium list`, etc.)
|
||||
*/
|
||||
driverName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin-specific manifest data as stored in a plugin's `package.json`
|
||||
*/
|
||||
export interface PluginMetadata {
|
||||
/**
|
||||
* Short name of the plugin (displayed in `appium list`, etc.)
|
||||
*/
|
||||
pluginName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic extension metadata as stored in the `appium` prop of an extension's `package.json`.
|
||||
*/
|
||||
export type ExtMetadata<ExtType extends ExtensionType> = (ExtType extends DriverType
|
||||
? DriverMetadata
|
||||
: ExtType extends PluginType
|
||||
? PluginMetadata
|
||||
: never) &
|
||||
CommonExtMetadata;
|
||||
|
||||
/**
|
||||
* Combination of external + internal extension data with `driverName`/`pluginName` removed (it becomes a key in an {@linkcode ExtRecord} object).
|
||||
* Part of `extensions.yaml`.
|
||||
*/
|
||||
export type ExtManifest<ExtType extends ExtensionType> = Omit<
|
||||
ExtMetadata<ExtType>,
|
||||
'driverName' | 'pluginName'
|
||||
> &
|
||||
InternalMetadata;
|
||||
|
||||
/**
|
||||
* Lookup of extension name to {@linkcode ExtManifest}.
|
||||
* @see {ManifestData}
|
||||
*/
|
||||
export type ExtRecord<ExtType extends ExtensionType> = Record<string, ExtManifest<ExtType>>;
|
||||
|
||||
/**
|
||||
* The shape of the `extensions.yaml` file
|
||||
*/
|
||||
export interface ManifestData {
|
||||
drivers: ExtRecord<DriverType>;
|
||||
plugins: ExtRecord<PluginType>;
|
||||
schemaRev: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of an installed extension, as it appears in `extensions.yaml`
|
||||
* (as a property name under `drivers` or `plugins`)
|
||||
*/
|
||||
export type ExtName<ExtType extends ExtensionType> = keyof ExtRecord<ExtType>;
|
||||
|
||||
/**
|
||||
* A `package.json` containing extension metadata.
|
||||
* Must have the following properties:
|
||||
* - `name`: the name of the extension
|
||||
* - `version`: the version of the extension
|
||||
* - `appium`: the metadata for the extension
|
||||
* - `peerDependencies.appium`: the maximum compatible version of Appium
|
||||
*/
|
||||
export type ExtPackageJson<ExtType extends ExtensionType> = SetRequired<
|
||||
PackageJson,
|
||||
'name' | 'version'
|
||||
> & {
|
||||
appium: ExtMetadata<ExtType>;
|
||||
peerDependencies: {appium: string; [key: string]: string};
|
||||
};
|
||||
|
||||
/**
|
||||
* A transient format between installation and insertion of extension metadata into the manifest.
|
||||
*/
|
||||
export type ExtInstallReceipt<ExtType extends ExtensionType> = ExtMetadata<ExtType> &
|
||||
InternalMetadata;
|
||||
@@ -3,6 +3,98 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.3.11](https://github.com/appium/appium/compare/@appium/base-driver@9.3.10...@appium/base-driver@9.3.11) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **schema:** update definitelytyped ([e967240](https://github.com/appium/appium/commit/e96724077ef2b5b8aae203856195f3bb8de56116))
|
||||
* **types:** update dependency type-fest to v3.11.0 ([19277f6](https://github.com/appium/appium/commit/19277f6e14a56e52b4669d633e148ad4a3da2c7a))
|
||||
* **types:** update dependency type-fest to v3.11.1 ([56499eb](https://github.com/appium/appium/commit/56499eb997b551739bed628f057de7987674ea7f))
|
||||
|
||||
|
||||
|
||||
## [9.3.10](https://github.com/appium/appium/compare/@appium/base-driver@9.3.9...@appium/base-driver@9.3.10) (2023-05-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** add missing @types/lodash ([63a429c](https://github.com/appium/appium/commit/63a429c53bf3da353e018ed86ec24e32bf238d18))
|
||||
|
||||
|
||||
|
||||
## [9.3.9](https://github.com/appium/appium/compare/@appium/base-driver@9.3.8...@appium/base-driver@9.3.9) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.8](https://github.com/appium/appium/compare/@appium/base-driver@9.3.7...@appium/base-driver@9.3.8) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** Ignore unknown script arguments ([#18575](https://github.com/appium/appium/issues/18575)) ([4f564ef](https://github.com/appium/appium/commit/4f564ef7ad350831b90994ffd4e8b00de227577d))
|
||||
* **support:** update dependency axios to v1.3.6 ([6692227](https://github.com/appium/appium/commit/66922279b7742a08613f472585a4a1cb70f80683))
|
||||
* **support:** update dependency axios to v1.4.0 ([91a6bc5](https://github.com/appium/appium/commit/91a6bc5925ab8ffc4ab6d05883900f7d186e49a9))
|
||||
* **types:** update dependency type-fest to v3.10.0 ([3c4d3ac](https://github.com/appium/appium/commit/3c4d3acc09d2ca1ed74dc77c18c62482e4c70239))
|
||||
* **types:** update dependency type-fest to v3.9.0 ([94a207f](https://github.com/appium/appium/commit/94a207fc9718068f3657c51cc8be0ef682f16b11))
|
||||
|
||||
|
||||
|
||||
## [9.3.7](https://github.com/appium/appium/compare/@appium/base-driver@9.3.6...@appium/base-driver@9.3.7) (2023-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **basedriver:** allow arbitrary session data to be returned by getSession ([6245022](https://github.com/appium/appium/commit/6245022be953b69acad70d0a4c877bc65eed2d3a))
|
||||
|
||||
|
||||
|
||||
## [9.3.6](https://github.com/appium/appium/compare/@appium/base-driver@9.3.5...@appium/base-driver@9.3.6) (2023-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency axios to v1.3.5 ([6cf1480](https://github.com/appium/appium/commit/6cf14802b70a462beffc12a1134476596060c005))
|
||||
* **types:** update dependency type-fest to v3.8.0 ([d6c42e9](https://github.com/appium/appium/commit/d6c42e99c08efce0b34796d5982ce379fca044d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.5](https://github.com/appium/appium/compare/@appium/base-driver@9.3.4...@appium/base-driver@9.3.5) (2023-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.7.2 ([5580539](https://github.com/appium/appium/commit/55805390b5a0c6aa718bb357b30f66651f3db281))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.4](https://github.com/appium/appium/compare/@appium/base-driver@9.3.3...@appium/base-driver@9.3.4) (2023-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,types,base-driver,fake-driver,driver-test-support:** normalize constraint defaults ([3c9fa7b](https://github.com/appium/appium/commit/3c9fa7ba73b639e610e1f3d41d239a9402845b4c))
|
||||
* backwards-compatible fixes for TS v5.x ([4974403](https://github.com/appium/appium/commit/49744036619ecc239e0e6255a13d38cafd709920))
|
||||
* **base-driver,base-plugin,types:** update PluginCommand and DriverCommand types ([0dcd5fa](https://github.com/appium/appium/commit/0dcd5fa371af523c6527e55de4cff6cd472fde22))
|
||||
* **base-driver,types:** fix websocket-related types on AppiumServer ([34891f5](https://github.com/appium/appium/commit/34891f56572f18dd740558b2348d8818680dc709))
|
||||
* **base-driver:** fix timeout mixin to use proper context types ([51a8f2f](https://github.com/appium/appium/commit/51a8f2fc347f0b4e222ca2e8dc92fa4bdf44d1e3))
|
||||
* **base-driver:** misc type fixes ([d303527](https://github.com/appium/appium/commit/d303527c3da4f657a28b97a2d82eb1a709c6b9bc))
|
||||
* **basedriver:** convert main driver implementation to typescript ([deb631b](https://github.com/appium/appium/commit/deb631b4562a0f99e4061c89a7ad21b0621f8a47)), closes [#18379](https://github.com/appium/appium/issues/18379) [#18379](https://github.com/appium/appium/issues/18379)
|
||||
* **types:** update dependency type-fest to v3.7.0 ([6912fa1](https://github.com/appium/appium/commit/6912fa14f2a7d338f17e1bed060e959de7aba1d6))
|
||||
* **types:** update dependency type-fest to v3.7.1 ([bc860c7](https://github.com/appium/appium/commit/bc860c733a73760f0c42cbfb384e04d50c376d5e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.3](https://github.com/appium/appium/compare/@appium/base-driver@9.3.2...@appium/base-driver@9.3.3) (2023-03-08)
|
||||
|
||||
|
||||
|
||||
@@ -11,19 +11,24 @@ const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}options`;
|
||||
|
||||
/**
|
||||
* Takes primary caps object and merges it into a secondary caps object.
|
||||
* @template {Constraints} [T={}]
|
||||
* @template {Constraints} [U={}]
|
||||
* @param {Capabilities<T>} [primary]
|
||||
* @param {Capabilities<U>} [secondary]
|
||||
* @returns {Merge<Capabilities<T>, Capabilities<U>>}
|
||||
* @template {Constraints} T
|
||||
* @template {Constraints} U
|
||||
* @template {Capabilities<T>} Primary
|
||||
* @template {Capabilities<U>} Secondary
|
||||
* @param {Primary} [primary]
|
||||
* @param {Secondary} [secondary]
|
||||
* @returns {MergeExclusive<Primary, Secondary>}
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-merging-capabilities)
|
||||
*/
|
||||
function mergeCaps(primary = {}, secondary = {}) {
|
||||
let result = /** @type {Merge<Capabilities<T>, Capabilities<U>>} */ ({
|
||||
function mergeCaps(
|
||||
primary = /** @type {Primary} */ ({}),
|
||||
secondary = /** @type {Secondary} */ ({})
|
||||
) {
|
||||
let result = /** @type {MergeExclusive<Primary, Secondary>} */ ({
|
||||
...primary,
|
||||
});
|
||||
|
||||
for (let [name, value] of /** @type {[keyof typeof secondary, any]} */ (_.toPairs(secondary))) {
|
||||
for (let [name, value] of Object.entries(secondary)) {
|
||||
// Overwriting is not allowed. Primary and secondary must have different properties (w3c rule 4.4)
|
||||
if (!_.isUndefined(primary[name])) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
@@ -38,9 +43,9 @@ function mergeCaps(primary = {}, secondary = {}) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Validates caps against a set of constraints
|
||||
/**
|
||||
* @template {Constraints} [C={}]
|
||||
* Validates caps against a set of constraints
|
||||
* @template {Constraints} C
|
||||
* @param {Capabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {ValidateCapsOpts} [opts]
|
||||
@@ -121,11 +126,11 @@ function isStandardCap(cap) {
|
||||
|
||||
/**
|
||||
* If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').NSCapabilities<C>} caps
|
||||
* @template {Constraints} C
|
||||
* @param {NSCapabilities<C>} caps
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities
|
||||
* @internal
|
||||
* @returns {import('@appium/types').Capabilities<C>}
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function stripAppiumPrefixes(caps) {
|
||||
// split into prefixed and non-prefixed.
|
||||
@@ -135,14 +140,12 @@ function stripAppiumPrefixes(caps) {
|
||||
);
|
||||
|
||||
// initialize this with the k/v pairs of the non-prefixed caps
|
||||
let strippedCaps = /** @type {import('@appium/types').Capabilities<C>} */ (
|
||||
_.pick(caps, nonPrefixedCaps)
|
||||
);
|
||||
let strippedCaps = /** @type {Capabilities<C>} */ (_.pick(caps, nonPrefixedCaps));
|
||||
const badPrefixedCaps = [];
|
||||
|
||||
// Strip out the 'appium:' prefix
|
||||
for (let prefixedCap of prefixedCaps) {
|
||||
const strippedCapName = /** @type {StringKeyOf<import('@appium/types').Capabilities<C>>} */ (
|
||||
const strippedCapName = /** @type {StringKeyOf<Capabilities<C>>} */ (
|
||||
prefixedCap.substring(APPIUM_VENDOR_PREFIX.length)
|
||||
);
|
||||
|
||||
@@ -175,8 +178,8 @@ function stripAppiumPrefixes(caps) {
|
||||
|
||||
/**
|
||||
* Get an array of all the unprefixed caps that are being used in 'alwaysMatch' and all of the 'firstMatch' object
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} caps A capabilities object
|
||||
* @template {Constraints} C
|
||||
* @param {W3CCapabilities<C>} caps A capabilities object
|
||||
*/
|
||||
function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
return _.chain([alwaysMatch, ...firstMatch])
|
||||
@@ -191,13 +194,25 @@ function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by {@linkcode parseCaps}
|
||||
* @template {Constraints} C
|
||||
* @typedef ParsedCaps
|
||||
* @property {NSCapabilities<C>[]} allFirstMatchCaps
|
||||
* @property {Capabilities<C>[]} validatedFirstMatchCaps
|
||||
* @property {NSCapabilities<C>} requiredCaps
|
||||
* @property {Capabilities<C>|null} matchedCaps
|
||||
* @property {string[]} validationErrors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse capabilities
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} caps
|
||||
* @template {Constraints} C
|
||||
* @param {W3CCapabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @see https://www.w3.org/TR/webdriver/#processing-capabilities
|
||||
* @returns {ParsedCaps<C>}
|
||||
*/
|
||||
function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps = true) {
|
||||
// If capabilities request is not an object, return error (#1.1)
|
||||
@@ -210,8 +225,8 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
// Let 'requiredCaps' be property named 'alwaysMatch' from capabilities request (#2)
|
||||
// and 'allFirstMatchCaps' be property named 'firstMatch' from capabilities request (#3)
|
||||
let {
|
||||
alwaysMatch: requiredCaps = {}, // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1)
|
||||
firstMatch: allFirstMatchCaps = [{}], // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1)
|
||||
alwaysMatch: requiredCaps = /** @type {NSCapabilities<C>} */ ({}), // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1)
|
||||
firstMatch: allFirstMatchCaps = /** @type {NSCapabilities<C>[]} */ ([{}]), // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1)
|
||||
} = caps;
|
||||
|
||||
// Reject 'firstMatch' argument if it's not an array (#3.2)
|
||||
@@ -241,6 +256,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
|
||||
// Strip out the 'appium:' prefix from all
|
||||
let strippedRequiredCaps = stripAppiumPrefixes(requiredCaps);
|
||||
/** @type {Capabilities<C>[]} */
|
||||
let strippedAllFirstMatchCaps = allFirstMatchCaps.map(stripAppiumPrefixes);
|
||||
|
||||
// Validate the requiredCaps. But don't validate 'presence' because if that constraint fails on 'alwaysMatch' it could still pass on one of the 'firstMatch' keys
|
||||
@@ -259,24 +275,23 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
/** @type {string[]} */
|
||||
let validationErrors = [];
|
||||
let validatedFirstMatchCaps = _.compact(
|
||||
strippedAllFirstMatchCaps.map(
|
||||
/**
|
||||
* @param {import('@appium/types').Capabilities<C>} firstMatchCaps
|
||||
*/
|
||||
(firstMatchCaps) => {
|
||||
try {
|
||||
// Validate firstMatch caps
|
||||
return shouldValidateCaps
|
||||
? validateCaps(firstMatchCaps, filteredConstraints)
|
||||
: firstMatchCaps;
|
||||
} catch (e) {
|
||||
validationErrors.push(e.message);
|
||||
}
|
||||
strippedAllFirstMatchCaps.map((firstMatchCaps) => {
|
||||
try {
|
||||
// Validate firstMatch caps
|
||||
return shouldValidateCaps
|
||||
? validateCaps(firstMatchCaps, filteredConstraints)
|
||||
: firstMatchCaps;
|
||||
} catch (e) {
|
||||
validationErrors.push(e.message);
|
||||
}
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
// Try to merge requiredCaps with first match capabilities, break once it finds its first match (see spec #6)
|
||||
/**
|
||||
* Try to merge requiredCaps with first match capabilities, break once it finds its first match
|
||||
* (see spec #6)
|
||||
* @type {ParsedCaps<C>['matchedCaps']}
|
||||
*/
|
||||
let matchedCaps = null;
|
||||
for (let firstMatchCaps of validatedFirstMatchCaps) {
|
||||
try {
|
||||
@@ -300,13 +315,14 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
};
|
||||
}
|
||||
|
||||
// Calls parseCaps and just returns the matchedCaps variable
|
||||
/**
|
||||
* Calls parseCaps and just returns the matchedCaps variable
|
||||
* @template {Constraints} C
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} w3cCaps
|
||||
* @template {W3CCapabilities<C>} W3CCaps
|
||||
* @param {W3CCaps} w3cCaps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @returns {import('@appium/types').Capabilities<C>}
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function processCapabilities(
|
||||
w3cCaps,
|
||||
@@ -400,8 +416,8 @@ function promoteAppiumOptionsForObject(obj) {
|
||||
* capability and promoted it to the top level.
|
||||
*
|
||||
* @template {Constraints} C
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} originalCaps
|
||||
* @return {import('@appium/types').W3CCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
* @param {W3CCapabilities<C>} originalCaps
|
||||
* @return {W3CCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
*/
|
||||
function promoteAppiumOptions(originalCaps) {
|
||||
const result = {};
|
||||
@@ -451,28 +467,26 @@ export {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').NSCapabilities<C, Extra>} NSCapabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').NSCapabilities<C>} NSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T,U
|
||||
* @typedef {import('type-fest').Merge<T,U>} Merge
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('type-fest').StringKeyOf<T>} StringKeyOf
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T,U
|
||||
* @typedef {import('type-fest').MergeExclusive<T, U>} MergeExclusive<T,U>
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import {Constraints, IEventCommands} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
import _ from 'lodash';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends IEventCommands {}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {mixin} from './mixin';
|
||||
import {BaseDriver} from '../driver';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends IExecuteCommands {}
|
||||
}
|
||||
|
||||
@@ -19,7 +20,7 @@ const ExecuteCommands: IExecuteCommands = {
|
||||
async executeMethod<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
script: string,
|
||||
protoArgs: [StringRecord] | any[]
|
||||
protoArgs: readonly [StringRecord<unknown>] | readonly unknown[]
|
||||
) {
|
||||
const Driver = this.constructor as DriverClass<Driver<C>>;
|
||||
const commandMetadata = {...Driver.executeMethodMap?.[script]};
|
||||
|
||||
@@ -4,6 +4,7 @@ import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends IFindCommands {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export * from './event';
|
||||
export * from './find';
|
||||
export * from './log';
|
||||
export * from './session';
|
||||
export * from './settings';
|
||||
export * from './timeout';
|
||||
export * from './execute';
|
||||
import './event';
|
||||
import './find';
|
||||
import './log';
|
||||
import './settings';
|
||||
import './timeout';
|
||||
import './execute';
|
||||
|
||||
@@ -4,6 +4,7 @@ import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends ILogCommands {}
|
||||
}
|
||||
|
||||
@@ -15,10 +16,7 @@ const LogCommands: ILogCommands = {
|
||||
return Object.keys(this.supportedLogTypes);
|
||||
},
|
||||
|
||||
async getLog<C extends Constraints>(
|
||||
this: Driver<C>,
|
||||
logType: keyof ILogCommands['supportedLogTypes']
|
||||
) {
|
||||
async getLog<C extends Constraints>(this: Driver<C>, logType: string) {
|
||||
this.log.debug(`Retrieving '${String(logType)}' logs`);
|
||||
|
||||
if (!(logType in this.supportedLogTypes)) {
|
||||
|
||||
@@ -2,13 +2,14 @@ import {Constraints} from '@appium/types';
|
||||
import {BaseDriver} from '../driver';
|
||||
|
||||
/**
|
||||
* This function assigns a mixin `T` to the `FakeDriver` class' prototype.
|
||||
* While each mixin has its own interface which is (in isolation) unrelated to `FakeDriver`, the constraint
|
||||
* on this generic type `T` is that it must be a partial of `FakeDriver`'s interface. This enforces
|
||||
* that it does not conflict with the existing interface of `FakeDriver`. In that way, you can
|
||||
* This function assigns a mixin `T` to the `BaseDriver` class' prototype.
|
||||
* While each mixin has its own interface which is (in isolation) unrelated to `BaseDriver`, the constraint
|
||||
* on this generic type `T` is that it must be a partial of `BaseDriver`'s interface. This enforces
|
||||
* that it does not conflict with the existing interface of `BaseDriver`. In that way, you can
|
||||
* think of it as a type guard.
|
||||
* @param mixin Mixin implementation
|
||||
*/
|
||||
export function mixin<C extends Constraints, T extends Partial<BaseDriver<C>>>(mixin: T): void {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
Object.assign(BaseDriver.prototype, mixin);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import {Constraints, ISessionCommands, MultiSessionData} from '@appium/types';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends ISessionCommands {}
|
||||
}
|
||||
|
||||
const SessionCommands: ISessionCommands = {
|
||||
async getSessions<C extends Constraints>(this: BaseDriver<C>) {
|
||||
const ret: MultiSessionData[] = [];
|
||||
|
||||
if (this.sessionId) {
|
||||
ret.push({
|
||||
id: this.sessionId,
|
||||
capabilities: this.caps,
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
*/
|
||||
async getSession<C extends Constraints>(this: BaseDriver<C>) {
|
||||
if (this.caps.eventTimings) {
|
||||
return {...this.caps, events: this.eventHistory};
|
||||
}
|
||||
return this.caps;
|
||||
},
|
||||
};
|
||||
|
||||
mixin(SessionCommands);
|
||||
@@ -3,6 +3,7 @@ import {Constraints, ISettingsCommands, StringRecord} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends ISettingsCommands {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
import {waitForCondition} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {util} from '@appium/support';
|
||||
@@ -11,13 +7,14 @@ import {Constraints, ITimeoutCommands} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface BaseDriver<C extends Constraints> extends ITimeoutCommands {}
|
||||
}
|
||||
|
||||
const MIN_TIMEOUT = 0;
|
||||
|
||||
const TimeoutCommands: ITimeoutCommands = {
|
||||
async timeouts(type, ms, script, pageLoad, implicit) {
|
||||
async timeouts<C extends Constraints>(this: BaseDriver<C>, type, ms, script, pageLoad, implicit) {
|
||||
if (util.hasValue(type) && util.hasValue(ms)) {
|
||||
this.log.debug(`MJSONWP timeout arguments: ${JSON.stringify({type, ms})}}`);
|
||||
|
||||
@@ -66,42 +63,42 @@ const TimeoutCommands: ITimeoutCommands = {
|
||||
},
|
||||
|
||||
// implicit
|
||||
async implicitWaitW3C(ms) {
|
||||
async implicitWaitW3C<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
await this.implicitWait(ms);
|
||||
},
|
||||
|
||||
async implicitWaitMJSONWP(ms) {
|
||||
async implicitWaitMJSONWP<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
await this.implicitWait(ms);
|
||||
},
|
||||
|
||||
async implicitWait(ms) {
|
||||
await this.setImplicitWait(this.parseTimeoutArgument(ms));
|
||||
async implicitWait<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
this.setImplicitWait(this.parseTimeoutArgument(ms));
|
||||
},
|
||||
|
||||
// pageLoad
|
||||
async pageLoadTimeoutW3C(ms) {
|
||||
async pageLoadTimeoutW3C<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
},
|
||||
|
||||
async pageLoadTimeoutMJSONWP(ms) {
|
||||
async pageLoadTimeoutMJSONWP<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
},
|
||||
|
||||
// script
|
||||
async scriptTimeoutW3C(ms) {
|
||||
async scriptTimeoutW3C<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
},
|
||||
|
||||
async scriptTimeoutMJSONWP(ms) {
|
||||
async scriptTimeoutMJSONWP<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
},
|
||||
|
||||
// command
|
||||
async newCommandTimeout(ms) {
|
||||
async newCommandTimeout<C extends Constraints>(this: BaseDriver<C>, ms) {
|
||||
this.setNewCommandTimeout(this.parseTimeoutArgument(ms));
|
||||
},
|
||||
|
||||
setImplicitWait(ms) {
|
||||
setImplicitWait<C extends Constraints>(this: BaseDriver<C>, ms: number) {
|
||||
// eslint-disable-line require-await
|
||||
this.implicitWaitMs = ms;
|
||||
this.log.debug(`Set implicit wait to ${ms}ms`);
|
||||
@@ -115,7 +112,7 @@ const TimeoutCommands: ITimeoutCommands = {
|
||||
}
|
||||
},
|
||||
|
||||
setNewCommandTimeout(ms) {
|
||||
setNewCommandTimeout<C extends Constraints>(this: BaseDriver<C>, ms: number) {
|
||||
this.newCommandTimeoutMs = ms;
|
||||
this.log.debug(`Set new command timeout to ${ms}ms`);
|
||||
if (this.managedDrivers && this.managedDrivers.length) {
|
||||
@@ -128,7 +125,10 @@ const TimeoutCommands: ITimeoutCommands = {
|
||||
}
|
||||
},
|
||||
|
||||
async implicitWaitForCondition(condFn) {
|
||||
async implicitWaitForCondition<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
condFn: (...args: any[]) => Promise<any>
|
||||
) {
|
||||
this.log.debug(`Waiting up to ${this.implicitWaitMs} ms for condition`);
|
||||
const wrappedCondFn = async (...args: any[]) => {
|
||||
// reset command timeout
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
|
||||
import {fs, logger} from '@appium/support';
|
||||
import {logger} from '@appium/support';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {EventEmitter} from 'events';
|
||||
import _ from 'lodash';
|
||||
@@ -9,18 +9,16 @@ import os from 'os';
|
||||
import {DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
||||
import {errors} from '../protocol';
|
||||
import DeviceSettings from './device-settings';
|
||||
import helpers from './helpers';
|
||||
|
||||
// for compat with running tests transpiled and in-place
|
||||
const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
|
||||
import helpers, {BASEDRIVER_VER} from './helpers';
|
||||
|
||||
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @implements {Core<C>}
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [Settings=StringRecord]
|
||||
* @implements {Core<C, Settings>}
|
||||
*/
|
||||
class DriverCore {
|
||||
/**
|
||||
@@ -40,7 +38,7 @@ class DriverCore {
|
||||
opts;
|
||||
|
||||
/**
|
||||
* @type {ServerArgs}
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
*/
|
||||
initialOpts;
|
||||
|
||||
@@ -98,7 +96,6 @@ class DriverCore {
|
||||
_log;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
shutdownUnexpectedly;
|
||||
@@ -119,7 +116,7 @@ class DriverCore {
|
||||
* we set it to an empty DeviceSettings instance here to make sure that the
|
||||
* default settings are applied even if an extending driver doesn't utilize
|
||||
* the settings functionality itself
|
||||
* @type {DeviceSettings}
|
||||
* @type {DeviceSettings<Settings>}
|
||||
*/
|
||||
settings;
|
||||
|
||||
@@ -184,11 +181,9 @@ class DriverCore {
|
||||
* specific driver sessions. This data can be later used to adjust
|
||||
* properties for driver instances running in parallel.
|
||||
* Override it in inherited driver classes if necessary.
|
||||
*
|
||||
* @return {Record<string,unknown>} Driver properties mapping
|
||||
*/
|
||||
get driverData() {
|
||||
return {};
|
||||
return /** @type {import('@appium/types').DriverData} */ ({});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +255,7 @@ class DriverCore {
|
||||
* @returns {Core<C> | null}
|
||||
*/
|
||||
driverForSession(sessionId) {
|
||||
return this;
|
||||
return /** @type {Core<C> | null} */ (this);
|
||||
}
|
||||
|
||||
isMjsonwpProtocol() {
|
||||
@@ -448,19 +443,10 @@ export {DriverCore};
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
/**
|
||||
* @template {StringRecord} [T={}]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<T>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Core<C>} Core
|
||||
* @template {StringRecord} [S=StringRecord]
|
||||
* @typedef {import('@appium/types').Core<C, S>} Core
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,13 +9,13 @@ import {errors} from '../protocol/errors';
|
||||
export const MAX_SETTINGS_SIZE = 20 * 1024 * 1024; // 20 MB
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template {import('@appium/types').StringRecord} T
|
||||
* @implements {IDeviceSettings<T>}
|
||||
*/
|
||||
class DeviceSettings {
|
||||
export class DeviceSettings {
|
||||
/**
|
||||
* @protected
|
||||
* @type {Record<string,T>}
|
||||
* @type {T}
|
||||
*/
|
||||
_settings;
|
||||
|
||||
@@ -27,17 +27,17 @@ class DeviceSettings {
|
||||
|
||||
/**
|
||||
* Creates a _shallow copy_ of the `defaultSettings` parameter!
|
||||
* @param {Record<string,T>} [defaultSettings]
|
||||
* @param {T} [defaultSettings]
|
||||
* @param {import('@appium/types').SettingsUpdateListener<T>} [onSettingsUpdate]
|
||||
*/
|
||||
constructor(defaultSettings = {}, onSettingsUpdate = async () => {}) {
|
||||
constructor(defaultSettings = /** @type {T} */ ({}), onSettingsUpdate = async () => {}) {
|
||||
this._settings = {...defaultSettings};
|
||||
this._onSettingsUpdate = onSettingsUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls updateSettings from implementing driver every time a setting is changed.
|
||||
* @param {Record<string,T>} newSettings
|
||||
* @param {T} newSettings
|
||||
*/
|
||||
async update(newSettings) {
|
||||
if (!_.isPlainObject(newSettings)) {
|
||||
@@ -54,8 +54,7 @@ class DeviceSettings {
|
||||
);
|
||||
}
|
||||
|
||||
const props = /** @type {(keyof T & string)[]} */ (_.keys(newSettings));
|
||||
for (const prop of props) {
|
||||
for (const prop in newSettings) {
|
||||
if (!_.isUndefined(this._settings[prop])) {
|
||||
if (this._settings[prop] === newSettings[prop]) {
|
||||
log.debug(`The value of '${prop}' setting did not change. Skipping the update for it`);
|
||||
@@ -73,9 +72,8 @@ class DeviceSettings {
|
||||
}
|
||||
|
||||
export default DeviceSettings;
|
||||
export {DeviceSettings};
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('@appium/types').DeviceSettings<T>} IDeviceSettings
|
||||
* @template {import('@appium/types').StringRecord} [T=import('@appium/types').StringRecord]
|
||||
* @typedef {import('@appium/types').IDeviceSettings<T>} IDeviceSettings
|
||||
*/
|
||||
|
||||
+87
-171
@@ -1,6 +1,3 @@
|
||||
/* eslint-disable require-await */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import {validateCaps, processCapabilities} from './capabilities';
|
||||
import {DriverCore} from './core';
|
||||
import {util} from '@appium/support';
|
||||
@@ -9,7 +6,23 @@ import _ from 'lodash';
|
||||
import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
||||
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
||||
import helpers from './helpers';
|
||||
import {BASE_DESIRED_CAP_CONSTRAINTS} from '@appium/types';
|
||||
import {
|
||||
AppiumServer,
|
||||
BaseDriverCapConstraints,
|
||||
BASE_DESIRED_CAP_CONSTRAINTS,
|
||||
Capabilities,
|
||||
Constraints,
|
||||
DefaultCreateSessionResult,
|
||||
Driver,
|
||||
DriverCaps,
|
||||
DriverData,
|
||||
DriverOpts,
|
||||
MultiSessionData,
|
||||
ServerArgs,
|
||||
SingularSessionData,
|
||||
StringRecord,
|
||||
W3CDriverCaps,
|
||||
} from '@appium/types';
|
||||
|
||||
const EVENT_SESSION_INIT = 'newSessionRequested';
|
||||
const EVENT_SESSION_START = 'newSessionStarted';
|
||||
@@ -17,56 +30,32 @@ const EVENT_SESSION_QUIT_START = 'quitSessionRequested';
|
||||
const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @implements {SessionHandler<C>}
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [CArgs=StringRecord]
|
||||
* @implements {Driver<C, CArgs>}
|
||||
* @extends {DriverCore<C>}
|
||||
*/
|
||||
export class BaseDriver extends DriverCore {
|
||||
/**
|
||||
* @type {CArgs & ServerArgs}
|
||||
*/
|
||||
cliArgs;
|
||||
export class BaseDriver<
|
||||
C extends Constraints,
|
||||
CArgs extends StringRecord = StringRecord,
|
||||
Settings extends StringRecord = StringRecord,
|
||||
SessionData extends StringRecord = StringRecord
|
||||
>
|
||||
extends DriverCore<C, Settings>
|
||||
implements Driver<C, CArgs>
|
||||
{
|
||||
cliArgs: CArgs & ServerArgs;
|
||||
|
||||
/**
|
||||
* @type {Capabilities<C>}
|
||||
*/
|
||||
caps;
|
||||
caps: DriverCaps<C>;
|
||||
originalCaps: W3CDriverCaps<C>;
|
||||
desiredCapConstraints: C;
|
||||
opts: DriverOpts<C>;
|
||||
server?: AppiumServer;
|
||||
serverHost?: string;
|
||||
serverPort?: number;
|
||||
serverPath?: string;
|
||||
|
||||
/**
|
||||
* @type {W3CCapabilities<C>}
|
||||
*/
|
||||
originalCaps;
|
||||
|
||||
/**
|
||||
* @type {C}
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/**
|
||||
* @type {DriverOpts<C> & DriverOpts<BaseDriverCapConstraints>}
|
||||
*/
|
||||
opts;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DriverOpts<C>} opts
|
||||
* @param {boolean} shouldValidateCaps
|
||||
*/
|
||||
constructor(opts = /** @type {DriverOpts<C>} */ ({}), shouldValidateCaps = true) {
|
||||
constructor(opts: DriverOpts<C>, shouldValidateCaps = true) {
|
||||
super(opts, shouldValidateCaps);
|
||||
|
||||
/**
|
||||
* This must be assigned here because the declaration of {@linkcode BaseDriver.opts} above
|
||||
* blows away {@linkcode DriverCore.opts}.
|
||||
*/
|
||||
this.opts = opts;
|
||||
this.caps = {} as DriverCaps<C>;
|
||||
|
||||
this.caps = {};
|
||||
|
||||
this.cliArgs = /** @type {CArgs & ServerArgs} */ ({});
|
||||
this.cliArgs = {} as CArgs & ServerArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,25 +64,20 @@ export class BaseDriver extends DriverCore {
|
||||
* Subclasses _shouldn't_ need to use this. If you need to use this, please create
|
||||
* an issue:
|
||||
* @see https://github.com/appium/appium/issues/new
|
||||
* @type {Readonly<BaseDriverCapConstraints & C>}
|
||||
* @protected
|
||||
*/
|
||||
get _desiredCapConstraints() {
|
||||
protected get _desiredCapConstraints(): Readonly<BaseDriverCapConstraints & C> {
|
||||
return Object.freeze(_.merge({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints));
|
||||
}
|
||||
|
||||
// This is the main command handler for the driver. It wraps command
|
||||
// execution with timeout logic, checking that we have a valid session,
|
||||
// and ensuring that we execute commands one at a time. This method is called
|
||||
// by MJSONWP's express router.
|
||||
/**
|
||||
* @param {string} cmd
|
||||
* @param {...any} args
|
||||
* @returns {Promise<any>}
|
||||
* This is the main command handler for the driver. It wraps command
|
||||
* execution with timeout logic, checking that we have a valid session,
|
||||
* and ensuring that we execute commands one at a time. This method is called
|
||||
* by MJSONWP's express router.
|
||||
*/
|
||||
async executeCommand(cmd, ...args) {
|
||||
async executeCommand<T = unknown>(cmd: string, ...args: any[]): Promise<T> {
|
||||
// get start time for this command, and log in special cases
|
||||
let startTime = Date.now();
|
||||
const startTime = Date.now();
|
||||
|
||||
if (cmd === 'createSession') {
|
||||
// If creating a session determine if W3C or MJSONWP protocol was requested and remember the choice
|
||||
@@ -161,12 +145,8 @@ export class BaseDriver extends DriverCore {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Error|import('../protocol/errors').NoSuchDriverError} err
|
||||
*/
|
||||
async startUnexpectedShutdown(
|
||||
err = new errors.NoSuchDriverError('The driver was unexpectedly shut down!')
|
||||
err: Error = new errors.NoSuchDriverError('The driver was unexpectedly shut down!')
|
||||
) {
|
||||
this.eventEmitter.emit(ON_UNEXPECTED_SHUTDOWN_EVENT, err); // allow others to listen for this
|
||||
this.shutdownUnexpectedly = true;
|
||||
@@ -200,14 +180,7 @@ export class BaseDriver extends DriverCore {
|
||||
}, this.newCommandTimeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('@appium/types').AppiumServer} server
|
||||
* @param {string} host
|
||||
* @param {number} port
|
||||
* @param {string} path
|
||||
*/
|
||||
assignServer(server, host, port, path) {
|
||||
assignServer(server: AppiumServer, host: string, port: number, path: string) {
|
||||
this.server = server;
|
||||
this.serverHost = host;
|
||||
this.serverPort = port;
|
||||
@@ -223,8 +196,8 @@ export class BaseDriver extends DriverCore {
|
||||
this.log.debug('Running generic full reset');
|
||||
|
||||
// preserving state
|
||||
let currentConfig = {};
|
||||
for (let property of [
|
||||
const currentConfig = {};
|
||||
for (const property of [
|
||||
'implicitWaitMs',
|
||||
'newCommandTimeoutMs',
|
||||
'sessionId',
|
||||
@@ -233,9 +206,6 @@ export class BaseDriver extends DriverCore {
|
||||
currentConfig[property] = this[property];
|
||||
}
|
||||
|
||||
// We also need to preserve the unexpected shutdown, and make sure it is not cancelled during reset.
|
||||
this.resetOnUnexpectedShutdown = () => {};
|
||||
|
||||
try {
|
||||
if (this.sessionId !== null) {
|
||||
await this.deleteSession(this.sessionId);
|
||||
@@ -244,7 +214,7 @@ export class BaseDriver extends DriverCore {
|
||||
await this.createSession(this.originalCaps);
|
||||
} finally {
|
||||
// always restore state.
|
||||
for (let [key, value] of _.toPairs(currentConfig)) {
|
||||
for (const [key, value] of _.toPairs(currentConfig)) {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -257,13 +227,14 @@ export class BaseDriver extends DriverCore {
|
||||
* Appium 2 has dropped the support of these, so now we only accept capability
|
||||
* objects in W3C format and thus allow any of the three arguments to represent
|
||||
* the latter.
|
||||
* @param {W3CCapabilities<C>} w3cCapabilities1
|
||||
* @param {W3CCapabilities<C>} [w3cCapabilities2]
|
||||
* @param {W3CCapabilities<C>} [w3cCapabilities]
|
||||
* @param {DriverData[]} [driverData]
|
||||
* @returns {Promise<[string,Capabilities<C>]>}
|
||||
*/
|
||||
async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities, driverData) {
|
||||
async createSession(
|
||||
w3cCapabilities1: W3CDriverCaps<C>,
|
||||
w3cCapabilities2?: W3CDriverCaps<C>,
|
||||
w3cCapabilities?: W3CDriverCaps<C>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
driverData?: DriverData[]
|
||||
): Promise<DefaultCreateSessionResult<C>> {
|
||||
if (this.sessionId !== null) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
'Cannot create a new session while one is in progress'
|
||||
@@ -289,15 +260,14 @@ export class BaseDriver extends DriverCore {
|
||||
`Creating session with W3C capabilities: ${JSON.stringify(originalCaps, null, 2)}`
|
||||
);
|
||||
|
||||
/** @type {Capabilities<C>} */
|
||||
let caps;
|
||||
let caps: DriverCaps<C>;
|
||||
try {
|
||||
caps = processCapabilities(
|
||||
originalCaps,
|
||||
this._desiredCapConstraints,
|
||||
this.shouldValidateCaps
|
||||
);
|
||||
caps = fixCaps(caps, this._desiredCapConstraints, this.log);
|
||||
) as DriverCaps<C>;
|
||||
caps = fixCaps(caps, this._desiredCapConstraints, this.log) as DriverCaps<C>;
|
||||
} catch (e) {
|
||||
throw new errors.SessionNotCreatedError(e.message);
|
||||
}
|
||||
@@ -334,7 +304,7 @@ export class BaseDriver extends DriverCore {
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.caps.newCommandTimeout)) {
|
||||
this.newCommandTimeoutMs = /** @type {number} */ (this.caps.newCommandTimeout) * 1000;
|
||||
this.newCommandTimeoutMs = (this.caps.newCommandTimeout as number) * 1000;
|
||||
}
|
||||
|
||||
this._log.prefix = helpers.generateDriverLogPrefix(this, this.sessionId);
|
||||
@@ -343,33 +313,43 @@ export class BaseDriver extends DriverCore {
|
||||
|
||||
return [this.sessionId, caps];
|
||||
}
|
||||
async getSessions() {
|
||||
const ret: MultiSessionData<C>[] = [];
|
||||
|
||||
if (this.sessionId) {
|
||||
ret.push({
|
||||
id: this.sessionId,
|
||||
capabilities: this.caps,
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} [sessionId]
|
||||
* @param {DriverData[]} [driverData]
|
||||
* @returns {Promise<void>}
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
*/
|
||||
async deleteSession(sessionId, driverData) {
|
||||
async getSession() {
|
||||
return this.caps.eventTimings ? {...this.caps, events: this.eventHistory} : this.caps;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async deleteSession(sessionId?: string | null) {
|
||||
await this.clearNewCommandTimeout();
|
||||
if (this.isCommandsQueueEnabled && this.commandsQueueGuard.isBusy()) {
|
||||
// simple hack to release pending commands if they exist
|
||||
// @ts-ignore
|
||||
for (const key of _.keys(this.commandsQueueGuard.queues)) {
|
||||
// @ts-ignore
|
||||
this.commandsQueueGuard.queues[key] = [];
|
||||
// @ts-expect-error private API
|
||||
const queues = this.commandsQueueGuard.queues;
|
||||
for (const key of _.keys(queues)) {
|
||||
queues[key] = [];
|
||||
}
|
||||
}
|
||||
this.sessionId = null;
|
||||
this._log.prefix = helpers.generateDriverLogPrefix(this);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities<C>} caps
|
||||
*/
|
||||
logExtraCaps(caps) {
|
||||
let extraCaps = _.difference(_.keys(caps), _.keys(this._desiredCapConstraints));
|
||||
logExtraCaps(caps: Capabilities<C>) {
|
||||
const extraCaps = _.difference(_.keys(caps), _.keys(this._desiredCapConstraints));
|
||||
if (extraCaps.length) {
|
||||
this.log.warn(
|
||||
`The following capabilities were provided, but are not ` + `recognized by Appium:`
|
||||
@@ -380,12 +360,7 @@ export class BaseDriver extends DriverCore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities<C>} caps
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateDesiredCaps(caps) {
|
||||
validateDesiredCaps(caps: any): caps is DriverCaps<C> {
|
||||
if (!this.shouldValidateCaps) {
|
||||
return true;
|
||||
}
|
||||
@@ -407,65 +382,6 @@ export class BaseDriver extends DriverCore {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from './commands';
|
||||
|
||||
export default BaseDriver;
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').HTTPMethod} HTTPMethod
|
||||
* @typedef {import('@appium/types').DriverData} DriverData
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').Constraint} Constraint
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
* @typedef {import('@appium/types').ServerArgs} ServerArgs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback UpdateServerCallback
|
||||
* @param {import('express').Express} app - Express app
|
||||
* @param {import('@appium/types').AppiumServer} httpServer - HTTP server
|
||||
* @returns {import('type-fest').Promisable<void>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is used to extend {@linkcode BaseDriver} by the mixins and also external drivers.
|
||||
* @template {Constraints} C
|
||||
* @template [Proto={}]
|
||||
* @template [Static={}]
|
||||
* @typedef {import('@appium/types').Class<BaseDriver<C> & Proto,import('@appium/types').DriverStatic & Static>} BaseDriverBase
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @typedef {import('@appium/types').SessionHandler<[string, object],void, C>} SessionHandler
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord} [CArgs=StringRecord]
|
||||
* @typedef {import('@appium/types').Driver<C, CArgs>} Driver
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ExternalDriver<C>} ExternalDriver
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').DriverOpts<C>} DriverOpts
|
||||
*/
|
||||
@@ -2,16 +2,24 @@ import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import logger from './logger';
|
||||
import {tempDir, fs, util, zip, net, timing, node} from '@appium/support';
|
||||
import {tempDir, fs, util, zip, timing, node} from '@appium/support';
|
||||
import LRU from 'lru-cache';
|
||||
import AsyncLock from 'async-lock';
|
||||
import axios from 'axios';
|
||||
import B from 'bluebird';
|
||||
|
||||
// for compat with running tests transpiled and in-place
|
||||
const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
|
||||
const IPA_EXT = '.ipa';
|
||||
const ZIP_EXTS = ['.zip', IPA_EXT];
|
||||
const ZIP_EXTS = new Set(['.zip', IPA_EXT]);
|
||||
const ZIP_MIME_TYPES = ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip'];
|
||||
const CACHED_APPS_MAX_AGE = 1000 * 60 * 60 * 24; // ms
|
||||
const MAX_CACHED_APPS = 1024;
|
||||
const HTTP_STATUS_NOT_MODIFIED = 304;
|
||||
const DEFAULT_REQ_HEADERS = Object.freeze({
|
||||
'user-agent': `Appium (BaseDriver v${BASEDRIVER_VER})`,
|
||||
});
|
||||
const AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC = 2;
|
||||
const APPLICATIONS_CACHE = new LRU({
|
||||
max: MAX_CACHED_APPS,
|
||||
ttl: CACHED_APPS_MAX_AGE, // expire after 24 hours
|
||||
@@ -52,77 +60,6 @@ process.on('exit', () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {Promise<import('axios').AxiosResponse['headers']>}
|
||||
*/
|
||||
async function retrieveHeaders(url) {
|
||||
try {
|
||||
return (
|
||||
await axios({
|
||||
url,
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
})
|
||||
).headers;
|
||||
} catch (e) {
|
||||
logger.info(`Cannot send HEAD request to '${url}'. Original error: ${e.message}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function getCachedApplicationPath(link, currentAppProps = {}, cachedAppInfo = {}) {
|
||||
const refresh = () => {
|
||||
logger.debug(`A fresh copy of the application is going to be downloaded from ${link}`);
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!_.isPlainObject(cachedAppInfo) || !_.isPlainObject(currentAppProps)) {
|
||||
// if an invalid arg is passed then assume cache miss
|
||||
return refresh();
|
||||
}
|
||||
|
||||
const {
|
||||
lastModified: currentModified,
|
||||
immutable: currentImmutable,
|
||||
// maxAge is in seconds
|
||||
maxAge: currentMaxAge,
|
||||
} = currentAppProps;
|
||||
const {
|
||||
// Date instance
|
||||
lastModified,
|
||||
// boolean
|
||||
immutable,
|
||||
// Unix time in milliseconds
|
||||
timestamp,
|
||||
fullPath,
|
||||
} = cachedAppInfo;
|
||||
if (lastModified && currentModified) {
|
||||
if (currentModified.getTime() <= lastModified.getTime()) {
|
||||
logger.debug(`The application at ${link} has not been modified since ${lastModified}`);
|
||||
return fullPath;
|
||||
}
|
||||
logger.debug(`The application at ${link} has been modified since ${lastModified}`);
|
||||
return refresh();
|
||||
}
|
||||
if (immutable && currentImmutable) {
|
||||
logger.debug(`The application at ${link} is immutable`);
|
||||
return fullPath;
|
||||
}
|
||||
if (currentMaxAge && timestamp) {
|
||||
const msLeft = timestamp + currentMaxAge * 1000 - Date.now();
|
||||
if (msLeft > 0) {
|
||||
logger.debug(
|
||||
`The cached application '${path.basename(fullPath)}' will expire in ${msLeft / 1000}s`
|
||||
);
|
||||
return fullPath;
|
||||
}
|
||||
logger.debug(`The cached application '${path.basename(fullPath)}' has expired`);
|
||||
}
|
||||
return refresh();
|
||||
}
|
||||
|
||||
function verifyAppExtension(app, supportedAppExtensions) {
|
||||
if (supportedAppExtensions.map(_.toLower).includes(_.toLower(path.extname(app)))) {
|
||||
return app;
|
||||
@@ -160,56 +97,14 @@ async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef PostProcessOptions
|
||||
* @property {?Object} cachedAppInfo The information about the previously cached app instance (if exists):
|
||||
* - packageHash: SHA1 hash of the package if it is a file and not a folder
|
||||
* - lastModified: Optional Date instance, the value of file's `Last-Modified` header
|
||||
* - immutable: Optional boolean value. Contains true if the file has an `immutable` mark
|
||||
* in `Cache-control` header
|
||||
* - maxAge: Optional integer representation of `maxAge` parameter in `Cache-control` header
|
||||
* - timestamp: The timestamp this item has been added to the cache (measured in Unix epoch
|
||||
* milliseconds)
|
||||
* - integrity: An object containing either `file` property with SHA1 hash of the file
|
||||
* or `folder` property with total amount of cached files and subfolders
|
||||
* - fullPath: the full path to the cached app
|
||||
* @property {boolean} isUrl Whether the app has been downloaded from a remote URL
|
||||
* @property {?Object} headers Optional headers object. Only present if `isUrl` is true and if the server
|
||||
* responds to HEAD requests. All header names are normalized to lowercase.
|
||||
* @property {string} appPath A string containing full path to the preprocessed application package (either
|
||||
* downloaded or a local one)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PostProcessResult
|
||||
* @property {string} appPath The full past to the post-processed application package on the
|
||||
* local file system (might be a file or a folder path)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ConfigureAppOptions
|
||||
* @property {(obj: PostProcessOptions) => (Promise<PostProcessResult|undefined>|PostProcessResult|undefined)} [onPostProcess]
|
||||
* Optional function, which should be applied
|
||||
* to the application after it is downloaded/preprocessed. This function may be async
|
||||
* and is expected to accept single object parameter.
|
||||
* The function is expected to either return a falsy value, which means the app must not be
|
||||
* cached and a fresh copy of it is downloaded each time. If this function returns an object
|
||||
* containing `appPath` property then the integrity of it will be verified and stored into
|
||||
* the cache.
|
||||
* @property {string[]} supportedExtensions List of supported application extensions (
|
||||
* including starting dots). This property is mandatory and must not be empty.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepares an app to be used in an automated test. The app gets cached automatically
|
||||
* if it is an archive or if it is downloaded from an URL.
|
||||
* If the downloaded app has `.zip` extension, this method will unzip it.
|
||||
* The unzip does not work when `onPostProcess` is provided.
|
||||
*
|
||||
* @param {string} app Either a full path to the app or a remote URL
|
||||
* @param {string|string[]|ConfigureAppOptions} options
|
||||
* @returns The full path to the resulting application bundle
|
||||
* @param {string} app
|
||||
* @param {string|string[]|import('@appium/types').ConfigureAppOptions} options
|
||||
*/
|
||||
async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({})) {
|
||||
async function configureApp(
|
||||
app,
|
||||
options = /** @type {import('@appium/types').ConfigureAppOptions} */ ({})
|
||||
) {
|
||||
if (!_.isString(app)) {
|
||||
// immediately shortcircuit if not given an app
|
||||
return;
|
||||
@@ -233,13 +128,14 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
|
||||
let newApp = app;
|
||||
let shouldUnzipApp = false;
|
||||
let packageHash = null;
|
||||
/** @type {import('axios').AxiosResponse['headers']?} */
|
||||
let headers = null;
|
||||
/** @type {import('axios').AxiosResponse['headers']|undefined} */
|
||||
let headers = undefined;
|
||||
/** @type {RemoteAppProps} */
|
||||
const remoteAppProps = {
|
||||
lastModified: null,
|
||||
immutable: false,
|
||||
maxAge: null,
|
||||
etag: null,
|
||||
};
|
||||
const {protocol, pathname} = url.parse(newApp);
|
||||
const isUrl = protocol === null ? false : ['http:', 'https:'].includes(protocol);
|
||||
@@ -250,94 +146,121 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
|
||||
if (isUrl) {
|
||||
// Use the app from remote URL
|
||||
logger.info(`Using downloadable app '${newApp}'`);
|
||||
headers = await retrieveHeaders(newApp);
|
||||
if (!_.isEmpty(headers)) {
|
||||
if (headers['last-modified']) {
|
||||
remoteAppProps.lastModified = new Date(headers['last-modified']);
|
||||
}
|
||||
logger.debug(`Last-Modified: ${headers['last-modified']}`);
|
||||
if (headers['cache-control']) {
|
||||
remoteAppProps.immutable = /\bimmutable\b/i.test(headers['cache-control']);
|
||||
const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(headers['cache-control']);
|
||||
if (maxAgeMatch) {
|
||||
remoteAppProps.maxAge = parseInt(maxAgeMatch[1], 10);
|
||||
}
|
||||
}
|
||||
logger.debug(`Cache-Control: ${headers['cache-control']}`);
|
||||
}
|
||||
const cachedPath = getCachedApplicationPath(app, remoteAppProps, cachedAppInfo);
|
||||
if (cachedPath) {
|
||||
if (await isAppIntegrityOk(cachedPath, cachedAppInfo?.integrity)) {
|
||||
logger.info(`Reusing previously downloaded application at '${cachedPath}'`);
|
||||
return verifyAppExtension(cachedPath, supportedAppExtensions);
|
||||
}
|
||||
logger.info(
|
||||
`The application at '${cachedPath}' does not exist anymore ` +
|
||||
`or its integrity has been damaged. Deleting it from the internal cache`
|
||||
);
|
||||
APPLICATIONS_CACHE.delete(app);
|
||||
const reqHeaders = {
|
||||
...DEFAULT_REQ_HEADERS,
|
||||
};
|
||||
if (cachedAppInfo?.etag) {
|
||||
reqHeaders['if-none-match'] = remoteAppProps.etag;
|
||||
} else if (cachedAppInfo?.lastModified) {
|
||||
reqHeaders['if-modified-since'] = remoteAppProps.lastModified?.toString();
|
||||
}
|
||||
|
||||
let fileName = null;
|
||||
const basename = fs.sanitizeName(path.basename(decodeURIComponent(pathname ?? '')), {
|
||||
replacement: SANITIZE_REPLACEMENT,
|
||||
});
|
||||
const extname = path.extname(basename);
|
||||
// to determine if we need to unzip the app, we have a number of places
|
||||
// to look: content type, content disposition, or the file extension
|
||||
if (ZIP_EXTS.includes(extname)) {
|
||||
fileName = basename;
|
||||
shouldUnzipApp = true;
|
||||
}
|
||||
if (headers['content-type']) {
|
||||
const ct = headers['content-type'];
|
||||
logger.debug(`Content-Type: ${ct}`);
|
||||
// the filetype may not be obvious for certain urls, so check the mime type too
|
||||
if (
|
||||
ZIP_MIME_TYPES.some((mimeType) =>
|
||||
new RegExp(`\\b${_.escapeRegExp(mimeType)}\\b`).test(ct)
|
||||
)
|
||||
) {
|
||||
if (!fileName) {
|
||||
fileName = `${DEFAULT_BASENAME}.zip`;
|
||||
let {headers, stream, status} = await queryAppLink(newApp, reqHeaders);
|
||||
try {
|
||||
if (!_.isEmpty(headers)) {
|
||||
logger.debug(`Etag: ${remoteAppProps?.etag} -> ${headers.etag}`);
|
||||
if (headers.etag) {
|
||||
remoteAppProps.etag = headers.etag;
|
||||
}
|
||||
logger.debug(
|
||||
`Last-Modified: ${remoteAppProps?.['last-modified']} -> ${headers['last-modified']}`
|
||||
);
|
||||
if (headers['last-modified']) {
|
||||
remoteAppProps.lastModified = new Date(headers['last-modified']);
|
||||
}
|
||||
logger.debug(
|
||||
`Cache-Control: ${remoteAppProps?.['cache-control']} -> ${headers['cache-control']}`
|
||||
);
|
||||
if (headers['cache-control']) {
|
||||
remoteAppProps.immutable = /\bimmutable\b/i.test(headers['cache-control']);
|
||||
const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(headers['cache-control']);
|
||||
if (maxAgeMatch) {
|
||||
remoteAppProps.maxAge = parseInt(maxAgeMatch[1], 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cachedAppInfo && status === HTTP_STATUS_NOT_MODIFIED) {
|
||||
if (await isAppIntegrityOk(cachedAppInfo.fullPath, cachedAppInfo.integrity)) {
|
||||
logger.info(`Reusing previously downloaded application at '${cachedAppInfo.fullPath}'`);
|
||||
return verifyAppExtension(cachedAppInfo.fullPath, supportedAppExtensions);
|
||||
}
|
||||
logger.info(
|
||||
`The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
|
||||
`or its integrity has been damaged. Deleting it from the internal cache`
|
||||
);
|
||||
APPLICATIONS_CACHE.delete(app);
|
||||
|
||||
if (!stream.closed) {
|
||||
stream.destroy();
|
||||
}
|
||||
({stream, headers, status} = await queryAppLink(newApp, {...DEFAULT_REQ_HEADERS}));
|
||||
}
|
||||
|
||||
let fileName = null;
|
||||
const basename = fs.sanitizeName(path.basename(decodeURIComponent(pathname ?? '')), {
|
||||
replacement: SANITIZE_REPLACEMENT,
|
||||
});
|
||||
const extname = path.extname(basename);
|
||||
// to determine if we need to unzip the app, we have a number of places
|
||||
// to look: content type, content disposition, or the file extension
|
||||
if (ZIP_EXTS.has(extname)) {
|
||||
fileName = basename;
|
||||
shouldUnzipApp = true;
|
||||
}
|
||||
}
|
||||
if (headers['content-disposition'] && /^attachment/i.test(headers['content-disposition'])) {
|
||||
logger.debug(`Content-Disposition: ${headers['content-disposition']}`);
|
||||
const match = /filename="([^"]+)/i.exec(headers['content-disposition']);
|
||||
if (match) {
|
||||
fileName = fs.sanitizeName(match[1], {
|
||||
replacement: SANITIZE_REPLACEMENT,
|
||||
});
|
||||
shouldUnzipApp = shouldUnzipApp || ZIP_EXTS.includes(path.extname(fileName));
|
||||
if (headers['content-type']) {
|
||||
const ct = headers['content-type'];
|
||||
logger.debug(`Content-Type: ${ct}`);
|
||||
// the filetype may not be obvious for certain urls, so check the mime type too
|
||||
if (
|
||||
ZIP_MIME_TYPES.some((mimeType) =>
|
||||
new RegExp(`\\b${_.escapeRegExp(mimeType)}\\b`).test(ct)
|
||||
)
|
||||
) {
|
||||
if (!fileName) {
|
||||
fileName = `${DEFAULT_BASENAME}.zip`;
|
||||
}
|
||||
shouldUnzipApp = true;
|
||||
}
|
||||
}
|
||||
if (headers['content-disposition'] && /^attachment/i.test(headers['content-disposition'])) {
|
||||
logger.debug(`Content-Disposition: ${headers['content-disposition']}`);
|
||||
const match = /filename="([^"]+)/i.exec(headers['content-disposition']);
|
||||
if (match) {
|
||||
fileName = fs.sanitizeName(match[1], {
|
||||
replacement: SANITIZE_REPLACEMENT,
|
||||
});
|
||||
shouldUnzipApp = shouldUnzipApp || ZIP_EXTS.has(path.extname(fileName));
|
||||
}
|
||||
}
|
||||
if (!fileName) {
|
||||
// assign the default file name and the extension if none has been detected
|
||||
const resultingName = basename
|
||||
? basename.substring(0, basename.length - extname.length)
|
||||
: DEFAULT_BASENAME;
|
||||
let resultingExt = extname;
|
||||
if (!supportedAppExtensions.includes(resultingExt)) {
|
||||
logger.info(
|
||||
`The current file extension '${resultingExt}' is not supported. ` +
|
||||
`Defaulting to '${_.first(supportedAppExtensions)}'`
|
||||
);
|
||||
resultingExt = /** @type {string} */ (_.first(supportedAppExtensions));
|
||||
}
|
||||
fileName = `${resultingName}${resultingExt}`;
|
||||
}
|
||||
const targetPath = await tempDir.path({
|
||||
prefix: fileName,
|
||||
suffix: '',
|
||||
});
|
||||
newApp = await fetchApp(stream, targetPath);
|
||||
} finally {
|
||||
if (!stream.closed) {
|
||||
stream.destroy();
|
||||
}
|
||||
}
|
||||
if (!fileName) {
|
||||
// assign the default file name and the extension if none has been detected
|
||||
const resultingName = basename
|
||||
? basename.substring(0, basename.length - extname.length)
|
||||
: DEFAULT_BASENAME;
|
||||
let resultingExt = extname;
|
||||
if (!supportedAppExtensions.includes(resultingExt)) {
|
||||
logger.info(
|
||||
`The current file extension '${resultingExt}' is not supported. ` +
|
||||
`Defaulting to '${_.first(supportedAppExtensions)}'`
|
||||
);
|
||||
resultingExt = /** @type {string} */ (_.first(supportedAppExtensions));
|
||||
}
|
||||
fileName = `${resultingName}${resultingExt}`;
|
||||
}
|
||||
const targetPath = await tempDir.path({
|
||||
prefix: fileName,
|
||||
suffix: '',
|
||||
});
|
||||
newApp = await downloadApp(newApp, targetPath);
|
||||
} else if (await fs.exists(newApp)) {
|
||||
// Use the local app
|
||||
logger.info(`Using local app '${newApp}'`);
|
||||
shouldUnzipApp = ZIP_EXTS.includes(path.extname(newApp));
|
||||
shouldUnzipApp = ZIP_EXTS.has(path.extname(newApp));
|
||||
} else {
|
||||
let errorMessage = `The application at '${newApp}' does not exist or is not accessible`;
|
||||
// protocol value for 'C:\\temp' is 'c:', so we check the length as well
|
||||
@@ -411,12 +334,14 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
|
||||
};
|
||||
|
||||
if (_.isFunction(onPostProcess)) {
|
||||
const result = await onPostProcess({
|
||||
cachedAppInfo: _.clone(cachedAppInfo),
|
||||
isUrl,
|
||||
headers: _.clone(headers),
|
||||
appPath: newApp,
|
||||
});
|
||||
const result = await onPostProcess(
|
||||
/** @type {import('@appium/types').PostProcessOptions<import('axios').AxiosResponseHeaders>} */ ({
|
||||
cachedAppInfo: _.clone(cachedAppInfo),
|
||||
isUrl,
|
||||
headers: _.clone(headers),
|
||||
appPath: newApp,
|
||||
})
|
||||
);
|
||||
return !result?.appPath || app === result?.appPath || !(await fs.exists(result?.appPath))
|
||||
? newApp
|
||||
: await storeAppInCache(result.appPath);
|
||||
@@ -429,16 +354,78 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadApp(app, targetPath) {
|
||||
const {href} = url.parse(app);
|
||||
/**
|
||||
* Sends a HTTP GET query to fetch the app with caching enabled.
|
||||
* Follows https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
|
||||
*
|
||||
* @param {string} appLink The URL to download an app from
|
||||
* @param {import('axios').RawAxiosRequestHeaders} reqHeaders Additional HTTP request headers
|
||||
* @returns {Promise<RemoteAppData>}
|
||||
*/
|
||||
async function queryAppLink(appLink, reqHeaders) {
|
||||
const {href} = url.parse(appLink);
|
||||
/**
|
||||
* @type {import('axios').RawAxiosRequestConfig}
|
||||
*/
|
||||
const requestOpts = {
|
||||
url: href,
|
||||
responseType: 'stream',
|
||||
timeout: APP_DOWNLOAD_TIMEOUT_MS,
|
||||
validateStatus: (status) =>
|
||||
(status >= 200 && status < 300) || status === HTTP_STATUS_NOT_MODIFIED,
|
||||
headers: reqHeaders,
|
||||
};
|
||||
try {
|
||||
await net.downloadFile(href, targetPath, {
|
||||
timeout: APP_DOWNLOAD_TIMEOUT_MS,
|
||||
const {data: stream, headers, status} = await axios(requestOpts);
|
||||
return {
|
||||
stream,
|
||||
headers,
|
||||
status,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(`Cannot download the app from ${href}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves app payload from the given stream. Also meters the download performance.
|
||||
*
|
||||
* @param {import('stream').Readable} srcStream The incoming stream
|
||||
* @param {string} dstPath The target file path to be written
|
||||
* @returns {Promise<string>} The same dstPath
|
||||
* @throws {Error} If there was a failure while downloading the file
|
||||
*/
|
||||
async function fetchApp(srcStream, dstPath) {
|
||||
const timer = new timing.Timer().start();
|
||||
try {
|
||||
const writer = fs.createWriteStream(dstPath);
|
||||
srcStream.pipe(writer);
|
||||
|
||||
await new B((resolve, reject) => {
|
||||
srcStream.once('error', reject);
|
||||
writer.once('finish', resolve);
|
||||
writer.once('error', (e) => {
|
||||
srcStream.unpipe(writer);
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Unable to download the app: ${err.message}`);
|
||||
throw new Error(`Cannot fetch the application: ${err.message}`);
|
||||
}
|
||||
return targetPath;
|
||||
|
||||
const secondsElapsed = timer.getDuration().asSeconds;
|
||||
const {size} = await fs.stat(dstPath);
|
||||
logger.debug(
|
||||
`The application (${util.toReadableSizeString(size)}) ` +
|
||||
`has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s`
|
||||
);
|
||||
// it does not make much sense to approximate the speed for short downloads
|
||||
if (secondsElapsed >= AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC) {
|
||||
const bytesPerSec = Math.floor(size / secondsElapsed);
|
||||
logger.debug(`Approximate download speed: ${util.toReadableSizeString(bytesPerSec)}/s`);
|
||||
}
|
||||
|
||||
return dstPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,11 +593,26 @@ export default {
|
||||
parseCapsArray,
|
||||
generateDriverLogPrefix,
|
||||
};
|
||||
export {configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray, generateDriverLogPrefix};
|
||||
export {
|
||||
configureApp,
|
||||
isPackageOrBundle,
|
||||
duplicateKeys,
|
||||
parseCapsArray,
|
||||
generateDriverLogPrefix,
|
||||
BASEDRIVER_VER,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef RemoteAppProps
|
||||
* @property {Date?} lastModified
|
||||
* @property {boolean} immutable
|
||||
* @property {number?} maxAge
|
||||
* @property {string?} etag
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef RemoteAppData Properties of the remote application (e.g. GET HTTP response) to be downloaded.
|
||||
* @property {number} status The HTTP status of the response
|
||||
* @property {import('stream').Readable} stream The HTTP response body represented as readable stream
|
||||
* @property {import('axios').RawAxiosResponseHeaders | import('axios').AxiosResponseHeaders} headers HTTP response headers
|
||||
*/
|
||||
|
||||
@@ -151,8 +151,10 @@ function configureHttp({httpServer, reject, keepAliveTimeout}) {
|
||||
notifier: new EventEmitter(),
|
||||
closed: false,
|
||||
};
|
||||
// TS does not love monkeypatching.
|
||||
const appiumServer = /** @type {AppiumServer} */ (/** @type {unknown} */ (httpServer));
|
||||
/**
|
||||
* @type {AppiumServer}
|
||||
*/
|
||||
const appiumServer = /** @type {any} */ (httpServer);
|
||||
appiumServer.addWebSocketHandler = addWebSocketHandler;
|
||||
appiumServer.removeWebSocketHandler = removeWebSocketHandler;
|
||||
appiumServer.removeAllWebSocketHandlers = removeAllWebSocketHandlers;
|
||||
|
||||
@@ -6,33 +6,21 @@ import B from 'bluebird';
|
||||
const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
|
||||
|
||||
/**
|
||||
* Adds websocket handler to express server instance.
|
||||
* It is expected this function is called in Express
|
||||
* server instance context.
|
||||
*
|
||||
* @this {AppiumServer} - An instance of express HTTP server.
|
||||
* @param {string} handlerPathname - Web socket endpoint path starting with
|
||||
* a single slash character. It is recommended to always add
|
||||
* DEFAULT_WS_PATHNAME_PREFIX to all web socket pathnames.
|
||||
* @param {import('ws').Server} handlerServer - WebSocket server instance. See
|
||||
* https://github.com/websockets/ws/pull/885 for more details
|
||||
* on how to configure the handler properly.
|
||||
* @returns {Promise<void>}
|
||||
* @this {AppiumServer}
|
||||
* @type {AppiumServer['addWebSocketHandler']}
|
||||
*/
|
||||
async function addWebSocketHandler(handlerPathname, handlerServer) {
|
||||
const server = /** @type {AppiumServer} */ (this);
|
||||
if (_.isUndefined(this.webSocketsMapping)) {
|
||||
server.webSocketsMapping = {};
|
||||
this.webSocketsMapping = {};
|
||||
// https://github.com/websockets/ws/pull/885
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
this.on('upgrade', (request, socket, head) => {
|
||||
let currentPathname;
|
||||
try {
|
||||
// @ts-expect-error
|
||||
currentPathname = new URL(request.url).pathname;
|
||||
currentPathname = new URL(/** @type {string} */ (request.url)).pathname;
|
||||
} catch {
|
||||
currentPathname = request.url;
|
||||
}
|
||||
for (const [pathname, wsServer] of _.toPairs(server.webSocketsMapping)) {
|
||||
for (const [pathname, wsServer] of _.toPairs(this.webSocketsMapping)) {
|
||||
if (currentPathname === pathname) {
|
||||
wsServer.handleUpgrade(request, socket, head, (ws) => {
|
||||
wsServer.emit('connection', ws, request);
|
||||
@@ -47,17 +35,9 @@ async function addWebSocketHandler(handlerPathname, handlerServer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns web socket handlers registered for the given server
|
||||
* instance.
|
||||
* It is expected this function is called in Express
|
||||
* server instance context.
|
||||
*
|
||||
* @this {AppiumServer}
|
||||
* @param {string?} [keysFilter] - Only include pathnames with given
|
||||
* `keysFilter` value if set. All pairs will be included by default.
|
||||
* @returns {Promise<Record<string, import('ws').Server>>} pathnames to websocket server instances mapping matching the search criteria or an empty object otherwise.
|
||||
* @type {AppiumServer['getWebSocketHandlers']}
|
||||
*/
|
||||
// eslint-disable-next-line require-await
|
||||
async function getWebSocketHandlers(keysFilter = null) {
|
||||
if (_.isEmpty(this.webSocketsMapping)) {
|
||||
return {};
|
||||
@@ -72,16 +52,9 @@ async function getWebSocketHandlers(keysFilter = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes existing websocket handler from express server instance.
|
||||
* The call is ignored if the given `handlerPathname` handler
|
||||
* is not present in the handlers list.
|
||||
* It is expected this function is called in Express
|
||||
* server instance context.
|
||||
* @this {AppiumServer}
|
||||
* @param {string} handlerPathname - Websocket endpoint path.
|
||||
* @returns {Promise<boolean>} true if the handlerPathname was found and deleted
|
||||
* @type {AppiumServer['removeWebSocketHandler']}
|
||||
*/
|
||||
// eslint-disable-next-line require-await
|
||||
async function removeWebSocketHandler(handlerPathname) {
|
||||
const wsServer = this.webSocketsMapping?.[handlerPathname];
|
||||
if (!wsServer) {
|
||||
@@ -103,11 +76,9 @@ async function removeWebSocketHandler(handlerPathname) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all existing websocket handler from express server instance.
|
||||
* It is expected this function is called in Express
|
||||
* server instance context.
|
||||
*
|
||||
* @this {AppiumServer}
|
||||
* @returns {Promise<boolean>} true if at least one handler has been deleted
|
||||
* @type {AppiumServer['removeAllWebSocketHandlers']}
|
||||
*/
|
||||
async function removeAllWebSocketHandlers() {
|
||||
if (_.isEmpty(this.webSocketsMapping)) {
|
||||
|
||||
@@ -83,7 +83,6 @@ export {isW3cCaps, fixCaps};
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@ import B from 'bluebird';
|
||||
import {formatResponseValue, formatStatus} from './helpers';
|
||||
import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
|
||||
import {isW3cCaps} from '../helpers/capabilities';
|
||||
import log from '../basedriver/logger';
|
||||
|
||||
const CREATE_SESSION_COMMAND = 'createSession';
|
||||
const DELETE_SESSION_COMMAND = 'deleteSession';
|
||||
@@ -225,7 +226,7 @@ function validateExecuteMethodParams(params, paramSpec) {
|
||||
`arguments to execute script and instead received: ${JSON.stringify(params)}`
|
||||
);
|
||||
}
|
||||
const args = params[0] ?? {};
|
||||
let args = params[0] ?? {};
|
||||
if (!_.isPlainObject(args)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`Did not receive an appropriate execute method parameters object. It needs to be ` +
|
||||
@@ -237,6 +238,11 @@ function validateExecuteMethodParams(params, paramSpec) {
|
||||
} else {
|
||||
paramSpec.required ??= [];
|
||||
paramSpec.optional ??= [];
|
||||
const unknownNames = _.difference(_.keys(args), paramSpec.required, paramSpec.optional);
|
||||
if (!_.isEmpty(unknownNames)) {
|
||||
log.info(`The following script arguments are not known and will be ignored: ${unknownNames}`);
|
||||
args = _.pickBy(args, (v, k) => !unknownNames.includes(k));
|
||||
}
|
||||
checkParams(paramSpec, args, null);
|
||||
}
|
||||
const argsToApply = makeArgs({}, args, paramSpec, null);
|
||||
@@ -277,7 +283,7 @@ function routeConfiguringFunction(driver) {
|
||||
`${basePath}${path}`,
|
||||
spec,
|
||||
driver,
|
||||
isSessionCommand(spec.command)
|
||||
isSessionCommand(/** @type {import('@appium/types').DriverMethodDef} */ (spec).command)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -454,7 +460,16 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
||||
} catch (err) {
|
||||
// if anything goes wrong, figure out what our response should be
|
||||
// based on the type of error that we encountered
|
||||
let actualErr = err;
|
||||
let actualErr;
|
||||
if (err instanceof Error || (_.has(err, 'stack') && _.has(err, 'message'))) {
|
||||
actualErr = err;
|
||||
} else {
|
||||
getLogger(driver, req.params.sessionId || newSessionId).warn(
|
||||
'The thrown error object does not seem to be a valid instance of the Error class. This ' +
|
||||
'might be a genuine bug of a driver or a plugin.'
|
||||
);
|
||||
actualErr = new Error(`${err ?? 'unknown'}`);
|
||||
}
|
||||
|
||||
currentProtocol =
|
||||
currentProtocol || extractProtocol(driver, req.params.sessionId || newSessionId);
|
||||
|
||||
@@ -18,6 +18,7 @@ const SET_ALERT_TEXT_PAYLOAD_PARAMS = {
|
||||
* define the routes, mapping of HTTP methods to particular driver commands, and
|
||||
* any parameters that are expected in a request parameters can be `required` or
|
||||
* `optional`
|
||||
* @satisfies {import('@appium/types').MethodMap<import('../basedriver/driver').BaseDriver>}
|
||||
*/
|
||||
const METHOD_MAP = /** @type {const} */ ({
|
||||
'/status': {
|
||||
@@ -129,7 +130,11 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
POST: {command: 'deactivateIMEEngine', deprecated: true},
|
||||
},
|
||||
'/session/:sessionId/ime/activate': {
|
||||
POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}, deprecated: true},
|
||||
POST: {
|
||||
command: 'activateIMEEngine',
|
||||
payloadParams: {required: ['engine']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/frame': {
|
||||
POST: {command: 'setFrame', payloadParams: {required: ['id']}},
|
||||
@@ -356,7 +361,11 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
DELETE: {command: 'releaseActions'},
|
||||
},
|
||||
'/session/:sessionId/touch/longclick': {
|
||||
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}, deprecated: true},
|
||||
POST: {
|
||||
command: 'touchLongClick',
|
||||
payloadParams: {required: ['elements']},
|
||||
deprecated: true,
|
||||
},
|
||||
},
|
||||
'/session/:sessionId/touch/flick': {
|
||||
POST: {
|
||||
@@ -922,7 +931,7 @@ const METHOD_MAP = /** @type {const} */ ({
|
||||
let ALL_COMMANDS = [];
|
||||
for (let v of _.values(METHOD_MAP)) {
|
||||
for (let m of _.values(v)) {
|
||||
if (m.command) {
|
||||
if ('command' in m && m.command) {
|
||||
ALL_COMMANDS.push(m.command);
|
||||
}
|
||||
}
|
||||
@@ -1006,13 +1015,3 @@ function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PATH) {
|
||||
const NO_SESSION_ID_COMMANDS = ['createSession', 'getStatus', 'getSessions'];
|
||||
|
||||
export {METHOD_MAP, ALL_COMMANDS, NO_SESSION_ID_COMMANDS, routeToCommandName};
|
||||
|
||||
/**
|
||||
* This checks compat with the `MethodMap` interface.
|
||||
* @ignore
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _validMethodMap =
|
||||
/** @type {import('@appium/types').MethodMap<import('@appium/types').ExternalDriver<import('@appium/types').BaseDriverCapConstraints>>} */ (
|
||||
METHOD_MAP
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.3.3",
|
||||
"version": "9.3.11",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -40,20 +40,22 @@
|
||||
"test": "npm run test:unit",
|
||||
"test:e2e": "mocha -p --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\"",
|
||||
"test:types": "tsd"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/types": "^0.10.1",
|
||||
"@appium/support": "^4.0.2",
|
||||
"@appium/types": "^0.12.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.4.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/lodash": "4.14.195",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/serve-favicon": "2.5.3",
|
||||
"@types/serve-favicon": "2.5.4",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.3.4",
|
||||
"axios": "1.4.0",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.2",
|
||||
"es6-error": "4.1.1",
|
||||
@@ -65,7 +67,7 @@
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.6.1",
|
||||
"type-fest": "3.11.1",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -75,8 +77,11 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72",
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "test/types"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import {expectAssignable} from 'tsd';
|
||||
// NOTE: this pulls in the distfiles
|
||||
import {BaseDriver} from '.../../..';
|
||||
import {BaseDriverCapConstraints, ExternalDriver, Driver, DriverOpts} from '@appium/types';
|
||||
|
||||
expectAssignable<Driver<BaseDriverCapConstraints>>(
|
||||
new BaseDriver({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
);
|
||||
expectAssignable<ExternalDriver<BaseDriverCapConstraints>>(
|
||||
new BaseDriver({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
);
|
||||
@@ -3,6 +3,73 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.11](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.10...@appium/base-plugin@2.2.11) (2023-06-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.10](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.9...@appium/base-plugin@2.2.10) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.8...@appium/base-plugin@2.2.9) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.8](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.7...@appium/base-plugin@2.2.8) (2023-05-17)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.7](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.6...@appium/base-plugin@2.2.7) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.6](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.5...@appium/base-plugin@2.2.6) (2023-04-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.5](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.4...@appium/base-plugin@2.2.5) (2023-04-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.4](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.3...@appium/base-plugin@2.2.4) (2023-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver,base-plugin,types:** update PluginCommand and DriverCommand types ([0dcd5fa](https://github.com/appium/appium/commit/0dcd5fa371af523c6527e55de4cff6cd472fde22))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.3](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.2...@appium/base-plugin@2.2.3) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Appium Base Plugin
|
||||
|
||||
The base class used to create Appium plugins. This plugin should not be installed directly as it does nothing. Instead, you extend this plugin when creating your *own* Appium plugins. Check out the [Building Plugins](https://appium.github.io/appium/docs/en/latest/ecosystem/build-plugins/) to learn how to make use of this base class to develop Appium plugins.
|
||||
The base class used to create Appium plugins. This plugin should not be installed directly as it does nothing. Instead, you extend this plugin when creating your *own* Appium plugins. Check out the [Building Plugins](https://appium.io/docs/en/latest/ecosystem/build-plugins/) to learn how to make use of this base class to develop Appium plugins.
|
||||
|
||||
@@ -60,7 +60,7 @@ class BasePlugin {
|
||||
* @param {NextPluginCallback} next
|
||||
* @param {Driver<C>} driver
|
||||
* @param {string} script
|
||||
* @param {[Record<string, any>]|[]} protoArgs
|
||||
* @param {readonly [import('@appium/types').StringRecord<unknown>] | readonly any[]} protoArgs
|
||||
*/
|
||||
async executeMethod(next, driver, script, protoArgs) {
|
||||
const Plugin = /** @type {import('@appium/types').PluginClass<Plugin>} */ (this.constructor);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.3",
|
||||
"version": "2.2.11",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -40,8 +40,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.3",
|
||||
"@appium/support": "^3.1.7"
|
||||
"@appium/base-driver": "^9.3.11",
|
||||
"@appium/support": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72",
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
|
||||
@@ -3,6 +3,79 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.17](https://github.com/appium/appium/compare/@appium/doctor@2.0.16...@appium/doctor@2.0.17) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.11.3 ([a0b434d](https://github.com/appium/appium/commit/a0b434dff7a4e425f66915e12266a6e943d10372))
|
||||
* **doctor:** update dependency appium-adb to v9.11.4 ([f34c75c](https://github.com/appium/appium/commit/f34c75c65068a299ab4d3427233e771e796fc43a))
|
||||
* **doctor:** update dependency appium-adb to v9.11.5 ([78c6794](https://github.com/appium/appium/commit/78c67942534705133093aa9fa54d84efb9fe2f69))
|
||||
|
||||
|
||||
|
||||
## [2.0.16](https://github.com/appium/appium/compare/@appium/doctor@2.0.15...@appium/doctor@2.0.16) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.15](https://github.com/appium/appium/compare/@appium/doctor@2.0.14...@appium/doctor@2.0.15) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.11.2 ([e7df6fd](https://github.com/appium/appium/commit/e7df6fd4f054d6ad6eb4125f77e7e0d58bc05ebf))
|
||||
* **docutils:** update dependency yargs to v17.7.2 ([3d1e4af](https://github.com/appium/appium/commit/3d1e4af858ea21bcf3ab1e1015aa7bdf1776f3ce))
|
||||
|
||||
|
||||
|
||||
## [2.0.14](https://github.com/appium/appium/compare/@appium/doctor@2.0.13...@appium/doctor@2.0.14) (2023-04-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.11.1 ([45cd434](https://github.com/appium/appium/commit/45cd43488ce3e7a3c32864912d8fb104934f90d8))
|
||||
|
||||
|
||||
|
||||
## [2.0.13](https://github.com/appium/appium/compare/@appium/doctor@2.0.12...@appium/doctor@2.0.13) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.12](https://github.com/appium/appium/compare/@appium/doctor@2.0.11...@appium/doctor@2.0.12) (2023-04-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.11](https://github.com/appium/appium/compare/@appium/doctor@2.0.10...@appium/doctor@2.0.11) (2023-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.11.0 ([d8b6334](https://github.com/appium/appium/commit/d8b633406a68e88cdd945906dde3f23a2a566a71))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.10](https://github.com/appium/appium/compare/@appium/doctor@2.0.9...@appium/doctor@2.0.10) (2023-03-28)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.9](https://github.com/appium/appium/compare/@appium/doctor@2.0.8...@appium/doctor@2.0.9) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
@@ -75,7 +75,7 @@ class OptionalFfmpegCommandCheck extends DoctorCheck {
|
||||
return ffmpegPath
|
||||
? okOptional(
|
||||
`ffmpeg is installed at: ${ffmpegPath}. ${
|
||||
(await exec('ffmpeg', ['-version'])).stdout.split(EOL)[0]
|
||||
(await exec('ffmpeg', ['-version'])).stdout.split('\n')[0]
|
||||
}`
|
||||
)
|
||||
: nokOptional('ffmpeg cannot be found');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.17",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,17 +44,17 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/support": "^4.0.2",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"appium-adb": "9.10.24",
|
||||
"appium-adb": "9.11.5",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.5",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"yargs": "17.7.1"
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
@@ -63,5 +63,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72"
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from 'node:path';
|
||||
import {
|
||||
EnvVarAndPathCheck,
|
||||
AndroidToolCheck,
|
||||
OptionalAppBundleCheck,
|
||||
OptionalGstreamerCheck,
|
||||
} from '../../lib/android';
|
||||
import {fs} from '@appium/support';
|
||||
import {fs, system} from '@appium/support';
|
||||
import * as adb from 'appium-adb';
|
||||
import * as utils from '../../lib/utils';
|
||||
import * as tp from 'teen_process';
|
||||
@@ -13,6 +14,9 @@ import B from 'bluebird';
|
||||
import {removeColors} from './helper';
|
||||
|
||||
describe('android', function () {
|
||||
const apkAnalyzerFilename = system.isWindows() ? 'apkanalyzer.bat' : 'apkanalyzer';
|
||||
const gstLaunchFilename = system.isWindows() ? 'gst-launch-1.0.exe' : 'gst-launch-1.0';
|
||||
const gstInspectFilename = system.isWindows() ? 'gst-inspect-1.0.exe' : 'gst-inspect-1.0';
|
||||
describe(
|
||||
'EnvVarAndPathCheck',
|
||||
withMocks({fs}, (mocks) => {
|
||||
@@ -64,12 +68,14 @@ describe('android', function () {
|
||||
});
|
||||
it('diagnose - success', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
mocks.adb.expects('getAndroidBinaryPath').exactly(check.tools.length)
|
||||
mocks.adb
|
||||
.expects('getAndroidBinaryPath')
|
||||
.exactly(check.tools.length)
|
||||
.returns(B.resolve('/path/to/binary'));
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: true,
|
||||
optional: false,
|
||||
message: 'adb, emulator, apkanalyzer exist: /a/b/c/d',
|
||||
message: `adb, emulator, ${apkAnalyzerFilename} exist: /a/b/c/d`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
@@ -79,19 +85,17 @@ describe('android', function () {
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: false,
|
||||
optional: false,
|
||||
message: 'adb, emulator, apkanalyzer could not be found because ' +
|
||||
'ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!',
|
||||
message: `adb, emulator, ${apkAnalyzerFilename} could not be found because ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
it('diagnose - failure - path not valid', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
mocks.adb.expects('getAndroidBinaryPath').exactly(check.tools.length)
|
||||
.throws();
|
||||
mocks.adb.expects('getAndroidBinaryPath').exactly(check.tools.length).throws();
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: false,
|
||||
optional: false,
|
||||
message: 'adb, emulator, apkanalyzer could NOT be found in /a/b/c/d!',
|
||||
message: `adb, emulator, ${apkAnalyzerFilename} could NOT be found in /a/b/c/d!`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
@@ -102,11 +106,9 @@ describe('android', function () {
|
||||
);
|
||||
});
|
||||
it('fix - install', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
process.env.ANDROID_HOME = path.resolve('/', 'a', 'b', 'c', 'd');
|
||||
removeColors(await check.fix()).should.equal(
|
||||
'Manually install adb, emulator, apkanalyzer and add it to PATH. ' +
|
||||
'https://developer.android.com/studio#cmdline-tools and ' +
|
||||
'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.'
|
||||
`Manually install adb, emulator, ${apkAnalyzerFilename} and add it to PATH. https://developer.android.com/studio#cmdline-tools and https://developer.android.com/studio/intro/update#sdk-manager may help to setup.`
|
||||
);
|
||||
});
|
||||
})
|
||||
@@ -138,8 +140,10 @@ describe('android', function () {
|
||||
mocks.verify();
|
||||
});
|
||||
it('fix', async function () {
|
||||
removeColors(await check.fix()).should.equal(
|
||||
'bundletool.jar is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it'
|
||||
removeColors(await check.fix()).should.match(
|
||||
new RegExp(
|
||||
'bundletool.jar is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it'
|
||||
)
|
||||
);
|
||||
});
|
||||
})
|
||||
@@ -158,8 +162,7 @@ describe('android', function () {
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: true,
|
||||
optional: true,
|
||||
message:
|
||||
'gst-launch-1.0 and gst-inspect-1.0 are installed at: path/to/gst-launch and path/to/gst-inspect',
|
||||
message: `${gstLaunchFilename} and ${gstInspectFilename} are installed at: path/to/gst-launch and path/to/gst-inspect`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
@@ -168,14 +171,13 @@ describe('android', function () {
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: false,
|
||||
optional: true,
|
||||
message: 'gst-launch-1.0 and/or gst-inspect-1.0 cannot be found',
|
||||
message: `${gstLaunchFilename} and/or ${gstInspectFilename} cannot be found`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
it('fix', async function () {
|
||||
removeColors(await check.fix()).should.equal(
|
||||
'gst-launch-1.0 and gst-inspect-1.0 are used to stream the screen of the device under test. ' +
|
||||
'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details'
|
||||
`${gstLaunchFilename} and ${gstInspectFilename} are used to stream the screen of the device under test. Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details`
|
||||
);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from 'node:path';
|
||||
import {BinaryIsInPathCheck, AndroidSdkExists} from '../../lib/dev';
|
||||
import {fs} from '@appium/support';
|
||||
import * as tp from 'teen_process';
|
||||
@@ -65,12 +66,12 @@ describe('dev', function () {
|
||||
check.autofix.should.not.be.ok;
|
||||
});
|
||||
it('diagnose - success', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
process.env.ANDROID_HOME = path.resolve('/', 'a', 'b', 'c', 'd');
|
||||
mocks.fs.expects('exists').once().returns(B.resolve(true));
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: true,
|
||||
optional: false,
|
||||
message: 'android-16 was found at: /a/b/c/d/platforms/android-16',
|
||||
message: `android-16 was found at: ${process.env.ANDROID_HOME}${path.sep}platforms${path.sep}android-16`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
@@ -84,12 +85,12 @@ describe('dev', function () {
|
||||
mocks.verify();
|
||||
});
|
||||
it('diagnose - failure - invalid path', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
process.env.ANDROID_HOME = path.resolve('/', 'a', 'b', 'c', 'd');
|
||||
mocks.fs.expects('exists').once().returns(B.resolve(false));
|
||||
(await check.diagnose()).should.deep.equal({
|
||||
ok: false,
|
||||
optional: false,
|
||||
message: "android-16 could NOT be found at '/a/b/c/d/platforms/android-16'!",
|
||||
message: `android-16 could NOT be found at '${process.env.ANDROID_HOME}${path.sep}platforms${path.sep}android-16'!`,
|
||||
});
|
||||
mocks.verify();
|
||||
});
|
||||
|
||||
@@ -3,6 +3,158 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.0](https://github.com/appium/appium/compare/@appium/docutils@0.3.13...@appium/docutils@0.4.0) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Allow to provide ipv6 addresses and handle broadcast addresses in logs ([#18674](https://github.com/appium/appium/issues/18674)) ([8f63dab](https://github.com/appium/appium/commit/8f63dabb5dca882e522026f243e3445f5418874b))
|
||||
* **docutils:** init: scafffold package.json ([2766dcc](https://github.com/appium/appium/commit/2766dcca4cfdc74904cc21f4881d748ed8ffd198))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency yaml to v2.3.0 ([325d237](https://github.com/appium/appium/commit/325d2372078ee01e8fb58613ffd13417f2527151))
|
||||
* **docutils:** update dependency yaml to v2.3.1 ([1a2260b](https://github.com/appium/appium/commit/1a2260bb15d75e4f4c23551dc61529108a7b0d88))
|
||||
* **support:** update dependency semver to v7.5.1 ([5a55509](https://github.com/appium/appium/commit/5a555090c5322318f845415d743ee5c645fb94a0))
|
||||
* **types:** update dependency type-fest to v3.11.0 ([19277f6](https://github.com/appium/appium/commit/19277f6e14a56e52b4669d633e148ad4a3da2c7a))
|
||||
* **types:** update dependency type-fest to v3.11.1 ([56499eb](https://github.com/appium/appium/commit/56499eb997b551739bed628f057de7987674ea7f))
|
||||
|
||||
|
||||
|
||||
## [0.3.13](https://github.com/appium/appium/compare/@appium/docutils@0.3.12...@appium/docutils@0.3.13) (2023-05-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** fix broken executable ([f0ebe2e](https://github.com/appium/appium/commit/f0ebe2e02f3e4164f07fd10ca36f460ff7a61682))
|
||||
|
||||
|
||||
|
||||
## [0.3.12](https://github.com/appium/appium/compare/@appium/docutils@0.3.11...@appium/docutils@0.3.12) (2023-05-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** add @types/which as it is exported ([9538c34](https://github.com/appium/appium/commit/9538c34131cd2f8a3c45039efd65678bc0980497))
|
||||
* **docutils:** exec mike/mkdocs via python -m ([426830d](https://github.com/appium/appium/commit/426830d14a348f163535a7f935a65b028008738d))
|
||||
* **docutils:** provide CJS executable for proper linking ([db73971](https://github.com/appium/appium/commit/db73971018b560ca47527173094720eee3d20a55))
|
||||
* **docutils:** run mkdocs via python -m ([5d24ea1](https://github.com/appium/appium/commit/5d24ea1ad294fa28f7ecce125e5342b081a84c83))
|
||||
* **docutils:** try python3 if python not in PATH ([7b17754](https://github.com/appium/appium/commit/7b177547865a8343625cf089c97cebfb93fd6955))
|
||||
|
||||
|
||||
|
||||
## [0.3.11](https://github.com/appium/appium/compare/@appium/docutils@0.3.10...@appium/docutils@0.3.11) (2023-05-19)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.10](https://github.com/appium/appium/compare/@appium/docutils@0.3.9...@appium/docutils@0.3.10) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** remove -t arg for mkdocs which prevents theme customizations (fix [#18536](https://github.com/appium/appium/issues/18536)) ([#18617](https://github.com/appium/appium/issues/18617)) ([cdfe7c3](https://github.com/appium/appium/commit/cdfe7c3d82a61776a784236140dff94ff1f34540))
|
||||
* **docutils:** update dependency yaml to v2.2.2 ([ddadd69](https://github.com/appium/appium/commit/ddadd6977d76a8a5ce1559d2a4ff4a31b58fb42f))
|
||||
* **docutils:** update dependency yargs to v17.7.2 ([3d1e4af](https://github.com/appium/appium/commit/3d1e4af858ea21bcf3ab1e1015aa7bdf1776f3ce))
|
||||
* **support:** update dependency semver to v7.5.0 ([c568523](https://github.com/appium/appium/commit/c568523e017a8b52bdf5132a48bbfde791de704f))
|
||||
* **types:** update dependency type-fest to v3.10.0 ([3c4d3ac](https://github.com/appium/appium/commit/3c4d3acc09d2ca1ed74dc77c18c62482e4c70239))
|
||||
* **types:** update dependency type-fest to v3.9.0 ([94a207f](https://github.com/appium/appium/commit/94a207fc9718068f3657c51cc8be0ef682f16b11))
|
||||
|
||||
|
||||
|
||||
## [0.3.9](https://github.com/appium/appium/compare/@appium/docutils@0.3.8...@appium/docutils@0.3.9) (2023-04-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** do not bail when no site_dir found ([c85b89e](https://github.com/appium/appium/commit/c85b89ecbe28f2fd720d73fca7cd11f283eb9a0a))
|
||||
|
||||
|
||||
|
||||
## [0.3.8](https://github.com/appium/appium/compare/@appium/docutils@0.3.7...@appium/docutils@0.3.8) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.7](https://github.com/appium/appium/compare/@appium/docutils@0.3.6...@appium/docutils@0.3.7) (2023-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency semver to v7.4.0 ([6ae86bd](https://github.com/appium/appium/commit/6ae86bd5c7be879a4d8852951d73eb2f68128df3))
|
||||
|
||||
|
||||
|
||||
## [0.3.6](https://github.com/appium/appium/compare/@appium/docutils@0.3.5...@appium/docutils@0.3.6) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.5](https://github.com/appium/appium/compare/@appium/docutils@0.3.4...@appium/docutils@0.3.5) (2023-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** properly respects log levels ([e9426fc](https://github.com/appium/appium/commit/e9426fcfecc1d47ab503fc72af4236c4f1ac4b1e))
|
||||
* **types:** update dependency type-fest to v3.8.0 ([d6c42e9](https://github.com/appium/appium/commit/d6c42e99c08efce0b34796d5982ce379fca044d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.4](https://github.com/appium/appium/compare/@appium/docutils@0.3.3...@appium/docutils@0.3.4) (2023-04-05)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.3](https://github.com/appium/appium/compare/@appium/docutils@0.3.2...@appium/docutils@0.3.3) (2023-04-04)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.2](https://github.com/appium/appium/compare/@appium/docutils@0.3.1...@appium/docutils@0.3.2) (2023-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.7.2 ([5580539](https://github.com/appium/appium/commit/55805390b5a0c6aa718bb357b30f66651f3db281))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.1](https://github.com/appium/appium/compare/@appium/docutils@0.3.0...@appium/docutils@0.3.1) (2023-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* backwards-compatible fixes for TS v5.x ([4974403](https://github.com/appium/appium/commit/49744036619ecc239e0e6255a13d38cafd709920))
|
||||
* **docutils:** inherit stdio in long-running processes ([ad11fbf](https://github.com/appium/appium/commit/ad11fbf97b57a0c4302d37146660e87c564a5404))
|
||||
* **docutils:** update dependency typedoc to v0.23.27 ([ef9cbe6](https://github.com/appium/appium/commit/ef9cbe6bcc27a3633eda01cd7c08a4466e304630))
|
||||
* **docutils:** update dependency typedoc to v0.23.28 ([dbc0bf6](https://github.com/appium/appium/commit/dbc0bf69e855744ed69ff3b75a9981b313579fa2))
|
||||
* **types:** update dependency type-fest to v3.7.0 ([6912fa1](https://github.com/appium/appium/commit/6912fa14f2a7d338f17e1bed060e959de7aba1d6))
|
||||
* **types:** update dependency type-fest to v3.7.1 ([bc860c7](https://github.com/appium/appium/commit/bc860c733a73760f0c42cbfb384e04d50c376d5e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/appium/appium/compare/@appium/docutils@0.2.2...@appium/docutils@0.3.0) (2023-03-08)
|
||||
|
||||
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const {main} = require('../build/lib/cli');
|
||||
const {getLogger} = require('../build/lib/logger');
|
||||
|
||||
const log = getLogger('cli');
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then, promise/prefer-await-to-callbacks
|
||||
main().catch((err) => {
|
||||
log.error('Caught otherwise-unhandled rejection (this is probably a bug):', err);
|
||||
});
|
||||
@@ -5,53 +5,54 @@
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {exec, SubProcess, SubProcessOptions, TeenProcessExecOptions} from 'teen_process';
|
||||
import path from 'node:path';
|
||||
import {exec, TeenProcessExecOptions} from 'teen_process';
|
||||
import {
|
||||
DEFAULT_DEPLOY_BRANCH,
|
||||
DEFAULT_DEPLOY_REMOTE,
|
||||
DEFAULT_SERVE_HOST,
|
||||
DEFAULT_SERVE_PORT,
|
||||
NAME_BIN,
|
||||
NAME_MIKE,
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_PYTHON,
|
||||
} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {findMkDocsYml, readPackageJson, whichMike} from '../fs';
|
||||
import logger from '../logger';
|
||||
import {argify, stopwatch, TeenProcessSubprocessStartOpts} from '../util';
|
||||
import {findMkDocsYml, findPython, readPackageJson} from '../fs';
|
||||
import {getLogger} from '../logger';
|
||||
import {argify, spawnBackgroundProcess, SpawnBackgroundProcessOpts, stopwatch} from '../util';
|
||||
|
||||
const log = logger.withTag('builder:deploy');
|
||||
const log = getLogger('builder:deploy');
|
||||
|
||||
/**
|
||||
* Runs `mike serve`
|
||||
* @param pythonPath Path to Python 3 executable
|
||||
* @param args Extra args to `mike build`
|
||||
* @param opts Extra options for `teen_process.Subprocess.start`
|
||||
* @param mikePath Path to `mike` executable
|
||||
*/
|
||||
async function doServe(
|
||||
pythonPath: string,
|
||||
args: string[] = [],
|
||||
{startDetector, detach, timeoutMs}: TeenProcessSubprocessStartOpts = {},
|
||||
mikePath?: string
|
||||
opts: SpawnBackgroundProcessOpts = {}
|
||||
) {
|
||||
mikePath = mikePath ?? (await whichMike());
|
||||
const finalArgs = ['serve', ...args];
|
||||
const opts: SubProcessOptions = {stdio: 'inherit'};
|
||||
log.debug('Launching %s with args: %s', mikePath, finalArgs);
|
||||
const proc = new SubProcess(mikePath, finalArgs, opts);
|
||||
return await proc.start(startDetector, detach, timeoutMs);
|
||||
const finalArgs = ['-m,', NAME_MIKE, 'serve', ...args];
|
||||
return spawnBackgroundProcess(pythonPath, finalArgs, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `mike build`
|
||||
* @param pythonPath Path to Python 3 executable
|
||||
* @param args Extra args to `mike build`
|
||||
* @param opts Extra options to `teen_process.exec`
|
||||
* @param mikePath Path to `mike` executable
|
||||
*/
|
||||
async function doDeploy(args: string[] = [], opts: TeenProcessExecOptions = {}, mikePath?: string) {
|
||||
mikePath = mikePath ?? (await whichMike());
|
||||
const finalArgs = ['deploy', ...args];
|
||||
log.debug('Launching %s with args: %O', mikePath, finalArgs);
|
||||
return await exec(mikePath, finalArgs, opts);
|
||||
async function doDeploy(
|
||||
pythonPath: string,
|
||||
args: string[] = [],
|
||||
opts: TeenProcessExecOptions = {}
|
||||
) {
|
||||
const finalArgs = ['-m', NAME_MIKE, 'deploy', ...args];
|
||||
log.debug('Executing %s via: %s %O', NAME_MIKE, pythonPath, finalArgs);
|
||||
return await exec(pythonPath, finalArgs, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,6 +94,15 @@ export async function deploy({
|
||||
execOpts,
|
||||
}: DeployOpts = {}) {
|
||||
const stop = stopwatch('deploy');
|
||||
|
||||
const pythonPath = await findPython();
|
||||
|
||||
if (!pythonPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_PYTHON}3/${NAME_PYTHON} executable in PATH; please install Python v3`
|
||||
);
|
||||
}
|
||||
|
||||
mkDocsYmlPath = mkDocsYmlPath ?? (await findMkDocsYml(cwd));
|
||||
if (!mkDocsYmlPath) {
|
||||
throw new DocutilsError(
|
||||
@@ -125,8 +135,9 @@ export async function deploy({
|
||||
}
|
||||
stop(); // discard
|
||||
// unsure about how SIGHUP is handled here
|
||||
await doServe(mikeArgs, serveOpts);
|
||||
await doServe(pythonPath, mikeArgs, serveOpts);
|
||||
} else {
|
||||
log.info('Deploying into branch %s', branch);
|
||||
const mikeArgs = [
|
||||
...argify(
|
||||
_.omitBy(
|
||||
@@ -139,9 +150,9 @@ export async function deploy({
|
||||
if (alias) {
|
||||
mikeArgs.push(alias);
|
||||
}
|
||||
await doDeploy(mikeArgs, execOpts);
|
||||
await doDeploy(pythonPath, mikeArgs, execOpts);
|
||||
|
||||
log.success('Mike finished deployment into branch %s (%dms)', branch, stop());
|
||||
log.success('Finished deployment into branch %s (%dms)', branch, stop());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +234,7 @@ export interface DeployOpts {
|
||||
execOpts?: TeenProcessExecOptions;
|
||||
|
||||
/**
|
||||
* Extra options for {@linkcode teen_process.Subprocess.start}
|
||||
* Extra options for {@linkcode spawnBackgroundProcess}
|
||||
*/
|
||||
serveOpts?: TeenProcessSubprocessStartOpts;
|
||||
serveOpts?: SpawnBackgroundProcessOpts;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user