mirror of
https://github.com/appium/appium.git
synced 2026-11-03 15:56:15 +08:00
Compare commits
168
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3cc1a874 | ||
|
|
51d6d69ff1 | ||
|
|
8c092b1172 | ||
|
|
b7bd6b426a | ||
|
|
8c6f8fff44 | ||
|
|
55f4bdca0e | ||
|
|
f545a6cde5 | ||
|
|
c7072027d6 | ||
|
|
ed8b65ce72 | ||
|
|
9399654c8a | ||
|
|
1547c7535e | ||
|
|
b18ba50f8e | ||
|
|
24a0efd695 | ||
|
|
2a452cec4b | ||
|
|
8c7acd52a5 | ||
|
|
5bca642efa | ||
|
|
6c451e1f7d | ||
|
|
9ce09845c8 | ||
|
|
4d7d8e4d38 | ||
|
|
a3ad768802 | ||
|
|
b20d0b82a2 | ||
|
|
2fa8b2d0c1 | ||
|
|
b186928e60 | ||
|
|
cc04e6595b | ||
|
|
c46060cae7 | ||
|
|
e7c3536c15 | ||
|
|
45e653151d | ||
|
|
4829984e2e | ||
|
|
46644bc2d1 | ||
|
|
2752df1aae | ||
|
|
daaf86220b | ||
|
|
4a7cab26fa | ||
|
|
07c8660a50 | ||
|
|
04231eb833 | ||
|
|
160238e929 | ||
|
|
bda6746d7b | ||
|
|
a77e5398f6 | ||
|
|
08fc54b536 | ||
|
|
7a1ee2b173 | ||
|
|
9ea95a27a3 | ||
|
|
86ac5f7a2b | ||
|
|
08f0bc8839 | ||
|
|
75f030b35f | ||
|
|
882958575b | ||
|
|
2eab139f18 | ||
|
|
0eb92e493b | ||
|
|
d945b0f2fe | ||
|
|
e5d2422990 | ||
|
|
31b6b2fe7d | ||
|
|
b2b2225aee | ||
|
|
4abe91004d | ||
|
|
2101da3843 | ||
|
|
595545ac58 | ||
|
|
096f0ab2fe | ||
|
|
2605714a0c | ||
|
|
9dc66a2451 | ||
|
|
abd52e3410 | ||
|
|
5f53988aeb | ||
|
|
df61246461 | ||
|
|
990645e296 | ||
|
|
6e41beedae | ||
|
|
6f738d6ead | ||
|
|
a5607c6593 | ||
|
|
87da292f9a | ||
|
|
72b115817c | ||
|
|
cd6fbd5806 | ||
|
|
2b91cfc9c5 | ||
|
|
6267ae33d5 | ||
|
|
3fda104947 | ||
|
|
507c56a63e | ||
|
|
18c408dc72 | ||
|
|
c26af8f852 | ||
|
|
bfef2db517 | ||
|
|
4c0181fb81 | ||
|
|
49d6a81973 | ||
|
|
ce44fc0fcf | ||
|
|
5ab78297e1 | ||
|
|
3764149753 | ||
|
|
21b7aa9b93 | ||
|
|
b67d72e4f3 | ||
|
|
e916a58d93 | ||
|
|
4090b56931 | ||
|
|
d48283cdca | ||
|
|
b2ad5a49db | ||
|
|
89cf01fda3 | ||
|
|
7d7bb7abc3 | ||
|
|
337241b8f6 | ||
|
|
0c62fc00e4 | ||
|
|
f0608a5d01 | ||
|
|
04c44260ac | ||
|
|
9bf9cc70fa | ||
|
|
0c70d27679 | ||
|
|
44422a1e58 | ||
|
|
21dc828306 | ||
|
|
f3a393aed8 | ||
|
|
12cdbb7fdd | ||
|
|
d14a0c5c0b | ||
|
|
74378cca66 | ||
|
|
92f4677b94 | ||
|
|
e2b3bdb09d | ||
|
|
6820f5ec4e | ||
|
|
743c764d5e | ||
|
|
ca1480adcf | ||
|
|
8ea98b1fe9 | ||
|
|
e09e5051c8 | ||
|
|
44e5f1ba05 | ||
|
|
0f735774af | ||
|
|
61de659616 | ||
|
|
07d4a81d27 | ||
|
|
d2a92c8bc1 | ||
|
|
c37f4500c2 | ||
|
|
b6c7533513 | ||
|
|
108b8c0209 | ||
|
|
41289724dc | ||
|
|
58d359d944 | ||
|
|
cf4f533022 | ||
|
|
0eb56a055e | ||
|
|
94dd88fc98 | ||
|
|
39d5cee1b7 | ||
|
|
17a803da27 | ||
|
|
3470ec5101 | ||
|
|
f5bc702c9a | ||
|
|
0668bc45dc | ||
|
|
a815e3be9d | ||
|
|
b5947991cf | ||
|
|
afc15599c0 | ||
|
|
75a9154366 | ||
|
|
8eea316484 | ||
|
|
ef9b99feee | ||
|
|
7dbf3c2127 | ||
|
|
d3c4d40b44 | ||
|
|
6357b30051 | ||
|
|
cfdf898e04 | ||
|
|
159703744f | ||
|
|
7ae4b5cb6a | ||
|
|
887cb449e6 | ||
|
|
c58f32441d | ||
|
|
325f9900cc | ||
|
|
337ec3e7ba | ||
|
|
888aac85ff | ||
|
|
ee0a579ddc | ||
|
|
8d6ffe0932 | ||
|
|
d4b983328a | ||
|
|
ce7b4520a4 | ||
|
|
dc4835f293 | ||
|
|
dc730e7307 | ||
|
|
ec79486df0 | ||
|
|
b5ea03ae85 | ||
|
|
3c6633eaa1 | ||
|
|
bf53db9fac | ||
|
|
fd57ff03a4 | ||
|
|
167fdb18f7 | ||
|
|
50964e0690 | ||
|
|
a2385f08d5 | ||
|
|
500c08f392 | ||
|
|
4655302239 | ||
|
|
5efd62af08 | ||
|
|
27ac2fa125 | ||
|
|
d8d2382327 | ||
|
|
faff3ce347 | ||
|
|
05b981afa2 | ||
|
|
5e047485ee | ||
|
|
b7e905eaf1 | ||
|
|
47de13c0d6 | ||
|
|
edc9a5e7e7 | ||
|
|
6c36683859 | ||
|
|
6db31553db | ||
|
|
de273dd370 |
@@ -7,3 +7,4 @@ sample-code
|
||||
**/*.d.ts
|
||||
**/build-fixtures/**
|
||||
**/*.ts
|
||||
packages/appium/docs/**/assets/**
|
||||
|
||||
+22
-2
@@ -4,7 +4,7 @@
|
||||
version: 3
|
||||
|
||||
project:
|
||||
id: github.com/appium/appium
|
||||
id: appium
|
||||
name: appium
|
||||
|
||||
paths:
|
||||
@@ -35,6 +35,26 @@ targets:
|
||||
path: ./packages/support
|
||||
- type: npm
|
||||
path: ./packages/test-support
|
||||
- type: npm
|
||||
path: ./packages/types
|
||||
- type: npm
|
||||
path: ./packages/schema
|
||||
- type: npm
|
||||
path: ./packages/universal-xmlplugin
|
||||
- type: npm
|
||||
path: ./packages/opencv
|
||||
- type: npm
|
||||
path: ./packages/images-plugin
|
||||
- type: npm
|
||||
path: ./packages/base-plugin
|
||||
- type: npm
|
||||
path: ./packages/relaxed-caps-plugin
|
||||
- type: npm
|
||||
path: ./packages/fake-plugin
|
||||
- type: npm
|
||||
path: ./packages/docutils
|
||||
|
||||
|
||||
|
||||
revision:
|
||||
branch: 2.0
|
||||
branch: master
|
||||
|
||||
@@ -17,7 +17,7 @@ _Put an `x` in the boxes that apply_
|
||||
_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._
|
||||
|
||||
- [ ] I have read the [CONTRIBUTING](https://github.com/appium/appium/blob/master/CONTRIBUTING.md) doc
|
||||
- [ ] I have signed the [CLA](https://cla.js.foundation/appium/appium)
|
||||
- [ ] I have signed the CLA
|
||||
- [ ] Lint and unit tests pass locally with my changes
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have added necessary documentation (if appropriate)
|
||||
|
||||
+11
-13
@@ -16,9 +16,9 @@ jobs:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
@@ -28,22 +28,18 @@ jobs:
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: Run smoke tests
|
||||
uses: boneskull/nodejs-production-test-action@v1
|
||||
with:
|
||||
script: test:smoke
|
||||
workspaces: true
|
||||
quiet: true
|
||||
- name: Run unit & E2E tests
|
||||
- name: Run smoke, unit & E2E tests
|
||||
run: npm run test:ci
|
||||
|
||||
lint:
|
||||
name: Lint & Check Types
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: ESLint
|
||||
run: npm run lint
|
||||
- name: Check types
|
||||
@@ -54,8 +50,10 @@ jobs:
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
- name: Generate docs
|
||||
run: npm run generate-docs
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Appium
|
||||
|
||||
@@ -11,13 +11,14 @@ jobs:
|
||||
name: Run FOSSA Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
with:
|
||||
ref: '2.0'
|
||||
- name: fossa analyze
|
||||
run: |
|
||||
npm install
|
||||
curl -sS -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install-latest.sh | bash
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
- name: Install fossa-cli
|
||||
run: ./scripts/install-fossa.sh -d
|
||||
- name: Run FOSSA analysis
|
||||
fossa analyze
|
||||
fossa test
|
||||
env:
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ module.exports = (wallaby) => {
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js'],
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js', '!**/local_appium_home/**'],
|
||||
workers: {
|
||||
restart: true,
|
||||
},
|
||||
|
||||
@@ -10,10 +10,36 @@
|
||||
[](https://vshymanskyy.github.io/StandWithUkraine/)
|
||||
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. We support simulators (iOS), emulators
|
||||
(Android), and real devices (iOS, Android, Windows, Mac).
|
||||
hybrid, mobile web and desktop apps. Initially created to automate iOS and Android mobile
|
||||
applications Appium has grown to a full-featured platform that provides [WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for the whole set of different mobile and desktop platforms.
|
||||
See [Drivers Maintained By The Appium Team](#drivers-maintained-by-the-appium-team)
|
||||
and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sections below for more details.
|
||||
|
||||
Want to skip straight to the action? Check out our [getting started](/docs/en/about-appium/getting-started.md) doc.
|
||||
:bangbang: Major documentation revision in progress
|
||||
|
||||
: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.
|
||||
|
||||
Appium is in the final stages of a major revision (to version 2.0). As such, the documentation
|
||||
found around the web may not be correct. The current Appium 2.0 documentation is very much in
|
||||
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/2.0/).
|
||||
|
||||
### Requirements
|
||||
|
||||
- macOS, Linux or Windows operating system
|
||||
- Node.js 14+
|
||||
- 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.
|
||||
|
||||
### Server
|
||||
|
||||
To install Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
|
||||
```bash
|
||||
npm install -g appium@next
|
||||
```
|
||||
|
||||
:bangbang: Running `npm install -g appium` would still install Appium 1 because version 2 is in its late beta stage.
|
||||
|
||||
### Drivers
|
||||
|
||||
@@ -75,6 +101,7 @@ your driver into the below list.
|
||||
[appium-youiengine-driver](https://github.com/YOU-i-Labs/appium-youiengine-driver) | Appium You.i Engine Driver is a test automation tool for devices of various platforms running applications built with [You.i Engine](http://www.youi.tv/youi-engine/)
|
||||
[appium-flutter-driver](https://github.com/appium-userland/appium-flutter-driver) | Allows automation of apps built using [Flutter](https://flutter.dev/) framework
|
||||
[appium-tizen-driver](https://github.com/Samsung/appium-tizen-driver) | Allows automation of apps built for devices running [Tizen OS](https://www.tizen.org/)
|
||||
[appium-linux-driver](https://github.com/fantonglang/appium-linux-driver) | Allows automation of Linux applications using [AtSpi2](https://www.freedesktop.org/wiki/Accessibility/AT-SPI2/) framework
|
||||
</details>
|
||||
|
||||
### Plugins
|
||||
@@ -130,6 +157,7 @@ plugin then don't hesitate to create a PR and add a link to your plugin into the
|
||||
Name | Description
|
||||
--- | ---
|
||||
[appium-device-farm](https://github.com/AppiumTestDistribution/appium-device-farm) | This is an Appium plugin designed to manage and create driver session on connected android devices and iOS Simulators.
|
||||
[appium-reporter-plugin](https://github.com/AppiumTestDistribution/appium-reporter-plugin) | This Plugin generates standalone consolidated html report with screenshots. Report can be fetched from appium server, without worrying about heavy lifting such as screenshot capturing, report generation etc.
|
||||
</details>
|
||||
|
||||
### Server Command Line Interface
|
||||
@@ -194,16 +222,9 @@ automation, for mobile and beyond. Finally!
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium Concepts](/docs/en/about-appium/intro.md).
|
||||
|
||||
### Requirements
|
||||
|
||||
Your environment needs to be set up for the particular platforms that you want
|
||||
to run tests on. Each of the drivers above documents the requirements for their
|
||||
particular brand of automation. At a minimum, you will need to be able to run
|
||||
the recent [LTS](https://nodejs.org/en/about/releases/) Node.js version.
|
||||
|
||||
### Get Started
|
||||
|
||||
Check out our [Getting Started](/docs/en/about-appium/getting-started.md) guide
|
||||
Check out our [Getting Started](http://appium.io/docs/en/about-appium/getting-started) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["source-map-support", "@babel/plugin-transform-runtime"],
|
||||
"plugins": ["source-map-support"],
|
||||
"comments": false,
|
||||
"sourceMaps": "both",
|
||||
"env": {
|
||||
|
||||
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
jobs:
|
||||
- job: generate_docs
|
||||
pool:
|
||||
vmImage: "macOS 10.14"
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- script: |
|
||||
# do git stuff
|
||||
npm install
|
||||
brew install hub
|
||||
hub --version
|
||||
git remote add triager https://triager:$GITHUB_TOKEN@github.com/appium/appium
|
||||
git checkout -b generate-docs-$BUILD_SOURCEVERSION
|
||||
git status
|
||||
displayName: Checkout Generate Docs Branch
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
- script: |
|
||||
# Generate the docs
|
||||
npm run generate-docs
|
||||
git add .
|
||||
|
||||
# log the status so we know what is going on, on failure
|
||||
git status -s
|
||||
|
||||
# If docs were generated, make a PR to update the docs
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
git commit -a -n -m 'Update generated docs [ci skip]'
|
||||
git push triager generate-docs-$BUILD_SOURCEVERSION
|
||||
hub pull-request -b "appium:master" -h "appium:generate-docs-$BUILD_SOURCEVERSION" -m "docs: Update auto-generated docs"
|
||||
fi
|
||||
displayName: Generate Docs and Make PR
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
@@ -1,2 +0,0 @@
|
||||
jobs:
|
||||
- template: templates/bundle-template.yml
|
||||
@@ -1,3 +0,0 @@
|
||||
const semver = require('semver');
|
||||
const {version} = require('../../package.json');
|
||||
console.log(semver(version).prerelease[0]); //eslint-disable-line no-console
|
||||
@@ -1,22 +0,0 @@
|
||||
const path = require('path');
|
||||
const {fs, logger} = require('appium-support');
|
||||
const {asyncify} = require('asyncbox');
|
||||
const packageJson = require('../../package.json');
|
||||
|
||||
const log = new logger.getLogger('Create Release Branch:');
|
||||
|
||||
async function setPackageJsonVersion(version = `${process.env.MINOR_BRANCH_NAME}.0-rc.0`) {
|
||||
packageJson.version = version;
|
||||
log.info(`Setting version to: ${version}`);
|
||||
await fs.writeFile(
|
||||
path.resolve(__dirname, '..', '..', 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
asyncify(setPackageJsonVersion);
|
||||
}
|
||||
|
||||
module.exports = setPackageJsonVersion;
|
||||
@@ -1,40 +0,0 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
jobs:
|
||||
- job: bundle_and_upload_appium_zip
|
||||
pool:
|
||||
vmImage: 'macOS 10.14'
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- script: gcc --version
|
||||
displayName: Print GCC Version
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- script: npm install
|
||||
displayName: Install NPM Dependencies
|
||||
- script: npm run build
|
||||
displayName: npm run build
|
||||
- script: npm prune --production
|
||||
displayName: Get rid of dev dependencies
|
||||
- script: |
|
||||
curl -o node-darwin.tar.gz https://nodejs.org/dist/v12.16.1/node-v12.16.1-darwin-x64.tar.gz
|
||||
mkdir node-darwin-unpacked
|
||||
tar -xzf node-darwin.tar.gz -C node-darwin-unpacked/ --strip-components 1
|
||||
rm -f node-darwin.tar.gz
|
||||
cp node-darwin-unpacked/bin/node node-darwin
|
||||
rm -rf node-darwin-unpacked
|
||||
displayName: Download NodeJS
|
||||
- script: npm run zip
|
||||
displayName: Create the bundle zip
|
||||
- script: npm install
|
||||
displayName: Reinstall dev dependencies
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
targetPath: appium.zip
|
||||
artifactName: appium
|
||||
- script: npm run upload
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
displayName: Upload to appium/appium-build-store Github Releases
|
||||
@@ -1,31 +0,0 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
jobs:
|
||||
- job: update_appium_io
|
||||
pool:
|
||||
vmImage: 'Ubuntu-18.04'
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
- script: |
|
||||
pip install mkdocs==0.16.3
|
||||
git config --global user.email "maintainers@appium.io"
|
||||
git config --global user.name "Appium Triager"
|
||||
git clone https://triager:$GITHUB_TOKEN@github.com/appium/appium.io.git --branch=gh-pages
|
||||
pushd appium.io
|
||||
git remote add triager https://triager:$GITHUB_TOKEN@github.com/appium/appium.io
|
||||
git checkout -b docs-$BUILD_SOURCEVERSION
|
||||
bundle install
|
||||
npm install
|
||||
npm run build:docs
|
||||
git add docs
|
||||
git commit -a -n -m "docs: Update documentation for latest version"
|
||||
git push triager docs-$BUILD_SOURCEVERSION
|
||||
hub pull-request -b "appium:gh-pages" -h "appium:docs-$BUILD_SOURCEVERSION" -m "docs: Update documentation for latest version"
|
||||
popd
|
||||
displayName: Make branch with Updated Docs and make PR
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
@@ -1,10 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"appium": ["../packages/appium"]
|
||||
"appium": ["../packages/appium"],
|
||||
"appium/plugin": ["../packages/appium/plugin"],
|
||||
"@appium/plugin-test-support": ["../packages/plugin-test-support"]
|
||||
},
|
||||
"types": ["webdriverio/async"]
|
||||
},
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [{"path": "../packages/appium"}, {"path": "../packages/base-plugin"}, {"path": "../packages/types"}]
|
||||
"references": [
|
||||
{"path": "../packages/appium"},
|
||||
{"path": "../packages/base-plugin"},
|
||||
{"path": "../packages/types"},
|
||||
{"path": "../packages/plugin-test-support"}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
The old Appium 1.x docs used to be in this directory. Now, they are available only on the [1.x
|
||||
branch](https://github.com/appium/appium/tree/1.x/docs).
|
||||
|
||||
Appium 2.x docs are located in [packages/appium/docs](packages/appium/docs) in this repo.
|
||||
Appium 2.x docs are located in [packages/appium/docs](../packages/appium/docs) in this repo.
|
||||
|
||||
Generated
+1823
-1770
File diff suppressed because it is too large
Load Diff
+22
-19
@@ -39,7 +39,6 @@
|
||||
"doctor": "node ./packages/doctor",
|
||||
"generate-docs": "lerna run --scope=appium generate-docs",
|
||||
"prepare": "npm run build:types || true",
|
||||
"install-fake-driver": "npm start -- driver install --source=local ./packages/fake-driver",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"precommit-lint": "lint-staged",
|
||||
@@ -53,9 +52,10 @@
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\"",
|
||||
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
|
||||
"test": "run-s build test:unit lint",
|
||||
"test:ci": "run-s install-fake-driver test:unit test:ci:e2e",
|
||||
"test:ci": "run-s test:smoke test:unit test:ci:e2e",
|
||||
"test:ci:e2e": "lerna run test:e2e",
|
||||
"test:e2e": "lerna run --parallel --max-buffer=128 test:e2e",
|
||||
"test:smoke": "smoker --all test:smoke",
|
||||
"test:unit": "lerna run test",
|
||||
"uninstall-fake-driver": "npm start -- driver uninstall fake",
|
||||
"upload": "gulp github-upload",
|
||||
@@ -71,6 +71,9 @@
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts*": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
@@ -83,6 +86,7 @@
|
||||
"@appium/base-plugin": "file:packages/base-plugin",
|
||||
"@appium/doctor": "file:packages/doctor",
|
||||
"@appium/docutils": "file:packages/docutils",
|
||||
"@appium/driver-test-support": "file:packages/driver-test-support",
|
||||
"@appium/eslint-config-appium": "file:packages/eslint-config-appium",
|
||||
"@appium/execute-driver-plugin": "file:packages/execute-driver-plugin",
|
||||
"@appium/fake-driver": "file:packages/fake-driver",
|
||||
@@ -90,6 +94,7 @@
|
||||
"@appium/gulp-plugins": "file:packages/gulp-plugins",
|
||||
"@appium/images-plugin": "file:packages/images-plugin",
|
||||
"@appium/opencv": "file:packages/opencv",
|
||||
"@appium/plugin-test-support": "file:packages/plugin-test-support",
|
||||
"@appium/relaxed-caps-plugin": "file:packages/relaxed-caps-plugin",
|
||||
"@appium/schema": "file:packages/schema",
|
||||
"@appium/support": "file:packages/support",
|
||||
@@ -101,19 +106,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-plugin": "1.3.1",
|
||||
"@babel/cli": "7.18.9",
|
||||
"@babel/core": "7.18.9",
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@babel/plugin-transform-runtime": "7.18.9",
|
||||
"@babel/preset-env": "7.18.9",
|
||||
"@babel/cli": "7.19.3",
|
||||
"@babel/core": "7.19.3",
|
||||
"@babel/preset-env": "7.19.4",
|
||||
"@babel/register": "7.18.9",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/chai": "4.3.1",
|
||||
"@types/chai": "4.3.3",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "16.11.46",
|
||||
"@types/semver": "7.3.10",
|
||||
"@types/mocha": "10.0.0",
|
||||
"@types/node": "16.11.64",
|
||||
"@types/semver": "7.3.12",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/sinon-chai": "3.2.8",
|
||||
"@types/teen_process": "1.16.1",
|
||||
@@ -124,12 +126,12 @@
|
||||
"chai": "4.3.6",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "9.0.0",
|
||||
"eslint-plugin-promise": "6.0.0",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
"eslint-plugin-promise": "6.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
@@ -139,6 +141,7 @@
|
||||
"lerna": "4.0.0",
|
||||
"lint-staged": "13.0.3",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "1.1.1",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.0.0",
|
||||
@@ -148,19 +151,19 @@
|
||||
"rewiremock": "3.14.3",
|
||||
"rimraf": "3.0.2",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "14.0.0",
|
||||
"sinon": "14.0.1",
|
||||
"sinon-chai": "3.7.0",
|
||||
"sync-monorepo-packages": "0.3.5",
|
||||
"through2": "4.0.2",
|
||||
"typescript": "4.7.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.20.7",
|
||||
"ws": "8.8.1",
|
||||
"webdriverio": "7.25.1",
|
||||
"ws": "8.9.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -3,6 +3,90 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.46](https://github.com/appium/appium/compare/appium@2.0.0-beta.45...appium@2.0.0-beta.46) (2022-10-14)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.45](https://github.com/appium/appium/compare/appium@2.0.0-beta.44...appium@2.0.0-beta.45) (2022-10-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,support:** enable detection of local extensions for improved DX ([b186928](https://github.com/appium/appium/commit/b186928e60493e7603dc3b27725dad8ee20d3750))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.44](https://github.com/appium/appium/compare/appium@2.0.0-beta.43...appium@2.0.0-beta.44) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** do not throw ENAMETOOLONG on long cli arg ([cfdf898](https://github.com/appium/appium/commit/cfdf898e04e94032e3dd48b595caa4b6f0828289))
|
||||
* **appium:** eat update info errors ([7d7bb7a](https://github.com/appium/appium/commit/7d7bb7abc3173d29b843b3b238465d9914eaf091)), closes [#17357](https://github.com/appium/appium/issues/17357)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* **appium:** remove test export ([6820f5e](https://github.com/appium/appium/commit/6820f5ec4e62fbb5e91ce739d8aadb7f7b7a7a74)), closes [#17398](https://github.com/appium/appium/issues/17398)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Adjust NODE_PATH so NPM could properly resolve component peer dependencies ([#17325](https://github.com/appium/appium/issues/17325)) ([39d5cee](https://github.com/appium/appium/commit/39d5cee1b71f611e810900d3faed8e0fed6e1ce0))
|
||||
* **support:** Move module root detection utility into support package ([#17427](https://github.com/appium/appium/issues/17427)) ([5ab7829](https://github.com/appium/appium/commit/5ab78297e172bc6a5751c636f81b3b202fbe2743))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **appium:** This removes the `test` export from `appium`. `import * from 'appium/test'` is no longer supported.
|
||||
|
||||
Use `@appium/test-support`, `@appium/driver-test-support` or `@appium/plugin-test-support` instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.43](https://github.com/appium/appium/compare/appium@2.0.0-beta.42...appium@2.0.0-beta.43) (2022-08-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** fix busted config file normalization algorithm ([dc4835f](https://github.com/appium/appium/commit/dc4835f293f371c36873801a0060bb22eead2cc0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,base-driver,fake-driver,fake-plugin,test-support,types:** updateServer receives cliArgs param ([d4b9833](https://github.com/appium/appium/commit/d4b983328af21d1e5c27a91e438e7934eb152ab1)), closes [#17304](https://github.com/appium/appium/issues/17304)
|
||||
* **base-driver,fake-driver,appium:** add convenience methods for defining execute script overloads ([#17321](https://github.com/appium/appium/issues/17321)) ([337ec3e](https://github.com/appium/appium/commit/337ec3e7ba216dd6f8cdc88143ecaa4c75f5d266))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.42](https://github.com/appium/appium/compare/appium@2.0.0-beta.41...appium@2.0.0-beta.42) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
* **appium:** add npm version check ([27ac2fa](https://github.com/appium/appium/commit/27ac2fa125bbfbe2e2ad5a3e7bf31d518510aa2d))
|
||||
* **appium:** partially revert 2a6a056187ce925d5776b7acc4954b10ecf9221b ([b7e905e](https://github.com/appium/appium/commit/b7e905eaf188f0f23f279620d8ce7dfba60f0b25))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** pass unknown args to extension scripts ([faff3ce](https://github.com/appium/appium/commit/faff3ce3471abaea24d2cb4c3e3b75b1af5ac3a1)), closes [#17250](https://github.com/appium/appium/issues/17250)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.41](https://github.com/appium/appium/compare/appium@2.0.0-beta.40...appium@2.0.0-beta.41) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -2,4 +2,54 @@
|
||||
title: Server CLI Args
|
||||
---
|
||||
|
||||
TODO
|
||||
To start the Appium server, you may either run `appium` or `appium server`. The `server` subcommand
|
||||
is considered to be the default, so if you omit it, Appium will interpret this as your request to
|
||||
run the Appium server. (You could run different subcommands, like `appium driver` or `appium
|
||||
plugin`: see the [Extensions CLI](./extensions.md) for more info).
|
||||
|
||||
The invocation of `appium` (or `appium server`) takes a number of arguments, which are detailed
|
||||
below.
|
||||
|
||||
!!! note
|
||||
|
||||
All of these arguments can be set via a [Configuration File](../guides/config.md) instead if
|
||||
you want. Any arguments set on the command line will override any arguments found in
|
||||
a configuration file.
|
||||
|
||||
|
||||
<!-- WARNING: anything between the AUTOGEN-START and AUTOGEN-STOP tags will get replaced by
|
||||
automatic docs generation tooling! Do not edit by hand! -->
|
||||
<!-- AUTOGEN-START -->
|
||||
|Argument |Description|Type|Default|Aliases|
|
||||
|--|--|--|--|--|
|
||||
|`--address`|IP address to listen on|string|`0.0.0.0`|`-a`|
|
||||
|`--allow-cors`|Whether the Appium server should allow web browser connections from any host|boolean|`false`||
|
||||
|`--allow-insecure`|Set which insecure features are allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Note that features defined via "deny-insecure" will be disabled, even if also listed here. If string, a path to a text file containing policy or a comma-delimited list.|array<string>|`[]`||
|
||||
|`--base-path`|Base path to use as the prefix for all webdriver routes running on the server|string|`""`|`-pa`|
|
||||
|`--callback-address`|Callback IP address (default: same as "address")|string||`-ca`|
|
||||
|`--callback-port`|Callback port (default: same as "port") (Value must be between `1` and `65535`)|integer|`4723`|`-cp`|
|
||||
|`--debug-log-spacing`|Add exaggerated spacing in logs to help with visual inspection|boolean|`false`||
|
||||
|`--default-capabilities`|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities. If a string, a path to a JSON file containing the capabilities, or raw JSON.|object||`-dc`|
|
||||
|`--deny-insecure`|Set which insecure features are not allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Features listed here will not be enabled even if also listed in "allow-insecure", and even if "relaxed-security" is enabled. If string, a path to a text file containing policy or a comma-delimited list.|array<string>|`[]`||
|
||||
|`--driver`|Driver-specific configuration. Keys should correspond to driver package names|object|||
|
||||
|`--keep-alive-timeout`|Number of seconds the Appium server should apply as both the keep-alive timeout and the connection timeout for all requests. A value of 0 disables the timeout.|integer|`600`|`-ka`|
|
||||
|`--local-timezone`|Use local timezone for timestamps|boolean|`false`||
|
||||
|`--log`|Also send log output to this file|string||`-g`|
|
||||
|`--log-filters`|One or more log filtering rules|array<string>|||
|
||||
|`--log-level`|Log level (console[:file]) (Value must be one of: `info`, `info:debug`, `info:info`, `info:warn`, `info:error`, `warn`, `warn:debug`, `warn:info`, `warn:warn`, `warn:error`, `error`, `error:debug`, `error:info`, `error:warn`, `error:error`, `debug`, `debug:debug`, `debug:info`, `debug:warn`, `debug:error`)|string|`debug`||
|
||||
|`--log-no-colors`|Do not use color in console output|boolean|`false`||
|
||||
|`--log-timestamp`|Show timestamps in console output|boolean|`false`||
|
||||
|`--long-stacktrace`|Add long stack traces to log entries. Recommended for debugging only.|boolean|`false`||
|
||||
|`--no-perms-check`|Do not check that needed files are readable and/or writable|boolean|`false`||
|
||||
|`--nodeconfig`|Path to configuration JSON file to register Appium as a node with Selenium Grid 3; otherwise the configuration itself|object|||
|
||||
|`--plugin`|Plugin-specific configuration. Keys should correspond to plugin package names|object|||
|
||||
|`--port`|Port to listen on (Value must be between `1` and `65535`)|integer|`4723`|`-p`|
|
||||
|`--relaxed-security`|Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it's not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using "deny-insecure"|boolean|`false`||
|
||||
|`--session-override`|Enables session override (clobbering)|boolean|`false`||
|
||||
|`--strict-caps`|Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device|boolean|`false`||
|
||||
|`--tmp`|Absolute path to directory Appium can use to manage temp files. Defaults to C:\Windows\Temp on Windows and /tmp otherwise.|string|||
|
||||
|`--trace-dir`|Absolute path to directory Appium can use to save iOS instrument traces; defaults to <tmp>/appium-instruments|string|||
|
||||
|`--use-drivers`|A list of drivers to activate. By default, all installed drivers will be activated.|array<string>|`[]`||
|
||||
|`--use-plugins`|A list of plugins to activate. To activate all plugins, the value should be an array with a single item "all".|array<string>|`[]`||
|
||||
|`--webhook`|Also send log output to this http listener|string||`-G`|
|
||||
<!-- AUTOGEN-STOP -->
|
||||
|
||||
@@ -2,4 +2,196 @@
|
||||
title: The Extension CLI
|
||||
---
|
||||
|
||||
TODO
|
||||
Appium allows for the flexible installation and management of _drivers_ (which provide Appium with
|
||||
the capability to automate a given platform) and *plugins* (which can augment or alter the way
|
||||
individual Appium commands work). For a conceptual understanding of these entities, please review
|
||||
the [Introduction](../intro/index.md). Management of drivers and plugins is handled by Appium's
|
||||
Extension CLI.
|
||||
|
||||
!!! note
|
||||
|
||||
This reference uses placeholders to refer to various options. Anywhere you see one of these
|
||||
placeholders in the reference, ensure you replace it with the correct type of actual content.
|
||||
|
||||
|Placeholder|Meaning|
|
||||
|--|--|
|
||||
|`<ext-type>`|"Extension type". It should be either `driver` or `plugin`. All the Extension CLI commands can be used with either drivers or plugins, so you must specify which type of extension will be used|
|
||||
|`<ext-name>`|"Extension name". This is the short name of the extension found in a call to `appium <ext-type> list`. This is distinct from the NPM package name of the extension or, in general, the "install spec" of the extension.|
|
||||
|`<install-spec>`|"Install specification". This refers to the string used to indicate what extension Appium should install.|
|
||||
|`<install-source>`|This refers to the method that Appium should use to install an extension.|
|
||||
|
||||
## Commands
|
||||
|
||||
All Extension CLI commands begin with `appium <ext-type>`, i.e., either `appium driver` or `appium
|
||||
plugin`.
|
||||
|
||||
All Extension CLI commands can take an optional `--json` argument, which will return the result of
|
||||
the command as a machine-readable JSON string rather than the standard output, which is colourized
|
||||
and tuned for human consumption.
|
||||
|
||||
### `list`
|
||||
|
||||
List installed and available extensions. "Available" extensions include those which are officially
|
||||
recognized by the Appium team, but you are not limited to installing only the extensions displayed
|
||||
in this list.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> list [--installed] [--updates] [--json]
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
- `<ext-type>`: must be `driver` or `plugin`
|
||||
|
||||
Optional arguments:
|
||||
|
||||
- `--installed`: show only installed extensions, not installed plus available extensions
|
||||
- `--updates`: for extensions installed via NPM, display a message if there are any updates
|
||||
- `--json`: return the result in JSON format
|
||||
|
||||
### `install`
|
||||
|
||||
Install an extension. If successful, respond with the short name of the extension which can be used
|
||||
in other invocations of the Extension CLI. If the extension is a driver, also note which platforms
|
||||
may be used with the driver.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> install <install-spec> [--source=<install-source>] [--package=<package-name>] [--json]
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
- `<ext-type>`: must be `driver` or `plugin`
|
||||
- `<install-spec>`: this is the name, location, and/or version of the extension you want to
|
||||
install. Its possible values are dependent on the `<install-source>` (see below).
|
||||
|
||||
Optional arguments:
|
||||
|
||||
- `--source`: this directs Appium where to find your extension. See below for a table of possible
|
||||
source types and corresponding install specification.
|
||||
- `--package`: when `<install-source>` is `git` or `github`, `--package` is required. It should be
|
||||
the Node.js package name of the extension. Without this information, Appium will not be able to
|
||||
find the installed package.
|
||||
- `--json`: return the result in JSON format
|
||||
|
||||
|Install source type|Behaviour|
|
||||
|--|--|
|
||||
|None|This is the default behaviour when no `--source` is used. In this case, Appium will look at `<install-spec>` and match it against the name of extensions available when running `appium <ext-type> list`, i.e., against the officially recognized extension names. If found, it will install that extension at the latest version via NPM|
|
||||
|`npm`|Install an extension based on its NPM package name. Here, `<install-spec>` must be the NPM package name with any additional NPM installation modifiers, like version (see below)|
|
||||
|`github`|Install an extension via a GitHub spec of the form `<org>/<repo>`|
|
||||
|`git`|Install an extension via a Git URL (e.g., `git+ssh://git-host.com/repo.git`)|
|
||||
|`local`|Install an extension via a local path. This must be a path to the directory where the Node.js package information for the driver is located.|
|
||||
|
||||
#### NPM-based `<install-spec>`
|
||||
|
||||
When Appium is installing an extension via NPM (as is the case when `--source` is either omitted or
|
||||
set to `npm`), the `<install-spec>` can be complex, and can include any kind of information allowed
|
||||
by `npm install`:
|
||||
|
||||
- `[@scope]/<name>`
|
||||
- `[@scope]/<name>@<version>`
|
||||
- `[@scope]/<name>@<tag>`
|
||||
- `[@scope]/<name>@<version range>`
|
||||
|
||||
#### Examples
|
||||
|
||||
- Install the latest XCUITest driver:
|
||||
|
||||
```
|
||||
appium driver install xcuitest
|
||||
```
|
||||
|
||||
- Install the XCUITest driver at version 4.11.1:
|
||||
|
||||
```
|
||||
appium driver install xcuitest@4.11.1
|
||||
```
|
||||
|
||||
- Install the `beta` version of the `@appium/fake-driver` from NPM:
|
||||
|
||||
```
|
||||
appium driver install --source=npm @appium/fake-driver@beta
|
||||
```
|
||||
|
||||
- Install a locally-developed plugin:
|
||||
|
||||
```
|
||||
appium plugin install --source=local /path/to/my/plugin
|
||||
```
|
||||
|
||||
### `update`
|
||||
|
||||
Update one or more extensions that have been installed via NPM. By default, Appium will not
|
||||
automatically update any extension that has a revision in its major version, so as to prevent
|
||||
unintended breaking changes.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> update <ext-name> [--unsafe] [--json]
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
- `<ext-type>`: must be `driver` or `plugin`
|
||||
- `<ext-name>`: the name of the extension to update, or the string `installed` (which will update
|
||||
all installed extensions)
|
||||
|
||||
Optional arguments:
|
||||
|
||||
- `--unsafe`: direct Appium to go ahead and update passed a major version boundary
|
||||
- `--json`: return the result in JSON format
|
||||
|
||||
### `uninstall`
|
||||
|
||||
Remove an installed extension.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> uninstall <ext-name> [--json]
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
- `<ext-type>`: must be `driver` or `plugin`
|
||||
- `<ext-name>`: the name of the extension to uninstall
|
||||
|
||||
Optional arguments:
|
||||
|
||||
- `--json`: return the result in JSON format
|
||||
|
||||
### `run`
|
||||
|
||||
Run a script included in an extension package. Extension authors can include runnable scripts that
|
||||
assist with setup or perform other tasks. These scripts are given names (called the `<script-name>`
|
||||
in this reference) by extension authors and will generally be documented in extension
|
||||
documentation.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> run <ext-name> [--json] <script-name> [script-args]
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
- `<ext-type>`: must be `driver` or `plugin`
|
||||
- `<ext-name>`: the name of the extension whose script you want to run
|
||||
- `<script-name>`: the name of the script the extension has published
|
||||
|
||||
Optional arguments:
|
||||
|
||||
* `script-args`: any arguments that Appium does not interpret as belonging to its own set of
|
||||
arguments will be passed along to the extension script
|
||||
- `--json`: return the result in JSON format
|
||||
|
||||
Example (run the `reset` script included with the UiAutomator2 driver):
|
||||
|
||||
```
|
||||
appium driver run uiautomator2 reset
|
||||
```
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
---
|
||||
title: Appium's Config System
|
||||
---
|
||||
|
||||
Appium 2 supports [configuration files](../guides/config.md). A configuration file is intended to
|
||||
have (nearly) 1:1 parity with command-line arguments. An end user can supply Appium 2 with
|
||||
a configuration file, CLI args, or both (the args have precedence over the config file).
|
||||
|
||||
This document will be a technical overview of how the configuration system works. It is intended
|
||||
for Appium contributors, but will also explain the system's fundamental features.
|
||||
|
||||
## Reading a Config File
|
||||
|
||||
A config file is a JSON, JavaScript, or YAML file which can be validated against a schema. By
|
||||
default, this file will be named `.appiumrc.{json,js,yaml,yml}` and should be in the root of the
|
||||
project which depends upon `appium`. Other filenames and locations are supported via the `--config
|
||||
<file>` flag. For obvious reasons, the `config` argument is disallowed within config files.
|
||||
|
||||
In lieu of a separate file, configuration can be embedded in a project's `package.json` using the
|
||||
`appiumConfig` property, e.g.,:
|
||||
|
||||
```json
|
||||
{
|
||||
"appiumConfig": {
|
||||
"server": {
|
||||
"port": 12345
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When an Appium server is started via the `appium` executable, the `init` function in `lib/main.js`
|
||||
will call into `lib/config-file.js` to load and/or search for a configuration file and in
|
||||
`package.json`.
|
||||
|
||||
!!! note
|
||||
|
||||
It is not an error if configuration isn't found!
|
||||
|
||||
The [`lilconfig`](https://npm.im/lilconfig) package provides the search & load functionality; refer
|
||||
to its documentation for more information about the search paths. Additionally, Appium provides
|
||||
support for config files written in YAML via the package [`yaml`](https://npm.im/yaml).
|
||||
|
||||
If a config file is found and successfully [validated](#validation), the result will be merged with
|
||||
a set of defaults and any additionall CLI arguments. CLI arguments have precedence over config
|
||||
files, and config files have precedence over defaults.
|
||||
|
||||
## Validation
|
||||
|
||||
The same system is used for _both_ validation of config files _and_ command-line arguments.
|
||||
|
||||
The package [`ajv`](https://npm.im/ajv) provides validation. Of course, to make `ajv` validate
|
||||
anything, it must be provided a _schema_.
|
||||
|
||||
The _base_ schema is a [JSON Schema
|
||||
Draft-7](https://json-schema.org/draft/2020-12/json-schema-core.html)-compliant object exported by
|
||||
`lib/schema/appium-config-schema.js`. This schema defines configuration _native to Appium_, and
|
||||
only concerns its behavior as a _server_; it does not define configuration for any other
|
||||
functionality (e.g., the `plugin` or `driver` subcommands).
|
||||
|
||||
!!! warning
|
||||
|
||||
Note that this file is the _base_ schema; this will become painfully relevant.
|
||||
|
||||
This file is is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
|
||||
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.
|
||||
|
||||
It is more straightforward to explain how config files are validated, so we'll start there.
|
||||
|
||||
### Validating Config Files
|
||||
|
||||
When a config file is found (`lib/config-file.js`), it will call the `validate` function exported
|
||||
from `lib/schema/schema.js` with the contents of the config file. In turn, this asks `ajv` to
|
||||
validate the data against the schema that Appium has provided it.
|
||||
|
||||
If the config file is invalid, errors will be generated to be displayed to the user. Finally, the
|
||||
`init` function will detect these errors, display them, and the process will exit.
|
||||
|
||||
I hope that made sense, because this is the easy part.
|
||||
|
||||
### Validating CLI Arguments
|
||||
|
||||
As mentioned earlier, the same system is used for validating both config files and CLI arguments.
|
||||
|
||||
Totally not judging, but Appium uses [`argparse`](https://npm.im/argparse) for its CLI argument
|
||||
parsing. This package, and others like it, provides an API to define the arguments a command-line
|
||||
Node.js script accepts, and will ultimately return an object representation of the user-supplied
|
||||
arguments.
|
||||
|
||||
Just as the schema defines what's allowed in a config file, it also defines what's allowed on the
|
||||
command-line.
|
||||
|
||||
#### Defining CLI Arguments via Schema
|
||||
|
||||
CLI arguments must be _defined_ before their values can be validated.
|
||||
|
||||
A JSON schema isn't a natural fit for defining CLI args--it needs some grease to make it work--but
|
||||
it's close enough that we can do so with an adapter and some custom metadata.
|
||||
|
||||
In `lib/cli/parser.js`, there's a wrapper around `argparse`'s `ArgumentParser`; it's called (wait
|
||||
for it)... `ArgParser`. The wrapper exists because we're doing some custom things with `argparse`,
|
||||
but is has nothing to do with the schema directly.
|
||||
|
||||
An `ArgParser` instance is created and its `parseArgs()` method is called with the raw CLI
|
||||
arguments. The definition of the accepted arguments comes from `lib/cli/args.js` in part--here, all
|
||||
of the arguments _not_ intended for use with the `server` subcommand are hard-coded (e.g., the
|
||||
`driver` subcommand and _its_ subcommands). `args.js` also contains a function `getServerArgs()`,
|
||||
which in turn calls into `toParserArgs` in `lib/schema/cli-args.js`. `lib/schema/cli-args.js` can
|
||||
be considered the "adapter" layer between `argparse` and the schema.
|
||||
|
||||
`toParserArgs` uses the `flattenSchema` function exported by `lib/schema/schema.js`, which
|
||||
"squashes" the schema into a key/value representation. Then, `toParserArgs` iterates over each
|
||||
key/value pair and "converts" it into a suitable `ArgumentOption` object for final handoff to
|
||||
`ArgParser`.
|
||||
|
||||
This adapter (`cli-args.js`) is where most of the mess is hidden; let's explore this rat's nest
|
||||
a bit further.
|
||||
|
||||
##### CLI & Schema Incongruities
|
||||
|
||||
The conversion algorithm (see function `subSchemaToArgDef` in `lib/schema/cli-args.js`) is mostly
|
||||
just hacks and special cases neatly packed into a function. Things that don't cleanly map from
|
||||
`argparse` to a JSON schema include, but are not limited to:
|
||||
|
||||
- A schema cannot natively express "store the value of `--foo=<value>` in a property called `bar`" in a schema (this corresponds to the `ArgumentOption['dest']` prop).
|
||||
- A schema cannot natively express aliases; e.g., `--verbose` can also be `-v`
|
||||
- A schema `enum` is not restricted to multiple types, but `argparse`'s equivalent `ArgumentOption['choices']` prop _is_
|
||||
- A schema does not know about `argparse`'s concept of "actions" (note that Appium is not currently using custom actions--though it did, and it could again).
|
||||
- `argparse` has no native type for `email`, `hostname`, `ipv4`, `uri` etc., and the schema does
|
||||
- Schema validation only _validates_, it does not perform translation, transformation, or coercion (mostly). `argparse` allows this.
|
||||
- Schemas allow the `null` type, for whatever reason. Ever pass `null` on the CLI?
|
||||
- `argparse` does not understand anything other than primitives; no objects, arrays, etc., and certainly not arrays of a particular type.
|
||||
|
||||
All of the above cases and others are handled by the adapter.
|
||||
|
||||
!!! warning
|
||||
|
||||
Some decisions made in the adapter were arrived at via coin toss. If you are curious about why
|
||||
something is the way it is, it's likely that it had to do _something_.
|
||||
|
||||
Let's look more closely at handling types.
|
||||
|
||||
#### Argument Types via `ajv`
|
||||
|
||||
While `argparse` allows consumers, via its API, to define the _type_ of various arguments (e.g.,
|
||||
a string, number, boolean flag, etc.), Appium mostly avoids these built-in types. _Why is that?_
|
||||
Well:
|
||||
|
||||
1. We already know the type of an argument, because we've defined it in a schema.
|
||||
2. `ajv` provides validation against a schema.
|
||||
3. A schema allows for greater expression of types, allowed values, etc., than `argparse` can provide natively.
|
||||
4. The expressiveness of a schema allows for better error messaging.
|
||||
|
||||
To that end, the adapter eschews `argparse`'s built-in types (see allowed string values of
|
||||
`ArgumentOption['type']`) and instead abuses the ability to provide a _function_ as a `type`. The
|
||||
exception is _boolean_ flags, which do not have a `type`, but rather `action: 'store_true'`. The
|
||||
world may never know why.
|
||||
|
||||
##### Types as Functions
|
||||
|
||||
When a `type` is a function, the function performs both validation _and_ coercion (if necessary).
|
||||
So what are these functions?
|
||||
|
||||
> Note: `type` is _omitted_ (and thus _not_ a function) from the `ArgumentOption` if the property
|
||||
> type is `boolean`, and is instead provided an `action` property of `store_true`. Yes, this is
|
||||
> weird. No, I don't know why.
|
||||
|
||||
Well... it depends upon the schema. But generally speaking, we create a _pipeline_ of functions,
|
||||
each corresponding to a keyword in the schema. Let's take the example of the `port` argument. In
|
||||
lieu of asking the OS which ports the `appium`-running user can bind to, this argument is expected
|
||||
to be an integer between 1 and 65535. This turns out to be two functions which we combine into
|
||||
a pipeline:
|
||||
|
||||
1. Convert the value to an integer, if possible. Because _every value in `process.argv` is a string_, we must coerce if we want a number.
|
||||
2. Use `ajv` to validate the integer against the schema for `port`. A schema lets us define a range via the `minimum` and `maximum` keywords. Read more about how this works in
|
||||
|
||||
Much like the config file validation, if errors are detected, Appium nicely tells the end-user and
|
||||
the process exits w/ some help text.
|
||||
|
||||
For other arguments which are naturally of non-primitive types, things are not so straightforward.
|
||||
|
||||
##### Transformers
|
||||
|
||||
Remember how `argparse` doesn't understand arrays? What if the most ergonomic way to express
|
||||
a value is, in fact, an array?
|
||||
|
||||
Well, Appium can't accept an array on the CLI, even though it can accept one in the config file.
|
||||
But Appium _can_ accept a comma-delimited string (a CSV "line"). Or a string filepath referring to
|
||||
a file which _contains_ a delimited list. Either way: by the time the value gets out of the
|
||||
argument parser, it should be an array.
|
||||
|
||||
And as mentioned above, the native facilities of a JSON schema cannot express this. However, it's
|
||||
possible to define a _custom keyword_ which Appium can then detect and handle accordingly. So
|
||||
that's what Appium does.
|
||||
|
||||
In this case, a custom keyword `appiumCliTransformer` is registered with `ajv`. The value of
|
||||
`appiumCliTransformer` (at the time of this writing) can be `csv` or `json`. In the base schema
|
||||
file, `appium-config-schema.js`, Appium uses `appiumCliTransformer: 'csv'` if this behavior is
|
||||
desired.
|
||||
|
||||
!!! note
|
||||
|
||||
Any property defined in the schema having type `array` will _automatically_ uses the `csv`
|
||||
transformer. Likewise, a property having type `object` will use the `json` transformer. It's
|
||||
conceivable that `array` may want to use the `json` transformer, but otherwise, the presence of
|
||||
the `appiumCliTransformer` keyword on an `array`-or-`object`-typed property is not stricly
|
||||
necessary.
|
||||
|
||||
The adapter (remember the adapter?) creates a pipeline function including a special "CSV
|
||||
transformer" (transformers are defined in `lib/schema/cli-transformers.js`), and uses this function
|
||||
as the `type` property of the `ArgumentOption` passed into `argparse`. In this case, the `type:
|
||||
'array'` in the schema is ignored.
|
||||
|
||||
!!! note
|
||||
|
||||
The config file doesn't _need_ to perform any complex transformation of values, because it
|
||||
naturally allows Appium to define exactly what it expects. So Appium does no post-processing of
|
||||
config file values.
|
||||
|
||||
Properties that do not need this special treatment use `ajv` directly for validation. How this
|
||||
works requires some explanation, so that's next.
|
||||
|
||||
#### Validation of Individual Arguments via `ajv`
|
||||
|
||||
When we think of a JSON schema, we tend to think, "I have this JSON file and I want to validate it
|
||||
against the schema". That's valid, and in fact Appium does just that with config files! However,
|
||||
Appium does not do this when validating arguments.
|
||||
|
||||
!!! note
|
||||
|
||||
During implementation, I was tempted to mash all of the arguments together into
|
||||
a config-file-like data structure and then validate it all at once. I think that would have
|
||||
been _possible_, but since an object full of CLI arguments is a flat key/value structure and
|
||||
the schema is not, this seemed like trouble.
|
||||
|
||||
Instead, Appium validates a value against a specific property _within_ the schema. To do this, it
|
||||
maintains a mapping between a CLI argument definition and its corresponding property. The mapping
|
||||
itself is a `Map` with a unique identifier for the argument as the key, and an `ArgSpec`
|
||||
(`lib/schema/arg-spec.js`) object as the value.
|
||||
|
||||
An `ArgSpec` object stores the following metadata:
|
||||
|
||||
| Property Name | Description |
|
||||
| --------------- | ------------------------------------------------------------------------------------- |
|
||||
| `name` | Canonical name of the argument, corresponding to the property name in the schema. |
|
||||
| `extType?` | `driver` or `plugin`, if appropriate |
|
||||
| `extName?` | Extension name, if appropriate |
|
||||
| `ref` | Computed `$id` of the property in the schema |
|
||||
| `arg` | Argument as accepted on CLI, without leading dashes |
|
||||
| `dest` | Property name in parsed arguments object (as returned by `argparse`'s `parse_args()`) |
|
||||
| `defaultValue?` | Value of the `default` keyword in schema, if appropriate |
|
||||
|
||||
When a schema is [finalized](#schema-finalization), the `Map` is populated with `ArgSpec` objects
|
||||
for all known arguments.
|
||||
|
||||
So when the adapter is creating the pipeline of functions for the argument's `type`, it already has
|
||||
an `ArgSpec` for the argument. It creates a function which calls `validate(value, ref)` (in
|
||||
`lib/schema/schema.js`) where `value` is whatever the user provided, and `ref` is the `ref`
|
||||
property of the `ArgSpec`. The concept is that `ajv` can validate using _any_ `ref` it knows about;
|
||||
each property in a schema can be referenced by this `ref` whether it's defined or not. To help
|
||||
visualize, if a schema is:
|
||||
|
||||
```json
|
||||
{
|
||||
"$id": "my-schema.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `ref` of `foo` would be `my-schema.json#/properties/foo`. Assuming our `Ajv` instance knows
|
||||
about this `my-schema.json`, then we can call its `getSchema(ref)` method (which has a `schema`
|
||||
property, but is a misnomer nonetheless) to get a validation function; `validate(value, ref)` in
|
||||
`schema.js` calls this validation function.
|
||||
|
||||
!!! note
|
||||
|
||||
The schema spec says a schema author can supply an explicit `$id` keyword to override this;
|
||||
it's unsupported by Appium at this time. If needed, extension authors must carefully use `$ref`
|
||||
without custom `$id`s. It's highly unlikely an extension would have a schema so complicated as
|
||||
to need this, however; Appium itself doesn't even use `$ref` to define its own properties!
|
||||
|
||||
Next, let's take a look at how Appium loads schemas. This actually happens _before_ any argument
|
||||
validation.
|
||||
|
||||
## Schema Loading
|
||||
|
||||
Let's ignore extensions for a moment, and start with the base schema.
|
||||
|
||||
When something first imports the `lib/schema/schema.js` module, an instance of an `AppiumSchema` is
|
||||
created. This is a singleton, and its methods are exported from the module (all of which are bound
|
||||
to the instance).
|
||||
|
||||
The constructor does very little; it instantiates an `Ajv` instance and configures it with Appium's
|
||||
[custom keywords](#custom-keyword-reference) and adds support for the `format` keyword via the
|
||||
[ajv-formats](https://npm.im/ajv-formats) module.
|
||||
|
||||
Otherwise, the `AppiumSchema` instance does not interact with the `Ajv` instance until its
|
||||
`finalize()` method (exported as `finalizeSchema()`) is called. When this method is called, we're
|
||||
saying "we are not going to add any more schemas; go ahead and create `ArgSpec` objects and
|
||||
register schemas with `ajv`".
|
||||
|
||||
When does finalization happen? Well:
|
||||
|
||||
1. When the `appium` executable begins, it _checks for and configures extensions_ (hand-wave) in `APPIUM_HOME`.
|
||||
2. Only then does it start to think about arguments--it instantiates an `ArgParser`, which (as you'll recall) runs the adapter to convert the schema to arguments.
|
||||
3. _Finalization happens here_--when creating the parser. Appium need the schema(s) to be registered with `ajv` in order to create validation functions for arguments.
|
||||
4. Thereafter, Appium parses the arguments with the `ArgParser`.
|
||||
5. Finally, decides what to do with the returned object.
|
||||
|
||||
Without extensions, `finalize()` still knows about the Appium base schema
|
||||
(`appium-config-schema.js`), and just registers that. However, step 1. above is doing a _lot of
|
||||
work_, so let's look at how extensions come into play.
|
||||
|
||||
## Extension Support
|
||||
|
||||
One of the design goals of this system is the following:
|
||||
|
||||
_An extension should be able to register custom CLI arguments with the Appium, and a user should be
|
||||
able to use them like any other argument_.
|
||||
|
||||
Previously, Appium 2.0 accepted arguments in this manner (via `--driverArgs`), but validation was
|
||||
hand-rolled and required extension implementors to use a custom API. It also required the user to
|
||||
awkwardly pass a JSON string as the configuration on the command-line. Further, no contextual help
|
||||
(via `--help`) existed for these arguments.
|
||||
|
||||
Now, by providing a schema for its options, a driver or plugin can register CLI arguments and
|
||||
config file schemas with Appium.
|
||||
|
||||
To register a schema, an extension must provide the `appium.schema` property in its `package.json`.
|
||||
The value may be a schema or a path to a schema. If the latter, the schema should be JSON or
|
||||
a CommonJS module (ESM not supported at this time, nor is YAML).
|
||||
|
||||
For any property in this schema, the property will appear as a CLI argument of the form
|
||||
`--<extension-type>-<extension-name>-<property-name>`. For example, if the `fake` driver provides
|
||||
a property `foo`, the argument will be `--driver-fake-foo`, and will show in `appium server --help`
|
||||
like any other CLI argument.
|
||||
|
||||
The corresponding property in a config file would be
|
||||
`server.<extension-type>.<extension-name>.<property-name>`, e.g.:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"fake": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The naming convention described above avoids problems of one extension type having a name conflict
|
||||
with a different extension type.
|
||||
|
||||
!!! note
|
||||
|
||||
While an extension can provide aliases via `appiumCliAliases`, "short" flags are disallowed,
|
||||
since all arguments from extensions are prefixed with `--<extension-type>-<extension-name>-`.
|
||||
The extension name and argument name will be kebab-cased for the CLI, according to [Lodash's
|
||||
rules](https://lodash.com/docs/4.17.15#kebabCase) around kebab-casing.
|
||||
|
||||
The schema object will look much like Appium's base schema, but it will only have top-level
|
||||
properties (nested properties are currently unsupported). Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "my rad schema for the cowabunga driver",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fizz": {
|
||||
"type": "string",
|
||||
"default": "buzz",
|
||||
"$comment": "corresponds to CLI --driver-cowabunga-fizz"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As written in a user's config file, this would be the `server.driver.cowabunga.fizz` property.
|
||||
|
||||
When extensions are loaded, the `schema` property is verified and the schema is registered with the
|
||||
`AppiumSchema` (it is _not_ registered with `Ajv` until `finalize()` is called).
|
||||
|
||||
During finalization, each registered schema is added to the `Ajv` instance. The schema is assigned
|
||||
an `$id` based on the extension type and name (which overrides whatever the extension provides, if
|
||||
anything). Schemas are also forced to disallowed unknown arguments via the `additionalProperties:
|
||||
false` keyword.
|
||||
|
||||
Behind the scenes, the base schema has `driver` and `plugin` properties which are objects. When
|
||||
finalized, a property is added to each--corresponding to an extension name--and the value of this
|
||||
property is a reference to the `$id` of a property in the extension schema. For example, the
|
||||
`server.driver` property will look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"driver": {
|
||||
"cowabunga": {
|
||||
"$ref": "driver-cowabunga.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is why we call it the "base" schema--it is _mutated_ when extensions provide schemas. The
|
||||
extension schemas are kept separately, but the _references_ are added to the schema before it's
|
||||
ultimately added to `ajv`. This works because an `Ajv` instance understands references _from_ any
|
||||
schema it knows about _to_ any schema it knows about.
|
||||
|
||||
!!! note
|
||||
|
||||
This makes it impossible to provide a complete static schema for Appium _and_ the installed
|
||||
extensions (as of Nov 5 2021). A static `.json` schema _is_ generated from the base (via a Gulp
|
||||
task), but it does not contain any extension schemas. The static schema also has uses beyond
|
||||
Appium; e.g., IDEs can provide contextual error-checking of config files this way. Let's solve
|
||||
this?
|
||||
|
||||
Just like how we look up the reference ID of a particular argument in the base schema, validation
|
||||
of arguments from extensions happens the exact same way. If the `cowabunga` driver has the schema
|
||||
ID `driver-cowabunga.json`, then the `fizz` property can be referenced from any schema registered
|
||||
with `ajv` via `driver-cowabunga.json#/properties/fizz`. "Base" schema arguments begin with
|
||||
`appium.json#properties/` instead.
|
||||
|
||||
## Development Environment Support
|
||||
|
||||
During the flow of development, a couple extra tasks have been automated to maintain the base
|
||||
schema:
|
||||
|
||||
- As a post-transpilation step, a `lib/appium-config.schema.json` gets generated from
|
||||
- `lib/schema/appium-config-schema.js` (in addition to its CJS counterpart generated by Babel).
|
||||
- This file is under version control. It ends up being _copied_ to
|
||||
- `build/lib/appium-config.schema.json` in this step. A pre-commit hook (see
|
||||
- `scripts/generate-schema-declarations.js` in the root monorepo) generates
|
||||
- a `types/appium-config-schema.d.ts` from the above JSON file. The types in `types/types.d.ts`
|
||||
- depend upon this file. This file is under version control.
|
||||
|
||||
## Custom Keyword Reference
|
||||
|
||||
Keywords are defined in `lib/schema/keywords.js`.
|
||||
|
||||
- `appiumCliAliases`: allows a schema to express aliases (e.g., a CLI argument can be `--verbose` or `-v`). This is an array of strings. Strings shorter than three (3) characters will begin with a single dash (`-`) instead of a double-dash (`--`). Note that any argument provided by an extension will begin with a double-dash, because these are required to have the `--<extension-type>-<extension-name>-` prefix.
|
||||
- `appiumCliDest`: allows a schema to specify a custom property name in the post-`argprase` arguments objects. If not set, this becomes a camelCased string.
|
||||
- `appiumCliDescription`: allows a schema to override the description of the argument when displayed on the command-line. This is useful paired with `appiumCliTransformer` (or `array`/`object`-typed properties), since there's a substantial difference between what a CLI-using user can provide vs. what a config-file-using user can provide.
|
||||
- `appiumCliTransformer`: currently a choice between `csv` and `json`. These are custom functions which post-process a value. They are not used when loading & validating config files, but the idea should be that they result in the same object you'd get if you used whatever the config file wanted (e.g., an array of strings). `csv` is for comma-delimited strings and CSV files; `json` is for raw JSON strings and `.json` files.
|
||||
- `appiumCliIgnore`: If `true`, do not support this property on the CLI.
|
||||
- `appiumDeprecated`: If `true`, the property is considered "deprecated", and will be displayed as such to the user (e.g., in the `--help` output). Note the JSON Schema draft-2019-09 introduces a new keyword `deprecated` which we should use instead if upgrading to this metaschema. When doing so, `appiumDeprecated` should itself be marked as `deprecated`.
|
||||
@@ -2,4 +2,22 @@
|
||||
title: Contributing to Appium
|
||||
---
|
||||
|
||||
This section of Appium's documentation is for those who are interested in contributing to Appium's
|
||||
development. It contains developer overviews, guides, and notes. Use the navigation menu to find
|
||||
a specific topic. All the information in these guides assumes in-depth familiarity with Appium
|
||||
already.
|
||||
|
||||
!!! warning
|
||||
|
||||
Developer information may not be kept up to date as frequently as user-facing information, or
|
||||
it may be most relevant in its current form on the online repository, not in this published
|
||||
version. Always be sure to check the repo or discuss with maintainers. We're always happy to
|
||||
help new contributors get started!
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
TODO
|
||||
|
||||
## The Appium Development Process
|
||||
|
||||
TODO
|
||||
|
||||
@@ -390,11 +390,56 @@ You can add custom CLI args if you want your driver to receive data from the com
|
||||
Appium server is started (for example, ports that a server administrator should set that should not
|
||||
be passed in as capabilities.
|
||||
|
||||
To do this you define a `schema` field within the `appium` field in `package.json`, pointing to
|
||||
a JS or JSON file defining an argument schema. (See the schema in `fake-driver`, for example).
|
||||
Arguments you define there will be available as a `--driver-<name>-<arg-name>` flag on the command
|
||||
line. If you define `my-arg` in the schema, for example, and if your driver's name is `awesome`,
|
||||
then users will be able to set your arg via `--driver-awesome-my-arg`.
|
||||
To define CLI arguments (or configuration properties) for the Appium server, your extension must provide a _schema_. In
|
||||
the `appium` property of your extension's `package.json`, add a `schema` property. This will either
|
||||
a) be a schema itself, or b) be a path to a schema file.
|
||||
|
||||
The rules for these schemas:
|
||||
|
||||
- Schemas must conform to [JSON Schema Draft-07](https://ajv.js.org/json-schema.html#draft-07).
|
||||
- If the `schema` property is a path to a schema file, the file must be in JSON or JS (CommonJS) format.
|
||||
- Custom `$id` values are unsupported. To use `$ref`, provide a value relative to the schema root, e.g., `/properties/foo`.
|
||||
- Known values of the `format` keyword are likely supported, but various other keywords may be unsupported. If you find a keyword that is unsupported which you need to use, please [ask for support](https://github.com/appium/appium/issues/new) or send a PR!
|
||||
- The schema must be of type `object` (`{"type": "object"}`), containing the arguments in a `properties` keyword. Nested properties are unsupported.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"test-web-server-port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "The port to use for the test web server"
|
||||
},
|
||||
"test-web-server-host": {
|
||||
"type": "string",
|
||||
"description": "The host to use for the test web server",
|
||||
"default": "sillyhost"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above schema defines two properties which can be set via CLI argument or configuration file. If
|
||||
this extension is a _driver_ and its name is "horace", the CLI args would be
|
||||
`--driver-horace-test-web-server-port` and `--driver-horace-test-web-server-host`, respectively.
|
||||
Alternatively, a user could provide a configuration file containing:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"horace": {
|
||||
"test-web-server-port": 1234,
|
||||
"test-web-server-host": "localhorse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add driver scripts
|
||||
|
||||
@@ -492,7 +537,7 @@ behaviours that don't map to any of the existing commands, you can create new co
|
||||
two ways:
|
||||
|
||||
1. Extending the WebDriver protocol and creating client-side plugins to access the extensions
|
||||
1. Overloading the Execute Script command
|
||||
1. Overloading the Execute Script command by defining "Execute Methods"
|
||||
|
||||
If you want to follow the first path, you can direct Appium to recognize new methods and add them
|
||||
to its set of allowed HTTP routes and command names. You do this by assigning the `newMethodMap`
|
||||
@@ -521,37 +566,56 @@ create and release client-side plugins for each language you want to support (di
|
||||
examples can be found at the relevant client docs).
|
||||
|
||||
An alternative to this way of doing things is to overload a command which all WebDriver clients
|
||||
have access to already: Execute Script. A common driver pattern is to create a magic prefix which
|
||||
the driver looks for at the beginning of any Execute Script invocation, and if it's present, to
|
||||
treat the rest of the script string as the name of a custom command to execute. This works in large
|
||||
part because for most platforms beyond web browsers, executing arbitrary JavaScript isn't a thing
|
||||
that makes sense.
|
||||
|
||||
Let's say you are building a driver for stereo system called `soundz`, and you wanted to create
|
||||
a command for playing a song by name. You could expose this to your users in such a way that they
|
||||
call something like:
|
||||
have access to already: Execute Script. Appium provides some a convenient tool for making this
|
||||
easy. Let's say you are building a driver for stereo system called `soundz`, and you wanted to
|
||||
create a command for playing a song by name. You could expose this to your users in such a way that
|
||||
they call something like:
|
||||
|
||||
```js
|
||||
// webdriverio example
|
||||
// webdriverio example. Calling webdriverio's `executeScript` command is what trigger's Appium's
|
||||
// Execute Script command handler
|
||||
driver.executeScript('soundz: playSong', [{song: 'Stairway to Heaven', artist: 'Led Zeppelin'}]);
|
||||
```
|
||||
|
||||
Then in your driver code you could simply look for `soundz:` at the beginning of your
|
||||
`execute` override, and do the appropriate thing:
|
||||
Then in your driver code you can define the static property `executeMethodMap` as a mapping of
|
||||
script names to methods on your driver. It has the same basic form as `newMethodMap`, described
|
||||
above. Once `executeMethodMap` is defined, you'll also need to implement the Execute Script command
|
||||
handler, which according to Appium's routes mapping is called `execute`. The implementation can
|
||||
call a single helper function, `this.executeMethod`, which takes care of looking at the script and
|
||||
arguments the user sent in and routing it to the correct custom handler you've defined. Here's an
|
||||
example:
|
||||
|
||||
```js
|
||||
async execute(script, args) {
|
||||
if (script === 'soundz: playSong') {
|
||||
const {song, artist} = args[0];
|
||||
// play the song based on song and artist details
|
||||
return;
|
||||
static executeMethodMap = {
|
||||
'soundz: playSong', {
|
||||
command: 'soundzPlaySong',
|
||||
params: {required: ['song', 'artist'], optional: []},
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise do the normal thing, or throw errors.NotYetImplementedError if you have nothing to
|
||||
// do (see the section on WebDriver errors in this guide)
|
||||
async soundzPlaySong(song, artist) {
|
||||
// play the song based on song and artist details
|
||||
}
|
||||
|
||||
async execute(script, args) {
|
||||
return await this.executeMethod(script, args);
|
||||
}
|
||||
```
|
||||
|
||||
A couple notes about this system:
|
||||
1. The arguments array sent via the call to Execute Script must contain only zero or one element(s). The
|
||||
first item in the list is considered to be the parameters object for your method. These parameters
|
||||
will be parsed, validated, and then applied to your overload method in the order specified in
|
||||
`executeMethodMap` (the order specified in the `required` parameters list, followed by the
|
||||
`optional` parameters list). I.e., this framework assumes only a single actual argument sent in via
|
||||
Execute Script (and this argument should be an object with keys/values representing the
|
||||
parameters your execute method expects).
|
||||
1. Appium does not automatically implement `execute` (the Execute Script handler) for you. You may
|
||||
wish, for example, to only call the `executeMethod` helper function when you're not in proxy
|
||||
mode!
|
||||
1. The `executeMethod` helper will reject with an error if a script name doesn't match one of the
|
||||
script names defined as a command in `executeMethodMap`, or if there are missing parameters.
|
||||
|
||||
### Implement handling of Appium settings
|
||||
|
||||
Appium users can send parameters to your driver via CLI args as well as via capabilities. But these
|
||||
|
||||
@@ -3,3 +3,137 @@ title: Capabilities
|
||||
---
|
||||
|
||||
TODO
|
||||
|
||||
## Special Notes for Cloud Providers
|
||||
|
||||
When managing an Appium cloud, your users may wish to target various independent versions of Appium
|
||||
drivers and plugins. It is of course up to each service provider how they wish to implement the
|
||||
discovery, installation, and availability of any official or third party drivers or plugins. _But
|
||||
the Appium team makes the following recommendations in terms of the *capabilities* service
|
||||
providers support, for consistency across the industry._ This is a recommendation only, and not
|
||||
a standard, but adopting it will help users to navigate the increased complexity that working with
|
||||
Appium 2.0 in a cloud environment may bring.
|
||||
|
||||
### Suggested capabilities
|
||||
|
||||
In addition to the standard `platformName`, `appium:deviceName`, `appium:automationName`, and
|
||||
`appium:platformVersion`, we recommend adopting the capability `$cloud:appiumOptions`, where the
|
||||
label `$cloud` is not meant to be interpreted literally but instead should be replaced by your
|
||||
vendor prefix (so for HeadSpin it would be `headspin`, Sauce Labs it would be `sauce`, and
|
||||
BrowserStack it would be `browserstack`, to name just a few examples). The `$cloud:appiumOptions`
|
||||
capability would itself be a JSON object, with the following internal keys:
|
||||
|
||||
| Capability | Used for | Example |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `version` | Designating which version of the Appium server is used to host and manage drivers. If ommitted, behavior left up to the provider, but the recommendation would be to provide the latest official version. | `2.0.0` |
|
||||
| `automationVersion` | Designating which version of the specified driver should be used. | `1.55.2` |
|
||||
| `automation` | Designating a custom driver to use (see below for more info). This would override `appium:automationName` and `$cloud:automationVersion` | `{"name": "@org/custom-driver", "source": "github", "package": "custom-driver"}` |
|
||||
| `plugins` | Designating the list of plugins (and potentially versions of plugins) to be activated (see below for more info). | `["images", "universal-xml"]` |
|
||||
|
||||
### Basic example
|
||||
|
||||
Appium extensions (drivers and plugins) have a set of properties that specify where they can be
|
||||
installed from. Cloud providers are obviously under no obligation to provide support for
|
||||
arbitrarily specified extensions, seeing as these may represent untrusted code running in a managed
|
||||
environment. In the case where arbitrary extensions are not supported, the `appium:automationName`,
|
||||
`$cloud:automationVersion`, and `$cloud:appiumPlugins` capabilities should be sufficient. See the
|
||||
following JSON object representing capabilities for a session:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"appium:platformVersion": "14.4",
|
||||
"appium:deviceName": "iPhone 11",
|
||||
"appium:app": "Some-App.app.zip",
|
||||
"appium:automationName": "XCUITest",
|
||||
"$cloud:appiumOptions": {
|
||||
"appiumVersion": "2.0.0",
|
||||
"automationVersion": "3.52.0",
|
||||
"plugins": ["images"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This set of capabilities requests an Appium 2+ server supporting the XCUITest driver at version
|
||||
`3.52.0`, and the `images` plugin active. This set is easy for a cloud provider to verify. The
|
||||
cloud provider can obviously do anything it wants in response to these capabilities, including
|
||||
downloading Appium and driver and plugin packages on the fly, or erroring out if the versions
|
||||
requested are not in a supported set, or if the plugin is not supported, etc...
|
||||
|
||||
### Basic example with `appium:options`
|
||||
|
||||
The previous example still looks a bit disorganized, so of course we also recommend that cloud
|
||||
providers support the `appium:options` capability as detailed above, which could turn the previous
|
||||
set of capabilities into the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"appium:options": {
|
||||
"platformVersion": "14.4",
|
||||
"deviceName": "iPhone 11",
|
||||
"app": "Some-App.app.zip",
|
||||
"automationName": "XCUITest"
|
||||
},
|
||||
"$cloud:appiumOptions": {
|
||||
"appiumVersion": "2.0.0",
|
||||
"automationVersion": "3.52.0",
|
||||
"plugins": ["images"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Extension objects
|
||||
|
||||
Some service providers may wish to dynamically allow access to all of the features of the Appium
|
||||
2.0 CLI, including downloading arbitrary drivers and plugins. To represent these extensions, we can
|
||||
define special JSON "extension objects", with the following keys:
|
||||
|
||||
- `name`: the name of the extension. This would be an NPM package name (if downloading from NPM), or a git or GitHub spec (if downloading from a git server or GitHub).
|
||||
- `version`: the version of the extension, e.g., the NPM package version or Git SHA.
|
||||
- (optional) `source`: a denotation of where the extension can be downloaded from. Recommended to support the following values: `appium`, `npm`, `git`, `github`. Here, `appium` means "Appium's own official list", and should be the default value if this key is not included.
|
||||
- (optional) `package`: when downloading extensions from git or github, the NPM package name of the extension must also be provided. This is optional for non-git sources.
|
||||
|
||||
Since each session is handled by a single driver, the `$cloud:appiumOptions`/`$automation`
|
||||
capability could be used with an extension object value to denote this driver, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"$cloud:appiumOptions": {
|
||||
"automation": {
|
||||
"name": "git+https://some-git-host.com/custom-driver-project.git",
|
||||
"version": "some-git-sha",
|
||||
"source": "git",
|
||||
"package": "driver-npm-package-name"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And since sessions can handle multiple plugins, each value in the list of `$cloud:appiumPlugins`
|
||||
could also be an extension object rather than a string, so that specific versions could be
|
||||
requested:
|
||||
|
||||
```json
|
||||
{
|
||||
"$cloud:appiumOptions": {
|
||||
"plugins": [{
|
||||
"name": "images",
|
||||
"version": "1.1.0"
|
||||
}, {
|
||||
"name": "my-github-org/my-custom-plugin",
|
||||
"version": "a83f2e",
|
||||
"source": "github",
|
||||
"package": "custom-plugin"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These serve as illustrative examples for the recommendations here. Of course it is up to the
|
||||
service providers to implement the handling of these capabilities at their front end / load
|
||||
balancer, to perform any error checking, or to actually run any of the `appium driver` or `appium
|
||||
plugin` CLI commands that support the end user's request. This section is merely a suggestion as to
|
||||
how service providers might design their user-facing capabilities API in a way which in principle
|
||||
supports all of the capabilities Appium itself would provide to the end user if they were running
|
||||
Appium on their own.
|
||||
|
||||
@@ -2,4 +2,94 @@
|
||||
title: The Appium Config File
|
||||
---
|
||||
|
||||
TODO
|
||||
Instead of passing arguments on the command line to Appium, you may add them to a special config
|
||||
file. Appium will read values from this config file when it runs. (Please note that CLI arguments
|
||||
have _precedence_ over configuration files; if a value is set in a config file _and_ via CLI
|
||||
argument, the CLI argument is preferred.)
|
||||
|
||||
## Supported Config File Formats
|
||||
|
||||
You can store your config data in the following kinds of files:
|
||||
|
||||
- JSON
|
||||
- YAML
|
||||
- JS (a JavaScript file exporting a JS object)
|
||||
- CJS (the same as above; the extension is for common JS)
|
||||
|
||||
!!! warning
|
||||
|
||||
Note: Configuration files in ESM format are not currently supported.
|
||||
|
||||
## Supported Config File Locations
|
||||
|
||||
Configuration files can be named anything, but the following filenames will be automatically
|
||||
discovered and loaded by Appium:
|
||||
|
||||
- `.appiumrc.json` (recommended)
|
||||
- `.appiumrc.yaml`
|
||||
- `.appiumrc.yml`
|
||||
- `.appiumrc.js`
|
||||
- `.appiumrc.cjs`
|
||||
- `appium.config.js`
|
||||
- `appium.config.cjs`
|
||||
- `.appiumrc` (which is considered to be JSON)
|
||||
|
||||
Further, _if your project uses Node.js,_ you can use store the configuration inside an `appium` property in your `package.json` and it will be automatically discovered.
|
||||
|
||||
### Config File Search
|
||||
|
||||
Appium will search _up_ the directory tree from the current working directory for one of these
|
||||
files. If it reaches the current user's home directory or filesystem root, it will stop looking.
|
||||
|
||||
To specify a _custom_ location for your config file, use `appium --config-file /path/to/config/file`.
|
||||
|
||||
#### Configuration File Format
|
||||
|
||||
First, you might want to look at some examples:
|
||||
|
||||
- [Appium Configuration - JSON](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.json)
|
||||
- [Appium Configuration - YAML](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.yaml)
|
||||
- [Appium Configuration - JS](https://github.com/appium/appium/blob/master/packages/appium/sample-code/appium.config.sample.js)
|
||||
|
||||
A description of the format is available, as well:
|
||||
|
||||
- [Appium Configuration File JSON Schema](https://github.com/appium/appium/blob/master/packages/schema/lib/appium-config.schema.json)
|
||||
- [TypeScript declarations for Appium Configuration](https://github.com/appium/appium/blob/master/packages/types/lib/config.ts)
|
||||
|
||||
To describe in words, the config file will have a root `server` property, and all arguments are
|
||||
child properties. For certain properties which must be supplied on the command-line as comma-delimited lists, JSON strings, and/or external filepaths, these instead will be of their "native" type. For example,
|
||||
`--use-plugins <value>` needs `<value>` to be comma-delimited string or path to a delimited file.
|
||||
However, the config file just wants an array, e.g.,:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"use-plugins": ["my-plugin", "some-other-plugin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuring extensions (drivers and plugins)
|
||||
|
||||
For `driver`-and-`plugin`-specific configuration, these live under the `server.driver` and
|
||||
`server.plugin` properties, respectively. Each driver or plugin will have its own named property,
|
||||
and the values of any specific configuration it provides are under this. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"driver": {
|
||||
"xcuitest": {
|
||||
"webkit-debug-proxy-port": 5400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
The above configuration corresponds to the `--driver-xcuitest-webkit-debug-proxy-port` CLI argument.
|
||||
|
||||
All properties are case-sensitive and will be in [kebab-case](https://en.wikipedia.org/wiki/Naming_convention_(programming)#Delimiter-separated_words). For example, `callback-port` is
|
||||
allowed, but `callbackPort` is not.
|
||||
|
||||
@@ -2,4 +2,216 @@
|
||||
title: Migrating from Appium 1.x to Appium 2.x
|
||||
---
|
||||
|
||||
This document is a guide for those who are using Appium 1.x and wish to migrate to Appium 2.x. It contains a list of breaking changes and how to migrate your environments or test suites to ensure compatibility with Appium 2.0.
|
||||
|
||||
## Overview of Appium 2.0
|
||||
|
||||
Appium 2.0 is the most major new release of Appium in over 5 years. The changes in Appium 2.0 are _not_ primarily related to changes in automation behaviors for specific platforms. Instead, Appium 2.0 reenvisions Appium as a _platform_ where "drivers" (code projects that introduce support for automation of a given platform) and "plugins" (code projects that allow for overriding, altering, extending, or adding behaviors to Appium) can be easily created and shared.
|
||||
|
||||
At the same time, the Appium project is taking the opportunity to remove many old and deprecated bits of functionality.
|
||||
|
||||
Together these do introduce a few breaking changes to how Appium is installed, how drivers and various features are managed, and protocol support. These are detailed below.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases) for the most comprehensive lists of changes. Here we call out the breaking changes and what you need to do do account for them.
|
||||
|
||||
### :bangbang: Installing drivers during setup
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium@next`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
|
||||
```
|
||||
appium driver install xcuitest
|
||||
appium driver install uiautomator2
|
||||
```
|
||||
|
||||
At this point, your drivers are installed and ready. There's a lot more you can do with this CLI so be sure to check out the docs on it.
|
||||
If you're running in a CI environment or want to install Appium along with some drivers all in one step, you can do so using some special flags during install, for example:
|
||||
|
||||
```
|
||||
npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
|
||||
### :bangbang: Chromedriver installation flags
|
||||
|
||||
In Appium 1.x it was possible to customize the way Chromedriver was installed (as part of the UiAutomator2 driver for example), using the following command line flags:
|
||||
|
||||
* `--chromedriver-skip-install`
|
||||
* `--chromedriver-version`
|
||||
* `--chromedriver-cdnurl`
|
||||
|
||||
Because Appium 2.0 now installs drivers for you, and because these flags were implemented as NPM config flags, they will no longer work. Instead, use the following environment variables during driver installation:
|
||||
|
||||
* `APPIUM_SKIP_CHROMEDRIVER_INSTALL`
|
||||
* `CHROMEDRIVER_VERSION`
|
||||
* `CHROMEDRIVER_CDNURL`
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
APPIUM_SKIP_CHROMEDRIVER_INSTALL=1 appium driver install uiautomator2
|
||||
```
|
||||
|
||||
### :bangbang: Driver-specific command line options
|
||||
|
||||
With Appium 1.x, command-line options specific to particular drivers were all hosted on the main Appium server. So, for example, `--chromedriver-executable` was a CLI parameter you could use with Appium to set the location of a specific Chromedriver version for use with, say, the UiAutomator2 driver.
|
||||
|
||||
With Appium 2.x, all driver- and platform-specific CLI params have been moved to the drivers themselves. To access them, you'll now need to prepend the argument with the extension type (either `driver` or `plugin`) and the name of the extension. For example, `--chromedriver-executable` becomes `--driver-uiautomator2-chromedriver-executable`.
|
||||
|
||||
### :bangbang: Driver-specific automation commands
|
||||
|
||||
The definition of certain commands that pertain only to specific drivers has been moved to those
|
||||
drivers' implementations. For example, `pressKeyCode` is specific to the UiAutomator2 driver and is
|
||||
now understood only by that driver. In practice, the only breaking change here is the kind of error
|
||||
you would encounter if the appropriate driver is not installed. Previously, you would get a `501
|
||||
Not Yet Implemented` error if using a driver that didn't implement the command. Now, you will get
|
||||
a `404 Not Found` error because if a driver that doesn't know about the command is not active, the
|
||||
main Appium server will not define the route corresponding to the command.
|
||||
|
||||
### :bangbang: Driver updates
|
||||
|
||||
In the past, to get updates to your iOS or Android drivers, you'd simply wait for those updates to be rolled into a new release of Appium, and then update your Appium version. With Appium 2.x, the Appium server and the Appium drivers are versioned and released separately. This means that drivers can be on their own release cadence and that you can get driver updates as they happen, rather than waiting for a new Appium server release. The way to check for driver updates is with the CLI:
|
||||
|
||||
```bash
|
||||
appium driver list --updates
|
||||
```
|
||||
|
||||
If any updates are available, you can then run the `update` command for any given driver:
|
||||
|
||||
```bash
|
||||
appium driver update xcuitest
|
||||
```
|
||||
|
||||
(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.
|
||||
|
||||
### :bangbang: Protocol changes
|
||||
|
||||
Appium's API is based on the [W3C WebDriver Protocol](https://www.w3.org/TR/webdriver/), and it has supported this protocol for years. Before the W3C WebDriver Protocol was designed as a web standard, several other protocols were used for both Selenium and Appium. These protocols were the "JSONWP" (JSON Wire Protocol) and "MJSONWP" (Mobile JSON Wire Protocol). The W3C Protocol differs from the (M)JSONWP protocols in a few small ways.
|
||||
|
||||
Up until Appium 2.0, Appium supported both protocols, so that older Selenium/Appium clients could still communicate with newer Appium servers. Moving forward, support for older protocols will be removed.
|
||||
|
||||
### :bangbang: _Capabilities_
|
||||
|
||||
One significant difference between old and new protocols is in the format of capabilities. Previously called "desired capabilities", and now called simply "capabilities", there is now a requirement for a so-called "vendor prefix" on any non-standard capabilities. The list of standard capabilities is given in the [WebDriver Protocol spec](https://www.w3.org/TR/webdriver/#capabilities), and includes a few commonly used capabilities such as `browserName` and `platformName`.
|
||||
|
||||
These standard capabilities continue to be used as-is. All other capabilities must include a "vendor prefix" in their name. A vendor prefix is a string followed by a colon, such as `appium:`. Most of Appium's capabilities go beyond the standard W3C capabilities and must therefore include vendor prefixes (we recommend that you use `appium:` unless directed otherwise by documentation). For example:
|
||||
|
||||
- `appium:app`
|
||||
- `appium:noReset`
|
||||
- `appium:deviceName`
|
||||
|
||||
This requirement may or may not be a breaking change for your test suites when targeting Appium 2.0. If you're using an updated Appium client, the client will add the `appium:` prefix for you on all necessary capabilities. New versions of the Appium Inspector tool will also do this. Cloud-based Appium providers may also do this. So simply be aware that if you get any messages to the effect that your capabilities lack a vendor prefix, this is how you solve that problem.
|
||||
|
||||
On a related note, it will no longer be possible to start Appium sessions using WebDriver clients that don't support the W3C protocol (see below for a comment to this effect for the WD library).
|
||||
|
||||
To make everyone's lives a bit easier, we've also introduced the option of wrapping up all Appium-related capabilities into one object capability, `appium:options`. You can bundle together anything that you would normally put an `appium:` prefix on into this one capability. Here's an example (in raw JSON) of how you might start an iOS session on the Safari browser using `appium:options`:
|
||||
|
||||
```json
|
||||
{
|
||||
"platformName": "iOS",
|
||||
"browserName": "Safari",
|
||||
"appium:options": {
|
||||
"platformVersion": "14.4",
|
||||
"deviceName": "iPhone 11",
|
||||
"automationName": "XCUITest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(Of course, each client will have a different way of creating structured capabilities like `appium:options` or other ones that you might have seen such as `goog:chromeOptions`). NB: capabilities that show up in `appium:options` will overwrite capabilities of the same name that show up at the top level of the object.
|
||||
|
||||
For more information on capabilities, have a look at the [Capabilities Guide](caps.md).
|
||||
|
||||
### :bangbang: _Removed Commands_
|
||||
|
||||
In addition to commands which have been moved to driver implementations, commands which were a part of the old JSON Wire Protocol and not a part of the W3C Protocol are no longer available:
|
||||
|
||||
- TODO (these commands are being identified and removed and will be updated here when complete)
|
||||
|
||||
If you use a modern Appium or Selenium client, you should no longer have access to these anyway, so any breaking changes should appear on the client side first and foremost.
|
||||
|
||||
### :bangbang: Image analysis features moved to plugin
|
||||
|
||||
One of the design goals for Appium 2.0 is to migrate non-core features into special extensions called [plugins](../ecosystem/index.md). This allows people to opt into features which require extra time to download or extra system setup. The various image-related features of Appium (image comparison, finding elements by image, etc...) have been moved into an officially supported plugin called [images](https://github.com/appium/appium/tree/master/packages/images-plugin).
|
||||
|
||||
If you use these image-related methods, to continue accessing them you will need to do two things.
|
||||
|
||||
1. Install the plugin: `appium plugin install images`
|
||||
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=images`
|
||||
|
||||
Image-related commands will also be removed on the client side of things, which means you will need to follow the instructions on the plugin README for installing client-side plugins to access these features.
|
||||
|
||||
### :bangbang: Execute Driver Script command moved to plugin
|
||||
|
||||
If you use the advanced Execute Driver Script feature (which allows you to send in a WebdriverIO script to have it executed completely on the server instead of command-by-command from the client), this functionality has been moved to a plugin. Here's what to do to keep using it:
|
||||
|
||||
1. Install the plugin: `appium plugin install execute-driver`
|
||||
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=execute-driver`
|
||||
|
||||
### :bangbang: External Files No Longer Supported for `--nodeconfig`, `--default-capabilities`, `--allow-insecure` and `--deny-insecure`
|
||||
|
||||
These options can be provided as strings on the command line (a JSON string for `--nodeconfig` and a comma-separated list of strings for `--allow-insecure` and `--deny-insecure`). Arguments provided on the command line will likely need to be quoted or escaped.
|
||||
|
||||
The recommended method to provide these options is now via a [configuration file](#tada-configuration-files).
|
||||
|
||||
In summary, if you are using a JSON Appium config file, you can simply cut-and-paste the contents of your "nodeconfig" JSON file into the value of the `server.nodeconfig` property. Any CSV-like files you had previously provided for `--allow-insecure` and `--deny-insecure` become the values of the `server.allow-insecure` and `server.deny-insecure` properties in the Appium config files (respectively); both are arrays of strings.
|
||||
|
||||
### :bangbang: Old drivers removed
|
||||
|
||||
The old iOS and Android (UiAutomator 1) drivers and related tools (e.g., `authorize-ios`) have been removed. They haven't been relevant for many years anyway.
|
||||
|
||||
### :warning: Internal packages renamed
|
||||
|
||||
Some Appium-internal NPM packages have been renamed (for example, `appium-base-driver` is now `@appium/base-driver`). This is not a breaking change for Appium users, only for people who have built software that directly incorporates Appium's code.
|
||||
|
||||
### :warning: "WD" JavaScript client library no longer supported
|
||||
|
||||
For many years, some of Appium's authors maintained the [WD](https://github.com/admc/wd) client library. This library has been deprecated and has not been updated for use with the W3C WebDriver protocol. As such, if you're using this library you'll need to move to a more modern one. We recommend [WebdriverIO](https://webdriver.io).
|
||||
|
||||
### :warning: Appium Inspector split out from Appium Desktop
|
||||
|
||||
The inspecting portion of Appium Desktop has been moved to its own app, Appium Inspector: [github.com/appium/appium-inspector](https://github.com/appium/appium-inspector). It's fully compatible with Appium 2.0 servers. Simply download it and run it on its own. You no longer need the GUI Appium Desktop server to inspect apps. The Appium Desktop server will continue to be supported at its original site: [github.com/appium/appium-desktop](https://github.com/appium/appium-desktop). It will simply no longer bundle the Inspector with it.
|
||||
|
||||
You can also now use the Appium Inspector without downloading anything, by visiting the [web version of Appium Inspector](https://inspector.appiumpro.com). Note that to test against local servers, you'll need to start the server with `--allow-cors` so that the browser-based version of Appium Inspector can access your Appium server to start sessions.
|
||||
|
||||
## Major New Features
|
||||
|
||||
Apart from the breaking changes mentioned above, in this section is a list of some of the major new features you may wish to take advantage of with Appium 2.0.
|
||||
|
||||
### Plugins
|
||||
|
||||
#### :tada: _Server Plugins_
|
||||
|
||||
Appium extension authors can now develop their own server plugins, which can intercept and modify
|
||||
any Appium command, or even adjust the way the underlying Appium HTTP server itself works. To learn
|
||||
more about plugins, read the new [Appium Introduction](../intro/index.md). Interested in building
|
||||
a plugin? Check out the [Building Plugins](../ecosystem/build-plugins.md) guide.
|
||||
|
||||
#### :tada: _Client Plugins_
|
||||
|
||||
TODO
|
||||
|
||||
### :tada: Install drivers and plugins from anywhere
|
||||
|
||||
You're no longer limited to the drivers that come with Appium, or that the Appium team even knows
|
||||
about! Appium extension authors can now develop custom drivers, which can be downloaded or
|
||||
installed via Appium's [Extension CLI](../cli/extensions.md) from NPM, Git, GitHub, or even the
|
||||
local filesystem. Interested in building a driver? Check out the [Building
|
||||
Drivers](../ecosystem/build-drivers.md) guide.
|
||||
|
||||
### :tada: Configuration Files
|
||||
|
||||
Appium now supports _configuration files_ in addition to command-line arguments. In a nutshell, nearly all arguments which Appium 1.x required to be provided on the CLI are now able to be expressed via a configuration file. Configuration files may be in JSON, JS, or YAML format. See the [Config Guide](config.md) for a full explanation.
|
||||
|
||||
## Special Notes for Cloud Providers
|
||||
|
||||
The rest of this document has applied to Appium generally, but some of the architectural changes in Appium 2.0 will constitute breaking changes for Appium-related service providers, whether a cloud-based Appium host or an internal service. At the end of the day, the maintainer of the Appium server is responsible for installing and making available the various Appium drivers and plugins that end users may wish to use.
|
||||
|
||||
We encourage cloud providers to thoroughly read and understand our [recommendation for cloud
|
||||
provider capabilities](caps.md#special-notes-for-cloud-providers) in order to support user needs in
|
||||
an industry-compatible way!
|
||||
|
||||
@@ -2,4 +2,123 @@
|
||||
title: Intro to Appium Clients
|
||||
---
|
||||
|
||||
TODO
|
||||
For all the reasons discussed in the [main intro](./index.md), Appium is based on the [W3C
|
||||
WebDriver specification](https://w3c.github.io/webdriver/webdriver-spec.html). This means that
|
||||
Appium implements a client-server architecture. The server (consisting of Appium itself along with
|
||||
any drivers or plugins you are using for automation) is connected to the devices under test, and
|
||||
is actually responsible for making automation happen on those devices. The client (driven by *you*,
|
||||
the Appium test author) is responsible for sending commands to the server over the network, and
|
||||
receiving responses from the server as a result. These responses can be used to tell whether
|
||||
automation commands are successful, or might contain information that you queried about the state
|
||||
of the application. This document is a conceptual introduction to the client side of this equation.
|
||||
|
||||
!!! info
|
||||
|
||||
For more about the server side of the equation (i.e., how does Appium actually control
|
||||
devices?), check out our [Intro to Appium Drivers](./drivers.md). To skip to a list of links to
|
||||
Appium client libraries, check out the [Ecosystem](../ecosystem/index.md) documentation.
|
||||
|
||||
What sorts of automation commands are available? That is up to the particular driver and plugins
|
||||
that you are using in any given session. A standard set of commands would include, for example, the
|
||||
following:
|
||||
|
||||
- Find Element
|
||||
- Click Element
|
||||
- Get Page Source
|
||||
- Take Screenshot
|
||||
|
||||
If you look at these commands in the WebDriver specification, you'll notice that they are not
|
||||
defined in terms of any particular programming language. They are not Java commands, or JavaScript
|
||||
commands, or Python commands. Instead, they form part of an HTTP API which can be accessed from
|
||||
within *any* programming language (or none! you could just use cURL if you want).
|
||||
|
||||
So, for example, the `Find Element` command corresponds to an HTTP `POST` request sent to the HTTP
|
||||
endpoint `/session/:sessionid/element` (where in this case, `:sessionid` is a placeholder for the
|
||||
unique session ID generated by the server in a previous call to `Create Session`).
|
||||
|
||||
This information is primarily useful for people developing technology that works with the WebDriver
|
||||
spec. It's not particularly useful for people trying to write Appium or Selenium tests. When you
|
||||
write an Appium test, you want to use a programming language you're familiar with. Luckily, there
|
||||
exist a set of [Appium client libraries](../ecosystem/index.md)[^1] that take care of the
|
||||
responsibility of speaking HTTP to the Appium server. Instead, they expose a set of "native"
|
||||
commands for a particular programming language, so that, to the test author, it just feels like
|
||||
you're writing Python, or JavaScript, or Java.
|
||||
|
||||
As an example, here's the same simple set of Appium commands in five different programming
|
||||
languages, using the recommended Appium client binding for each language (note that this is not
|
||||
working sample code including all appropriate imports; see each client library's instructions for
|
||||
setup and command reference):
|
||||
|
||||
=== "JavaScript (Webdriver.io)"
|
||||
|
||||
```js
|
||||
const element = await driver.$('//*[@text="Foo"]');
|
||||
await element.click();
|
||||
console.log(await element.getText())
|
||||
console.log(await driver.getPageSource())
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
WebElement element = driver.findElement(By.Xpath("//*[@text='Foo']"))
|
||||
element.click()
|
||||
System.out.println(element.getText())
|
||||
System.out.println(driver.getPageSource())
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
element = driver.find_element(by=By.XPATH, value='//*[@text="Foo"]')
|
||||
element.click()
|
||||
print(element.text)
|
||||
print(driver.page_source)
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
element = driver.find_element :xpath, '//*[@text="Foo"]'
|
||||
element.click
|
||||
puts element.text
|
||||
puts driver.page_source
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```dotnet
|
||||
AppiumElement element = driver.FindElement(MobileBy.AccessibilityId("Views"));
|
||||
element.click();
|
||||
System.Console.WriteLine(element.Text);
|
||||
System.Console.WriteLine(driver.PageSource);
|
||||
```
|
||||
|
||||
Each of these scripts, despite being in different languages, does the same thing under the hood:
|
||||
|
||||
1. Call `Find Element` with a `using` parameter of `xpath` and a `value` parameter expressing the
|
||||
XPath query used to find an element. (If you're confused about these terms, you might find an
|
||||
introduction to Appium or Selenium useful)
|
||||
2. Call `Click Element` with the ID of the element found in the previous call.
|
||||
3. Call `Get Element Text` with the ID of the same element, and print it to the console.
|
||||
4. Call `Get Page Source` to retrieve the page/app source and print it to the console.
|
||||
|
||||
The only other thing to keep in mind before choosing or using a client is that each client is
|
||||
independently maintained. Just because a feature is available in one client, it doesn't mean it's
|
||||
available in another client (though all clients support at least the standard W3C protocol plus any
|
||||
common appium extensions). Just because one client has a nice set of helper functions, doesn't mean
|
||||
another will. Some clients are kept very frequently up to date, and others are not! So when
|
||||
thinking about choosing a library, the first consideration is the language you want to use, and the
|
||||
second consideration is how full-featured and well-maintained the library is!
|
||||
|
||||
To learn how to use an Appium client, visit that client's homepage to learn more. In many cases,
|
||||
the Appium client for a given language is built *on top of* the *Selenium* client for that
|
||||
language, and so certain Appium clients may only document the features which the Appium client
|
||||
added on top of the Selenium client. All that to say, for a full reference, you may need to visit
|
||||
both the Appium client documentation as well as the Selenium client documentation.
|
||||
|
||||
That's all you need to know about Appium clients! Head over to the
|
||||
[Ecosystem](../ecosystem/index.md) page to check out the current list of clients.
|
||||
|
||||
[^1]: These libraries are alternately called "clients", "client libraries", or "client bindings".
|
||||
They all mean the same thing!
|
||||
|
||||
@@ -3,10 +3,13 @@ title: Install the UiAutomator2 Driver
|
||||
---
|
||||
|
||||
You can't do much with Appium unless you have a [driver](../intro/drivers.md), which is an
|
||||
interface that allows Appium to automate a particular platform. For this quickstart guide, we're
|
||||
going to be automating an app on the Android platform, because the system requirements for Android
|
||||
automation via Appium are the same as for Appium itself (whereas the iOS driver, for example,
|
||||
requires you to be using macOS).
|
||||
interface that allows Appium to automate a particular platform.
|
||||
|
||||
!!! info
|
||||
|
||||
For this quickstart guide, we're going to be automating an app on the Android platform, because
|
||||
the system requirements for Android automation via Appium are the same as for Appium itself
|
||||
(whereas the iOS driver, for example, requires you to be using macOS).
|
||||
|
||||
The driver we're going to use is called the [UiAutomator2
|
||||
Driver](https://github.com/appium/appium-uiautomator2-driver). It's worth visiting that driver's
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/en
|
||||
edit_uri: edit/2.0/packages/appium/docs/en
|
||||
edit_uri: edit/master/packages/appium/docs/en
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: en
|
||||
@@ -26,7 +26,7 @@ nav:
|
||||
- quickstart/test-js.md
|
||||
- quickstart/test-py.md
|
||||
- quickstart/test-java.md
|
||||
- quickstart/test-ruby.md
|
||||
- quickstart/test-rb.md
|
||||
- quickstart/next-steps.md
|
||||
- CLI Reference:
|
||||
- cli/index.md
|
||||
@@ -50,6 +50,8 @@ nav:
|
||||
- ecosystem/build-drivers.md
|
||||
- ecosystem/build-plugins.md
|
||||
- ecosystem/build-docs.md
|
||||
- Contributing: contributing/index.md
|
||||
- Contributing:
|
||||
- contributing/index.md
|
||||
- contributing/config-system.md
|
||||
- More Appium Resources: resources.md
|
||||
- Issues: https://github.com/appium/appium/issues
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/ja
|
||||
edit_uri: edit/2.0/packages/appium/docs/ja
|
||||
edit_uri: edit/master/packages/appium/docs/ja
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: ja
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INHERIT: ./base-mkdocs.yml
|
||||
site_url: https://appium.github.io/appium/docs/zh
|
||||
edit_uri: edit/2.0/packages/appium/docs/zh
|
||||
edit_uri: edit/master/packages/appium/docs/zh
|
||||
site_description: The Appium automation project documentation
|
||||
|
||||
docs_dir: zh
|
||||
|
||||
@@ -19,7 +19,7 @@ const {version} = require('../../package.json');
|
||||
|
||||
const branch = process.env.APPIUM_DOCS_BRANCH || DOCS_BRANCH;
|
||||
const prefix = process.env.APPIUM_DOCS_PREFIX || DOCS_PREFIX;
|
||||
const remote = process.env.APPIUM_DOCS_PREFIX || DOCS_REMOTE;
|
||||
const remote = process.env.APPIUM_DOCS_REMOTE || DOCS_REMOTE;
|
||||
|
||||
const shouldPush = !!process.env.APPIUM_DOCS_PUBLISH;
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// for simplicity this file is not transpiled and is run directly via an npm script
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable promise/prefer-await-to-then */
|
||||
|
||||
const path = require('path');
|
||||
const {fs} = require('@appium/support');
|
||||
const {AppiumConfigJsonSchema} = require('@appium/schema');
|
||||
|
||||
const DOC_PATH = path.resolve(__dirname, '..', 'en', 'cli', 'args.md');
|
||||
|
||||
const START_TAG = `<!-- AUTOGEN-START -->`;
|
||||
const STOP_TAG = `<!-- AUTOGEN-STOP -->`;
|
||||
|
||||
async function main() {
|
||||
console.log(`Gathering current cli doc file contents...`);
|
||||
const [preStart, postEnd] = await getDocParts();
|
||||
console.log(`Generating new cli arg table...`);
|
||||
const argTable = getCliArgTable();
|
||||
console.log(`Writing new doc file...`);
|
||||
await writeNewDoc(preStart, argTable, postEnd);
|
||||
console.log(`...done!`);
|
||||
}
|
||||
|
||||
async function getDocParts() {
|
||||
if (!(await fs.exists(DOC_PATH))) {
|
||||
throw new Error(`Cannot find args.md at ${DOC_PATH}`);
|
||||
}
|
||||
|
||||
const curContents = await fs.readFile(DOC_PATH, 'utf-8');
|
||||
|
||||
// attempt to verify the start/stop tags
|
||||
const [preStart, postStart] = curContents.split(START_TAG);
|
||||
if (!postStart) {
|
||||
throw new Error(`Could not find autogen start tag '${START_TAG}'. Make sure it's in place!`);
|
||||
}
|
||||
const [, postEnd] = postStart.split(STOP_TAG);
|
||||
if (!postEnd) {
|
||||
throw new Error(`Could not find autogen stop tag '${STOP_TAG}'. Make sure it's in place!`);
|
||||
}
|
||||
|
||||
return [preStart, postEnd];
|
||||
}
|
||||
|
||||
function getCliArgTable() {
|
||||
const pad = ' '.repeat(30);
|
||||
const header = `|Argument${pad}|Description|Type|Default|Aliases|\n` + `|--|--|--|--|--|`;
|
||||
const serverProps = AppiumConfigJsonSchema.properties.server.properties;
|
||||
const args = Object.keys(serverProps).map((arg) => {
|
||||
let {appiumCliAliases = [], description, type, items, maximum, minimum} = serverProps[arg];
|
||||
let def = serverProps[arg].default;
|
||||
let enums = serverProps[arg].enum;
|
||||
if (type === 'array' || type === 'object' || (type === 'string' && def === '')) {
|
||||
def = JSON.stringify(def);
|
||||
}
|
||||
if (type === 'array' && items.type) {
|
||||
type = `array<${items.type}>`;
|
||||
}
|
||||
if (type === 'integer' && typeof minimum !== 'undefined' && typeof maximum !== 'undefined') {
|
||||
description = `${description} (Value must be between \`${minimum}\` and \`${maximum}\`)`;
|
||||
}
|
||||
if (enums) {
|
||||
const enumsFormatted = enums.map((e) => `\`${e}\``).join(', ');
|
||||
description = `${description} (Value must be one of: ${enumsFormatted})`;
|
||||
}
|
||||
return {
|
||||
arg: `--${arg}`,
|
||||
aliases: appiumCliAliases.map((a) => `-${a}`),
|
||||
type,
|
||||
def,
|
||||
description,
|
||||
};
|
||||
});
|
||||
const body = args
|
||||
.map(({arg, aliases, type, def, description}) => {
|
||||
const aliasStr = aliases.map((a) => `\`${a}\``).join(', ');
|
||||
def = typeof def !== 'undefined' ? `\`${def}\`` : '';
|
||||
return `|\`${arg}\`|${description}|${type}|${def}|${aliasStr}|`;
|
||||
})
|
||||
.join('\n');
|
||||
return `${header}\n${body}`;
|
||||
}
|
||||
|
||||
async function writeNewDoc(preStart, argTable, postEnd) {
|
||||
const newContents = `${preStart}${START_TAG}\n${argTable}\n${STOP_TAG}${postEnd}`;
|
||||
await fs.writeFile(DOC_PATH, newContents);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = main;
|
||||
@@ -1,5 +1,123 @@
|
||||
---
|
||||
title: Intro to Appium Clients
|
||||
title: Appium客户端介绍
|
||||
---
|
||||
|
||||
TODO
|
||||
由于[概述](./index.md)中讨论的全部原因, Appium基于[W3C
|
||||
WebDriver 规范](https://w3c.github.io/webdriver/webdriver-spec.html).
|
||||
这意味着Appium实现了客户端-服务器架构.
|
||||
服务器(由Appium本身和您用于自动化的任何驱动程序或插件) 连接到被测设备,
|
||||
并且实际上负责在这些设备上实现自动化.
|
||||
客户端(由Appium测试作者 *您* 驱动)
|
||||
负责通过网络向服务器发送命令,
|
||||
并且接收从服务器响应的结果.
|
||||
这些响应可用于表示自动化命令是否成功,
|
||||
或者可能包含您查询的有关应用程序状态的信息.
|
||||
本文档是等同于对客户端的概念介绍.
|
||||
|
||||
!!! info
|
||||
|
||||
有关服务端的更多信息(例如, Appium如何实际控制设备?) ,
|
||||
可以查看我们的[Appium驱动简介](./drivers.md).
|
||||
要跳到Appium客户端的链接列表, 请查看[生态系统](../ecosystem/index.md) 文档
|
||||
|
||||
有哪些类型的自动化命令可用?
|
||||
这取决于您在任何给定会话中使用的特定的驱动程序和插件.
|
||||
一组标准命令将包括的内容, 如下所示:
|
||||
|
||||
- 查找元素
|
||||
- 点击元素
|
||||
- 获取源码
|
||||
- 截图
|
||||
|
||||
如果您在WebDriver规范中查看这些命令,
|
||||
您会注意到它们不是根据任何特定编程语言定义.
|
||||
它们不是Java命令或JavaScript命令或Python命令.
|
||||
相反, 它们构成HTTP API的一部分,
|
||||
可以从在*任何*编程语言中(或者没有!如果需要, 可以仅使用cURL) .
|
||||
|
||||
因此, 例如, `Find Element`命令对应于发送到HTTP的HTTP `POST` 请求端点
|
||||
`/session/:sessionid/element`
|
||||
(在本例中, `:sessionid`是服务器在先前调用`创建会话`时生成的唯一会话ID) .
|
||||
|
||||
这些信息主要对从事WebDriver规范开发的人有用.
|
||||
对于试图编写Appium或Selenium测试的人来说,
|
||||
它不是特别有用. 当你编写一个Appium测试时,
|
||||
你想使用你熟悉的编程语言.
|
||||
幸运的是存在一组[Appium客户端库](../ecosystem/index.md)[^1],
|
||||
用于处理负责向Appium服务器发送HTTP请求.
|
||||
相反, 它们为特定编程语言公开了一组 "原生" 命令,
|
||||
因此, 对于测试作者来说, 感觉就像是在编写Python、JavaScript或Java.
|
||||
|
||||
举个例子, 下面是在四种不同编程语言中
|
||||
使用的同一组简单的Appium命令,
|
||||
为每种语言使用推荐的Appium客户端绑定
|
||||
(注意, 这不是包括所需引用的可工作示例代码;
|
||||
请查看每个客户端库的说明设置和命令参考):
|
||||
|
||||
=== "JavaScript (Webdriver.io)"
|
||||
|
||||
```js
|
||||
const element = await driver.$('//*[@text="Foo"]');
|
||||
await element.click();
|
||||
console.log(await element.getText())
|
||||
console.log(await driver.getPageSource())
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java
|
||||
WebElement element = driver.findElement(By.Xpath("//*[@text='Foo']"))
|
||||
element.click()
|
||||
System.out.println(element.getText())
|
||||
System.out.println(driver.getPageSource())
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```py
|
||||
element = driver.find_element(by=By.XPATH, value='//*[@text="Foo"]')
|
||||
element.click()
|
||||
print(element.text)
|
||||
print(driver.page_source)
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```rb
|
||||
element = driver.find_element :xpath, '//*[@text="Foo"]'
|
||||
element.click
|
||||
puts element.text
|
||||
puts driver.page_source
|
||||
```
|
||||
|
||||
每一个脚本, 尽管使用不同的语言, 但在幕后做着相同的事情:
|
||||
|
||||
1. 使用`xpath`的 `using`参数和表示用于查找元素的XPath查询的`value`参数调用`查找元素`.
|
||||
(如果您对这些术语感到困惑, 您可能会发现对Appium或Selenium的介绍很有用)
|
||||
2. 使用上一次调用中找到的元素的ID调用`点击元素`.
|
||||
3. 使用相同元素的ID调用`获取元素文本`, 并将其打印到控制台.
|
||||
4. 调用`获取源码`以检索页面/应用程序源码并将其打印到控制台.
|
||||
|
||||
|
||||
在选择或使用客户端之前, 要记住的另一件事是, 每个客户端都是独立维护的.
|
||||
仅仅因为一个功能在一个客户端中可用, 并不意味着它在另一个客户端上可用
|
||||
(尽管所有客户端至少支持标准的W3C协议以及任何通用的appium扩展) .
|
||||
仅仅因为一个客户端有组很好的辅助函数,
|
||||
这并不意味着另一个会.
|
||||
一些客户经常保持及时最新, 而其他客户则不是!
|
||||
所以当考虑选择一个库时,
|
||||
首先要考虑的是您想要使用的语言,
|
||||
第二个考虑因素是类库功能齐全, 维护良好!
|
||||
|
||||
为了学习如何使用Appium客户端,
|
||||
请访问该客户的主页以了解更多信息.
|
||||
在许多情况下,
|
||||
给定语言的Appium客户端构建在该语言的*Selenium*客户端*之上*,
|
||||
因此某些Appium客户端可能仅记录Appium客户机在Selenium客户端之上添加的功能.
|
||||
所有这些都是为了全面参考,
|
||||
您可能需要访问Appium客户端文档以及Selenium客户端文档.
|
||||
|
||||
这就是您需要了解的关于Appium客户端的所有信息!
|
||||
转到[生态系统](../ecosystem/index.md) 页面查看当前的客户端列表.
|
||||
|
||||
[^1]: 这些库又称为 "客户端"、"客户端库" 或 "客户端绑定". 他们是同一种概念!
|
||||
|
||||
@@ -14,13 +14,9 @@ import AsyncLock from 'async-lock';
|
||||
import {parseCapsForInnerDriver, pullSettings} from './utils';
|
||||
import {util, node, logger} from '@appium/support';
|
||||
import {getDefaultsForExtension} from './schema';
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE} from './constants';
|
||||
import {DRIVER_TYPE} from './constants';
|
||||
|
||||
/**
|
||||
* Invariant set of base constraints
|
||||
* @type {Readonly<Constraints>}
|
||||
*/
|
||||
const desiredCapabilityConstraints = Object.freeze({
|
||||
const desiredCapabilityConstraints = /** @type {const} */ ({
|
||||
automationName: {
|
||||
presence: true,
|
||||
isString: true,
|
||||
@@ -30,6 +26,9 @@ const desiredCapabilityConstraints = Object.freeze({
|
||||
isString: true,
|
||||
},
|
||||
});
|
||||
/**
|
||||
* @typedef {typeof desiredCapabilityConstraints} AppiumDriverConstraints
|
||||
*/
|
||||
|
||||
const sessionsListGuard = new AsyncLock();
|
||||
const pendingDriversGuard = new AsyncLock();
|
||||
@@ -181,10 +180,10 @@ class AppiumDriver extends DriverCore {
|
||||
/**
|
||||
* Retrieves all CLI arguments for a specific plugin.
|
||||
* @param {string} extName - Plugin name
|
||||
* @returns {Record<string,unknown>} Arguments object. If none, an empty object.
|
||||
* @returns {StringRecord} Arguments object. If none, an empty object.
|
||||
*/
|
||||
getCliArgsForPlugin(extName) {
|
||||
return /** @type {Record<string,unknown>} */ (this.args.plugin?.[extName] ?? {});
|
||||
return /** @type {StringRecord} */ (this.args.plugin?.[extName] ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,12 +193,10 @@ class AppiumDriver extends DriverCore {
|
||||
*
|
||||
* _Note that this behavior currently (May 18 2022) differs from how plugins are handled_ (see {@linkcode AppiumDriver.getCliArgsForPlugin}).
|
||||
* @param {string} extName - Driver name
|
||||
* @returns {Record<string,unknown>|undefined} Arguments object. If none, `undefined`
|
||||
* @returns {StringRecord|undefined} Arguments object. If none, `undefined`
|
||||
*/
|
||||
getCliArgsForDriver(extName) {
|
||||
const allCliArgsForExt = /** @type {Record<string,unknown>|undefined} */ (
|
||||
this.args.driver?.[extName]
|
||||
);
|
||||
const allCliArgsForExt = /** @type {StringRecord|undefined} */ (this.args.driver?.[extName]);
|
||||
|
||||
if (!_.isEmpty(allCliArgsForExt)) {
|
||||
const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
|
||||
@@ -214,9 +211,9 @@ class AppiumDriver extends DriverCore {
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
* @param {W3CCapabilities} jsonwpCaps JSONWP formatted desired capabilities
|
||||
* @param {W3CCapabilities} reqCaps Required capabilities (JSONWP standard)
|
||||
* @param {W3CCapabilities} w3cCapabilities W3C capabilities
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} jsonwpCaps JSONWP formatted desired capabilities
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} reqCaps Required capabilities (JSONWP standard)
|
||||
* @param {W3CCapabilities<AppiumDriverConstraints>} w3cCapabilities W3C capabilities
|
||||
* @param {DriverData[]} [driverData]
|
||||
*/
|
||||
async createSession(jsonwpCaps, reqCaps, w3cCapabilities, driverData) {
|
||||
@@ -249,9 +246,11 @@ class AppiumDriver extends DriverCore {
|
||||
);
|
||||
|
||||
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
|
||||
/** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps);
|
||||
/** @type {import('./utils').ParsedDriverCaps<AppiumDriverConstraints>} */ (parsedCaps);
|
||||
protocol = parsedCaps.protocol;
|
||||
const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps).error;
|
||||
const error = /** @type {import('./utils').InvalidCaps<AppiumDriverConstraints>} */ (
|
||||
parsedCaps
|
||||
).error;
|
||||
// If the parsing of the caps produced an error, throw it in here
|
||||
if (error) {
|
||||
throw error;
|
||||
@@ -783,6 +782,11 @@ class AppiumDriver extends DriverCore {
|
||||
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {import('@appium/types').RouteMatcher[]}
|
||||
*/
|
||||
getProxyAvoidList(sessionId) {
|
||||
const dstSession = this.sessions[sessionId];
|
||||
return dstSession ? dstSession.getProxyAvoidList() : [];
|
||||
@@ -826,7 +830,6 @@ export {AppiumDriver};
|
||||
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').DriverData} DriverData
|
||||
* @typedef {import('@appium/types').ServerArgs} DriverOpts
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
@@ -837,6 +840,7 @@ export {AppiumDriver};
|
||||
* @typedef {import('@appium/types').PluginClass} PluginClass
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').SessionHandler<SessionHandlerResult<any[]>,SessionHandlerResult<void>>} SessionHandler
|
||||
*/
|
||||
|
||||
@@ -849,3 +853,13 @@ export {AppiumDriver};
|
||||
* @property {Error} [error]
|
||||
* @property {string} [protocol]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
*/
|
||||
|
||||
@@ -214,8 +214,8 @@ function makeRunArgs(type) {
|
||||
default: null,
|
||||
type: 'str',
|
||||
help:
|
||||
`Name of the script to run from the ${type}. The script name must be cached ` +
|
||||
`inside the "scripts" field under "appium" inside the ${type}'s "package.json" file`,
|
||||
`Name of the script to run from the ${type}. The script name must be a key ` +
|
||||
`inside the "appium.scripts" field inside the ${type}'s "package.json" file`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -33,8 +33,8 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
return await super._update({installSpec: driver, unsafe});
|
||||
}
|
||||
|
||||
async run({driver, scriptName}) {
|
||||
return await super._run({installSpec: driver, scriptName});
|
||||
async run({driver, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: driver, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
getPostInstallText({extName, extData}) {
|
||||
|
||||
@@ -576,13 +576,14 @@ class ExtensionCommand {
|
||||
// this is a helper method, 'ext' is assumed to already be installed here, and of the npm
|
||||
// install type
|
||||
const {version, pkgName} = this.config.installedExtensions[ext];
|
||||
/** @type {string?} */
|
||||
let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
|
||||
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
||||
this.config.appiumHome,
|
||||
pkgName,
|
||||
version
|
||||
);
|
||||
if (!util.compareVersions(unsafeUpdate, '>', version)) {
|
||||
if (unsafeUpdate !== null && !util.compareVersions(unsafeUpdate, '>', version)) {
|
||||
// the latest version is not greater than the current version, so there's no possible update
|
||||
unsafeUpdate = null;
|
||||
safeUpdate = null;
|
||||
@@ -628,7 +629,7 @@ class ExtensionCommand {
|
||||
* @param {RunOptions} opts
|
||||
* @return {Promise<RunOutput>}
|
||||
*/
|
||||
async _run({installSpec, scriptName}) {
|
||||
async _run({installSpec, scriptName, extraArgs = []}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
||||
}
|
||||
@@ -657,7 +658,7 @@ class ExtensionCommand {
|
||||
);
|
||||
}
|
||||
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName], ...extraArgs], {
|
||||
cwd: this.config.getInstallPath(installSpec),
|
||||
});
|
||||
|
||||
@@ -755,6 +756,7 @@ export {ExtensionCommand};
|
||||
* @typedef RunOptions
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,9 @@ export const commandClasses = Object.freeze(
|
||||
* Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
|
||||
* can be represented as a JS object.
|
||||
*
|
||||
* @param {Object} args - JS object where the key is the parameter name (as defined in
|
||||
* @param {import('appium/types').Args<import('appium/types').WithExtSubcommand>} args - JS object where the key is the parameter name (as defined in
|
||||
* driver-parser.js)
|
||||
* @template {import('../extension/manifest').ExtensionType} ExtType
|
||||
* @template {ExtensionType} ExtType
|
||||
* @param {import('../extension/extension-config').ExtensionConfig<ExtType>} config - Extension config object
|
||||
*/
|
||||
async function runExtensionCommand(args, config) {
|
||||
@@ -30,6 +30,7 @@ async function runExtensionCommand(args, config) {
|
||||
throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
|
||||
}
|
||||
let {json, suppressOutput} = args;
|
||||
json = Boolean(json);
|
||||
if (suppressOutput) {
|
||||
json = true;
|
||||
}
|
||||
@@ -56,6 +57,17 @@ async function runExtensionCommand(args, config) {
|
||||
export {runExtensionCommand};
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').ExtensionType} ExtType
|
||||
* @typedef {ExtType extends import('@appium/types').DriverType ? import('@appium/types').Class<DriverCommand> : ExtType extends import('@appium/types').PluginType ? import('@appium/types').Class<PluginCommand> : never} ExtCommand
|
||||
* @template {ExtensionType} ExtType
|
||||
* @typedef {ExtType extends DriverType ? Class<DriverCommand> : ExtType extends PluginType ? Class<PluginCommand> : never} ExtCommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('@appium/types').Class<T>} Class
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,8 @@ import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
|
||||
import {rootDir} from '../config';
|
||||
import {getExtensionArgs, getServerArgs} from './args';
|
||||
|
||||
export const EXTRA_ARGS = 'extraArgs';
|
||||
|
||||
/**
|
||||
* If the parsed args do not contain any of these values, then we
|
||||
* will automatially inject the `server` subcommand.
|
||||
@@ -97,8 +99,17 @@ class ArgParser {
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = this.parser.parse_args(args);
|
||||
return ArgParser._transformParsedArgs(parsed);
|
||||
const parsed = this.parser.parse_known_args(args);
|
||||
const [knownArgs, unknownArgs] = parsed;
|
||||
if (
|
||||
unknownArgs?.length &&
|
||||
(knownArgs.driverCommand === 'run' || knownArgs.pluginCommand === 'run')
|
||||
) {
|
||||
return ArgParser._transformParsedArgs(knownArgs, unknownArgs);
|
||||
} else if (unknownArgs?.length) {
|
||||
throw new Error(`[ERROR] Unrecognized arguments: ${unknownArgs.join(' ')}`);
|
||||
}
|
||||
return ArgParser._transformParsedArgs(knownArgs);
|
||||
} catch (err) {
|
||||
if (this.debug) {
|
||||
throw err;
|
||||
@@ -123,10 +134,11 @@ class ArgParser {
|
||||
*
|
||||
* E.g., `{'driver-foo-bar': baz}` becomes `{driver: {foo: {bar: 'baz'}}}`
|
||||
* @param {object} args
|
||||
* @param {string[]} [unknownArgs]
|
||||
* @returns {object}
|
||||
*/
|
||||
static _transformParsedArgs(args) {
|
||||
return _.reduce(
|
||||
static _transformParsedArgs(args, unknownArgs = []) {
|
||||
const result = _.reduce(
|
||||
args,
|
||||
(unpacked, value, key) => {
|
||||
if (!_.isUndefined(value) && hasArgSpec(key)) {
|
||||
@@ -140,6 +152,8 @@ class ArgParser {
|
||||
},
|
||||
{}
|
||||
);
|
||||
result[EXTRA_ARGS] = unknownArgs;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +197,7 @@ class ArgParser {
|
||||
* @param {import('argparse').SubParser} subParsers
|
||||
*/
|
||||
static _addExtensionCommandsToParser(subParsers) {
|
||||
for (const type of [DRIVER_TYPE, PLUGIN_TYPE]) {
|
||||
for (const type of /** @type {[DriverType, PluginType]} */ ([DRIVER_TYPE, PLUGIN_TYPE])) {
|
||||
const extParser = subParsers.add_parser(type, {
|
||||
add_help: true,
|
||||
help: `Access the ${type} management CLI commands`,
|
||||
@@ -195,6 +209,9 @@ class ArgParser {
|
||||
dest: `${type}Command`,
|
||||
});
|
||||
const extensionArgs = getExtensionArgs();
|
||||
/**
|
||||
* @type { {command: import('appium/types').CliExtensionSubcommand, args: import('./args').ArgumentDefinitions, help: string}[] }
|
||||
*/
|
||||
const parserSpecs = [
|
||||
{
|
||||
command: 'list',
|
||||
@@ -254,3 +271,8 @@ function getParser(debug) {
|
||||
}
|
||||
|
||||
export {getParser, ArgParser};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverType} DriverType
|
||||
* @typedef {import('@appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
@@ -33,8 +33,8 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
return await super._update({installSpec: plugin, unsafe});
|
||||
}
|
||||
|
||||
async run({plugin, scriptName}) {
|
||||
return await super._run({installSpec: plugin, scriptName});
|
||||
async run({plugin, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: plugin, scriptName, extraArgs});
|
||||
}
|
||||
|
||||
getPostInstallText({extName, extData}) {
|
||||
|
||||
@@ -161,7 +161,9 @@ export function normalizeConfig(config) {
|
||||
|
||||
return _.mapValues(mappedObj, (value, property) => {
|
||||
const nextSection = section ? `${section}.${property}` : property;
|
||||
return isSchemaTypeObject(value) ? normalize(config, nextSection) : value;
|
||||
return isSchemaTypeObject(schema.properties?.[property])
|
||||
? normalize(config, nextSection)
|
||||
: value;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -169,7 +171,7 @@ export function normalizeConfig(config) {
|
||||
* Returns `true` if the schema prop references an object, or if it's an object itself
|
||||
* @param {import('ajv').SchemaObject|object} schema - Referencing schema object
|
||||
*/
|
||||
const isSchemaTypeObject = (schema) => Boolean(schema.properties);
|
||||
const isSchemaTypeObject = (schema) => Boolean(schema?.properties || schema?.type === 'object');
|
||||
|
||||
return normalize(config);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import _ from 'lodash';
|
||||
import {system, fs} from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import {exec} from 'teen_process';
|
||||
import logger from './logger';
|
||||
import semver from 'semver';
|
||||
import findUp from 'find-up';
|
||||
import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
|
||||
@@ -12,6 +11,7 @@ const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
const APPIUM_VER = npmPackage.version;
|
||||
const MIN_NODE_VERSION = npmPackage.engines.node;
|
||||
const MIN_NPM_VERSION = npmPackage.engines.npm;
|
||||
|
||||
const GIT_META_ROOT = '.git';
|
||||
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
|
||||
@@ -28,6 +28,15 @@ function getNodeVersion() {
|
||||
return /** @type {import('semver').SemVer} */ (semver.coerce(process.version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns version of `npm`
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getNpmVersion() {
|
||||
const {stdout} = await exec(system.isWindows() ? 'npm.cmd' : 'npm', ['--version']);
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [useGithubApiFallback]
|
||||
*/
|
||||
@@ -133,7 +142,18 @@ function getBuildInfo() {
|
||||
function checkNodeOk() {
|
||||
const version = getNodeVersion();
|
||||
if (!semver.satisfies(version, MIN_NODE_VERSION)) {
|
||||
logger.errorAndThrow(`Node version must be ${MIN_NODE_VERSION}. Currently ${version.version}`);
|
||||
throw new Error(
|
||||
`Node version must be at least ${MIN_NODE_VERSION}; current is ${version.version}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkNpmOk() {
|
||||
const npmVersion = await getNpmVersion();
|
||||
if (!semver.satisfies(npmVersion, MIN_NPM_VERSION)) {
|
||||
throw new Error(
|
||||
`npm version must be at least ${MIN_NPM_VERSION}; current is ${npmVersion}. Run "npm install -g npm" to upgrade.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ export class DriverConfig extends ExtensionConfig {
|
||||
|
||||
/**
|
||||
* Given capabilities, find a matching driver within the config. Load its class and return it along with version and driver name.
|
||||
* @param {Capabilities} caps
|
||||
* @template {import('@appium/types').StringRecord} C
|
||||
* @param {C} caps
|
||||
* @returns {MatchedDriver}
|
||||
*/
|
||||
findMatchingDriver({automationName, platformName}) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Module containing {@link Manifest} which handles reading & writing of extension config files.
|
||||
*/
|
||||
|
||||
import B from 'bluebird';
|
||||
import glob from 'glob';
|
||||
import {env, fs} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
@@ -11,23 +13,6 @@ import log from '../logger';
|
||||
import {INSTALL_TYPE_NPM} from './extension-config';
|
||||
import {packageDidChange} from './package-changed';
|
||||
|
||||
/**
|
||||
* Default depth to search in directory tree for whatever it is we're looking for.
|
||||
*
|
||||
* It's 4 because smaller numbers didn't work.
|
||||
*/
|
||||
const DEFAULT_SEARCH_DEPTH = 4;
|
||||
|
||||
/**
|
||||
* Default options for {@link findExtensions}.
|
||||
* @type {Readonly<import('klaw').Options>}
|
||||
*/
|
||||
const DEFAULT_FIND_EXTENSIONS_OPTS = Object.freeze({
|
||||
depthLimit: DEFAULT_SEARCH_DEPTH,
|
||||
/* istanbul ignore next */
|
||||
filter: (filepath) => !path.basename(filepath).startsWith('.'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Current configuration schema revision!
|
||||
*/
|
||||
@@ -175,26 +160,58 @@ export class Manifest {
|
||||
|
||||
/**
|
||||
* Searches `APPIUM_HOME` for installed extensions and adds them to the manifest.
|
||||
* @param {SyncWithInstalledExtensionsOpts} opts
|
||||
* @returns {Promise<boolean>} `true` if any extensions were added, `false` otherwise.
|
||||
*/
|
||||
async syncWithInstalledExtensions({depthLimit = DEFAULT_SEARCH_DEPTH} = {}) {
|
||||
const walkOpts = _.defaults({depthLimit}, DEFAULT_FIND_EXTENSIONS_OPTS);
|
||||
async syncWithInstalledExtensions() {
|
||||
// this could be parallelized, but we can't use fs.walk as an async iterator
|
||||
let didChange = false;
|
||||
for await (const {stats, path: filepath} of fs.walk(this._appiumHome, walkOpts)) {
|
||||
if (filepath !== this._appiumHome && stats.isDirectory()) {
|
||||
try {
|
||||
const pkg = await env.readPackageInDir(filepath);
|
||||
if (pkg && isExtension(pkg)) {
|
||||
// it's possible that this extension already exists in the manifest,
|
||||
// so only update `didChange` if it's new.
|
||||
const added = this.addExtensionFromPackage(pkg, path.join(filepath, 'package.json'));
|
||||
didChange = didChange || added;
|
||||
|
||||
/**
|
||||
* Listener for the `match` event of a `glob` instance
|
||||
* @param {string} filepath - Path to a `package.json`
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const onMatch = async (filepath) => {
|
||||
try {
|
||||
const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
|
||||
if (isDriver(pkg) || isPlugin(pkg)) {
|
||||
const changed = this.addExtensionFromPackage(pkg, filepath);
|
||||
didChange = didChange || changed;
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of `Promise`s which read `package.json` files looking for Appium extensions.
|
||||
* @type {Promise<void>[]}
|
||||
*/
|
||||
const queue = [
|
||||
// look at `package.json` in `APPIUM_HOME` only
|
||||
onMatch(path.join(this._appiumHome, 'package.json')),
|
||||
];
|
||||
|
||||
// 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);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
.on('error', reject)
|
||||
.on('match', (filepath) => {
|
||||
queue.push(onMatch(filepath));
|
||||
});
|
||||
});
|
||||
|
||||
// wait for everything to finish
|
||||
await B.all(queue);
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
|
||||
+25
-31
@@ -4,7 +4,7 @@ import {init as logsinkInit} from './logsink'; // this import needs to come firs
|
||||
import logger from './logger'; // logger needs to remain second
|
||||
// @ts-ignore
|
||||
import {routeConfiguringFunction as makeRouter, server as baseServer} from '@appium/base-driver';
|
||||
import {logger as logFactory, util, env, fs} from '@appium/support';
|
||||
import {logger as logFactory, util, env} from '@appium/support';
|
||||
import {asyncify} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {AppiumDriver} from './appium';
|
||||
@@ -15,21 +15,20 @@ import {
|
||||
checkNodeOk,
|
||||
getGitRev,
|
||||
getNonDefaultServerArgs,
|
||||
rootDir,
|
||||
showConfig,
|
||||
showBuildInfo,
|
||||
validateTmpDir,
|
||||
warnNodeDeprecations,
|
||||
checkNpmOk,
|
||||
} from './config';
|
||||
import {readConfigFile} from './config-file';
|
||||
import {loadExtensions, getActivePlugins, getActiveDrivers} from './extension';
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from './constants';
|
||||
import registerNode from './grid-register';
|
||||
import {getDefaultsForSchema, validate} from './schema/schema';
|
||||
import {inspect} from './utils';
|
||||
import path from 'path';
|
||||
import {inspect, adjustNodePath} from './utils';
|
||||
|
||||
const {resolveAppiumHome, hasAppiumDependency} = env;
|
||||
const {resolveAppiumHome} = env;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,6 +38,7 @@ const {resolveAppiumHome, hasAppiumDependency} = env;
|
||||
async function preflightChecks(args, throwInsteadOfExit = false) {
|
||||
try {
|
||||
checkNodeOk();
|
||||
await checkNpmOk();
|
||||
if (args.longStacktrace) {
|
||||
require('longjohn').async_trace_limit = -1;
|
||||
}
|
||||
@@ -172,18 +172,7 @@ function areServerCommandArgs(args) {
|
||||
async function init(args) {
|
||||
const appiumHome = args?.appiumHome ?? (await resolveAppiumHome());
|
||||
|
||||
// this is here so extensions installed in e.g., `~/.appium` can find the peer dependency
|
||||
// of `appium`, which lives wherever it lives (see `rootDir`).
|
||||
if (!(await hasAppiumDependency(appiumHome))) {
|
||||
try {
|
||||
await fs.mkdirp(path.join(appiumHome, 'node_modules'));
|
||||
await fs.symlink(rootDir, path.join(appiumHome, 'node_modules', 'appium'), 'junction');
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw new Error(`Unable to create symlink to appium: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
adjustNodePath();
|
||||
|
||||
const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
|
||||
|
||||
@@ -222,20 +211,7 @@ async function init(args) {
|
||||
// 1. command line args
|
||||
// 2. config file
|
||||
// 3. defaults from config file.
|
||||
if (!areServerCommandArgs(preConfigArgs)) {
|
||||
// if the user has requested the 'driver' CLI, don't run the normal server,
|
||||
// but instead pass control to the driver CLI
|
||||
if (preConfigArgs.subcommand === DRIVER_TYPE) {
|
||||
await runExtensionCommand(preConfigArgs, driverConfig);
|
||||
return {};
|
||||
}
|
||||
if (preConfigArgs.subcommand === PLUGIN_TYPE) {
|
||||
await runExtensionCommand(preConfigArgs, pluginConfig);
|
||||
return {};
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return {}; // should never happen
|
||||
} else {
|
||||
if (areServerCommandArgs(preConfigArgs)) {
|
||||
const defaults = getDefaultsForSchema(false);
|
||||
|
||||
/** @type {ParsedArgs} */
|
||||
@@ -282,6 +258,22 @@ async function init(args) {
|
||||
driverConfig,
|
||||
pluginConfig,
|
||||
});
|
||||
} else {
|
||||
const extensionCommandArgs = /** @type {Args<import('appium/types').WithExtSubcommand>} */ (
|
||||
preConfigArgs
|
||||
);
|
||||
// if the user has requested the 'driver' CLI, don't run the normal server,
|
||||
// but instead pass control to the driver CLI
|
||||
if (preConfigArgs.subcommand === DRIVER_TYPE) {
|
||||
await runExtensionCommand(extensionCommandArgs, driverConfig);
|
||||
return {};
|
||||
}
|
||||
if (preConfigArgs.subcommand === PLUGIN_TYPE) {
|
||||
await runExtensionCommand(extensionCommandArgs, pluginConfig);
|
||||
return {};
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return {}; // should never happen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +306,7 @@ async function main(args) {
|
||||
const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
|
||||
const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
|
||||
|
||||
/** @type {import('@appium/base-driver').ServerOpts} */
|
||||
const serverOpts = {
|
||||
routeConfiguringFunction,
|
||||
port: parsedArgs.port,
|
||||
@@ -322,6 +315,7 @@ async function main(args) {
|
||||
basePath: parsedArgs.basePath,
|
||||
serverUpdaters,
|
||||
extraMethodMap,
|
||||
cliArgs: parsedArgs,
|
||||
};
|
||||
if (parsedArgs.keepAliveTimeout) {
|
||||
serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ArgumentTypeError} from 'argparse';
|
||||
import {readFileSync} from 'fs';
|
||||
import {readFileSync, existsSync} from 'fs';
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
@@ -55,25 +55,30 @@ export const transformers = {
|
||||
/**
|
||||
* Given a CSV-style string or pathname, parse it into an array.
|
||||
* The file can also be split on newlines.
|
||||
* @param {string} value
|
||||
* @param {string} csvOrPath
|
||||
* @returns {string[]}
|
||||
*/
|
||||
csv: (value) => {
|
||||
let body;
|
||||
csv: (csvOrPath) => {
|
||||
let csv = csvOrPath;
|
||||
let loadedFromFile = false;
|
||||
// since this value could be a single string (no commas) _or_ a pathname, we will need
|
||||
// to attempt to parse it as a file _first_.
|
||||
try {
|
||||
body = readFileSync(value, 'utf8');
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw new ArgumentTypeError(`Could not read file ${body}: ${err.message}`);
|
||||
if (existsSync(csvOrPath)) {
|
||||
try {
|
||||
csv = readFileSync(csvOrPath, 'utf8');
|
||||
} catch (err) {
|
||||
throw new ArgumentTypeError(`Could not read file '${csvOrPath}': ${err.message}`);
|
||||
}
|
||||
loadedFromFile = true;
|
||||
}
|
||||
|
||||
try {
|
||||
return body ? parseCsvFile(body) : parseCsvLine(value);
|
||||
return loadedFromFile ? parseCsvFile(csv) : parseCsvLine(csv);
|
||||
} catch (err) {
|
||||
throw new ArgumentTypeError('Must be a comma-delimited string, e.g., "foo,bar,baz"');
|
||||
const msg = loadedFromFile
|
||||
? `The provided value of '${csvOrPath}' must be a valid CSV`
|
||||
: `Must be a comma-delimited string, e.g., "foo,bar,baz"`;
|
||||
throw new TypeError(`${msg}. Original error: ${err.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -85,19 +90,18 @@ export const transformers = {
|
||||
json: (jsonOrPath) => {
|
||||
let json = jsonOrPath;
|
||||
let loadedFromFile = false;
|
||||
try {
|
||||
// use synchronous file access, as `argparse` provides no way of either
|
||||
// awaiting or using callbacks. This step happens in startup, in what is
|
||||
// effectively command-line code, so nothing is blocked in terms of
|
||||
// sessions, so holding up the event loop does not incur the usual
|
||||
// drawbacks.
|
||||
json = readFileSync(jsonOrPath, 'utf8');
|
||||
loadedFromFile = true;
|
||||
} catch (err) {
|
||||
// unreadable files don't count... but other problems do.
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
if (existsSync(jsonOrPath)) {
|
||||
try {
|
||||
// use synchronous file access, as `argparse` provides no way of either
|
||||
// awaiting or using callbacks. This step happens in startup, in what is
|
||||
// effectively command-line code, so nothing is blocked in terms of
|
||||
// sessions, so holding up the event loop does not incur the usual
|
||||
// drawbacks.
|
||||
json = readFileSync(jsonOrPath, 'utf8');
|
||||
} catch (err) {
|
||||
throw new ArgumentTypeError(`Could not read file '${jsonOrPath}': ${err.message}`);
|
||||
}
|
||||
loadedFromFile = true;
|
||||
}
|
||||
try {
|
||||
const result = JSON.parse(json);
|
||||
|
||||
+143
-62
@@ -1,9 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import {processCapabilities, PROTOCOLS} from '@appium/base-driver';
|
||||
import {processCapabilities, PROTOCOLS, STANDARD_CAPS} from '@appium/base-driver';
|
||||
import {inspect as dump} from 'util';
|
||||
import {node} from '@appium/support';
|
||||
import path from 'path';
|
||||
|
||||
const W3C_APPIUM_PREFIX = 'appium';
|
||||
const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -33,16 +36,18 @@ const inspect = _.flow(
|
||||
* Takes the caps that were provided in the request and translates them
|
||||
* into caps that can be used by the inner drivers.
|
||||
*
|
||||
* @param {any} jsonwpCapabilities
|
||||
* @param {W3CCapabilities} w3cCapabilities
|
||||
* @param {import('@appium/types').Constraints} constraints
|
||||
* @param {import('@appium/types').DefaultCapabilitiesConfig} [defaultCapabilities]
|
||||
* @returns {ParsedDriverCaps|InvalidCaps}
|
||||
* @template {Constraints} C
|
||||
* @template [J=any]
|
||||
* @param {J} jsonwpCapabilities
|
||||
* @param {W3CCapabilities<C>} w3cCapabilities
|
||||
* @param {C} constraints
|
||||
* @param {NSCapabilities<C>} [defaultCapabilities]
|
||||
* @returns {ParsedDriverCaps<C,J>|InvalidCaps<C,J>}
|
||||
*/
|
||||
function parseCapsForInnerDriver(
|
||||
jsonwpCapabilities,
|
||||
w3cCapabilities,
|
||||
constraints = {},
|
||||
constraints = /** @type {C} */ ({}),
|
||||
defaultCapabilities = {}
|
||||
) {
|
||||
// Check if the caller sent JSONWP caps, W3C caps, or both
|
||||
@@ -50,14 +55,14 @@ function parseCapsForInnerDriver(
|
||||
_.isPlainObject(w3cCapabilities) &&
|
||||
(_.has(w3cCapabilities, 'alwaysMatch') || _.has(w3cCapabilities, 'firstMatch'));
|
||||
const hasJSONWPCaps = _.isPlainObject(jsonwpCapabilities);
|
||||
let desiredCaps = /** @type {ParsedDriverCaps['desiredCaps']} */ ({});
|
||||
/** @type {ParsedDriverCaps['processedW3CCapabilities']} */
|
||||
let desiredCaps = /** @type {ParsedDriverCaps<C>['desiredCaps']} */ ({});
|
||||
/** @type {ParsedDriverCaps<C>['processedW3CCapabilities']} */
|
||||
let processedW3CCapabilities;
|
||||
/** @type {ParsedDriverCaps['processedJsonwpCapabilities']} */
|
||||
/** @type {ParsedDriverCaps<C>['processedJsonwpCapabilities']} */
|
||||
let processedJsonwpCapabilities;
|
||||
|
||||
if (!hasW3CCaps) {
|
||||
return /** @type {InvalidCaps} */ ({
|
||||
return /** @type {InvalidCaps<C>} */ ({
|
||||
protocol: PROTOCOLS.W3C,
|
||||
error: new Error('W3C capabilities should be provided'),
|
||||
});
|
||||
@@ -76,7 +81,7 @@ function parseCapsForInnerDriver(
|
||||
for (const [defaultCapKey, defaultCapValue] of _.toPairs(defaultCapabilities)) {
|
||||
let isCapAlreadySet = false;
|
||||
// Check if the key is already present in firstMatch entries
|
||||
for (const firstMatchEntry of w3cCapabilities.firstMatch || []) {
|
||||
for (const firstMatchEntry of w3cCapabilities.firstMatch ?? []) {
|
||||
if (
|
||||
_.isPlainObject(firstMatchEntry) &&
|
||||
_.has(removeAppiumPrefixes(firstMatchEntry), removeAppiumPrefix(defaultCapKey))
|
||||
@@ -100,7 +105,9 @@ function parseCapsForInnerDriver(
|
||||
|
||||
// Only add the default capability if it is not overridden
|
||||
if (_.isEmpty(w3cCapabilities.firstMatch)) {
|
||||
w3cCapabilities.firstMatch = [{[defaultCapKey]: defaultCapValue}];
|
||||
w3cCapabilities.firstMatch = /** @type {W3CCapabilities<C>['firstMatch']} */ ([
|
||||
{[defaultCapKey]: defaultCapValue},
|
||||
]);
|
||||
} else {
|
||||
w3cCapabilities.firstMatch[0][defaultCapKey] = defaultCapValue;
|
||||
}
|
||||
@@ -127,7 +134,7 @@ function parseCapsForInnerDriver(
|
||||
desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
|
||||
} catch (error) {
|
||||
logger.info(`Could not parse W3C capabilities: ${error.message}`);
|
||||
return /** @type {InvalidCaps} */ ({
|
||||
return /** @type {InvalidCaps<C,J>} */ ({
|
||||
desiredCaps,
|
||||
processedJsonwpCapabilities,
|
||||
processedW3CCapabilities,
|
||||
@@ -143,7 +150,7 @@ function parseCapsForInnerDriver(
|
||||
};
|
||||
}
|
||||
|
||||
return /** @type {ParsedDriverCaps} */ ({
|
||||
return /** @type {ParsedDriverCaps<C,J>} */ ({
|
||||
desiredCaps,
|
||||
processedJsonwpCapabilities,
|
||||
processedW3CCapabilities,
|
||||
@@ -153,62 +160,101 @@ function parseCapsForInnerDriver(
|
||||
|
||||
/**
|
||||
* Takes a capabilities objects and prefixes capabilities with `appium:`
|
||||
* @param {Capabilities} caps Desired capabilities object
|
||||
* @returns {AppiumW3CCapabilities}
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {Capabilities<C>} caps - Desired capabilities object
|
||||
* @returns {NSCapabilities<C>}
|
||||
*/
|
||||
function insertAppiumPrefixes(caps) {
|
||||
// Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
|
||||
const STANDARD_CAPS = [
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
'acceptInsecureCerts',
|
||||
'pageLoadStrategy',
|
||||
'proxy',
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
];
|
||||
|
||||
let prefixedCaps = {};
|
||||
for (let [name, value] of _.toPairs(caps)) {
|
||||
if (STANDARD_CAPS.includes(name) || name.includes(':')) {
|
||||
prefixedCaps[name] = value;
|
||||
} else {
|
||||
prefixedCaps[`${W3C_APPIUM_PREFIX}:${name}`] = value;
|
||||
}
|
||||
}
|
||||
return prefixedCaps;
|
||||
return /** @type {NSCapabilities<C>} */ (
|
||||
_.mapKeys(caps, (_, key) =>
|
||||
STANDARD_CAPS_LOWERCASE.has(key.toLowerCase()) || key.includes(':')
|
||||
? key
|
||||
: `${W3C_APPIUM_PREFIX}:${key}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AppiumW3CCapabilities} caps
|
||||
* @returns {Capabilities}
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {NSCapabilities<C>} caps
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function removeAppiumPrefixes(caps) {
|
||||
if (!_.isPlainObject(caps)) {
|
||||
return caps;
|
||||
}
|
||||
|
||||
/** @type {Capabilities} */
|
||||
const fixedCaps = {};
|
||||
for (let [name, value] of _.toPairs(caps)) {
|
||||
fixedCaps[removeAppiumPrefix(name)] = value;
|
||||
}
|
||||
return fixedCaps;
|
||||
return /** @type {Capabilities<C>} */ (_.mapKeys(caps, (_, key) => removeAppiumPrefix(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @returns {string}
|
||||
*/
|
||||
function removeAppiumPrefix(key) {
|
||||
const prefix = `${W3C_APPIUM_PREFIX}:`;
|
||||
return _.startsWith(key, prefix) ? key.substring(prefix.length) : key;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pkgName
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
function getPackageVersion(pkgName) {
|
||||
const pkgInfo = require(`${pkgName}/package.json`) || {};
|
||||
return pkgInfo.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts NODE_PATH environment variable,
|
||||
* so drivers and plugins could load their peer dependencies.
|
||||
* Read https://nodejs.org/api/modules.html#loading-from-the-global-folders
|
||||
* for more details.
|
||||
* @returns {void}
|
||||
*/
|
||||
function adjustNodePath() {
|
||||
const selfRoot = node.getModuleRootSync('appium', __filename);
|
||||
if (!selfRoot || path.dirname(selfRoot).length >= selfRoot.length) {
|
||||
return;
|
||||
}
|
||||
const nodeModulesRoot = path.dirname(selfRoot);
|
||||
|
||||
const refreshRequirePaths = () => {
|
||||
try {
|
||||
// ! This hack allows us to avoid modification of import
|
||||
// ! statements in client modules. It uses a private API though,
|
||||
// ! so it could break (maybe, eventually).
|
||||
// See https://gist.github.com/branneman/8048520#7-the-hack
|
||||
// @ts-ignore
|
||||
require('module').Module._initPaths();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!process.env.NODE_PATH) {
|
||||
process.env.NODE_PATH = nodeModulesRoot;
|
||||
if (refreshRequirePaths()) {
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
delete process.env.NODE_PATH;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const nodePathParts = process.env.NODE_PATH.split(path.delimiter);
|
||||
if (nodePathParts.includes(nodeModulesRoot)) {
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
return;
|
||||
}
|
||||
|
||||
nodePathParts.push(nodeModulesRoot);
|
||||
process.env.NODE_PATH = nodePathParts.join(path.delimiter);
|
||||
if (refreshRequirePaths()) {
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
process.env.NODE_PATH = _.without(nodePathParts, nodeModulesRoot).join(path.delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the initial values of Appium settings from the given capabilities argument.
|
||||
* Each setting item must satisfy the following format:
|
||||
@@ -250,29 +296,64 @@ export {
|
||||
getPackageVersion,
|
||||
pullSettings,
|
||||
removeAppiumPrefixes,
|
||||
adjustNodePath,
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo protocol is more specific
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template [J=any]
|
||||
* @typedef ParsedDriverCaps
|
||||
* @property {Capabilities} desiredCaps
|
||||
* @property {Capabilities<C>} desiredCaps
|
||||
* @property {string} protocol
|
||||
* @property {any} [processedJsonwpCapabilities]
|
||||
* @property {W3CCapabilities} [processedW3CCapabilities]
|
||||
* @property {J} [processedJsonwpCapabilities]
|
||||
* @property {W3CCapabilities<C>} [processedW3CCapabilities]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo protocol is more specific
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template [J=any]
|
||||
* @typedef InvalidCaps
|
||||
* @property {Error} error
|
||||
* @property {string} protocol
|
||||
* @property {Capabilities} [desiredCaps]
|
||||
* @property {any} [processedJsonwpCapabilities]
|
||||
* @property {W3CCapabilities} [processedW3CCapabilities]
|
||||
* @property {Capabilities<C>} [desiredCaps]
|
||||
* @property {J} [processedJsonwpCapabilities]
|
||||
* @property {W3CCapabilities<C>} [processedW3CCapabilities]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').NSCapabilities<C, Extra>} NSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('type-fest').StringKeyOf<T>} StringKeyOf
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.41",
|
||||
"version": "2.0.0-beta.46",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -37,13 +37,13 @@
|
||||
"driver.*",
|
||||
"support.*",
|
||||
"plugin.*",
|
||||
"test.*",
|
||||
"scripts/autoinstall-extensions.js",
|
||||
"types"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "babel lib --root-mode=upward --out-dir=build/lib",
|
||||
"build:docs": "node docs/scripts/build-docs.js",
|
||||
"build:docs:cli": "node docs/scripts/gen-cli-args-docs.js",
|
||||
"build:docs:assets": "node docs/scripts/copy-assets.js",
|
||||
"dev": "npm run build -- --watch",
|
||||
"dev:docs": "npm run build:docs:assets && npm run dev:docs:en",
|
||||
@@ -54,26 +54,24 @@
|
||||
"postinstall": "node ./scripts/autoinstall-extensions.js",
|
||||
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
|
||||
"prepare": "npm run build",
|
||||
"publish:docs": "APPIUM_DOCS_PUBLISH=1 npm run build:docs",
|
||||
"publish:docs": "cross-env APPIUM_DOCS_PUBLISH=1 npm run build:docs",
|
||||
"test": "npm run test:unit",
|
||||
"test:e2e": "mocha --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
|
||||
"test:smoke": "node ./index.js --show-build-info",
|
||||
"test:smoke": "cross-env APPIUM_HOME=./local_appium_home node ./index.js driver install uiautomator2 && cross-env APPIUM_HOME=./local_appium_home node ./index.js driver list",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.6.0",
|
||||
"@appium/base-plugin": "^1.10.0",
|
||||
"@appium/docutils": "^0.0.8",
|
||||
"@appium/schema": "^0.0.8",
|
||||
"@appium/support": "^2.59.3",
|
||||
"@appium/test-support": "^1.4.0",
|
||||
"@appium/types": "^0.3.0",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@sidvind/better-ajv-errors": "2.0.0",
|
||||
"@appium/base-driver": "^8.7.3",
|
||||
"@appium/base-plugin": "^1.10.5",
|
||||
"@appium/docutils": "^0.0.13",
|
||||
"@appium/schema": "^0.0.9",
|
||||
"@appium/support": "^2.61.1",
|
||||
"@appium/types": "^0.5.0",
|
||||
"@sidvind/better-ajv-errors": "2.1.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"@types/fancy-log": "2.0.0",
|
||||
"@types/semver": "7.3.10",
|
||||
"@types/semver": "7.3.12",
|
||||
"@types/teen_process": "1.16.1",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.11.0",
|
||||
@@ -83,7 +81,9 @@
|
||||
"asyncbox": "2.9.2",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "8.0.3",
|
||||
"lilconfig": "2.0.6",
|
||||
"lodash": "4.17.21",
|
||||
"longjohn": "0.2.12",
|
||||
@@ -91,17 +91,17 @@
|
||||
"ora": "5.4.1",
|
||||
"package-changed": "1.9.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.3.7",
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0",
|
||||
"type-fest": "2.17.0",
|
||||
"winston": "3.8.1",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.1.0",
|
||||
"winston": "3.8.2",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.1.1"
|
||||
"yaml": "2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// @ts-check
|
||||
|
||||
const {writeFileSync} = require('fs');
|
||||
const path = require('path');
|
||||
let parser;
|
||||
try {
|
||||
parser = require('../build/lib/cli/parser.js');
|
||||
} catch {
|
||||
console.error('Could not load parser CLI file; please execute `npm run build`');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** @type {import('../lib/cli/args').ArgumentDefinitions} */
|
||||
const appiumArguments = parser.getParser().rawArgs;
|
||||
const docFile = path.normalize(
|
||||
path.join(__dirname, '..', '..', '..', 'docs', 'en', 'writing-running-appium', 'server-args.md')
|
||||
);
|
||||
let md = `
|
||||
<!-- THIS FILE IS AUTO-GENERATED BY scripts/write-server-args-docs.js; DO NOT EDIT -->
|
||||
# Appium server arguments
|
||||
|
||||
Many Appium 1.5 server arguments have been deprecated in favor of the [--default-capabilities flag](/docs/en/writing-running-appium/default-capabilities-arg.md).
|
||||
|
||||
Usage: \`node . [flags]\`
|
||||
|
||||
## Server Flags
|
||||
|
||||
All flags are optional, but some are required in conjunction with certain others.
|
||||
|
||||
<expand_table>
|
||||
|
||||
|Flag|Default|Description|Example|
|
||||
|----|-------|-----------|-------|
|
||||
`;
|
||||
appiumArguments.forEach(function handleArguments(argOpts, argNames) {
|
||||
// handle empty objects
|
||||
if (JSON.stringify(argOpts.default) === '{}') {
|
||||
argOpts.default = '{}';
|
||||
}
|
||||
|
||||
md += '|`' + argNames.join('`, `') + '`';
|
||||
md += '|' + (typeof argOpts.default === 'undefined' ? '' : argOpts.default);
|
||||
md += '|' + argOpts.help;
|
||||
md += '|';
|
||||
md += '|\n';
|
||||
});
|
||||
|
||||
try {
|
||||
writeFileSync(docFile, md);
|
||||
console.error(
|
||||
'New docs written! Do not forget to commit:\ngit add -A %s && git commit -m "Update %s"',
|
||||
path.relative(process.cwd(), docFile),
|
||||
path.basename(docFile)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Could not write to file %s: %s', docFile, err);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
export * from '@appium/test-support';
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').SessionHelpers} SessionHelpers
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').E2ESetupOpts} E2ESetupOpts
|
||||
*/
|
||||
@@ -1,13 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* This module is here to re-export `@appium/test-support` for Appium extensions.
|
||||
*
|
||||
* @see https://npm.im/@appium/test-support
|
||||
* @example
|
||||
* const { getPort } = require('appium/test');
|
||||
*/
|
||||
|
||||
module.exports = require('@appium/test-support');
|
||||
@@ -1,5 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import getPort from 'get-port';
|
||||
import {exec} from 'teen_process';
|
||||
import {npm, env, fs, tempDir, util} from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
import path from 'path';
|
||||
@@ -15,13 +17,14 @@ import {
|
||||
KNOWN_DRIVERS,
|
||||
PLUGIN_TYPE,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
import {FAKE_DRIVER_DIR, PACKAGE_ROOT, resolveFixture} from '../helpers';
|
||||
import {
|
||||
installLocalExtension,
|
||||
runAppiumJson,
|
||||
runAppiumRaw,
|
||||
readAppiumArgErrorFixture,
|
||||
formatAppiumArgErrorOutput,
|
||||
EXECUTABLE,
|
||||
} from './e2e-helpers';
|
||||
|
||||
const {MANIFEST_RELATIVE_PATH} = env;
|
||||
@@ -38,6 +41,22 @@ describe('CLI behavior', function () {
|
||||
resolveFixture('test-driver-invalid-peer-dep/package.json')
|
||||
);
|
||||
|
||||
describe('when appium is already installed', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
it('should not duplicate it as peer dependencies for drivers', async function () {
|
||||
const run = runAppiumJson(appiumHome);
|
||||
await run([DRIVER_TYPE, INSTALL, 'appium-fake-driver', '--source', 'npm']);
|
||||
await fs.exists(path.join(appiumHome, 'node_modules', 'appium')).should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when appium is a dependency of the project in the current working directory', function () {
|
||||
/** @type {string} */
|
||||
let hashPath;
|
||||
@@ -69,108 +88,129 @@ describe('CLI behavior', function () {
|
||||
*/
|
||||
let runJson;
|
||||
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
// an example package.json referencing appium dependency
|
||||
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
});
|
||||
|
||||
describe('without drivers installed', function () {
|
||||
it('should list no drivers', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.satisfy(
|
||||
/** @param {typeof res} value */ (value) =>
|
||||
Object.values(value).every(({installed}) => !installed)
|
||||
);
|
||||
describe('when the project is an extension', function () {
|
||||
before(async function () {
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
await fs.copyFile(resolveFixture('test-driver/package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
it('should automatically discover the extension', async function () {
|
||||
const res = await runJson([DRIVER_TYPE, LIST]);
|
||||
expect(res).to.have.property('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('after a driver is installed', function () {
|
||||
describe('when the project is not an extension', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
appiumHome = await tempDir.openDir();
|
||||
hashPath = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
||||
manifestPath = path.join(appiumHome, MANIFEST_RELATIVE_PATH);
|
||||
appiumHomePkgPath = path.join(appiumHome, 'package.json');
|
||||
runJson = runAppiumJson(appiumHome);
|
||||
|
||||
// an example package.json referencing appium dependency
|
||||
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
|
||||
});
|
||||
|
||||
it('should list the driver', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should be resolvable from the local directory', function () {
|
||||
(() => resolveFrom(appiumHome, '@appium/fake-driver/package.json')).should.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a driver is installed via npm', function () {
|
||||
before(async function () {
|
||||
// remove the hash file and the manifest to start clean.
|
||||
// this leaves any previously-installed extension in place, which should make this a little faster.
|
||||
await fs.rimraf(path.dirname(hashPath));
|
||||
await npm.exec(INSTALL, [FAKE_DRIVER_DIR], {
|
||||
json: true,
|
||||
cwd: appiumHome,
|
||||
describe('without drivers installed', function () {
|
||||
it('should list no drivers', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.satisfy(
|
||||
/** @param {typeof res} value */ (value) =>
|
||||
Object.values(value).every(({installed}) => !installed)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(LIST, function () {
|
||||
/** @type {ExtensionListData} */
|
||||
let res;
|
||||
|
||||
beforeEach(async function () {
|
||||
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
describe('after a driver is installed', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
it('should list the driver', function () {
|
||||
|
||||
it('should list the driver', async function () {
|
||||
const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should update the manifest', async function () {
|
||||
const manifestParsed = await readManifest();
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should update the package hash', async function () {
|
||||
(await fs.exists(hashPath)).should.be.true;
|
||||
it('should be resolvable from the local directory', function () {
|
||||
(() => resolveFrom(appiumHome, '@appium/fake-driver/package.json')).should.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a different driver is installed via "appium driver install"', function () {
|
||||
/** @type {string} */
|
||||
let oldHash;
|
||||
|
||||
describe('when a driver is installed via npm', function () {
|
||||
before(async function () {
|
||||
await runJson([DRIVER_TYPE, LIST]);
|
||||
oldHash = await readHash();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
// remove the hash file and the manifest to start clean.
|
||||
// this leaves any previously-installed extension in place, which should make this a little faster.
|
||||
await fs.rimraf(path.dirname(hashPath));
|
||||
await npm.exec(INSTALL, [FAKE_DRIVER_DIR], {
|
||||
json: true,
|
||||
cwd: appiumHome,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update package.json', async function () {
|
||||
const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8'));
|
||||
expect(newPkg).to.have.nested.property('devDependencies.test-driver');
|
||||
});
|
||||
describe(LIST, function () {
|
||||
/** @type {ExtensionListData} */
|
||||
let res;
|
||||
|
||||
it('should update the hash', async function () {
|
||||
const newHash = await readHash();
|
||||
newHash.should.not.equal(oldHash);
|
||||
});
|
||||
beforeEach(async function () {
|
||||
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
|
||||
});
|
||||
|
||||
it('should update the manifest with the new driver', async function () {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
/** @type {ManifestData} */
|
||||
const manifestParsed = YAML.parse(manifest);
|
||||
manifestParsed.should.have.nested.property('drivers.test');
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
it('should list the driver', function () {
|
||||
res.should.have.property('fake');
|
||||
});
|
||||
|
||||
it('should actually install both drivers', function () {
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
|
||||
it('should update the manifest', async function () {
|
||||
const manifestParsed = await readManifest();
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should update the package hash', async function () {
|
||||
(await fs.exists(hashPath)).should.be.true;
|
||||
});
|
||||
|
||||
describe('when a different driver is installed via "appium driver install"', function () {
|
||||
/** @type {string} */
|
||||
let oldHash;
|
||||
|
||||
before(async function () {
|
||||
await runJson([DRIVER_TYPE, LIST]);
|
||||
oldHash = await readHash();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
});
|
||||
|
||||
it('should update package.json', async function () {
|
||||
const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8'));
|
||||
expect(newPkg).to.have.nested.property('devDependencies.@appium/test-driver');
|
||||
});
|
||||
|
||||
it('should update the hash', async function () {
|
||||
const newHash = await readHash();
|
||||
newHash.should.not.equal(oldHash);
|
||||
});
|
||||
|
||||
it('should update the manifest with the new driver', async function () {
|
||||
const manifest = await fs.readFile(manifestPath, 'utf8');
|
||||
/** @type {ManifestData} */
|
||||
const manifestParsed = YAML.parse(manifest);
|
||||
manifestParsed.should.have.nested.property('drivers.test');
|
||||
manifestParsed.should.have.nested.property('drivers.fake');
|
||||
});
|
||||
|
||||
it('should actually install both drivers', function () {
|
||||
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
|
||||
expect(() => resolveFrom(appiumHome, '@appium/test-driver')).not.to.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -267,6 +307,14 @@ describe('CLI behavior', function () {
|
||||
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST, '--updates'], {});
|
||||
stderr.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
|
||||
});
|
||||
|
||||
describe('if a driver is not published to npm', function () {
|
||||
it('should not throw an error', async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
await expect(runList(['--updates'])).not.to.be.rejected;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(INSTALL, function () {
|
||||
@@ -438,28 +486,49 @@ describe('CLI behavior', function () {
|
||||
});
|
||||
|
||||
describe('run', function () {
|
||||
const driverName = 'fake';
|
||||
before(async function () {
|
||||
await clear();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
it('should run a valid driver, valid script, and result in success', async function () {
|
||||
const driverName = 'fake';
|
||||
|
||||
describe('when the driver and script is valid', function () {
|
||||
const scriptName = 'fake-success';
|
||||
const out = await runRun([driverName, scriptName]);
|
||||
out.should.not.have.property('error');
|
||||
|
||||
describe('when the script completes successfully', function () {
|
||||
it('should result in success', async function () {
|
||||
const out = await runRun([driverName, scriptName]);
|
||||
out.should.not.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the script fails', function () {
|
||||
it('should return an error', async function () {
|
||||
const out = await runRun([driverName, 'fake-error']);
|
||||
out.should.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed extra arguments', function () {
|
||||
it('should pass them to the script', async function () {
|
||||
const out = await runRun([driverName, scriptName, '--foo', '--bar']);
|
||||
out.should.not.have.property('error');
|
||||
out.output.should.match(/--foo --bar/);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should run a valid driver, valid error prone script, and return error in json', async function () {
|
||||
const driverName = 'fake';
|
||||
const out = await runRun([driverName, 'fake-error']);
|
||||
out.should.have.property('error');
|
||||
|
||||
describe('when the driver is valid but the script is not', function () {
|
||||
it('should throw an error', async function () {
|
||||
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
it('should take a valid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'fake';
|
||||
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
it('should take an invalid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'foo';
|
||||
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
|
||||
|
||||
describe('when the driver and script are invalid', function () {
|
||||
it('should throw an error', async function () {
|
||||
const driverName = 'foo';
|
||||
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -502,7 +571,41 @@ describe('CLI behavior', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('argument error handling', function () {
|
||||
describe('argument parsing', function () {
|
||||
describe('when the user provides a very long string for an arg accepting a blob or filename', function () {
|
||||
describe('when the very long string is a JSON blob', function () {
|
||||
it('should not throw an ENAMETOOLONG exception', async function () {
|
||||
this.timeout(10000);
|
||||
const capsArg = JSON.stringify({
|
||||
'appium:platformName': 'ANDROID',
|
||||
'appium:platformVersion': '11',
|
||||
'appium:deviceName':
|
||||
'Spicy jalapeno bacon ipsum dolor amet deserunt tempor pork belly aliqua drumstick, occaecat dolor venison et labore. Rump meatball pork chop tail. Consequat adipisicing kielbasa occaecat laborum pig. Qui pork chop chicken nostrud boudin fugiat. Proident ut culpa, chuck nulla sunt pastrami ut tri-tip. Buffalo dolore adipisicing, labore venison elit beef fatback kevin burgdoggen tail pancetta filet mignon. Dolor turducken rump, anim kevin sunt exercitation ham filet mignon beef ribs ad officia eiusmod id cillum.',
|
||||
});
|
||||
|
||||
await expect(
|
||||
exec(
|
||||
process.execPath,
|
||||
[
|
||||
EXECUTABLE,
|
||||
'-pa=/wd/hub',
|
||||
'--session-override',
|
||||
'--local-timezone',
|
||||
'--relaxed-security',
|
||||
`--default-capabilities=${capsArg}`,
|
||||
'--port',
|
||||
await getPort(),
|
||||
],
|
||||
{
|
||||
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
|
||||
cwd: PACKAGE_ROOT,
|
||||
timeout: 5000,
|
||||
}
|
||||
)
|
||||
).to.be.rejectedWith(Error, /timed out/);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when the user provides an string where a number was expected', function () {
|
||||
describe('when color output is supported', function () {
|
||||
it('should output a fancy error message', async function () {
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('config file behavior', function () {
|
||||
it('should return a valid config object', async function () {
|
||||
const result = await readConfigFile(GOOD_FILEPATH);
|
||||
result.should.deep.equal({
|
||||
config: require(GOOD_FILEPATH),
|
||||
config: normalizeConfig(require(GOOD_FILEPATH)),
|
||||
filepath: GOOD_FILEPATH,
|
||||
errors: [],
|
||||
});
|
||||
@@ -74,7 +74,7 @@ describe('config file behavior', function () {
|
||||
it('should return a valid config object', async function () {
|
||||
const result = await readConfigFile(SECURITY_ARRAY_FILEPATH);
|
||||
result.should.deep.equal({
|
||||
config: require(SECURITY_ARRAY_FILEPATH),
|
||||
config: normalizeConfig(require(SECURITY_ARRAY_FILEPATH)),
|
||||
filepath: SECURITY_ARRAY_FILEPATH,
|
||||
errors: [],
|
||||
});
|
||||
|
||||
@@ -114,6 +114,14 @@ describe('FakeDriver - via HTTP', function () {
|
||||
const {data} = await axios.get(`${testServerBaseUrl}/fakedriver`);
|
||||
data.should.eql({fakedriver: 'fakeResponse'});
|
||||
});
|
||||
it('should update the server with cliArgs', async function () {
|
||||
// we don't need to check the entire object, since it's large, but we can ensure an
|
||||
// arg got through.
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/fakedriverCliArgs`)).data.should.have.property(
|
||||
'appiumHome',
|
||||
appiumHome
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli args handling for empty args', function () {
|
||||
|
||||
@@ -24,7 +24,7 @@ const wdOpts = {
|
||||
capabilities: W3C_PREFIXED_CAPS,
|
||||
};
|
||||
const FAKE_DRIVER_DIR = path.join(PROJECT_ROOT, 'packages', 'fake-driver');
|
||||
const FAKE_PLUGIN_DIR = path.join(PROJECT_ROOT, 'node_modules', '@appium', 'fake-plugin');
|
||||
const FAKE_PLUGIN_DIR = path.join(PROJECT_ROOT, 'packages', 'fake-plugin');
|
||||
|
||||
describe('FakePlugin', function () {
|
||||
/** @type {string} */
|
||||
@@ -150,9 +150,11 @@ describe('FakePlugin', function () {
|
||||
describe(`with plugin registered via type ${registrationType}`, function () {
|
||||
/** @type {AppiumServer} */
|
||||
let server;
|
||||
/** @type {import('type-fest').LiteralUnion<'all', string>[]} */
|
||||
let usePlugins;
|
||||
before(async function () {
|
||||
// then start server if we need to
|
||||
const usePlugins = registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all'];
|
||||
usePlugins = registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all'];
|
||||
const args = {
|
||||
appiumHome,
|
||||
port,
|
||||
@@ -171,7 +173,12 @@ describe('FakePlugin', function () {
|
||||
const res = {fake: 'fakeResponse'};
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/fake`)).data.should.eql(res);
|
||||
});
|
||||
|
||||
it('should update the server with cliArgs', async function () {
|
||||
const res = usePlugins;
|
||||
// we don't need to check the entire object, since it's large, but we can ensure an
|
||||
// arg got through.
|
||||
(await axios.post(`http://${TEST_HOST}:${port}/cliArgs`)).data.usePlugins.should.eql(res);
|
||||
});
|
||||
it('should modify the method map with new commands', async function () {
|
||||
const driver = await wdio(wdOpts);
|
||||
const {sessionId} = driver;
|
||||
|
||||
@@ -1 +1 @@
|
||||
main.js: error: unrecognized arguments: --pigs=sheep
|
||||
[ERROR] Unrecognized arguments: --pigs=sheep
|
||||
|
||||
+5
-2
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"name": "test-driver",
|
||||
"name": "@appium/test-driver",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0"
|
||||
"appium": "^2.0.0-beta.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"appium": "next"
|
||||
},
|
||||
"appium": {
|
||||
"driverName": "test",
|
||||
|
||||
@@ -33,6 +33,11 @@ describe('config-file', function () {
|
||||
*/
|
||||
let formatErrors;
|
||||
|
||||
/**
|
||||
* @type {import('appium/lib/config-file').normalizeConfig}
|
||||
*/
|
||||
let normalizeConfig;
|
||||
|
||||
/**
|
||||
* Mock instance of `lilconfig` containing stubs for
|
||||
* `lilconfig#search()` and `lilconfig#load`.
|
||||
@@ -93,7 +98,7 @@ describe('config-file', function () {
|
||||
// loads the `config-file` module using the lilconfig mock.
|
||||
// we only mock lilconfig because it'd otherwise be a pain in the rear to test
|
||||
// searching for config files, and it increases the likelihood that we'd load the wrong file.
|
||||
({readConfigFile, formatErrors} = rewiremock.proxy(
|
||||
({readConfigFile, formatErrors, normalizeConfig} = rewiremock.proxy(
|
||||
() => require('../../lib/config-file'),
|
||||
mocks
|
||||
));
|
||||
@@ -114,19 +119,19 @@ describe('config-file', function () {
|
||||
|
||||
it('should support yaml', async function () {
|
||||
const {config} = await readConfigFile(GOOD_YAML_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should support json', async function () {
|
||||
const {config} = await readConfigFile(GOOD_JSON_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should support js', async function () {
|
||||
const {config} = await readConfigFile(GOOD_JS_CONFIG_FILEPATH);
|
||||
expect(config).to.eql(GOOD_JSON_CONFIG);
|
||||
expect(config).to.eql(normalizeConfig(GOOD_JSON_CONFIG));
|
||||
expect(schema.validate).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
@@ -188,7 +193,7 @@ describe('config-file', function () {
|
||||
|
||||
it('should resolve with an object having `config` property and empty array of errors', function () {
|
||||
expect(result).to.deep.equal({
|
||||
config: GOOD_JSON_CONFIG,
|
||||
config: normalizeConfig(GOOD_JSON_CONFIG),
|
||||
errors: [],
|
||||
filepath: GOOD_JSON_CONFIG_FILEPATH,
|
||||
});
|
||||
@@ -291,7 +296,7 @@ describe('config-file', function () {
|
||||
it('should resolve with an object having `config` property and empty array of errors', function () {
|
||||
expect(result).to.deep.equal({
|
||||
errors: [],
|
||||
config: GOOD_JSON_CONFIG,
|
||||
config: normalizeConfig(GOOD_JSON_CONFIG),
|
||||
filepath: GOOD_JSON_CONFIG_FILEPATH,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('Config', function () {
|
||||
{
|
||||
config: {
|
||||
// @ts-expect-error
|
||||
server: {callbackAddress: 'quux'},
|
||||
server: {'callback-address': 'quux'},
|
||||
},
|
||||
},
|
||||
{port: 1234},
|
||||
|
||||
@@ -5,6 +5,7 @@ import {DRIVER_TYPE, PLUGIN_TYPE} from '../../../lib/constants';
|
||||
import {resolveFixture, rewiremock} from '../../helpers';
|
||||
import {initMocks} from './mocks';
|
||||
import {version as APPIUM_VER} from '../../../package.json';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -23,6 +24,9 @@ describe('Manifest', function () {
|
||||
/** @type {import('./mocks').MockAppiumSupport} */
|
||||
let MockAppiumSupport;
|
||||
|
||||
/** @type {import('./mocks').MockGlob} */
|
||||
let MockGlob;
|
||||
|
||||
before(async function () {
|
||||
yamlFixture = await fs.readFile(resolveFixture('extensions.yaml'), 'utf8');
|
||||
});
|
||||
@@ -34,7 +38,7 @@ describe('Manifest', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
let overrides;
|
||||
({MockPackageChanged, MockAppiumSupport, overrides, sandbox} = initMocks());
|
||||
({MockPackageChanged, MockAppiumSupport, MockGlob, overrides, sandbox} = initMocks());
|
||||
MockAppiumSupport.fs.readFile.resolves(yamlFixture);
|
||||
({Manifest} = rewiremock.proxy(() => require('../../../lib/extension/manifest'), overrides));
|
||||
|
||||
@@ -504,49 +508,60 @@ describe('Manifest', function () {
|
||||
|
||||
describe('syncWithInstalledExtensions()', function () {
|
||||
beforeEach(function () {
|
||||
const next = sandbox.stub();
|
||||
next.onFirstCall().resolves({
|
||||
value: {
|
||||
stats: {
|
||||
isDirectory: sandbox.stub().returns(true),
|
||||
MockAppiumSupport.fs.readFile.resolves(
|
||||
JSON.stringify({
|
||||
name: 'foo',
|
||||
version: '1.0.0',
|
||||
readme: 'stuff!',
|
||||
_id: 'totally unique',
|
||||
appium: {
|
||||
automationName: 'derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
driverName: 'myDriver',
|
||||
},
|
||||
path: '/some/dir',
|
||||
},
|
||||
});
|
||||
next.onSecondCall().resolves({
|
||||
done: true,
|
||||
});
|
||||
MockAppiumSupport.fs.walk.returns(
|
||||
/** @type {import('klaw').Walker} */ (
|
||||
/** @type {unknown} */ ({
|
||||
[Symbol.asyncIterator]: sandbox.stub().returns({
|
||||
next,
|
||||
}),
|
||||
})
|
||||
)
|
||||
peerDependencies: {
|
||||
appium: '2.0.0',
|
||||
},
|
||||
})
|
||||
);
|
||||
MockAppiumSupport.env.readPackageInDir.resolves({
|
||||
name: 'foo',
|
||||
version: '1.0.0',
|
||||
readme: 'stuff!',
|
||||
_id: 'totally unique',
|
||||
appium: {
|
||||
automationName: 'derp',
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
platformNames: ['dogs', 'cats'],
|
||||
driverName: 'myDriver',
|
||||
},
|
||||
peerDependencies: {
|
||||
appium: '2.0.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a found extension', async function () {
|
||||
await manifest.syncWithInstalledExtensions();
|
||||
expect(manifest.getExtensionData(DRIVER_TYPE)).to.have.property('myDriver');
|
||||
});
|
||||
|
||||
describe('when the underyling implementation emits "error"', function () {
|
||||
beforeEach(function () {
|
||||
MockGlob.callsFake(() => {
|
||||
const ee = new EventEmitter();
|
||||
setTimeout(() => {
|
||||
ee.emit('error', new Error('bogus'));
|
||||
});
|
||||
return ee;
|
||||
});
|
||||
});
|
||||
it('should reject', function () {
|
||||
expect(manifest.syncWithInstalledExtensions()).to.be.rejectedWith(Error, 'bogus');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the underlying implementation completes with an error', function () {
|
||||
beforeEach(function () {
|
||||
MockGlob.callsFake((spec, opts, done) => {
|
||||
const ee = new EventEmitter();
|
||||
setTimeout(() => {
|
||||
done(new Error('wack'), []);
|
||||
});
|
||||
return ee;
|
||||
});
|
||||
});
|
||||
it('should reject', function () {
|
||||
expect(manifest.syncWithInstalledExtensions()).to.be.rejectedWith(Error, 'wack');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDriver()', function () {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* A collection of mocks reused across unit tests.
|
||||
*/
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
import path from 'path';
|
||||
import {createSandbox} from 'sinon';
|
||||
import {version as APPIUM_VER} from '../../../package.json';
|
||||
@@ -109,17 +110,32 @@ export function initMocks(sandbox = createSandbox()) {
|
||||
sandbox.stub().callsFake((cwd, id) => path.join(cwd, id))
|
||||
);
|
||||
|
||||
const MockGlob = /** @type {MockGlob} */ (
|
||||
sandbox.stub().callsFake((spec, opts, done) => {
|
||||
const ee = new EventEmitter();
|
||||
setTimeout(() => {
|
||||
ee.emit('match', path.join(opts.cwd, 'package.json'));
|
||||
setTimeout(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
return ee;
|
||||
})
|
||||
);
|
||||
|
||||
/** @type {Overrides} */
|
||||
const overrides = {
|
||||
'@appium/support': MockAppiumSupport,
|
||||
'resolve-from': MockResolveFrom,
|
||||
'package-changed': MockPackageChanged,
|
||||
glob: MockGlob,
|
||||
};
|
||||
|
||||
return {
|
||||
MockAppiumSupport,
|
||||
MockPackageChanged,
|
||||
MockResolveFrom,
|
||||
MockGlob,
|
||||
sandbox,
|
||||
overrides,
|
||||
};
|
||||
@@ -194,9 +210,14 @@ export function initMocks(sandbox = createSandbox()) {
|
||||
* @typedef {SinonStubbedMember<import('resolve-from')>} MockResolveFrom
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock of package `glob`
|
||||
* @typedef {SinonStubbedMember<import('glob')>} MockGlob
|
||||
*/
|
||||
|
||||
/**
|
||||
* For passing into `rewiremock.proxy()`
|
||||
* @typedef { { '@appium/support': MockAppiumSupport, 'resolve-from': MockResolveFrom, 'package-changed': MockPackageChanged } } Overrides
|
||||
* @typedef { { '@appium/support': MockAppiumSupport, 'resolve-from': MockResolveFrom, 'package-changed': MockPackageChanged, glob: MockGlob } } Overrides
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
pullSettings,
|
||||
removeAppiumPrefixes,
|
||||
inspect,
|
||||
adjustNodePath,
|
||||
} from '../../lib/utils';
|
||||
import {BASE_CAPS, W3C_CAPS} from '../helpers';
|
||||
import _ from 'lodash';
|
||||
import {stripColors} from '@colors/colors';
|
||||
import {createSandbox} from 'sinon';
|
||||
import logger from '../../lib/logger';
|
||||
import {fs} from '@appium/support';
|
||||
|
||||
describe('utils', function () {
|
||||
describe('parseCapsForInnerDriver()', function () {
|
||||
@@ -252,4 +254,25 @@ describe('utils', function () {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adjustNodePath()', function () {
|
||||
const prevValue = process.env.NODE_PATH;
|
||||
|
||||
beforeEach(function () {
|
||||
if (process.env.NODE_PATH) {
|
||||
delete process.env.NODE_PATH;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (prevValue) {
|
||||
process.env.NODE_PATH = prevValue;
|
||||
}
|
||||
});
|
||||
|
||||
it('should adjust NODE_PATH', async function () {
|
||||
adjustNodePath();
|
||||
(await fs.exists(process.env.NODE_PATH)).should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"@appium/base-plugin": ["../base-plugin"],
|
||||
"@appium/types": ["../types"],
|
||||
"@appium/schema": ["../schema"],
|
||||
"@appium/test-support": ["../test-support"],
|
||||
"appium": ["."]
|
||||
},
|
||||
"checkJs": true
|
||||
|
||||
@@ -92,6 +92,21 @@ export interface ExtArgs extends WithExtSubcommand {
|
||||
* Subcommands of `plugin` subcommand
|
||||
*/
|
||||
pluginCommand?: CliExtensionSubcommand;
|
||||
|
||||
/**
|
||||
* Output JSON instead of human-readable text
|
||||
*/
|
||||
json?: boolean;
|
||||
|
||||
/**
|
||||
* Output nothing
|
||||
*/
|
||||
suppressOutput?: boolean;
|
||||
|
||||
/**
|
||||
* Extra args to pass to extension scripts
|
||||
*/
|
||||
extraArgs?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.7.3](https://github.com/appium/appium/compare/@appium/base-driver@8.7.2...@appium/base-driver@8.7.3) (2022-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **basedriver:** ensure "opts" is defined at time of construction ([51d6d69](https://github.com/appium/appium/commit/51d6d69ff13a0cc23d612257926c02892685dcee))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.2](https://github.com/appium/appium/compare/@appium/base-driver@8.7.1...@appium/base-driver@8.7.2) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.1](https://github.com/appium/appium/compare/@appium/base-driver@8.7.0...@appium/base-driver@8.7.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.7.0](https://github.com/appium/appium/compare/@appium/base-driver@8.6.1...@appium/base-driver@8.7.0) (2022-08-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,base-driver,fake-driver,fake-plugin,test-support,types:** updateServer receives cliArgs param ([d4b9833](https://github.com/appium/appium/commit/d4b983328af21d1e5c27a91e438e7934eb152ab1)), closes [#17304](https://github.com/appium/appium/issues/17304)
|
||||
* **base-driver,fake-driver,appium:** add convenience methods for defining execute script overloads ([#17321](https://github.com/appium/appium/issues/17321)) ([337ec3e](https://github.com/appium/appium/commit/337ec3e7ba216dd6f8cdc88143ecaa4c75f5d266))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.1](https://github.com/appium/appium/compare/@appium/base-driver@8.6.0...@appium/base-driver@8.6.1) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.6.0](https://github.com/appium/appium/compare/@appium/base-driver@8.5.7...@appium/base-driver@8.6.0) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -10,17 +10,21 @@ const APPIUM_VENDOR_PREFIX = 'appium:';
|
||||
const APPIUM_OPTS_CAP = 'options';
|
||||
const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}${APPIUM_OPTS_CAP}`;
|
||||
|
||||
// Takes primary caps object and merges it into a secondary caps object.
|
||||
// (see https://www.w3.org/TR/webdriver/#dfn-merging-capabilities)
|
||||
/**
|
||||
* @param {Capabilities} [primary]
|
||||
* @param {Capabilities} [secondary]
|
||||
* @returns {Capabilities}
|
||||
* Takes primary caps object and merges it into a secondary caps object.
|
||||
* @template {Constraints} [T={}]
|
||||
* @template {Constraints} [U={}]
|
||||
* @param {Capabilities<T>} [primary]
|
||||
* @param {Capabilities<U>} [secondary]
|
||||
* @returns {import('type-fest').Merge<Capabilities<T>, Capabilities<U>>}
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-merging-capabilities)
|
||||
*/
|
||||
function mergeCaps(primary = {}, secondary = {}) {
|
||||
let result = Object.assign({}, primary);
|
||||
let result = /** @type {import('type-fest').Merge<Capabilities<T>, Capabilities<U>>} */ ({
|
||||
...primary,
|
||||
});
|
||||
|
||||
for (let [name, value] of _.toPairs(secondary)) {
|
||||
for (let [name, value] of /** @type {[keyof typeof secondary, any]} */ (_.toPairs(secondary))) {
|
||||
// Overwriting is not allowed. Primary and secondary must have different properties (w3c rule 4.4)
|
||||
if (!_.isUndefined(primary[name])) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
@@ -29,7 +33,7 @@ function mergeCaps(primary = {}, secondary = {}) {
|
||||
)}) and secondary (${JSON.stringify(secondary)}) object`
|
||||
);
|
||||
}
|
||||
result[name] = value;
|
||||
result[/** @type {keyof typeof result} */ (name)] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -37,29 +41,37 @@ function mergeCaps(primary = {}, secondary = {}) {
|
||||
|
||||
// Validates caps against a set of constraints
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities} caps
|
||||
* @param {Constraints} [constraints]
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {Capabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {ValidateCapsOpts} [opts]
|
||||
* @returns {Capabilities}
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function validateCaps(caps, constraints = {}, opts = {}) {
|
||||
function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) {
|
||||
let {skipPresenceConstraint} = opts;
|
||||
|
||||
if (!_.isPlainObject(caps)) {
|
||||
throw new errors.InvalidArgumentError(`must be a JSON object`);
|
||||
}
|
||||
|
||||
constraints = _.cloneDeep(constraints); // Defensive copy
|
||||
// Remove the 'presence' constraint if we're not checking for it
|
||||
constraints = /** @type {C} */ (
|
||||
_.mapValues(
|
||||
constraints,
|
||||
skipPresenceConstraint
|
||||
? /** @param {Constraint} constraint */
|
||||
(constraint) => _.omit(constraint, 'presence')
|
||||
: /** @param {Constraint} constraint */
|
||||
(constraint) => {
|
||||
if (constraint.presence === true) {
|
||||
return {..._.omit(constraint, 'presence'), presence: {allowEmpty: false}};
|
||||
}
|
||||
return constraint;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (skipPresenceConstraint) {
|
||||
// Remove the 'presence' constraint if we're not checking for it
|
||||
for (let key of _.keys(constraints)) {
|
||||
delete constraints[key].presence;
|
||||
}
|
||||
}
|
||||
|
||||
let validationErrors = validator.validate(_.pickBy(caps, util.hasValue), constraints, {
|
||||
const validationErrors = validator.validate(_.pickBy(caps, util.hasValue), constraints, {
|
||||
fullMessages: false,
|
||||
});
|
||||
|
||||
@@ -77,54 +89,78 @@ function validateCaps(caps, constraints = {}, opts = {}) {
|
||||
return caps;
|
||||
}
|
||||
|
||||
// Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
|
||||
const STANDARD_CAPS = [
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
'acceptInsecureCerts',
|
||||
'pageLoadStrategy',
|
||||
'proxy',
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
];
|
||||
/**
|
||||
* Standard, non-prefixed capabilities
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
|
||||
*/
|
||||
export const STANDARD_CAPS = Object.freeze(
|
||||
new Set(
|
||||
/** @type {import('type-fest').StringKeyOf<import('@appium/types').StandardCapabilities>[]} */ ([
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
'acceptInsecureCerts',
|
||||
'pageLoadStrategy',
|
||||
'proxy',
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
const STANDARD_CAPS_LOWER = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
|
||||
|
||||
/**
|
||||
* @param {string} cap
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isStandardCap(cap) {
|
||||
return !!_.find(
|
||||
STANDARD_CAPS,
|
||||
(standardCap) => standardCap.toLowerCase() === `${cap}`.toLowerCase()
|
||||
);
|
||||
return STANDARD_CAPS_LOWER.has(cap.toLowerCase());
|
||||
}
|
||||
|
||||
// If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix (see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities)
|
||||
// (NOTE: Method is destructive and mutates contents of caps)
|
||||
/**
|
||||
* If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').NSCapabilities<C>} caps
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities
|
||||
* @internal
|
||||
* @returns {import('@appium/types').Capabilities<C>}
|
||||
*/
|
||||
function stripAppiumPrefixes(caps) {
|
||||
const prefix = 'appium:';
|
||||
const prefixedCaps = _.filter(_.keys(caps), (cap) => `${cap}`.startsWith(prefix));
|
||||
// split into prefixed and non-prefixed.
|
||||
// non-prefixed should be standard caps at this point
|
||||
const [prefixedCaps, nonPrefixedCaps] = _.partition(_.keys(caps), (cap) =>
|
||||
String(cap).startsWith(APPIUM_VENDOR_PREFIX)
|
||||
);
|
||||
|
||||
// initialize this with the k/v pairs of the non-prefixed caps
|
||||
let strippedCaps = /** @type {import('@appium/types').Capabilities<C>} */ (
|
||||
_.pick(caps, nonPrefixedCaps)
|
||||
);
|
||||
const badPrefixedCaps = [];
|
||||
|
||||
// Strip out the 'appium:' prefix
|
||||
for (let prefixedCap of prefixedCaps) {
|
||||
const strippedCapName = prefixedCap.substr(prefix.length);
|
||||
const strippedCapName =
|
||||
/** @type {import('type-fest').StringKeyOf<import('@appium/types').Capabilities<C>>} */ (
|
||||
prefixedCap.substring(APPIUM_VENDOR_PREFIX.length)
|
||||
);
|
||||
|
||||
// If it's standard capability that was prefixed, add it to an array of incorrectly prefixed capabilities
|
||||
if (isStandardCap(strippedCapName)) {
|
||||
badPrefixedCaps.push(strippedCapName);
|
||||
if (_.isNil(caps[strippedCapName])) {
|
||||
caps[strippedCapName] = caps[prefixedCap];
|
||||
if (_.isNil(strippedCaps[strippedCapName])) {
|
||||
strippedCaps[strippedCapName] = caps[prefixedCap];
|
||||
} else {
|
||||
log.warn(
|
||||
`Ignoring capability '${prefixedCap}=${caps[prefixedCap]}' and ` +
|
||||
`using capability '${strippedCapName}=${caps[strippedCapName]}'`
|
||||
`using capability '${strippedCapName}=${strippedCaps[strippedCapName]}'`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
caps[strippedCapName] = caps[prefixedCap];
|
||||
strippedCaps[strippedCapName] = caps[prefixedCap];
|
||||
}
|
||||
|
||||
// Strip out the prefix
|
||||
delete caps[prefixedCap];
|
||||
}
|
||||
|
||||
// If we found standard caps that were incorrectly prefixed, throw an exception (e.g.: don't accept 'appium:platformName', only accept just 'platformName')
|
||||
@@ -135,11 +171,13 @@ function stripAppiumPrefixes(caps) {
|
||||
)} are standard capabilities and do not require "appium:" prefix`
|
||||
);
|
||||
}
|
||||
return strippedCaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all the unprefixed caps that are being used in 'alwaysMatch' and all of the 'firstMatch' object
|
||||
* @param {Object} caps A capabilities object
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} caps A capabilities object
|
||||
*/
|
||||
function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
return _.chain([alwaysMatch, ...firstMatch])
|
||||
@@ -154,15 +192,15 @@ function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
.value();
|
||||
}
|
||||
|
||||
// Parse capabilities (based on https://www.w3.org/TR/webdriver/#processing-capabilities)
|
||||
/**
|
||||
*
|
||||
* @param {W3CCapabilities} caps
|
||||
* @param {Constraints} [constraints]
|
||||
* Parse capabilities
|
||||
* @template {Constraints} [C={}]
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @returns
|
||||
* @see https://www.w3.org/TR/webdriver/#processing-capabilities
|
||||
*/
|
||||
function parseCaps(caps, constraints = {}, shouldValidateCaps = true) {
|
||||
function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps = true) {
|
||||
// If capabilities request is not an object, return error (#1.1)
|
||||
if (!_.isPlainObject(caps)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
@@ -203,49 +241,47 @@ function parseCaps(caps, constraints = {}, shouldValidateCaps = true) {
|
||||
}
|
||||
|
||||
// Strip out the 'appium:' prefix from all
|
||||
stripAppiumPrefixes(requiredCaps);
|
||||
for (let firstMatchCaps of allFirstMatchCaps) {
|
||||
stripAppiumPrefixes(firstMatchCaps);
|
||||
}
|
||||
let strippedRequiredCaps = stripAppiumPrefixes(requiredCaps);
|
||||
let strippedAllFirstMatchCaps = allFirstMatchCaps.map(stripAppiumPrefixes);
|
||||
|
||||
// Validate the requiredCaps. But don't validate 'presence' because if that constraint fails on 'alwaysMatch' it could still pass on one of the 'firstMatch' keys
|
||||
if (shouldValidateCaps) {
|
||||
requiredCaps = validateCaps(requiredCaps, constraints, {
|
||||
strippedRequiredCaps = validateCaps(strippedRequiredCaps, constraints, {
|
||||
skipPresenceConstraint: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the 'presence' constraint for any keys that are already present in 'requiredCaps'
|
||||
// since we know that this constraint has already passed
|
||||
let filteredConstraints = {...constraints};
|
||||
let requiredCapsKeys = _.keys(requiredCaps);
|
||||
for (let key of _.keys(filteredConstraints)) {
|
||||
if (requiredCapsKeys.includes(key)) {
|
||||
delete filteredConstraints[key];
|
||||
}
|
||||
}
|
||||
const filteredConstraints = /** @type {C} */ (
|
||||
_.omitBy(constraints, (_, key) => key in strippedRequiredCaps)
|
||||
);
|
||||
|
||||
// Validate all of the first match capabilities and return an array with only the valid caps (see spec #5)
|
||||
/** @type {string[]} */
|
||||
let validationErrors = [];
|
||||
/** @type {Capabilities[]} */
|
||||
let validatedFirstMatchCaps = _.compact(
|
||||
allFirstMatchCaps.map((firstMatchCaps) => {
|
||||
try {
|
||||
// Validate firstMatch caps
|
||||
return shouldValidateCaps
|
||||
? validateCaps(firstMatchCaps, filteredConstraints)
|
||||
: firstMatchCaps;
|
||||
} catch (e) {
|
||||
validationErrors.push(e.message);
|
||||
strippedAllFirstMatchCaps.map(
|
||||
/**
|
||||
* @param {import('@appium/types').Capabilities<C>} firstMatchCaps
|
||||
*/
|
||||
(firstMatchCaps) => {
|
||||
try {
|
||||
// Validate firstMatch caps
|
||||
return shouldValidateCaps
|
||||
? validateCaps(firstMatchCaps, filteredConstraints)
|
||||
: firstMatchCaps;
|
||||
} catch (e) {
|
||||
validationErrors.push(e.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Try to merge requiredCaps with first match capabilities, break once it finds its first match (see spec #6)
|
||||
let matchedCaps = null;
|
||||
for (let firstMatchCaps of validatedFirstMatchCaps) {
|
||||
try {
|
||||
matchedCaps = mergeCaps(requiredCaps, firstMatchCaps);
|
||||
matchedCaps = mergeCaps(strippedRequiredCaps, firstMatchCaps);
|
||||
if (matchedCaps) {
|
||||
break;
|
||||
}
|
||||
@@ -267,13 +303,17 @@ function parseCaps(caps, constraints = {}, shouldValidateCaps = true) {
|
||||
|
||||
// Calls parseCaps and just returns the matchedCaps variable
|
||||
/**
|
||||
*
|
||||
* @param {W3CCapabilities} w3cCaps
|
||||
* @param {Constraints} [constraints]
|
||||
* @template {Constraints} C
|
||||
* @param {import('@appium/types').W3CCapabilities<C>} w3cCaps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @returns {Capabilities}
|
||||
* @returns {import('@appium/types').Capabilities<C>}
|
||||
*/
|
||||
function processCapabilities(w3cCaps, constraints = {}, shouldValidateCaps = true) {
|
||||
function processCapabilities(
|
||||
w3cCaps,
|
||||
constraints = /** @type {C} */ ({}),
|
||||
shouldValidateCaps = true
|
||||
) {
|
||||
const {matchedCaps, validationErrors} = parseCaps(w3cCaps, constraints, shouldValidateCaps);
|
||||
|
||||
// If we found an error throw an exception
|
||||
@@ -291,7 +331,7 @@ function processCapabilities(w3cCaps, constraints = {}, shouldValidateCaps = tru
|
||||
}
|
||||
}
|
||||
|
||||
return matchedCaps ?? {};
|
||||
return /** @type {Capabilities<C>} */ (matchedCaps ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,10 +357,9 @@ function promoteAppiumOptions(originalCaps) {
|
||||
}
|
||||
|
||||
// first get rid of any prefixes inside appium:options
|
||||
stripAppiumPrefixes(appiumOptions);
|
||||
|
||||
const strippedAppiumOptions = stripAppiumPrefixes(appiumOptions);
|
||||
// warn if we are going to overwrite any keys on the base caps object
|
||||
const overwrittenKeys = _.intersection(Object.keys(caps), Object.keys(appiumOptions));
|
||||
const overwrittenKeys = _.intersection(Object.keys(caps), Object.keys(strippedAppiumOptions));
|
||||
if (overwrittenKeys.length > 0) {
|
||||
log.warn(
|
||||
`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` +
|
||||
@@ -329,7 +368,7 @@ function promoteAppiumOptions(originalCaps) {
|
||||
}
|
||||
|
||||
// now just apply them to the main caps object
|
||||
caps = {...caps, ...appiumOptions};
|
||||
caps = {...caps, ...strippedAppiumOptions};
|
||||
|
||||
// and remove all traces of the options cap
|
||||
delete caps[APPIUM_OPTS_CAP];
|
||||
@@ -351,12 +390,31 @@ export {
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').Constraint} Constraint
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ValidateCapsOpts
|
||||
* @property {boolean} [skipPresenceConstraint] - if true, skip the presence constraint
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').NSCapabilities<C, Extra>} NSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import _ from 'lodash';
|
||||
import {errors, makeArgs, checkParams} from '../../protocol';
|
||||
|
||||
/**
|
||||
* @param {SessionBase} Base
|
||||
* @returns {ExecuteBase}
|
||||
*/
|
||||
export function ExecuteMixin(Base) {
|
||||
/**
|
||||
* @implements {IExecuteCommands}
|
||||
*/
|
||||
class ExecuteCommands extends Base {
|
||||
/**
|
||||
* @param {string} script
|
||||
* @param {[Record<string, any>]|[]} protoArgs
|
||||
*/
|
||||
async executeMethod(script, protoArgs) {
|
||||
// the w3c protocol will give us an array of arguments to apply to a javascript function.
|
||||
// that's not what we're doing. we're going to look for a JS object as the first arg, so we
|
||||
// can perform validation on it. we'll ignore everything else.
|
||||
if (!protoArgs || !_.isArray(protoArgs) || protoArgs.length > 1) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`Did not get correct format of arguments for execute method. Expected zero or one ` +
|
||||
`arguments to execute script and instead received: ${JSON.stringify(protoArgs)}`
|
||||
);
|
||||
}
|
||||
let args = protoArgs[0] ?? {};
|
||||
if (!_.isPlainObject(args)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`Did not receive an appropriate execute method parameters object. It needs to be ` +
|
||||
`deserializable as a plain JS object`
|
||||
);
|
||||
}
|
||||
|
||||
const Driver = /** @type {DriverClass} */ (this.constructor);
|
||||
const availableScripts = _.keys(Driver.executeMethodMap);
|
||||
const commandMetadata = Driver.executeMethodMap[script];
|
||||
if (!commandMetadata) {
|
||||
throw new errors.UnsupportedOperationError(
|
||||
`Unsupported execute method '${script}'. Available methods ` +
|
||||
`are: ${availableScripts.join(', ')}`
|
||||
);
|
||||
}
|
||||
let argsToApply = [];
|
||||
if (!commandMetadata.params) {
|
||||
commandMetadata.params = {required: [], optional: []};
|
||||
} else {
|
||||
commandMetadata.params.required ??= [];
|
||||
commandMetadata.params.optional ??= [];
|
||||
checkParams(commandMetadata.params, args, null);
|
||||
}
|
||||
argsToApply = makeArgs({}, args, commandMetadata.params, null);
|
||||
return await this[Driver.executeMethodMap[script].command](...argsToApply);
|
||||
}
|
||||
}
|
||||
return ExecuteCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ExecuteCommands} IExecuteCommands
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('./session').SessionBase} SessionBase
|
||||
* @typedef {import('../driver').BaseDriverBase<import('@appium/types').TimeoutCommands & import('@appium/types').EventCommands & import('@appium/types').FindCommands & import('@appium/types').LogCommands & import('@appium/types').SettingsCommands & import('@appium/types').SessionCommands & IExecuteCommands>} ExecuteBase
|
||||
*/
|
||||
@@ -6,6 +6,7 @@ import {LogMixin} from './log';
|
||||
import {SessionMixin} from './session';
|
||||
import {SettingsMixin} from './settings';
|
||||
import {TimeoutMixin} from './timeout';
|
||||
import {ExecuteMixin} from './execute';
|
||||
|
||||
/**
|
||||
* Applies all the mixins to the `BaseDriverBase` class.
|
||||
@@ -19,7 +20,8 @@ export function createBaseDriverClass(Base) {
|
||||
const WithLogCommands = LogMixin(WithFindCommands);
|
||||
const WithSettingsCommands = SettingsMixin(WithLogCommands);
|
||||
const WithSessionCommands = SessionMixin(WithSettingsCommands);
|
||||
return WithSessionCommands;
|
||||
const WithExecuteCommands = ExecuteMixin(WithSessionCommands);
|
||||
return WithExecuteCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,8 +17,10 @@ const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
|
||||
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @implements {Core}
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @implements {Core<C>}
|
||||
*/
|
||||
class DriverCore {
|
||||
/**
|
||||
@@ -27,13 +29,16 @@ class DriverCore {
|
||||
*/
|
||||
static baseVersion = BASEDRIVER_VER;
|
||||
|
||||
/** @type {ExecuteMethodMap} */
|
||||
static executeMethodMap = {};
|
||||
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
sessionId = null;
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverOpts}
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
*/
|
||||
opts;
|
||||
|
||||
@@ -42,16 +47,6 @@ class DriverCore {
|
||||
*/
|
||||
initialOpts;
|
||||
|
||||
/**
|
||||
* @type {Capabilities}
|
||||
*/
|
||||
caps;
|
||||
|
||||
/**
|
||||
* @type {W3CCapabilities}
|
||||
*/
|
||||
originalCaps;
|
||||
|
||||
helpers = helpers;
|
||||
|
||||
/**
|
||||
@@ -123,7 +118,11 @@ class DriverCore {
|
||||
*/
|
||||
settings = new DeviceSettings();
|
||||
|
||||
constructor(opts = /** @type {ServerArgs} */ ({}), shouldValidateCaps = true) {
|
||||
/**
|
||||
* @param {DriverOpts<C>} opts
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
*/
|
||||
constructor(opts = /** @type {DriverOpts<C>} */ ({}), shouldValidateCaps = true) {
|
||||
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this));
|
||||
|
||||
// setup state
|
||||
@@ -137,7 +136,7 @@ class DriverCore {
|
||||
this.shouldValidateCaps = shouldValidateCaps;
|
||||
|
||||
// keeping track of initial opts
|
||||
this.initialOpts = _.cloneDeep(this.opts);
|
||||
this.initialOpts = _.cloneDeep(opts);
|
||||
|
||||
this.sessionId = null;
|
||||
}
|
||||
@@ -237,7 +236,7 @@ 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 | null}
|
||||
* @returns {Core<C> | null}
|
||||
*/
|
||||
driverForSession(sessionId) {
|
||||
return this;
|
||||
@@ -348,7 +347,7 @@ class DriverCore {
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @returns {[string, RegExp][]}
|
||||
* @returns {import('@appium/types').RouteMatcher[]}
|
||||
*/
|
||||
getProxyAvoidList(sessionId) {
|
||||
return [];
|
||||
@@ -419,11 +418,32 @@ class DriverCore {
|
||||
export {DriverCore};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').Core} Core
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').ExecuteMethodMap} ExecuteMethodMap
|
||||
* @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=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
/**
|
||||
* @template {StringRecord} [T={}]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<T>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Core<C>} Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').DriverOpts<C>} DriverOpts
|
||||
*/
|
||||
|
||||
@@ -2,61 +2,11 @@ import log from './logger';
|
||||
import _validator from 'validate.js';
|
||||
import B from 'bluebird';
|
||||
|
||||
const validator =
|
||||
export const validator =
|
||||
/** @type {import('validate.js').ValidateJS & {promise: typeof import('bluebird')}} */ (
|
||||
_validator
|
||||
);
|
||||
|
||||
/** @type {import('@appium/types').Constraints} */
|
||||
let desiredCapabilityConstraints = {
|
||||
platformName: {
|
||||
presence: true,
|
||||
isString: true,
|
||||
},
|
||||
deviceName: {
|
||||
isString: true,
|
||||
},
|
||||
platformVersion: {
|
||||
isString: true,
|
||||
},
|
||||
newCommandTimeout: {
|
||||
isNumber: true,
|
||||
},
|
||||
automationName: {
|
||||
isString: true,
|
||||
},
|
||||
autoLaunch: {
|
||||
isBoolean: true,
|
||||
},
|
||||
udid: {
|
||||
isString: true,
|
||||
},
|
||||
orientation: {
|
||||
inclusion: ['LANDSCAPE', 'PORTRAIT'],
|
||||
},
|
||||
autoWebview: {
|
||||
isBoolean: true,
|
||||
},
|
||||
noReset: {
|
||||
isBoolean: true,
|
||||
},
|
||||
fullReset: {
|
||||
isBoolean: true,
|
||||
},
|
||||
language: {
|
||||
isString: true,
|
||||
},
|
||||
locale: {
|
||||
isString: true,
|
||||
},
|
||||
eventTimings: {
|
||||
isBoolean: true,
|
||||
},
|
||||
printPageSourceOnFindFailure: {
|
||||
isBoolean: true,
|
||||
},
|
||||
};
|
||||
|
||||
validator.validators.isString = function isString(value) {
|
||||
if (typeof value === 'string') {
|
||||
return null;
|
||||
@@ -147,5 +97,3 @@ validator.promise = B;
|
||||
validator.prettify = function prettify(val) {
|
||||
return val;
|
||||
};
|
||||
|
||||
export {desiredCapabilityConstraints, validator};
|
||||
|
||||
@@ -16,7 +16,7 @@ import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
||||
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
||||
import {createBaseDriverClass} from './commands';
|
||||
import helpers from './helpers';
|
||||
import {desiredCapabilityConstraints} from './desired-caps';
|
||||
import {BASE_DESIRED_CAP_CONSTRAINTS} from '@appium/types';
|
||||
|
||||
const EVENT_SESSION_INIT = 'newSessionRequested';
|
||||
const EVENT_SESSION_START = 'newSessionStarted';
|
||||
@@ -25,14 +25,67 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @implements {SessionHandler}
|
||||
* @implements {SessionHandler<C>}
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [CArgs=StringRecord]
|
||||
* @extends {DriverCore<C>}
|
||||
*/
|
||||
export class BaseDriverCore extends DriverCore {
|
||||
_constraints = _.cloneDeep(desiredCapabilityConstraints);
|
||||
|
||||
/** @type {Record<string,any>|undefined} */
|
||||
/**
|
||||
* @type {CArgs & ServerArgs}
|
||||
*/
|
||||
cliArgs;
|
||||
|
||||
/**
|
||||
* @type {Capabilities<C>}
|
||||
*/
|
||||
caps;
|
||||
|
||||
/**
|
||||
* @type {W3CCapabilities<C>}
|
||||
*/
|
||||
originalCaps;
|
||||
|
||||
/**
|
||||
* @type {C}
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/**
|
||||
* @type {DriverOpts<C> & DriverOpts<BaseDriverCapConstraints>}
|
||||
*/
|
||||
opts;
|
||||
|
||||
static BASE_DESIRED_CAP_CONSTRAINTS = BASE_DESIRED_CAP_CONSTRAINTS;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DriverOpts<C>} opts
|
||||
* @param {boolean} shouldValidateCaps
|
||||
*/
|
||||
constructor(opts = /** @type {DriverOpts<C>} */ ({}), shouldValidateCaps = true) {
|
||||
super(opts, shouldValidateCaps);
|
||||
|
||||
/**
|
||||
* This must be assigned here because the declaration of {@linkcode BaseDriverCore.opts} above
|
||||
* blows away {@linkcode DriverCore.opts}.
|
||||
*/
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the base constraints plus whatever the subclass wants to add.
|
||||
*
|
||||
* Subclasses _shouldn't_ need to use this. If you need to use this, please create
|
||||
* an issue:
|
||||
* @see https://github.com/appium/appium/issues/new
|
||||
* @type {Readonly<BaseDriverCapConstraints & C>}
|
||||
* @protected
|
||||
*/
|
||||
get _desiredCapConstraints() {
|
||||
return Object.freeze(_.merge({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints));
|
||||
}
|
||||
|
||||
// This is the main command handler for the driver. It wraps command
|
||||
// execution with timeout logic, checking that we have a valid session,
|
||||
// and ensuring that we execute commands one at a time. This method is called
|
||||
@@ -114,7 +167,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Error} err
|
||||
* @param {Error|import('../protocol/errors').NoSuchDriverError} err
|
||||
*/
|
||||
async startUnexpectedShutdown(
|
||||
err = new errors.NoSuchDriverError('The driver was unexpectedly shut down!')
|
||||
@@ -208,11 +261,11 @@ export class BaseDriverCore extends DriverCore {
|
||||
* Appium 2 has dropped the support of these, so now we only accept capability
|
||||
* objects in W3C format and thus allow any of the three arguments to represent
|
||||
* the latter.
|
||||
* @param {W3CCapabilities} w3cCapabilities1
|
||||
* @param {W3CCapabilities} [w3cCapabilities2]
|
||||
* @param {W3CCapabilities} [w3cCapabilities]
|
||||
* @param {W3CCapabilities<C>} w3cCapabilities1
|
||||
* @param {W3CCapabilities<C>} [w3cCapabilities2]
|
||||
* @param {W3CCapabilities<C>} [w3cCapabilities]
|
||||
* @param {DriverData[]} [driverData]
|
||||
* @returns {Promise<[string,object]>}
|
||||
* @returns {Promise<[string,Capabilities<C>]>}
|
||||
*/
|
||||
async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities, driverData) {
|
||||
if (this.sessionId !== null) {
|
||||
@@ -235,21 +288,26 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
this.setProtocolW3C();
|
||||
|
||||
this.originalCaps = _.cloneDeep(originalCaps);
|
||||
this.originalCaps = originalCaps;
|
||||
this.log.debug(
|
||||
`Creating session with W3C capabilities: ${JSON.stringify(originalCaps, null, 2)}`
|
||||
);
|
||||
|
||||
/** @type {Capabilities<C>} */
|
||||
let caps;
|
||||
try {
|
||||
caps = processCapabilities(originalCaps, this.desiredCapConstraints, this.shouldValidateCaps);
|
||||
caps = processCapabilities(
|
||||
originalCaps,
|
||||
this._desiredCapConstraints,
|
||||
this.shouldValidateCaps
|
||||
);
|
||||
if (caps[APPIUM_OPTS_CAP]) {
|
||||
this.log.debug(
|
||||
`Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`
|
||||
);
|
||||
caps = promoteAppiumOptions(caps);
|
||||
}
|
||||
caps = fixCaps(caps, this.desiredCapConstraints, this.log);
|
||||
caps = fixCaps(caps, this._desiredCapConstraints, this.log);
|
||||
} catch (e) {
|
||||
throw new errors.SessionNotCreatedError(e.message);
|
||||
}
|
||||
@@ -258,10 +316,8 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
this.sessionId = util.uuidV4();
|
||||
this.caps = caps;
|
||||
this.opts = _.cloneDeep(this.initialOpts);
|
||||
|
||||
// merge caps onto opts so we don't need to worry about what's where
|
||||
Object.assign(this.opts, this.caps);
|
||||
this.opts = {..._.cloneDeep(this.initialOpts), ...this.caps};
|
||||
|
||||
// deal with resets
|
||||
// some people like to do weird things by setting noReset and fullReset
|
||||
@@ -288,7 +344,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.caps.newCommandTimeout)) {
|
||||
this.newCommandTimeoutMs = this.caps.newCommandTimeout * 1000;
|
||||
this.newCommandTimeoutMs = /** @type {number} */ (this.caps.newCommandTimeout) * 1000;
|
||||
}
|
||||
|
||||
this._log.prefix = helpers.generateDriverLogPrefix(this, this.sessionId);
|
||||
@@ -323,7 +379,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
* @param {Capabilities} caps
|
||||
*/
|
||||
logExtraCaps(caps) {
|
||||
let extraCaps = _.difference(_.keys(caps), _.keys(this._constraints));
|
||||
let extraCaps = _.difference(_.keys(caps), _.keys(this._desiredCapConstraints));
|
||||
if (extraCaps.length) {
|
||||
this.log.warn(
|
||||
`The following capabilities were provided, but are not ` + `recognized by Appium:`
|
||||
@@ -336,7 +392,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities} caps
|
||||
* @param {Capabilities<C>} caps
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateDesiredCaps(caps) {
|
||||
@@ -345,7 +401,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
}
|
||||
|
||||
try {
|
||||
validateCaps(caps, this._constraints);
|
||||
validateCaps(caps, this._desiredCapConstraints);
|
||||
} catch (e) {
|
||||
this.log.errorAndThrow(
|
||||
new errors.SessionNotCreatedError(
|
||||
@@ -359,47 +415,25 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// we only want subclasses to ever extend the contraints
|
||||
set desiredCapConstraints(constraints) {
|
||||
this._constraints = Object.assign(this._constraints, constraints);
|
||||
// 'presence' means different things in different versions of the validator,
|
||||
// when we say 'true' we mean that it should not be able to be empty
|
||||
for (const [, value] of _.toPairs(this._constraints)) {
|
||||
if (value && value.presence === true) {
|
||||
value.presence = {
|
||||
allowEmpty: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get desiredCapConstraints() {
|
||||
return this._constraints;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that all of the mixins correctly implement the interface described in {@linkcode Driver}.
|
||||
* @implements {Driver}
|
||||
* @template {Constraints} [C={}]
|
||||
* @implements {Driver<C>}
|
||||
*/
|
||||
class _BaseDriver extends createBaseDriverClass(BaseDriverCore) {}
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverClass<Driver>}
|
||||
*/
|
||||
const BaseDriver = _BaseDriver;
|
||||
|
||||
export {BaseDriver};
|
||||
export class BaseDriver extends createBaseDriverClass(BaseDriverCore) {}
|
||||
export default BaseDriver;
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').HTTPMethod} HTTPMethod
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').DriverData} DriverData
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').Constraint} Constraint
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
* @typedef {import('@appium/types').ServerArgs} ServerArgs
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -417,5 +451,33 @@ export default BaseDriver;
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').SessionHandler<[string, object],void>} SessionHandler
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @typedef {import('@appium/types').SessionHandler<[string, object],void, C>} SessionHandler
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Driver<C>} Driver
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').DriverOpts<C>} DriverOpts
|
||||
*/
|
||||
|
||||
@@ -40,6 +40,7 @@ async function server(opts) {
|
||||
routeConfiguringFunction,
|
||||
port,
|
||||
hostname,
|
||||
cliArgs,
|
||||
allowCors = true,
|
||||
basePath = DEFAULT_BASE_PATH,
|
||||
extraMethodMap = {},
|
||||
@@ -71,7 +72,7 @@ async function server(opts) {
|
||||
});
|
||||
// allow extensions to update the app and http server objects
|
||||
for (const updater of serverUpdaters) {
|
||||
await updater(app, appiumServer);
|
||||
await updater(app, appiumServer, cliArgs);
|
||||
}
|
||||
|
||||
// once all configurations and updaters have been applied, make sure to set up a catchall
|
||||
@@ -292,6 +293,7 @@ export {server, configureServer, normalizeBasePath};
|
||||
* @typedef ServerOpts
|
||||
* @property {RouteConfiguringFunction} routeConfiguringFunction
|
||||
* @property {number} port
|
||||
* @property {import('@appium/types').ServerArgs} cliArgs
|
||||
* @property {string} [hostname]
|
||||
* @property {boolean} [allowCors]
|
||||
* @property {string} [basePath]
|
||||
|
||||
@@ -26,19 +26,20 @@ function isW3cCaps(caps) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities} originalCaps
|
||||
* @param {Constraints} desiredCapConstraints
|
||||
* @template {Constraints} C
|
||||
* @param {any} oldCaps
|
||||
* @param {C} desiredCapConstraints
|
||||
* @param {AppiumLogger} log
|
||||
* @returns {Capabilities}
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function fixCaps(originalCaps, desiredCapConstraints, log) {
|
||||
let caps = _.clone(originalCaps);
|
||||
function fixCaps(oldCaps, desiredCapConstraints, log) {
|
||||
let caps = _.clone(oldCaps);
|
||||
|
||||
// boolean capabilities can be passed in as strings 'false' and 'true'
|
||||
// which we want to translate into boolean values
|
||||
let booleanCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isBoolean === true));
|
||||
for (let cap of booleanCaps) {
|
||||
let value = originalCaps[cap];
|
||||
let value = oldCaps[cap];
|
||||
if (_.isString(value)) {
|
||||
value = value.toLowerCase();
|
||||
if (value === 'true' || value === 'false') {
|
||||
@@ -51,9 +52,11 @@ function fixCaps(originalCaps, desiredCapConstraints, log) {
|
||||
}
|
||||
|
||||
// int capabilities are often sent in as strings by frameworks
|
||||
let intCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true));
|
||||
let intCaps = /** @type {import('type-fest').StringKeyOf<typeof caps>[]} */ (
|
||||
_.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true))
|
||||
);
|
||||
for (let cap of intCaps) {
|
||||
let value = originalCaps[cap];
|
||||
let value = oldCaps[cap];
|
||||
if (_.isString(value)) {
|
||||
value = value.trim();
|
||||
let newValue = parseInt(value, 10);
|
||||
@@ -73,7 +76,14 @@ function fixCaps(originalCaps, desiredCapConstraints, log) {
|
||||
export {isW3cCaps, fixCaps};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ export {getSummaryByCode, codes as statusCodes} from './jsonwp-status/status';
|
||||
// W3C capabilities parser
|
||||
export {
|
||||
PREFIXED_APPIUM_OPTS_CAP,
|
||||
STANDARD_CAPS,
|
||||
processCapabilities,
|
||||
isStandardCap,
|
||||
validateCaps,
|
||||
@@ -37,3 +38,7 @@ export {
|
||||
|
||||
// Web socket helpers
|
||||
export {DEFAULT_WS_PATHNAME_PREFIX} from './express/websocket';
|
||||
|
||||
/**
|
||||
* @typedef {import('./express/server').ServerOpts} ServerOpts
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
CREATE_SESSION_COMMAND,
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
makeArgs,
|
||||
checkParams,
|
||||
} from './protocol';
|
||||
import {NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, routeToCommandName} from './routes';
|
||||
import {errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode} from './errors';
|
||||
@@ -13,6 +15,8 @@ export {
|
||||
routeConfiguringFunction,
|
||||
errors,
|
||||
isErrorType,
|
||||
makeArgs,
|
||||
checkParams,
|
||||
errorFromMJSONWPStatusCode,
|
||||
errorFromW3CJsonCode,
|
||||
ALL_COMMANDS,
|
||||
|
||||
@@ -94,7 +94,7 @@ function unwrapParams(paramSets, jsonObj) {
|
||||
return res;
|
||||
}
|
||||
|
||||
function checkParams(paramSets, jsonObj, protocol) {
|
||||
export function checkParams(paramSets, jsonObj, protocol) {
|
||||
let requiredParams = [];
|
||||
let optionalParams = [];
|
||||
let receivedParams = _.keys(jsonObj);
|
||||
@@ -162,7 +162,7 @@ function checkParams(paramSets, jsonObj, protocol) {
|
||||
* on handling parameters. This method returns an array of arguments which will
|
||||
* be applied to a command.
|
||||
*/
|
||||
function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
||||
export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
||||
// We want to pass the "url" parameters to the commands in reverse order
|
||||
// since the command will sometimes want to ignore, say, the sessionId.
|
||||
// This has the effect of putting sessionId last, which means in JS we can
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "8.6.0",
|
||||
"version": "8.7.3",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,34 +45,33 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.61.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.1.5",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/async-lock": "1.3.0",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/serve-favicon": "2.5.3",
|
||||
"async-lock": "1.3.2",
|
||||
"asyncbox": "2.9.2",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.0",
|
||||
"body-parser": "1.20.1",
|
||||
"es6-error": "4.1.1",
|
||||
"express": "4.18.1",
|
||||
"express": "4.18.2",
|
||||
"http-status-codes": "2.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.13.1",
|
||||
"lru-cache": "7.14.0",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "2.17.0",
|
||||
"type-fest": "3.1.0",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getTestPort,
|
||||
TEST_HOST,
|
||||
createSessionHelpers,
|
||||
} from '@appium/test-support';
|
||||
} from '@appium/driver-test-support';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -68,5 +68,5 @@ describe('BaseDriver', function () {
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/test-support').SessionHelpers} SessionHelpers
|
||||
* @typedef {import('@appium/driver-test-support').SessionHelpers} SessionHelpers
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ import finalhandler from 'finalhandler';
|
||||
import serveStatic from 'serve-static';
|
||||
import contentDisposition from 'content-disposition';
|
||||
import B from 'bluebird';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
function getFixture(file) {
|
||||
// XXX: __dirname disallowed in native ESM
|
||||
|
||||
@@ -3,7 +3,7 @@ import {server, routeConfiguringFunction, DEFAULT_WS_PATHNAME_PREFIX} from '../.
|
||||
import {FakeDriver} from '../protocol/fake-driver';
|
||||
import WebSocket from 'ws';
|
||||
import B from 'bluebird';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
describe('Websockets (e2e)', function () {
|
||||
let baseServer;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {server} from '../../../lib';
|
||||
import axios from 'axios';
|
||||
import {createSandbox} from 'sinon';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
describe('server', function () {
|
||||
let hwServer;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {server, routeConfiguringFunction, errors, JWProxy, BaseDriver} from '../../../lib';
|
||||
import {FakeDriver} from './fake-driver';
|
||||
import axios from 'axios';
|
||||
@@ -7,7 +5,7 @@ import {createSandbox} from 'sinon';
|
||||
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
|
||||
import {createProxyServer} from './helpers';
|
||||
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../../../lib/constants';
|
||||
import {TEST_HOST, getTestPort} from '@appium/test-support';
|
||||
import {TEST_HOST, getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
let port;
|
||||
let baseUrl;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
stripAppiumPrefixes,
|
||||
} from '../../../lib/basedriver/capabilities';
|
||||
import _ from 'lodash';
|
||||
import {desiredCapabilityConstraints} from '../../../lib/basedriver/desired-caps';
|
||||
import {BASE_DESIRED_CAP_CONSTRAINTS} from '@appium/types';
|
||||
import {isW3cCaps} from '../../../lib/helpers/capabilities';
|
||||
|
||||
describe('caps', function () {
|
||||
@@ -231,7 +231,9 @@ describe('caps', function () {
|
||||
},
|
||||
};
|
||||
|
||||
parseCaps(caps, constraints).validatedFirstMatchCaps.should.deep.equal(caps.firstMatch);
|
||||
parseCaps(caps, constraints).validatedFirstMatchCaps.should.deep.equal(
|
||||
caps.firstMatch.map(stripAppiumPrefixes)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns no vendor prefix error if the firstMatch[2] does not have it because of no bject', function () {
|
||||
@@ -353,7 +355,7 @@ describe('caps', function () {
|
||||
});
|
||||
|
||||
describe('validate Appium constraints', function () {
|
||||
const constraints = {...desiredCapabilityConstraints};
|
||||
const constraints = {...BASE_DESIRED_CAP_CONSTRAINTS};
|
||||
const expectedMatchingCaps = {
|
||||
platformName: 'Fake',
|
||||
automationName: 'Fake',
|
||||
@@ -547,8 +549,7 @@ describe('caps', function () {
|
||||
const nonAppiumCaps = {
|
||||
platformName: 'iOS',
|
||||
};
|
||||
const appiumUnprefixedCaps = _.clone(appiumCaps);
|
||||
stripAppiumPrefixes(appiumUnprefixedCaps);
|
||||
const appiumUnprefixedCaps = stripAppiumPrefixes(appiumCaps);
|
||||
const simpleCaps = {
|
||||
...nonAppiumCaps,
|
||||
...appiumUnprefixedCaps,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import B from 'bluebird';
|
||||
import {BaseDriver, errors} from '../../../lib';
|
||||
import {BaseDriver, errors} from '../../../lib/index';
|
||||
import {validator} from '../../../lib/basedriver/desired-caps';
|
||||
import {createSandbox} from 'sinon';
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should check added required caps in addition to base', async function () {
|
||||
d.desiredCapConstraints = {
|
||||
d.desiredCapConstraints = /** @type {const} */ ({
|
||||
necessary: {
|
||||
presence: true,
|
||||
},
|
||||
@@ -87,7 +87,7 @@ describe('Desired Capabilities', function () {
|
||||
isString: true,
|
||||
inclusion: ['Delorean', 'Reventon'],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await d
|
||||
.createSession(null, null, {
|
||||
@@ -100,15 +100,19 @@ describe('Desired Capabilities', function () {
|
||||
});
|
||||
|
||||
it('should accept extra capabilities', async function () {
|
||||
await d.createSession(null, null, {
|
||||
alwaysMatch: {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
'appium:extra': 'cheese',
|
||||
'appium:hold the': 'sauce',
|
||||
},
|
||||
firstMatch: [{}],
|
||||
}).should.be.fulfilled;
|
||||
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;
|
||||
});
|
||||
|
||||
it('should log the use of extra caps', async function () {
|
||||
|
||||
@@ -22,6 +22,14 @@ describe('DeviceSettings', function () {
|
||||
expect(() => new DeviceSettings()).not.to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not hold on to reference of defaults in constructor', function () {
|
||||
let obj = {foo: 'bar'};
|
||||
let d1 = new DeviceSettings(obj);
|
||||
let d2 = new DeviceSettings(obj);
|
||||
d1.getSettings().foo = 'baz';
|
||||
expect(d1.getSettings()).to.not.eql(d2.getSettings());
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance method', function () {
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import BaseDriver from '../../../lib';
|
||||
import {driverUnitTestSuite} from '@appium/test-support';
|
||||
import {driverUnitTestSuite} from '@appium/driver-test-support';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
driverUnitTestSuite(BaseDriver, {
|
||||
platformName: 'iOS',
|
||||
'appium:deviceName': 'Delorean',
|
||||
});
|
||||
|
||||
describe('BaseDriver', function () {
|
||||
describe('constructor', function () {
|
||||
it('should initialize "opts"', function () {
|
||||
const driver = new BaseDriver();
|
||||
expect(driver.opts).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {server, routeConfiguringFunction} from '../../../lib';
|
||||
import {configureServer, normalizeBasePath} from '../../../lib/express/server';
|
||||
import {createSandbox} from 'sinon';
|
||||
import {getTestPort} from '@appium/test-support';
|
||||
import {getTestPort} from '@appium/driver-test-support';
|
||||
|
||||
const newMethodMap = {
|
||||
'/session/:sessionId/fake': {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// transpile:mocha
|
||||
|
||||
import {JWProxy} from '../../../lib';
|
||||
import request from './mock-request';
|
||||
import {isErrorType, errors} from '../../../lib/protocol/errors';
|
||||
import {getTestPort, TEST_HOST} from '@appium/test-support';
|
||||
import {getTestPort, TEST_HOST} from '@appium/driver-test-support';
|
||||
|
||||
function buildReqRes(url, method, body) {
|
||||
let req = {originalUrl: url, method, body};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {JWProxy} from '../../../lib';
|
||||
import {getTestPort, TEST_HOST, createAppiumURL} from '@appium/test-support';
|
||||
import {getTestPort, TEST_HOST, createAppiumURL} from '@appium/driver-test-support';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('JWProxy', function () {
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
"paths": {
|
||||
"@appium/base-driver": ["."],
|
||||
"@appium/support": ["../support"],
|
||||
"@appium/types": ["../types"]
|
||||
"@appium/types": ["../types"],
|
||||
"@appium/driver-test-support": ["../driver-test-support"]
|
||||
},
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["lib"],
|
||||
"references": [{"path": "../support"}, {"path": "../types"}]
|
||||
"references": [
|
||||
{"path": "../support"},
|
||||
{"path": "../types"}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.5](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.4...@appium/base-plugin@1.10.5) (2022-10-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.4](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.3...@appium/base-plugin@1.10.4) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.3](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.2...@appium/base-plugin@1.10.3) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.2](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.1...@appium/base-plugin@1.10.2) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.1](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.0...@appium/base-plugin@1.10.1) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.10.0](https://github.com/appium/appium/compare/@appium/base-plugin@1.9.2...@appium/base-plugin@1.10.0) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "1.10.0",
|
||||
"version": "1.10.5",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -31,16 +31,26 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3"
|
||||
"@appium/support": "^2.61.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"types": "./build/lib/plugin.d.ts",
|
||||
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"homepage": "https://appium.io"
|
||||
"homepage": "https://appium.io",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.28](https://github.com/appium/appium/compare/@appium/doctor@1.16.27...@appium/doctor@1.16.28) (2022-10-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.27](https://github.com/appium/appium/compare/@appium/doctor@1.16.26...@appium/doctor@1.16.27) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.26](https://github.com/appium/appium/compare/@appium/doctor@1.16.25...@appium/doctor@1.16.26) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.25](https://github.com/appium/appium/compare/@appium/doctor@1.16.24...@appium/doctor@1.16.25) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.24](https://github.com/appium/appium/compare/@appium/doctor@1.16.23...@appium/doctor@1.16.24) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.23](https://github.com/appium/appium/compare/@appium/doctor@1.16.22...@appium/doctor@1.16.23) (2022-07-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "1.16.23",
|
||||
"version": "1.16.28",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -49,22 +49,21 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.61.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"@types/teen_process": "1.16.1",
|
||||
"appium-adb": "9.10.1",
|
||||
"appium-adb": "9.10.11",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.4",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0",
|
||||
"yargs": "17.5.1"
|
||||
"teen_process": "2.0.2",
|
||||
"yargs": "17.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.13](https://github.com/appium/appium/compare/@appium/docutils@0.0.12...@appium/docutils@0.0.13) (2022-10-14)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.12](https://github.com/appium/appium/compare/@appium/docutils@0.0.11...@appium/docutils@0.0.12) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.11](https://github.com/appium/appium/compare/@appium/docutils@0.0.10...@appium/docutils@0.0.11) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.10](https://github.com/appium/appium/compare/@appium/docutils@0.0.9...@appium/docutils@0.0.10) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.9](https://github.com/appium/appium/compare/@appium/docutils@0.0.8...@appium/docutils@0.0.9) (2022-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.8](https://github.com/appium/appium/compare/@appium/docutils@0.0.7...@appium/docutils@0.0.8) (2022-07-28)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.13",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"appium",
|
||||
"docs"
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
@@ -41,17 +47,16 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.59.3",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@appium/support": "^2.61.1",
|
||||
"docdash": "1.2.0",
|
||||
"jsdoc": "3.6.11",
|
||||
"jsdoc-plugin-typescript": "2.1.0",
|
||||
"jsdoc-plugin-typescript": "2.2.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0"
|
||||
"teen_process": "2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user