mirror of
https://github.com/appium/appium.git
synced 2026-10-31 22:32:24 +08:00
Compare commits
198
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
820428b769 | ||
|
|
034a51b686 | ||
|
|
f2cff24685 | ||
|
|
53bfc68eee | ||
|
|
dce45bdf83 | ||
|
|
e73e47de18 | ||
|
|
a080b72920 | ||
|
|
c8e9059711 | ||
|
|
d5f3a288c2 | ||
|
|
998e2586a7 | ||
|
|
78a22b2a1c | ||
|
|
a5876c8f26 | ||
|
|
4ef0c49b24 | ||
|
|
c18b43f2bd | ||
|
|
2f69e6db2a | ||
|
|
ed8e83c76c | ||
|
|
d6204b6902 | ||
|
|
f0a01aea48 | ||
|
|
b64092dc8c | ||
|
|
6082cb1164 | ||
|
|
b88ac49577 | ||
|
|
c933732089 | ||
|
|
b71340a4ba | ||
|
|
5a2611137b | ||
|
|
aaf58c3de6 | ||
|
|
fb34ab9172 | ||
|
|
a21ae6ab2b | ||
|
|
1358937db2 | ||
|
|
f9ba31383a | ||
|
|
b85a92a912 | ||
|
|
544aeec149 | ||
|
|
97c5d9268a | ||
|
|
3e1eda0193 | ||
|
|
a9e86aabc6 | ||
|
|
a888e26331 | ||
|
|
12147d518f | ||
|
|
03ddcdfcaf | ||
|
|
fc01fb958a | ||
|
|
9a5c9ae55c | ||
|
|
2e187804b2 | ||
|
|
6c4189b400 | ||
|
|
95a34185d1 | ||
|
|
a8cc551210 | ||
|
|
9ad0dfc3c8 | ||
|
|
5f39ae1857 | ||
|
|
aaf31a577c | ||
|
|
97d8f0df44 | ||
|
|
de5b55ae90 | ||
|
|
5d32946a44 | ||
|
|
93b6a470cd | ||
|
|
ead861077a | ||
|
|
8ef51855e3 | ||
|
|
c320e8a1a6 | ||
|
|
f548a8bfed | ||
|
|
0ffafd034b | ||
|
|
39e6b234c4 | ||
|
|
ec57ff4c2a | ||
|
|
803660ca29 | ||
|
|
41836a4a7b | ||
|
|
ff62e208d9 | ||
|
|
6e752b03de | ||
|
|
75650a6b74 | ||
|
|
e5b569c3a0 | ||
|
|
b9151a573b | ||
|
|
43aedc21e8 | ||
|
|
cccd602dc7 | ||
|
|
f1760727bd | ||
|
|
5e479eadbe | ||
|
|
7cd52db673 | ||
|
|
8241e748c7 | ||
|
|
151c19c1c3 | ||
|
|
87214fd757 | ||
|
|
3fa73a1d1a | ||
|
|
1ab323b525 | ||
|
|
b8e1f38455 | ||
|
|
02896d0513 | ||
|
|
8a6a4ba7e7 | ||
|
|
52b646c65b | ||
|
|
2b351705d4 | ||
|
|
c13333bb97 | ||
|
|
62f4244753 | ||
|
|
26eb766a79 | ||
|
|
679865ed2f | ||
|
|
f1d768c62c | ||
|
|
d6cca5175c | ||
|
|
3d614d6d41 | ||
|
|
ee9b2a30de | ||
|
|
e30ad0e88c | ||
|
|
01061b2919 | ||
|
|
24e7aeca7d | ||
|
|
ed1856d720 | ||
|
|
757fbaaf42 | ||
|
|
fdd2ac098d | ||
|
|
3906f7d1ca | ||
|
|
f09fbb64dc | ||
|
|
97fe159276 | ||
|
|
cb145875c0 | ||
|
|
d75338f1c8 | ||
|
|
67e7140216 | ||
|
|
81256f8f90 | ||
|
|
0bbb25dfb4 | ||
|
|
5263366744 | ||
|
|
4ab2780978 | ||
|
|
58b71869d1 | ||
|
|
08a1f1be94 | ||
|
|
b7cecfffb5 | ||
|
|
52a755a913 | ||
|
|
35e0012d8a | ||
|
|
ed20e00754 | ||
|
|
9e60e80b13 | ||
|
|
1a5b8cca2d | ||
|
|
ced560206b | ||
|
|
7dbf6f2cd3 | ||
|
|
84c49b1529 | ||
|
|
81bc527be6 | ||
|
|
2e909478e9 | ||
|
|
00ae1625d7 | ||
|
|
88293f8039 | ||
|
|
54a4a8da03 | ||
|
|
598b255e8d | ||
|
|
63a5c22127 | ||
|
|
af066eb5c3 | ||
|
|
e5254b5b45 | ||
|
|
c18eb03261 | ||
|
|
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 |
@@ -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",
|
||||
@@ -27,9 +21,17 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["./test/setup.js", "./**/scripts/**/*.js", "./packages/*/index.js"],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,6 +6,20 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: ':stop_sign: _For support questions, please visit [http://discuss.appium.io](http://discuss.appium.io) instead._'
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 'Do I have the most recent component updates?'
|
||||
description: 'Appium 1-related issues are going to be ignored as this version has reached its end of life.'
|
||||
options:
|
||||
- label: 'I use the most recent available driver/plugin and server versions'
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 'Is the component officially supported by the Appium team?'
|
||||
description: 'Make sure you report an issue for a component which is officially supported/maintained by the Appium team.'
|
||||
options:
|
||||
- label: 'I have verified the component repository is present under the Appium organization in GitHub'
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 'Is there an existing issue for this?'
|
||||
@@ -34,7 +48,7 @@ body:
|
||||
An easy way to get started is to copy [sample code](https://github.com/appium/appium/tree/master/packages/appium/sample-code) and modify it accordingly.
|
||||
|
||||
If the result is more than a screenful of text _or_ requires multiple files, please:
|
||||
|
||||
|
||||
- _Attach_ (do not paste) it to this textarea, _or_
|
||||
- Put it in a [Gist](https://gist.github.com) and paste the link, _or_
|
||||
- Provide a link to a new or existing public repository exhibiting the issue
|
||||
@@ -46,14 +60,12 @@ body:
|
||||
description: 'Please provide the following information about your environment; feel free to remove any items which are not relevant.'
|
||||
value: |
|
||||
- Operating system:
|
||||
- If running via `appium` CLI...
|
||||
- Appium CLI version (output of `appium --version`):
|
||||
- Node.js version (output of `node --version`):
|
||||
- `npm` version (output of `npm --version`):
|
||||
- Last `appium` version which did _not_ exhibit the problem:
|
||||
- If running Appium Desktop, its version:
|
||||
- Appium server version (output of `appium --version`):
|
||||
- Appium driver(s) and their version(s):
|
||||
- Appium plugin(s) and their version(s):
|
||||
- Node.js version (output of `node --version`):
|
||||
- `npm` version (output of `npm --version`):
|
||||
- Last component(s) version which did _not_ exhibit the problem:
|
||||
- Platform and version under test:
|
||||
- Real device or emulator/simulator:
|
||||
validations:
|
||||
@@ -63,12 +75,12 @@ body:
|
||||
label: 'Link to Appium Logs'
|
||||
description: |
|
||||
Create a [Gist](https://gist.github.com)—which contains your _full_ Appium logs—and link it here. Alternatively, you can attach a logfile to this issue (drag it into the "Further Information" field below).
|
||||
|
||||
|
||||
:warning: _Remember to redact or remove any sensitive information!_
|
||||
placeholder: 'https://gist.github.com/...'
|
||||
placeholder: 'https://gist.github.com/...'
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Futher Information
|
||||
label: Further Information
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
|
||||
@@ -42,21 +42,45 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Tests
|
||||
prepare_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
versions: ${{ steps.generate-matrix.outputs.versions }}
|
||||
steps:
|
||||
- name: Select 3 most recent LTS versions of Node.js
|
||||
id: generate-matrix
|
||||
run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:3] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test:
|
||||
needs:
|
||||
- prepare_matrix
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
node-version: ${{ fromJSON(needs.prepare_matrix.outputs.versions) }}
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# TODO: Windows is not stable and slow to run
|
||||
# - windows-latest
|
||||
# TODO: Enable below envs after all tests have been verified green
|
||||
# - macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # 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
|
||||
@@ -70,7 +94,7 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -14,20 +14,19 @@ 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 }}
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Appium
|
||||
run: |
|
||||
if [[ "$GITHUB_REF_TYPE" == tag ]]; then
|
||||
npm i -g $GITHUB_REF_NAME
|
||||
npm i --location=global $GITHUB_REF_NAME
|
||||
else
|
||||
npm i -g appium@next
|
||||
npm i --location=global appium
|
||||
fi
|
||||
shell: bash
|
||||
- name: Install drivers and plugins
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
- name: Install dependencies (Node.js)
|
||||
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run FOSSA Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
with:
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: Publish docs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- 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: Publish docs
|
||||
run: |
|
||||
cd packages/appium
|
||||
npm run publish:docs
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
name: Check Renovate Configs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
|
||||
- name: Validate Renovate Config
|
||||
uses: rinchsan/renovate-config-validator@1ea1e8514f6a33fdd71c40b0a5fa3512b9e7b936 # tag=v0
|
||||
with:
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
module.exports = {
|
||||
'*.js': ['eslint --fix', 'prettier --write'],
|
||||
'*.ts': ['prettier --write'],
|
||||
'*.(js|ts)': ['eslint --fix', 'prettier --write'],
|
||||
'appium-config-schema.js': () => [
|
||||
'npm run --workspace=./packages/schema build',
|
||||
'git add -A packages/schema/lib/appium-config.schema.json',
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"stateful.runme"
|
||||
]
|
||||
}
|
||||
@@ -16,18 +16,16 @@ 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: The current Appium 2.0 documentation can be found [here](https://appium.io/docs/en/latest/).
|
||||
|
||||
: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.
|
||||
: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, 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.io/docs/en/latest/).
|
||||
:bangbang: If you still use the deprecated Appium Desktop containing the built-in server then you won't be able to upgrade to Appium 2. Consider switching to [Appium Inspector](https://github.com/appium/appium-inspector) instead.
|
||||
|
||||
### Requirements
|
||||
|
||||
- macOS, Linux or Windows operating system
|
||||
- Node.js 14+
|
||||
- Node.js 16+. Any relevant [LTS release](https://nodejs.dev/en/about/releases/) should work just fine.
|
||||
- NPM (Node Package Manager) 8+
|
||||
|
||||
These are only server requirements. Each driver might have its own requirements. Consider checking the corresponding driver tutorial for more details.
|
||||
@@ -37,10 +35,16 @@ These are only server requirements. Each driver might have its own requirements.
|
||||
To install Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
|
||||
```bash
|
||||
npm install -g appium@next
|
||||
npm i --location=global appium
|
||||
```
|
||||
|
||||
:bangbang: Running `npm install -g appium` would still install Appium 1 because version 2 is in its late beta stage.
|
||||
To update Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
|
||||
```bash
|
||||
npm install --location=global appium@latest
|
||||
```
|
||||
|
||||
:bangbang: Make sure Appium 1 is fully uninstalled before upgrading it to Appium 2 (`npm uninstall --location=global appium`). Unexpected errors might appear if this has not been done.
|
||||
|
||||
### Drivers
|
||||
|
||||
@@ -190,10 +194,6 @@ Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test, and run Appium from the source.
|
||||
|
||||
### Roadmap
|
||||
|
||||
Interested in where Appium is heading in the future? Check out the [Roadmap](ROADMAP.md)
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
Appium Roadmap
|
||||
---------------
|
||||
|
||||
This document is designed to be a living record of the current and projected
|
||||
priorities for the Appium project. It is, of course, always subject to change
|
||||
and exists solely to give the community an idea of where we're going. The
|
||||
roadmap is set by the Appium core team. If you have feature suggestions for
|
||||
Appium, please submit a [GitHub issue](https://github.com/appium/appium/issues)
|
||||
and, if of sufficient size and approved by the team, it will be added here. If
|
||||
you have concerns about the roadmap, you can e-mail the Appium devs at
|
||||
`appium-developers@googlegroups.com` or raise a GitHub issue. Note that
|
||||
bugfixes and miscellaneous features are not considered "roadmap projects", and
|
||||
will be addressed in the due course of normal Appium development. This page
|
||||
exists to outline large-scale future plans for Appium.
|
||||
|
||||
## Current Roadmap Projects
|
||||
|
||||
| Project | Description | ETD | Projected Appium Release |
|
||||
|---------|-------------|-----|--------------------------|
|
||||
|[Appium CI + Metrics Improvements](https://github.com/appium/appium/issues/7168)|We have CI servers running via Cloudbees that we don't understand and which break. Let's document and own them so they work reliably. Also, let's implement gathering various metrics about driver performance so we can track this over time and ensure we're not releasing a driver which has decreased in performance or reliability.|Q2 2017|N/A|
|
||||
|
||||
## Upcoming Projects (in rough order of priority)
|
||||
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
|[Appium Client Library Updates](https://github.com/appium/appium/issues/7170)|It's been too long since we ensured that Appium's client libraries were up to date with the server|
|
||||
|[Test App Overhaul](https://github.com/appium/appium/issues/7172)|Our current test applications (the ones used to test Appium's iOS and Android support) are not ideal. We use too many of them and the ones we use have a random hodgepodge of app SDK features, sometimes not enough to sufficiently test automation support. It'd be great to find a way to ensure our test app(s) have all the features we need to test, and keep in sync with our requirements.|
|
||||
|[iOS Test Startup Time Improvement](https://github.com/appium/appium/issues/7173)|iOS startup times have gotten progressively longer with each iOS release. In part this is Apple's fault and there's not anything we can do about it. But we should investigate and see if we can be more efficient in how we start, especially given that we can now focus on XCUITest and leave Instruments behind|
|
||||
|[Appium Dearchitecture](https://github.com/appium/appium/issues/7174)|OK, we went a bit overboard in Appium 1.5 by exploding Appium into 40+ repos and NPM packages. Let's think a little more critically about it and consolidate into the minimal number of repos/packages that make sense for our code and community patterns.|
|
||||
|[Appium 2.0](https://github.com/appium/appium/issues/7175)|Appium, rather than bundling all the separate drivers by default, becomes a driver management interface (in addition to continuing to play the role of automation frontend server). Appium gets a set of CLI tools for users to pick and choose which drivers and versions of drivers they want to use via Appium|
|
||||
|
||||
## Projects Under Consideration (unplanned and unprioritized)
|
||||
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
|Windows Phone Support|There has been some work done on automating Windows Phone apps. We could take this all the way to integration with Appium in the form of a WindowsDriver.|
|
||||
|Apple Watch support|Appium should support Apple Watch apps via the Watch simulator|
|
||||
|Unity3d backend POC|This is a research project to determine whether we can add some kind of support for 3d game automation, probably based on the Unity3d game development platform.|
|
||||
|
||||
## Completed Roadmap Projects
|
||||
|
||||
| Project | Description | Completed | Appium Release | Notes |
|
||||
|---------|-------------|-----------|----------------|-------|
|
||||
|[Sample Code Overhaul](https://github.com/appium/appium/issues/7171)|Our sample code repo has grown tangled and broken. Let's make a new one with minimal examples which are the same across every language, and actually maintain the repo so as not to let it become overgrown.|
|
||||
|[Onboarding Overhaul](https://github.com/appium/appium/issues/7169)|Have a new site built with MkDocs that documents all of the Selenium and Appium commands with code samples|1/2018|n/a|appium.io|
|
||||
|[New Appium GUIs](https://github.com/appium/appium/issues/7167)|The current Appium GUIs (Appium.app and Appium.exe) have a number of issues. They aren't maintained by the core Appium team. They're written in Objective-C and C# respectively: two languages, and specifically, two languages other than Appium's core language. Because of this they lag behind the Appium server and are often broken. However, users love them primarily because of the Inspector feature (the thing that lets them graphically navigate their app), so we have to field a ton of complaints about these projects. There's an opportunity to start over. With [Electron](http://electron.atom.io/), we'd be able to take the new version of Appium we're working on and embed it directly inside a Node-based GUI app written using the languages and technologies the core team is experienced with. We'd have one codebase and could distribute the app on Mac, Windows, and Linux. The GUIs would become an official part of Appium's release cycle so they wouldn't be broken.|3/2017|N/A|[Appium Desktop website](https://github.com/appium/appium-desktop)|
|
||||
|[Android 7.1.1 Support](https://github.com/appium/appium/issues/7165)|Google has released Android 7.1.1 (API Level 25). Ensure it works with UiAutomator 1/2|2/2017|1.6.4||
|
||||
|[Android 7.0 Support](https://github.com/appium/appium/issues/7156)|Support Android 7 (Nougat)|11/2016|1.6.3||
|
||||
|[iOS 10.2 Support](https://github.com/appium/appium/issues/7166)|Support iOS 10.2|11/2016|1.6.1||
|
||||
|[iOS 10.1 Support](https://github.com/appium/appium/issues/7105)|Support iOS 10.1|11/2016|1.6.1||
|
||||
|UiAutomator2 integration|Google has released [UiAutomator 2](http://developer.android.com/reference/android/support/test/uiautomator/package-summary.html), and it promises to fix some issues and limitations with the previous UiAutomator support. We will build this inside of Selendroid since it requires an instrumentation context.|10/2016?|1.6.0|
|
||||
|XCUITest integration / iOS 10 support|Instruments + UIAutomation has been working well for many Appium users, but requires crazy workarounds to deal with bugs which remain unfixed on the Apple side. Also, UIAutomation has been removed in favor of XCUITest for iOS 10+. This new backend would exist alongside the UIAutomation backend.|10/2016|1.6.0||
|
||||
|iOS 9.3 support|Appium should support iOS 9.3 / Xcode 7.3|3/2016|1.5.1||
|
||||
|[Architecture Overhaul](https://github.com/appium/appium/issues/5169)|Appium's architecture has grown unwieldy enough that adding new features and preventing regressions has become difficult. A rearchitecture is in order to solve these issues as well as migrate the codebase to a form of JS much easier to work in and understand.|2/2015|1.5.0||
|
||||
|iOS 9.2 support|Appium should support iOS 9.2 / Xcode 7.2|11/2015|1.4.14||
|
||||
|iOS 9.1 support|Appium should support iOS 9.1 / Xcode 7.1|10/2015|1.4.12||
|
||||
|iOS 9.0 support|Appium should support iOS 9.0 / Xcode 7.0|10/2015|1.4.11||
|
||||
|iOS 8.4 support|Appium should support iOS 8.4 / Xcode 6.2|5/2015|1.4.0||
|
||||
|Appium CI|Appium has unique needs for Continuous Integration because of the highly specialized environments required (iOS simulators, Xcode, Android emulators, the Android SDK, etc...). We therefore need to create a custom CI solution (perhaps sitting underneath Cloudbees or some other OSS-friendly CI server) that can be used for all Appium projects which need integration tests.|5/2015|_N/A_|Usage instructions at [appium/appium-ci](https://github.com/appium/appium-ci)|
|
||||
Generated
+3305
-3947
File diff suppressed because it is too large
Load Diff
+24
-23
@@ -34,7 +34,7 @@
|
||||
"clean": "run-s clean:distfiles clean:workspaces",
|
||||
"clean:dangerously": "run-s clean:workspaces clean:force",
|
||||
"clean:deps": "lerna clean --yes",
|
||||
"clean:distfiles": "npx rimraf \"./packages/*/build\" \"./packages/*/**/.tsconfig.tsbuildinfo\"; npx nx reset",
|
||||
"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",
|
||||
@@ -84,46 +84,47 @@
|
||||
"singleQuote": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"@commitlint/cli": "17.6.3",
|
||||
"@commitlint/config-conventional": "17.6.3",
|
||||
"@colors/colors": "1.6.0",
|
||||
"@commitlint/cli": "17.7.1",
|
||||
"@commitlint/config-conventional": "17.7.0",
|
||||
"@tsconfig/node14": "1.0.3",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/diff": "5.0.3",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.16.5",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/sinon": "10.0.14",
|
||||
"@types/node": "18.17.6",
|
||||
"@types/semver": "7.5.0",
|
||||
"@types/sinon": "10.0.16",
|
||||
"@types/sinon-chai": "3.2.9",
|
||||
"@types/source-map-support": "0.5.6",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/ws": "8.5.4",
|
||||
"@types/ws": "8.5.5",
|
||||
"@types/yargs": "17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
||||
"@typescript-eslint/parser": "5.59.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"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.40.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"conventional-changelog-conventionalcommits": "6.1.0",
|
||||
"cpy-cli": "5.0.0",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-prettier": "8.10.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.7",
|
||||
"handlebars": "4.7.8",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "12.0.0",
|
||||
"json-schema-to-typescript": "13.0.2",
|
||||
"lerna": "6.6.2",
|
||||
"lint-staged": "13.2.2",
|
||||
"lint-staged": "13.3.0",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "2.0.3",
|
||||
"midnight-smoker": "2.0.4",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.2.0",
|
||||
@@ -131,9 +132,9 @@
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.8.8",
|
||||
"rewiremock": "3.14.5",
|
||||
"rimraf": "3.0.2",
|
||||
"rimraf": "5.0.1",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "15.0.4",
|
||||
"sinon": "15.2.0",
|
||||
"sinon-chai": "3.7.0",
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
@@ -142,9 +143,9 @@
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.9.5",
|
||||
"typescript": "5.0.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.31.1",
|
||||
"webdriverio": "7.32.3",
|
||||
"ws": "8.13.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,132 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.1.1](https://github.com/appium/appium/compare/appium@2.1.0...appium@2.1.1) (2023-08-21)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.1.0](https://github.com/appium/appium/compare/appium@2.0.1...appium@2.1.0) (2023-08-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **base-driver:** Make it possible to provide settings as a map in session capabilities ([#18970](https://github.com/appium/appium/issues/18970)) ([aaf58c3](https://github.com/appium/appium/commit/aaf58c3de66fee2eb10225d05e156e7416a4ac79))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** Fix the plural form ([97d8f0d](https://github.com/appium/appium/commit/97d8f0df44d915b346a6ecd0d052369dd80a9569))
|
||||
* **appium:** update dependency winston to v3.10.0 ([b85a92a](https://github.com/appium/appium/commit/b85a92a912b3d2854e8102b41119fc780e1a6cc2))
|
||||
* **appium:** Update the GET /status response to be in sync with the standard ([#18972](https://github.com/appium/appium/issues/18972)) ([5a26111](https://github.com/appium/appium/commit/5a2611137b2a7428bb2e593fc945e21874c4cdae))
|
||||
* **support:** update dependency glob to v10 ([#18490](https://github.com/appium/appium/issues/18490)) ([aaf31a5](https://github.com/appium/appium/commit/aaf31a577cb0b9cbe22646dcd888dc393a03aa11))
|
||||
* **support:** update dependency semver to v7.5.4 ([03ddcdf](https://github.com/appium/appium/commit/03ddcdfcafbccf6698868a272e499cbdac41d791))
|
||||
* **types:** update dependency type-fest to v3.13.1 ([fb34ab9](https://github.com/appium/appium/commit/fb34ab917216121d2b554677a12f07a03393d218))
|
||||
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/appium/appium/compare/appium@2.0.0...appium@2.0.1) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** ensure plugin commands reset newCommandTimeout ([41836a4](https://github.com/appium/appium/commit/41836a4a7bfb0d7602839c081a4e3c4792e869df))
|
||||
* **support:** update dependency teen_process to v2.0.4 ([151c19c](https://github.com/appium/appium/commit/151c19c1c3b4c9b94aba10033c2d863f567d849b))
|
||||
|
||||
|
||||
|
||||
## [2.0.0-rc.5](https://github.com/appium/appium/compare/appium@2.0.0-rc.4...appium@2.0.0-rc.5) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** restrict address to ipv6/hostname ([#18824](https://github.com/appium/appium/issues/18824)) ([f09fbb6](https://github.com/appium/appium/commit/f09fbb64dce0e179a007f3a0ae9800e61fbe90eb)), closes [#18716](https://github.com/appium/appium/issues/18716)
|
||||
* **appium:** type fixes for ts v5 ([c13333b](https://github.com/appium/appium/commit/c13333bb979ee3409e6a62a5800c629781553b42))
|
||||
* **support:** update dependency semver to v7.5.3 ([ed1856d](https://github.com/appium/appium/commit/ed1856d720c442842d5c3fe5f6caeccb8229b5f1))
|
||||
|
||||
|
||||
|
||||
## [2.0.0-rc.4](https://github.com/appium/appium/compare/appium@2.0.0-rc.3...appium@2.0.0-rc.4) (2023-06-29)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-rc.3](https://github.com/appium/appium/compare/appium@2.0.0-rc.2...appium@2.0.0-rc.3) (2023-06-15)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.0-rc.2](https://github.com/appium/appium/compare/appium@2.0.0-rc.1...appium@2.0.0-rc.2) (2023-06-14)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -16,18 +16,16 @@ 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: The current Appium 2.0 documentation can be found [here](https://appium.io/docs/en/latest/).
|
||||
|
||||
: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.
|
||||
: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, 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.io/docs/en/latest/).
|
||||
:bangbang: If you still use the deprecated Appium Desktop containing the built-in server then you won't be able to upgrade to Appium 2. Consider switching to [Appium Inspector](https://github.com/appium/appium-inspector) instead.
|
||||
|
||||
### Requirements
|
||||
|
||||
- macOS, Linux or Windows operating system
|
||||
- Node.js 14+
|
||||
- Node.js 16+. Any relevant [LTS release](https://nodejs.dev/en/about/releases/) should work just fine.
|
||||
- NPM (Node Package Manager) 8+
|
||||
|
||||
These are only server requirements. Each driver might have its own requirements. Consider checking the corresponding driver tutorial for more details.
|
||||
@@ -37,10 +35,16 @@ These are only server requirements. Each driver might have its own requirements.
|
||||
To install Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
|
||||
```bash
|
||||
npm install -g appium@next
|
||||
npm i --location=global appium
|
||||
```
|
||||
|
||||
:bangbang: Running `npm install -g appium` would still install Appium 1 because version 2 is in its late beta stage.
|
||||
To update Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
|
||||
```bash
|
||||
npm install --location=global appium@latest
|
||||
```
|
||||
|
||||
:bangbang: Make sure Appium 1 is fully uninstalled before upgrading it to Appium 2 (`npm uninstall --location=global appium`). Unexpected errors might appear if this has not been done.
|
||||
|
||||
### Drivers
|
||||
|
||||
@@ -190,10 +194,6 @@ Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test, and run Appium from the source.
|
||||
|
||||
### Roadmap
|
||||
|
||||
Interested in where Appium is heading in the future? Check out the [Roadmap](ROADMAP.md)
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Server Environment Variables
|
||||
---
|
||||
|
||||
The primary way of configuring the Appium server is via [CLI Args](./args.md). However, some more
|
||||
advanced features are toggled or configured via environment variables. To set environment
|
||||
variables, refer to the documentation for your operating system and terminal. These are the
|
||||
environment variables that the Appium server understands:
|
||||
|
||||
|Variable |Description|
|
||||
|--------|-----------|
|
||||
|`APPIUM_HOME`|By default, Appium creates a directory called `.appium` in the home directory for your system user. You can adjust the directory with this variable, as detailed in the [Managing Extensions](../guides/managing-exts.md) guide.|
|
||||
|`APPIUM_TMP_DIR`|By default, Appium uses a random temporary directory for many of its operations. If you wish to use a specific directory, you may do so by including an absolute path as the value of this variable. The behaviour is equivalent to using the `--tmp` CLI arg.|
|
||||
|`APPIUM_PREFER_SYSTEM_UNZIP`|Set to `0` or `false` to request that Appium not use the `unzip` binary included on your system for unzipping downloaded apps or other artifacts. Instead it will use a JS-based unzip library. This could help on some systems with non-existent or non-standard `unzip` commands. Note that if unzipping fails using the system library, the fallback library will be attempted in any case, so setting this env var merely saves time in the event you know the system unzip will fail.|
|
||||
|`APPIUM_HOST`|Same as the `--address` CLI arg|
|
||||
|`APPIUM_PORT`|Same as the `--port` CLI arg|
|
||||
|`APPIUM_RELOAD_EXTENSIONS`|Set to `1` to cause Appium to re-require extensions when new sessions are created. This is mostly useful for [building extensions](../ecosystem/build-drivers.md)|
|
||||
|`APPIUM_OMIT_PEER_DEPS`|Adds `--omit=peer` to all the NPM commands run internally by Appium. Mostly an internal feature.|
|
||||
@@ -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,11 +2,11 @@
|
||||
title: Building Docs for Appium Extensions
|
||||
---
|
||||
|
||||
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 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.
|
||||
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
|
||||
@@ -16,16 +16,16 @@ this information in two places creates an opportunity for the docs to get out of
|
||||
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 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.
|
||||
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.
|
||||
|
||||
Luckily, 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.
|
||||
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
|
||||
|
||||
@@ -42,32 +42,180 @@ The other main piece is automatic documentation generation from your code files.
|
||||
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 can then be included in your docs site.
|
||||
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).
|
||||
|
||||
### Setup
|
||||
### Prerequisites
|
||||
|
||||
To take advantage of Appium's documentation utilities, you'll need to make sure of the following:
|
||||
To take advantage of Appium's documentation utilities, you'll need to install:
|
||||
|
||||
- Python 3 is available
|
||||
- The appropriate Python dependencies are available (the versions used by Appium itself are listed
|
||||
- in the doc utility's
|
||||
[requirements.txt](https://github.com/appium/appium/blob/master/packages/docutils/requirements.txt)).
|
||||
It's recommended you use the same versions.
|
||||
- Include the `@appium/docutils` NPM package as a dev dependency of your project
|
||||
- Annotate your commands using TypeDoc (this works for JS projects and not just TypeScript
|
||||
projects).
|
||||
- Put your markdown docs files in a directory
|
||||
- Create a [`mkdocs.yml`](https://www.mkdocs.org/user-guide/configuration/) file that extends
|
||||
Appium's base MkDocs configuration. (If your `mkdocs.yml` file is in the root of your repo, then
|
||||
the line at the top of it should look like:
|
||||
```yml
|
||||
INHERIT: ./node_modules/@appium/docutils/base-mkdocs.yml
|
||||
```
|
||||
Basically, make sure you're inheriting the path to `@appium/docutils`'s `base-mkdocs.yml`.
|
||||
- [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
|
||||
|
||||
@@ -77,16 +225,16 @@ examples:
|
||||
|
||||
```bash
|
||||
# Generate reference and build the mkdocs site into the site dir
|
||||
appium-docs build
|
||||
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
|
||||
appium-docs build --serve
|
||||
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!
|
||||
appium-docs build \
|
||||
npx appium-docs build \
|
||||
--deploy \
|
||||
-b docs-site \
|
||||
-m 'docs: auto-build docs for appium-xcuitest-driver@%s' \
|
||||
|
||||
@@ -152,6 +152,16 @@ your driver locally:
|
||||
appium driver install --source=local /path/to/your/driver/dev/dir
|
||||
```
|
||||
|
||||
### Refreshing your driver during development
|
||||
|
||||
When the Appium server starts, it loads your driver into memory. Changes to your driver code will
|
||||
not take effect until the next time the Appium server starts. Simply starting a new session is not
|
||||
sufficient to cause your driver's code to be reloaded.
|
||||
|
||||
However, you can set the `APPIUM_RELOAD_EXTENSIONS` environment variable to `1` to request that
|
||||
Appium clear its module cache and reload extensions whenever a new session is requested. This may
|
||||
obviate the need to restart the server when you make code changes to your driver.
|
||||
|
||||
## Standard driver implementation ideas
|
||||
|
||||
These are things you will probably find yourself wanting to do when creating a driver.
|
||||
|
||||
@@ -141,7 +141,9 @@ Appium's start server methods to handle starting and stopping the Appium server
|
||||
Of course, you can always install it locally as described above as well.
|
||||
|
||||
Anytime you make changes to your plugin code, you'll need to restart the Appium server to make sure
|
||||
it picks up the latest code.
|
||||
it picks up the latest code. As with drivers, you can set the `APPIUM_RELOAD_EXTENSIONS`
|
||||
environment variable if you wish Appium to try to re-require your plugin module when a new session
|
||||
starts.
|
||||
|
||||
## Standard plugin implementation ideas
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ You can also set server arguments as [Config file](./config.md) properties.
|
||||
|
||||
### :bangbang: Installing drivers during setup
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium@next`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install --location=global appium`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
|
||||
```bash
|
||||
appium driver install uiautomator2 # installs the latest driver version
|
||||
@@ -42,10 +42,10 @@ At this point, your drivers are installed and ready. There's a lot more you can
|
||||
If you're running in a CI environment or want to install Appium along with some drivers all in one step, you can do so using some special flags during install, for example:
|
||||
|
||||
```bash
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
npm install --location=global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall -g appium`) if you get an installation or startup error.
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall --location=global appium`) if you get an installation or startup error.
|
||||
|
||||
### :bangbang: Drivers installation path
|
||||
|
||||
@@ -122,7 +122,7 @@ appium driver update xcuitest
|
||||
(For a complete description of the update command, check out the [Extension
|
||||
CLI](../cli/extensions.md) doc)
|
||||
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install -g appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install --location=global appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
|
||||
If you would like to update to a specific version, not the latest, please uninstall the driver and install the desired version using the `install` subcommand instead of `update`.
|
||||
|
||||
@@ -225,11 +225,11 @@ Some Appium-internal NPM packages have been renamed (for example, `appium-base-d
|
||||
|
||||
For many years, some of Appium's authors maintained the [WD](https://github.com/admc/wd) client library. This library has been deprecated and has not been updated for use with the W3C WebDriver protocol. As such, if you're using this library you'll need to move to a more modern one. We recommend [WebdriverIO](https://webdriver.io).
|
||||
|
||||
### :warning: Appium Inspector split out from Appium Desktop
|
||||
### :warning: Appium Desktop replaced with Appium Inspector
|
||||
|
||||
The inspecting portion of Appium Desktop has been moved to its own app, Appium Inspector: [github.com/appium/appium-inspector](https://github.com/appium/appium-inspector). It's fully compatible with Appium 2.0 servers. Simply download it and run it on its own. You no longer need the GUI Appium Desktop server to inspect apps. The Appium Desktop server will continue to be supported at its original site: [github.com/appium/appium-desktop](https://github.com/appium/appium-desktop). It will simply no longer bundle the Inspector with it. Note that Appium Desktop 1.21 and lower versions depend on the deprecated [WD](https://github.com/admc/wd) client, and are not compatible with Appium 2.0. There is currently no Appium 2.0 support for Appium Desktop planned, now that the Inspector is a standalone app.
|
||||
The inspector functionality of Appium Desktop has been moved to its own app: [Appium Inspector](https://github.com/appium/appium-inspector). It's fully compatible with standalone Appium 2.0 servers, but also works with later versions of Appium 1.x servers. Appium Desktop itself has been deprecated and is not compatible with Appium 2.0.
|
||||
|
||||
You can also now use the Appium Inspector without downloading anything, by visiting the [web version of Appium Inspector](https://inspector.appiumpro.com). Note that to test against local servers, you'll need to start the server with `--allow-cors` so that the browser-based version of Appium Inspector can access your Appium server to start sessions.
|
||||
In addition to the app, Appium Inspector also has a browser version, accessible at https://inspector.appiumpro.com. Note that in order to use the browser version with a local Appium server, you'll need to first start the server with the `--allow-cors` flag.
|
||||
|
||||
## Major New Features
|
||||
|
||||
|
||||
@@ -41,6 +41,18 @@ would construct a set of capabilities that includes the following in its JSON re
|
||||
}
|
||||
```
|
||||
|
||||
Also, since base-driver version 9.4.0, there is a possibility to provide multiple settings
|
||||
in a single `appium:settings` capability value:
|
||||
|
||||
```json
|
||||
{
|
||||
"appium:settings": {
|
||||
"ignoreUnimportantViews": true,
|
||||
"allowInvisibleElements": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Of course, initializing a setting via capabilities doesn't prevent you from changing it later on
|
||||
via the Settings API. To learn more about how to use the Settings API in the context of your
|
||||
specific client library, visit the documentation for that library.
|
||||
|
||||
@@ -5,14 +5,9 @@ title: Install Appium
|
||||
Installing Appium is as easy as running a single NPM command:
|
||||
|
||||
```bash
|
||||
npm i -g appium@next
|
||||
npm i --location=global appium
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Currently, you must use `appium@next` instead of just `appium`. Once Appium 2.0 has been
|
||||
officially published, you can simply use `appium`.
|
||||
|
||||
This command installs Appium globally on your system so that you can access it from the command
|
||||
line simply by running the `appium` command. Go ahead and run it now:
|
||||
|
||||
|
||||
@@ -78,10 +78,10 @@ You can also set server arguments as [Config file](./config.md) properties.
|
||||
### :bangbang: ドライバーのインストール
|
||||
|
||||
<!---
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium@next`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install --location=global appium`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
--->
|
||||
|
||||
Appium 1.xをインストールしたとき、全ての入手可能なドライバーは主となるAppiumサーバーと合わせてインストールされていました。Appium 2.0ではそうではありません。Appium 2.0のインストール(例えば `npm install -g appium@next`)は単にAppiumサーバーのみをインストールし、ドライバーはインストールしません。ドライバーをインストールするためには新しい[Appium拡張コマンドラインインタフェース(Appium extension CLI)](../cli/extensions.md)を使わなければいけません。例えば、最新のXCUITestとUiAutomator2ドライバーをインストールする場合、Appiumをインストールしたのちに次のコマンドを実行する必要があります。
|
||||
Appium 1.xをインストールしたとき、全ての入手可能なドライバーは主となるAppiumサーバーと合わせてインストールされていました。Appium 2.0ではそうではありません。Appium 2.0のインストール(例えば `npm install --location=global appium`)は単にAppiumサーバーのみをインストールし、ドライバーはインストールしません。ドライバーをインストールするためには新しい[Appium拡張コマンドラインインタフェース(Appium extension CLI)](../cli/extensions.md)を使わなければいけません。例えば、最新のXCUITestとUiAutomator2ドライバーをインストールする場合、Appiumをインストールしたのちに次のコマンドを実行する必要があります。
|
||||
|
||||
```bash
|
||||
appium driver install uiautomator2 # installs the latest driver version
|
||||
@@ -95,14 +95,14 @@ At this point, your drivers are installed and ready. There's a lot more you can
|
||||
これにより、ドライバーはインストールされ、利用可能になります。このCLIはいろいろな機能を提供しているので、CLIのドキュメントを確認してみてください。もしCI上であったり、いくつかのドライバーをAppiumと一緒にインストールしたい場合、以下のようなフラグを利用することで実現可能です。
|
||||
|
||||
```bash
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
npm install --location=global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
<!---
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall -g appium`) if you get an installation or startup error.
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall --location=global appium`) if you get an installation or startup error.
|
||||
--->
|
||||
|
||||
これはAppiumと2つのドライバーを一つのコマンドでインストールします。もしセットアップで何らかの問題が発生した場合、既存のAppium 1.xを`npm uninstall -g appium`で削除してみてください。
|
||||
これはAppiumと2つのドライバーを一つのコマンドでインストールします。もしセットアップで何らかの問題が発生した場合、既存のAppium 1.xを`npm uninstall --location=global appium`で削除してみてください。
|
||||
|
||||
### :bangbang: Drivers installation path
|
||||
|
||||
@@ -166,7 +166,7 @@ appium driver update xcuitest
|
||||
(For a complete description of the update command, check out the [Extension
|
||||
CLI](../cli/extensions.md) doc)
|
||||
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install -g appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install --location=global appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
|
||||
If you would like to update to a specific version, not the latest, please uninstall the driver and install the desired version using the `install` subcommand instead of `update`.
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ nav:
|
||||
- CLI Reference:
|
||||
- cli/index.md
|
||||
- cli/args.md
|
||||
- cli/env-vars.md
|
||||
- cli/extensions.md
|
||||
- Guides:
|
||||
- guides/migrating-1-to-2.md
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
promoteAppiumOptionsForObject,
|
||||
} from '@appium/base-driver';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {parseCapsForInnerDriver, pullSettings} from './utils';
|
||||
import {parseCapsForInnerDriver, pullSettings, makeNonW3cCapsError} from './utils';
|
||||
import {util, node, logger} from '@appium/support';
|
||||
import {getDefaultsForExtension} from './schema';
|
||||
import {DRIVER_TYPE} from './constants';
|
||||
@@ -114,6 +114,7 @@ class AppiumDriver extends DriverCore {
|
||||
this.sessionPlugins = {};
|
||||
this.sessionlessPlugins = [];
|
||||
this.desiredCapConstraints = desiredCapabilityConstraints;
|
||||
this._isShuttingDown = false;
|
||||
|
||||
// allow this to happen in the background, so no `await`
|
||||
(async () => {
|
||||
@@ -155,15 +156,36 @@ class AppiumDriver extends DriverCore {
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async getStatus() {
|
||||
// https://www.w3.org/TR/webdriver/#dfn-status
|
||||
const statusObj = this._isShuttingDown
|
||||
? {
|
||||
ready: false,
|
||||
message: 'The server is shutting down',
|
||||
} : {
|
||||
ready: true,
|
||||
message: 'The server is ready to accept new connections',
|
||||
};
|
||||
return {
|
||||
...statusObj,
|
||||
build: _.clone(getBuildInfo()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|null} reason An optional shutdown reason
|
||||
*/
|
||||
async shutdown(reason = null) {
|
||||
this._isShuttingDown = true;
|
||||
await this.deleteAllSessions({
|
||||
force: true,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
async getSessions() {
|
||||
return _.toPairs(this.sessions).map(([id, driver]) => ({
|
||||
id,
|
||||
capabilities: /** @type {import('@appium/types').DriverCaps} */ (driver.caps),
|
||||
capabilities: /** @type {import('@appium/types').DriverCaps<any>} */ (driver.caps),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -231,6 +253,12 @@ class AppiumDriver extends DriverCore {
|
||||
jsonwpCaps = _.cloneDeep(jsonwpCaps);
|
||||
const jwpSettings = {...defaultSettings, ...pullSettings(jsonwpCaps)};
|
||||
w3cCapabilities = _.cloneDeep(w3cCapabilities);
|
||||
if (
|
||||
!_.isPlainObject(w3cCapabilities) ||
|
||||
!(_.isArray(w3cCapabilities?.firstMatch) || _.isPlainObject(w3cCapabilities?.alwaysMatch))
|
||||
) {
|
||||
throw makeNonW3cCapsError();
|
||||
}
|
||||
// It is possible that the client only provides caps using JSONWP standard,
|
||||
// although firstMatch/alwaysMatch properties are still present.
|
||||
// In such case we assume the client understands W3C protocol and merge the given
|
||||
@@ -634,6 +662,16 @@ class AppiumDriver extends DriverCore {
|
||||
// get any plugins which are registered as handling this command
|
||||
const plugins = this.pluginsToHandleCmd(cmd, sessionId);
|
||||
|
||||
// if any plugins are going to handle this command, we can't guarantee that the default
|
||||
// driver's executeCommand method will be called, which means we can't guarantee that the
|
||||
// newCommandTimeout will be cleared. So we do it here as well.
|
||||
if (plugins.length && dstSession) {
|
||||
this.log.debug(
|
||||
'Clearing new command timeout pre-emptively since plugin(s) will handle this command'
|
||||
);
|
||||
await dstSession.clearNewCommandTimeout();
|
||||
}
|
||||
|
||||
// now we define a 'cmdHandledBy' object which will keep track of which plugins have handled this
|
||||
// command. we care about this because (a) multiple plugins can handle the same command, and
|
||||
// (b) there's no guarantee that a plugin will actually call the next() method which runs the
|
||||
@@ -699,6 +737,22 @@ class AppiumDriver extends DriverCore {
|
||||
// handling the command and which didn't
|
||||
this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
|
||||
|
||||
// if we had plugins, and if they did not ultimately call the default handler, this means our
|
||||
// new command timeout was not restarted by the default handler's executeCommand call, so
|
||||
// restart it here using the same logic as in BaseDriver's executeCommand
|
||||
if (
|
||||
dstSession &&
|
||||
!cmdHandledBy.default &&
|
||||
dstSession.isCommandsQueueEnabled &&
|
||||
cmd !== DELETE_SESSION_COMMAND
|
||||
) {
|
||||
this.log.debug(
|
||||
'Restarting new command timeout via umbrella driver since plugin did not ' +
|
||||
'allow default handler to execute'
|
||||
);
|
||||
await dstSession.startNewCommandTimeout();
|
||||
}
|
||||
|
||||
// And finally, if the command was createSession, we want to migrate any plugins which were
|
||||
// previously sessionless to use the new sessionId, so that plugins can share state between
|
||||
// their createSession method and other instance methods
|
||||
|
||||
@@ -9,7 +9,11 @@ import {getSchema, validate} from './schema/schema';
|
||||
* @type {import('lilconfig').LoaderSync}
|
||||
*/
|
||||
function yamlLoader(filepath, content) {
|
||||
return yaml.parse(content);
|
||||
try {
|
||||
return yaml.parse(content);
|
||||
} catch (e) {
|
||||
throw new Error(`The YAML config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,7 +31,11 @@ const rawConfig = new Map();
|
||||
*/
|
||||
function jsonLoader(filepath, content) {
|
||||
rawConfig.set(filepath, content);
|
||||
return JSON.parse(content);
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
throw new Error(`The JSON config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import B from 'bluebird';
|
||||
import glob from 'glob';
|
||||
import {env, fs} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
@@ -212,23 +211,13 @@ export class Manifest {
|
||||
];
|
||||
|
||||
// add dependencies to the queue
|
||||
await new B((resolve, reject) => {
|
||||
glob(
|
||||
'node_modules/{*,@*/*}/package.json',
|
||||
{cwd: this.#appiumHome, silent: true, absolute: true},
|
||||
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
.on('error', reject)
|
||||
.on('match', (filepath) => {
|
||||
queue.push(onMatch(filepath));
|
||||
});
|
||||
const filepaths = await fs.glob('node_modules/{*,@*/*}/package.json', {
|
||||
cwd: this.#appiumHome,
|
||||
absolute: true,
|
||||
});
|
||||
for (const filepath of filepaths) {
|
||||
queue.push(onMatch(filepath));
|
||||
}
|
||||
|
||||
// wait for everything to finish
|
||||
await B.all(queue);
|
||||
@@ -284,7 +273,11 @@ export class Manifest {
|
||||
...internal,
|
||||
};
|
||||
if (!_.isEqual(value, this.#data.drivers[pkgJson.appium.driverName])) {
|
||||
this.setExtension(DRIVER_TYPE, pkgJson.appium.driverName, value);
|
||||
this.setExtension(
|
||||
/** @type {ExtType} */ (DRIVER_TYPE),
|
||||
pkgJson.appium.driverName,
|
||||
/** @type {ExtManifest<ExtType>} */ (value)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -294,7 +287,11 @@ export class Manifest {
|
||||
...internal,
|
||||
};
|
||||
if (!_.isEqual(value, this.#data.plugins[pkgJson.appium.pluginName])) {
|
||||
this.setExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, value);
|
||||
this.setExtension(
|
||||
/** @type {ExtType} */ (PLUGIN_TYPE),
|
||||
pkgJson.appium.pluginName,
|
||||
/** @type {ExtManifest<ExtType>} */ (value)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+44
-12
@@ -2,7 +2,11 @@
|
||||
|
||||
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
|
||||
import {routeConfiguringFunction as makeRouter, server as baseServer} from '@appium/base-driver';
|
||||
import {
|
||||
routeConfiguringFunction as makeRouter,
|
||||
server as baseServer,
|
||||
normalizeBasePath,
|
||||
} from '@appium/base-driver';
|
||||
import {logger as logFactory, util, env, fs} from '@appium/support';
|
||||
import {asyncify} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
@@ -32,8 +36,12 @@ import {
|
||||
isExtensionCommandArgs,
|
||||
isPluginCommandArgs,
|
||||
isServerCommandArgs,
|
||||
fetchInterfaces,
|
||||
V4_BROADCAST_IP,
|
||||
V6_BROADCAST_IP,
|
||||
} from './utils';
|
||||
import os from 'node:os';
|
||||
import net from 'node:net';
|
||||
|
||||
const {resolveAppiumHome} = env;
|
||||
|
||||
@@ -164,8 +172,8 @@ async function prepareAppiumHome(name, appiumHome) {
|
||||
}
|
||||
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}`
|
||||
`to a valid folder writeable for the current user account '${os.userInfo().username}'. ` +
|
||||
`Original error: ${err.message}`
|
||||
);
|
||||
}
|
||||
if (!stat.isDirectory()) {
|
||||
@@ -178,8 +186,8 @@ async function prepareAppiumHome(name, appiumHome) {
|
||||
} 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}`
|
||||
`writeable for the current user account '${os.userInfo().username}. ` +
|
||||
`Original error: ${e.message}`
|
||||
);
|
||||
}
|
||||
return appiumHome;
|
||||
@@ -317,6 +325,34 @@ 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) {
|
||||
const urlObj = new URL(url);
|
||||
logger.info(`Appium REST http interface listener started on ${url}`);
|
||||
if (![V4_BROADCAST_IP, V6_BROADCAST_IP, `[${V6_BROADCAST_IP}]`].includes(urlObj.hostname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interfaces = fetchInterfaces(urlObj.hostname === V4_BROADCAST_IP ? 4 : 6);
|
||||
const toLabel = (/** @type {os.NetworkInterfaceInfo} */ iface) => {
|
||||
const href = urlObj.href.replace(urlObj.hostname, iface.address);
|
||||
return iface.internal
|
||||
? `${href} (only accessible from the same host)`
|
||||
: href;
|
||||
};
|
||||
logger.info(
|
||||
`You can provide the following ${interfaces.length === 1 ? 'URL' : 'URLs'} ` +
|
||||
`in your client code to connect to this server:\n` +
|
||||
interfaces.map((iface) => `\t${toLabel(iface)}`).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Appium's config. Starts server if appropriate and resolves the
|
||||
* server instance if so; otherwise resolves w/ `undefined`.
|
||||
@@ -404,10 +440,7 @@ async function main(args) {
|
||||
process.once(signal, async function onSignal() {
|
||||
logger.info(`Received ${signal} - shutting down`);
|
||||
try {
|
||||
await appiumDriver.deleteAllSessions({
|
||||
force: true,
|
||||
reason: `The process has received ${signal} signal`,
|
||||
});
|
||||
await appiumDriver.shutdown(`The process has received ${signal} signal`);
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
@@ -417,9 +450,8 @@ async function main(args) {
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Appium REST http interface listener started on ${parsedArgs.address}:${parsedArgs.port}${parsedArgs.basePath}`
|
||||
);
|
||||
const address = net.isIPv6(parsedArgs.address) ? `[${parsedArgs.address}]` : parsedArgs.address;
|
||||
logServerAddress(`http://${address}:${parsedArgs.port}${normalizeBasePath(parsedArgs.basePath)}`);
|
||||
|
||||
driverConfig.print();
|
||||
pluginConfig.print([...pluginClasses.values()]);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import {processCapabilities, PROTOCOLS, STANDARD_CAPS} from '@appium/base-driver';
|
||||
import {processCapabilities, PROTOCOLS, STANDARD_CAPS, errors} from '@appium/base-driver';
|
||||
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 = '::';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -18,6 +22,18 @@ const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLo
|
||||
*/
|
||||
const isStdoutTTY = process.stdout.isTTY;
|
||||
|
||||
/**
|
||||
* Creates an error object in case a session gets incompatible capabilities as the input.
|
||||
*
|
||||
* @returns {Error}
|
||||
*/
|
||||
export function makeNonW3cCapsError() {
|
||||
return new errors.SessionNotCreatedError(
|
||||
'Session capabilities format must comply to the W3C standard. Make sure your client is up to date. ' +
|
||||
'See https://www.w3.org/TR/webdriver/#new-session for more details.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps to value to the console using `info` logger.
|
||||
*
|
||||
@@ -65,7 +81,7 @@ function parseCapsForInnerDriver(
|
||||
if (!hasW3CCaps) {
|
||||
return /** @type {InvalidCaps<C>} */ ({
|
||||
protocol: PROTOCOLS.W3C,
|
||||
error: new Error('W3C capabilities should be provided'),
|
||||
error: makeNonW3cCapsError(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -260,11 +276,18 @@ function adjustNodePath() {
|
||||
/**
|
||||
* Pulls the initial values of Appium settings from the given capabilities argument.
|
||||
* Each setting item must satisfy the following format:
|
||||
* `setting[setting_name]: setting_value`
|
||||
* `settings[setting_name]: setting_value`
|
||||
* or
|
||||
* ```
|
||||
* settings = {
|
||||
* setting_name1: 'setting_value1',
|
||||
* setting_name2: 'setting_value2',
|
||||
* }
|
||||
* ```
|
||||
* The capabilities argument itself gets mutated, so it does not contain parsed
|
||||
* settings anymore to avoid further parsing issues.
|
||||
* Check
|
||||
* https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md
|
||||
* https://appium.io/docs/en/latest/guides/settings/
|
||||
* for more details on the available settings.
|
||||
*
|
||||
* @param {?Object} caps - Capabilities dictionary. It is mutated if
|
||||
@@ -279,14 +302,19 @@ function pullSettings(caps) {
|
||||
}
|
||||
|
||||
const result = {};
|
||||
const singleSettings = {};
|
||||
for (const [key, value] of _.toPairs(caps)) {
|
||||
const match = /\bsettings\[(\S+)\]$/.exec(key);
|
||||
if (!match) {
|
||||
continue;
|
||||
let match;
|
||||
if (/^(s|appium:s)ettings$/.test(key) && _.isPlainObject(value)) {
|
||||
Object.assign(result, value);
|
||||
delete caps[key];
|
||||
} else if ((match = /^(s|appium:s)ettings\[(\S+)\]$/.exec(key))) {
|
||||
singleSettings[match[2]] = value;
|
||||
delete caps[key];
|
||||
}
|
||||
|
||||
result[match[1]] = value;
|
||||
delete caps[key];
|
||||
}
|
||||
if (!_.isEmpty(singleSettings)) {
|
||||
Object.assign(result, singleSettings);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -331,6 +359,27 @@ export function isPluginCommandArgs(args) {
|
||||
return args.subcommand === PLUGIN_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of matched network interfaces 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 {os.NetworkInterfaceInfo[]} The list of matched interfcaes
|
||||
*/
|
||||
export function fetchInterfaces (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'];
|
||||
}
|
||||
// @ts-ignore The linter does not understand the below filter
|
||||
return _.flatMap(_.values(os.networkInterfaces()).filter(Boolean))
|
||||
// @ts-ignore The linter does not understand the above filter
|
||||
.filter(({family}) => !familyValue || familyValue && familyValue.includes(family));
|
||||
}
|
||||
|
||||
export {
|
||||
inspect,
|
||||
parseCapsForInnerDriver,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.67",
|
||||
"version": "2.1.1",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -60,17 +60,17 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.8",
|
||||
"@appium/base-plugin": "^2.2.8",
|
||||
"@appium/docutils": "^0.3.10",
|
||||
"@appium/schema": "^0.2.6",
|
||||
"@appium/support": "^4.0.0",
|
||||
"@appium/types": "^0.11.1",
|
||||
"@appium/base-driver": "^9.3.18",
|
||||
"@appium/base-plugin": "^2.2.18",
|
||||
"@appium/docutils": "^0.4.7",
|
||||
"@appium/schema": "^0.3.1",
|
||||
"@appium/support": "^4.1.5",
|
||||
"@appium/types": "^0.13.3",
|
||||
"@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",
|
||||
@@ -82,30 +82,28 @@
|
||||
"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",
|
||||
"npmlog": "7.0.1",
|
||||
"ora": "5.4.1",
|
||||
"package-changed": "3.0.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.5.0",
|
||||
"semver": "7.5.4",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.10.0",
|
||||
"winston": "3.8.2",
|
||||
"teen_process": "2.0.4",
|
||||
"type-fest": "3.13.1",
|
||||
"winston": "3.10.0",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.2.2"
|
||||
"yaml": "2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"tag": "next"
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"gitHead": "3fa73a1d1a95e3868fbceb005f8b1deb4ac53eaf",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/main.js"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "7.31.1"
|
||||
"webdriverio": "^8.11.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const capabilities = {
|
||||
};
|
||||
|
||||
const wdOpts = {
|
||||
host: process.env.APPIUM_HOST || 'localhost',
|
||||
hostname: process.env.APPIUM_HOST || 'localhost',
|
||||
port: parseInt(process.env.APPIUM_PORT, 10) || 4723,
|
||||
logLevel: 'info',
|
||||
capabilities,
|
||||
|
||||
@@ -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(
|
||||
(
|
||||
|
||||
@@ -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,8 +315,12 @@ 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`)
|
||||
new RegExp(`${_.escapeRegExp(INVALID_JSON_FILEPATH)}`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -240,6 +240,28 @@ describe('FakeDriver via HTTP', function () {
|
||||
|
||||
await B.delay(250);
|
||||
await driver.getPageSource().should.eventually.be.rejectedWith(/terminated/);
|
||||
|
||||
await driver.getSessions().should.eventually.be.empty;
|
||||
});
|
||||
|
||||
it('should not allow umbrella commands to prevent newCommandTimeout on inner driver', async function () {
|
||||
let localCaps = Object.assign(
|
||||
{
|
||||
'appium:newCommandTimeout': 0.25,
|
||||
},
|
||||
caps
|
||||
);
|
||||
let driver = await wdio({...wdOpts, capabilities: localCaps});
|
||||
should.exist(driver.sessionId);
|
||||
|
||||
// get the session list 6 times over 300ms. each request will be below the new command
|
||||
// timeout but since they are not received by the driver the session should still time out
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await driver.getSessions();
|
||||
await B.delay(50);
|
||||
}
|
||||
await driver.getPageSource().should.eventually.be.rejectedWith(/terminated/);
|
||||
await driver.getSessions().should.eventually.be.empty;
|
||||
});
|
||||
|
||||
it('should accept valid W3C capabilities and start a W3C session', async function () {
|
||||
|
||||
@@ -275,6 +275,31 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
|
||||
}
|
||||
shutdownErr.message.should.match(/either terminated or not started/);
|
||||
});
|
||||
|
||||
it('should allow plugin handled commands to reset newCommandTimeout', async function () {
|
||||
/** @type {import('webdriverio').RemoteOptions} */
|
||||
const newOpts = {...wdOpts};
|
||||
newOpts.capabilities = {
|
||||
...(newOpts.capabilities ?? {}),
|
||||
'appium:newCommandTimeout': 2,
|
||||
};
|
||||
const driver = await wdio(newOpts);
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
const start = Date.now();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await B.delay(500);
|
||||
await driver.getPageSource();
|
||||
}
|
||||
// prove that we went beyond the new command timeout as a result of sending commands
|
||||
(Date.now() - start).should.be.above(2500);
|
||||
await driver
|
||||
.getPageSource()
|
||||
.should.eventually.eql(`<Fake>${JSON.stringify([sessionId])}</Fake>`);
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
describe('cli args handling for plugin args', function () {
|
||||
|
||||
+5
-3
@@ -11,13 +11,15 @@ 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',
|
||||
anyOf: [
|
||||
{format: 'hostname', type: 'string'},
|
||||
{format: 'ipv6', 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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ export function initMocks(sandbox = createSandbox()) {
|
||||
.returns({next: sandbox.stub().resolves({done: true})}),
|
||||
})
|
||||
),
|
||||
glob: /** @type {MockAppiumSupportFs['glob']} */ (sandbox.stub().resolves([])),
|
||||
mkdirp: /** @type {MockAppiumSupportFs['mkdirp']} */ (sandbox.stub().resolves()),
|
||||
readPackageJsonFrom: /** @type {MockAppiumSupportFs['readPackageJsonFrom']} */ (
|
||||
sandbox.stub().returns({version: APPIUM_VER, engines: {node: '>=12'}})
|
||||
@@ -166,6 +167,7 @@ export function initMocks(sandbox = createSandbox()) {
|
||||
* @property {SinonStubbedMember<SupportFs['readFile']>} readFile
|
||||
* @property {SinonStubbedMember<SupportFs['writeFile']>} writeFile
|
||||
* @property {SinonStubbedMember<SupportFs['walk']>} walk
|
||||
* @property {SinonStubbedMember<SupportFs['glob']>} glob
|
||||
* @property {SinonStubbedMember<SupportFs['mkdirp']>} mkdirp
|
||||
* @property {SinonStubbedMember<SupportFs['readPackageJsonFrom']>} readPackageJsonFrom
|
||||
* @property {SinonStubbedMember<SupportFs['findRoot']>} findRoot
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
removeAppiumPrefixes,
|
||||
inspect,
|
||||
adjustNodePath,
|
||||
fetchInterfaces,
|
||||
} from '../../lib/utils';
|
||||
import {BASE_CAPS, W3C_CAPS} from '../helpers';
|
||||
import _ from 'lodash';
|
||||
@@ -231,6 +232,26 @@ describe('utils', function () {
|
||||
settings.should.eql({});
|
||||
caps.should.eql({});
|
||||
});
|
||||
it('should pull combined settings', function () {
|
||||
const caps = {
|
||||
platformName: 'foo',
|
||||
browserName: 'bar',
|
||||
'appium:settings[foo]': 'baz2',
|
||||
'appium:settings': {
|
||||
foo: 'baz',
|
||||
yolo: 'bar',
|
||||
},
|
||||
};
|
||||
const settings = pullSettings(caps);
|
||||
settings.should.eql({
|
||||
foo: 'baz2',
|
||||
yolo: 'bar',
|
||||
});
|
||||
caps.should.eql({
|
||||
platformName: 'foo',
|
||||
browserName: 'bar',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspect()', function () {
|
||||
@@ -275,4 +296,19 @@ describe('utils', function () {
|
||||
(await fs.exists(process.env.NODE_PATH)).should.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchInterfaces()', function () {
|
||||
it('should fetch interfaces for ipv4 only', async function () {
|
||||
fetchInterfaces(4).length.should.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should fetch interfaces for ipv6 only', async function () {
|
||||
fetchInterfaces(6).length.should.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should fetch interfaces for ipv4 and ipv6', async function () {
|
||||
fetchInterfaces().length.should.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -3,6 +3,105 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.3.18](https://github.com/appium/appium/compare/@appium/base-driver@9.3.17...@appium/base-driver@9.3.18) (2023-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** Fix possible NPE while cleaning up the state listener ([#19020](https://github.com/appium/appium/issues/19020)) ([53bfc68](https://github.com/appium/appium/commit/53bfc68eee5766ca8c6851c424c89d6b123d602e))
|
||||
* **base-driver:** Respect basic auth credentials if provided ([#19000](https://github.com/appium/appium/issues/19000)) ([ed8e83c](https://github.com/appium/appium/commit/ed8e83c76c5b660e619833515c2ea2ec29082e18))
|
||||
* **driver-test-support:** update dependency @types/lodash to v4.14.197 ([a080b72](https://github.com/appium/appium/commit/a080b729208b4e3c352456f0f230b63b0b7ee9ee))
|
||||
|
||||
|
||||
|
||||
## [9.3.17](https://github.com/appium/appium/compare/@appium/base-driver@9.3.16...@appium/base-driver@9.3.17) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** Tune responses caching logic ([#18922](https://github.com/appium/appium/issues/18922)) ([de5b55a](https://github.com/appium/appium/commit/de5b55ae90fefcce86962a48d5aceb73962e56b6))
|
||||
* **driver-test-support:** update definitelytyped ([c320e8a](https://github.com/appium/appium/commit/c320e8a1a61cb6d980d6d944ae47b60da97aa398))
|
||||
* **support:** update dependency glob to v10 ([#18490](https://github.com/appium/appium/issues/18490)) ([aaf31a5](https://github.com/appium/appium/commit/aaf31a577cb0b9cbe22646dcd888dc393a03aa11))
|
||||
* **test-support:** update dependency @colors/colors to v1.6.0 ([1358937](https://github.com/appium/appium/commit/1358937db2edf08ce1ebe3dff2f70ac6b07cd373))
|
||||
* **types:** update dependency type-fest to v3.13.1 ([fb34ab9](https://github.com/appium/appium/commit/fb34ab917216121d2b554677a12f07a03393d218))
|
||||
|
||||
|
||||
|
||||
## [9.3.16](https://github.com/appium/appium/compare/@appium/base-driver@9.3.15...@appium/base-driver@9.3.16) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** Use proper cached headers for app download ([#18874](https://github.com/appium/appium/issues/18874)) ([75650a6](https://github.com/appium/appium/commit/75650a6b744dfcbd50d45972f04040472fdbcd45))
|
||||
|
||||
|
||||
|
||||
## [9.3.15](https://github.com/appium/appium/compare/@appium/base-driver@9.3.14...@appium/base-driver@9.3.15) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** allow subclass to define shape of settings object ([3d614d6](https://github.com/appium/appium/commit/3d614d6d414d0d34026f516dbfad7d296000efcf))
|
||||
* **base-driver:** pass thru all type args to ExternalDriver ([2b35170](https://github.com/appium/appium/commit/2b351705d401e2db8da76022989c1475cbdda0f8))
|
||||
* **types,base-driver:** remove deviceName from base constraints ([01061b2](https://github.com/appium/appium/commit/01061b291981333bdec59adfbea60f0cef3d69c1))
|
||||
* **types:** separate the type of opts from initialOpts ([d6cca51](https://github.com/appium/appium/commit/d6cca5175c3e55d4670936c9d216cd3a6610d16b))
|
||||
|
||||
|
||||
|
||||
## [9.3.14](https://github.com/appium/appium/compare/@appium/base-driver@9.3.13...@appium/base-driver@9.3.14) (2023-06-29)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.13](https://github.com/appium/appium/compare/@appium/base-driver@9.3.12...@appium/base-driver@9.3.13) (2023-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** update def of findElOrEls and findElOrElsWithProcessing ([54a4a8d](https://github.com/appium/appium/commit/54a4a8da030dbecde783168bb92634df837d5a41))
|
||||
* **types:** various fixes for reality ([81bc527](https://github.com/appium/appium/commit/81bc527be6aa54dd30a012156b5752b8b821ac0c))
|
||||
|
||||
|
||||
|
||||
## [9.3.12](https://github.com/appium/appium/compare/@appium/base-driver@9.3.11...@appium/base-driver@9.3.12) (2023-06-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Constraints, IFindCommands} from '@appium/types';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {Constraints, Element, IFindCommands} from '@appium/types';
|
||||
import {errors} from '../../protocol';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
@@ -8,6 +9,67 @@ declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends IFindCommands {}
|
||||
}
|
||||
|
||||
async function findElOrEls<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: true,
|
||||
context?: any
|
||||
): Promise<Element[]>;
|
||||
async function findElOrEls<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: false,
|
||||
context?: any
|
||||
): Promise<Element>;
|
||||
async function findElOrEls<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: boolean,
|
||||
context?: any
|
||||
): Promise<Element[] | Element> {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
}
|
||||
|
||||
async function findElOrElsWithProcessing<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: true,
|
||||
context?: any
|
||||
): Promise<Element[]>;
|
||||
async function findElOrElsWithProcessing<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: false,
|
||||
context?: any
|
||||
): Promise<Element>;
|
||||
async function findElOrElsWithProcessing<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: boolean,
|
||||
context?: any
|
||||
): Promise<Element[] | Element> {
|
||||
this.validateLocatorStrategy(strategy);
|
||||
try {
|
||||
// @ts-expect-error TS does not understand how to deal with the overload here
|
||||
return await this.findElOrEls(strategy, selector, mult, context);
|
||||
} catch (err) {
|
||||
if (this.opts.printPageSourceOnFindFailure) {
|
||||
const src = await this.getPageSource();
|
||||
this.log.debug(`Error finding element${mult ? 's' : ''}: ${err.message}`);
|
||||
this.log.debug(`Page source requested through 'printPageSourceOnFindFailure':`);
|
||||
this.log.debug(src);
|
||||
}
|
||||
// still want the error to occur
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const FindCommands: IFindCommands = {
|
||||
async findElement<C extends Constraints>(this: BaseDriver<C>, strategy, selector) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false);
|
||||
@@ -35,46 +97,13 @@ const FindCommands: IFindCommands = {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true, elementId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object which adheres to the way the JSON Wire Protocol represents elements:
|
||||
*
|
||||
* Override this for your own driver!
|
||||
*/
|
||||
async findElOrEls<C extends Constraints, Mult extends boolean, Ctx = any>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: Mult,
|
||||
context: Ctx
|
||||
) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
},
|
||||
findElOrEls,
|
||||
|
||||
async getPageSource<C extends Constraints>(this: BaseDriver<C>) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
},
|
||||
|
||||
async findElOrElsWithProcessing<C extends Constraints, Mult extends boolean, Ctx = any>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: Mult,
|
||||
context?: Ctx
|
||||
) {
|
||||
this.validateLocatorStrategy(strategy);
|
||||
try {
|
||||
return await this.findElOrEls(strategy, selector, mult, context);
|
||||
} catch (err) {
|
||||
if (this.opts.printPageSourceOnFindFailure) {
|
||||
const src = await this.getPageSource();
|
||||
this.log.debug(`Error finding element${mult ? 's' : ''}: ${err.message}`);
|
||||
this.log.debug(`Page source requested through 'printPageSourceOnFindFailure':`);
|
||||
this.log.debug(src);
|
||||
}
|
||||
// still want the error to occur
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
findElOrElsWithProcessing,
|
||||
};
|
||||
|
||||
mixin(FindCommands);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './event';
|
||||
import './find';
|
||||
import './log';
|
||||
import './settings';
|
||||
import './timeout';
|
||||
import './execute';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Constraints, Driver, ILogCommands, LogDefRecord} from '@appium/types';
|
||||
import type {Constraints, Driver, ILogCommands} from '@appium/types';
|
||||
import _ from 'lodash';
|
||||
import {BaseDriver} from '../driver';
|
||||
import type {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
@@ -9,7 +9,7 @@ declare module '../driver' {
|
||||
}
|
||||
|
||||
const LogCommands: ILogCommands = {
|
||||
supportedLogTypes: <LogDefRecord>{},
|
||||
supportedLogTypes: {},
|
||||
|
||||
async getLogTypes<C extends Constraints>(this: BaseDriver<C>) {
|
||||
this.log.debug('Retrieving supported log types');
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import {BaseDriver} from '../driver';
|
||||
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 {}
|
||||
}
|
||||
|
||||
const SettingsCommands: ISettingsCommands = {
|
||||
async updateSettings<C extends Constraints>(this: BaseDriver<C>, newSettings: StringRecord) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
||||
}
|
||||
return await this.settings.update(newSettings);
|
||||
},
|
||||
|
||||
async getSettings<C extends Constraints>(this: BaseDriver<C>) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
||||
}
|
||||
return this.settings.getSettings();
|
||||
},
|
||||
};
|
||||
|
||||
mixin(SettingsCommands);
|
||||
+75
-151
@@ -2,10 +2,23 @@
|
||||
/* eslint-disable require-await */
|
||||
|
||||
import {logger} from '@appium/support';
|
||||
import type {
|
||||
AppiumLogger,
|
||||
Constraints,
|
||||
Core,
|
||||
Driver,
|
||||
DriverOpts,
|
||||
EventHistory,
|
||||
HTTPMethod,
|
||||
InitialOpts,
|
||||
Protocol,
|
||||
RouteMatcher,
|
||||
StringRecord,
|
||||
} from '@appium/types';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {EventEmitter} from 'events';
|
||||
import _ from 'lodash';
|
||||
import os from 'os';
|
||||
import {EventEmitter} from 'node:events';
|
||||
import os from 'node:os';
|
||||
import {DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
||||
import {errors} from '../protocol';
|
||||
import DeviceSettings from './device-settings';
|
||||
@@ -15,35 +28,22 @@ const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [Settings=StringRecord]
|
||||
* @implements {Core<C, Settings>}
|
||||
*/
|
||||
class DriverCore {
|
||||
export class DriverCore<const C extends Constraints, Settings extends StringRecord = StringRecord>
|
||||
implements Core<C, Settings>
|
||||
{
|
||||
/**
|
||||
* Make the basedriver version available so for any driver which inherits from this package, we
|
||||
* know which version of basedriver it inherited from
|
||||
*/
|
||||
static baseVersion = BASEDRIVER_VER;
|
||||
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
sessionId;
|
||||
sessionId: string | null;
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
*/
|
||||
opts;
|
||||
opts: DriverOpts<C>;
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
*/
|
||||
initialOpts;
|
||||
initialOpts: InitialOpts;
|
||||
|
||||
/** @type {typeof helpers} */
|
||||
helpers;
|
||||
helpers: typeof helpers;
|
||||
|
||||
/**
|
||||
* basePath is used for several purposes, for example in setting up
|
||||
@@ -52,83 +52,58 @@ class DriverCore {
|
||||
* initially but it is automatically updated during any actual program
|
||||
* execution by the routeConfiguringFunction, which is necessarily run as
|
||||
* the entrypoint for any Appium server
|
||||
* @type {string}
|
||||
*/
|
||||
basePath;
|
||||
basePath: string;
|
||||
|
||||
/** @type {boolean} */
|
||||
relaxedSecurityEnabled;
|
||||
relaxedSecurityEnabled: boolean;
|
||||
|
||||
/** @type {string[]} */
|
||||
allowInsecure;
|
||||
allowInsecure: string[];
|
||||
|
||||
/** @type {string[]} */
|
||||
denyInsecure;
|
||||
denyInsecure: string[];
|
||||
|
||||
/** @type {number} */
|
||||
newCommandTimeoutMs;
|
||||
newCommandTimeoutMs: number;
|
||||
|
||||
/** @type {number} */
|
||||
implicitWaitMs;
|
||||
implicitWaitMs: number;
|
||||
|
||||
/** @type {string[]} */
|
||||
locatorStrategies;
|
||||
locatorStrategies: string[];
|
||||
|
||||
/** @type {string[]} */
|
||||
webLocatorStrategies;
|
||||
webLocatorStrategies: string[];
|
||||
|
||||
/** @type {Driver[]} */
|
||||
managedDrivers;
|
||||
managedDrivers: Driver[];
|
||||
|
||||
/** @type {NodeJS.Timeout?} */
|
||||
noCommandTimer;
|
||||
noCommandTimer: NodeJS.Timeout | null;
|
||||
|
||||
/** @type {EventHistory} */
|
||||
_eventHistory;
|
||||
protected _eventHistory: EventHistory;
|
||||
|
||||
// used to handle driver events
|
||||
/** @type {NodeJS.EventEmitter} */
|
||||
eventEmitter;
|
||||
eventEmitter: NodeJS.EventEmitter;
|
||||
|
||||
/**
|
||||
* @type {AppiumLogger}
|
||||
* @privateRemarks XXX: unsure why this is wrapped in a getter when nothing else is
|
||||
*/
|
||||
_log;
|
||||
protected _log: AppiumLogger;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
shutdownUnexpectedly;
|
||||
shutdownUnexpectedly: boolean;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
shouldValidateCaps;
|
||||
shouldValidateCaps: boolean;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {AsyncLock}
|
||||
*/
|
||||
commandsQueueGuard;
|
||||
protected commandsQueueGuard: AsyncLock;
|
||||
|
||||
/**
|
||||
* settings should be instantiated by drivers which extend BaseDriver, but
|
||||
* 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<Settings>}
|
||||
*/
|
||||
settings;
|
||||
settings: DeviceSettings<Settings>;
|
||||
|
||||
/**
|
||||
* @param {DriverOpts<C>} opts
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
*/
|
||||
constructor(opts = /** @type {DriverOpts<C>} */ ({}), shouldValidateCaps = true) {
|
||||
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this));
|
||||
protocol?: Protocol;
|
||||
|
||||
constructor(opts: InitialOpts = <InitialOpts>{}, shouldValidateCaps = true) {
|
||||
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this as Core<C>));
|
||||
|
||||
// setup state
|
||||
this.opts = opts;
|
||||
this.opts = opts as DriverOpts<C>;
|
||||
|
||||
// use a custom tmp dir to avoid losing data and app when computer is
|
||||
// restarted
|
||||
@@ -168,11 +143,11 @@ class DriverCore {
|
||||
* when the driver is shut down unexpectedly. Multiple calls to this method
|
||||
* will cause the handler to be executed mutiple times
|
||||
*
|
||||
* @param {(...args: any[]) => void} handler The code to be executed on unexpected shutdown.
|
||||
* @param handler The code to be executed on unexpected shutdown.
|
||||
* The function may accept one argument, which is the actual error instance, which
|
||||
* caused the driver to shut down.
|
||||
*/
|
||||
onUnexpectedShutdown(handler) {
|
||||
onUnexpectedShutdown(handler: (...args: any[]) => void) {
|
||||
this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler);
|
||||
}
|
||||
|
||||
@@ -183,7 +158,7 @@ class DriverCore {
|
||||
* Override it in inherited driver classes if necessary.
|
||||
*/
|
||||
get driverData() {
|
||||
return /** @type {import('@appium/types').DriverData} */ ({});
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,13 +166,13 @@ class DriverCore {
|
||||
* handles new driver commands received from the client.
|
||||
* Override it for inherited classes only in special cases.
|
||||
*
|
||||
* @return {boolean} If the returned value is true (default) then all the commands
|
||||
* @return If the returned value is true (default) then all the commands
|
||||
* received by the particular driver instance are going to be put into the queue,
|
||||
* so each following command will not be executed until the previous command
|
||||
* execution is completed. False value disables that queue, so each driver command
|
||||
* is executed independently and does not wait for anything.
|
||||
*/
|
||||
get isCommandsQueueEnabled() {
|
||||
get isCommandsQueueEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -211,9 +186,8 @@ class DriverCore {
|
||||
|
||||
/**
|
||||
* API method for driver developers to log timings for important events
|
||||
* @param {string} eventName
|
||||
*/
|
||||
logEvent(eventName) {
|
||||
logEvent(eventName: string) {
|
||||
if (eventName === 'commands') {
|
||||
throw new Error('Cannot log commands directly');
|
||||
}
|
||||
@@ -240,10 +214,8 @@ class DriverCore {
|
||||
/**
|
||||
* method required by MJSONWP in order to determine whether it should
|
||||
* respond with an invalid session response
|
||||
* @param {string} [sessionId]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
sessionExists(sessionId) {
|
||||
sessionExists(sessionId: string): boolean {
|
||||
if (!sessionId) return false; // eslint-disable-line curly
|
||||
return sessionId === this.sessionId;
|
||||
}
|
||||
@@ -251,11 +223,9 @@ class DriverCore {
|
||||
/**
|
||||
* method required by MJSONWP in order to determine if the command should
|
||||
* be proxied directly to the driver
|
||||
* @param {string} sessionId
|
||||
* @returns {Core<C> | null}
|
||||
*/
|
||||
driverForSession(sessionId) {
|
||||
return /** @type {Core<C> | null} */ (this);
|
||||
driverForSession(sessionId: string): Core<Constraints> | null {
|
||||
return this as Core<Constraints>;
|
||||
}
|
||||
|
||||
isMjsonwpProtocol() {
|
||||
@@ -277,11 +247,9 @@ class DriverCore {
|
||||
/**
|
||||
* Check whether a given feature is enabled via its name
|
||||
*
|
||||
* @param {string} name - name of feature/command
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @param name - name of feature/command
|
||||
*/
|
||||
isFeatureEnabled(name) {
|
||||
isFeatureEnabled(name: string): boolean {
|
||||
// if we have explicitly denied this feature, return false immediately
|
||||
if (this.denyInsecure && _.includes(this.denyInsecure, name)) {
|
||||
return false;
|
||||
@@ -306,10 +274,10 @@ class DriverCore {
|
||||
* Assert that a given feature is enabled and throw a helpful error if it's
|
||||
* not
|
||||
*
|
||||
* @param {string} name - name of feature/command
|
||||
* @param name - name of feature/command
|
||||
* @deprecated
|
||||
*/
|
||||
ensureFeatureEnabled(name) {
|
||||
ensureFeatureEnabled(name: string) {
|
||||
this.assertFeatureEnabled(name);
|
||||
}
|
||||
|
||||
@@ -317,26 +285,20 @@ class DriverCore {
|
||||
* Assert that a given feature is enabled and throw a helpful error if it's
|
||||
* not
|
||||
*
|
||||
* @param {string} name - name of feature/command
|
||||
* @param name - name of feature/command
|
||||
*/
|
||||
assertFeatureEnabled(name) {
|
||||
assertFeatureEnabled(name: string) {
|
||||
if (!this.isFeatureEnabled(name)) {
|
||||
throw new Error(
|
||||
`Potentially insecure feature '${name}' has not been ` +
|
||||
`enabled. If you want to enable this feature and accept ` +
|
||||
`the security ramifications, please do so by following ` +
|
||||
`the documented instructions at https://github.com/appium` +
|
||||
`/appium/blob/master/docs/en/writing-running-appium/security.md`
|
||||
`the documented instructions at http://appium.io/docs/en/2.0/guides/security/`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} strategy
|
||||
* @param {boolean} [webContext]
|
||||
*/
|
||||
validateLocatorStrategy(strategy, webContext = false) {
|
||||
validateLocatorStrategy(strategy: string, webContext = false) {
|
||||
let validStrategies = this.locatorStrategies;
|
||||
this.log.debug(`Valid locator strategies for this request: ${validStrategies.join(', ')}`);
|
||||
|
||||
@@ -351,30 +313,15 @@ class DriverCore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} [sessionId]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
proxyActive(sessionId) {
|
||||
proxyActive(sessionId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {import('@appium/types').RouteMatcher[]}
|
||||
*/
|
||||
getProxyAvoidList(sessionId) {
|
||||
getProxyAvoidList(sessionId: string): RouteMatcher[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} [sessionId]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canProxy(sessionId) {
|
||||
canProxy(sessionId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -382,28 +329,28 @@ class DriverCore {
|
||||
* Whether a given command route (expressed as method and url) should not be
|
||||
* proxied according to this driver
|
||||
*
|
||||
* @param {string} sessionId - the current sessionId (in case the driver runs
|
||||
* @param sessionId - the current sessionId (in case the driver runs
|
||||
* multiple session ids and requires it). This is not used in this method but
|
||||
* should be made available to overridden methods.
|
||||
* @param {import('@appium/types').HTTPMethod} method - HTTP method of the route
|
||||
* @param {string} url - url of the route
|
||||
* @param {any} [body] - webdriver request body
|
||||
* @param method - HTTP method of the route
|
||||
* @param url - url of the route
|
||||
* @param [body] - webdriver request body
|
||||
*
|
||||
* @returns {boolean} - whether the route should be avoided
|
||||
* @returns whether the route should be avoided
|
||||
*/
|
||||
proxyRouteIsAvoided(sessionId, method, url, body) {
|
||||
for (let avoidSchema of this.getProxyAvoidList(sessionId)) {
|
||||
proxyRouteIsAvoided(sessionId: string, method: HTTPMethod, url: string, body?: any): boolean {
|
||||
for (const avoidSchema of this.getProxyAvoidList(sessionId)) {
|
||||
if (!_.isArray(avoidSchema) || avoidSchema.length !== 2) {
|
||||
throw new Error('Proxy avoidance must be a list of pairs');
|
||||
}
|
||||
let [avoidMethod, avoidPathRegex] = avoidSchema;
|
||||
const [avoidMethod, avoidPathRegex] = avoidSchema;
|
||||
if (!_.includes(['GET', 'POST', 'DELETE'], avoidMethod)) {
|
||||
throw new Error(`Unrecognized proxy avoidance method '${avoidMethod}'`);
|
||||
}
|
||||
if (!_.isRegExp(avoidPathRegex)) {
|
||||
throw new Error('Proxy avoidance path must be a regular expression');
|
||||
}
|
||||
let normalizedUrl = url.replace(new RegExp(`^${_.escapeRegExp(this.basePath)}`), '');
|
||||
const normalizedUrl = url.replace(new RegExp(`^${_.escapeRegExp(this.basePath)}`), '');
|
||||
if (avoidMethod === method && avoidPathRegex.test(normalizedUrl)) {
|
||||
return true;
|
||||
}
|
||||
@@ -415,7 +362,7 @@ class DriverCore {
|
||||
*
|
||||
* @param {Driver} driver
|
||||
*/
|
||||
addManagedDriver(driver) {
|
||||
addManagedDriver(driver: Driver) {
|
||||
this.managedDrivers.push(driver);
|
||||
}
|
||||
|
||||
@@ -430,26 +377,3 @@ class DriverCore {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {DriverCore};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').ServerArgs} ServerArgs
|
||||
* @typedef {import('@appium/types').EventHistory} EventHistory
|
||||
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [S=StringRecord]
|
||||
* @typedef {import('@appium/types').Core<C, S>} Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').DriverOpts<C>} DriverOpts
|
||||
*/
|
||||
@@ -9,7 +9,7 @@ import {errors} from '../protocol/errors';
|
||||
export const MAX_SETTINGS_SIZE = 20 * 1024 * 1024; // 20 MB
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').StringRecord} T
|
||||
* @template {StringRecord} T
|
||||
* @implements {IDeviceSettings<T>}
|
||||
*/
|
||||
export class DeviceSettings {
|
||||
@@ -74,6 +74,10 @@ export class DeviceSettings {
|
||||
export default DeviceSettings;
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').StringRecord} [T=import('@appium/types').StringRecord]
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {StringRecord} [T=StringRecord]
|
||||
* @typedef {import('@appium/types').IDeviceSettings<T>} IDeviceSettings
|
||||
*/
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
import {validateCaps, processCapabilities} from './capabilities';
|
||||
import {DriverCore} from './core';
|
||||
import {util} from '@appium/support';
|
||||
import {
|
||||
BASE_DESIRED_CAP_CONSTRAINTS,
|
||||
type AppiumServer,
|
||||
type BaseDriverCapConstraints,
|
||||
type Capabilities,
|
||||
type Constraints,
|
||||
type DefaultCreateSessionResult,
|
||||
type Driver,
|
||||
type DriverCaps,
|
||||
type DriverData,
|
||||
type MultiSessionData,
|
||||
type ServerArgs,
|
||||
type StringRecord,
|
||||
type W3CDriverCaps,
|
||||
type InitialOpts,
|
||||
type DefaultDeleteSessionResult,
|
||||
type SingularSessionData,
|
||||
} from '@appium/types';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
||||
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
||||
import {processCapabilities, validateCaps} from './capabilities';
|
||||
import {DriverCore} from './core';
|
||||
import helpers from './helpers';
|
||||
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';
|
||||
@@ -31,26 +32,26 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
export class BaseDriver<
|
||||
C extends Constraints,
|
||||
const C extends Constraints,
|
||||
CArgs extends StringRecord = StringRecord,
|
||||
Settings extends StringRecord = StringRecord,
|
||||
CreateResult = DefaultCreateSessionResult<C>,
|
||||
DeleteResult = DefaultDeleteSessionResult,
|
||||
SessionData extends StringRecord = StringRecord
|
||||
>
|
||||
extends DriverCore<C, Settings>
|
||||
implements Driver<C, CArgs>
|
||||
implements Driver<C, CArgs, Settings, CreateResult, DeleteResult, SessionData>
|
||||
{
|
||||
cliArgs: CArgs & ServerArgs;
|
||||
|
||||
caps: DriverCaps<C>;
|
||||
originalCaps: W3CDriverCaps<C>;
|
||||
desiredCapConstraints: C;
|
||||
opts: DriverOpts<C>;
|
||||
server?: AppiumServer;
|
||||
serverHost?: string;
|
||||
serverPort?: number;
|
||||
serverPath?: string;
|
||||
|
||||
constructor(opts: DriverOpts<C>, shouldValidateCaps = true) {
|
||||
constructor(opts: InitialOpts, shouldValidateCaps = true) {
|
||||
super(opts, shouldValidateCaps);
|
||||
|
||||
this.caps = {} as DriverCaps<C>;
|
||||
@@ -63,7 +64,7 @@ export class BaseDriver<
|
||||
*
|
||||
* 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
|
||||
* @see {@link https://github.com/appium/appium/issues/new}
|
||||
*/
|
||||
protected get _desiredCapConstraints(): Readonly<BaseDriverCapConstraints & C> {
|
||||
return Object.freeze(_.merge({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints));
|
||||
@@ -234,7 +235,7 @@ export class BaseDriver<
|
||||
w3cCapabilities?: W3CDriverCaps<C>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
driverData?: DriverData[]
|
||||
): Promise<DefaultCreateSessionResult<C>> {
|
||||
): Promise<CreateResult> {
|
||||
if (this.sessionId !== null) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
'Cannot create a new session while one is in progress'
|
||||
@@ -311,7 +312,7 @@ export class BaseDriver<
|
||||
|
||||
this.log.info(`Session created with session id: ${this.sessionId}`);
|
||||
|
||||
return [this.sessionId, caps];
|
||||
return [this.sessionId, caps] as CreateResult;
|
||||
}
|
||||
async getSessions() {
|
||||
const ret: MultiSessionData<C>[] = [];
|
||||
@@ -330,7 +331,9 @@ export class BaseDriver<
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
*/
|
||||
async getSession() {
|
||||
return this.caps.eventTimings ? {...this.caps, events: this.eventHistory} : this.caps;
|
||||
return (
|
||||
this.caps.eventTimings ? {...this.caps, events: this.eventHistory} : this.caps
|
||||
) as SingularSessionData<C, SessionData>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -380,6 +383,20 @@ export class BaseDriver<
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateSettings(newSettings: Settings) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
||||
}
|
||||
return await this.settings.update(newSettings);
|
||||
}
|
||||
|
||||
async getSettings() {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
||||
}
|
||||
return this.settings.getSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export * from './commands';
|
||||
|
||||
@@ -72,7 +72,7 @@ function verifyAppExtension(app, supportedAppExtensions) {
|
||||
}
|
||||
|
||||
async function calculateFolderIntegrity(folderPath) {
|
||||
return (await fs.glob('**/*', {cwd: folderPath, strict: false, nosort: true})).length;
|
||||
return (await fs.glob('**/*', {cwd: folderPath})).length;
|
||||
}
|
||||
|
||||
async function calculateFileIntegrity(filePath) {
|
||||
@@ -141,6 +141,9 @@ async function configureApp(
|
||||
const isUrl = protocol === null ? false : ['http:', 'https:'].includes(protocol);
|
||||
|
||||
const cachedAppInfo = APPLICATIONS_CACHE.get(app);
|
||||
if (cachedAppInfo) {
|
||||
logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
|
||||
}
|
||||
|
||||
return await APPLICATIONS_CACHE_GUARD.acquire(app, async () => {
|
||||
if (isUrl) {
|
||||
@@ -150,27 +153,23 @@ async function configureApp(
|
||||
...DEFAULT_REQ_HEADERS,
|
||||
};
|
||||
if (cachedAppInfo?.etag) {
|
||||
reqHeaders['if-none-match'] = remoteAppProps.etag;
|
||||
reqHeaders['if-none-match'] = cachedAppInfo.etag;
|
||||
} else if (cachedAppInfo?.lastModified) {
|
||||
reqHeaders['if-modified-since'] = remoteAppProps.lastModified?.toString();
|
||||
reqHeaders['if-modified-since'] = cachedAppInfo.lastModified.toUTCString();
|
||||
}
|
||||
|
||||
let {headers, stream, status} = await queryAppLink(newApp, reqHeaders);
|
||||
try {
|
||||
if (!_.isEmpty(headers)) {
|
||||
logger.debug(`Etag: ${remoteAppProps?.etag} -> ${headers.etag}`);
|
||||
logger.debug(`Etag: ${headers.etag}`);
|
||||
if (headers.etag) {
|
||||
remoteAppProps.etag = headers.etag;
|
||||
}
|
||||
logger.debug(
|
||||
`Last-Modified: ${remoteAppProps?.['last-modified']} -> ${headers['last-modified']}`
|
||||
);
|
||||
logger.debug(`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']}`
|
||||
);
|
||||
logger.debug(`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']);
|
||||
@@ -363,12 +362,19 @@ async function configureApp(
|
||||
* @returns {Promise<RemoteAppData>}
|
||||
*/
|
||||
async function queryAppLink(appLink, reqHeaders) {
|
||||
const {href} = url.parse(appLink);
|
||||
const {href, auth} = url.parse(appLink);
|
||||
const axiosUrl = auth ? href.replace(`${auth}@`, '') : href;
|
||||
/** @type {import('axios').AxiosBasicCredentials|undefined} */
|
||||
const axiosAuth = auth ? {
|
||||
username: auth.substring(0, auth.indexOf(':')),
|
||||
password: auth.substring(auth.indexOf(':') + 1),
|
||||
} : undefined;
|
||||
/**
|
||||
* @type {import('axios').RawAxiosRequestConfig}
|
||||
*/
|
||||
const requestOpts = {
|
||||
url: href,
|
||||
url: axiosUrl,
|
||||
auth: axiosAuth,
|
||||
responseType: 'stream',
|
||||
timeout: APP_DOWNLOAD_TIMEOUT_MS,
|
||||
validateStatus: (status) =>
|
||||
@@ -383,7 +389,7 @@ async function queryAppLink(appLink, reqHeaders) {
|
||||
status,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(`Cannot download the app from ${href}: ${err.message}`);
|
||||
throw new Error(`Cannot download the app from ${axiosUrl}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +482,6 @@ async function unzipApp(zipPath, dstRoot, supportedAppExtensions) {
|
||||
const sortedBundleItems = (
|
||||
await fs.glob(globPattern, {
|
||||
cwd: tmpRoot,
|
||||
strict: false,
|
||||
// Get the top level match
|
||||
})
|
||||
).sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {util} from '@appium/support';
|
||||
import {Protocol} from '@appium/types';
|
||||
|
||||
// The default maximum length of a single log record
|
||||
// containing http request/response body
|
||||
@@ -12,7 +13,7 @@ const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
|
||||
const PROTOCOLS = {
|
||||
W3C: 'W3C',
|
||||
MJSONWP: 'MJSONWP',
|
||||
};
|
||||
} as const satisfies Record<Protocol, Protocol>;
|
||||
|
||||
// Before Appium 2.0, this default value was '/wd/hub' by historical reasons.
|
||||
const DEFAULT_BASE_PATH = '';
|
||||
@@ -1,34 +1,31 @@
|
||||
import log from './logger';
|
||||
import LRU from 'lru-cache';
|
||||
import {fs, util} from '@appium/support';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
const CACHE_SIZE = 1024;
|
||||
const IDEMPOTENT_RESPONSES = new LRU({
|
||||
max: CACHE_SIZE,
|
||||
max: 64,
|
||||
ttl: 30 * 60 * 1000,
|
||||
updateAgeOnGet: true,
|
||||
dispose(key, {response}) {
|
||||
if (response) {
|
||||
fs.rimrafSync(response);
|
||||
}
|
||||
},
|
||||
updateAgeOnHas: true,
|
||||
dispose: (key, {responseStateListener}) => {
|
||||
responseStateListener?.removeAllListeners();
|
||||
}
|
||||
});
|
||||
const MONITORED_METHODS = ['POST', 'PATCH'];
|
||||
const IDEMPOTENCY_KEY_HEADER = 'x-idempotency-key';
|
||||
|
||||
process.on('exit', () => {
|
||||
const resPaths = [...IDEMPOTENT_RESPONSES.values()].map(({response}) => response).filter(Boolean);
|
||||
for (const resPath of resPaths) {
|
||||
try {
|
||||
// Asynchronous calls are not supported in onExit handler
|
||||
fs.rimrafSync(resPath);
|
||||
} catch (ign) {}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
function cacheResponse(key, req, res) {
|
||||
if (!res.socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
const responseStateListener = new EventEmitter();
|
||||
IDEMPOTENT_RESPONSES.set(key, {
|
||||
method: req.method,
|
||||
@@ -36,33 +33,23 @@ function cacheResponse(key, req, res) {
|
||||
response: null,
|
||||
responseStateListener,
|
||||
});
|
||||
const tmpFile = path.resolve(os.tmpdir(), `${util.uuidV4()}.response`);
|
||||
const responseListener = fs.createWriteStream(tmpFile, {
|
||||
emitClose: true,
|
||||
});
|
||||
const originalSocketWriter = res.socket.write.bind(res.socket);
|
||||
let response = '';
|
||||
const patchedWriter = (chunk, encoding, next) => {
|
||||
if (responseListener.writable) {
|
||||
responseListener.write(chunk);
|
||||
if (_.isString(chunk)) {
|
||||
response += chunk;
|
||||
} else if (_.isFunction(chunk.toString)) {
|
||||
response += chunk.toString(_.isString(encoding) ? encoding : undefined);
|
||||
}
|
||||
return originalSocketWriter(chunk, encoding, next);
|
||||
};
|
||||
res.socket.write = patchedWriter;
|
||||
let writeError = null;
|
||||
let httpErrorMessage = null;
|
||||
let isResponseFullySent = false;
|
||||
responseListener.once('error', (e) => {
|
||||
writeError = e;
|
||||
});
|
||||
res.once('finish', () => {
|
||||
isResponseFullySent = true;
|
||||
responseListener.end();
|
||||
});
|
||||
res.once('error', (e) => { httpErrorMessage = e.message; });
|
||||
req.once('error', (e) => { httpErrorMessage = e.message; });
|
||||
res.once('finish', () => { isResponseFullySent = true; });
|
||||
res.once('close', () => {
|
||||
if (!isResponseFullySent) {
|
||||
responseListener.end();
|
||||
}
|
||||
});
|
||||
responseListener.once('close', () => {
|
||||
if (res.socket?.write === patchedWriter) {
|
||||
res.socket.write = originalSocketWriter;
|
||||
}
|
||||
@@ -70,35 +57,41 @@ function cacheResponse(key, req, res) {
|
||||
if (!IDEMPOTENT_RESPONSES.has(key)) {
|
||||
log.info(
|
||||
`Could not cache the response identified by '${key}'. ` +
|
||||
`Cache consistency has been damaged`
|
||||
`Cache consistency has been damaged`
|
||||
);
|
||||
return responseStateListener.emit('ready', null);
|
||||
}
|
||||
if (writeError) {
|
||||
log.info(`Could not cache the response identified by '${key}': ${writeError.message}`);
|
||||
} else if (httpErrorMessage) {
|
||||
log.info(`Could not cache the response identified by '${key}': ${httpErrorMessage}`);
|
||||
IDEMPOTENT_RESPONSES.delete(key);
|
||||
return responseStateListener.emit('ready', null);
|
||||
}
|
||||
if (!isResponseFullySent) {
|
||||
} else if (!isResponseFullySent) {
|
||||
log.info(
|
||||
`Could not cache the response identified by '${key}', ` +
|
||||
`because it has not been completed`
|
||||
`because it has not been completed`
|
||||
);
|
||||
log.info('Does the client terminate connections too early?');
|
||||
IDEMPOTENT_RESPONSES.delete(key);
|
||||
return responseStateListener.emit('ready', null);
|
||||
}
|
||||
|
||||
IDEMPOTENT_RESPONSES.get(key).response = tmpFile;
|
||||
responseStateListener.emit('ready', tmpFile);
|
||||
const value = IDEMPOTENT_RESPONSES.get(key);
|
||||
if (value) {
|
||||
value.response = Buffer.from(response, 'utf8');
|
||||
responseStateListener.emit('ready', value.response);
|
||||
} else {
|
||||
responseStateListener.emit('ready', null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function handleIdempotency(req, res, next) {
|
||||
const key = req.headers[IDEMPOTENCY_KEY_HEADER];
|
||||
if (!key) {
|
||||
const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER];
|
||||
if (!keyOrArr) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const key = _.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr;
|
||||
if (!MONITORED_METHODS.includes(req.method)) {
|
||||
// GET, DELETE, etc. requests are idempotent by default
|
||||
// there is no need to cache them
|
||||
@@ -112,39 +105,32 @@ async function handleIdempotency(req, res, next) {
|
||||
}
|
||||
|
||||
const {
|
||||
method: storedMethod,
|
||||
path: storedPath,
|
||||
method,
|
||||
path,
|
||||
response,
|
||||
responseStateListener,
|
||||
} = IDEMPOTENT_RESPONSES.get(key);
|
||||
if (req.method !== storedMethod || req.path !== storedPath) {
|
||||
if (req.method !== method || req.path !== path) {
|
||||
log.warn(`Got two different requests with the same idempotency key '${key}'`);
|
||||
log.warn('Is the client generating idempotency keys properly?');
|
||||
return next();
|
||||
}
|
||||
|
||||
const rerouteCachedResponse = async (cachedResPath) => {
|
||||
if (!(await fs.exists(cachedResPath))) {
|
||||
IDEMPOTENT_RESPONSES.delete(key);
|
||||
log.warn(`Could not read the cached response identified by key '${key}'`);
|
||||
log.warn('The temporary storage is not accessible anymore');
|
||||
return next();
|
||||
}
|
||||
fs.createReadStream(cachedResPath).pipe(res.socket);
|
||||
};
|
||||
|
||||
if (response) {
|
||||
log.info(`The same request with the idempotency key '${key}' has been already processed`);
|
||||
log.info(`Rerouting its response to the current request`);
|
||||
await rerouteCachedResponse(response);
|
||||
if (!res.socket?.writable) {
|
||||
return next();
|
||||
}
|
||||
res.socket.write(response.toString('utf8'));
|
||||
} else {
|
||||
log.info(`The same request with the idempotency key '${key}' is being processed`);
|
||||
log.info(`Waiting for the response to be rerouted to the current request`);
|
||||
responseStateListener.once('ready', async (cachedResponsePath) => {
|
||||
if (!cachedResponsePath) {
|
||||
responseStateListener.once('ready', async (/** @type {Buffer?} */ cachedResponse) => {
|
||||
if (!cachedResponse || !res.socket?.writable) {
|
||||
return next();
|
||||
}
|
||||
await rerouteCachedResponse(cachedResponsePath);
|
||||
res.socket.write(cachedResponse.toString('utf8'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ function configureServer({
|
||||
} else {
|
||||
app.use(allowCrossDomainAsyncExecute(basePath));
|
||||
}
|
||||
// @ts-ignore
|
||||
app.use(handleIdempotency);
|
||||
app.use(fixPythonContentType(basePath));
|
||||
app.use(defaultToJSONContentType);
|
||||
|
||||
@@ -15,7 +15,7 @@ export default BaseDriver;
|
||||
// MJSONWP exports
|
||||
export * from './protocol';
|
||||
export {errorFromMJSONWPStatusCode as errorFromCode} from './protocol';
|
||||
export {DEFAULT_BASE_PATH, PROTOCOLS} from './constants';
|
||||
export {DEFAULT_BASE_PATH, PROTOCOLS, W3C_ELEMENT_KEY} from './constants';
|
||||
|
||||
// Express exports
|
||||
export {STATIC_DIR} from './express/static';
|
||||
|
||||
@@ -320,6 +320,12 @@ class JWProxy {
|
||||
return commandName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {import('@appium/types').HTTPMethod} method
|
||||
* @param {any?} body
|
||||
*/
|
||||
async proxyCommand(url, method, body = null) {
|
||||
const commandName = this.requestToCommandName(url, method);
|
||||
if (!commandName) {
|
||||
@@ -330,6 +336,13 @@ class JWProxy {
|
||||
return await this.protocolConverter.convertAndProxy(commandName, url, method, body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {import('@appium/types').HTTPMethod} method
|
||||
* @param {any?} body
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async command(url, method, body = null) {
|
||||
let response;
|
||||
let resBodyObj;
|
||||
|
||||
@@ -843,24 +843,43 @@ export class UnableToCaptureScreen extends ProtocolError {
|
||||
}
|
||||
}
|
||||
|
||||
function generateBadParametersMessage(requiredParams, actualParams) {
|
||||
const toArray = (/** @type {any} */ x) => _.isArray(x) ? x : [];
|
||||
|
||||
const requiredParamNames = toArray(requiredParams?.required);
|
||||
const actualParamNames = toArray(actualParams);
|
||||
const missingRequiredParamNames = _.difference(requiredParamNames, actualParamNames);
|
||||
/** @type {string[]} */
|
||||
const resultLines = [];
|
||||
resultLines.push(_.isEmpty(missingRequiredParamNames)
|
||||
// This should not happen
|
||||
? 'Some of the provided parameters are not known'
|
||||
: (`The following required parameter${missingRequiredParamNames.length === 1 ? ' is ' : 's are '}`
|
||||
+ `missing: ${JSON.stringify(missingRequiredParamNames)}`)
|
||||
);
|
||||
if (!_.isEmpty(requiredParamNames)) {
|
||||
resultLines.push(`Known required parameters are: ${JSON.stringify(requiredParamNames)}`);
|
||||
}
|
||||
const optionalParamNames = _.difference(toArray(requiredParams?.optional), ['sessionId', 'id']);
|
||||
if (!_.isEmpty(optionalParamNames)) {
|
||||
resultLines.push(`Known optional parameters are: ${JSON.stringify(optionalParamNames)}`);
|
||||
}
|
||||
resultLines.push(
|
||||
`You have provided${_.isEmpty(actualParamNames) ? ' none' : (': ' + JSON.stringify(actualParams))}`
|
||||
);
|
||||
return resultLines.join('\n');
|
||||
}
|
||||
|
||||
// Equivalent to W3C InvalidArgumentError
|
||||
export class BadParametersError extends ES6Error {
|
||||
static error() {
|
||||
return 'invalid argument';
|
||||
}
|
||||
constructor(requiredParams, actualParams, errMessage) {
|
||||
let message;
|
||||
if (!errMessage) {
|
||||
message =
|
||||
`Parameters were incorrect. We wanted ` +
|
||||
`${JSON.stringify(requiredParams)} and you ` +
|
||||
`sent ${JSON.stringify(actualParams)}`;
|
||||
} else {
|
||||
message = `Parameters were incorrect. You sent ${JSON.stringify(
|
||||
actualParams
|
||||
)}, ${errMessage}`;
|
||||
}
|
||||
super(message);
|
||||
super(errMessage
|
||||
? `Parameters were incorrect. You sent ${JSON.stringify(actualParams)}, ${errMessage}`
|
||||
: generateBadParametersMessage(requiredParams, actualParams)
|
||||
);
|
||||
this.w3cStatus = HTTPStatusCodes.BAD_REQUEST;
|
||||
}
|
||||
}
|
||||
@@ -982,7 +1001,13 @@ function isUnknownError(err) {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an Error is of a specific type
|
||||
* @template {Error} T
|
||||
* @param {any} err
|
||||
* @param {import('@appium/types').Class<T>} type
|
||||
* @returns {err is T}
|
||||
*/
|
||||
function isErrorType(err, type) {
|
||||
// `name` property is the constructor name
|
||||
if (type.name === ProtocolError.name) {
|
||||
@@ -1072,6 +1097,7 @@ function getResponseForW3CError(err) {
|
||||
w3cLog.debug(`Bad parameters: ${err}`);
|
||||
w3cErrorString = BadParametersError.error();
|
||||
} else {
|
||||
// @ts-expect-error unclear what the problem is here
|
||||
w3cErrorString = err.error;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.3.8",
|
||||
"version": "9.3.18",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -37,19 +37,20 @@
|
||||
"!build/tsconfig.tsbuildinfo"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:unit",
|
||||
"test:e2e": "mocha -p --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
||||
"test": "run-p test:unit test:types",
|
||||
"test:e2e": "mocha --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\"",
|
||||
"test:types": "tsd"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.0.0",
|
||||
"@appium/types": "^0.11.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@appium/support": "^4.1.5",
|
||||
"@appium/types": "^0.13.3",
|
||||
"@colors/colors": "1.6.0",
|
||||
"@types/async-lock": "1.4.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/lodash": "4.14.197",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/serve-favicon": "2.5.4",
|
||||
"async-lock": "1.4.0",
|
||||
@@ -66,7 +67,7 @@
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.10.0",
|
||||
"type-fest": "3.13.1",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -428,7 +428,7 @@ describe('Protocol', function () {
|
||||
status.should.equal(400);
|
||||
|
||||
const {error: w3cError, message, stacktrace} = data.value;
|
||||
message.should.match(/Parameters were incorrect/);
|
||||
message.should.match(/following required parameter/);
|
||||
stacktrace.should.match(/protocol.js/);
|
||||
w3cError.should.be.a.string;
|
||||
w3cError.should.equal(errors.InvalidArgumentError.error());
|
||||
|
||||
@@ -4,8 +4,8 @@ import {BaseDriver} from '.../../..';
|
||||
import {BaseDriverCapConstraints, ExternalDriver, Driver, DriverOpts} from '@appium/types';
|
||||
|
||||
expectAssignable<Driver<BaseDriverCapConstraints>>(
|
||||
new BaseDriver({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
new BaseDriver<BaseDriverCapConstraints>({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
);
|
||||
expectAssignable<ExternalDriver<BaseDriverCapConstraints>>(
|
||||
new BaseDriver({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
new BaseDriver<BaseDriverCapConstraints>({} as DriverOpts<BaseDriverCapConstraints>)
|
||||
);
|
||||
|
||||
@@ -36,18 +36,15 @@ describe('Desired Capabilities', function () {
|
||||
it('should require platformName', async function () {
|
||||
await d
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
'appium:deviceName': 'Delorean',
|
||||
},
|
||||
alwaysMatch: {},
|
||||
firstMatch: [{}],
|
||||
})
|
||||
.should.be.rejectedWith(errors.SessionNotCreatedError, /platformName/);
|
||||
});
|
||||
|
||||
it('should not care about cap order', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
'appium:deviceName': 'Delorean',
|
||||
platformName: 'iOS',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -67,10 +64,9 @@ describe('Desired Capabilities', function () {
|
||||
};
|
||||
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
})
|
||||
@@ -90,7 +86,7 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
'appium:necessary': 'yup',
|
||||
},
|
||||
@@ -100,28 +96,22 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should accept extra capabilities', async function () {
|
||||
await d.createSession(
|
||||
null,
|
||||
null,
|
||||
/** @type {import('@appium/types').W3CCapabilities<{'hold the':string}>} */ ({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:extra': 'cheese',
|
||||
'appium:hold the': 'sauce',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
})
|
||||
).should.be.fulfilled;
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:extra': 'cheese',
|
||||
'appium:hold the': 'sauce',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
}).should.be.fulfilled;
|
||||
});
|
||||
|
||||
it('should log the use of extra caps', async function () {
|
||||
this.timeout(500);
|
||||
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:extra': 'cheese',
|
||||
'appium:hold the': 'sauce',
|
||||
},
|
||||
@@ -133,11 +123,10 @@ describe('Desired Capabilities', function () {
|
||||
|
||||
it('should be sensitive to the case of caps', async function () {
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
// @ts-expect-error
|
||||
platformname: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
})
|
||||
@@ -146,10 +135,9 @@ describe('Desired Capabilities', function () {
|
||||
|
||||
describe('boolean capabilities', function () {
|
||||
it('should allow a string "false"', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
// @ts-expect-error
|
||||
'appium:noReset': 'false',
|
||||
},
|
||||
@@ -162,10 +150,9 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should allow a string "true"', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
// @ts-expect-error
|
||||
'appium:noReset': 'true',
|
||||
},
|
||||
@@ -178,10 +165,9 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should allow a string "true" in string capabilities', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:language': 'true',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -195,10 +181,9 @@ describe('Desired Capabilities', function () {
|
||||
|
||||
describe('number capabilities', function () {
|
||||
it('should allow a string "1"', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
// @ts-expect-error
|
||||
'appium:newCommandTimeout': '1',
|
||||
},
|
||||
@@ -211,10 +196,9 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should allow a string "1.1"', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
// @ts-expect-error
|
||||
'appium:newCommandTimeout': '1.1',
|
||||
},
|
||||
@@ -227,10 +211,9 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should allow a string "1" in string capabilities', async function () {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:language': '1',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -244,11 +227,10 @@ describe('Desired Capabilities', function () {
|
||||
|
||||
it('should error if objects in caps', async function () {
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
// @ts-expect-error
|
||||
platformName: {a: 'iOS'},
|
||||
'appium:deviceName': 'Delorean',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
})
|
||||
@@ -264,10 +246,9 @@ describe('Desired Capabilities', function () {
|
||||
},
|
||||
};
|
||||
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:lynx-version': 5,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -285,10 +266,9 @@ describe('Desired Capabilities', function () {
|
||||
},
|
||||
};
|
||||
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:lynx-version': 5,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -305,10 +285,9 @@ describe('Desired Capabilities', function () {
|
||||
};
|
||||
|
||||
try {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': null,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -318,10 +297,9 @@ describe('Desired Capabilities', function () {
|
||||
}
|
||||
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': 1,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -329,10 +307,9 @@ describe('Desired Capabilities', function () {
|
||||
.should.be.rejectedWith(/'foo' must be of type string/);
|
||||
|
||||
try {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': undefined,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -342,10 +319,9 @@ describe('Desired Capabilities', function () {
|
||||
}
|
||||
|
||||
try {
|
||||
await d.createSession(null, null, {
|
||||
await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': '',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -363,10 +339,9 @@ describe('Desired Capabilities', function () {
|
||||
};
|
||||
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': null,
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -374,10 +349,10 @@ describe('Desired Capabilities', function () {
|
||||
.should.be.rejectedWith(/blank/);
|
||||
|
||||
await d
|
||||
// @ts-expect-error `null` is not actually allowed here
|
||||
.createSession(null, {
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': '',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -389,7 +364,6 @@ describe('Desired Capabilities', function () {
|
||||
firstMatch: [
|
||||
{
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': {},
|
||||
},
|
||||
],
|
||||
@@ -401,7 +375,6 @@ describe('Desired Capabilities', function () {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': [],
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -412,7 +385,6 @@ describe('Desired Capabilities', function () {
|
||||
.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Dumb',
|
||||
'appium:foo': ' ',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
@@ -422,17 +394,15 @@ describe('Desired Capabilities', function () {
|
||||
|
||||
describe('w3c', function () {
|
||||
it('should accept w3c capabilities', async function () {
|
||||
const [sessionId, caps] = await d.createSession(null, null, {
|
||||
const [sessionId, caps] = await d.createSession({
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
});
|
||||
sessionId.should.exist;
|
||||
caps.should.eql({
|
||||
platformName: 'iOS',
|
||||
deviceName: 'Delorean',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -442,7 +412,7 @@ describe('Desired Capabilities', function () {
|
||||
await B.map(testValues, (val) =>
|
||||
d
|
||||
// @ts-expect-error
|
||||
.createSession(null, null, val)
|
||||
.createSession(val)
|
||||
.should.be.rejectedWith(errors.SessionNotCreatedError)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.18](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.17...@appium/base-plugin@2.2.18) (2023-08-21)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.17](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.16...@appium/base-plugin@2.2.17) (2023-08-17)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.16](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.15...@appium/base-plugin@2.2.16) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.15](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.14...@appium/base-plugin@2.2.15) (2023-07-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.14](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.13...@appium/base-plugin@2.2.14) (2023-06-29)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.13](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.12...@appium/base-plugin@2.2.13) (2023-06-15)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.12](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.11...@appium/base-plugin@2.2.12) (2023-06-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.8",
|
||||
"version": "2.2.18",
|
||||
"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.8",
|
||||
"@appium/support": "^4.0.0"
|
||||
"@appium/base-driver": "^9.3.18",
|
||||
"@appium/support": "^4.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,87 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.24](https://github.com/appium/appium/compare/@appium/doctor@2.0.23...@appium/doctor@2.0.24) (2023-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.14.5 ([d5f3a28](https://github.com/appium/appium/commit/d5f3a288c2497d4020b7eabc667814744e69ac48))
|
||||
|
||||
|
||||
|
||||
## [2.0.23](https://github.com/appium/appium/compare/@appium/doctor@2.0.22...@appium/doctor@2.0.23) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.14.4 ([#18844](https://github.com/appium/appium/issues/18844)) ([a9e86aa](https://github.com/appium/appium/commit/a9e86aabc657bf22df8b1c5f67ab93acc0ced934))
|
||||
* **doctor:** update dependency inquirer to v8.2.6 ([#18940](https://github.com/appium/appium/issues/18940)) ([a8cc551](https://github.com/appium/appium/commit/a8cc551210c31b014025f7c56b091163f4d15df1))
|
||||
* **test-support:** update dependency @colors/colors to v1.6.0 ([1358937](https://github.com/appium/appium/commit/1358937db2edf08ce1ebe3dff2f70ac6b07cd373))
|
||||
|
||||
|
||||
|
||||
## [2.0.22](https://github.com/appium/appium/compare/@appium/doctor@2.0.21...@appium/doctor@2.0.22) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency teen_process to v2.0.4 ([151c19c](https://github.com/appium/appium/commit/151c19c1c3b4c9b94aba10033c2d863f567d849b))
|
||||
|
||||
|
||||
|
||||
## [2.0.21](https://github.com/appium/appium/compare/@appium/doctor@2.0.20...@appium/doctor@2.0.21) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v9.11.7 ([fdd2ac0](https://github.com/appium/appium/commit/fdd2ac098d1b6e0d17b1429defa0123660d85ca9))
|
||||
|
||||
|
||||
|
||||
## [2.0.20](https://github.com/appium/appium/compare/@appium/doctor@2.0.19...@appium/doctor@2.0.20) (2023-06-29)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.19](https://github.com/appium/appium/compare/@appium/doctor@2.0.18...@appium/doctor@2.0.19) (2023-06-15)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.18](https://github.com/appium/appium/compare/@appium/doctor@2.0.17...@appium/doctor@2.0.18) (2023-06-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -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.15",
|
||||
"version": "2.0.24",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,16 +44,16 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.0.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@appium/support": "^4.1.5",
|
||||
"@colors/colors": "1.6.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"appium-adb": "9.11.2",
|
||||
"appium-adb": "9.14.5",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.5",
|
||||
"inquirer": "8.2.6",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"teen_process": "2.0.4",
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
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';
|
||||
import {withMocks, stubEnv} from '@appium/test-support';
|
||||
import B from 'bluebird';
|
||||
import {removeColors} from './helper';
|
||||
import {createSandbox} from 'sinon';
|
||||
|
||||
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) => {
|
||||
@@ -55,8 +60,17 @@ describe('android', function () {
|
||||
})
|
||||
);
|
||||
describe(
|
||||
'AndroidToolCheck',
|
||||
withMocks({adb}, (mocks) => {
|
||||
'AndroidToolCheck', function () {
|
||||
// withMocks won't work with ESM modules: https://github.com/sinonjs/sinon/issues/2530
|
||||
/** @type {sinon.SinonSandbox} */
|
||||
let sandbox;
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
stubEnv();
|
||||
const check = new AndroidToolCheck();
|
||||
it('autofix', function () {
|
||||
@@ -64,14 +78,13 @@ describe('android', function () {
|
||||
});
|
||||
it('diagnose - success', async function () {
|
||||
process.env.ANDROID_HOME = '/a/b/c/d';
|
||||
mocks.adb.expects('getAndroidBinaryPath').exactly(check.tools.length)
|
||||
.returns(B.resolve('/path/to/binary'));
|
||||
sandbox.stub(adb, 'getAndroidBinaryPath')
|
||||
.get(() => sandbox.fake.resolves('/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();
|
||||
});
|
||||
it('diagnose - failure - no ANDROID_HOME', async function () {
|
||||
delete process.env.ANDROID_HOME;
|
||||
@@ -79,21 +92,18 @@ 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();
|
||||
sandbox.stub(adb, 'getAndroidBinaryPath')
|
||||
.get(() => sandbox.fake.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();
|
||||
});
|
||||
it('fix - ANDROID_HOME', async function () {
|
||||
delete process.env.ANDROID_HOME;
|
||||
@@ -102,14 +112,12 @@ 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.`
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
describe(
|
||||
@@ -138,8 +146,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 +168,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 +177,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,118 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.7](https://github.com/appium/appium/compare/@appium/docutils@0.4.6...@appium/docutils@0.4.7) (2023-08-21)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.6](https://github.com/appium/appium/compare/@appium/docutils@0.4.5...@appium/docutils@0.4.6) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency semver to v7.5.4 ([03ddcdf](https://github.com/appium/appium/commit/03ddcdfcafbccf6698868a272e499cbdac41d791))
|
||||
* **types:** update dependency type-fest to v3.13.1 ([fb34ab9](https://github.com/appium/appium/commit/fb34ab917216121d2b554677a12f07a03393d218))
|
||||
|
||||
|
||||
|
||||
## [0.4.5](https://github.com/appium/appium/compare/@appium/docutils@0.4.4...@appium/docutils@0.4.5) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency teen_process to v2.0.4 ([151c19c](https://github.com/appium/appium/commit/151c19c1c3b4c9b94aba10033c2d863f567d849b))
|
||||
|
||||
|
||||
|
||||
## [0.4.4](https://github.com/appium/appium/compare/@appium/docutils@0.4.3...@appium/docutils@0.4.4) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency semver to v7.5.3 ([ed1856d](https://github.com/appium/appium/commit/ed1856d720c442842d5c3fe5f6caeccb8229b5f1))
|
||||
|
||||
|
||||
|
||||
## [0.4.3](https://github.com/appium/appium/compare/@appium/docutils@0.4.2...@appium/docutils@0.4.3) (2023-06-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** docs versioning and update latest ([#18804](https://github.com/appium/appium/issues/18804)) ([52a755a](https://github.com/appium/appium/commit/52a755a9135e06ad676cc422e37b5cab019c5b85))
|
||||
|
||||
|
||||
|
||||
## [0.4.2](https://github.com/appium/appium/compare/@appium/docutils@0.4.1...@appium/docutils@0.4.2) (2023-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** set mike version to major.minor by default ([#18770](https://github.com/appium/appium/issues/18770)) ([1a5b8cc](https://github.com/appium/appium/commit/1a5b8cca2d22b0be0150fe87be963973c4cc04e5))
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/appium/appium/compare/@appium/docutils@0.4.0...@appium/docutils@0.4.1) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** fixes some weirdness with default behavior and the "implies" keyword ([#18751](https://github.com/appium/appium/issues/18751)) ([e5254b5](https://github.com/appium/appium/commit/e5254b5b4564450252fd3eb55b773c0d7c615316))
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
// @ts-check
|
||||
|
||||
'use strict';
|
||||
require('source-map-support').install();
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -13,10 +13,12 @@ import {
|
||||
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 {findMike, findMkDocsYml, findPython, readPackageJson} from '../fs';
|
||||
import {getLogger} from '../logger';
|
||||
import {argify, spawnBackgroundProcess, SpawnBackgroundProcessOpts, stopwatch} from '../util';
|
||||
|
||||
@@ -24,30 +26,28 @@ const log = getLogger('builder:deploy');
|
||||
|
||||
/**
|
||||
* Runs `mike serve`
|
||||
* @param mikePath Path to `mike` 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(
|
||||
mikePath: string,
|
||||
args: string[] = [],
|
||||
opts: SpawnBackgroundProcessOpts = {},
|
||||
mikePath?: string
|
||||
opts: SpawnBackgroundProcessOpts = {}
|
||||
) {
|
||||
mikePath = mikePath ?? (await whichMike());
|
||||
const finalArgs = ['serve', ...args];
|
||||
return spawnBackgroundProcess(mikePath, finalArgs, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `mike build`
|
||||
* @param mikePath Path to `mike` 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());
|
||||
async function doDeploy(mikePath: string, args: string[] = [], opts: TeenProcessExecOptions = {}) {
|
||||
const finalArgs = ['deploy', ...args];
|
||||
log.debug('Launching %s with args: %O', mikePath, finalArgs);
|
||||
log.debug('Executing %s via: %s %O', NAME_MIKE, mikePath, finalArgs);
|
||||
return await exec(mikePath, finalArgs, opts);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,14 @@ async function findDeployVersion(packageJsonPath?: string, cwd = process.cwd()):
|
||||
'No "version" field found in package.json; please add one or specify a version to deploy'
|
||||
);
|
||||
}
|
||||
return version;
|
||||
|
||||
// return MAJOR.MINOR as the version by default, if that is a thing we can extract, otherwise
|
||||
// just return the version as is
|
||||
const versionParts = version.split('.');
|
||||
if (versionParts.length === 1) {
|
||||
return version;
|
||||
}
|
||||
return `${versionParts[0]}.${versionParts[1]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +97,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(
|
||||
@@ -112,17 +128,25 @@ export async function deploy({
|
||||
port,
|
||||
host,
|
||||
};
|
||||
|
||||
const mikePath = await findMike();
|
||||
if (!mikePath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_MIKE} executable; please run "${NAME_BIN} init"`
|
||||
);
|
||||
}
|
||||
if (serve) {
|
||||
const mikeArgs = [
|
||||
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value))),
|
||||
version,
|
||||
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value)))
|
||||
];
|
||||
if (alias) {
|
||||
mikeArgs.push(alias);
|
||||
mikeArgs.push('--update-aliases', version, alias);
|
||||
} else {
|
||||
mikeArgs.push(version);
|
||||
}
|
||||
stop(); // discard
|
||||
// unsure about how SIGHUP is handled here
|
||||
await doServe(mikeArgs, serveOpts);
|
||||
await doServe(mikePath, mikeArgs, serveOpts);
|
||||
} else {
|
||||
log.info('Deploying into branch %s', branch);
|
||||
const mikeArgs = [
|
||||
@@ -132,12 +156,13 @@ export async function deploy({
|
||||
(value, key) => _.includes(['port', 'host'], key) || (!_.isNumber(value) && !value)
|
||||
)
|
||||
),
|
||||
version,
|
||||
];
|
||||
if (alias) {
|
||||
mikeArgs.push(alias);
|
||||
mikeArgs.push('--update-aliases', version, alias);
|
||||
} else {
|
||||
mikeArgs.push(version);
|
||||
}
|
||||
await doDeploy(mikeArgs, execOpts);
|
||||
await doDeploy(mikePath, mikeArgs, execOpts);
|
||||
|
||||
log.success('Finished deployment into branch %s (%dms)', branch, stop());
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ export function parseNav(nav: MkDocsYmlNav): ParsedNavData[] {
|
||||
const queue: QueueItem[] = [{entries, keypath: ''}];
|
||||
|
||||
while (queue.length) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {entries, keypath} = queue.shift()!;
|
||||
for (const [key, item] of entries) {
|
||||
if (_.isString(item)) {
|
||||
@@ -99,8 +100,10 @@ export function parseNav(nav: MkDocsYmlNav): ParsedNavData[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all items within the list of parsed nav data which correpsond to the header. This is
|
||||
* imperfect, as it's possible for the header string to appear in multiple places in the nav, but let's just ignore that until we can't.
|
||||
* Finds all items within the list of parsed nav data which correpsond to the header.
|
||||
*
|
||||
* This is imperfect, as it's possible for the header string to appear in multiple places in the
|
||||
* nav, but let's just ignore that until we can't.
|
||||
* @param navData Some parsed nav data
|
||||
* @param header Header name
|
||||
*/
|
||||
@@ -109,6 +112,8 @@ function filterHeaderItems(navData: ParsedNavData[], header: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a root header keypath (like a prefix), a numeric offset, and some parsed nav data, compute
|
||||
* a keypath
|
||||
*
|
||||
* @param rootHeaderKeypath Root keypath as determined by {@linkcode getRootHeaderKeypath}
|
||||
* @param offset Numeric offset within the array of items for the header
|
||||
@@ -135,16 +140,16 @@ function navDataDidChange(
|
||||
newNavData: Array<Omit<ParsedNavData, 'name'>>,
|
||||
navData: ParsedNavData[]
|
||||
): boolean {
|
||||
const diff = _.xorWith(
|
||||
newNavData,
|
||||
navData,
|
||||
(a, b) => a.keypath === b.keypath && a.fileOrUrl === b.fileOrUrl
|
||||
);
|
||||
// the result should be the items from newNavData which either don't appear in
|
||||
// navdata, or the items which do appear in navData but have a different fileOrUrl
|
||||
const matchedKeypaths = _.intersectionBy(newNavData, navData, 'keypath');
|
||||
const diff = _.xorBy(newNavData, matchedKeypaths, 'fileOrUrl');
|
||||
return !_.isEmpty(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "root" keypath for a particular header
|
||||
*
|
||||
* @param headerItems Some subset of the nav data having keypaths corresponding to `header`
|
||||
* @param header Header string
|
||||
* @returns The keypath up to the header string, inclusive
|
||||
@@ -153,6 +158,7 @@ function getRootHeaderKeypath(headerItems: ParsedNavData[], header: string) {
|
||||
// these are the parts of the keypath of the first item, which will contain the header string. it
|
||||
// dosn't matter whether we pick the first one or any one; they will all contain the same root
|
||||
// keypath by definition.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const rootHeaderKeypathParts = _.toPath(_.first(headerItems)!.keypath);
|
||||
|
||||
// this is the keypath up to the header string, inclusive.
|
||||
@@ -165,7 +171,6 @@ function getRootHeaderKeypath(headerItems: ParsedNavData[], header: string) {
|
||||
* by `dir`; if `all` is true, we just use the default header, because we will be returning a whole
|
||||
* lot of headers).
|
||||
*
|
||||
*
|
||||
* @param dir Abs path to directory containing markdown files generated by TypeDoc
|
||||
* @param mkDocsDocsDir Configured `docs_dir` or via options
|
||||
* @param navData Nav data parsed by {@linkcode parseNav}
|
||||
@@ -200,7 +205,6 @@ async function getNavItemsForDir(
|
||||
const offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
|
||||
const newOffset = offset >= 0 ? offset : navHeaderItems.length;
|
||||
const data = navHeaderItems[offset];
|
||||
log.warn(rootHeaderKeypath, newOffset, data);
|
||||
const keypath = getKeypathForHeaderItem(rootHeaderKeypath, newOffset, data);
|
||||
newNavHeaderItems.push({keypath, fileOrUrl});
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import {exec, TeenProcessExecOptions} from 'teen_process';
|
||||
import {DEFAULT_SITE_DIR, NAME_BIN, NAME_MKDOCS_YML} from '../constants';
|
||||
import {DEFAULT_SITE_DIR, NAME_BIN, NAME_MKDOCS, NAME_MKDOCS_YML, NAME_PYTHON} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {findMkDocsYml, readMkDocsYml, whichMkDocs} from '../fs';
|
||||
import {findMkDocsYml, findPython, readMkDocsYml} from '../fs';
|
||||
import {getLogger} from '../logger';
|
||||
import {relative, spawnBackgroundProcess, SpawnBackgroundProcessOpts, stopwatch} from '../util';
|
||||
|
||||
@@ -17,37 +17,31 @@ const log = getLogger('mkdocs');
|
||||
|
||||
/**
|
||||
* Runs `mkdocs serve`
|
||||
* @param pythonPath Path to Python 3 executable
|
||||
* @param args Extra args to `mkdocs build`
|
||||
* @param opts Extra options for `teen_process.Subprocess.start`
|
||||
* @param mkDocsPath Path to `mkdocs` executable
|
||||
*/
|
||||
async function doServe(
|
||||
pythonPath: string,
|
||||
args: string[] = [],
|
||||
opts: SpawnBackgroundProcessOpts = {},
|
||||
mkDocsPath?: string
|
||||
opts: SpawnBackgroundProcessOpts = {}
|
||||
) {
|
||||
mkDocsPath = mkDocsPath ?? (await whichMkDocs());
|
||||
const finalArgs = ['serve', ...args];
|
||||
|
||||
log.debug('Launching %s with args: %s', mkDocsPath, finalArgs);
|
||||
return spawnBackgroundProcess(mkDocsPath, finalArgs, opts);
|
||||
const finalArgs = ['-m', NAME_MKDOCS, 'serve', ...args];
|
||||
log.debug('Executing %s via: %s, %O', NAME_MKDOCS, pythonPath, finalArgs);
|
||||
return spawnBackgroundProcess(pythonPath, finalArgs, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `mkdocs build`
|
||||
* @param pythonPath Path to Python 3 executable
|
||||
* @param args Extra args to `mkdocs build`
|
||||
* @param opts Extra options to `teen_process.exec`
|
||||
* @param mkDocsPath Path to `mkdocs` executable
|
||||
*/
|
||||
async function doBuild(
|
||||
args: string[] = [],
|
||||
opts: TeenProcessExecOptions = {},
|
||||
mkDocsPath?: string
|
||||
) {
|
||||
mkDocsPath = mkDocsPath ?? (await whichMkDocs());
|
||||
const finalArgs = ['build', ...args];
|
||||
log.debug('Launching %s with args: %O', mkDocsPath, finalArgs);
|
||||
return await exec(mkDocsPath, finalArgs, opts);
|
||||
async function doBuild(pythonPath: string, args: string[] = [], opts: TeenProcessExecOptions = {}) {
|
||||
const finalArgs = ['-m', NAME_MKDOCS, 'build', ...args];
|
||||
log.debug('Executing %s via: %s, %O', NAME_MKDOCS, pythonPath, finalArgs);
|
||||
return await exec(pythonPath, finalArgs, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,6 +57,14 @@ export async function buildSite({
|
||||
execOpts,
|
||||
}: BuildMkDocsOpts = {}) {
|
||||
const stop = stopwatch('build-mkdocs');
|
||||
|
||||
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
|
||||
? path.resolve(process.cwd(), mkDocsYmlPath)
|
||||
: await findMkDocsYml(cwd);
|
||||
@@ -77,10 +79,10 @@ export async function buildSite({
|
||||
}
|
||||
if (serve) {
|
||||
// unsure about how SIGHUP is handled here
|
||||
await doServe(mkdocsArgs, serveOpts);
|
||||
await doServe(pythonPath, mkdocsArgs, serveOpts);
|
||||
} else {
|
||||
log.info('Building site...');
|
||||
await doBuild(mkdocsArgs, execOpts);
|
||||
await doBuild(pythonPath, mkdocsArgs, execOpts);
|
||||
let relSiteDir;
|
||||
if (siteDir) {
|
||||
relSiteDir = relative(cwd, siteDir);
|
||||
|
||||
@@ -79,7 +79,10 @@ export async function checkMissingPaths<T extends Record<string, Options>>(
|
||||
|
||||
if (missingPaths.length) {
|
||||
return missingPaths
|
||||
.map(({id, path}) => `Path specified using --${_.kebabCase(id)} (${path}) does not exist`)
|
||||
.map(
|
||||
({id, path}) =>
|
||||
`Default or specified path via --${_.kebabCase(id)} (${path}) does not exist`
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ const opts = {
|
||||
remote: {
|
||||
alias: 'r',
|
||||
describe: 'Remote to push to',
|
||||
implies: ['deploy', 'push'],
|
||||
implies: 'push',
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
requiresArg: true,
|
||||
@@ -133,7 +133,7 @@ const opts = {
|
||||
},
|
||||
prefix: {
|
||||
describe: 'Subdirectory within <branch> to commit to',
|
||||
implies: ['deploy', 'branch'],
|
||||
implies: 'branch',
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
@@ -168,9 +168,10 @@ const opts = {
|
||||
},
|
||||
rebase: {
|
||||
describe: 'Rebase <branch> with remote before deploy',
|
||||
implies: ['deploy', 'branch', 'remote'],
|
||||
implies: 'deploy',
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'boolean',
|
||||
defaultDescription: 'true',
|
||||
},
|
||||
serve: {
|
||||
describe: 'Start development server',
|
||||
|
||||
@@ -30,6 +30,7 @@ const opts = {
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
dir: {
|
||||
default: '.',
|
||||
@@ -59,6 +60,7 @@ const opts = {
|
||||
group: InitCommandGroup.MkDocs,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
mkdocs: {
|
||||
default: true,
|
||||
@@ -74,6 +76,7 @@ const opts = {
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
'package-json': {
|
||||
defaultDescription: './package.json',
|
||||
@@ -98,6 +101,7 @@ const opts = {
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'python',
|
||||
},
|
||||
'repo-name': {
|
||||
defaultDescription: '(derived from --repo-url)',
|
||||
@@ -106,6 +110,7 @@ const opts = {
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
'repo-url': {
|
||||
defaultDescription: '(from package.json)',
|
||||
@@ -114,6 +119,7 @@ const opts = {
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
'site-description': {
|
||||
defaultDescription: '(from package.json)',
|
||||
@@ -122,6 +128,7 @@ const opts = {
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
'site-name': {
|
||||
defaultDescription: '(extension package name)',
|
||||
@@ -130,6 +137,7 @@ const opts = {
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'mkdocs',
|
||||
},
|
||||
'tsconfig-json': {
|
||||
defaultDescription: './tsconfig.json',
|
||||
@@ -139,12 +147,14 @@ const opts = {
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'typescript',
|
||||
},
|
||||
typedoc: {
|
||||
default: true,
|
||||
description: 'Create typedoc.json if needed',
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
implies: 'entry-point',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
@@ -154,6 +164,17 @@ const opts = {
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'typedoc',
|
||||
},
|
||||
'entry-point': {
|
||||
alias: 'e',
|
||||
describe: 'Source entry point of extension',
|
||||
group: InitCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: false,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'typedoc',
|
||||
},
|
||||
typescript: {
|
||||
default: true,
|
||||
@@ -167,6 +188,7 @@ const opts = {
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
conflicts: 'force',
|
||||
implies: 'python',
|
||||
},
|
||||
} as const satisfies Record<string, Options>;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const opts = {
|
||||
},
|
||||
'python-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to python 3 executable',
|
||||
description: 'Path to python3 executable',
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
@@ -81,6 +81,15 @@ const opts = {
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
'typedoc-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to typedoc executable',
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
typescript: {
|
||||
default: true,
|
||||
description: 'Validate TypeScript environment',
|
||||
|
||||
@@ -8,19 +8,20 @@
|
||||
|
||||
import {getLogger} from '../logger';
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import {sync as readPkg} from 'read-pkg';
|
||||
import {hideBin} from 'yargs/helpers';
|
||||
import yargs from 'yargs/yargs';
|
||||
import {DEFAULT_LOG_LEVEL, LogLevelMap, NAME_BIN} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {build, init, validate} from './command';
|
||||
import {findConfig} from './config';
|
||||
import {fs} from '@appium/support';
|
||||
import {sync as readPkg} from 'read-pkg';
|
||||
|
||||
const pkg = readPkg({cwd: fs.findRoot(__dirname)});
|
||||
|
||||
const log = getLogger('cli');
|
||||
const IMPLICATIONS_FAILED_REGEX = /implications\s+failed:\n\s*(.+)\s->\s(.+)$/i;
|
||||
|
||||
export async function main(argv = hideBin(process.argv)) {
|
||||
const config = await findConfig(argv);
|
||||
|
||||
@@ -70,12 +71,34 @@ export async function main(argv = hideBin(process.argv)) {
|
||||
* Custom failure handler so we can log nicely.
|
||||
*/
|
||||
(msg: string | null, error) => {
|
||||
/**
|
||||
* yargs' default output if an "implication" fails (e.g., arg _A_ requires arg _B_) leaves much to be desired.
|
||||
*
|
||||
* @remarks Unfortunately, we do not have access to the parsed arguments object, since it may have failed parsing.
|
||||
* @param msg Implication failure message
|
||||
* @returns Whether the message was an implication failure
|
||||
*/
|
||||
const handleImplicationFailure = (msg: string | null): boolean => {
|
||||
let match: RegExpMatchArray | null | undefined;
|
||||
if (!(match = msg?.match(IMPLICATIONS_FAILED_REGEX))) {
|
||||
return false;
|
||||
}
|
||||
const [, arg, missingArg] = match;
|
||||
log.error(
|
||||
`Argument "--${arg}" requires "--${missingArg}"; note that "--${arg}" may be enabled by default`
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
// if it is a DocutilsError, it has nothing to do with the CLI
|
||||
if (error instanceof DocutilsError) {
|
||||
log.error(error.message);
|
||||
} else {
|
||||
y.showHelp();
|
||||
log.error(`\n\n${msg ?? error.message}`);
|
||||
|
||||
if (!handleImplicationFailure(msg)) {
|
||||
log.error(`\n\n${msg ?? error.message}`);
|
||||
}
|
||||
}
|
||||
y.exit(1, error);
|
||||
}
|
||||
|
||||
+73
-22
@@ -3,28 +3,29 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import findUp from 'find-up';
|
||||
import YAML from 'yaml';
|
||||
import readPkg, {NormalizedPackageJson, PackageJson} from 'read-pkg';
|
||||
import path from 'node:path';
|
||||
import {JsonValue} from 'type-fest';
|
||||
import {fs} from '@appium/support';
|
||||
import findUp from 'find-up';
|
||||
import * as JSON5 from 'json5';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import _pkgDir from 'pkg-dir';
|
||||
import {getLogger} from './logger';
|
||||
import readPkg, {NormalizedPackageJson, PackageJson} from 'read-pkg';
|
||||
import {JsonValue} from 'type-fest';
|
||||
import {Application, TypeDocReader} from 'typedoc';
|
||||
import YAML from 'yaml';
|
||||
import {
|
||||
NAME_TYPEDOC_JSON,
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_PACKAGE_JSON,
|
||||
NAME_MKDOCS,
|
||||
NAME_NPM,
|
||||
NAME_PYTHON,
|
||||
NAME_MIKE,
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_NPM,
|
||||
NAME_PACKAGE_JSON,
|
||||
NAME_PYTHON,
|
||||
NAME_TYPEDOC,
|
||||
NAME_TYPEDOC_JSON,
|
||||
} from './constants';
|
||||
import {DocutilsError} from './error';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml} from './model';
|
||||
import { exec } from 'teen_process';
|
||||
|
||||
const log = getLogger('fs');
|
||||
|
||||
@@ -207,27 +208,77 @@ export function safeWriteFile(filepath: string, content: JsonValue, overwrite =
|
||||
/**
|
||||
* `which` with memoization
|
||||
*/
|
||||
export const cachedWhich = _.memoize(fs.which);
|
||||
|
||||
/**
|
||||
* Finds `mkdocs` executable
|
||||
*/
|
||||
export const whichMkDocs = _.partial(cachedWhich, NAME_MKDOCS);
|
||||
const cachedWhich = _.memoize(fs.which as typeof import('which'));
|
||||
|
||||
/**
|
||||
* Finds `npm` executable
|
||||
*/
|
||||
export const whichNpm = _.partial(cachedWhich, NAME_NPM);
|
||||
export const whichNpm = _.partial(cachedWhich, NAME_NPM, {nothrow: true});
|
||||
|
||||
/**
|
||||
* Finds `python` executable
|
||||
*/
|
||||
export const whichPython = _.partial(cachedWhich, NAME_PYTHON);
|
||||
const whichPython = _.partial(cachedWhich, NAME_PYTHON, {nothrow: true});
|
||||
|
||||
/**
|
||||
* Finds `mike` executable
|
||||
* Finds `python3` executable
|
||||
*/
|
||||
export const whichMike = _.partial(cachedWhich, NAME_MIKE);
|
||||
const whichPython3 = _.partial(cachedWhich, `${NAME_PYTHON}3`, {nothrow: true});
|
||||
|
||||
/**
|
||||
* Finds `typedoc` executable
|
||||
*/
|
||||
const whichTypeDoc = _.partial(cachedWhich, NAME_TYPEDOC, {nothrow: true});
|
||||
|
||||
/**
|
||||
* `mike` cannot be invoked via `python -m`, so we need to find the script.
|
||||
*/
|
||||
export const findMike = _.partial(async () => {
|
||||
// see if it's in PATH
|
||||
let mikePath = await cachedWhich(NAME_MIKE, {nothrow: true});
|
||||
if (mikePath) {
|
||||
return mikePath;
|
||||
}
|
||||
// if it isn't, it may be in a user dir
|
||||
const pythonPath = await findPython();
|
||||
if (!pythonPath) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// the user dir can be found this way.
|
||||
// usually it's something like ~/.local
|
||||
const {stdout} = await exec(pythonPath, ['-m', 'site', '--user-base']);
|
||||
if (stdout) {
|
||||
mikePath = path.join(stdout.trim(), 'bin', 'mike');
|
||||
if (await fs.isExecutable(mikePath)) {
|
||||
return mikePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the `typedoc` executable.
|
||||
*
|
||||
* Looks in the `node_modules/.bin` dir from the current working directory, and if this fails, in the `node_modules` dir for the file which `.bin/typedoc` should be a symlink of, and if _that_ fails, try the `PATH`.
|
||||
*/
|
||||
export const findTypeDoc = _.memoize(async (cwd = process.cwd()): Promise<string | undefined> => {
|
||||
// .cmd is for win32, of course. note that glob _always_ uses posix dir separators.
|
||||
const globResult =
|
||||
(await fs.glob('node_modules/.bin/typedoc?(.cmd)', {cwd, nodir: true})) ??
|
||||
(await fs.glob('node_modules/**/typedoc/bin/typedoc', {cwd, nodir: true, follow: true}));
|
||||
return _.first(globResult) ?? (await whichTypeDoc());
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the `python3` or `python` executable in the user's `PATH`.
|
||||
*
|
||||
* `python3` is preferred over `python`, since the latter could be Python 2.
|
||||
*/
|
||||
export const findPython = _.memoize(
|
||||
async (): Promise<string | undefined> => (await whichPython3()) ?? (await whichPython())
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads an `mkdocs.yml` file, merges inherited configs, and returns the result. The result is cached.
|
||||
|
||||
@@ -11,16 +11,17 @@ import {
|
||||
NAME_PYTHON,
|
||||
REQUIREMENTS_TXT_PATH,
|
||||
NAME_TYPEDOC_JSON,
|
||||
NAME_PACKAGE_JSON,
|
||||
} from './constants';
|
||||
import YAML from 'yaml';
|
||||
import {exec} from 'teen_process';
|
||||
import {Simplify} from 'type-fest';
|
||||
import {PackageJson, Simplify} from 'type-fest';
|
||||
import {DocutilsError} from './error';
|
||||
import {createScaffoldTask, ScaffoldTaskOptions} from './scaffold';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml, TsConfigJson, TypeDocJson} from './model';
|
||||
import _ from 'lodash';
|
||||
import {stringifyJson5, stringifyYaml} from './fs';
|
||||
import {findPython, stringifyJson5, stringifyYaml} from './fs';
|
||||
|
||||
/**
|
||||
* Data for the base `mkdocs.yml` file
|
||||
@@ -54,9 +55,19 @@ const BASE_TSCONFIG_JSON: Readonly<TsConfigJson> = Object.freeze({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Data for the base `package.json` file.
|
||||
* We expect `package.json` to exist, and we are not in the business of creating it.
|
||||
* However, we will need to add a `typedoc.entryPoint` prop to it.
|
||||
*/
|
||||
const BASE_PACKAGE_JSON: Readonly<PackageJson> = Object.freeze({});
|
||||
|
||||
const log = getLogger('init');
|
||||
const dryRunLog = getLogger('dry-run', log);
|
||||
|
||||
/**
|
||||
* Files included, by default, in `tsconfig.json`
|
||||
*/
|
||||
const DEFAULT_INCLUDE = ['lib', 'test', 'index.js'];
|
||||
/**
|
||||
* Function which scaffolds a `tsconfig.json` file
|
||||
@@ -97,6 +108,26 @@ export const initTypeDocJson = createScaffoldTask<InitTypeDocOptions, TypeDocJso
|
||||
'TypeDoc configuration'
|
||||
);
|
||||
|
||||
/**
|
||||
* Function which scaffolds a `package.json` file
|
||||
*
|
||||
* This only amends prop `typedoc.entryPoint` to the `package.json` file.
|
||||
*
|
||||
* If, strangely, `package.json` did not exist, then it will now contain _only_ that prop.
|
||||
*/
|
||||
export const initTypeDocPkgJson = createScaffoldTask<InitTypeDocOptions, PackageJson>(
|
||||
NAME_PACKAGE_JSON,
|
||||
BASE_PACKAGE_JSON,
|
||||
'Package configuration for TypeDoc',
|
||||
{
|
||||
transform: (content, opts) =>
|
||||
({
|
||||
...content,
|
||||
typedoc: {entryPoint: opts.typeDocEntryPoint},
|
||||
} as PackageJson),
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Function which scaffolds an `mkdocs.yml` file
|
||||
*/
|
||||
@@ -158,10 +189,12 @@ export const initMkDocs = createScaffoldTask<InitMkDocsOptions, MkDocsYml>(
|
||||
* @param opts Options
|
||||
*/
|
||||
export async function initPython({
|
||||
pythonPath = NAME_PYTHON,
|
||||
pythonPath,
|
||||
dryRun = false,
|
||||
upgrade = false,
|
||||
}: InitPythonOptions = {}): Promise<void> {
|
||||
pythonPath = pythonPath ?? (await findPython()) ?? NAME_PYTHON;
|
||||
|
||||
const args = ['-m', 'pip', 'install', '-r', REQUIREMENTS_TXT_PATH];
|
||||
if (upgrade) {
|
||||
args.push('--upgrade');
|
||||
@@ -222,6 +255,7 @@ export async function init({
|
||||
pythonPath,
|
||||
upgrade,
|
||||
typedocJson: typeDocJsonPath,
|
||||
entryPoint: typeDocEntryPoint,
|
||||
}: InitOptions = {}): Promise<void> {
|
||||
if (!typescript && typedoc) {
|
||||
log.warn(
|
||||
@@ -248,6 +282,13 @@ export async function init({
|
||||
dryRun,
|
||||
cwd,
|
||||
});
|
||||
await initTypeDocPkgJson({
|
||||
packageJson: packageJsonPath,
|
||||
overwrite: true, // <-- always overwrite
|
||||
dryRun,
|
||||
cwd,
|
||||
typeDocEntryPoint,
|
||||
});
|
||||
}
|
||||
|
||||
if (python) {
|
||||
@@ -269,7 +310,10 @@ export async function init({
|
||||
}
|
||||
}
|
||||
|
||||
export type InitTypeDocOptions = ScaffoldTaskOptions;
|
||||
export interface InitTypeDocOptions extends ScaffoldTaskOptions {
|
||||
typeDocEntryPoint?: string;
|
||||
}
|
||||
|
||||
export interface InitTsConfigOptions extends ScaffoldTaskOptions {
|
||||
/**
|
||||
* List of source files (globs supported); typically `src` or `lib`
|
||||
@@ -332,5 +376,10 @@ export type InitOptions = Simplify<
|
||||
* If `true`, upgrade only
|
||||
*/
|
||||
upgrade?: boolean;
|
||||
|
||||
/**
|
||||
* Path to entry point of extension (source; not "main" field)
|
||||
*/
|
||||
entryPoint?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import figures from 'figures';
|
||||
import logSymbols from 'log-symbols';
|
||||
import chalk, {ForegroundColor, BackgroundColor} from 'chalk';
|
||||
import chalk, {type BackgroundColor, type ForegroundColor} from 'chalk';
|
||||
import consola, {
|
||||
logType as LogType,
|
||||
ConsolaReporterLogObject,
|
||||
type Consola,
|
||||
type ConsolaReporterLogObject,
|
||||
FancyReporter,
|
||||
FancyReporterOptions,
|
||||
Consola,
|
||||
ConsolaOptions,
|
||||
LogLevel,
|
||||
type FancyReporterOptions,
|
||||
type LogLevel,
|
||||
type logType as LogType,
|
||||
} from 'consola';
|
||||
import {DEFAULT_LOG_LEVEL, LogLevelMap} from './constants';
|
||||
import figures from 'figures';
|
||||
import _ from 'lodash';
|
||||
import logSymbols from 'log-symbols';
|
||||
import {DEFAULT_LOG_LEVEL, LogLevelMap} from './constants';
|
||||
|
||||
/**
|
||||
* This is a reporter for `consola` which uses some extra/custom icons and colors.
|
||||
|
||||
@@ -92,6 +92,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
const relativeDest = relativePath(dest);
|
||||
log.debug('Initializing %s', relativeDest);
|
||||
let shouldWriteDest = false;
|
||||
let isNew = false;
|
||||
let destContent: T;
|
||||
let result: ScaffoldTaskResult<T>;
|
||||
try {
|
||||
@@ -105,6 +106,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
shouldWriteDest = true;
|
||||
log.debug('Creating new file %s', relativeDest);
|
||||
destContent = {} as T;
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
const defaults: T = transform(defaultContent, opts, pkg);
|
||||
@@ -113,7 +115,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
shouldWriteDest = shouldWriteDest || !_.isEqual(destContent, finalDestContent);
|
||||
|
||||
if (shouldWriteDest) {
|
||||
log.info('Changes needed to %s', relativeDest);
|
||||
log.info('Changes needed in %s', relativeDest);
|
||||
log.debug('Original %s: %O', relativeDest, destContent);
|
||||
log.debug('Final %s: %O', relativeDest, finalDestContent);
|
||||
const patch = makePatch(dest, destContent, finalDestContent, serialize);
|
||||
@@ -126,6 +128,11 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
|
||||
try {
|
||||
await safeWriteFile(dest, finalDestContent, overwrite);
|
||||
if (isNew) {
|
||||
log.success('Initialized %s', description);
|
||||
} else {
|
||||
log.success('Updated %s', description);
|
||||
}
|
||||
} catch (e) {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
// this should only be thrown if `force` is false
|
||||
@@ -139,9 +146,9 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info('No changes to %s', relativeDest);
|
||||
log.info('No changes necessary for %s', relativeDest);
|
||||
}
|
||||
log.success('Initialized %s', description);
|
||||
log.success(`${description}: done`);
|
||||
return {path: dest, content: finalDestContent};
|
||||
};
|
||||
}
|
||||
@@ -153,7 +160,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
|
||||
export type ScaffoldTaskTransformer<Opts extends ScaffoldTaskOptions, T extends JsonValue> = (
|
||||
content: Readonly<T>,
|
||||
opts: TaskSpecificOpts<Opts>,
|
||||
pkg: NormalizedPackageJson
|
||||
pkg: Readonly<NormalizedPackageJson>
|
||||
) => T;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import _ from 'lodash';
|
||||
import {SpawnOptions, spawn} from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import type {SubProcess} from 'teen_process';
|
||||
|
||||
/**
|
||||
* Computes a relative path, prepending `./`
|
||||
@@ -26,7 +25,7 @@ export function stopwatch(id: string) {
|
||||
const start = Date.now();
|
||||
stopwatch.cache.set(id, start);
|
||||
return () => {
|
||||
const result = Date.now() - stopwatch.cache.get(id)!;
|
||||
const result = Date.now() - (stopwatch.cache.get(id) ?? 0);
|
||||
stopwatch.cache.delete(id);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -31,14 +31,14 @@ import {
|
||||
} from './constants';
|
||||
import {DocutilsError} from './error';
|
||||
import {
|
||||
findPkgDir,
|
||||
findMkDocsYml,
|
||||
findPkgDir,
|
||||
findTypeDoc,
|
||||
readJson5,
|
||||
readTypedocJson,
|
||||
whichMkDocs,
|
||||
whichNpm,
|
||||
whichPython,
|
||||
readMkDocsYml,
|
||||
readTypedocJson,
|
||||
whichNpm,
|
||||
findPython,
|
||||
} from './fs';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml, PipPackage, TypeDocJson} from './model';
|
||||
@@ -62,7 +62,7 @@ const TYPEDOC_VERSION_REGEX = /TypeDoc\s(\d+\.\d+\..+)/;
|
||||
/**
|
||||
* Matches the MkDocs version string from `mkdocs --version`
|
||||
*/
|
||||
const MKDOCS_VERSION_REGEX = /mkdocs,\s+version\s+(\d+\.\d+\.\S+)/;
|
||||
const MKDOCS_VERSION_REGEX = /\s+version\s+(\d+\.\d+\.\S+)/;
|
||||
|
||||
const log = getLogger('validate');
|
||||
|
||||
@@ -95,12 +95,12 @@ export class DocutilsValidator extends EventEmitter {
|
||||
/**
|
||||
* Path to `npm` executable.
|
||||
*/
|
||||
protected readonly npmPath: string | undefined;
|
||||
protected readonly npmPath?: string;
|
||||
|
||||
/**
|
||||
* Path to `python` executable.
|
||||
*/
|
||||
protected readonly pythonPath: string | undefined;
|
||||
protected readonly pythonPath?: string;
|
||||
|
||||
/**
|
||||
* List of validations to perform
|
||||
@@ -120,27 +120,29 @@ export class DocutilsValidator extends EventEmitter {
|
||||
/**
|
||||
* Path to `mkdocs.yml`. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected mkDocsYmlPath: string | undefined;
|
||||
protected mkDocsYmlPath?: string;
|
||||
|
||||
/**
|
||||
* Path to `package.json`. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected packageJsonPath: string | undefined;
|
||||
protected packageJsonPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the package directory. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected pkgDir: string | undefined;
|
||||
protected pkgDir?: string;
|
||||
|
||||
/**
|
||||
* Path to `tsconfig.json`. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected tsconfigJsonPath: string | undefined;
|
||||
protected tsconfigJsonPath?: string;
|
||||
|
||||
/**
|
||||
* Path to `typedoc.json`. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected typeDocJsonPath: string | undefined;
|
||||
protected typeDocJsonPath?: string;
|
||||
|
||||
protected typeDocPath?: string;
|
||||
|
||||
/**
|
||||
* Emitted when validation begins with a list of validation kinds to be performed
|
||||
@@ -181,6 +183,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
this.typeDocJsonPath = opts.typedocJson;
|
||||
this.npmPath = opts.npm;
|
||||
this.mkDocsYmlPath = opts.mkdocsYml;
|
||||
this.typeDocPath = opts.typedocPath;
|
||||
|
||||
if (opts.python) {
|
||||
this.validations.add(NAME_PYTHON);
|
||||
@@ -314,21 +317,19 @@ export class DocutilsValidator extends EventEmitter {
|
||||
* Validates that the correct version of `mkdocs` is installed
|
||||
*/
|
||||
protected async validateMkDocs() {
|
||||
let mkDocsPath: string | undefined;
|
||||
try {
|
||||
mkDocsPath = await whichMkDocs();
|
||||
} catch {
|
||||
// _pretty sure_ the exception code is always ENOENT
|
||||
const pythonPath = this.pythonPath ?? (await findPython());
|
||||
|
||||
if (!pythonPath) {
|
||||
return this.fail(
|
||||
`Could not find ${NAME_MKDOCS} executable in PATH. If it is installed, check your PATH environment variable.`
|
||||
`Could not find ${NAME_PYTHON} executable in PATH. If it is installed, check your PATH environment variable.`
|
||||
);
|
||||
}
|
||||
|
||||
let rawMkDocsVersion: string | undefined;
|
||||
try {
|
||||
({stdout: rawMkDocsVersion} = await exec(mkDocsPath, ['--version']));
|
||||
({stdout: rawMkDocsVersion} = await exec(pythonPath, ['-m', NAME_MKDOCS, '--version']));
|
||||
} catch (err) {
|
||||
return this.fail(`${mkDocsPath} --version failed: ${err}`);
|
||||
return this.fail(`Failed to get MkDocs version: ${err}`);
|
||||
}
|
||||
const match = rawMkDocsVersion.match(MKDOCS_VERSION_REGEX);
|
||||
if (match) {
|
||||
@@ -343,12 +344,12 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const {version: mkDocsReqdVersion} = mkDocsPipPkg;
|
||||
if (version !== mkDocsReqdVersion) {
|
||||
return this.fail(
|
||||
`${NAME_MKDOCS} at ${mkDocsPath} is v${version}, but ${REQUIREMENTS_TXT_PATH} requires v${mkDocsReqdVersion}`
|
||||
`${NAME_MKDOCS} is v${version}, but ${REQUIREMENTS_TXT_PATH} requires v${mkDocsReqdVersion}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new DocutilsError(
|
||||
`Could not parse version from "${mkDocsPath} --version". This is a bug. Output was ${rawMkDocsVersion}`
|
||||
`Could not parse version from MkDocs. This is a bug. Output was ${rawMkDocsVersion}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -423,7 +424,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
*/
|
||||
protected async validatePythonDeps() {
|
||||
let pipListOutput: string;
|
||||
const pythonPath = this.pythonPath ?? (await whichPython());
|
||||
const pythonPath = this.pythonPath ?? (await findPython());
|
||||
if (!pythonPath) {
|
||||
return this.fail(`Could not find ${NAME_PYTHON} in PATH. Is it installed?`);
|
||||
}
|
||||
@@ -499,7 +500,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
* Asserts that the Python version is 3.x
|
||||
*/
|
||||
protected async validatePythonVersion() {
|
||||
const pythonPath = this.pythonPath ?? (await whichPython());
|
||||
const pythonPath = this.pythonPath ?? (await findPython());
|
||||
if (!pythonPath) {
|
||||
return this.fail(`Could not find ${NAME_PYTHON} in PATH. Is it installed?`);
|
||||
}
|
||||
@@ -520,17 +521,30 @@ export class DocutilsValidator extends EventEmitter {
|
||||
/**
|
||||
* Asserts TypeDoc is installed, runnable, the correct version, and that the config file is readable
|
||||
* and constaints required options
|
||||
*
|
||||
* @todo Another option would be to `npm exec typedoc@<version>` which delegates to `npx`.
|
||||
*/
|
||||
protected async validateTypeDoc() {
|
||||
const pkgDir = await this.findPkgDir();
|
||||
const typeDocPath = this.typeDocPath ?? (await findTypeDoc(pkgDir));
|
||||
|
||||
if (!typeDocPath) {
|
||||
return this.fail(`Could not find ${NAME_TYPEDOC}; is it installed?`);
|
||||
}
|
||||
log.debug('Found %s at %s', NAME_TYPEDOC, typeDocPath);
|
||||
|
||||
let rawTypeDocVersion: string;
|
||||
let typeDocVersion: string;
|
||||
try {
|
||||
({stdout: rawTypeDocVersion} = await exec('npm', ['exec', NAME_TYPEDOC, '--', '--version'], {
|
||||
({stdout: rawTypeDocVersion} = await exec(process.execPath, [typeDocPath, '--version'], {
|
||||
cwd: pkgDir,
|
||||
}));
|
||||
} catch {
|
||||
return this.fail(`Could not find ${NAME_TYPEDOC} executable from ${pkgDir}`);
|
||||
} catch (err) {
|
||||
return this.fail(
|
||||
`Could not execute ${process.execPath} ${typeDocPath} from ${pkgDir}. Reason: ${
|
||||
(err as Error).message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
if (rawTypeDocVersion) {
|
||||
@@ -543,7 +557,8 @@ export class DocutilsValidator extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
const reqdTypeDocVersion = DOCUTILS_PKG.dependencies!.typedoc!;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const reqdTypeDocVersion = DOCUTILS_PKG.dependencies!.typedoc!; // this is our own package.json
|
||||
if (!satisfies(typeDocVersion, reqdTypeDocVersion)) {
|
||||
return this.fail(
|
||||
`Found TypeDoc version ${typeDocVersion}, but ${reqdTypeDocVersion} is required`
|
||||
@@ -596,7 +611,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
if (!typeDocJson.out) {
|
||||
return this.fail(
|
||||
new DocutilsError(
|
||||
`Missing "out" property in ${relTypeDocJsonPath}; path "${DEFAULT_REL_TYPEDOC_OUT_PATH} is recommended`
|
||||
`Missing "out" property in ${relTypeDocJsonPath}; path "${DEFAULT_REL_TYPEDOC_OUT_PATH}" is recommended`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -714,6 +729,10 @@ export interface DocutilsValidatorOpts {
|
||||
* If `true`, run TypeDoc validation
|
||||
*/
|
||||
typedoc?: boolean;
|
||||
/**
|
||||
* Path to `typedoc` executable
|
||||
*/
|
||||
typedocPath?: string;
|
||||
/**
|
||||
* Path to `typedoc.json`
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.3.10",
|
||||
"version": "0.4.7",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -28,7 +28,7 @@
|
||||
"lib": "lib"
|
||||
},
|
||||
"bin": {
|
||||
"appium-docs": "./build/lib/cli/index.js"
|
||||
"appium-docs": "./bin/appium-docs.js"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
@@ -49,10 +49,11 @@
|
||||
"start": "node ./build/lib/cli/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.0.0",
|
||||
"@appium/support": "^4.1.5",
|
||||
"@appium/tsconfig": "^0.3.0",
|
||||
"@appium/typedoc-plugin-appium": "^0.6.4",
|
||||
"@appium/typedoc-plugin-appium": "^0.6.6",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"@types/which": "3.0.0",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
@@ -64,15 +65,15 @@
|
||||
"log-symbols": "4.1.0",
|
||||
"pkg-dir": "5.0.0",
|
||||
"read-pkg": "5.2.0",
|
||||
"semver": "7.5.0",
|
||||
"semver": "7.5.4",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.10.0",
|
||||
"teen_process": "2.0.4",
|
||||
"type-fest": "3.13.1",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.9.5",
|
||||
"yaml": "2.2.2",
|
||||
"yaml": "2.3.1",
|
||||
"yargs": "17.7.2",
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mkdocs==1.4.3
|
||||
mkdocs-material==9.1.9
|
||||
mkdocs==1.5.2
|
||||
mkdocs-material==9.1.21
|
||||
mkdocs-material-extensions==1.1.1
|
||||
mike==1.1.2
|
||||
|
||||
@@ -3,6 +3,71 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.13](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.12...@appium/driver-test-support@0.4.13) (2023-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **driver-test-support:** update dependency @types/lodash to v4.14.197 ([a080b72](https://github.com/appium/appium/commit/a080b729208b4e3c352456f0f230b63b0b7ee9ee))
|
||||
|
||||
|
||||
|
||||
## [0.4.12](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.11...@appium/driver-test-support@0.4.12) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **driver-test-support:** update definitelytyped ([c320e8a](https://github.com/appium/appium/commit/c320e8a1a61cb6d980d6d944ae47b60da97aa398))
|
||||
* **test-support:** update dependency sinon to v15.2.0 ([a21ae6a](https://github.com/appium/appium/commit/a21ae6ab2bcaba44ecdacae67076cf011b4e5ae8))
|
||||
* **types:** update dependency type-fest to v3.13.1 ([fb34ab9](https://github.com/appium/appium/commit/fb34ab917216121d2b554677a12f07a03393d218))
|
||||
|
||||
|
||||
|
||||
## [0.4.11](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.10...@appium/driver-test-support@0.4.11) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **test-support:** update dependency sinon to v15.1.2 ([e5b569c](https://github.com/appium/appium/commit/e5b569c3a0a0545210f126822031420d236ad1f4))
|
||||
|
||||
|
||||
|
||||
## [0.4.10](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.9...@appium/driver-test-support@0.4.10) (2023-07-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/driver-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.9](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.8...@appium/driver-test-support@0.4.9) (2023-06-29)
|
||||
|
||||
**Note:** Version bump only for package @appium/driver-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.8](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.7...@appium/driver-test-support@0.4.8) (2023-06-15)
|
||||
|
||||
**Note:** Version bump only for package @appium/driver-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.7](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.6...@appium/driver-test-support@0.4.7) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **schema:** update definitelytyped ([e967240](https://github.com/appium/appium/commit/e96724077ef2b5b8aae203856195f3bb8de56116))
|
||||
* **test-support:** update dependency sinon to v15.1.0 ([4c61d53](https://github.com/appium/appium/commit/4c61d533b8f52131a637f5cd0b019375f85f075f))
|
||||
* **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.4.6](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.5...@appium/driver-test-support@0.4.6) (2023-05-17)
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import B from 'bluebird';
|
||||
import {TEST_HOST, getTestPort, createAppiumURL} from './helpers';
|
||||
import chai from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import {Agent} from 'node:http';
|
||||
|
||||
const should = chai.should();
|
||||
const {expect} = chai;
|
||||
|
||||
/**
|
||||
* Creates some helper functions for E2E tests to manage sessions.
|
||||
@@ -148,6 +150,9 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
|
||||
describe('session handling', function () {
|
||||
it('should handle idempotency while creating sessions', async function () {
|
||||
// workaround for https://github.com/node-fetch/node-fetch/issues/1735
|
||||
const httpAgent = new Agent({keepAlive: true});
|
||||
|
||||
const sessionIds = [];
|
||||
let times = 0;
|
||||
do {
|
||||
@@ -159,11 +164,7 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
headers: {
|
||||
'X-Idempotency-Key': '123456',
|
||||
},
|
||||
// XXX: I'm not sure what these are, as they are not documented axios options,
|
||||
// nor are they mentioned in our source
|
||||
// @ts-expect-error
|
||||
simple: false,
|
||||
resolveWithFullResponse: true,
|
||||
httpAgent,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -178,6 +179,9 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
});
|
||||
|
||||
it('should handle idempotency while creating parallel sessions', async function () {
|
||||
// workaround for https://github.com/node-fetch/node-fetch/issues/1735
|
||||
const httpAgent = new Agent({keepAlive: true});
|
||||
|
||||
const reqs = [];
|
||||
let times = 0;
|
||||
do {
|
||||
@@ -192,6 +196,7 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
headers: {
|
||||
'X-Idempotency-Key': '12345',
|
||||
},
|
||||
httpAgent,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -396,22 +401,49 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
});
|
||||
|
||||
describe('event timings', function () {
|
||||
it('should not add timings if not using opt-in cap', async function () {
|
||||
const session = await startSession({capabilities: {alwaysMatch: defaultCaps}});
|
||||
const res = await getSession(session.sessionId);
|
||||
should.not.exist(res.events);
|
||||
await endSession(session.sessionId);
|
||||
/** @type {NewSessionResponse} */
|
||||
let session;
|
||||
/** @type {SingularSessionData} */
|
||||
let res;
|
||||
|
||||
describe('when not provided the eventTimings cap', function () {
|
||||
before(async function () {
|
||||
session = await startSession({capabilities: {alwaysMatch: defaultCaps}});
|
||||
res = await getSession(session.sessionId);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
if (session) {
|
||||
await endSession(session.sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not respond with events', function () {
|
||||
expect(res.events).to.be.undefined;
|
||||
});
|
||||
});
|
||||
it('should add start session timings', async function () {
|
||||
const caps = {...defaultCaps, 'appium:eventTimings': true};
|
||||
const session = await startSession({capabilities: {alwaysMatch: caps}});
|
||||
const res = await getSession(session.sessionId);
|
||||
should.exist(res.events);
|
||||
should.exist(res.events?.newSessionRequested);
|
||||
should.exist(res.events?.newSessionStarted);
|
||||
res.events?.newSessionRequested[0].should.be.a('number');
|
||||
res.events?.newSessionStarted[0].should.be.a('number');
|
||||
await endSession(session.sessionId);
|
||||
|
||||
describe('when provided the eventTimings cap', function () {
|
||||
before(async function () {
|
||||
session = await startSession({
|
||||
capabilities: {alwaysMatch: {...defaultCaps, 'appium:eventTimings': true}},
|
||||
});
|
||||
res = await getSession(session.sessionId);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
if (session) {
|
||||
await endSession(session.sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should add a newSessionRequested event', function () {
|
||||
expect(res.events?.newSessionRequested?.[0]).to.be.a('number');
|
||||
});
|
||||
|
||||
it('should add a newSessionStarted event', function () {
|
||||
expect(res.events?.newSessionRequested?.[0]).to.be.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.13",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,18 +41,18 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.11.1",
|
||||
"@types/lodash": "4.14.194",
|
||||
"@appium/types": "^0.13.3",
|
||||
"@types/lodash": "4.14.197",
|
||||
"@types/stoppable": "1.1.1",
|
||||
"axios": "1.4.0",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.7",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "15.0.4",
|
||||
"sinon": "15.2.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"stoppable": "1.1.0",
|
||||
"type-fest": "3.10.0"
|
||||
"type-fest": "3.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.0.4](https://github.com/appium/appium/compare/@appium/eslint-config-appium@8.0.3...@appium/eslint-config-appium@8.0.4) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **eslint-config-appium:** remove prototype assignment warning ([679865e](https://github.com/appium/appium/commit/679865ed2f1df59baef8c4d884b0a63a9222940e))
|
||||
|
||||
|
||||
|
||||
## [8.0.3](https://github.com/appium/appium/compare/@appium/eslint-config-appium@8.0.2...@appium/eslint-config-appium@8.0.3) (2023-04-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/eslint-config-appium
|
||||
|
||||
@@ -91,18 +91,6 @@ module.exports = {
|
||||
'require-atomic-updates': 0,
|
||||
'no-prototype-builtins': 1,
|
||||
'no-redeclare': 1,
|
||||
'no-restricted-syntax': [
|
||||
'warn',
|
||||
{
|
||||
selector: 'AssignmentExpression[left.object.property.name="prototype"]',
|
||||
message: 'Avoid assignment to prototype; use class fields, methods or mixins instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'CallExpression[callee.object.name="Object"][callee.property.name="assign"][arguments.0.property.name="prototype"]',
|
||||
message: 'Avoid assignment to prototype; use class fields, methods or mixins instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium",
|
||||
"version": "8.0.3",
|
||||
"version": "8.0.4",
|
||||
"description": "Shared ESLint config for Appium projects",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.16](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.15...@appium/execute-driver-plugin@3.0.16) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.32.3 ([9a5c9ae](https://github.com/appium/appium/commit/9a5c9ae55c97ea3e5c604ecbc3f972480d091ba9))
|
||||
|
||||
|
||||
|
||||
## [3.0.15](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.14...@appium/execute-driver-plugin@3.0.15) (2023-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.32.0 ([b9151a5](https://github.com/appium/appium/commit/b9151a573b6a8b0a5194658882ad59cca1a5f8b5))
|
||||
|
||||
|
||||
|
||||
## [3.0.14](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.13...@appium/execute-driver-plugin@3.0.14) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency vm2 to v3.9.19 ([8594d13](https://github.com/appium/appium/commit/8594d13bf6a1cb59bc76cbe2d39976ae86cdb3b2))
|
||||
|
||||
|
||||
|
||||
## [3.0.13](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.12...@appium/execute-driver-plugin@3.0.13) (2023-05-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "3.0.13",
|
||||
"version": "3.0.16",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -42,8 +42,8 @@
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.17",
|
||||
"webdriverio": "7.31.1"
|
||||
"vm2": "3.9.19",
|
||||
"webdriverio": "7.32.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
|
||||
@@ -3,6 +3,53 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.8](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.7...@appium/fake-driver@5.2.8) (2023-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **universal-xml-plugin:** update dependency xpath to v0.0.33 ([#18952](https://github.com/appium/appium/issues/18952)) ([f2cff24](https://github.com/appium/appium/commit/f2cff24685e96578f17549454de7b7931c13df83))
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.6...@appium/fake-driver@5.2.7) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **universal-xml-plugin:** update dependency @xmldom/xmldom to v0.8.10 ([#18949](https://github.com/appium/appium/issues/18949)) ([12147d5](https://github.com/appium/appium/commit/12147d518fc3ac86b01c3401b9e462b5dca99ca4))
|
||||
|
||||
|
||||
|
||||
## [5.2.6](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.5...@appium/fake-driver@5.2.6) (2023-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** separate the type of opts from initialOpts ([d6cca51](https://github.com/appium/appium/commit/d6cca5175c3e55d4670936c9d216cd3a6610d16b))
|
||||
|
||||
|
||||
|
||||
## [5.2.5](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.4...@appium/fake-driver@5.2.5) (2023-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fake-driver:** update definition of findElOrEls ([598b255](https://github.com/appium/appium/commit/598b255e8d4a2b41cca0ec0c02a54c46946eadb5))
|
||||
* **types,fake-driver:** getLog doesn't necessarily return an array ([00ae162](https://github.com/appium/appium/commit/00ae1625d73305b8840d45a0dc98ac3cc628ac37))
|
||||
* **types:** various fixes for reality ([81bc527](https://github.com/appium/appium/commit/81bc527be6aa54dd30a012156b5752b8b821ac0c))
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.3...@appium/fake-driver@5.2.4) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **universal-xml-plugin:** update dependency @xmldom/xmldom to v0.8.8 ([3bf4060](https://github.com/appium/appium/commit/3bf406015eb9854abbf6cf024e3bd724468c924d))
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.2...@appium/fake-driver@5.2.3) (2023-04-03)
|
||||
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ const ContextsMixin: FakeDriverContextsMixin = {
|
||||
this.appModel.deactivateFrame();
|
||||
} else {
|
||||
const nodes = this.appModel.xpathQuery(`//iframe[@id="${frameId}"]`);
|
||||
if (!nodes.length) {
|
||||
if (!_.isArray(nodes) || _.isEmpty(nodes)) {
|
||||
throw new errors.NoSuchFrameError();
|
||||
}
|
||||
this.appModel.activateFrame(nodes[0]);
|
||||
|
||||
@@ -7,17 +7,82 @@ import {util} from '@appium/support';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
const {W3C_WEB_ELEMENT_IDENTIFIER} = util;
|
||||
|
||||
async function findElOrEls<Ctx = any>(
|
||||
this: FakeDriver<any>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: true,
|
||||
context?: Ctx
|
||||
): Promise<Element[]>;
|
||||
async function findElOrEls<Ctx = any>(
|
||||
this: FakeDriver<any>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: false,
|
||||
context?: Ctx
|
||||
): Promise<Element>;
|
||||
async function findElOrEls<Ctx = any>(
|
||||
this: FakeDriver<any>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: boolean,
|
||||
context?: Ctx
|
||||
): Promise<Element | Element[]> {
|
||||
const qMap = {
|
||||
xpath: 'xpathQuery',
|
||||
id: 'idQuery',
|
||||
'accessibility id': 'idQuery',
|
||||
'class name': 'classQuery',
|
||||
'tag name': 'classQuery',
|
||||
'css selector': 'cssQuery',
|
||||
} as Record<string, keyof FakeDriver['appModel']>;
|
||||
// TODO this error checking should probably be part of MJSONWP?
|
||||
if (!_.includes(_.keys(qMap), strategy)) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
if (selector === 'badsel') {
|
||||
throw new errors.InvalidSelectorError();
|
||||
}
|
||||
const els = this.appModel[qMap[strategy]](selector, context);
|
||||
|
||||
let retval: Element | Element[];
|
||||
if (els.length) {
|
||||
if (mult) {
|
||||
const allEls: Element[] = [];
|
||||
for (const el of els) {
|
||||
allEls.push(this.wrapNewEl(el));
|
||||
}
|
||||
retval = allEls;
|
||||
} else {
|
||||
retval = this.wrapNewEl(els[0]);
|
||||
}
|
||||
} else if (mult) {
|
||||
retval = [];
|
||||
} else {
|
||||
throw new errors.NoSuchElementError();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
interface FakeDriverFindMixin {
|
||||
getExistingElementForNode(node: FakeElement): string | null;
|
||||
wrapNewEl(obj: FakeElement): Element;
|
||||
|
||||
findElOrEls<Many extends boolean, Ctx = any>(
|
||||
findElOrEls(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
many: Many,
|
||||
context?: Ctx
|
||||
): Promise<Many extends true ? Element[] : Element>;
|
||||
mult: true,
|
||||
context?: any
|
||||
): Promise<Element[]>;
|
||||
findElOrEls(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: false,
|
||||
context?: any
|
||||
): Promise<Element>;
|
||||
findElement(strategy: string, selector: string): Promise<Element>;
|
||||
findElements(strategy: string, selector: string): Promise<Element[]>;
|
||||
findElementFromElement(elementId: string, strategy: string, selector: string): Promise<Element>;
|
||||
@@ -56,48 +121,7 @@ const FindMixin: FakeDriverFindMixin = {
|
||||
return {ELEMENT: maxElId, [W3C_WEB_ELEMENT_IDENTIFIER]: maxElId};
|
||||
},
|
||||
|
||||
async findElOrEls<Many extends boolean, Ctx = any>(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
many: Many,
|
||||
context: Ctx
|
||||
): Promise<Many extends true ? Element[] : Element> {
|
||||
const qMap = {
|
||||
xpath: 'xpathQuery',
|
||||
id: 'idQuery',
|
||||
'accessibility id': 'idQuery',
|
||||
'class name': 'classQuery',
|
||||
'tag name': 'classQuery',
|
||||
'css selector': 'cssQuery',
|
||||
} as Record<string, keyof FakeDriver['appModel']>;
|
||||
// TODO this error checking should probably be part of MJSONWP?
|
||||
if (!_.includes(_.keys(qMap), strategy)) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
if (selector === 'badsel') {
|
||||
throw new errors.InvalidSelectorError();
|
||||
}
|
||||
const els = this.appModel[qMap[strategy]](selector, context);
|
||||
|
||||
let retval: Element | Element[];
|
||||
if (els.length) {
|
||||
if (many) {
|
||||
const allEls: Element[] = [];
|
||||
for (const el of els) {
|
||||
allEls.push(this.wrapNewEl(el));
|
||||
}
|
||||
retval = allEls;
|
||||
} else {
|
||||
retval = this.wrapNewEl(els[0]);
|
||||
}
|
||||
} else if (many) {
|
||||
retval = [];
|
||||
} else {
|
||||
throw new errors.NoSuchElementError();
|
||||
}
|
||||
return retval as Many extends true ? Element[] : Element;
|
||||
},
|
||||
findElOrEls,
|
||||
|
||||
/**
|
||||
* This should override whatever's in ExternalDriver
|
||||
|
||||
@@ -8,7 +8,7 @@ const ORIENTATIONS = new Set(['LANDSCAPE', 'PORTRAIT']);
|
||||
interface FakeDriverGeneralMixin {
|
||||
title(): Promise<string>;
|
||||
keys(value: string | string[]): Promise<void>;
|
||||
setGeoLocation(location: Location): Promise<void>;
|
||||
setGeoLocation(location: Location): Promise<Location>;
|
||||
getGeoLocation(): Promise<Location>;
|
||||
getPageSource(): Promise<string>;
|
||||
getOrientation(): Promise<string>;
|
||||
@@ -22,7 +22,7 @@ interface FakeDriverGeneralMixin {
|
||||
doubleClick(): Promise<void>;
|
||||
execute(script: string, args: any[]): Promise<any>;
|
||||
fakeAddition(a: number, b: number, c?: number): Promise<number>;
|
||||
getLog(type: string): Promise<unknown[]>;
|
||||
getLog(type: string): Promise<any>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
@@ -46,6 +46,7 @@ const GeneralMixin: FakeDriverGeneralMixin = {
|
||||
// TODO test this adequately once WD bug is fixed
|
||||
this.appModel.lat = location.latitude;
|
||||
this.appModel.long = location.longitude;
|
||||
return location;
|
||||
},
|
||||
|
||||
async getGeoLocation(this: FakeDriver) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user