Compare commits

...
Author SHA1 Message Date
Jonathan Lipps f545a6cde5 Publish
- appium@2.0.0-beta.45
 - @appium/base-driver@8.7.2
 - @appium/base-plugin@1.10.4
 - @appium/doctor@1.16.27
 - @appium/docutils@0.0.12
 - @appium/driver-test-support@0.2.1
 - @appium/execute-driver-plugin@2.0.6
 - @appium/fake-driver@4.2.2
 - @appium/fake-plugin@2.1.2
 - @appium/gulp-plugins@7.0.7
 - @appium/images-plugin@1.3.7
 - @appium/opencv@1.0.13
 - @appium/plugin-test-support@0.2.1
 - @appium/relaxed-caps-plugin@1.0.0-beta.13
 - @appium/support@2.61.0
 - @appium/test-support@2.0.1
 - @appium/types@0.5.0
 - @appium/universal-xml-plugin@1.0.0-beta.15
2022-10-13 16:25:16 -07:00
renovate[bot] c7072027d6 chore(workflows): update actions/checkout digest to 93ea575 2022-10-11 19:04:38 +00:00
renovate[bot] ed8b65ce72 chore(gulp-plugins): update dependency @babel/preset-env to v7.19.4 2022-10-11 18:43:03 +00:00
Mykola Mokhnach 9399654c8a docs: Delete the absolute requirements section 2022-10-11 15:19:21 +02:00
Mykola Mokhnach 1547c7535e docs: Update Appium 2 installation tutorial (#17590) 2022-10-11 15:15:32 +02:00
renovate[bot] b18ba50f8e chore(fake-driver): update dependency @xmldom/xmldom to v0.8.3 2022-10-11 09:58:42 +00:00
renovate[bot] 24a0efd695 chore(types): update dependency type-fest to v3.1.0 2022-10-10 10:13:17 +00:00
renovate[bot] 2a452cec4b chore(base-driver): update dependency express to v4.18.2 2022-10-09 08:06:46 +00:00
renovate[bot] 8c7acd52a5 chore(gulp-plugins): update dependency eslint to v8.25.0 2022-10-08 07:34:35 +00:00
renovate[bot] 5bca642efa chore(base-driver): update dependency body-parser to v1.20.1 2022-10-07 13:18:13 +00:00
Mykola Mokhnach 6c451e1f7d chore(appium): Remove logging from adjustNodePath helper (#17578) 2022-10-07 10:58:56 +02:00
Christopher Hiller 9ce09845c8 chore(): replaced a ternary 2022-10-06 15:30:22 -07:00
Christopher Hiller 4d7d8e4d38 fix(types): fix driverForSession declaration 2022-10-06 15:30:22 -07:00
Christopher Hiller a3ad768802 chore(): review fixes 2022-10-06 15:30:22 -07:00
Christopher Hiller b20d0b82a2 chore(appium,base-driver,driver-test-support,types): overhaul types for constraints and caps
The big idea here is:

1. `desiredCapConstraints` should always contain the constraints _for that driver_; it no longer contains the "base" constraints. This is the only thing that really required any logic changes (though they are somewhat spread out).  The "merged" constraints object is available via `_desiredCapConstraints`.
2. `Capabilities` and W3C-style `W3CCapabilities` are now _derived from_ constraints.  This absolves us (and extension authors) from needing to create an interface describing their capabilities.
3. Changes to caps and constraints now reflect in `DriverOpts` (`this.opts`) in drivers
4. `caps` and `originalCaps` declarations have moved from `Core` to `Driver`
5. TypeScript compiles the JS output from `@appium/types` (which is just a static "constraints" object)

Resolves #17518
2022-10-06 15:30:22 -07:00
Christopher Hiller 2fa8b2d0c1 fix(ci): do not install fake-driver; it is already present 2022-10-06 15:15:18 -07:00
Christopher Hiller b186928e60 feat(appium,support): enable detection of local extensions for improved DX
This change makes Appium recognize the existence of an extension in the current (or parent) dir, and automatically adds it to the extension manifest.

Resolution of Appium (e.g., _how_ Appium was installed) is no longer taken into account when determining if we're "in" a package that depends upon Appium.  So you can easily `npm link appium` in an extension working copy if you wish, and this will not impact Appium's behavior.

To support this change, `hasAppiumDependency()` in `@appium/support`'s `env` has changed to be a wrapper around the new `findAppiumDependency()`.  The strategy here is to use `npm list --long` to get detailed information about the current directory.  The result will have an object with a `path` property, and that property points to the current workspace root (or current project root).  We can then read the `package.json` in the `path` property--or just the CWD--to make the determination about an `appium` dependency (or lack thereof).  For extension working copies, this will return `true` because `appium` will be present in `devDependencies`, `peerDependencies`, or both.  For any other project, it will usually just be in `devDependencies`.  If there's no `package.json` in the CWD and we're not in a workspace, Appium continues to use the default `APPIUM_HOME`.

One side effect of this is that running `appium` from the monorepo itself treats the dir hierarchy as a package which has an `appium` dependency (because it does!).  To that end, the default `APPIUM_HOME` will _not_ be used when running `appium` from _anywhere in the monorepo_ unless you explicitly set `APPIUM_HOME` in an environment variable.  Generally, our tests prescribe their own temporary `APPIUM_HOME` directories, so this should have no effect on them (in the general case).  It _will_ have an effect on running `appium` manually in a development environment, as the workspace root will be treated as `APPIUM_HOME`; running `node ./packages/appium/index.js driver install uiautomator2` (or `npm start -- driver install uiautomator2`) will install that driver as a dev dependency (and save it).  I could add a special case _just for the monorepo_ if we don't want this to happen, but it may introduce complexity we/I don't want to maintain, so I left it out.  Furthermore, `npm start -- driver list` will show the `fake` driver, and `npm start -- plugin list` will show all of the plugins in `packages/`.

Regardless, I view this as a solid win for extension developers.

Bonus:

While I don't have benchmarks, this should considerably improve performance when detecting locally-installed (`node_modules/some-extension`) or extension working copies.  Instead of using `klaw` to walk the filesystem (in serial!) we can just use `glob` to look for relevant `package.json` files.  Instead of waiting until `glob` is completely finished searching, we can listen for its `match` evens and begin processing `package.json` files as soon as they are found. Because `@appium/support` only exposes a promisifed `glob()`--with no access to the `EventEmitter` it returns--`appium` depends on `glob` directly.
2022-10-06 15:15:18 -07:00
Christopher Hiller cc04e6595b chore(): remove babel-runtime
Closes #17536
2022-10-06 11:51:49 -07:00
renovate[bot] c46060cae7 chore(appium): update dependency yaml to v2.1.3 2022-10-06 11:02:59 +00:00
renovate[bot] e7c3536c15 chore(): update dependency @types/mocha to v10 2022-10-05 13:24:12 -07:00
renovate[bot] 45e653151d chore(types): update dependency type-fest to v3 2022-10-05 13:24:04 -07:00
renovate[bot] 4829984e2e chore(support): update dependency semver to v7.3.8 2022-10-05 13:43:21 +00:00
renovate[bot] 46644bc2d1 chore(test-support): update dependency sinon to v14.0.1 2022-10-04 15:08:39 +00:00
renovate[bot] 2752df1aae chore(doctor): update dependency appium-adb to v9.10.11 2022-10-04 10:09:40 +00:00
renovate[bot] daaf86220b chore(): update dependency @types/node to v16.11.64 2022-10-04 09:37:13 +00:00
renovate[bot] 4a7cab26fa chore(appium): update dependency yaml to v2.1.2 2022-10-03 11:13:04 +00:00
renovate[bot] 07c8660a50 chore(): update dependency @types/node to v16.11.63 2022-10-03 10:46:25 +00:00
renovate[bot] 04231eb833 chore(gulp-plugins): update dependency yargs to v17.6.0 2022-10-01 08:03:07 +00:00
renovate[bot] 160238e929 chore(types): update webdriverio monorepo to v7.25.1 2022-10-01 07:35:17 +00:00
renovate[bot] bda6746d7b chore(driver-test-support): update dependency @types/lodash to v4.14.186 2022-09-29 08:26:26 +00:00
renovate[bot] a77e5398f6 chore(): update babel monorepo to v7.19.3 2022-09-28 11:43:54 +00:00
renovate[bot] 08fc54b536 chore(workflows): update actions/setup-node digest to 969bd26 2022-09-28 11:11:40 +00:00
renovate[bot] 7a1ee2b173 chore(): update dependency @types/node to v16.11.62 2022-09-27 13:30:32 +00:00
renovate[bot] 9ea95a27a3 chore(support): update dependency teen_process to v2 2022-09-26 15:26:35 -07:00
renovate[bot] 86ac5f7a2b chore(support): update dependency @types/glob to v8 2022-09-26 10:52:41 -07:00
Christopher Hiller 08f0bc8839 feat(support): add fs.isExecutable()
This is handy for drivers which need to find executables in the filesystem.

Also fixes possible false-positives in `fs.exists()`/`fs.hasAccess()` on Windows.
2022-09-26 10:50:32 -07:00
renovate[bot] 75f030b35f chore(types): update webdriverio monorepo to v7.25.0 2022-09-26 03:14:41 +00:00
Mykola Mokhnach 882958575b docs: Add Linux driver to the list of third parties (#17523) 2022-09-25 19:00:21 +02:00
renovate[bot] 2eab139f18 chore(gulp-plugins): update dependency eslint to v8.24.0 2022-09-24 14:51:27 +00:00
renovate[bot] 0eb92e493b chore(opencv): update dependency @types/bluebird to v3.5.37 2022-09-24 11:47:38 +00:00
renovate[bot] d945b0f2fe chore(): update dependency @types/node to v16.11.60 2022-09-24 11:19:19 +00:00
renovate[bot] e5d2422990 chore(): update dependency ws to v8.9.0 2022-09-23 11:48:16 +00:00
renovate[bot] 31b6b2fe7d chore(types): update webdriverio monorepo to v7.24.1 2022-09-22 13:12:22 +00:00
Christopher Hiller b2b2225aee chore(): format .ts* files 2022-09-19 12:30:34 -07:00
Christopher Hiller 4abe91004d feat(types): adds types to derive capabilities from constraints
Driver authors and end-users may find these types useful.  I still haven't fleshed out how to best provide these to end-users, but we can use `DriverCaps<MyDriver>` which is an object containing Appium's caps _in addition to_ those defined by the `desiredCapConstraints` of `MyDriver`, e.g.:

```js

class MyClass {
  desiredCapConstraints = {foo: {isString: true, inclusion: ['bar', 'baz']}, quux: {isNumber: true}}
}

/** @typedef {DriverCaps<MyClass>} MyClassCaps */
// looks like {platformName: string, ..., foo: 'bar' | baz', quux: number}
```
2022-09-19 12:30:34 -07:00
renovate[bot] 2101da3843 chore(docutils): update dependency jsdoc-plugin-typescript to v2.2.0 2022-09-17 11:45:58 +00:00
renovate[bot] 595545ac58 chore(base-driver): update dependency @types/async-lock to v1.3.0 2022-09-16 15:40:58 +00:00
renovate[bot] 096f0ab2fe chore(support): update dependency jimp to v0.16.2 2022-09-16 12:10:33 +00:00
renovate[bot] 2605714a0c chore(appium): update dependency @sidvind/better-ajv-errors to v2.1.0 2022-09-15 21:04:45 +00:00
renovate[bot] 9dc66a2451 chore(gulp-plugins): update babel monorepo to v7.19.1 2022-09-15 17:06:15 +00:00
Jonathan Lipps abd52e3410 docs(appium): add extension CLI doc 2022-09-14 10:42:09 -07:00
renovate[bot] 5f53988aeb chore(types): update dependency @types/express to v4.17.14 2022-09-14 16:43:07 +00:00
renovate[bot] df61246461 chore(): update dependency @types/node to v16.11.59 2022-09-14 13:47:54 +00:00
Jonathan Lipps 990645e296 chore: remove CLA link in PR template
it's managed by a bot now anyway
2022-09-13 13:43:36 -07:00
Dor Blayzer 6e41beedae docs: Fix Broken Getting Started link (#17474) 2022-09-13 00:12:03 -07:00
Jonathan Lipps 6f738d6ead docs(appium): create a cli args docs generation system
- generate markdown tables and insert into args.md
- remove 1.x args generation docs
- write cli args docs file preamble
2022-09-12 10:48:04 -07:00
renovate[bot] a5607c6593 chore(gulp-plugins): update dependency eslint to v8.23.1 2022-09-12 16:10:55 +00:00
renovate[bot] 87da292f9a chore(driver-test-support): update dependency @types/lodash to v4.14.185 2022-09-12 12:23:55 +00:00
ian zhang 72b115817c docs(appium): Update Clients intro Chinese localization (#17465) 2022-09-09 14:59:43 -07:00
Mykola Mokhnach cd6fbd5806 ci: Remove obsolete scripts (#17464) 2022-09-09 18:49:07 +02:00
renovate[bot] 2b91cfc9c5 chore(doctor): update dependency appium-adb to v9.10.10 2022-09-09 12:42:07 +00:00
Mykola Mokhnach 6267ae33d5 refactor(appium): Prefer condition-based flow over exception-based (#17457) 2022-09-09 08:15:16 +02:00
renovate[bot] 3fda104947 chore(appium): update dependency winston to v3.8.2 2022-09-09 01:05:37 +00:00
Jonathan LippsandKazuaki Matsuo 507c56a63e docs(appium): add the client intro doc (#17459)
* docs(appium): add the client intro doc
also fix an erroneous doc link

* chore: add ruby example

Co-authored-by: Kazuaki Matsuo <fly.49.89.over@gmail.com>
2022-09-08 09:22:50 -07:00
renovate[bot] 18c408dc72 chore(): update dependency @types/node to v16.11.58 2022-09-08 15:08:44 +00:00
92 changed files with 3135 additions and 2364 deletions
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+26 -10
View File
@@ -10,15 +10,37 @@
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](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
View File
@@ -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.
-2
View File
@@ -1,2 +0,0 @@
jobs:
- template: templates/bundle-template.yml
-3
View File
@@ -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;
-46
View File
@@ -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
-31
View File
@@ -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)
+1015 -1224
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -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": {
+11
View File
@@ -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)
+51 -1
View File
@@ -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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|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 -->
+193 -1
View File
@@ -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
+111 -1
View File
@@ -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
+1 -1
View File
@@ -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 = '&nbsp;'.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;
+120 -2
View File
@@ -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]: 这些库又称为 "客户端"、"客户端库" 或 "客户端绑定". 他们是同一种概念!
+27 -18
View File
@@ -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}) {
+49 -32
View File
@@ -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;
}
+27 -24
View File
@@ -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);
+87 -66
View File
@@ -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
*/
+15 -14
View File
@@ -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;
}
+97 -76
View File
@@ -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
View File
@@ -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 () {
+22 -1
View File
@@ -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
*/
/**
+8
View File
@@ -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
*/
+34 -18
View File
@@ -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};
+98 -51
View File
@@ -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
*/
+1
View File
@@ -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,
+8 -9
View File
@@ -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 () {
+8
View File
@@ -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
+2 -2
View File
@@ -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",
+8
View File
@@ -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
+6 -7
View File
@@ -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",
+8
View File
@@ -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
+4 -5
View File
@@ -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)
+13 -7
View File
@@ -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
*/
/**
+8 -3
View File
@@ -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
*/
+4 -5
View File
@@ -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
+3 -4
View File
@@ -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"
+8
View File
@@ -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
+3 -4
View File
@@ -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",
+8
View File
@@ -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
+4 -5
View File
@@ -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"
+1 -1
View File
@@ -10,7 +10,7 @@
}
]
],
"plugins": ["source-map-support", "@babel/plugin-transform-runtime"],
"plugins": ["source-map-support"],
"comments": false,
"sourceMaps": "both",
"env": {
+8
View File
@@ -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
+7 -9
View File
@@ -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"
+8
View File
@@ -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
+2 -3
View File
@@ -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"
},
+8
View File
@@ -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
+3 -4
View File
@@ -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)
+3 -4
View File
@@ -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 -2
View File
@@ -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"
},
+12
View File
@@ -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
View File
@@ -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;
}
);
+34 -7
View File
@@ -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
*/
+7 -8
View File
@@ -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"
+20 -6
View File
@@ -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'));
});
+35
View File
@@ -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;
});
});
});
+1 -1
View File
@@ -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 -125
View File
@@ -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({
+8
View File
@@ -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)
+3 -4
View File
@@ -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": {
+16
View File
@@ -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)
+174 -42
View File
@@ -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>;
+53
View File
@@ -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;
+65 -43
View File
@@ -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
View File
@@ -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';
+5 -4
View File
@@ -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",
+2
View File
@@ -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 -2
View File
@@ -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
View File
@@ -1,6 +1,7 @@
{
"files": [],
"compilerOptions": {
"noErrorTruncation": true,
"esModuleInterop": true
},
"references": [