mirror of
https://github.com/appium/appium.git
synced 2026-11-03 15:56:15 +08:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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)
|
||||
|
||||
@@ -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@969bd2663942d722d85b6a8626225850c2f7be4b # tag=v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
name: Lint & Check Types
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1
|
||||
with:
|
||||
|
||||
@@ -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@969bd2663942d722d85b6a8626225850c2f7be4b # tag=v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Appium
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run FOSSA Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||
with:
|
||||
ref: master
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -10,15 +10,37 @@
|
||||
[](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.
|
||||
|
||||
: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
|
||||
|
||||
Appium supports app automation across a variety of platforms, like iOS,
|
||||
@@ -79,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
|
||||
@@ -199,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,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,46 +0,0 @@
|
||||
# parameters: # No params yet, but may add later
|
||||
|
||||
parameters:
|
||||
vmImage: 'macOS-latest'
|
||||
bundleNodeJSVersion: '16.17.0'
|
||||
nodeJSVersion: '16.x'
|
||||
|
||||
jobs:
|
||||
- job: bundle_and_upload_appium_zip
|
||||
pool:
|
||||
vmImage: ${{ parameters.vmImage }}
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- script: gcc --version
|
||||
displayName: Print GCC Version
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: ${{ parameters.nodeJSVersion }}
|
||||
- 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/v${{ parameters.bundleNodeJSVersion }}/node-v${{ parameters.bundleNodeJSVersion }}-darwin-x64.tar.gz
|
||||
mkdir node-darwin-unpacked
|
||||
tar -xzf node-darwin.tar.gz -C node-darwin-unpacked/ --strip-components 1
|
||||
rm -f node-darwin.tar.gz
|
||||
cp node-darwin-unpacked/bin/node node-darwin
|
||||
rm -rf node-darwin-unpacked
|
||||
displayName: Download NodeJS to bundle it
|
||||
- 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)
|
||||
Generated
+1015
-1224
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -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,7 +52,7 @@
|
||||
"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 test:smoke 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",
|
||||
@@ -72,6 +71,9 @@
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts*": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
@@ -104,17 +106,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-plugin": "1.3.1",
|
||||
"@babel/cli": "7.18.10",
|
||||
"@babel/core": "7.19.0",
|
||||
"@babel/plugin-transform-runtime": "7.18.10",
|
||||
"@babel/preset-env": "7.19.0",
|
||||
"@babel/cli": "7.19.3",
|
||||
"@babel/core": "7.19.3",
|
||||
"@babel/preset-env": "7.19.4",
|
||||
"@babel/register": "7.18.9",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/chai": "4.3.3",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "16.11.57",
|
||||
"@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",
|
||||
@@ -126,7 +126,7 @@
|
||||
"chai": "4.3.6",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"eslint": "8.23.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
@@ -151,14 +151,14 @@
|
||||
"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.24.0",
|
||||
"ws": "8.8.1",
|
||||
"webdriverio": "7.25.1",
|
||||
"ws": "8.9.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
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.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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/r
|
||||
|
||||
### :bangbang: Installing drivers during setup
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
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
|
||||
@@ -84,6 +84,9 @@ If any updates are available, you can then run the `update` command for any give
|
||||
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
|
||||
@@ -184,7 +187,10 @@ Apart from the breaking changes mentioned above, in this section is a list of so
|
||||
|
||||
#### :tada: _Server Plugins_
|
||||
|
||||
TODO
|
||||
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_
|
||||
|
||||
@@ -192,7 +198,11 @@ TODO
|
||||
|
||||
### :tada: Install drivers and plugins from anywhere
|
||||
|
||||
TODO
|
||||
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
|
||||
|
||||
|
||||
@@ -2,4 +2,114 @@
|
||||
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 four 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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -831,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
|
||||
@@ -842,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
|
||||
*/
|
||||
|
||||
@@ -854,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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,20 +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.
|
||||
// also `ENAMETOOLONG` can happen if we try to open a file that's a huge JSON string.
|
||||
if (err.code !== 'ENOENT' && err.code !== 'ENAMETOOLONG') {
|
||||
throw err;
|
||||
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);
|
||||
|
||||
@@ -1,11 +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()));
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -35,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
|
||||
@@ -52,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'),
|
||||
});
|
||||
@@ -78,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))
|
||||
@@ -102,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;
|
||||
}
|
||||
@@ -129,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,
|
||||
@@ -145,7 +150,7 @@ function parseCapsForInnerDriver(
|
||||
};
|
||||
}
|
||||
|
||||
return /** @type {ParsedDriverCaps} */ ({
|
||||
return /** @type {ParsedDriverCaps<C,J>} */ ({
|
||||
desiredCaps,
|
||||
processedJsonwpCapabilities,
|
||||
processedW3CCapabilities,
|
||||
@@ -155,57 +160,43 @@ 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;
|
||||
@@ -235,7 +226,6 @@ function adjustNodePath() {
|
||||
require('module').Module._initPaths();
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.info(`Module init paths cannot be refreshed. Original error: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -243,7 +233,6 @@ function adjustNodePath() {
|
||||
if (!process.env.NODE_PATH) {
|
||||
process.env.NODE_PATH = nodeModulesRoot;
|
||||
if (refreshRequirePaths()) {
|
||||
logger.info(`Setting NODE_PATH to '${nodeModulesRoot}'`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
delete process.env.NODE_PATH;
|
||||
@@ -253,7 +242,6 @@ function adjustNodePath() {
|
||||
|
||||
const nodePathParts = process.env.NODE_PATH.split(path.delimiter);
|
||||
if (nodePathParts.includes(nodeModulesRoot)) {
|
||||
logger.info(`NODE_PATH already includes '${nodeModulesRoot}'`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
return;
|
||||
}
|
||||
@@ -261,7 +249,6 @@ function adjustNodePath() {
|
||||
nodePathParts.push(nodeModulesRoot);
|
||||
process.env.NODE_PATH = nodePathParts.join(path.delimiter);
|
||||
if (refreshRequirePaths()) {
|
||||
logger.info(`Adding '${nodeModulesRoot}' to NODE_PATH`);
|
||||
process.env.APPIUM_OMIT_PEER_DEPS = '1';
|
||||
} else {
|
||||
process.env.NODE_PATH = _.without(nodePathParts, nodeModulesRoot).join(path.delimiter);
|
||||
@@ -313,26 +300,60 @@ export {
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.44",
|
||||
"version": "2.0.0-beta.45",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,6 +43,7 @@
|
||||
"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",
|
||||
@@ -60,16 +61,15 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.7.1",
|
||||
"@appium/base-plugin": "^1.10.3",
|
||||
"@appium/docutils": "^0.0.11",
|
||||
"@appium/base-driver": "^8.7.2",
|
||||
"@appium/base-plugin": "^1.10.4",
|
||||
"@appium/docutils": "^0.0.12",
|
||||
"@appium/schema": "^0.0.9",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@sidvind/better-ajv-errors": "2.0.0",
|
||||
"@appium/support": "^2.61.0",
|
||||
"@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.12",
|
||||
"@types/teen_process": "1.16.1",
|
||||
@@ -83,6 +83,7 @@
|
||||
"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",
|
||||
@@ -90,13 +91,13 @@
|
||||
"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.19.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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -88,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.@appium/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, '@appium/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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0"
|
||||
"appium": "^2.0.0-beta.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"appium": "next"
|
||||
},
|
||||
"appium": {
|
||||
"driverName": "test",
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
@@ -36,7 +38,7 @@ class DriverCore {
|
||||
sessionId = null;
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverOpts}
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
*/
|
||||
opts;
|
||||
|
||||
@@ -45,16 +47,6 @@ class DriverCore {
|
||||
*/
|
||||
initialOpts;
|
||||
|
||||
/**
|
||||
* @type {Capabilities}
|
||||
*/
|
||||
caps;
|
||||
|
||||
/**
|
||||
* @type {W3CCapabilities}
|
||||
*/
|
||||
originalCaps;
|
||||
|
||||
helpers = helpers;
|
||||
|
||||
/**
|
||||
@@ -126,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
|
||||
@@ -140,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;
|
||||
}
|
||||
@@ -240,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;
|
||||
@@ -422,12 +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,52 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
|
||||
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
|
||||
/**
|
||||
* @implements {SessionHandler}
|
||||
* @implements {SessionHandler<C>}
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [A=StringRecord]
|
||||
* @extends {DriverCore<C>}
|
||||
*/
|
||||
export class BaseDriverCore extends DriverCore {
|
||||
_constraints = _.cloneDeep(desiredCapabilityConstraints);
|
||||
|
||||
/** @type {Record<string,any>|undefined} */
|
||||
/**
|
||||
* @type {A & ServerArgs}
|
||||
*/
|
||||
cliArgs;
|
||||
|
||||
/**
|
||||
* @type {Capabilities<C>}
|
||||
*/
|
||||
caps;
|
||||
|
||||
/**
|
||||
* @type {W3CCapabilities<C>}
|
||||
*/
|
||||
originalCaps;
|
||||
|
||||
/**
|
||||
* @type {C}
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/**
|
||||
* @type {DriverOpts<C> & DriverOpts<BaseDriverCapConstraints>}
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
static BASE_DESIRED_CAP_CONSTRAINTS = BASE_DESIRED_CAP_CONSTRAINTS;
|
||||
|
||||
// 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 +152,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 +246,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 +273,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 +301,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 +329,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 +364,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 +377,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Capabilities} caps
|
||||
* @param {Capabilities<C>} caps
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateDesiredCaps(caps) {
|
||||
@@ -345,7 +386,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 +400,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 +436,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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "8.7.1",
|
||||
"version": "8.7.2",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,21 +45,20 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/support": "^2.61.0",
|
||||
"@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.14.0",
|
||||
@@ -67,7 +66,7 @@
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "2.19.0",
|
||||
"type-fest": "3.1.0",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "1.10.3",
|
||||
"version": "1.10.4",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -31,7 +31,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0"
|
||||
"@appium/support": "^2.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "1.16.26",
|
||||
"version": "1.16.27",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -49,18 +49,17 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/support": "^2.61.0",
|
||||
"@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.9",
|
||||
"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",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.12",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -47,13 +47,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/support": "^2.61.0",
|
||||
"docdash": "1.2.0",
|
||||
"jsdoc": "3.6.11",
|
||||
"jsdoc-plugin-typescript": "2.1.1",
|
||||
"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",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.1](https://github.com/appium/appium/compare/@appium/driver-test-support@0.2.0...@appium/driver-test-support@0.2.1) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/driver-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 0.2.0 (2022-09-07)
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export function createSessionHelpers(port, address = TEST_HOST) {
|
||||
* Creates E2E test suites for a driver.
|
||||
* @template {Driver} P
|
||||
* @param {DriverClass<P>} DriverClass
|
||||
* @param {AppiumW3CCapabilities} [defaultCaps]
|
||||
* @param {Partial<BaseNSCapabilities>} [defaultCaps]
|
||||
*/
|
||||
export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
let address = defaultCaps['appium:address'] ?? TEST_HOST;
|
||||
@@ -130,7 +130,7 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
let postCommand;
|
||||
before(async function () {
|
||||
port = port ?? (await getTestPort());
|
||||
defaultCaps = {...defaultCaps, 'appium:port': port};
|
||||
defaultCaps = {...defaultCaps};
|
||||
d = new DriverClass({port, address});
|
||||
baseServer = await server({
|
||||
routeConfiguringFunction: routeConfiguringFunction(d),
|
||||
@@ -320,7 +320,7 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
});
|
||||
await B.delay(400);
|
||||
const value = await getSession(/** @type {string} */ (sessionId));
|
||||
value.error.should.equal('invalid session id');
|
||||
/** @type {string} */ (value.error).should.equal('invalid session id');
|
||||
should.equal(d.sessionId, null);
|
||||
const resp = (await endSession(newSession.sessionId)).data.value;
|
||||
/** @type {string} */ (/** @type { {error: string} } */ (resp).error).should.equal(
|
||||
@@ -426,10 +426,12 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Capabilities} Capabilities
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').DriverStatic} DriverStatic
|
||||
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
* @typedef {import('axios').AxiosRequestConfig} AxiosRequestConfig
|
||||
* @typedef {import('@appium/types').SingularSessionData} SingularSessionData
|
||||
*/
|
||||
@@ -440,14 +442,18 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef NewSessionData
|
||||
* @property {import('type-fest').RequireAtLeastOne<import('@appium/types').W3CCapabilities, 'firstMatch'|'alwaysMatch'>} capabilities
|
||||
* @property {import('type-fest').RequireAtLeastOne<import('@appium/types').W3CCapabilities<C, Extra>, 'firstMatch'|'alwaysMatch'>} capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=BaseDriverCapConstraints]
|
||||
* @template {StringRecord|void} [Extra=void]
|
||||
* @typedef NewSessionResponse
|
||||
* @property {string} sessionId,
|
||||
* @property {import('@appium/types').Capabilities} capabilities
|
||||
* @property {import('@appium/types').Capabilities<C, Extra>} capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
export * from './e2e-suite';
|
||||
export {createSessionHelpers, driverE2ETestSuite} from './e2e-suite';
|
||||
export * from './unit-suite';
|
||||
export * from './helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').Constraints} [C=import('@appium/types').BaseDriverCapConstraints]
|
||||
* @template {import('@appium/types').StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@ const {expect} = chai;
|
||||
/**
|
||||
* Creates unit test suites for a driver.
|
||||
* @param {DriverClass} DriverClass
|
||||
* @param {AppiumW3CCapabilities} [defaultCaps]
|
||||
* @param {Partial<BaseNSCapabilities>} [defaultCaps]
|
||||
*/
|
||||
|
||||
export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
||||
@@ -627,6 +627,11 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
||||
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').Constraints} [C=import('@appium/types').BaseDriverCapConstraints]
|
||||
* @template {import('@appium/types').StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,15 +44,14 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/lodash": "4.14.184",
|
||||
"@appium/types": "^0.5.0",
|
||||
"@types/lodash": "4.14.186",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.6",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "14.0.0",
|
||||
"sinon": "14.0.1",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.6](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.5...@appium/execute-driver-plugin@2.0.6) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.5](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.4...@appium/execute-driver-plugin@2.0.5) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.6",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -40,13 +40,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.11",
|
||||
"webdriverio": "7.24.0"
|
||||
"webdriverio": "7.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.2.2](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.1...@appium/fake-driver@4.2.2) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.1](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.0...@appium/fake-driver@4.2.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -47,9 +47,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"@xmldom/xmldom": "0.8.3",
|
||||
"asyncbox": "2.9.2",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.1.2](https://github.com/appium/appium/compare/@appium/fake-plugin@2.1.1...@appium/fake-plugin@2.1.2) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.1.1](https://github.com/appium/appium/compare/@appium/fake-plugin@2.1.0...@appium/fake-plugin@2.1.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-plugin",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"description": "A fake Appium 2.0 plugin",
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
@@ -33,10 +33,9 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^1.10.3",
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@appium/base-plugin": "^1.10.4",
|
||||
"@appium/support": "^2.61.0",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["source-map-support", "@babel/plugin-transform-runtime"],
|
||||
"plugins": ["source-map-support"],
|
||||
"comments": false,
|
||||
"sourceMaps": "both",
|
||||
"env": {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.0.7](https://github.com/appium/appium/compare/@appium/gulp-plugins@7.0.6...@appium/gulp-plugins@7.0.7) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/gulp-plugins
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.0.6](https://github.com/appium/appium/compare/@appium/gulp-plugins@7.0.5...@appium/gulp-plugins@7.0.6) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/gulp-plugins
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/gulp-plugins",
|
||||
"version": "7.0.6",
|
||||
"version": "7.0.7",
|
||||
"description": "Custom gulp plugins to be used across all appium modules",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,11 +44,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/eslint-config-appium": "^7.0.0",
|
||||
"@babel/core": "7.19.0",
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@babel/plugin-transform-runtime": "7.18.10",
|
||||
"@babel/preset-env": "7.19.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@babel/core": "7.19.3",
|
||||
"@babel/eslint-parser": "7.19.1",
|
||||
"@babel/preset-env": "7.19.4",
|
||||
"@octokit/rest": "18.12.0",
|
||||
"@types/fancy-log": "2.0.0",
|
||||
"@types/gulp": "4.0.9",
|
||||
@@ -60,7 +58,7 @@
|
||||
"babel-plugin-source-map-support": "2.2.0",
|
||||
"bluebird": "3.7.2",
|
||||
"del": "6.1.1",
|
||||
"eslint": "8.23.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
@@ -88,12 +86,12 @@
|
||||
"plugin-error": "2.0.0",
|
||||
"replace-in-file": "6.3.5",
|
||||
"rimraf": "3.0.2",
|
||||
"semver": "7.3.7",
|
||||
"semver": "7.3.8",
|
||||
"shell-quote": "1.7.3",
|
||||
"source-map-support": "0.5.21",
|
||||
"through2": "4.0.2",
|
||||
"vinyl-paths": "3.0.1",
|
||||
"yargs": "17.5.1"
|
||||
"yargs": "17.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"gulp": "^4.0.2"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.7](https://github.com/appium/appium/compare/@appium/images-plugin@1.3.6...@appium/images-plugin@1.3.7) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.6](https://github.com/appium/appium/compare/@appium/images-plugin@1.3.5...@appium/images-plugin@1.3.6) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/images-plugin",
|
||||
"version": "1.3.6",
|
||||
"version": "1.3.7",
|
||||
"description": "Plugin for working with images and image elements in Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -40,8 +40,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/opencv": "^1.0.12",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/opencv": "^1.0.13",
|
||||
"lru-cache": "7.14.0",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.13](https://github.com/appium/appium/compare/@appium/opencv@1.0.12...@appium/opencv@1.0.13) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/opencv
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.12](https://github.com/appium/appium/compare/@appium/opencv@1.0.11...@appium/opencv@1.0.12) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/opencv
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/opencv",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.13",
|
||||
"description": "OpenCV-related helper library",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,10 +44,9 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/bluebird": "3.5.36",
|
||||
"@types/bluebird": "3.5.37",
|
||||
"bluebird": "3.7.2",
|
||||
"jimp": "0.16.1",
|
||||
"jimp": "0.16.2",
|
||||
"lodash": "4.17.21",
|
||||
"opencv-bindings": "4.5.5",
|
||||
"source-map-support": "0.5.21"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.1](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.2.0...@appium/plugin-test-support@0.2.1) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/plugin-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 0.2.0 (2022-09-07)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/plugin-test-support",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "Testing utilities for Appium plugins",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,12 +44,11 @@
|
||||
"test:unit": "exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/types": "^0.5.0",
|
||||
"get-port": "5.1.1",
|
||||
"log-symbols": "4.1.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0"
|
||||
"teen_process": "2.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.0.0-beta.13](https://github.com/appium/appium/compare/@appium/relaxed-caps-plugin@1.0.0-beta.12...@appium/relaxed-caps-plugin@1.0.0-beta.13) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/relaxed-caps-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0-beta.12](https://github.com/appium/appium/compare/@appium/relaxed-caps-plugin@1.0.0-beta.11...@appium/relaxed-caps-plugin@1.0.0-beta.12) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/relaxed-caps-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/relaxed-caps-plugin",
|
||||
"version": "1.0.0-beta.12",
|
||||
"version": "1.0.0-beta.13",
|
||||
"description": "An Appium 2.0 plugin that loosens requirements for vendor prefixes on caps",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -31,7 +31,6 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.19.0",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.61.0](https://github.com/appium/appium/compare/@appium/support@2.60.0...@appium/support@2.61.0) (2022-10-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium,support:** enable detection of local extensions for improved DX ([b186928](https://github.com/appium/appium/commit/b186928e60493e7603dc3b27725dad8ee20d3750))
|
||||
* **support:** add fs.isExecutable() ([08f0bc8](https://github.com/appium/appium/commit/08f0bc8839e774bd94da70c1e3df28b1d03d0911))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.60.0](https://github.com/appium/appium/compare/@appium/support@2.59.5...@appium/support@2.60.0) (2022-09-07)
|
||||
|
||||
|
||||
|
||||
+67
-78
@@ -2,9 +2,9 @@
|
||||
import _ from 'lodash';
|
||||
import {homedir} from 'os';
|
||||
import path from 'path';
|
||||
import pkgDir from 'pkg-dir';
|
||||
import readPkg from 'read-pkg';
|
||||
import {npm} from './npm';
|
||||
import log from './logger';
|
||||
|
||||
/**
|
||||
* Path to the default `APPIUM_HOME` dir (`~/.appium`).
|
||||
@@ -29,69 +29,73 @@ export const MANIFEST_RELATIVE_PATH = path.join(
|
||||
MANIFEST_BASENAME
|
||||
);
|
||||
|
||||
const OLD_VERSION_REGEX = /^[01]/;
|
||||
|
||||
/**
|
||||
* Returns `true` if appium is installed as a dependency of a local package at `cwd`.
|
||||
*
|
||||
* The following special cases are considered:
|
||||
*
|
||||
* - If `resolved` starts with `file:`, then `cwd` likely points to `appium-monorepo`, and (for now) we expect `APPIUM_HOME` to be the default.
|
||||
* - If the `version` begins with `0` or `1`, we're looking at an old version of `appium` (not this one!)
|
||||
*
|
||||
* Note that we are _not_ performing the check "is the currently running Appium the same one as found in the list of dependencies", because I'm not sure it actually matters (assuming the versions are compatible).
|
||||
* Regardless we may want to make this more robust in the future, as we hit edge cases.
|
||||
*
|
||||
* _Also_ note that:
|
||||
*
|
||||
* - if `appium` appears as a dependency in `package.json` _but it is not yet installed_ this function will resolve `false`.
|
||||
* - if `appium` _does not appear in `package.json`_, but is installed anyway (extraneous) this function will resolve `false`.
|
||||
*
|
||||
* This may not be exactly what we want.
|
||||
* Resolves `true` if an `appium` dependency can be found somewhere in the given `cwd`.
|
||||
*
|
||||
* @param {string} cwd
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export async function hasAppiumDependency(cwd) {
|
||||
/**
|
||||
* @todo type this
|
||||
* @type {object}
|
||||
*/
|
||||
let listResult;
|
||||
/** @type {string|undefined} */
|
||||
let resolved;
|
||||
/** @type {string|undefined} */
|
||||
let version;
|
||||
try {
|
||||
listResult = await npm.list(cwd, 'appium');
|
||||
|
||||
// short-circuit if `appium` is hanging around but the user hasn't added it to `package.json`.
|
||||
if (listResult?.dependencies?.appium?.extraneous) {
|
||||
return false;
|
||||
}
|
||||
// if "resolved" is empty, then `appium` is in dependencies, but `npm install` has not been run.
|
||||
// in other words, this function can resolve `true` even if `resolved` is empty...
|
||||
resolved = listResult?.dependencies?.appium?.resolved ?? '';
|
||||
// ...however, it cannot do so unless `version` is nonempty.
|
||||
version = listResult?.dependencies?.appium?.version ?? '';
|
||||
} catch {
|
||||
try {
|
||||
const pkg = await readPackageInDir(cwd);
|
||||
// we're only going to look at these three fields for now, but we can change it later if need be.
|
||||
version = resolved =
|
||||
pkg?.dependencies?.appium ??
|
||||
pkg?.devDependencies?.appium ??
|
||||
pkg?.optionalDependencies?.appium;
|
||||
} catch {}
|
||||
}
|
||||
return Boolean(
|
||||
version &&
|
||||
(!resolved || (resolved && !resolved.startsWith('file:'))) &&
|
||||
// doing any further checking here may be a fool's errand, because you can pin the version
|
||||
// to a _lot_ of different things (tags, URLs, etc).
|
||||
!version.startsWith('1') &&
|
||||
!version.startsWith('0')
|
||||
);
|
||||
return Boolean(await findAppiumDependencyPackage(cwd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given `cwd`, use `npm` to find the closest package _or workspace root_, and return the path if the root depends upon `appium`.
|
||||
*
|
||||
* Looks at `dependencies` and `devDependencies` for `appium`.
|
||||
*/
|
||||
export const findAppiumDependencyPackage = _.memoize(
|
||||
/**
|
||||
* @param {string} [cwd]
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
async (cwd = process.cwd()) => {
|
||||
/**
|
||||
* Tries to read `package.json` in `cwd` and resolves the identity if it depends on `appium`;
|
||||
* otherwise resolves `undefined`.
|
||||
* @param {string} cwd
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
const readPkg = async (cwd) => {
|
||||
/** @type {string|undefined} */
|
||||
let pkgPath;
|
||||
try {
|
||||
const pkg = await readPackageInDir(cwd);
|
||||
const version =
|
||||
pkg?.dependencies?.appium ??
|
||||
pkg?.devDependencies?.appium ??
|
||||
pkg?.peerDependencies?.appium;
|
||||
pkgPath = version && !OLD_VERSION_REGEX.test(String(version)) ? cwd : undefined;
|
||||
} catch {}
|
||||
if (pkgPath) {
|
||||
log.debug(`Found package.json having current Appium dep in ${pkgPath}`);
|
||||
} else {
|
||||
log.debug(`No package.json having current Appium dep in ${cwd}`);
|
||||
}
|
||||
return pkgPath;
|
||||
};
|
||||
|
||||
cwd = path.resolve(cwd);
|
||||
|
||||
/** @type {string} */
|
||||
let pkgDir;
|
||||
try {
|
||||
const {json: list} = await npm.exec('list', ['--long', '--json'], {cwd});
|
||||
({path: pkgDir} = list);
|
||||
if (pkgDir) {
|
||||
log.debug(`Determined package/workspace root from ${cwd} => ${pkgDir}`);
|
||||
} else {
|
||||
pkgDir = cwd;
|
||||
}
|
||||
} catch {
|
||||
pkgDir = cwd;
|
||||
}
|
||||
return await readPkg(pkgDir);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Read a `package.json` in dir `cwd`. If none found, return `undefined`.
|
||||
*/
|
||||
@@ -110,8 +114,7 @@ export const readPackageInDir = _.memoize(
|
||||
* Determines location of Appium's "home" dir
|
||||
*
|
||||
* - If `APPIUM_HOME` is set in the environment, use that
|
||||
* - If we have an `extensions.yaml` in {@linkcode DEFAULT_APPIUM_HOME}, then use that.
|
||||
* - If we find a `package.json` in or above `cwd` and {@linkcode shouldUseCwdForAppiumHome} returns `true`, then use the directory containing the `package.json`.
|
||||
* - If we find a `package.json` in or above `cwd` and it has an `appium` dependency, use that.
|
||||
*
|
||||
* All returned paths will be absolute.
|
||||
*/
|
||||
@@ -126,30 +129,16 @@ export const resolveAppiumHome = _.memoize(
|
||||
}
|
||||
|
||||
if (process.env.APPIUM_HOME) {
|
||||
log.debug(`Using APPIUM_HOME from env: ${process.env.APPIUM_HOME}`);
|
||||
return path.resolve(cwd, process.env.APPIUM_HOME);
|
||||
}
|
||||
|
||||
/** @type {string|undefined} */
|
||||
let currentPkgDir;
|
||||
|
||||
try {
|
||||
currentPkgDir = await pkgDir(cwd);
|
||||
|
||||
// if we can't find a `package.json`, use the default
|
||||
if (!currentPkgDir) {
|
||||
return DEFAULT_APPIUM_HOME;
|
||||
} else {
|
||||
// it's unclear from the contract of `pkgDir` whether or not it can return a relative path,
|
||||
// so let's just be defensive.
|
||||
currentPkgDir = path.resolve(cwd, currentPkgDir);
|
||||
}
|
||||
} catch {
|
||||
// unclear if this can actually happen
|
||||
/* istanbul ignore next */
|
||||
return DEFAULT_APPIUM_HOME;
|
||||
const pkgPath = await findAppiumDependencyPackage(cwd);
|
||||
if (pkgPath) {
|
||||
return pkgPath;
|
||||
}
|
||||
|
||||
return (await hasAppiumDependency(currentPkgDir)) ? currentPkgDir : DEFAULT_APPIUM_HOME;
|
||||
log.debug(`Using default APPIUM_HOME: ${DEFAULT_APPIUM_HOME}`);
|
||||
return DEFAULT_APPIUM_HOME;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import sanitize from 'sanitize-filename';
|
||||
import which from 'which';
|
||||
import log from './logger';
|
||||
import Timer from './timing';
|
||||
import {isWindows} from './system';
|
||||
import {pluralize} from './util';
|
||||
|
||||
const ncpAsync =
|
||||
@@ -36,13 +37,38 @@ const findRootCached = _.memoize(pkgDir.sync);
|
||||
const fs = {
|
||||
/**
|
||||
* Resolves `true` if `path` is _readable_, which differs from Node.js' default behavior of "can we see it?"
|
||||
* @param {import('fs').PathLike} path
|
||||
*
|
||||
* On Windows, ACLs are not supported, so this becomes a simple check for existence.
|
||||
*
|
||||
* This function will never reject.
|
||||
* @param {PathLike} path
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async hasAccess(path) {
|
||||
try {
|
||||
await fsPromises.access(path, constants.R_OK);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves `true` if `path` is executable; `false` otherwise.
|
||||
*
|
||||
* On Windows, this function delegates to {@linkcode fs.hasAccess}.
|
||||
*
|
||||
* This function will never reject.
|
||||
* @param {PathLike} path
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async isExecutable(path) {
|
||||
try {
|
||||
if (isWindows()) {
|
||||
return await fs.hasAccess(path);
|
||||
}
|
||||
await fsPromises.access(path, constants.R_OK | constants.X_OK);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -50,7 +76,7 @@ const fs = {
|
||||
|
||||
/**
|
||||
* Alias for {@linkcode fs.hasAccess}
|
||||
* @param {import('fs').PathLike} path
|
||||
* @param {PathLike} path
|
||||
*/
|
||||
async exists(path) {
|
||||
return await fs.hasAccess(path);
|
||||
@@ -104,7 +130,7 @@ const fs = {
|
||||
|
||||
/**
|
||||
* Create an MD5 hash of a file.
|
||||
* @param {import('fs').PathLike} filePath
|
||||
* @param {PathLike} filePath
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async md5(filePath) {
|
||||
@@ -136,7 +162,7 @@ const fs = {
|
||||
|
||||
/**
|
||||
* Create a hex digest of some file at `filePath`
|
||||
* @param {import('fs').PathLike} filePath
|
||||
* @param {PathLike} filePath
|
||||
* @param {string} [algorithm]
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
@@ -170,7 +196,7 @@ const fs = {
|
||||
|
||||
/**
|
||||
* Recursively create a directory.
|
||||
* @param {import('fs').PathLike} dir
|
||||
* @param {PathLike} dir
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
async mkdirp(dir) {
|
||||
@@ -305,7 +331,7 @@ const fs = {
|
||||
/**
|
||||
* Warning: this is a promisified {@linkcode open fs.open}.
|
||||
* It resolves w/a file descriptor instead of a {@linkcode fsPromises.FileHandle FileHandle} object, as {@linkcode fsPromises.open} does. Use {@linkcode fs.openFile} if you want a `FileHandle`.
|
||||
* @type {(path: import('fs').PathLike, flags: import('fs').OpenMode, mode?: import('fs').Mode) => Promise<number>}
|
||||
* @type {(path: PathLike, flags: import('fs').OpenMode, mode?: import('fs').Mode) => Promise<number>}
|
||||
*/
|
||||
open: B.promisify(open),
|
||||
openFile: fsPromises.open,
|
||||
@@ -362,4 +388,5 @@ export default fs;
|
||||
/**
|
||||
* @typedef {import('glob')} glob
|
||||
* @typedef {import('mv')} mv
|
||||
* @typedef {import('fs').PathLike} PathLike
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/support",
|
||||
"version": "2.60.0",
|
||||
"version": "2.61.0",
|
||||
"description": "Support libs used across appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,13 +44,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.4.1",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/types": "^0.5.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/archiver": "5.3.1",
|
||||
"@types/base64-stream": "1.0.2",
|
||||
"@types/find-root": "1.1.2",
|
||||
"@types/glob": "7.2.0",
|
||||
"@types/glob": "8.0.0",
|
||||
"@types/jsftp": "2.1.2",
|
||||
"@types/klaw": "3.0.3",
|
||||
"@types/lockfile": "1.0.2",
|
||||
@@ -74,7 +73,7 @@
|
||||
"form-data": "4.0.0",
|
||||
"get-stream": "6.0.1",
|
||||
"glob": "8.0.3",
|
||||
"jimp": "0.16.1",
|
||||
"jimp": "0.16.2",
|
||||
"jsftp": "2.1.3",
|
||||
"klaw": "3.0.0",
|
||||
"lockfile": "1.0.4",
|
||||
@@ -93,12 +92,12 @@
|
||||
"resolve-from": "5.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"semver": "7.3.7",
|
||||
"semver": "7.3.8",
|
||||
"shell-quote": "1.7.3",
|
||||
"source-map-support": "0.5.21",
|
||||
"supports-color": "8.1.1",
|
||||
"teen_process": "1.16.0",
|
||||
"type-fest": "2.19.0",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.1.0",
|
||||
"uuid": "8.3.2",
|
||||
"which": "2.0.2",
|
||||
"yauzl": "2.10.0"
|
||||
|
||||
@@ -7,12 +7,15 @@ import {
|
||||
readPackageInDir,
|
||||
resolveAppiumHome,
|
||||
resolveManifestPath,
|
||||
findAppiumDependencyPackage,
|
||||
} from '../../lib/env';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('environment', function () {
|
||||
/** @type {string} */
|
||||
let cwd;
|
||||
/** @type {string|undefined} */
|
||||
let oldEnvAppiumHome;
|
||||
|
||||
before(async function () {
|
||||
@@ -23,6 +26,7 @@ describe('environment', function () {
|
||||
// all of these functions are memoized, so we need to reset them before each test.
|
||||
resolveManifestPath.cache = new Map();
|
||||
resolveAppiumHome.cache = new Map();
|
||||
findAppiumDependencyPackage.cache = new Map();
|
||||
readPackageInDir.cache = new Map();
|
||||
});
|
||||
|
||||
@@ -98,7 +102,10 @@ describe('environment', function () {
|
||||
await expect(resolveAppiumHome(cwd)).to.eventually.equal(DEFAULT_APPIUM_HOME);
|
||||
});
|
||||
});
|
||||
describe('when `appium` is a dependency', function () {
|
||||
describe('when `appium` is a dependency and APPIUM_HOME is unset', function () {
|
||||
beforeEach(function () {
|
||||
delete process.env.APPIUM_HOME;
|
||||
});
|
||||
describe('when `appium` is installed', function () {
|
||||
before(async function () {
|
||||
await fs.mkdirp(path.join(cwd, 'node_modules'));
|
||||
@@ -109,27 +116,34 @@ describe('environment', function () {
|
||||
});
|
||||
|
||||
describe('when `appium` is at the current version', function () {
|
||||
before(async function () {
|
||||
beforeEach(async function () {
|
||||
await fs.copyFile(
|
||||
path.join(__dirname, 'fixture', 'appium-v2-dependency.package.json'),
|
||||
path.join(cwd, 'package.json')
|
||||
);
|
||||
// await fs.symlink(path.join(__dirname, '..', 'appium'), path.join(cwd, 'node_modules', 'appium'), 'junction');
|
||||
await fs.copyFile(
|
||||
path.join(__dirname, 'fixture', 'appium-v2-package'),
|
||||
path.join(cwd, 'node_modules', 'appium')
|
||||
);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
afterEach(async function () {
|
||||
await fs.unlink(path.join(cwd, 'package.json'));
|
||||
});
|
||||
|
||||
it('should resolve with `cwd`', async function () {
|
||||
await expect(resolveAppiumHome(cwd)).to.eventually.equal(cwd);
|
||||
// NOTE: resolveAppiumHome() can resolve w/ a _real_ path by way of output from npm.
|
||||
// on macOS, /var/whatever is really /private/var/whatever.
|
||||
if (process.platform === 'darwin' && cwd.startsWith('/var/')) {
|
||||
await expect(resolveAppiumHome(cwd)).to.eventually.equal(path.join('/private', cwd));
|
||||
} else {
|
||||
await expect(resolveAppiumHome(cwd)).to.eventually.equal(cwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('when `appium` is an old version', function () {
|
||||
before(async function () {
|
||||
beforeEach(async function () {
|
||||
await fs.copyFile(
|
||||
path.join(__dirname, 'fixture', 'appium-v1-dependency.package.json'),
|
||||
path.join(cwd, 'package.json')
|
||||
@@ -140,7 +154,7 @@ describe('environment', function () {
|
||||
);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
afterEach(async function () {
|
||||
await fs.unlink(path.join(cwd, 'package.json'));
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import fs from '../../lib/fs';
|
||||
import {isWindows} from '../../lib/system';
|
||||
|
||||
describe('isExecutable()', function () {
|
||||
describe('when the path does not exist', function () {
|
||||
it('should return `false`', async function () {
|
||||
await fs.isExecutable('/path/to/nowhere').should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the path exists', function () {
|
||||
describe('when the path is not executable', function () {
|
||||
it('should return `false`', async function () {
|
||||
await fs.isExecutable(__filename).should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the path is executable', function () {
|
||||
beforeEach(function () {
|
||||
if (isWindows()) {
|
||||
return this.skip();
|
||||
}
|
||||
});
|
||||
it('should return `true`', async function () {
|
||||
await fs.isExecutable('/bin/bash').should.eventually.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the parameter is not a path', function () {
|
||||
it('should return `false`', async function () {
|
||||
await fs.isExecutable().should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -87,5 +87,5 @@ export function initMocks(sandbox = createSandbox()) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef { { exec: sinon.SinonStub<[string, string[], import('../lib/npm').TeenProcessExecOpts], import('../lib/npm').TeenProcessExecResult>, __stdout: string, __stderr: string, __code: number} } MockTeenProcess
|
||||
* @typedef { { exec: sinon.SinonStub<[string, string[], import('teen_process').ExecOptions], import('teen_process').ExecResult>, __stdout: string, __stderr: string, __code: number} } MockTeenProcess
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,10 @@ describe('env', function () {
|
||||
delete process.env.APPIUM_HOME;
|
||||
|
||||
env = rewiremock.proxy(() => require('../../lib/env'), overrides);
|
||||
|
||||
env.findAppiumDependencyPackage.cache = new Map();
|
||||
env.resolveManifestPath.cache = new Map();
|
||||
env.resolveAppiumHome.cache = new Map();
|
||||
});
|
||||
|
||||
describe('resolveManifestPath()', function () {
|
||||
@@ -94,12 +98,12 @@ describe('env', function () {
|
||||
|
||||
describe('when APPIUM_HOME is not set in env', function () {
|
||||
describe('when Appium is not resolvable from cwd', function () {
|
||||
describe('when `appium` is not a dependency of the local package', function () {
|
||||
describe('when `appium` is not a dependency of the package in the cwd', function () {
|
||||
beforeEach(function () {
|
||||
// this is needed because the default behavior is `resolvesArg(0)`; `.resolves()`
|
||||
// does not override this behavior! I don't know why!
|
||||
MockPkgDir.resetBehavior();
|
||||
MockPkgDir.resolves();
|
||||
// .resetBehavior();
|
||||
MockReadPkg.resolves();
|
||||
});
|
||||
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
@@ -109,35 +113,10 @@ describe('env', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency of the local package', function () {
|
||||
describe('when `appium` is a dependency of the package in the cwd', function () {
|
||||
describe('when the `appium` dependency spec begins with `file:`', function () {
|
||||
beforeEach(function () {
|
||||
MockPkgDir.resolves('/some/path/to/package.json');
|
||||
});
|
||||
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal(
|
||||
env.DEFAULT_APPIUM_HOME
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency which does not resolve to a file path`', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '2.0.0-beta.25',
|
||||
resolved: 'https://some/appium-tarball.tgz',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
MockReadPkg.resolves({devDependencies: {appium: 'file:../appium'}});
|
||||
});
|
||||
|
||||
it('should resolve with the identity', async function () {
|
||||
@@ -145,46 +124,19 @@ describe('env', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency which resolves to a file path`', function () {
|
||||
describe('when `appium` is a dependency which does not resolve to a file path`', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '2.0.0-beta.25',
|
||||
resolved: 'file:../some/relative/path',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
MockReadPkg.resolves({devDependencies: {appium: '2.0.0-beta.25'}});
|
||||
});
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal(
|
||||
env.DEFAULT_APPIUM_HOME
|
||||
);
|
||||
|
||||
it('should resolve with the identity', async function () {
|
||||
await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal('/somewhere');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency for version 0.x', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '0.1.2',
|
||||
resolved: 'https://whatever',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
MockReadPkg.resolves({devDependencies: {appium: '0.9.0'}});
|
||||
});
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal(
|
||||
@@ -195,20 +147,7 @@ describe('env', function () {
|
||||
|
||||
describe('when `appium` is a dependency for version 1.x', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '1.x',
|
||||
resolved: 'https://whatever',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
MockReadPkg.resolves({devDependencies: {appium: '1.2.3'}});
|
||||
});
|
||||
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
@@ -218,12 +157,29 @@ describe('env', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency of the workspace root of cwd', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
path: '/somewhere',
|
||||
}),
|
||||
});
|
||||
MockReadPkg.resolves({devDependencies: {appium: '2.x'}});
|
||||
});
|
||||
|
||||
it('should resolve with the workspace root', async function () {
|
||||
await expect(env.resolveAppiumHome('/somewhere/else')).to.eventually.equal(
|
||||
'/somewhere'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when reading `package.json` causes an exception', function () {
|
||||
beforeEach(function () {
|
||||
// unclear if this is even possible
|
||||
MockPkgDir.rejects(new Error('on the fritz'));
|
||||
MockReadPkg.rejects(new Error('on the fritz'));
|
||||
});
|
||||
|
||||
it('should resolve with DEFAULT_APPIUM_HOME', async function () {
|
||||
@@ -310,8 +266,8 @@ describe('env', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve `false`', async function () {
|
||||
await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false);
|
||||
it('should resolve `true`', async function () {
|
||||
await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -352,51 +308,6 @@ describe('env', function () {
|
||||
});
|
||||
|
||||
describe('when `appium` is installed', function () {
|
||||
describe('when `appium` is a dependency which does not resolve to a file path`', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '2.0.0-beta.25',
|
||||
resolved: 'https://some/appium-tarball.tgz',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve `true`', async function () {
|
||||
await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency which resolves to a file path`', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
stdout: JSON.stringify({
|
||||
version: '0.0.0',
|
||||
name: 'some-pkg',
|
||||
dependencies: {
|
||||
appium: {
|
||||
version: '2.0.0-beta.25',
|
||||
resolved: 'file:../some/relative/path',
|
||||
},
|
||||
},
|
||||
}),
|
||||
stderr: '',
|
||||
code: 0,
|
||||
});
|
||||
});
|
||||
it('should resolve `false`', async function () {
|
||||
await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `appium` is a dependency for version 0.x', function () {
|
||||
beforeEach(function () {
|
||||
MockTeenProcess.exec.resolves({
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.1](https://github.com/appium/appium/compare/@appium/test-support@2.0.0...@appium/test-support@2.0.1) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0](https://github.com/appium/appium/compare/@appium/test-support@1.5.0...@appium/test-support@2.0.0) (2022-09-07)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/test-support",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "A collection of test utilities used across Appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -48,13 +48,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@appium/support": "^2.61.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"loud-rejection": "2.2.0",
|
||||
"sinon": "14.0.0",
|
||||
"sinon": "14.0.1",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.5.0](https://github.com/appium/appium/compare/@appium/types@0.4.1...@appium/types@0.5.0) (2022-10-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** fix driverForSession declaration ([4d7d8e4](https://github.com/appium/appium/commit/4d7d8e4d38563aca5e9070ecab30aecad3205937))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **types:** adds types to derive capabilities from constraints ([4abe910](https://github.com/appium/appium/commit/4abe91004d4089e4280494a6d285b074c33a43c1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/appium/appium/compare/@appium/types@0.4.0...@appium/types@0.4.1) (2022-09-07)
|
||||
|
||||
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
import type {Capabilities as WdioCaps} from '@wdio/types';
|
||||
import {StringRecord, Constraint, Constraints} from '.';
|
||||
import {BaseDriverCapConstraints} from './constraints';
|
||||
|
||||
export type StandardCapabilities = WdioCaps.Capabilities;
|
||||
export type W3C_APPIUM_PREFIX = 'appium';
|
||||
|
||||
/**
|
||||
* The mask of a W3C-style namespaced capability proped name.
|
||||
* Base capabilities as derived from {@linkcode BaseDriverCapConstraints}.
|
||||
*/
|
||||
type Namespaced = `${string}:${string}`;
|
||||
export type BaseCapabilities = ConstraintsToCaps<BaseDriverCapConstraints>;
|
||||
|
||||
/**
|
||||
* An object with keys conforming to {@linkcode Namespaced}.
|
||||
* Like {@linkcode BaseCapabilities}, except all Appium-specific keys are namespaced.
|
||||
*/
|
||||
type NamespacedRecord = Record<Namespaced, any>;
|
||||
|
||||
/**
|
||||
* An object with keys for strings.
|
||||
*/
|
||||
type StringRecord = Record<string, any>;
|
||||
|
||||
/**
|
||||
* All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type, accepting additional optional caps.
|
||||
* All properties are optional.
|
||||
*/
|
||||
type BaseCapabilities<OptionalCaps extends StringRecord = StringRecord> = Partial<
|
||||
WdioCaps.Capabilities & OptionalCaps
|
||||
>;
|
||||
|
||||
/**
|
||||
* All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumCapabilities} type, accepting additional optional caps.
|
||||
*
|
||||
* In practice, the properties `platformName` and `automationName` are required by Appium.
|
||||
*/
|
||||
export type Capabilities<OptionalCaps extends StringRecord = StringRecord> = BaseCapabilities<
|
||||
WdioCaps.AppiumCapabilities & OptionalCaps
|
||||
>;
|
||||
|
||||
/**
|
||||
* All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumW3CCapabilities} type, accepting additional optional _namespaced_ caps.
|
||||
*/
|
||||
export type AppiumW3CCapabilities<
|
||||
OptionalNamespacedCaps extends NamespacedRecord = NamespacedRecord
|
||||
> = BaseCapabilities<WdioCaps.AppiumW3CCapabilities & OptionalNamespacedCaps>;
|
||||
|
||||
/**
|
||||
* All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumW3Capabilities} type, accepting additional optional _namespaced_ caps, in W3C-compatible format (`alwaysMatch`/`firstMatch`).
|
||||
* In practice, the properties `appium:platformName` and `appium:automationName` are required by Appium _somewhere_ in this object; this cannot be expressed in TypeScript.
|
||||
*/
|
||||
export type W3CCapabilities<OptionalNamespacedCaps extends NamespacedRecord = NamespacedRecord> = {
|
||||
alwaysMatch: AppiumW3CCapabilities<OptionalNamespacedCaps>;
|
||||
firstMatch: AppiumW3CCapabilities<OptionalNamespacedCaps>[];
|
||||
};
|
||||
export type BaseNSCapabilities = CapsToNSCaps<ConstraintsToCaps<BaseDriverCapConstraints>>;
|
||||
|
||||
/**
|
||||
* These may (or should) be reused by drivers.
|
||||
@@ -57,3 +24,168 @@ export type AppiumXCUICommandTimeouts = WdioCaps.AppiumXCUICommandTimeouts;
|
||||
export type AppiumXCUIProcessArguments = WdioCaps.AppiumXCUIProcessArguments;
|
||||
export type AppiumXCUISafariGlobalPreferences = WdioCaps.AppiumXCUISafariGlobalPreferences;
|
||||
export type AppiumXCUITestCapabilities = WdioCaps.AppiumXCUITestCapabilities;
|
||||
|
||||
/**
|
||||
* Given a {@linkcode Constraint} `C` and a type `T`, see if `inclusion`/`inclusionCaseInsensitive` is present, and create a union of its allowed literals; otherwise just use `T`.
|
||||
*/
|
||||
type ConstraintChoice<
|
||||
C extends Constraint,
|
||||
T
|
||||
> = C['inclusionCaseInsensitive'] extends ReadonlyArray<T>
|
||||
? AnyCase<C['inclusionCaseInsensitive'][number]>
|
||||
: C['inclusion'] extends ReadonlyArray<T>
|
||||
? C['inclusion'][number]
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Given {@linkcode Constraint} `C`, determine the associated type of the capability.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - Only `number` and `string` values can have "choices" (`inclusion`/`inclusionCaseInesnsitive`) associated with them.
|
||||
* - If `isArray` is `true`, the type is always of type `string[]`. If this is incorrect, then it will be `any[]`.
|
||||
* - There is no way to express the shape of an object if `ifObject` is `true`.
|
||||
*/
|
||||
type ConstraintToCapKind<C extends Constraint> = C['isString'] extends true
|
||||
? ConstraintChoice<C, string>
|
||||
: C['isNumber'] extends true
|
||||
? ConstraintChoice<C, number>
|
||||
: C['isBoolean'] extends true
|
||||
? boolean
|
||||
: C['isArray'] extends true
|
||||
? string[]
|
||||
: C['isObject'] extends true
|
||||
? object
|
||||
: any;
|
||||
|
||||
/**
|
||||
* Given {@linkcode Constraint} `C`, determine if it is required or optional.
|
||||
*
|
||||
* In practice, _all_ capabilities are considered optional per types, but various errors might be thrown if some are not present.
|
||||
*/
|
||||
export type ConstraintToCap<C extends Constraint> = C['presence'] extends
|
||||
| true
|
||||
| {allowEmpty: boolean}
|
||||
? ConstraintToCapKind<C>
|
||||
: ConstraintToCapKind<C> | undefined;
|
||||
|
||||
/**
|
||||
* Given `string` `T`, this is a case-insensitive version of `T`.
|
||||
*/
|
||||
export type AnyCase<T extends string> = string extends T
|
||||
? string
|
||||
: T extends `${infer F1}${infer F2}${infer R}`
|
||||
? `${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${AnyCase<R>}`
|
||||
: T extends `${infer F}${infer R}`
|
||||
? `${Uppercase<F> | Lowercase<F>}${AnyCase<R>}`
|
||||
: '';
|
||||
|
||||
/**
|
||||
* Given {@linkcode StringRecord} `T` and namespace string `NS`, a type with the key names prefixed by `${NS}:` _except_ for standard capabilities. `NS` defaults to `appium`.
|
||||
*
|
||||
* If `T` is already namespaced, well, it'll get _double_-namespaced.
|
||||
*/
|
||||
export type CapsToNSCaps<T extends StringRecord, NS extends string = W3C_APPIUM_PREFIX> = {
|
||||
[K in keyof T as K extends keyof StandardCapabilities
|
||||
? K
|
||||
: NamespacedString<K & string, NS>]: T[K];
|
||||
};
|
||||
|
||||
export type NamespacedString<
|
||||
S extends string,
|
||||
NS extends string = W3C_APPIUM_PREFIX
|
||||
> = `${NS}:${S}`;
|
||||
|
||||
/**
|
||||
* Converts {@linkcode Constraint} `C` to a {@linkcode Capabilities} object.
|
||||
*/
|
||||
export type ConstraintsToCaps<C extends Constraints> = {
|
||||
-readonly [K in keyof C]: ConstraintToCap<C[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given some constraints, return the entire set of supported capabilities it supports (including whatever is in its desired caps).
|
||||
*
|
||||
* Does not contain {@linkcode BaseCapabilities}; see {@linkcode DriverCaps}.
|
||||
*/
|
||||
export type Capabilities<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = Partial<ConstraintsToCaps<C> & Extra>;
|
||||
|
||||
/**
|
||||
* Like {@linkcode Capabilities}, except W3C-style.
|
||||
*
|
||||
* Does not contain {@linkcode BaseCapabilities}; see {@linkcode W3CDriverCaps}.
|
||||
*/
|
||||
export type W3CCapabilities<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = {
|
||||
alwaysMatch: NSCapabilities<C, Extra>;
|
||||
firstMatch: NSCapabilities<C, Extra>[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespaced caps (where appropriate).
|
||||
*
|
||||
* Does not contain {@linkcode BaseCapabilities}; see {@linkcode NSDriverCaps}.
|
||||
*/
|
||||
export type NSCapabilities<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void,
|
||||
NS extends string = W3C_APPIUM_PREFIX
|
||||
> = Partial<CapsToNSCaps<ConstraintsToCaps<C> & Extra, NS>>;
|
||||
|
||||
/**
|
||||
* Capabilities for drivers extending `BaseDriver`.
|
||||
*
|
||||
* Includes {@linkcode BaseCapabilities}.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyDriver extends BaseDriver {
|
||||
* async createSession (w3ccaps: W3CDriverCaps<MyDriverConstraints>, ...args: any[]) {
|
||||
* const [
|
||||
* sessionId: string,
|
||||
* caps: DriverCaps<MyDriverConstraints>
|
||||
* ] = await super.createSession(w3ccaps, ...args);
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
export type DriverCaps<
|
||||
C extends Constraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = Capabilities<BaseDriverCapConstraints & C, Extra>;
|
||||
|
||||
/**
|
||||
* W3C-style capabilities for drivers extending `BaseDriver`.
|
||||
*
|
||||
* Includes {@linkcode BaseCapabilities}.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyDriver extends BaseDriver {
|
||||
* async createSession (w3ccaps: W3CDriverCaps<MyDriverConstraints>, ...args: any[]) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type W3CDriverCaps<
|
||||
C extends Constraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = W3CCapabilities<BaseDriverCapConstraints & C, Extra>;
|
||||
|
||||
/**
|
||||
* Namespaced capabilities for drivers extending `BaseDriver`.
|
||||
*
|
||||
* Includes {@linkcode BaseCapabilities}.
|
||||
*/
|
||||
export type NSDriverCaps<
|
||||
C extends Constraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = NSCapabilities<BaseDriverCapConstraints & C, Extra>;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
export const BASE_DESIRED_CAP_CONSTRAINTS = {
|
||||
platformName: {
|
||||
presence: true,
|
||||
isString: true,
|
||||
},
|
||||
app: {
|
||||
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,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type BaseDriverCapConstraints = typeof BASE_DESIRED_CAP_CONSTRAINTS;
|
||||
@@ -2,14 +2,17 @@ import type {EventEmitter} from 'events';
|
||||
import {Element, ActionSequence} from './action';
|
||||
import {
|
||||
HTTPMethod,
|
||||
Capabilities,
|
||||
AppiumServer,
|
||||
UpdateServerCallback,
|
||||
Class,
|
||||
MethodMap,
|
||||
AppiumLogger,
|
||||
StringRecord,
|
||||
ConstraintsToCaps,
|
||||
BaseDriverCapConstraints,
|
||||
W3CCapabilities,
|
||||
Capabilities,
|
||||
} from '.';
|
||||
import {W3CCapabilities} from './capabilities';
|
||||
import {ServerArgs} from './config';
|
||||
|
||||
export interface TimeoutCommands {
|
||||
@@ -46,24 +49,30 @@ export interface SessionCommands {
|
||||
}
|
||||
|
||||
export interface ExecuteCommands {
|
||||
executeMethod(script: string, args: [Record<string, any>]|[]): Promise<any>;
|
||||
executeMethod(script: string, args: [StringRecord] | []): Promise<any>;
|
||||
}
|
||||
|
||||
export interface ExecuteMethodDef {
|
||||
command: string,
|
||||
command: string;
|
||||
params?: {
|
||||
required?: string[],
|
||||
optional?: string[],
|
||||
}
|
||||
};
|
||||
required?: string[];
|
||||
optional?: string[];
|
||||
};
|
||||
}
|
||||
export type ExecuteMethodMap = Record<string, ExecuteMethodDef>;
|
||||
|
||||
export interface MultiSessionData {
|
||||
export interface MultiSessionData<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> {
|
||||
id: string;
|
||||
capabilities: Capabilities;
|
||||
capabilities: Capabilities<C, Extra>;
|
||||
}
|
||||
|
||||
export type SingularSessionData = Capabilities & {events?: EventHistory};
|
||||
export type SingularSessionData<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> = Capabilities<C, Extra> & {events?: EventHistory; error?: string};
|
||||
|
||||
export interface FindCommands {
|
||||
findElement(strategy: string, selector: string): Promise<Element>;
|
||||
@@ -106,15 +115,20 @@ export interface LogCommands {
|
||||
}
|
||||
|
||||
export interface SettingsCommands {
|
||||
updateSettings: (settings: Record<string, any>) => Promise<void>;
|
||||
getSettings(): Promise<Record<string, any>>;
|
||||
updateSettings: (settings: StringRecord) => Promise<void>;
|
||||
getSettings(): Promise<StringRecord>;
|
||||
}
|
||||
|
||||
export interface SessionHandler<CreateResult, DeleteResult> {
|
||||
export interface SessionHandler<
|
||||
CreateResult,
|
||||
DeleteResult,
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
Extra extends StringRecord | void = void
|
||||
> {
|
||||
createSession(
|
||||
w3cCaps1: W3CCapabilities,
|
||||
w3cCaps2?: W3CCapabilities,
|
||||
w3cCaps?: W3CCapabilities,
|
||||
w3cCaps1: W3CCapabilities<C, Extra>,
|
||||
w3cCaps2?: W3CCapabilities<C, Extra>,
|
||||
w3cCaps?: W3CCapabilities<C, Extra>,
|
||||
driverData?: DriverData[]
|
||||
): Promise<CreateResult>;
|
||||
|
||||
@@ -140,24 +154,24 @@ export type DriverData = Record<string, unknown>;
|
||||
* }
|
||||
*/
|
||||
export interface Constraint {
|
||||
presence?: boolean | {allowEmpty: boolean};
|
||||
isString?: boolean;
|
||||
isNumber?: boolean;
|
||||
isBoolean?: boolean;
|
||||
isObject?: boolean;
|
||||
isArray?: boolean;
|
||||
deprecated?: boolean;
|
||||
inclusion?: any[];
|
||||
inclusionCaseInsensitive?: any[];
|
||||
readonly presence?: boolean | Readonly<{allowEmpty: boolean}>;
|
||||
readonly isString?: boolean;
|
||||
readonly isNumber?: boolean;
|
||||
readonly isBoolean?: boolean;
|
||||
readonly isObject?: boolean;
|
||||
readonly isArray?: boolean;
|
||||
readonly deprecated?: boolean;
|
||||
readonly inclusion?: Readonly<[any, ...any[]]>;
|
||||
readonly inclusionCaseInsensitive?: Readonly<[any, ...any[]]>;
|
||||
}
|
||||
export type Constraints = Record<string, Constraint>;
|
||||
export type Constraints = Readonly<Record<string, Constraint>>;
|
||||
|
||||
export interface DriverHelpers {
|
||||
export interface DriverHelpers<C extends Constraints> {
|
||||
configureApp: (app: string, supportedAppExtensions: string[]) => Promise<string>;
|
||||
isPackageOrBundle: (app: string) => boolean;
|
||||
duplicateKeys: <T>(input: T, firstKey: string, secondKey: string) => T;
|
||||
parseCapsArray: (cap: string | string[]) => string[];
|
||||
generateDriverLogPrefix: (obj: Core, sessionId?: string) => string;
|
||||
generateDriverLogPrefix: (obj: Core<C>, sessionId?: string) => string;
|
||||
}
|
||||
|
||||
export type SettingsUpdateListener<T extends Record<string, unknown> = Record<string, unknown>> = (
|
||||
@@ -267,15 +281,13 @@ export interface EventHistoryCommand {
|
||||
*
|
||||
* This should not be used directly by external code.
|
||||
*/
|
||||
export interface Core {
|
||||
export interface Core<C extends Constraints = BaseDriverCapConstraints> {
|
||||
shouldValidateCaps: boolean;
|
||||
sessionId: string | null;
|
||||
opts: DriverOpts;
|
||||
opts: DriverOpts<C>;
|
||||
initialOpts: ServerArgs;
|
||||
caps?: Capabilities;
|
||||
originalCaps?: W3CCapabilities;
|
||||
protocol?: string;
|
||||
helpers: DriverHelpers;
|
||||
helpers: DriverHelpers<C>;
|
||||
basePath: string;
|
||||
relaxedSecurityEnabled: boolean;
|
||||
allowInsecure: string[];
|
||||
@@ -306,32 +318,36 @@ export interface Core {
|
||||
getManagedDrivers(): Driver[];
|
||||
clearNewCommandTimeout(): Promise<void>;
|
||||
logEvent(eventName: string): void;
|
||||
driverForSession(sessionId: string): Core | null;
|
||||
driverForSession(sessionId: string): Core<C> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* `BaseDriver` implements this. It contains default behavior;
|
||||
* external drivers are expected to implement {@linkcode ExternalDriver} instead.
|
||||
*/
|
||||
export interface Driver
|
||||
extends SessionCommands,
|
||||
export interface Driver<
|
||||
C extends Constraints = BaseDriverCapConstraints,
|
||||
A extends StringRecord = StringRecord
|
||||
> extends SessionCommands,
|
||||
LogCommands,
|
||||
FindCommands,
|
||||
SettingsCommands,
|
||||
TimeoutCommands,
|
||||
EventCommands,
|
||||
SessionHandler<[string, any], void>,
|
||||
SessionHandler<[string, any], void, C>,
|
||||
ExecuteCommands,
|
||||
Core {
|
||||
cliArgs?: Record<string, any>;
|
||||
cliArgs?: A;
|
||||
// The following methods are implemented by `BaseDriver`.
|
||||
executeCommand(cmd: string, ...args: any[]): Promise<any>;
|
||||
startUnexpectedShutdown(err?: Error): Promise<void>;
|
||||
startNewCommandTimeout(): Promise<void>;
|
||||
reset(): Promise<void>;
|
||||
desiredCapConstraints: Constraints;
|
||||
validateDesiredCaps(caps: Capabilities): boolean;
|
||||
logExtraCaps(caps: Capabilities): void;
|
||||
caps?: Capabilities<C>;
|
||||
originalCaps?: W3CCapabilities<C>;
|
||||
desiredCapConstraints: C;
|
||||
validateDesiredCaps(caps: Capabilities<C>): boolean;
|
||||
logExtraCaps(caps: Capabilities<C>): void;
|
||||
assignServer?(server: AppiumServer, host: string, port: number, path: string): void;
|
||||
}
|
||||
|
||||
@@ -581,12 +597,18 @@ export type DriverClass<
|
||||
S extends DriverStatic = DriverStatic
|
||||
> = Class<D, S, [] | [Partial<ServerArgs>] | [Partial<ServerArgs>, boolean]>;
|
||||
|
||||
export interface ExtraDriverOpts {
|
||||
fastReset?: boolean;
|
||||
skipUninstall?: boolean;
|
||||
}
|
||||
/**
|
||||
* Options as passed into a driver constructor, which is just a union of {@linkcode ServerArgs} and {@linkcode Capabilities}.
|
||||
*
|
||||
* The combination happens within Appium prior to calling the constructor.
|
||||
*/
|
||||
export type DriverOpts = ServerArgs & Capabilities;
|
||||
export type DriverOpts<C extends Constraints = BaseDriverCapConstraints> = ServerArgs &
|
||||
ExtraDriverOpts &
|
||||
Partial<ConstraintsToCaps<C>>;
|
||||
|
||||
export type DriverCommand<TArgs = any, TReturn = unknown> = (...args: TArgs[]) => Promise<TReturn>;
|
||||
|
||||
|
||||
+32
-24
@@ -3,7 +3,6 @@ import type {Socket} from 'net';
|
||||
import type {Server} from 'http';
|
||||
import type {Class as _Class, ConditionalPick, MultidimensionalReadonlyArray} from 'type-fest';
|
||||
import {ServerArgs} from './config';
|
||||
import {Capabilities, W3CCapabilities} from './capabilities';
|
||||
import type {Express} from 'express';
|
||||
import {ExternalDriver} from './driver';
|
||||
import type {Logger} from 'npmlog';
|
||||
@@ -11,10 +10,12 @@ import type {Logger} from 'npmlog';
|
||||
export * from './driver';
|
||||
export * from './action';
|
||||
export * from './plugin';
|
||||
export {AppiumW3CCapabilities} from './capabilities';
|
||||
export {AppiumConfig, NormalizedAppiumConfig} from './config';
|
||||
export * from './capabilities';
|
||||
export * from './constraints';
|
||||
export * from './config';
|
||||
export * from './appium-config';
|
||||
export {ServerArgs, Capabilities, W3CCapabilities};
|
||||
|
||||
export type StringRecord = Record<string, any>;
|
||||
|
||||
/**
|
||||
* A log prefix for {@linkcode AppiumLogger}
|
||||
@@ -63,15 +64,10 @@ export type AppiumServer = Omit<Server, 'close'> & AppiumServerExtension;
|
||||
|
||||
export interface AppiumServerExtension {
|
||||
close(): Promise<void>;
|
||||
addWebSocketHandler(
|
||||
handlerPathname: string,
|
||||
handlerServer: WSServer
|
||||
): Promise<void>;
|
||||
addWebSocketHandler(handlerPathname: string, handlerServer: WSServer): Promise<void>;
|
||||
removeWebSocketHandler(handlerPathname: string): Promise<boolean>;
|
||||
removeAllWebSocketHandlers(): Promise<boolean>;
|
||||
getWebSocketHandlers(
|
||||
keysFilter: string | null | undefined
|
||||
): Promise<Record<string, WSServer>>;
|
||||
getWebSocketHandlers(keysFilter: string | null | undefined): Promise<Record<string, WSServer>>;
|
||||
webSocketsMapping: Record<string, WSServer>;
|
||||
}
|
||||
|
||||
@@ -164,8 +160,11 @@ export type ExtensionType = DriverType | PluginType;
|
||||
* @param httpServer - the node HTTP server that hosts the app
|
||||
* @param cliArgs - Arguments from config files, CLI, etc.
|
||||
*/
|
||||
export type UpdateServerCallback = (expressApp: Express, httpServer: AppiumServer, cliArgs: ServerArgs) => Promise<void>;
|
||||
|
||||
export type UpdateServerCallback = (
|
||||
expressApp: Express,
|
||||
httpServer: AppiumServer,
|
||||
cliArgs: ServerArgs
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Possible HTTP methods, as stolen from `axios`.
|
||||
@@ -173,14 +172,23 @@ export type UpdateServerCallback = (expressApp: Express, httpServer: AppiumServe
|
||||
* @see https://npm.im/axios
|
||||
*/
|
||||
export type HTTPMethod =
|
||||
| 'get' | 'GET'
|
||||
| 'delete' | 'DELETE'
|
||||
| 'head' | 'HEAD'
|
||||
| 'options' | 'OPTIONS'
|
||||
| 'post' | 'POST'
|
||||
| 'put' | 'PUT'
|
||||
| 'patch' | 'PATCH'
|
||||
| 'purge' | 'PURGE'
|
||||
| 'link' | 'LINK'
|
||||
| 'unlink' | 'UNLINK';
|
||||
|
||||
| 'get'
|
||||
| 'GET'
|
||||
| 'delete'
|
||||
| 'DELETE'
|
||||
| 'head'
|
||||
| 'HEAD'
|
||||
| 'options'
|
||||
| 'OPTIONS'
|
||||
| 'post'
|
||||
| 'POST'
|
||||
| 'put'
|
||||
| 'PUT'
|
||||
| 'patch'
|
||||
| 'PATCH'
|
||||
| 'purge'
|
||||
| 'PURGE'
|
||||
| 'link'
|
||||
| 'LINK'
|
||||
| 'unlink'
|
||||
| 'UNLINK';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/types",
|
||||
"version": "0.4.1",
|
||||
"version": "0.5.0",
|
||||
"description": "Various type declarations used across Appium",
|
||||
"keywords": [
|
||||
"appium",
|
||||
@@ -23,6 +23,7 @@
|
||||
"build",
|
||||
"lib"
|
||||
],
|
||||
"main": "./build/index.js",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/generate-schema-types.js",
|
||||
"dev": "npm run build -- --watch",
|
||||
@@ -33,11 +34,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/schema": "^0.0.9",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/npmlog": "4.1.4",
|
||||
"@types/ws": "8.5.3",
|
||||
"@wdio/types": "7.24.0",
|
||||
"type-fest": "2.19.0"
|
||||
"@wdio/types": "7.25.1",
|
||||
"type-fest": "3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
"paths": {
|
||||
"@appium/schema": ["../schema"]
|
||||
},
|
||||
"emitDeclarationOnly": false,
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2015"]
|
||||
},
|
||||
"include": ["lib"],
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.0.0-beta.15](https://github.com/appium/appium-plugins/compare/@appium/universal-xml-plugin@1.0.0-beta.14...@appium/universal-xml-plugin@1.0.0-beta.15) (2022-10-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/universal-xml-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0-beta.14](https://github.com/appium/appium-plugins/compare/@appium/universal-xml-plugin@1.0.0-beta.13...@appium/universal-xml-plugin@1.0.0-beta.14) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @appium/universal-xml-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/universal-xml-plugin",
|
||||
"version": "1.0.0-beta.14",
|
||||
"version": "1.0.0-beta.15",
|
||||
"description": "Appium plugin for making XML source and XPath queries the same across iOS and Android",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -39,7 +39,6 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.19.0",
|
||||
"@types/xmldom": "0.1.31",
|
||||
"fast-xml-parser": "3.21.1",
|
||||
"source-map-support": "0.5.21",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"compilerOptions": {
|
||||
"noErrorTruncation": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"references": [
|
||||
|
||||
Reference in New Issue
Block a user