Compare commits

...
Author SHA1 Message Date
Jonathan Lipps 35f4e2b7e4 chore: publish
- appium@2.0.0-beta.50
 - @appium/base-driver@9.2.1
 - @appium/base-plugin@2.1.1
 - @appium/doctor@2.0.3
 - @appium/docutils@0.1.3
 - @appium/driver-test-support@0.3.3
 - @appium/execute-driver-plugin@3.0.3
 - @appium/fake-driver@5.1.1
 - @appium/fake-plugin@3.1.1
 - @appium/images-plugin@2.0.3
 - @appium/opencv@2.0.3
 - @appium/plugin-test-support@0.3.3
 - @appium/relaxed-caps-plugin@1.0.2
 - @appium/schema@0.2.1
 - @appium/support@3.1.1
 - @appium/test-support@3.0.3
 - @appium/tsconfig@0.2.1
 - @appium/typedoc-plugin-appium@0.3.1
 - @appium/types@0.8.1
 - @appium/universal-xml-plugin@1.0.2
2023-01-13 13:19:27 -08:00
Jonathan Lipps 568e6d3b19 chore: ensure all packages mention public access in their publish config 2023-01-13 13:18:47 -08:00
Jonathan Lipps b48a697d5b chore: commit lerna publish changes 2023-01-13 13:12:00 -08:00
Jonathan Lipps 422142302f chore: publish
- appium@2.0.0-beta.49
 - @appium/base-driver@9.2.0
 - @appium/base-plugin@2.1.0
 - @appium/doctor@2.0.2
 - @appium/docutils@0.1.2
 - @appium/driver-test-support@0.3.2
 - @appium/execute-driver-plugin@3.0.2
 - @appium/fake-driver@5.1.0
 - @appium/fake-plugin@3.1.0
 - @appium/images-plugin@2.0.2
 - @appium/opencv@2.0.2
 - @appium/plugin-test-support@0.3.2
 - @appium/relaxed-caps-plugin@1.0.1
 - @appium/schema@0.2.0
 - @appium/support@3.1.0
 - @appium/test-support@3.0.2
 - @appium/tsconfig@0.2.0
 - @appium/typedoc-plugin-appium@0.3.0
 - @appium/types@0.8.0
 - @appium/universal-xml-plugin@1.0.1
2023-01-13 13:09:28 -08:00
Jonathan Lipps c65054c232 chore(relaxed-caps-plugin): take out of beta 2023-01-13 13:08:17 -08:00
Jonathan Lipps 888d892a17 chore(universal-xml-plugin): take xml plugin out of beta 2023-01-13 13:07:48 -08:00
Christopher Hiller 4f65cb3865 chore(typedoc-plugin-appium): fussing
- made test helpers more sane
- add a test or two
- export a `setup` fn from main entry point to configure options and theme
2023-01-13 11:49:57 -08:00
Christopher Hiller e98928a39d chore(typedoc-plugin-appium): make empty test a pending 2023-01-13 11:49:57 -08:00
Christopher Hiller 03c505d321 chore(typedoc-plugin-appium): wrap all tests in same suite title
Everything else here is whitespace.

Using this pattern helps with some 3p tools to navigate tests in the monorepo, as they can easily be grouped by the top-level suite title
2023-01-13 11:49:57 -08:00
Christopher Hiller 2a12b88b2f feat(typedoc-plugin-appium): display response type
This now displays the type of the response in the command output.  Since all return types from commands are `Promise<T>`, it unwraps `T` and displays only that (since `Promise` is very specific to JS).
2023-01-13 11:49:57 -08:00
Christopher Hiller 734127144d chore(typedoc-plugin-appium): tweak guard; avoid bad link 2023-01-13 11:49:57 -08:00
Christopher Hiller 8c421220c5 chore(typedoc-plugin-appium): remove unused import 2023-01-13 11:49:57 -08:00
Christopher Hiller 0cad4b785c chore(typedoc-plugin-appium): rename some vars 2023-01-13 11:49:57 -08:00
Christopher Hiller 00e7e3f1ef chore(wallaby): add templates to wallaby, enabling theme tests 2023-01-13 11:49:57 -08:00
Christopher Hiller 75183ba38b chore(typedoc-plugin-appium): refactored some stuff that didn't make sense
- better test organization
- plugin now exports a couple `Promise`-returning functions for programmatic consumption. TypeDoc doesn't use these, but they're helpful to test with
- simplified `omitDefaultReflections()`; moved into `converter/builder` module since it's a post-conversion thing
2023-01-13 11:49:57 -08:00
Jonathan Lipps ad1dce5545 fix(base-driver): move setClipboard deprecation to correct place 2023-01-13 09:45:11 -08:00
renovate[bot] 8c86e355d2 chore(deps): update commitlint monorepo to v17.4.2 2023-01-13 08:36:31 +00:00
renovate[bot] 4156b7bf6b chore(deps): update dependency eslint-plugin-import to v2.27.4 2023-01-12 23:04:17 +00:00
Jonathan Lipps 7055a0b281 feat(base-driver): deprecate non-standard routes
and add deprecation warnings in the log when these routes are accessed
2023-01-12 10:11:08 -08:00
renovate[bot] bc3f1373fd chore(docutils): update dependency mkdocs-material to v9.0.4 2023-01-12 14:14:50 +00:00
renovate[bot] 2bbac72667 chore(deps): update dependency midnight-smoker to v2.0.1 2023-01-12 02:50:11 +00:00
Jonathan Lipps f04e1171d0 docs(appium): note the breaking change in default base path (#18042) 2023-01-11 14:04:42 -08:00
Christopher Hiller 696c94f1ab fix(images-plugin,universal-xml-plugin,typedoc-plugin-appium): add missing lodash dependency 2023-01-11 11:40:13 -08:00
Ikko Eltociear Ashimine 6f846f76c9 chore(appium): fix typo in driver.e2e.spec.js (#18041)
recieve -> receive
2023-01-11 11:05:21 -08:00
Jonathan Lipps d8588e107c docs: add ExternalDriver docstrings (#17985) 2023-01-10 14:36:48 -08:00
Christopher Hiller c5bbb950ed fix: run "npm audit fix" because there was a warning 2023-01-10 14:00:11 -08:00
renovate[bot] fb4f0408dc chore(docutils): update dependency mkdocs-material to v9 (#18002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-10 10:06:44 -08:00
Christopher Hiller 697b4427e4 fix(execute-driver-plugin): fix bad types in execute-child module 2023-01-10 09:53:18 -08:00
Christopher Hiller 6fb05b2fff docs: enable auto command generation for plugins
Also enables type checks in `execute-driver-plugins` and exports types where we weren't before
2023-01-10 09:53:18 -08:00
Christopher Hiller 7add23e418 fix(execute-driver-plugin): use correct return type annotation 2023-01-10 09:53:18 -08:00
Christopher Hiller b036dde5dd feat(typedoc-plugin-appium): add plugin support
Most of this "just works", but the additional parameters to a plugin command need special handling.  To do that, we have to understand what is a plugin and what is not.

Note that this is incompatible with the "mixin" pattern as found in `BaseDriver` and `FakeDriver`. In other words, a _Plugin_ should not use mixin classes.  I would not recommend mixin classes going forward due to its complexity and opacity.
2023-01-10 09:53:18 -08:00
Christopher Hiller 1f99c97264 chore(types): update definition of PluginCommand type
maybe some day we will even use this!
2023-01-10 09:53:18 -08:00
Christopher Hiller df31cddbe7 chore(typedoc-plugin-appium): tweaks
- `BuiltinMethodMapConverter`'s `convert()` will _always_ return a `BuiltinCommands` / removed unused import
- Verbose logging for skipped methods
- Remove useless type aliases from option declarations and add some no-op typechecks (these will be compiled away)
- Add some docstrings to guards and types
- `CallSignatureReflectionWithParams` is now `CallSignatureReflectionWithArity`, and it is specific about having a nonzero arity. Broke out `CallSignatureReflection` from this, since the guard and type didn't fully match up
2023-01-10 09:32:46 -08:00
Christopher Hiller 9d5a737daa fix(typedoc-plugin-appium): allow commands as properties referencing async methods
Instead of only inspecting `ReflectionKind.Method`, we now examine `ReflectionKind.Property`, which can be of type `ReflectionType` pointing to an async function.  Example:

```js
async function baz() {}

class Foo {
  async bar() {} // ReflectionKind.Method
  baz = baz; // ReflectionKind.Property referencing a function
}
```

Also ensures `static` methods aren't picked up; this could happen if a static method shares the same name with a known command method as defined in the builtin routes.
2023-01-10 09:32:46 -08:00
Christopher Hiller 0ca54b7994 feat(typedoc-plugin-appium): add options and 3p support
E2E tests are quasi-blocking on merge & release of https://github.com/TypeStrong/typedoc/issues/2130; it is a huge pain to test without this.

- Adds some options to control output
- Rename `CommandsReflection` to `ExtensionReflection` due to poor granular control over the display name
- Rename the custom "kinds"; add kinds for drivers and plugins (which affects display titles)
- Remove namespace from "kinds" for same reason
- Refactors, reformatting
- Rename entry point from `plugin.ts` to `index.ts`
- Update keywords in `package.json` for auto-discovery
- Remove cruft from root `typedoc.json`
- Update `README.md`
- Update peer dependencies; the other plugins must be peer deps or typedoc will be unable to auto-discover them
2023-01-10 09:32:46 -08:00
Christopher Hiller 7942118429 chore(tsconfig): strip internal stuff from declarations
typedoc respects this flag as well
2023-01-10 09:32:46 -08:00
Christopher Hiller 0aac077e30 fix(typedoc-plugin-appium): add peer dep of appium 2023-01-10 09:32:46 -08:00
Christopher Hiller 5ef4ae9206 fix(typedoc-plugin-appium): bad directory in repository url 2023-01-10 09:32:46 -08:00
renovate[bot] a22c97dbf9 chore(deps): update dependency @commitlint/cli to v17.4.1 2023-01-10 12:47:22 +00:00
renovate[bot] 57b0ee5dbb chore(deps): update dependency ws to v8.12.0 2023-01-08 13:48:18 +00:00
renovate[bot] 31a3cee0bc chore(deps): update dependency typedoc to v0.23.24 2023-01-08 11:54:20 +00:00
renovate[bot] b28a1134f0 chore(deps): update dependency prettier to v2.8.2 2023-01-08 10:00:47 +00:00
pacozaa 8f7171c42a refactor: add type for driver-command,plugin-command,doctor,docutils,execute-child (#17962) 2023-01-07 17:11:04 +01:00
renovate[bot] da230ca243 chore(deps): update dependency lerna to v6.4.0 2023-01-07 00:14:20 +00:00
Christopher Hiller edde4882f0 fix(base-driver): add missing dependency @appium/types
Closes #18006
2023-01-06 12:56:26 -08:00
renovate[bot] d8323b3d3c chore(workflows): update actions/setup-node digest to 64ed1c7 2023-01-06 18:40:00 +00:00
Mykola Mokhnach ed07ae3046 docs(appium): Document basics of application bundles caching (#18015) 2023-01-06 15:33:20 +01:00
renovate[bot] 5d1090555f chore(workflows): update actions/checkout digest to ac59398 2023-01-06 12:29:27 +00:00
renovate[bot] 4b5ab4da7b fix(types): update dependency type-fest to v3.5.1 2023-01-05 12:11:09 +00:00
Christopher Hiller f036f0a612 chore: all pkgs consume @appium/tsconfig 2023-01-04 12:09:08 -08:00
renovate[bot] 79105b59e2 chore(deps): update commitlint monorepo to v17.4.0 2023-01-04 14:56:42 +00:00
renovate[bot] 783989b56c fix(appium): update dependency ajv to v8.12.0 2023-01-04 11:54:41 +00:00
Mykola Mokhnach 49eddb4368 docs (appium): Add "Write a Test" Java tutorial (#18000) 2023-01-04 08:58:19 +01:00
Kazuaki MatsuoandJonathan Lipps b4670e13f9 docs(appium): address appium 1.x uninstallation (#17999)
* docs(appium): address appium 1.x uninstallation

* Update packages/appium/docs/en/guides/migrating-1-to-2.md

Co-authored-by: Jonathan Lipps <jlipps@gmail.com>

Co-authored-by: Jonathan Lipps <jlipps@gmail.com>
2023-01-03 17:50:48 -08:00
Christopher Hiller fe7b7b6d21 chore(base-driver): do not use EmptyObject
I do not trust it
2023-01-03 12:50:53 -08:00
Christopher Hiller 7d7b510a03 feat: create @appium/tsconfig
For the TypeDoc plugin to correctly pull information from `@appium/types` and `@appium/base-driver`, they both need `tsconfig.json` files.

They currently _have_ `tsconfig.json` files, but they extend base configs in the monorepo root, which is problematic.  Moving the configs into a package seems reasonable.  Further, all packages must publish `tsconfig.json`.  There's precedent for publishing a `tsconfig.json` this way.

FWIW the `paths` and `references` fields in the `tsconfig.json` files seem to be ignored by TypeDoc, so we can get away with this.
2023-01-03 12:50:53 -08:00
Christopher Hiller 63d8f2ca2d chore(typedoc-plugin-appium): better sorting, refactors
trying to make some things less opaque.  maybe failed
2023-01-03 12:50:53 -08:00
Christopher Hiller 8b33414901 feat(typedoc-appium-plugin): implement cross-referencing of methods
This is getting pretty close, but:

- Commands from a driver will show even w/o a `newMethodMap` or `execMethodMap`; it is able to understand that it's overriding `BaseDriver`
- It's also able to understand the associated routes
- And it's able to understand the routes even if `BaseDriver` does not implement it

Still working on the parameters display, but we have the name override working at least.

Added some docstrings in `fake-driver`, `base-driver` for testing
2023-01-03 12:50:53 -08:00
renovate[bot] 949719811a chore(deps): update dependency husky to v8.0.3 2023-01-03 13:53:07 +00:00
renovate[bot] 47ec6af185 chore(deps): update dependency eslint-config-prettier to v8.6.0 2023-01-03 10:07:18 +00:00
Dor Blayzer 9ee40374f5 docs: Remove travis CI build status in appium-doctor (#17964)
* docs: update build status link

Since June 15th, 2021, the building on travis-ci.org is ceased. Please use travis-ci.com from now on.

* docs: Fix broken link

* docs: Remove Travis CI build status
2023-01-02 13:48:46 -08:00
Kazuaki Matsuo e9042b85d9 docs(appium): add ja resources more (#18001)
* docs(appium): add more JP resources

* docs: tweak

* docs: add ja more
2023-01-02 10:32:53 -08:00
renovate[bot] 7bb4e07d68 fix(support): update dependency klaw to v4.1.0 2022-12-31 18:47:53 +00:00
renovate[bot] 098ee1932d chore(deps): update dependency eslint to v8.31.0 2022-12-31 15:30:43 +00:00
renovate[bot] 7c59380e6b fix(execute-driver-plugin): update dependency webdriverio to v7.29.1 2022-12-31 12:40:01 +00:00
renovate[bot] b763880779 fix(appium): update dependency yaml to v2.2.1 2022-12-30 14:23:56 +00:00
renovate[bot] a3fad3ed28 fix(execute-driver-plugin): update dependency webdriverio to v7.29.0 2022-12-30 12:01:46 +00:00
renovate[bot] 6055f162d8 fix(types): update dependency @types/ws to v8.5.4 2022-12-30 09:11:50 +00:00
renovate[bot] 5291ca672b fix(support): update dependency axios to v1.2.2 2022-12-29 11:18:58 +00:00
Jonathan Lipps 23224cf002 fix(appium): inability to find automationName inside appium:options (#17966)
* fix(appium): inability to find automationName inside appium:options

we fixed this before but only fixed it in basedriver's createSession, which is not
actually called by Appium's createSession (though maybe it should be)

* test(base-driver): remove appium:options test
2022-12-28 10:54:39 -08:00
Kazuaki Matsuo 0b5f7a4085 docs(): update readme links to appium.github.io (#17974)
* docs(): update readme links to appium.github.io

* docs(): use latest
2022-12-27 14:45:41 -08:00
renovate[bot] d2cf2ed4e3 chore(deps): update dependency lerna to v6.3.0 2022-12-27 12:58:19 +00:00
renovate[bot] 3f3b1529ed chore(deps): update dependency @types/node to v18.11.18 2022-12-27 09:58:08 +00:00
renovate[bot] 8c8bfe824d fix(types): update dependency type-fest to v3.5.0 2022-12-26 10:26:59 +00:00
renovate[bot] 2bb4f32329 fix(execute-driver-plugin): update dependency webdriverio to v7.28.1 2022-12-25 08:44:30 +00:00
renovate[bot] ac2c735968 fix(doctor): update dependency appium-adb to v9.10.21 2022-12-24 10:26:38 +00:00
renovate[bot] 29bf77cf5a fix(doctor): update dependency appium-adb to v9.10.20 2022-12-23 11:17:08 +00:00
renovate[bot] 28c6204b75 fix(appium): update dependency yaml to v2.2.0 2022-12-22 12:08:46 +00:00
Kazuaki Matsuo e0dc5b6299 docs(appium): add /appium/ (#17969) 2022-12-21 16:41:33 -08:00
Jonathan Lipps 6fb1f2cc32 docs(appium): fix reference building script paths 2022-12-21 12:06:42 -08:00
Jonathan Lipps 2e76ba9607 chore: publish
- appium@2.0.0-beta.48
 - @appium/base-driver@9.1.0
 - @appium/base-plugin@2.0.1
 - @appium/doctor@2.0.1
 - @appium/docutils@0.1.1
 - @appium/driver-test-support@0.3.1
 - @appium/execute-driver-plugin@3.0.1
 - @appium/fake-plugin@3.0.1
 - @appium/images-plugin@2.0.1
 - @appium/opencv@2.0.1
 - @appium/plugin-test-support@0.3.1
 - @appium/support@3.0.1
 - @appium/test-support@3.0.1
 - @appium/typedoc-plugin-appium@0.2.1
 - @appium/types@0.7.0
2022-12-21 11:57:39 -08:00
Jonathan Lipps aab415685e fix(docutils): package should publish the base mkdocs yml (#17968) 2022-12-21 11:54:50 -08:00
Jonathan Lipps d13d292c4b docs(appium): add grid 4 appium setup examples (#17965) 2022-12-21 10:58:46 -08:00
Christopher Hiller df250cc1e0 fix(appium): fix manifest migrations
Manifest migrations were not being fully applied due to a method in `Manifest` which refused to overwrite data.

- `Manifest` now has full control of the manifest file and its in-memory representation. Nothing else should ever modify the in-memory data directly; instead, use its API. This restriction also applies to migrations.
- Corrected some incorrect explanations in `manifest-migrations`
- `Manifest#addExtension` became `Manifest#setExtension` and allows overwriting of data, which prevented migrations from being fully applied.
- More tests
2022-12-19 10:37:33 -08:00
renovate[bot] 5565febc9d chore(deps): update dependency typedoc to v0.23.23 2022-12-18 11:41:04 +00:00
renovate[bot] 49a0fe164b chore(deps): update dependency @types/node to v18.11.17 2022-12-18 09:58:38 +00:00
renovate[bot] 94911eb59f chore(deps): update dependency eslint to v8.30.0 2022-12-17 14:13:16 +00:00
renovate[bot] 1b4a41bce9 chore(deps): update dependency @types/node to v18.11.16 2022-12-17 11:20:33 +00:00
renovate[bot] 5d0d4f5a44 fix(test-support): update dependency sinon to v15.0.1 2022-12-16 11:18:48 +00:00
renovate[bot] f81e2e92ef fix(execute-driver-plugin): update dependency webdriverio to v7.28.0 2022-12-16 08:50:11 +00:00
Kazuaki MatsuoandChristopher Hiller 316ecca8b1 feat(base): add get computed role and label in W3C v2 (#17928)
* feat(base): add get computed role and label in W3C v2

* add command names

* chore: add type

* chore: use `?` instead of `| null`

Co-authored-by: Christopher Hiller <boneskull@boneskull.com>

* Revert "chore: use `?` instead of `| null`"

This reverts commit 62af5e80ac.

Co-authored-by: Christopher Hiller <boneskull@boneskull.com>
2022-12-15 20:46:44 -08:00
Christopher Hiller 20667be9a2 fix(appium): ensure migration always gets applied
The manifest file was not getting its migrations applied _to disk_ in all situations.  Essentially: the migration could repeat over and over, depending on the environment--the changes would only exist in memory.

Added a test, as well.  I didn't originally add a test because the whole migration thing came out of a rabbit hole intended to fix a separate issue.  My bad!
2022-12-15 17:11:35 -08:00
Christopher Hiller 48b7b05a1f chore(support): quiet some noisy debug logging 2022-12-15 17:11:35 -08:00
Christopher Hiller 2df077b08c chore: remove packweb.json 2022-12-15 17:11:14 -08:00
Jonathan Lipps 0e195caafe fix: add 'webSocketUrl' as standard cap for bidi support (#17936) 2022-12-15 16:36:50 -08:00
Christopher Hiller 9842e231c5 chore: remove @appium/gulp-plugins
Closes #17907
2022-12-15 16:11:58 -08:00
Christopher Hiller feeaac4713 chore: revert 840a3b4a02
This reverts a workaround which has since been fixed in `sync-monorepo-packages@1.0.2`.
2022-12-15 16:11:48 -08:00
renovate[bot] ad7e2648c1 chore(deps): update dependency sync-monorepo-packages to v1.0.2 2022-12-15 15:07:14 -08:00
renovate[bot] 2cb1af4357 fix(doctor): update dependency appium-adb to v9.10.19 2022-12-15 15:07:08 -08:00
Christopher Hiller 45b6aed22d chore(fossa): fix invalid .fossa.yml "revision" prop 2022-12-15 15:06:59 -08:00
Sarin Suriyakoon 26c84c9361 refactor: add type for driver install 2022-12-15 15:05:21 -08:00
Christopher Hiller ad06a0612f chore(appium): fix E2E test for update functionality
The problem was that after we published a new major of `@appium/fake-driver`, the `updateVersion` field of the response was `null`, but `unsafeUpdateVersion` was populated; the test only used the former.  Now, the test uses `updateVersion` but falls back to `unsafeUpdateVersion`.
2022-12-15 14:43:46 -08:00
Jonathan Lipps fe67e4cb2f feat(appium): add chromium driver to list of officially supported drivers (#17939) 2022-12-14 14:28:53 -08:00
Christopher Hiller fee1c98ee9 chore: run e2e tests in parallel
This change makes Lerna execute E2E tests across two cores.  Some of the slower E2E suites (`appium`, `support`, `opencv` and `base-driver`) also run in parallel via Mocha's `--parallel` flag.

In Appium, the main culprit was `cli.e2e.spec.js`, which I split into four files: `args` (argument handling), `cli-driver` (`driver` command), `cli-plugin` (`plugin` command) and `local` (behavior when Appium is a dependency).  This should run faster (in theory) since Mocha runs tests in parallel _per file_ and will expand to the available cores.

CI has limited cores, so this won't really help there--but it will speed up local development.  When running tests locally, you may see various delays, as Mocha buffers the output of a single test file.

This is a continuation of the work I did in #17252.
2022-12-14 13:28:31 -08:00
Christopher Hiller 9fea6980e5 chore(commitlint): disable DUMB RULES
This disables the rules which:

- require specific capitalization
- disallow the use of periods in subjects
- warn if there's no leading blank line in the commit body

because the above are dumb
2022-12-14 13:27:28 -08:00
renovate[bot] 37f71c327a fix(types): update dependency type-fest to v3.4.0 2022-12-14 14:57:42 +00:00
renovate[bot] 172fcb9aff fix(types): update definitelytyped 2022-12-14 13:01:18 +00:00
renovate[bot] 3993decde4 chore(workflows): update actions/checkout digest to 755da8c 2022-12-14 10:10:32 +00:00
252 changed files with 9780 additions and 13752 deletions
+1 -5
View File
@@ -29,8 +29,6 @@ targets:
path: ./packages/eslint-config-appium
- type: npm
path: ./packages/fake-driver
- type: npm
path: ./packages/gulp-plugins
- type: npm
path: ./packages/support
- type: npm
@@ -54,7 +52,5 @@ targets:
- type: npm
path: ./packages/docutils
revision:
branch: master
- branch: master
+3 -3
View File
@@ -13,9 +13,9 @@ jobs:
node-version: [14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -33,7 +33,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Install dependencies
uses: bahmutov/npm-install@1a235c31658a322a3b024444759650ee6345c26d # tag=v1
with:
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # 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@7dd9e2a3dc350cf687eb1b2a4fadfee8c8e49675 # v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
with:
ref: master
- name: Install dependencies
+1
View File
@@ -10,3 +10,4 @@ package.json
**/generated/**
# generated
packages/types/lib/appium-config.ts
**/*.hbs
+1
View File
@@ -50,6 +50,7 @@
"internalConsoleOptions": "openOnSessionStart",
"name": "Typedoc",
"program": "${workspaceFolder}/node_modules/.bin/typedoc",
"args": ["--logLevel", "Verbose"],
"request": "launch",
"skipFiles": [
"<node_internals>/**"
+15 -2
View File
@@ -4,11 +4,24 @@
{
"type": "npm",
"script": "dev",
"group": {"kind": "build", "isDefault": true},
"problemMatcher": ["$tsc-watch"],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$tsc-watch"
],
"label": "npm: dev",
"detail": "npm run dev",
"isBackground": true
},
{
"type": "npm",
"script": "typedoc",
"problemMatcher": [],
"label": "npm: typedoc",
"detail": "typedoc --logLevel Verbose",
"group": "build"
}
]
}
+31 -18
View File
@@ -3,24 +3,30 @@
module.exports = (wallaby) => {
return {
compilers: {
'**/*.js': wallaby.compilers.babel(),
'**/*.js': wallaby.compilers.typeScript({
allowJs: true,
allowSyntheticDefaultImports: true,
resolveJsonModule: true,
isolatedModules: true,
}),
'**/*.ts?(x)': wallaby.compilers.typeScript(),
},
debug: true,
env: {
type: 'node',
},
files: [
'./packages/**/*.js',
'./packages/**/*.json',
'!./packages/**/build/**',
'!./packages/**/test/**/*-specs.js',
'!./packages/**/test/**/*.spec.js',
'!./packages/*/node_modules/**',
'!./packages/*/gulpfile.js',
'!./packages/*/scripts/**',
'./packages/*/test/**/fixtures/**/*',
'./babel.config.json',
// below this are fixtures
'./packages/*/build/**/*',
'./packages/*/lib/**/*.(j|t)s',
'./packages/*/test/**/*helper*.(j|t)s',
'./packages/*/test/**/*mock*.(j|t)s',
'./packages/*/package.json',
'./packages/*/tsconfig*.json',
'./packages/*/test/**/fixture?(s)/**/*',
{
instrument: false,
pattern: './packages/typedoc-plugin-appium/resources/**/*',
},
{
binary: true,
pattern: './packages/support/test/unit/assets/sample_binary.plist',
@@ -33,17 +39,21 @@ module.exports = (wallaby) => {
instrument: false,
pattern: './packages/base-driver/static/**/*',
},
{
instrument: false,
pattern: './packages/typedoc-plugin-appium/lib/theme/resources/**',
},
'!./packages/*/test/**/*-specs.js',
'!./packages/*/test/**/*.e2e.spec.(j|t)s',
'!**/local_appium_home/**',
],
testFramework: 'mocha',
tests: ['./packages/*/test/unit/**/*.spec.js', '!**/local_appium_home/**'],
tests: ['./packages/*/test/unit/**/*.spec.(j|t)s', '!**/local_appium_home/**'],
workers: {
restart: true,
// restart: true,
},
setup() {
// This copied out of `./test/setup.js`, which uses `@babel/register`.
// Wallaby doesn't need `@babel/register` (and it probably makes Wallaby slow),
// but we need the other stuff, so here it is.
setup(wallaby) {
// copied out of `./test/setup.js`
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
@@ -55,6 +65,9 @@ module.exports = (wallaby) => {
// `should()` is only necessary when working with some `null` or `undefined` values.
global.should = chai.should();
const mocha = wallaby.testFramework;
mocha.timeout(10000);
},
runMode: 'onsave',
};
+8 -15
View File
@@ -17,11 +17,11 @@ and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sect
:bangbang: Major documentation revision in progress
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://github.com/appium/appium/blob/master/packages/appium/docs/en/guides/migrating-1-to-2.md) to manage the Appium server.
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
Appium is in the final stages of a major revision (to version 2.0). As such, the documentation
found around the web may not be correct. The current Appium 2.0 documentation is very much in
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/2.0/).
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/latest/).
### Requirements
@@ -170,9 +170,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).
### Get Started
### Quickstart
Check out our [Getting Started](http://appium.io/docs/en/about-appium/getting-started) guide
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
to get going with Appium.
There is also a sample code that contains [many examples of tests in a variety
@@ -180,15 +180,9 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
### Documentation
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
always find the full list of Appium doc pages at [Appium's GitHub
Repo](https://github.com/appium/appium/tree/master/docs) as well.
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
in the appium.io repository with the documentation update.
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
You can always find the full list of Appium doc pages at [Appium's GitHub
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
### Contributing
@@ -201,8 +195,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
### Project History, Credits & Inspiration
* [History](http://appium.io/history)
* [Credits](/docs/en/contributing-to-appium/credits.md)
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
### User Forums
+4 -1
View File
@@ -1,8 +1,11 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'header-max-length': [0],
'body-leading-blank': [0],
'body-max-line-length': [0],
'footer-max-line-length': [0],
'header-max-length': [0],
'subject-case': [0],
'subject-full-stop': [0],
},
};
+1397 -9002
View File
File diff suppressed because it is too large Load Diff
+22 -22
View File
@@ -45,6 +45,7 @@
"lint:fix": "run-s \"lint -- --fix\"",
"lint:commit": "commitlint",
"lint:staged": "lint-staged",
"pack-all": "npm pack --workspaces --include-workspace-root",
"prepublishOnly": "run-s rebuild",
"reinstall": "run-s clean ci",
"rebuild": "run-s clean:artifacts build:all",
@@ -52,12 +53,12 @@
"sync-pkgs": "run-p sync-pkgs:*",
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,bugs,homepage,engines",
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\"",
"sync-pkgs:license": "sync-monorepo-packages --force --packages=\"packages/*\" --no-package-json LICENSE",
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages\"!packages/typedoc-plugin-appium\"",
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
"stage-synced-pkgs": "git add -A packages/appium/README.md \"packages/*/package.json\"",
"test": "npm-run-all -p lint build -s test:unit",
"test:ci": "run-s test:smoke test:unit test:e2e",
"test:e2e": "lerna run --concurrency=1 --max-buffer=128 test:e2e",
"test:e2e": "lerna run --concurrency=2 test:e2e",
"test:smoke": "smoker --all test:smoke",
"test:unit": "lerna run test",
"typedoc": "typedoc --logLevel Verbose",
@@ -73,60 +74,59 @@
},
"devDependencies": {
"@colors/colors": "1.5.0",
"@commitlint/cli": "17.3.0",
"@commitlint/config-conventional": "17.3.0",
"@commitlint/cli": "17.4.2",
"@commitlint/config-conventional": "17.4.2",
"@tsconfig/node14": "1.0.3",
"@types/chai": "4.3.4",
"@types/chai-as-promised": "7.1.5",
"@types/mocha": "10.0.1",
"@types/node": "18.11.14",
"@types/node": "18.11.18",
"@types/semver": "7.3.13",
"@types/sinon": "10.0.13",
"@types/sinon-chai": "3.2.9",
"@types/teen_process": "2.0.0",
"@types/ws": "8.5.3",
"@types/ws": "8.5.4",
"asyncbox": "2.9.4",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"chai-webdriverio-async": "2.7.0",
"cpy-cli": "4.2.0",
"eslint": "8.29.0",
"eslint-config-prettier": "8.5.0",
"eslint": "8.31.0",
"eslint-config-prettier": "8.6.0",
"eslint-find-rules": "4.1.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.4",
"eslint-plugin-mocha": "10.1.0",
"eslint-plugin-promise": "6.1.1",
"fancy-log": "2.0.0",
"finalhandler": "1.2.0",
"get-port": "5.1.1",
"gulp": "4.0.2",
"handlebars": "4.7.7",
"husky": "8.0.2",
"husky": "8.0.3",
"json-schema-to-typescript": "11.0.2",
"lerna": "6.1.0",
"lerna": "6.4.0",
"lint-staged": "13.1.0",
"log-symbols": "4.1.0",
"midnight-smoker": "2.0.0",
"midnight-smoker": "2.0.1",
"mjpeg-consumer": "2.0.0",
"mjpeg-server": "0.3.1",
"mocha": "10.2.0",
"npm-run-all": "4.1.5",
"prettier": "2.8.1",
"prettier": "2.8.2",
"rewiremock": "3.14.4",
"rimraf": "3.0.2",
"serve-static": "1.15.0",
"sinon": "15.0.0",
"sinon": "15.0.1",
"sinon-chai": "3.7.0",
"sync-monorepo-packages": "1.0.1",
"sync-monorepo-packages": "1.0.2",
"through2": "4.0.2",
"ts-node": "10.9.1",
"typedoc": "0.23.22",
"typedoc": "0.23.24",
"typedoc-plugin-markdown": "3.14.0",
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
"typescript": "4.7.4",
"validate.js": "0.13.1",
"webdriverio": "7.27.0",
"ws": "8.11.0",
"webdriverio": "7.29.1",
"ws": "8.12.0",
"yaml-js": "0.3.1"
},
"engines": {
@@ -139,10 +139,10 @@
"overrides": {
"jpeg-js": "0.4.4",
"@sidvind/better-ajv-errors": {
"ajv": "8.11.2"
"ajv": "8.12.0"
},
"ajv-formats": {
"ajv": "8.11.2"
"ajv": "8.12.0"
}
}
}
+47
View File
@@ -3,6 +3,53 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.0.0-beta.50](https://github.com/appium/appium/compare/appium@2.0.0-beta.49...appium@2.0.0-beta.50) (2023-01-13)
**Note:** Version bump only for package appium
# [2.0.0-beta.49](https://github.com/appium/appium/compare/appium@2.0.0-beta.48...appium@2.0.0-beta.49) (2023-01-13)
### Bug Fixes
* **appium:** inability to find automationName inside appium:options ([#17966](https://github.com/appium/appium/issues/17966)) ([23224cf](https://github.com/appium/appium/commit/23224cf002b7dd9e4e5d8426b4bbe1cb28f62605))
* **appium:** update dependency ajv to v8.12.0 ([783989b](https://github.com/appium/appium/commit/783989b56c80ebc0eebf693c168754130627b55f))
* **appium:** update dependency yaml to v2.2.0 ([28c6204](https://github.com/appium/appium/commit/28c6204b751ead304cce21d534c91381fa39d79d))
* **appium:** update dependency yaml to v2.2.1 ([b763880](https://github.com/appium/appium/commit/b763880779abaea900ac2c129dfb009b0e8ed0a9))
* **execute-driver-plugin:** update dependency webdriverio to v7.28.1 ([2bb4f32](https://github.com/appium/appium/commit/2bb4f32329938e15062e42b585dd760cca5ae17b))
* **execute-driver-plugin:** update dependency webdriverio to v7.29.0 ([a3fad3e](https://github.com/appium/appium/commit/a3fad3ed28822b1dfaae72cbfb8d7906ec35e158))
* **execute-driver-plugin:** update dependency webdriverio to v7.29.1 ([7c59380](https://github.com/appium/appium/commit/7c59380e6b97691ff16a8d98a912d67b7972593a))
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
### Features
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
# [2.0.0-beta.48](https://github.com/appium/appium/compare/appium@2.0.0-beta.47...appium@2.0.0-beta.48) (2022-12-21)
### Bug Fixes
- **appium:** ensure migration always gets applied ([20667be](https://github.com/appium/appium/commit/20667be9a21e71963ecb7e25e1b8d51864bce011))
- **appium:** fix manifest migrations ([df250cc](https://github.com/appium/appium/commit/df250cc1e024f4971abe7e756594f7feee32cba6))
- **execute-driver-plugin:** update dependency webdriverio to v7.28.0 ([f81e2e9](https://github.com/appium/appium/commit/f81e2e92eff25c33d36f767209f423227d288218))
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
### Features
- **appium:** add chromium driver to list of officially supported drivers ([#17939](https://github.com/appium/appium/issues/17939)) ([fe67e4c](https://github.com/appium/appium/commit/fe67e4cb2fca977dd9994f1cfbba4a0edc61ef83))
# [2.0.0-beta.47](https://github.com/appium/appium/compare/appium@2.0.0-beta.46...appium@2.0.0-beta.47) (2022-12-14)
### Bug Fixes
+8 -15
View File
@@ -17,11 +17,11 @@ and [Drivers Provided By Third Parties](#drivers-provided-by-third-parties) sect
:bangbang: Major documentation revision in progress
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://github.com/appium/appium/blob/master/packages/appium/docs/en/guides/migrating-1-to-2.md) to manage the Appium server.
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x anymore, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.github.io/appium/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
Appium is in the final stages of a major revision (to version 2.0). As such, the documentation
found around the web may not be correct. The current Appium 2.0 documentation is very much in
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/2.0/).
progress. Currently, it can be found [here](https://appium.github.io/appium/docs/en/latest/).
### Requirements
@@ -170,9 +170,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).
### Get Started
### Quickstart
Check out our [Getting Started](http://appium.io/docs/en/about-appium/getting-started) guide
Check out our [Quickstart](https://appium.github.io/appium/docs/en/latest/quickstart/) guide
to get going with Appium.
There is also a sample code that contains [many examples of tests in a variety
@@ -180,15 +180,9 @@ of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
### Documentation
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
always find the full list of Appium doc pages at [Appium's GitHub
Repo](https://github.com/appium/appium/tree/master/docs) as well.
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
in the appium.io repository with the documentation update.
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
For prettily-rendered docs, please visit [Appium Documentation](https://appium.github.io/appium).
You can always find the full list of Appium doc pages at [Appium's GitHub
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
### Contributing
@@ -201,8 +195,7 @@ Interested in where Appium is heading in the future? Check out the [Roadmap](ROA
### Project History, Credits & Inspiration
* [History](http://appium.io/history)
* [Credits](/docs/en/contributing-to-appium/credits.md)
* [History](https://appium.github.io/appium/docs/en/latest/intro/history/)
### User Forums
+3 -3
View File
@@ -22,11 +22,11 @@ extra:
link: https://twitter.com/AppiumDevs
alternate:
- name: English
link: /docs/en/
link: /appium/docs/en/
lang: en
- name: 日本
link: /docs/ja/
link: /appium/docs/ja/
lang: ja
- name: 中文简体
link: /docs/zh/
link: /appium/docs/zh/
lang: zh
@@ -22,6 +22,7 @@ Intro](../intro/drivers.md)
|Driver|Installation Key|Platform(s)|Mode(s)|
|--|--|--|--|
|[Chromium](https://github.com/appium/appium-chromium-driver)|`chromium`|macOS, Windows, Linux|Web|
|[Espresso](https://github.com/appium/appium-espresso-driver)|`espresso`|Android|Native|
|[Gecko](https://github.com/appium/appium-geckodriver)|`gecko`|macOS, Windows, Linux, Android|Web|
|[Mac2](https://github.com/appium/appium-mac2-driver)|`mac2`|macOS|Native|
+66
View File
@@ -0,0 +1,66 @@
---
title: Application Bundles Caching Logic
---
Appium Base driver provides a feature which enables caching of application builds provided, for example,
as `app` capability value or to endpoints similar to the `installApp` one. This article explains common caching
principles, so you could create more performant and efficient test suite execution strategies.
## Why Caching Is Necessary
Mobile application bundles could reach hundreds of megabytes is size. This could become a serious
performance issue if a test suite is executed, and it is necessary to fetch/extract the same application
bundle for each test.
## What Is Cached
Caching could be applied to application bundles generated by
[configureApp](../../../../../packages/base-driver/lib/basedriver/helpers.js) helper call. Inherited drivers
can customize their caching logic by providing own `onPostProcess` property definition, but the general
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
first before they could be actually installed on the device under test. On iOS, for example, these are .ipa or
.zip compressed application bundles or .aab on Android.
## How Remote Application Bundles Are Cached
In order to validate whether an app bundle downloaded from the given URL could be (re)used from the cache the following
steps are applied:
1. The script is sending `HEAD` request to the given link in order to only fetch response headers. If this request fails/
times out then no caching is applied.
2. The following header values are being extracted: `Last-Modified`, `Cache-Control`. If no `Last-Modified` header is present
or the header value cannot be parsed as a valid datetime then no caching is applied.
3. The script checks if the given URL is already present in the cache. If the app is not cached yet then it gets downloaded
and its current header values along with hashsum are added to the cache.
4. If the URL was already in the cache then the script verifies that:
- the current `Last-Modified` datetime is not different from the previous one
- the difference between the current `Last-Modified` datetime and the cached one is not greater than max-age (if present)
- the hashsum of the cached file/folder is not changed (e.g. it was not corrupted while sitting in the cache)
5. If all validations above succeed then the cached build is returned, otherwise the currently cached entry gets
deleted and a new one is downloaded instead.
## How Local Application Bundles Are Cached
It only makes sense to cache application bundles if they need some preprocessing before being installed on the device under test.
For example, on iOS .ipa bundles must be unzipped, because the system installer only works with .app folders.
1. The script verifies if the given bundle path is already present in the cache. If the bundle was not in the cache yet
then it gets preprocessed and added there.
2. The script validates the hashsum of the bundle and compares it to the previously stored one. If hash sums don't match
then the cached item gets deleted and the preprocessing of the bundle repeats.
## How The Cache File System Is Configured
The cache where the base driver keeps all application bundles is located in the system temp folder. It is configured
on per-process basis, so each test session initialized in scope of the same Appium server process takes advantages
of it. It is a [LRU Cache](https://www.npmjs.com/package/lru-cache) with the following limitations:
- Max items: 1024
- Max time to live (TTL) for each entry: 24 hours
- TTL is refreshed for each entry upon access
!!! warning
Note: The cache root folder is set up for automatic deletion on Appium process termination. This would only
work if Appium server is killed with SIGINT or SIGTERM. If SIGKILL is used then no cache cleanup
would be performed.
+118 -24
View File
@@ -2,39 +2,131 @@
title: Appium and Selenium Grid
---
## Selenium Grid 4
## Using Selenium Grid 4+
The **relay** feature in Grid 4 allows you to proxy Appium requests to an Appium server instance.
The
[relay](https://www.selenium.dev/documentation/grid/configuration/toml_options/#relaying-commands-to-a-service-endpoint-that-supports-webdriver)
feature in Grid 4 allows you to proxy Appium requests to an Appium server instance. Here is
an example walkthrough of how you would connect two different Appium instances to a Selenium Grid.
Please check [Relaying commands to a service endpoint that supports WebDriver](https://www.selenium.dev/documentation/grid/configuration/toml_options/#relaying-commands-to-a-service-endpoint-that-supports-webdriver) and [Selenium Grid 4 and Appium together in harmony](https://www.youtube.com/watch?v=3_aP2rsqZD0) about the configuration and for more details.
### Define the Appium configs
## Selenium Grid 3
Each Appium instance should have a config file that can be easily updated. It should contain any
information which needs to be unique to that particular server (e.g., ports its drivers should use
that others should not). We are going to have 2 Appium servers, so we will need 2 config files:
You are able to register your Appium server with a local [Selenium Grid 3](https://www.selenium.dev/documentation/legacy/selenium_3/grid_3/)
([setup docs](https://www.selenium.dev/documentation/legacy/grid_3/setting_up_your_own_grid/)) by using the
```yaml
# appium1.yml
server:
port: 4723
use-drivers:
- xcuitest
default-capabilities:
wdaLocalPort: 8100
mjpegServerPort: 9100
mjpegScreenshotUrl: "http://localhost:9100"
```
In the above YAML config file, we specify the Appium server port, the driver used, and parameters
for the driver that will be sent in as default capabilities. Our goal is to ensure that any other
drivers running on this host will not compete with system ports or other resources. The second
config file could look like the following, where we simply adjust a few ports to prevent clashes:
```yaml
# appium2.yml
server:
port: 4733
use-drivers:
- xcuitest
default-capabilities:
wdaLocalPort: 8110
mjpegServerPort: 9110
mjpegScreenshotUrl: "http://localhost:9110"
```
### Define the Grid node configs
We will be launching one Grid "node" per Appium server, to manage relaying commands and determining
capacity and online status, etc... So we should have one config file per Grid node as well. Each
node config should include the address of the Appium server it will target, as well as a list of
capability "configs" it should accept to relay a session request to Appium. Here is what the config
could look like for the two nodes:
```toml
# node1.toml
[server]
port = 5555
[node]
detect-drivers = false
[relay]
url = "http://localhost:4723"
status-endpoint = "/status"
configs = [
"1", "{\"platformName\": \"iOS\", \"appium:platformVersion\": \"15.5\", \"appium:deviceName\": \"iPhone 13\", \"appium:automationName\": \"XCUITest\"}"
]
```
```toml
# node2.toml
[server]
port = 5565
[node]
detect-drivers = false
[relay]
url = "http://localhost:4733"
status-endpoint = "/status"
configs = [
"1", "{\"platformName\": \"iOS\", \"appium:platformVersion\": \"15.5\", \"appium:deviceName\": \"iPhone 12\", \"appium:automationName\": \"XCUITest\"}"
]
```
Note that each node config also specifies a different port itself for the node to run on.
### Putting it together
The Grid nodes aren't enough--you'll also want a Grid "hub" that acts as a load balancer and
manager for all the nodes. So in the end we'll have 5 processes running at once: 2 Appium servers,
2 Grid nodes, and 1 Grid hub. It's best to run each of these in a separate terminal window to avoid
confusion of logs. Here is how you'd start each process:
0. `appium --config appium1.yml`
0. `appium --config appium2.yml`
0. `java -jar /path/to/selenium.jar node --config node1.toml`
0. `java -jar /path/to/selenium.jar node --config node2.toml`
0. `java -jar /path/to/selenium.jar hub`
Once you wait a few moments for the nodes to detect their Appium servers, and to register with the
hub, you'll be able to send all your Appium traffic via the single hub endpoint (defaulting to
`http://localhost:4444`).
And of course, you're able to link up Appium servers and nodes running on different machines in
your network to form a larger grid.
## Using Selenium Grid 3
It is possible to register your Appium server with a local [Selenium Grid 3](https://www.selenium.dev/documentation/legacy/selenium_3/grid_3/)
([setup docs](https://www.selenium.dev/documentation/legacy/grid_3/setting_up_your_own_grid/)) instance by using the
`--nodeconfig` server argument.
```bash
appium server --nodeconfig /path/to/nodeconfig.json --base-path=/wd/hub
```
In the node config file you have to define the `browserName`,
`version` and `platform` and based on these parameters the grid
will re-direct your test to the right device. You will also need to
configure your **host** details and the **Selenium Grid** details. For
a full list of all parameters and descriptions look
[here](https://www.selenium.dev/documentation/legacy/selenium_3/grid_setup/)
In the referenced config file you have to define the `browserName`, `version` and `platform`
capabilities and based on these parameters the grid will re-direct your test to the right device.
You will also need to configure your host details and the Selenium Grid details. For a full list of
all parameters and descriptions see
[here](https://www.selenium.dev/documentation/legacy/selenium_3/grid_setup/).
Once you start the appium server and it registers with the grid,
you will see your device on the grid console page:
Once you start the Appium server it will register with the grid, and you will see your device on
the grid console page:
`http://**\<grid-ip-adress\>**:**\<grid-port\>**/grid/console`
New session capabilities should use an `appium:` prefix for non-W3C-standard capabilities such as
`automationName`, since the grid appends the given capabilities in both `desiredCapabilities`
and `firstMatch` in `capabilities`.
### Example Grid Node Configuration JSON
```json
@@ -60,13 +152,15 @@ and `firstMatch` in `capabilities`.
"register": true,
"registerCycle": 5000,
"hubPort": <grid_port>,
"hubHost": "<Grid_host_name_or_grid_ip-address>"
"hubHost": "<Grid_host_name_or_grid_ip-address>",
"hubProtocol": "<Protocol_of_Grid_defaults_to_http>"
}
}
```
If `url`, `host`, and `port` are not given, the config will be auto updated
to point to localhost:whatever-port-Appium-started-on.
If `url`, `host`, and `port` are not given, the config will be auto updated to point to
`localhost:<appium-port>`.
If your Appium server is running on a different machine to your Selenium Grid server, make sure you use an external name/IP address in your `host` & `url` docs; `localhost` and `127.0.0.1` will prevent Selenium Grid from connecting correctly.
If your Appium server is running on a different machine to your Selenium Grid server, make sure you
use an external name/IP address in your `host` and `url` configuration; `localhost` and `127.0.0.1`
will prevent Selenium Grid from connecting correctly.
@@ -14,7 +14,20 @@ Together these do introduce a few breaking changes to how Appium is installed, h
## Breaking Changes
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases) for the most comprehensive lists of changes. Here we call out the breaking changes and what you need to do do account for them.
Here we call out the breaking changes and what you need to do to account for them.
### :bangbang: Default server base path
With Appium 1.x, the server would accept commands by default on `http://localhost:4723/wd/hub`. The
`/wd/hub` base path was a legacy convention from the days of migrating from Selenium 1 to Selenium
2, and is no longer relevant. As such the default base path for the server is now `/`. If you want
to retain the old behaviour, you can set the base path via a command line argument as follows:
```
appium --base-path=/wd/hub
```
You can also set server arguments as [Config file](./config.md) properties.
### :bangbang: Installing drivers during setup
@@ -32,7 +45,7 @@ If you're running in a CI environment or want to install Appium along with some
npm install --global appium --drivers=xcuitest,uiautomator2
```
This will install Appium and the two drivers for you in one go.
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall -g appium`) if you get an installation or startup error.
### :bangbang: Drivers installation path
@@ -2,4 +2,17 @@
title: Write a Test (Java)
---
TODO
The Appium team maintains an official [client](https://github.com/appium/java-client) for the Java programming language.
It is built on top of [Selenium](https://github.com/SeleniumHQ/selenium).
You can also use this client in your Kotlin projects.
Follow the [Add Appium java client to your test framework](https://github.com/appium/java-client#add-appium-java-client-to-your-test-framework)
tutorial in order to connect the library to your test framework sources.
The Appium Java client has dedicated classes to support most of the official Appium drivers. For other drivers
you could simply use the [AppiumDriver](https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/AppiumDriver.java) class
or build your custom derivatives from it. Check the [Drivers Support](https://github.com/appium/java-client#drivers-support)
article to learn more about the current driver class implementations.
Follow the [Usage Examples](https://github.com/appium/java-client#usage-examples) article in order understand
how to invoke Java client features from your test framework.
@@ -0,0 +1,276 @@
---
title: Appium 1.xからAppium 2.xへ移行する
---
<!---
This document is a guide for those who are using Appium 1.x and wish to migrate to Appium 2.x. It contains a list of breaking changes and how to migrate your environments or test suites to ensure compatibility with Appium 2.0.
--->
このドキュメントは既にAppium 1.xを利用している人がAppium 2.xに移行するための手引きです。
破壊的変更(breaking changes)の一覧や、実行環境・テストコードをAppium 2.0互換にするための方法が含まれます。
<!---
## Overview of Appium 2.0
--->
## Appium 2.0の概要
<!---
Appium 2.0 is the most major new release of Appium in over 5 years. The changes in Appium 2.0 are _not_ primarily related to changes in automation behaviors for specific platforms. Instead, Appium 2.0 reenvisions Appium as a _platform_ where "drivers" (code projects that introduce support for automation of a given platform) and "plugins" (code projects that allow for overriding, altering, extending, or adding behaviors to Appium) can be easily created and shared.
--->
Appium 2.0は過去5年間におけるAppiumのリリース野中で最も大きなリリースです。Appium 2.0の主要な変更は特定のプラットフォームに対する自動化された動作に関するものでは**ありません**。Appium 2.0はAppiumをドライバー(drivers)(あるプラットフォームに対する自動化を支援するためのプロジェクト)とプラグイン(plugins)(Appiumの動作を上書き、代替、拡張、もしくは追加するためのプロジェクト)という容易に実装、共有できる**仕組みを提供するプラットフォーム**として再考しています。
<!---
At the same time, the Appium project is taking the opportunity to remove many old and deprecated bits of functionality.
--->
同時に、Appiumプロジェクトは多くの古くなったり非推奨となっている機能や依存を取り除こうとしています。
<!---
Together these do introduce a few breaking changes to how Appium is installed, how drivers and various features are managed, and protocol support. These are detailed below.
--->
これらに合わせて、Appiumのインストール方法、ドライバーやフィーチャーの管理、プロトコルサポートに関していくつかの破壊的変更をが導入されます。詳細は以下になります。
<!---
## Breaking Changes
--->
## 破壊的変更
<!---
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases) for the most comprehensive lists of changes. Here we call out the breaking changes and what you need to do to account for them.
--->
リリース毎の変更一覧に関しては[Appium 2.0リリースノート](https://github.com/appium/appium/releases)を確認してください。ここでは破壊的変更と何をする必要があるのかを示します。
<!---
### :bangbang: Installing drivers during setup
--->
### :bangbang: ドライバーのインストール
<!---
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium@next`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../cli/extensions.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
--->
Appium 1.xをインストールしたとき、全ての入手可能なドライバーは主となるAppiumサーバーと合わせてインストールされていました。Appium 2.0ではそうではありません。Appium 2.0のインストール(例えば `npm install -g appium@next`)は単にAppiumサーバーのみをインストールし、ドライバーはインストールしません。ドライバーをインストールするためには新しい[Appium拡張コマンドラインインタフェース(Appium extension CLI)](../cli/extensions.md)を使わなければいけません。例えば、最新のXCUITestとUiAutomator2ドライバーをインストールする場合、Appiumをインストールしたのちに次のコマンドを実行する必要があります。
```bash
appium driver install uiautomator2 # installs the latest driver version
appium driver install xcuitest@4.12.2 # installs a specific driver version
```
<!---
At this point, your drivers are installed and ready. There's a lot more you can do with this CLI so be sure to check out the docs on it. If you're running in a CI environment or want to install Appium along with some drivers all in one step, you can do so using some special flags during install, for example:
--->
これにより、ドライバーはインストールされ、利用可能になります。このCLIはいろいろな機能を提供しているので、CLIのドキュメントを確認してみてください。もしCI上であったり、いくつかのドライバーをAppiumと一緒にインストールしたい場合、以下のようなフラグを利用することで実現可能です。
```bash
npm install --global appium --drivers=xcuitest,uiautomator2
```
<!---
This will install Appium and the two drivers for you in one go.
--->
これはAppiumと2つのドライバーを一つのコマンドでインストールします。
### :bangbang: Drivers installation path
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server.
The path was `/path/to/appium/node_modules`.
For example, `appium-webdriveragent` to build WebDriverAgent manually was `/path/to/appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent`.
Appium 2.0 installs such dependencies in `APPIUM_HOME` environment variable. The default path is `~/.appium`.
So, the path to `appium-webdriveragent` could be `$APPIUM_HOME/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent` after installing the XCUITest driver package.
### :bangbang: Chromedriver installation flags
In Appium 1.x it was possible to customize the way Chromedriver was installed (as part of the UiAutomator2 driver for example), using the following command line flags:
* `--chromedriver-skip-install`
* `--chromedriver-version`
* `--chromedriver-cdnurl`
Because Appium 2.0 now installs drivers for you, and because these flags were implemented as NPM config flags, they will no longer work. Instead, use the following environment variables during driver installation:
* `APPIUM_SKIP_CHROMEDRIVER_INSTALL`
* `CHROMEDRIVER_VERSION`
* `CHROMEDRIVER_CDNURL`
For example:
```bash
APPIUM_SKIP_CHROMEDRIVER_INSTALL=1 appium driver install uiautomator2
```
### :bangbang: Driver-specific command line options
With Appium 1.x, command-line options specific to particular drivers were all hosted on the main Appium server. So, for example, `--chromedriver-executable` was a CLI parameter you could use with Appium to set the location of a specific Chromedriver version for use with, say, the UiAutomator2 driver.
With Appium 2.x, all driver- and platform-specific CLI params have been moved to the drivers themselves. To access them, you'll now need to prepend the argument with the extension type (either `driver` or `plugin`) and the name of the extension. For example, `--chromedriver-executable` becomes `--driver-uiautomator2-chromedriver-executable`.
### :bangbang: Driver-specific automation commands
The definition of certain commands that pertain only to specific drivers has been moved to those
drivers' implementations. For example, `pressKeyCode` is specific to the UiAutomator2 driver and is
now understood only by that driver. In practice, the only breaking change here is the kind of error
you would encounter if the appropriate driver is not installed. Previously, you would get a `501
Not Yet Implemented` error if using a driver that didn't implement the command. Now, you will get
a `404 Not Found` error because if a driver that doesn't know about the command is not active, the
main Appium server will not define the route corresponding to the command.
### :bangbang: Driver updates
In the past, to get updates to your iOS or Android drivers, you'd simply wait for those updates to be rolled into a new release of Appium, and then update your Appium version. With Appium 2.x, the Appium server and the Appium drivers are versioned and released separately. This means that drivers can be on their own release cadence and that you can get driver updates as they happen, rather than waiting for a new Appium server release. The way to check for driver updates is with the CLI:
```bash
appium driver list --updates
```
If any updates are available, you can then run the `update` command for any given driver:
```bash
appium driver update xcuitest
```
(For a complete description of the update command, check out the [Extension
CLI](../cli/extensions.md) doc)
To update the Appium server itself, you do the same thing as in the past: `npm install -g appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
If you would like to update to a specific version, not the latest, please uninstall the driver and install the desired version using the `install` subcommand instead of `update`.
```bash
appium driver uninstall xcuitest
appium driver install xcuitest@4.11.1
```
### :bangbang: Protocol changes
Appium's API is based on the [W3C WebDriver Protocol](https://www.w3.org/TR/webdriver/), and it has supported this protocol for years. Before the W3C WebDriver Protocol was designed as a web standard, several other protocols were used for both Selenium and Appium. These protocols were the "JSONWP" (JSON Wire Protocol) and "MJSONWP" (Mobile JSON Wire Protocol). The W3C Protocol differs from the (M)JSONWP protocols in a few small ways.
Up until Appium 2.0, Appium supported both protocols, so that older Selenium/Appium clients could still communicate with newer Appium servers. Moving forward, support for older protocols will be removed.
### :bangbang: _Capabilities_
One significant difference between old and new protocols is in the format of capabilities. Previously called "desired capabilities", and now called simply "capabilities", there is now a requirement for a so-called "vendor prefix" on any non-standard capabilities. The list of standard capabilities is given in the [WebDriver Protocol spec](https://www.w3.org/TR/webdriver/#capabilities), and includes a few commonly used capabilities such as `browserName` and `platformName`.
These standard capabilities continue to be used as-is. All other capabilities must include a "vendor prefix" in their name. A vendor prefix is a string followed by a colon, such as `appium:`. Most of Appium's capabilities go beyond the standard W3C capabilities and must therefore include vendor prefixes (we recommend that you use `appium:` unless directed otherwise by documentation). For example:
- `appium:app`
- `appium:noReset`
- `appium:deviceName`
This requirement may or may not be a breaking change for your test suites when targeting Appium 2.0. If you're using an updated Appium client (at least one maintained by the Appium team), the client will add the `appium:` prefix for you on all necessary capabilities automatically. New versions of [Appium Inspector](https://github.com/appium/appium-inspector) will also do this. Cloud-based Appium providers may also do this. So simply be aware that if you get any messages to the effect that your capabilities lack a vendor prefix, this is how you solve that problem.
On a related note, it will no longer be possible to start Appium sessions using WebDriver clients that don't support the W3C protocol (see below for a comment to this effect for the WD library).
To make everyone's lives a bit easier, we've also introduced the option of wrapping up all Appium-related capabilities into one object capability, `appium:options`. You can bundle together anything that you would normally put an `appium:` prefix on into this one capability. Here's an example (in raw JSON) of how you might start an iOS session on the Safari browser using `appium:options`:
```json
{
"platformName": "iOS",
"browserName": "Safari",
"appium:options": {
"platformVersion": "14.4",
"deviceName": "iPhone 11",
"automationName": "XCUITest"
}
}
```
(Of course, each client will have a different way of creating structured capabilities like `appium:options` or other ones that you might have seen such as `goog:chromeOptions`). NB: capabilities that show up in `appium:options` will overwrite capabilities of the same name that show up at the top level of the object. (The new `appium:options` syntax support by cloud providers may vary.)
For more information on capabilities, have a look at the [Capabilities Guide](caps.md).
### :bangbang: _Removed Commands_
In addition to commands which have been moved to driver implementations, commands which were a part of the old JSON Wire Protocol and not a part of the W3C Protocol are no longer available:
- TODO (these commands are being identified and removed and will be updated here when complete)
If you use a modern Appium or Selenium client, you should no longer have access to these anyway, so any breaking changes should appear on the client side first and foremost.
### :bangbang: Image analysis features moved to plugin
One of the design goals for Appium 2.0 is to migrate non-core features into special extensions called [plugins](../ecosystem/index.md). This allows people to opt into features which require extra time to download or extra system setup. The various image-related features of Appium (image comparison, finding elements by image, etc...) have been moved into an officially supported plugin called [images](https://github.com/appium/appium/tree/master/packages/images-plugin).
If you use these image-related methods, to continue accessing them you will need to do two things.
1. Install the plugin: `appium plugin install images`
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=images`
Image-related commands will also be removed on the client side of things, which means you will need to follow the instructions on the plugin README for installing client-side plugins to access these features.
### :bangbang: Execute Driver Script command moved to plugin
If you use the advanced Execute Driver Script feature (which allows you to send in a WebdriverIO script to have it executed completely on the server instead of command-by-command from the client), this functionality has been moved to a plugin. Here's what to do to keep using it:
1. Install the plugin: `appium plugin install execute-driver`
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --use-plugins=execute-driver`
### :bangbang: External Files No Longer Supported for `--nodeconfig`, `--default-capabilities`, `--allow-insecure` and `--deny-insecure`
These options can be provided as strings on the command line (a JSON string for `--nodeconfig` and a comma-separated list of strings for `--allow-insecure` and `--deny-insecure`). Arguments provided on the command line will likely need to be quoted or escaped.
The recommended method to provide these options is now via a [configuration file](#tada-configuration-files).
In summary, if you are using a JSON Appium config file, you can simply cut-and-paste the contents of your "nodeconfig" JSON file into the value of the `server.nodeconfig` property. Any CSV-like files you had previously provided for `--allow-insecure` and `--deny-insecure` become the values of the `server.allow-insecure` and `server.deny-insecure` properties in the Appium config files (respectively); both are arrays of strings.
### :bangbang: Old drivers removed
The old iOS and Android (UiAutomator 1) drivers and related tools (e.g., `authorize-ios`) have been removed. They haven't been relevant for many years anyway.
### :warning: Internal packages renamed
Some Appium-internal NPM packages have been renamed (for example, `appium-base-driver` is now `@appium/base-driver`). This is not a breaking change for Appium users, only for people who have built software that directly incorporates Appium's code.
### :warning: "WD" JavaScript client library no longer supported
For many years, some of Appium's authors maintained the [WD](https://github.com/admc/wd) client library. This library has been deprecated and has not been updated for use with the W3C WebDriver protocol. As such, if you're using this library you'll need to move to a more modern one. We recommend [WebdriverIO](https://webdriver.io).
### :warning: Appium Inspector split out from Appium Desktop
The inspecting portion of Appium Desktop has been moved to its own app, Appium Inspector: [github.com/appium/appium-inspector](https://github.com/appium/appium-inspector). It's fully compatible with Appium 2.0 servers. Simply download it and run it on its own. You no longer need the GUI Appium Desktop server to inspect apps. The Appium Desktop server will continue to be supported at its original site: [github.com/appium/appium-desktop](https://github.com/appium/appium-desktop). It will simply no longer bundle the Inspector with it. Note that Appium Desktop 1.21 and lower versions depend on the deprecated [WD](https://github.com/admc/wd) client, and are not compatible with Appium 2.0. There is currently no Appium 2.0 support for Appium Desktop planned, now that the Inspector is a standalone app.
You can also now use the Appium Inspector without downloading anything, by visiting the [web version of Appium Inspector](https://inspector.appiumpro.com). Note that to test against local servers, you'll need to start the server with `--allow-cors` so that the browser-based version of Appium Inspector can access your Appium server to start sessions.
## Major New Features
Apart from the breaking changes mentioned above, in this section is a list of some of the major new features you may wish to take advantage of with Appium 2.0.
### Plugins
#### :tada: _Server Plugins_
Appium extension authors can now develop their own server plugins, which can intercept and modify
any Appium command, or even adjust the way the underlying Appium HTTP server itself works. To learn
more about plugins, read the new [Appium Introduction](../intro/index.md). Interested in building
a plugin? Check out the [Building Plugins](../ecosystem/build-plugins.md) guide.
#### :tada: _Client Plugins_
TODO
### :tada: Install drivers and plugins from anywhere
You're no longer limited to the drivers that come with Appium, or that the Appium team even knows
about! Appium extension authors can now develop custom drivers, which can be downloaded or
installed via Appium's [Extension CLI](../cli/extensions.md) from NPM, Git, GitHub, or even the
local filesystem. Interested in building a driver? Check out the [Building
Drivers](../ecosystem/build-drivers.md) guide.
### :tada: Configuration Files
Appium now supports _configuration files_ in addition to command-line arguments. In a nutshell, nearly all arguments which Appium 1.x required to be provided on the CLI are now able to be expressed via a configuration file. Configuration files may be in JSON, JS, or YAML format. See the [Config Guide](config.md) for a full explanation.
## Special Notes for Cloud Providers
The rest of this document has applied to Appium generally, but some of the architectural changes in Appium 2.0 will constitute breaking changes for Appium-related service providers, whether a cloud-based Appium host or an internal service. At the end of the day, the maintainer of the Appium server is responsible for installing and making available the various Appium drivers and plugins that end users may wish to use.
We encourage cloud providers to thoroughly read and understand our [recommendation for cloud
provider capabilities](caps.md#special-notes-for-cloud-providers) in order to support user needs in
an industry-compatible way!
+1
View File
@@ -44,6 +44,7 @@ nav:
- guides/event-timing.md
- guides/log-filters.md
- guides/grid.md
- guides/caching.md
- Developer Reference:
- BaseDriver: reference/base_driver.md
- Ecosystem:
+2 -2
View File
@@ -32,8 +32,8 @@ nav:
# - cli/index.md
# - cli/args.md
# - cli/extensions.md
# - Guides: # 手引き?
# - guides/migrating-1-to-2.md
- 手引き:
- guides/migrating-1-to-2.md
# - guides/managing-exts.md
# - guides/config.md
# - guides/security.md
@@ -5,7 +5,6 @@ const {fs} = require('@appium/support');
const {log, LANGS, DOCS_DIR} = require('./utils');
const path = require('path');
const monorepoRoot = path.resolve(__dirname, '..', '..', '..', '..');
const {out: typedocOut} = JSON.parse(fs.readFileSync(path.join(monorepoRoot, 'typedoc.json')));
const {exec} = require('teen_process');
const typedocMap = [[['commands', 'appium_base_driver.md'], ['base_driver.md']]];
@@ -14,12 +13,14 @@ async function main() {
log.info('Generating typedoc reference material');
await exec('npm', ['run', 'typedoc'], {cwd: monorepoRoot});
const {out: typedocOut} = JSON.parse(await fs.readFile(path.join(monorepoRoot, 'typedoc.json')));
for (const lang of LANGS) {
const langRefPath = path.resolve(DOCS_DIR, lang, 'reference');
await fs.rimraf(langRefPath);
await fs.mkdirp(langRefPath);
for (const [from, to] of typedocMap) {
const sourcePath = path.resolve(typedocOut, ...from);
const sourcePath = path.resolve(monorepoRoot, typedocOut, ...from);
const destPath = path.resolve(langRefPath, ...to);
log.info(`Copying ${sourcePath} to ${destPath}`);
await fs.copyFile(sourcePath, destPath);
+2 -1
View File
@@ -9,6 +9,7 @@ import {
CREATE_SESSION_COMMAND,
DELETE_SESSION_COMMAND,
GET_STATUS_COMMAND,
promoteAppiumOptions,
} from '@appium/base-driver';
import AsyncLock from 'async-lock';
import {parseCapsForInnerDriver, pullSettings} from './utils';
@@ -240,7 +241,7 @@ class AppiumDriver extends DriverCore {
// Parse the caps into a format that the InnerDriver will accept
const parsedCaps = parseCapsForInnerDriver(
jsonwpCaps,
w3cCapabilities,
promoteAppiumOptions(w3cCapabilities),
this.desiredCapConstraints,
defaultCapabilities
);
+69 -1
View File
@@ -8,6 +8,7 @@ const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mai
/**
* @extends {ExtensionCommand<DriverType>}
*/
export default class DriverCommand extends ExtensionCommand {
/**
* @param {import('./extension-command').ExtensionCommandOptions<DriverType>} opts
@@ -17,6 +18,12 @@ export default class DriverCommand extends ExtensionCommand {
this.knownExtensions = KNOWN_DRIVERS;
}
/**
* Install a driver
*
* @param {DriverInstallOpts} opts
* @return {Promise<ExtRecord<DriverType>>}
*/
async install({driver, installType, packageName}) {
return await super._install({
installSpec: driver,
@@ -25,18 +32,40 @@ export default class DriverCommand extends ExtensionCommand {
});
}
/**
* Uninstall a driver
*
* @param {DriverUninstallOpts} opts
* @return {Promise<ExtRecord<DriverType>>}
*/
async uninstall({driver}) {
return await super._uninstall({installSpec: driver});
}
/**
* Update a driver
*
* @param {DriverUpdateOpts} opts
* @return {Promise<import('./extension-command').ExtensionUpdateResult>}
*/
async update({driver, unsafe}) {
return await super._update({installSpec: driver, unsafe});
}
/**
*
* @param {DriverRunOptions} opts
* @return {Promise<import('./extension-command').RunOutput>}
*/
async run({driver, scriptName, extraArgs}) {
return await super._run({installSpec: driver, scriptName, extraArgs});
}
/**
*
* @param {import('./extension-command').ExtensionArgs} opts
* @returns {string}
*/
getPostInstallText({extName, extData}) {
return (
`Driver ${extName}@${extData.version} successfully installed\n`.green +
@@ -69,6 +98,16 @@ export default class DriverCommand extends ExtensionCommand {
}
}
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('@appium/types').DriverType} DriverType
*/
/**
* @template {ExtensionType} ExtType
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
*/
/**
* @typedef DriverCommandOptions
* @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
@@ -76,5 +115,34 @@ export default class DriverCommand extends ExtensionCommand {
*/
/**
* @typedef {import('@appium/types').DriverType} DriverType
* Options for {@linkcode DriverCommand.install}
* @typedef DriverInstallOpts
* @property {string} driver - the name or spec of a driver to install
* @property {InstallType} installType - how to install this driver. One of the INSTALL_TYPES
* @property {string} [packageName] - for git/github installs, the driver node package name
*/
/**
* @typedef {import('appium/types').InstallType} InstallType
*/
/**
* Options for {@linkcode DriverCommand.uninstall}
* @typedef DriverUninstallOpts
* @property {string} driver - the name or spec of a driver to uninstall
*/
/**
* Options for {@linkcode DriverCommand.update}
* @typedef DriverUpdateOpts
* @property {string} driver - the name of the driver to update
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
*/
/**
* Options for {@linkcode DriverCommand.run}.
* @typedef DriverRunOptions
* @property {string} driver - name of the driver to run a script from
* @property {string} scriptName - name of the script to run
* @property {string[]} [extraArgs] - arguments to pass to the script
*/
+4 -5
View File
@@ -75,7 +75,7 @@ class ExtensionCommand {
* nor is something like `@returns {never}` which does not imply a thrown exception.
* @param {string} message
* @protected
* @returns {Error}
* @throws {Error}
*/
_createFatalError(message) {
return new Error(this.log.decorate(message, 'error'));
@@ -322,6 +322,7 @@ class ExtensionCommand {
// this _should_ be the same as `probablyExtName` as the one derived above unless
// install type is local.
/** @type {string} */
const extName = receipt[/** @type {string} */ (`${this.type}Name`)];
// check _a second time_ with the more-accurate extName
@@ -360,7 +361,7 @@ class ExtensionCommand {
await this.config.addExtension(extName, extManifest);
// update the if we've changed the local `package.json`
// update the hash if we've changed the local `package.json`
if (await env.hasAppiumDependency(this.config.appiumHome)) {
await packageDidChange(this.config.appiumHome);
}
@@ -782,9 +783,7 @@ export {ExtensionCommand};
/**
* Possible return value for {@linkcode ExtensionCommand.list}
* @typedef UninstalledExtensionListData
* @property {string} pkgName
* @property {false} installed
* @typedef {Partial<InstalledExtensionListData> & {pkgName: string, installed: false}} UninstalledExtensionListData
*/
/**
+69 -2
View File
@@ -17,6 +17,12 @@ export default class PluginCommand extends ExtensionCommand {
this.knownExtensions = KNOWN_PLUGINS;
}
/**
* Install a plugin
*
* @param {PluginInstallOpts} opts
* @returns {Promise<ExtRecord<PluginType>>}
*/
async install({plugin, installType, packageName}) {
return await super._install({
installSpec: plugin,
@@ -25,26 +31,48 @@ export default class PluginCommand extends ExtensionCommand {
});
}
/**
* Uninstall a plugin
*
* @param {PluginUninstallOpts} opts
* @returns {Promise<ExtRecord<PluginType>>}
*/
async uninstall({plugin}) {
return await super._uninstall({installSpec: plugin});
}
/**
* Update a plugin
*
* @param {PluginUpdateOpts} opts
* @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
*/
async update({plugin, unsafe}) {
return await super._update({installSpec: plugin, unsafe});
}
/**
*
* @param {PluginRunOptions} opts
* @returns {Promise<import('./extension-command').RunOutput>}
*/
async run({plugin, scriptName, extraArgs}) {
return await super._run({installSpec: plugin, scriptName, extraArgs});
}
/**
*
* @param {import('./extension-command').ExtensionArgs} opts
* @returns {string}
*/
getPostInstallText({extName, extData}) {
return `Plugin ${extName}@${extData.version} successfully installed`.green;
}
/**
* Validates fields in `appium` field of `driverMetadata`
* Validates fields in `appium` field of `pluginMetadata`
*
* For any `package.json` fields which a driver requires, validate the type of
* For any `package.json` fields which a plugin requires, validate the type of
* those fields on the `package.json` data, throwing an error if anything is
* amiss.
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
@@ -67,5 +95,44 @@ export default class PluginCommand extends ExtensionCommand {
}
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('@appium/types').PluginType} PluginType
*/
/**
* @template {ExtensionType} ExtType
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
*/
/**
* Options for {@linkcode PluginCommand.install}
* @typedef PluginInstallOpts
* @property {string} plugin - the name or spec of a plugin to install
* @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
* @property {string} [packageName] - for git/github installs, the plugin node package name
*/
/**
* @typedef {import('appium/types').InstallType} InstallType
*/
/**
* Options for {@linkcode PluginCommand.uninstall}
* @typedef PluginUninstallOpts
* @property {string} plugin - the name or spec of a plugin to uninstall
*/
/**
* Options for {@linkcode PluginCommand.update}
* @typedef PluginUpdateOpts
* @property {string} plugin - the name of the plugin to update
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
*/
/**
* Options for {@linkcode PluginCommand.run}.
* @typedef PluginRunOptions
* @property {string} plugin - name of the plugin to run a script from
* @property {string} scriptName - name of the script to run
* @property {string[]} [extraArgs] - arguments to pass to the script
*/
+1
View File
@@ -45,6 +45,7 @@ export const KNOWN_DRIVERS = Object.freeze(
espresso: 'appium-espresso-driver',
safari: 'appium-safari-driver',
gecko: 'appium-geckodriver',
chromium: 'appium-chromium-driver',
})
);
@@ -33,13 +33,18 @@ const INSTALL_TYPES = new Set([
* @template {ExtensionType} ExtType
*/
export class ExtensionConfig {
/** @type {ExtType} */
/**
* The type of extension this class is responsible for.
* @type {ExtType}
*/
extensionType;
/** @type {`${ExtType}s`} */
configKey;
/** @type {ExtRecord<ExtType>} */
/**
* Manifest data for the extensions of this type.
*
* This data should _not_ be written to by anything but {@linkcode Manifest}.
* @type {Readonly<ExtRecord<ExtType>>}
*/
installedExtensions;
/** @type {import('@appium/types').AppiumLogger} */
@@ -49,9 +54,9 @@ export class ExtensionConfig {
manifest;
/**
* @type {ExtensionListData}
* @type {ExtensionListData|undefined}
*/
_listDataCache;
#listDataCache;
/**
* @protected
@@ -60,7 +65,6 @@ export class ExtensionConfig {
*/
constructor(extensionType, manifest) {
this.extensionType = extensionType;
this.configKey = `${extensionType}s`;
this.installedExtensions = manifest.getExtensionData(extensionType);
this.manifest = manifest;
}
@@ -204,8 +208,8 @@ export class ExtensionConfig {
if (!_.isEmpty(errorSummaries)) {
log.error(
`Appium encountered ${util.pluralize('error', errorMap.size, true)} while validating ${
this.configKey
} found in manifest ${this.manifestPath}`
this.extensionType
}s found in manifest ${this.manifestPath}`
);
for (const summary of errorSummaries) {
log.error(summary);
@@ -219,7 +223,7 @@ export class ExtensionConfig {
'warning',
warningMap.size,
true
)} while validating ${this.configKey} found in manifest ${this.manifestPath}`
)} while validating ${this.extensionType}s found in manifest ${this.manifestPath}`
);
for (const summary of warningSummaries) {
log.warn(summary);
@@ -231,18 +235,20 @@ export class ExtensionConfig {
/**
* Retrieves listing data for extensions via command class.
* Caches the result in {@linkcode ExtensionConfig._listDataCache}
*
* This is an expensive operation, so the result is cached. Currently, there is no
* use case for invalidating the cache.
* @protected
* @returns {Promise<ExtensionListData>}
*/
async getListData() {
if (this._listDataCache) {
return this._listDataCache;
if (this.#listDataCache) {
return this.#listDataCache;
}
const CommandClass = /** @type {ExtCommand<ExtType>} */ (commandClasses[this.extensionType]);
const cmd = new CommandClass({config: this, json: true});
const listData = await cmd.list({showInstalled: true, showUpdates: true});
this._listDataCache = listData;
this.#listDataCache = listData;
return listData;
}
@@ -433,7 +439,7 @@ export class ExtensionConfig {
* @returns {Promise<void>}
*/
async addExtension(extName, extManifest, {write = true} = {}) {
this.manifest.addExtension(this.extensionType, extName, extManifest);
this.manifest.setExtension(this.extensionType, extName, extManifest);
if (write) {
await this.manifest.write();
}
@@ -446,10 +452,10 @@ export class ExtensionConfig {
* @returns {Promise<void>}
*/
async updateExtension(extName, extManifest, {write = true} = {}) {
this.installedExtensions[extName] = {
this.manifest.setExtension(this.extensionType, extName, {
...this.installedExtensions[extName],
...extManifest,
};
});
if (write) {
await this.manifest.write();
}
@@ -463,7 +469,7 @@ export class ExtensionConfig {
* @returns {Promise<void>}
*/
async removeExtension(extName, {write = true} = {}) {
delete this.installedExtensions[extName];
this.manifest.deleteExtension(this.extensionType, extName);
if (write) {
await this.manifest.write();
}
@@ -477,13 +483,13 @@ export class ExtensionConfig {
print(activeNames) {
if (_.isEmpty(this.installedExtensions)) {
log.info(
`No ${this.configKey} have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
`No ${this.extensionType}s have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
'command to install the one(s) you want to use.'
);
return;
}
log.info(`Available ${this.configKey}:`);
log.info(`Available ${this.extensionType}s:`);
for (const [extName, extManifest] of /** @type {[string, ExtManifest<ExtType>][]} */ (
_.toPairs(this.installedExtensions)
)) {
@@ -552,7 +558,7 @@ export class ExtensionConfig {
* @returns {boolean}
*/
isInstalled(extName) {
return _.includes(Object.keys(this.installedExtensions), extName);
return extName in this.installedExtensions;
}
/**
@@ -1,3 +1,4 @@
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
import log from '../logger';
/**
@@ -12,7 +13,13 @@ import log from '../logger';
const SCHEMA_REV_3 = 3;
/**
* Collection of functions to migrate from one version to another
* Collection of functions to migrate from one version to another.
*
* These functions _should not actually perform the migration_; rather, they
* should return `true` if the migration should be performed. The migration
* itself will happen within {@linkcode Manifest.syncWithInstalledExtensions}; the extensions
* will be checked and the manifest file updated per the state of the filesystem.
*
* @type {{[P in keyof ManifestDataVersions]?: Migration}}
*/
const Migrations = {
@@ -24,9 +31,13 @@ const Migrations = {
*
* @type {Migration}
*/
[SCHEMA_REV_3]: (data) => {
[SCHEMA_REV_3]: (manifest) => {
let shouldSync = false;
const allExtData = [...Object.values(data.drivers), ...Object.values(data.plugins)];
/** @type {Array<ExtManifest<PluginType>|ExtManifest<DriverType>>} */
const allExtData = [
...Object.values(manifest.getExtensionData(DRIVER_TYPE)),
...Object.values(manifest.getExtensionData(PLUGIN_TYPE)),
];
for (const metadata of allExtData) {
if (!('installPath' in metadata)) {
shouldSync = true;
@@ -38,14 +49,20 @@ const Migrations = {
};
/**
* Set `schemaRev` to a specific version
* @param {AnyManifestDataVersion} data
* Set `schemaRev` to a specific version.
*
* This _does_ mutate `data` in-place, unlike functions in
* {@linkcode Migrations}.
*
* Again, returning `true` means that the manifest should be--at
* minimum--persisted to disk, since we changed it.
* @param {Readonly<Manifest>} manifest
* @param {keyof ManifestDataVersions} version
* @returns {boolean} Whether the data was modified
*/
function setSchemaRev(data, version) {
if (data.schemaRev ?? 0 < version) {
data.schemaRev = version;
function setSchemaRev(manifest, version) {
if (manifest.schemaRev ?? 0 < version) {
manifest.setSchemaRev(version);
return true;
}
return false;
@@ -56,44 +73,48 @@ function setSchemaRev(data, version) {
*
* `data` is modified in-place.
*
* @param {Manifest} manifest
* @param {AnyManifestDataVersion} data
* @param {Readonly<Manifest>} manifest
* @returns {Promise<boolean>} If `true` existing packages should be synced from disk and the manifest should be persisted.
*/
export async function migrate(manifest, data) {
export async function migrate(manifest) {
let didChange = false;
for await (const [v, migration] of Object.entries(Migrations)) {
const version = /** @type {keyof ManifestDataVersions} */ (Number(v));
didChange = (await migration(data, manifest)) || didChange;
didChange = setSchemaRev(data, version) || didChange;
didChange = (await migration(manifest)) || didChange;
didChange = setSchemaRev(manifest, version) || didChange;
}
if (didChange) {
// this is not _technically_ true, since we don't actually write the file here.
log.info(`Upgraded extension manifest to schema v${data.schemaRev}`);
log.info(`Upgraded extension manifest to schema v${manifest.schemaRev}`);
}
return didChange;
}
/**
* Migration functions take a {@linkcode Manifest} and its data and **mutates `data` in-place**.
* A migration function. It will return `true` if a change _should be made_.
*
* A migration function should not modify `schemaRev`, as this is done automatically.
*
* A migration function can also call out to, say, {@linkcode Manifest.syncWithInstalledExtensions}, which
* may apply the mutation itself.
*
* Note that at the time of writing, we're not able to determine the version of the _current_ manifest file if there is no `schemaRev` prop present (and we may not want to trust it anyway).
* @callback Migration
* @param {AnyManifestDataVersion} data - The raw data from `Manifest`
* @param {Manifest} manifest - The `Manifest` instance
* @param {Readonly<Manifest>} manifest - The `Manifest` instance
* @returns {boolean|Promise<boolean>} If `true`, then something changed
*/
/**
* @typedef {import('appium/types').ManifestData} ManifestData
* @typedef {import('@appium/types').DriverType} DriverType
* @typedef {import('@appium/types').PluginType} PluginType
* @typedef {import('appium/types').AnyManifestDataVersion} AnyManifestDataVersion
* @typedef {import('appium/types').ManifestDataVersions} ManifestDataVersions
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('appium/types').ManifestV2.ManifestData} ManifestDataV2
* @typedef {import('./manifest').Manifest} Manifest
*/
/**
* @template {ExtensionType} ExtType
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
*/
+113 -39
View File
@@ -38,6 +38,8 @@ const INITIAL_MANIFEST_DATA = Object.freeze({
/**
* Given a `package.json` return `true` if it represents an Appium Extension (either a driver or plugin).
*
* _This is a type guard; not a validator._
*
* The `package.json` must have an `appium` property which is an object.
* @param {any} value
* @returns {value is ExtPackageJson<ExtensionType>}
@@ -50,32 +52,35 @@ function isExtension(value) {
_.isString(value.version)
);
}
/**
* Given a `package.json`, return `true` if it represents an Appium Driver.
*
* To be considered a driver, a `package.json` must have a fields
* `appium.driverName`, `appium.automationName` and `appium.platformNames`.
* _This is a type guard; not a validator._
*
* To be considered a driver, a `package.json` must have an `appium.driverName` field.
*
* Further validation of the `appium` property happens elsewhere.
* @param {any} value - Value to test
* @returns {value is ExtPackageJson<DriverType>}
*/
function isDriver(value) {
return (
isExtension(value) &&
_.isString(_.get(value, 'appium.driverName')) &&
_.isString(_.get(value, 'appium.automationName')) &&
_.isArray(_.get(value, 'appium.platformNames'))
);
return isExtension(value) && 'driverName' in value.appium && _.isString(value.appium.driverName);
}
/**
* Given a `package.json`, return `true` if it represents an Appium Plugin.
*
* _This is a type guard; not a validator._
*
* To be considered a plugin, a `package.json` must have an `appium.pluginName` field.
*
* Further validation of the `appium` property happens elsewhere.
* @param {any} value - Value to test
* @returns {value is ExtPackageJson<PluginType>}
*/
function isPlugin(value) {
return isExtension(value) && _.isString(_.get(value, 'appium.pluginName'));
return isExtension(value) && 'pluginName' in value.appium && _.isString(value.appium.pluginName);
}
/**
@@ -166,7 +171,23 @@ export class Manifest {
const onMatch = async (filepath) => {
try {
const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
if (isDriver(pkg) || isPlugin(pkg)) {
if (isExtension(pkg)) {
const extType = isDriver(pkg) ? DRIVER_TYPE : PLUGIN_TYPE;
/**
* this should only be 'unknown' if the extension's `package.json` is invalid
* @type {string}
*/
const name = isDriver(pkg)
? pkg.appium.driverName
: isPlugin(pkg)
? pkg.appium.pluginName
: '(unknown)';
if (
(isDriver(pkg) && !this.hasDriver(name)) ||
(isPlugin(pkg) && !this.hasPlugin(name))
) {
log.info(`Discovered installed ${extType} "${name}"`);
}
const changed = this.addExtensionFromPackage(pkg, filepath);
didChange = didChange || changed;
}
@@ -228,11 +249,10 @@ export class Manifest {
/**
* Given a path to a `package.json`, add it as either a driver or plugin to the manifest.
*
* Will _not_ overwrite existing entries.
* @template {ExtensionType} ExtType
* @param {ExtPackageJson<ExtType>} pkgJson
* @param {string} pkgPath
* @returns {boolean} - `true` upon success, `false` if the extension is already registered.
* @returns {boolean} - `true` if this method did anything.
*/
addExtensionFromPackage(pkgJson, pkgPath) {
const extensionPath = path.dirname(pkgPath);
@@ -250,26 +270,28 @@ export class Manifest {
};
if (isDriver(pkgJson)) {
if (!this.hasDriver(pkgJson.appium.driverName)) {
this.addExtension(DRIVER_TYPE, pkgJson.appium.driverName, {
..._.omit(pkgJson.appium, 'driverName'),
...internal,
});
const value = {
..._.omit(pkgJson.appium, 'driverName'),
...internal,
};
if (!_.isEqual(value, this.#data.drivers[pkgJson.appium.driverName])) {
this.setExtension(DRIVER_TYPE, pkgJson.appium.driverName, value);
return true;
}
return false;
} else if (isPlugin(pkgJson)) {
if (!this.hasPlugin(pkgJson.appium.pluginName)) {
this.addExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, {
..._.omit(pkgJson.appium, 'pluginName'),
...internal,
});
const value = {
..._.omit(pkgJson.appium, 'pluginName'),
...internal,
};
if (!_.isEqual(value, this.#data.plugins[pkgJson.appium.pluginName])) {
this.setExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, value);
return true;
}
return false;
} else {
throw new TypeError(
`The extension in ${extensionPath} is neither a valid driver nor a valid plugin.`
`The extension in ${extensionPath} is neither a valid ${DRIVER_TYPE} nor a valid ${PLUGIN_TYPE}.`
);
}
}
@@ -285,12 +307,29 @@ export class Manifest {
* @param {ExtManifest<ExtType>} extData - Extension metadata
* @returns {ExtManifest<ExtType>} A clone of `extData`, potentially with a mutated `appiumVersion` field
*/
addExtension(extType, extName, extData) {
const data = _.clone(extData);
setExtension(extType, extName, extData) {
const data = _.cloneDeep(extData);
this.#data[`${extType}s`][extName] = data;
return data;
}
/**
* Sets the schema revision
* @param {keyof import('./manifest-migrations').ManifestDataVersions} rev
*/
setSchemaRev(rev) {
this.#data.schemaRev = rev;
}
/**
* Remove an extension from the manifest.
* @param {ExtensionType} extType
* @param {string} extName
*/
deleteExtension(extType, extName) {
delete this.#data[`${extType}s`][extName];
}
/**
* Returns the `APPIUM_HOME` path
*/
@@ -305,12 +344,19 @@ export class Manifest {
return this.#manifestPath;
}
/**
* Returns the schema rev of this manifest
*/
get schemaRev() {
return this.#data.schemaRev;
}
/**
* Returns extension data for a particular type.
*
* @template {ExtensionType} ExtType
* @param {ExtType} extType
* @returns {ExtRecord<ExtType>}
* @returns {Readonly<ExtRecord<ExtType>>}
*/
getExtensionData(extType) {
return this.#data[/** @type {string} */ (`${extType}s`)];
@@ -319,11 +365,18 @@ export class Manifest {
/**
* Reads manifest from disk and _overwrites_ the internal data.
*
* If the manifest does not exist on disk, an {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created.
* If the manifest does not exist on disk, an
* {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created, as
* well as its directory if needed.
*
* If `APPIUM_HOME` contains a `package.json` with an `appium` dependency, then a hash of the `package.json` will be taken. If this hash differs from the last hash, the contents of `APPIUM_HOME/node_modules` will be scanned for extensions that may have been installed outside of the `appium` CLI. Any found extensions will be added to the manifest file, and if so, the manifest file will be written to disk.
* This will also, if necessary:
* 1. perform a migration of the manifest data
* 2. sync the manifest with extensions on-disk (kind of like "auto
* discovery")
* 3. write the manifest to disk.
*
* Only one read operation can happen at a time.
*
* Only one read operation should happen at a time.
* @returns {Promise<ManifestData>} The data
*/
async read() {
@@ -335,17 +388,23 @@ export class Manifest {
this.#reading = (async () => {
/** @type {ManifestData} */
let data;
let isNewFile = false;
/**
* This will be `true` if, after reading, we need to update the manifest data
* and write it again to disk.
*/
let shouldWrite = false;
await this.#setManifestPath();
try {
log.debug(`Reading ${this.#manifestPath}...`);
const yaml = await fs.readFile(this.#manifestPath, 'utf8');
data = YAML.parse(yaml);
log.debug(`Parsed manifest file: ${JSON.stringify(data, null, 2)}`);
log.debug(
`Parsed manifest file at ${this.#manifestPath}: ${JSON.stringify(data, null, 2)}`
);
} catch (err) {
if (err.code === 'ENOENT') {
log.debug(`No manifest file found at ${this.#manifestPath}; creating`);
data = _.cloneDeep(INITIAL_MANIFEST_DATA);
isNewFile = true;
shouldWrite = true;
} else {
if (this.#manifestPath) {
throw new Error(
@@ -364,27 +423,42 @@ export class Manifest {
this.#data = data;
let shouldWrite = false;
if ((data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
/**
* the only way `shouldWrite` is `true` is if we have a new file. a new
* file will get the latest schema revision, so we can skip the migration.
*/
if (!shouldWrite && (data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
log.debug(
`Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
);
shouldWrite = await migrate(this, this.#data);
shouldWrite = await migrate(this);
}
/**
* we still may want to sync with installed extensions even if we have a
* new file. right now this is limited to the following cases:
* 1. we have a brand new manifest file
* 2. we have performed a migration on a manifest file
* 3. `appium` is a dependency within `package.json`, and `package.json`
* has changed since last time we checked.
*
* It may also make sense to sync with the extensions in an arbitrary
* `APPIUM_HOME`, but we don't do that here.
*/
if (
shouldWrite ||
((await env.hasAppiumDependency(this.appiumHome)) &&
(await packageDidChange(this.appiumHome)))
) {
shouldWrite = await this.syncWithInstalledExtensions();
log.debug('Discovering newly installed extensions...');
shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
}
if (isNewFile || shouldWrite) {
if (shouldWrite) {
await this.write();
}
})();
try {
await this.#reading;
return this.#data;
+16 -15
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.0.0-beta.47",
"version": "2.0.0-beta.50",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -38,7 +38,8 @@
"support.*",
"plugin.*",
"scripts/autoinstall-extensions.js",
"types"
"types",
"tsconfig.json"
],
"scripts": {
"build:docs": "node docs/scripts/build-docs.js",
@@ -52,17 +53,17 @@
"postinstall": "node ./scripts/autoinstall-extensions.js",
"publish:docs": "cross-env APPIUM_DOCS_PUBLISH=1 npm run build:docs",
"test": "npm run test:unit",
"test:e2e": "mocha --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
"test:e2e": "mocha -p --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
"test:smoke": "cross-env APPIUM_HOME=./local_appium_home node ./index.js driver install uiautomator2 && cross-env APPIUM_HOME=./local_appium_home node ./index.js driver list",
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.0.0",
"@appium/base-plugin": "^2.0.0",
"@appium/docutils": "^0.1.0",
"@appium/schema": "^0.1.0",
"@appium/support": "^3.0.0",
"@appium/types": "^0.6.0",
"@appium/base-driver": "^9.2.1",
"@appium/base-plugin": "^2.1.1",
"@appium/docutils": "^0.1.3",
"@appium/schema": "^0.2.1",
"@appium/support": "^3.1.1",
"@appium/types": "^0.8.1",
"@sidvind/better-ajv-errors": "2.1.0",
"@types/argparse": "2.0.10",
"@types/bluebird": "3.5.38",
@@ -70,12 +71,12 @@
"@types/semver": "7.3.13",
"@types/teen_process": "2.0.0",
"@types/wrap-ansi": "3.0.0",
"ajv": "8.11.2",
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"argparse": "2.0.1",
"async-lock": "1.4.0",
"asyncbox": "2.9.4",
"axios": "1.2.1",
"axios": "1.2.2",
"bluebird": "3.7.2",
"cross-env": "7.0.3",
"find-up": "5.0.0",
@@ -90,10 +91,10 @@
"semver": "7.3.8",
"source-map-support": "0.5.21",
"teen_process": "2.0.2",
"type-fest": "3.3.0",
"type-fest": "3.5.1",
"winston": "3.8.2",
"wrap-ansi": "7.0.0",
"yaml": "2.1.3"
"yaml": "2.2.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -103,8 +104,8 @@
"access": "public",
"tag": "next"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
"typedoc": {
"entryPoint": "./build/lib/main.js"
"entryPoint": "./lib/main.js"
}
}
@@ -1,5 +1,5 @@
{
"devDependencies": {
"webdriverio": "7.27.0"
"webdriverio": "7.29.1"
}
}
+106
View File
@@ -0,0 +1,106 @@
import getPort from 'get-port';
import {tempDir, fs} from '@appium/support';
import {exec} from 'teen_process';
import B from 'bluebird';
import {
APPIUM_ROOT,
readAppiumArgErrorFixture,
formatAppiumArgErrorOutput,
EXECUTABLE,
runAppiumRaw,
} from './e2e-helpers';
const {expect} = chai;
describe('argument parsing', function () {
/**
* @type {string}
*/
let appiumHome;
before(async function () {
appiumHome = await tempDir.openDir();
});
after(async function () {
await fs.rimraf(appiumHome);
});
describe('when the user provides a very long string for an arg accepting a blob or filename', function () {
describe('when the very long string is a JSON blob', function () {
it('should not throw an ENAMETOOLONG exception', async function () {
this.timeout(10000);
const capsArg = JSON.stringify({
'appium:platformName': 'ANDROID',
'appium:platformVersion': '11',
'appium:deviceName':
'Spicy jalapeno bacon ipsum dolor amet deserunt tempor pork belly aliqua drumstick, occaecat dolor venison et labore. Rump meatball pork chop tail. Consequat adipisicing kielbasa occaecat laborum pig. Qui pork chop chicken nostrud boudin fugiat. Proident ut culpa, chuck nulla sunt pastrami ut tri-tip. Buffalo dolore adipisicing, labore venison elit beef fatback kevin burgdoggen tail pancetta filet mignon. Dolor turducken rump, anim kevin sunt exercitation ham filet mignon beef ribs ad officia eiusmod id cillum.',
});
await expect(
exec(
process.execPath,
[
EXECUTABLE,
'-pa=/wd/hub',
'--session-override',
'--local-timezone',
'--relaxed-security',
`--default-capabilities=${capsArg}`,
'--port',
String(await getPort()),
],
{
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
cwd: APPIUM_ROOT,
timeout: 5000,
}
)
).to.be.rejectedWith(Error, /timed out/);
});
});
});
describe('when the user provides an string where a number was expected', function () {
describe('when color output is supported', function () {
it('should output a fancy error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--port=sheep'], {
env: {FORCE_COLOR: '1'},
}),
readAppiumArgErrorFixture('cli/cli-error-output-color.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
describe('when color output is unsupported', function () {
it('should output a colorless yet fancy error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--port=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
});
describe('when the user provides a value for a boolean argument', function () {
it('should output a basic error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--relaxed-security=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output-boolean.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
describe('when the user provides an unknown argument', function () {
it('should output a basic error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--pigs=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output-unknown.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
});
@@ -0,0 +1,421 @@
// @ts-check
import {exec} from 'teen_process';
import {fs, tempDir, util} from '@appium/support';
import path from 'path';
import resolveFrom from 'resolve-from';
import {
DRIVER_TYPE,
EXT_SUBCOMMAND_INSTALL as INSTALL,
EXT_SUBCOMMAND_LIST as LIST,
EXT_SUBCOMMAND_RUN as RUN,
EXT_SUBCOMMAND_UNINSTALL as UNINSTALL,
KNOWN_DRIVERS,
} from '../../lib/constants';
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
import {installLocalExtension, runAppiumJson, runAppiumRaw} from './e2e-helpers';
const {expect} = chai;
const TEST_DRIVER_DIR = path.dirname(resolveFixture('test-driver/package.json'));
const TEST_DRIVER_INVALID_PEERS_DIR = path.dirname(
resolveFixture('test-driver-invalid-peer-dep/package.json')
);
describe('Driver CLI', function () {
/**
* @type {string}
*/
let appiumHome;
/**
* @type {(args?: string[]) => Promise<ExtensionListData>}
*/
let runList;
/**
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
*/
let runRun;
/**
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
*/
let runInstall;
/**
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
*/
let runUninstall;
async function resetAppiumHome() {
await fs.rimraf(appiumHome);
await fs.mkdirp(appiumHome);
}
before(async function () {
appiumHome = await tempDir.openDir();
const run = runAppiumJson(appiumHome);
runInstall = async (args) =>
/** @type {ReturnType<typeof runInstall>} */ (await run([DRIVER_TYPE, INSTALL, ...args]));
runUninstall = async (args) =>
/** @type {ReturnType<typeof runUninstall>} */ (await run([DRIVER_TYPE, UNINSTALL, ...args]));
runList = async (args = []) =>
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
runRun = async (args) =>
/** @type {ReturnType<typeof runRun>} */ (await run([DRIVER_TYPE, RUN, ...args]));
});
after(async function () {
await fs.rimraf(appiumHome);
});
describe(LIST, function () {
it('should list available drivers', async function () {
// note this is raw, not json
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST], {});
for (const d of Object.keys(KNOWN_DRIVERS)) {
stderr.should.match(new RegExp(`${d}.+[not installed]`));
}
});
it('should list available drivers in json format', async function () {
const driverData = await runList();
for (const d of Object.keys(KNOWN_DRIVERS)) {
driverData[d].should.eql({
installed: false,
pkgName: KNOWN_DRIVERS[d],
});
}
});
it('should allow filtering by installed drivers', async function () {
const out = await runList(['--installed']);
out.should.eql({});
});
it('should show updates for installed drivers with --updates', async function () {
const versions = /** @type {string[]} */ (
JSON.parse(
(
await exec('npm', ['view', '@appium/fake-driver', 'versions', '--json'], {
encoding: 'utf-8',
})
).stdout
)
);
const penultimateFakeDriverVersionAsOfRightNow = versions[versions.length - 2];
await resetAppiumHome();
await runInstall([
`@appium/fake-driver@${penultimateFakeDriverVersionAsOfRightNow}`,
'--source',
'npm',
]);
const {fake} =
/** @type {Record<string,import('appium/lib/cli/extension-command').InstalledExtensionListData>} */ (
await runList(['--updates'])
);
const updateVersion = String(fake.updateVersion || fake.unsafeUpdateVersion);
util.compareVersions(
String(updateVersion),
'>',
penultimateFakeDriverVersionAsOfRightNow
).should.be.true;
// TODO: this could probably be replaced by looking at updateVersion in the JSON
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST, '--updates'], {});
stderr.should.match(new RegExp(`fake.+[${updateVersion} available]`));
});
describe('if a driver is not published to npm', function () {
it('should not throw an error', async function () {
await resetAppiumHome();
await installLocalExtension(appiumHome, DRIVER_TYPE, TEST_DRIVER_DIR);
await expect(runList(['--updates'])).not.to.be.rejected;
});
});
});
describe(INSTALL, function () {
beforeEach(async function () {
await resetAppiumHome();
});
it('should not install appium in APPIUM_HOME', async function () {
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
await fs.stat(path.join(appiumHome, 'node_modules', 'appium')).should.be.rejected;
});
it('should install a driver from the list of known drivers', async function () {
const ret = await runInstall(['uiautomator2']);
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
ret.uiautomator2.installType.should.eql('npm');
ret.uiautomator2.installSpec.should.eql('uiautomator2');
const list = await runList(['--installed']);
// @ts-ignore
delete list.uiautomator2.installed;
list.should.eql(ret);
});
it('should install a driver from npm', async function () {
const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('@appium/fake-driver');
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from npm and a local driver', async function () {
await runInstall(['@appium/fake-driver', '--source', 'npm']);
await installLocalExtension(appiumHome, DRIVER_TYPE, TEST_DRIVER_DIR);
const list = await runList(['--installed']);
expect(list.fake).to.exist;
expect(list.test).to.exist;
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
});
it('should install _two_ drivers from npm', async function () {
await runInstall(['@appium/fake-driver', '--source', 'npm']);
await runInstall(['appium-uiautomator2-driver', '--source', 'npm']);
const list = await runList(['--installed']);
expect(list.fake).to.exist;
expect(list.uiautomator2).to.exist;
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, 'appium-uiautomator2-driver')).not.to.throw;
});
it('should install a driver from npm with a specific version/tag', async function () {
const currentFakeDriverVersionAsOfRightNow = '3.0.5';
const installSpec = `@appium/fake-driver@${currentFakeDriverVersionAsOfRightNow}`;
const ret = await runInstall([installSpec, '--source', 'npm']);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql(installSpec);
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from GitHub', async function () {
const ret = await runInstall([
'appium/appium-fake-driver',
'--source',
'github',
'--package',
'appium-fake-driver',
]);
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('github');
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a local git repo', async function () {
const ret = await runInstall([
FAKE_DRIVER_DIR,
'--source',
'git',
'--package',
'@appium/fake-driver',
]);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql(FAKE_DRIVER_DIR);
const list = await runList(['--installed', '--json']);
// @ts-ignore
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a remote git repo', async function () {
const ret = await runInstall([
'git+https://github.com/appium/appium-fake-driver.git',
'--source',
'git',
'--package',
'appium-fake-driver',
]);
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
list.should.eql(ret);
});
describe('when peer dependencies are invalid', function () {
it('should install the driver anyway', async function () {
const ret = await installLocalExtension(
appiumHome,
DRIVER_TYPE,
TEST_DRIVER_INVALID_PEERS_DIR
);
ret.test.pkgName.should.equal('test-driver-invalid-peer-dep');
const list = await runList(['--installed']);
list.test.pkgName.should.equal('test-driver-invalid-peer-dep');
});
it('should warn the user that peer deps are invalid', async function () {
const ret = await runAppiumRaw(
appiumHome,
[DRIVER_TYPE, INSTALL, '--source', 'local', TEST_DRIVER_INVALID_PEERS_DIR],
{}
);
ret.stderr.should.match(/may be incompatible with the current version of Appium/i);
ret.stderr.should.match(/successfully installed/i);
});
});
describe('when peer dependencies are valid', function () {
it('should not display a warning', async function () {
const ret = await runAppiumRaw(
appiumHome,
[DRIVER_TYPE, INSTALL, '--source', 'local', TEST_DRIVER_DIR],
{}
);
ret.stderr.should.not.match(/may be incompatible with the current version of Appium/i);
ret.stderr.should.match(/successfully installed/i);
});
});
});
describe(`Local ${INSTALL}`, function () {
// NOTE: This suite is separate because we don't to run `clear()` before each test.
/** @type {ExtRecord<DriverType>} */
let installResult;
/** @type {ExtensionListData} */
let listResult;
/** @type {string} */
let installPath;
before(async function () {
await resetAppiumHome();
// take advantage of the fact that we know we have fake driver installed as a dependency in
// this module, so we know its local path on disk
installResult = await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
listResult = await runList(['--installed']);
installPath = resolveFrom(appiumHome, '@appium/fake-driver');
});
it('should install a driver from a local npm module', function () {
expect(installResult.fake).to.include({
pkgName: '@appium/fake-driver',
installType: 'local',
installSpec: FAKE_DRIVER_DIR,
});
});
it('should show the installed driver in the list of extensions', function () {
expect(listResult.fake).to.deep.include(installResult.fake);
});
// this fails in CI and I don't know why
it.skip('should create a symlink', async function () {
const srcStat = await fs.lstat(FAKE_DRIVER_DIR);
const destStat = await fs.lstat(appiumHome);
if (srcStat.dev !== destStat.dev) {
return this.skip();
}
// it should be a link! this may be npm-version dependent, but it's worked
// this way for quite awhile
const stat = await fs.lstat(installPath);
expect(stat.isSymbolicLink()).to.be.true;
});
});
describe('uninstall', function () {
beforeEach(async function () {
await resetAppiumHome();
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
});
it('should uninstall a driver based on its driver name', async function () {
const uninstall = await runUninstall(['fake']);
uninstall.should.not.have.key('fake');
expect(fs.exists(path.join(appiumHome, 'node_modules', '@appium', 'fake-driver'))).to
.eventually.be.false;
});
});
describe('run', function () {
const driverName = 'fake';
before(async function () {
await resetAppiumHome();
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
});
describe('when the driver and script is valid', function () {
const scriptName = 'fake-success';
describe('when the script completes successfully', function () {
it('should result in success', async function () {
const out = await runRun([driverName, scriptName]);
out.should.not.have.property('error');
});
});
describe('when the script fails', function () {
it('should return an error', async function () {
const out = await runRun([driverName, 'fake-error']);
out.should.have.property('error');
});
});
describe('when passed extra arguments', function () {
it('should pass them to the script', async function () {
const out = await runRun([driverName, scriptName, '--foo', '--bar']);
out.should.not.have.property('error');
out.output.should.match(/--foo --bar/);
});
});
});
describe('when the driver is valid but the script is not', function () {
it('should throw an error', async function () {
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
});
});
describe('when the driver and script are invalid', function () {
it('should throw an error', async function () {
const driverName = 'foo';
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
});
});
});
});
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('appium/types').ManifestData} ManifestData
* @typedef {import('@appium/types').DriverType} DriverType
* @typedef {import('@appium/types').PluginType} PluginType
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
*/
/**
* @template ExtSubCommand
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
*/
/**
* @template T
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
*/
@@ -0,0 +1,79 @@
// @ts-check
import {fs, tempDir} from '@appium/support';
import {EXT_SUBCOMMAND_RUN as RUN, PLUGIN_TYPE} from '../../lib/constants';
import {FAKE_PLUGIN_DIR} from '../helpers';
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
const {expect} = chai;
describe('Plugin CLI', function () {
/**
* @type {string}
*/
let appiumHome;
/**
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
*/
let runRun;
before(async function () {
appiumHome = await tempDir.openDir();
const run = runAppiumJson(appiumHome);
runRun = async (args) =>
/** @type {ReturnType<typeof runRun>} */ (await run([PLUGIN_TYPE, RUN, ...args]));
});
after(async function () {
await fs.rimraf(appiumHome);
});
describe('run', function () {
before(async function () {
await installLocalExtension(appiumHome, PLUGIN_TYPE, FAKE_PLUGIN_DIR);
});
it('should run a valid plugin, valid script, and result in success', async function () {
const pluginName = 'fake';
const scriptName = 'fake-success';
const out = await runRun([pluginName, scriptName, '--json']);
out.should.not.have.property('error');
});
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
const pluginName = 'fake';
const out = await runRun([pluginName, 'fake-error', '--json']);
out.should.have.property('error');
});
it('should take a valid plugin, invalid script, and throw an error', async function () {
const pluginName = 'fake';
await expect(runRun([pluginName, 'foo', '--json'])).to.be.rejectedWith(Error);
});
it('should take an invalid plugin, invalid script, and throw an error', async function () {
await expect(runRun(['foo', 'bar', '--json'])).to.be.rejectedWith(Error);
});
});
});
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('appium/types').ManifestData} ManifestData
* @typedef {import('@appium/types').DriverType} DriverType
* @typedef {import('@appium/types').PluginType} PluginType
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
*/
/**
* @template ExtSubCommand
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
*/
/**
* @template T
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
*/
-708
View File
@@ -1,708 +0,0 @@
// @ts-check
import getPort from 'get-port';
import {exec} from 'teen_process';
import {npm, env, fs, tempDir, util} from '@appium/support';
import B from 'bluebird';
import path from 'path';
import resolveFrom from 'resolve-from';
import YAML from 'yaml';
import {
DRIVER_TYPE,
EXT_SUBCOMMAND_INSTALL as INSTALL,
EXT_SUBCOMMAND_LIST as LIST,
EXT_SUBCOMMAND_RUN as RUN,
EXT_SUBCOMMAND_UNINSTALL as UNINSTALL,
PKG_HASHFILE_RELATIVE_PATH,
KNOWN_DRIVERS,
PLUGIN_TYPE,
} from '../../lib/constants';
import {FAKE_DRIVER_DIR, APPIUM_ROOT, resolveFixture} from '../helpers';
import {
installLocalExtension,
runAppiumJson,
runAppiumRaw,
readAppiumArgErrorFixture,
formatAppiumArgErrorOutput,
EXECUTABLE,
} from './e2e-helpers';
const {MANIFEST_RELATIVE_PATH} = env;
const {expect} = chai;
describe('CLI behavior', function () {
/**
* @type {string}
*/
let appiumHome;
const testDriverPath = path.dirname(resolveFixture('test-driver/package.json'));
const testDriverInvalidPeerDepsPath = path.dirname(
resolveFixture('test-driver-invalid-peer-dep/package.json')
);
describe('when appium is already installed', function () {
before(async function () {
appiumHome = await tempDir.openDir();
});
after(async function () {
await fs.rimraf(appiumHome);
});
it('should not duplicate it as peer dependencies for drivers', async function () {
const run = runAppiumJson(appiumHome);
await run([DRIVER_TYPE, INSTALL, 'appium-fake-driver', '--source', 'npm']);
await fs.exists(path.join(appiumHome, 'node_modules', 'appium')).should.eventually.be.false;
});
});
describe('when appium is a dependency of the project in the current working directory', function () {
/** @type {string} */
let hashPath;
/** @type {string} */
let manifestPath;
/** @type {string} */
let appiumHomePkgPath;
/**
* Helper fn
* @returns {Promise<ManifestData>}
*/
async function readManifest() {
const manifest = await fs.readFile(manifestPath, 'utf8');
return YAML.parse(manifest);
}
/**
* Helper fn
* @returns {Promise<string>}
*/
async function readHash() {
return await fs.readFile(hashPath, 'utf8');
}
/**
* @template {CliExtensionSubcommand} ExtSubcommand
* @type {import('lodash').CurriedFunction1<CliExtArgs<ExtSubcommand> | CliArgs, Promise<unknown>>}
*/
let runJson;
after(async function () {
await fs.rimraf(appiumHome);
});
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('when the project is not 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);
// an example package.json referencing appium dependency
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
});
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('after a driver is installed', function () {
before(async function () {
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
});
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(LIST, function () {
/** @type {ExtensionListData} */
let res;
beforeEach(async function () {
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
});
it('should list the driver', function () {
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;
});
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;
});
});
});
});
});
});
describe('Extension Commands', function () {
/**
* @type {(args?: string[]) => Promise<ExtensionListData>}
*/
let runList;
/**
* @type {(args: string[]) => Promise<{ output: string, error?: string }>}
*/
let runRun;
before(async function () {
appiumHome = await tempDir.openDir();
});
after(async function () {
await fs.rimraf(appiumHome);
});
async function clear() {
await fs.rimraf(appiumHome);
await fs.mkdirp(appiumHome);
}
describe('Driver CLI', function () {
/**
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
*/
let runInstall;
/**
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
*/
let runUninstall;
before(function () {
const run = runAppiumJson(appiumHome);
runInstall = async (args) =>
/** @type {ReturnType<typeof runInstall>} */ (await run([DRIVER_TYPE, INSTALL, ...args]));
runUninstall = async (args) =>
/** @type {ReturnType<typeof runUninstall>} */ (
await run([DRIVER_TYPE, UNINSTALL, ...args])
);
runList = async (args = []) =>
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
runRun = async (args) =>
/** @type {ReturnType<typeof runRun>} */ (await run([DRIVER_TYPE, RUN, ...args]));
});
describe(LIST, function () {
it('should list available drivers', async function () {
// note this is raw, not json
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST], {});
for (const d of Object.keys(KNOWN_DRIVERS)) {
stderr.should.match(new RegExp(`${d}.+[not installed]`));
}
});
it('should list available drivers in json format', async function () {
const driverData = await runList();
for (const d of Object.keys(KNOWN_DRIVERS)) {
driverData[d].should.eql({
installed: false,
pkgName: KNOWN_DRIVERS[d],
});
}
});
it('should allow filtering by installed drivers', async function () {
const out = await runList(['--installed']);
out.should.eql({});
});
it('should show updates for installed drivers with --updates', async function () {
const penultimateFakeDriverVersionAsOfRightNow = '3.0.4';
await clear();
await runInstall([
`@appium/fake-driver@${penultimateFakeDriverVersionAsOfRightNow}`,
'--source',
'npm',
]);
const {fake} =
/** @type {Record<string,import('appium/lib/cli/extension-command').InstalledExtensionListData>} */ (
await runList(['--updates'])
);
util.compareVersions(
fake.updateVersion,
'>',
penultimateFakeDriverVersionAsOfRightNow
).should.be.true;
// TODO: this could probably be replaced by looking at updateVersion in the JSON
const {stderr} = await runAppiumRaw(appiumHome, [DRIVER_TYPE, LIST, '--updates'], {});
stderr.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
});
describe('if a driver is not published to npm', function () {
it('should not throw an error', async function () {
await clear();
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
await expect(runList(['--updates'])).not.to.be.rejected;
});
});
});
describe(INSTALL, function () {
beforeEach(async function () {
await clear();
});
it('should install a driver from the list of known drivers', async function () {
const ret = await runInstall(['uiautomator2']);
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
ret.uiautomator2.installType.should.eql('npm');
ret.uiautomator2.installSpec.should.eql('uiautomator2');
const list = await runList(['--installed']);
delete list.uiautomator2.installed;
list.should.eql(ret);
});
it('should install a driver from npm', async function () {
const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('@appium/fake-driver');
const list = await runList(['--installed']);
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from npm and a local driver', async function () {
await runInstall(['@appium/fake-driver', '--source', 'npm']);
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
const list = await runList(['--installed']);
expect(list.fake).to.exist;
expect(list.test).to.exist;
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
});
it('should install _two_ drivers from npm', async function () {
await runInstall(['@appium/fake-driver', '--source', 'npm']);
await runInstall(['appium-uiautomator2-driver', '--source', 'npm']);
const list = await runList(['--installed']);
expect(list.fake).to.exist;
expect(list.uiautomator2).to.exist;
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, 'appium-uiautomator2-driver')).not.to.throw;
});
it('should install a driver from npm with a specific version/tag', async function () {
const currentFakeDriverVersionAsOfRightNow = '3.0.5';
const installSpec = `@appium/fake-driver@${currentFakeDriverVersionAsOfRightNow}`;
const ret = await runInstall([installSpec, '--source', 'npm']);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql(installSpec);
const list = await runList(['--installed']);
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from github', async function () {
const ret = await runInstall([
'appium/appium-fake-driver',
'--source',
'github',
'--package',
'appium-fake-driver',
]);
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('github');
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
const list = await runList(['--installed']);
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a local git repo', async function () {
const ret = await runInstall([
FAKE_DRIVER_DIR,
'--source',
'git',
'--package',
'@appium/fake-driver',
]);
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql(FAKE_DRIVER_DIR);
const list = await runList(['--installed', '--json']);
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a remote git repo', async function () {
const ret = await runInstall([
'git+https://github.com/appium/appium-fake-driver.git',
'--source',
'git',
'--package',
'appium-fake-driver',
]);
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
const list = await runList(['--installed']);
delete list.fake.installed;
list.should.eql(ret);
});
describe('when peer dependencies are invalid', function () {
it('should install the driver anyway', async function () {
const ret = await installLocalExtension(
appiumHome,
DRIVER_TYPE,
testDriverInvalidPeerDepsPath
);
ret.test.pkgName.should.equal('test-driver-invalid-peer-dep');
const list = await runList(['--installed']);
list.test.pkgName.should.equal('test-driver-invalid-peer-dep');
});
it('should warn the user that peer deps are invalid', async function () {
const ret = await runAppiumRaw(
appiumHome,
[DRIVER_TYPE, INSTALL, '--source', 'local', testDriverInvalidPeerDepsPath],
{}
);
ret.stderr.should.match(/may be incompatible with the current version of Appium/i);
ret.stderr.should.match(/successfully installed/i);
});
});
describe('when peer dependencies are valid', function () {
it('should not display a warning', async function () {
const ret = await runAppiumRaw(
appiumHome,
[DRIVER_TYPE, INSTALL, '--source', 'local', testDriverPath],
{}
);
ret.stderr.should.not.match(/may be incompatible with the current version of Appium/i);
ret.stderr.should.match(/successfully installed/i);
});
});
});
describe('local npm install', function () {
describe('when installing from a local npm module', function () {
/** @type {ExtRecord<DriverType>} */
let installResult;
/** @type {ExtensionListData} */
let listResult;
/** @type {string} */
let installPath;
before(async function () {
await clear();
// take advantage of the fact that we know we have fake driver installed as a dependency in
// this module, so we know its local path on disk
installResult = await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
listResult = await runList(['--installed']);
installPath = path.join(appiumHome, 'node_modules', '@appium', 'fake-driver');
});
it('should install a driver from a local npm module', function () {
expect(installResult.fake).to.include({
pkgName: '@appium/fake-driver',
installType: 'local',
installSpec: FAKE_DRIVER_DIR,
});
});
it('should show the installed driver in the list of extensions', function () {
expect(listResult.fake).to.deep.include(installResult.fake);
});
// this fails in CI and I don't know why
it.skip('should create a symlink', async function () {
const srcStat = await fs.lstat(FAKE_DRIVER_DIR);
const destStat = await fs.lstat(appiumHome);
if (srcStat.dev !== destStat.dev) {
return this.skip();
}
// it should be a link! this may be npm-version dependent, but it's worked
// this way for quite awhile
const stat = await fs.lstat(installPath);
expect(stat.isSymbolicLink()).to.be.true;
});
});
});
describe('uninstall', function () {
/** @type {string} */
let pkgName;
beforeEach(async function () {
await clear();
const result = await runInstall(['@appium/fake-driver', '--source', 'npm']);
({pkgName} = result.fake);
});
it('should uninstall a driver based on its driver name', async function () {
// XXX: incorrect if APPIUM_HOME is an extension working copy
const installPath = resolveFrom(appiumHome, pkgName);
let list = await runList(['--installed']);
list.fake.installed.should.be.true;
const uninstall = await runUninstall(['fake']);
uninstall.should.not.have.key('fake');
await fs.exists(installPath).should.eventually.be.false;
});
});
describe('run', function () {
const driverName = 'fake';
before(async function () {
await clear();
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
});
describe('when the driver and script is valid', function () {
const scriptName = 'fake-success';
describe('when the script completes successfully', function () {
it('should result in success', async function () {
const out = await runRun([driverName, scriptName]);
out.should.not.have.property('error');
});
});
describe('when the script fails', function () {
it('should return an error', async function () {
const out = await runRun([driverName, 'fake-error']);
out.should.have.property('error');
});
});
describe('when passed extra arguments', function () {
it('should pass them to the script', async function () {
const out = await runRun([driverName, scriptName, '--foo', '--bar']);
out.should.not.have.property('error');
out.output.should.match(/--foo --bar/);
});
});
});
describe('when the driver is valid but the script is not', function () {
it('should throw an error', async function () {
await expect(runRun([driverName, 'foo'])).to.be.rejectedWith(Error);
});
});
describe('when the driver and script are invalid', function () {
it('should throw an error', async function () {
const driverName = 'foo';
await expect(runRun([driverName, 'bar'])).to.be.rejectedWith(Error);
});
});
});
});
describe('Plugin CLI', function () {
const FAKE_PLUGIN_DIR = path.dirname(require.resolve('@appium/fake-plugin/package.json'));
before(function () {
const run = runAppiumJson(appiumHome);
runList = async (args = []) =>
/** @type {ReturnType<typeof runList>} */ (await run([PLUGIN_TYPE, LIST, ...args]));
runRun = async (args) =>
/** @type {ReturnType<typeof runRun>} */ (await run([PLUGIN_TYPE, RUN, ...args]));
});
describe('run', function () {
before(async function () {
await clear();
await installLocalExtension(appiumHome, PLUGIN_TYPE, FAKE_PLUGIN_DIR);
});
it('should run a valid plugin, valid script, and result in success', async function () {
const pluginName = 'fake';
const scriptName = 'fake-success';
const out = await runRun([pluginName, scriptName, '--json']);
out.should.not.have.property('error');
});
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
const pluginName = 'fake';
const out = await runRun([pluginName, 'fake-error', '--json']);
out.should.have.property('error');
});
it('should take a valid plugin, invalid script, and throw an error', async function () {
const pluginName = 'fake';
await expect(runRun([pluginName, 'foo', '--json'])).to.be.rejectedWith(Error);
});
it('should take an invalid plugin, invalid script, and throw an error', async function () {
await expect(runRun(['foo', 'bar', '--json'])).to.be.rejectedWith(Error);
});
});
});
});
describe('argument parsing', function () {
describe('when the user provides a very long string for an arg accepting a blob or filename', function () {
describe('when the very long string is a JSON blob', function () {
it('should not throw an ENAMETOOLONG exception', async function () {
this.timeout(10000);
const capsArg = JSON.stringify({
'appium:platformName': 'ANDROID',
'appium:platformVersion': '11',
'appium:deviceName':
'Spicy jalapeno bacon ipsum dolor amet deserunt tempor pork belly aliqua drumstick, occaecat dolor venison et labore. Rump meatball pork chop tail. Consequat adipisicing kielbasa occaecat laborum pig. Qui pork chop chicken nostrud boudin fugiat. Proident ut culpa, chuck nulla sunt pastrami ut tri-tip. Buffalo dolore adipisicing, labore venison elit beef fatback kevin burgdoggen tail pancetta filet mignon. Dolor turducken rump, anim kevin sunt exercitation ham filet mignon beef ribs ad officia eiusmod id cillum.',
});
await expect(
exec(
process.execPath,
[
EXECUTABLE,
'-pa=/wd/hub',
'--session-override',
'--local-timezone',
'--relaxed-security',
`--default-capabilities=${capsArg}`,
'--port',
String(await getPort()),
],
{
env: {APPIUM_HOME: appiumHome, PATH: process.env.PATH},
cwd: APPIUM_ROOT,
timeout: 5000,
}
)
).to.be.rejectedWith(Error, /timed out/);
});
});
});
describe('when the user provides an string where a number was expected', function () {
describe('when color output is supported', function () {
it('should output a fancy error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--port=sheep'], {
env: {FORCE_COLOR: '1'},
}),
readAppiumArgErrorFixture('cli/cli-error-output-color.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
describe('when color output is unsupported', function () {
it('should output a colorless yet fancy error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--port=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
});
describe('when the user provides a value for a boolean argument', function () {
it('should output a basic error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--relaxed-security=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output-boolean.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
describe('when the user provides an unknown argument', function () {
it('should output a basic error message', async function () {
const [{stderr: actual}, expected] = await B.all([
runAppiumRaw(appiumHome, ['--pigs=sheep'], {}),
readAppiumArgErrorFixture('cli/cli-error-output-unknown.txt'),
]);
expect(formatAppiumArgErrorOutput(actual)).to.equal(expected);
});
});
});
});
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('appium/types').ManifestData} ManifestData
* @typedef {import('@appium/types').DriverType} DriverType
* @typedef {import('@appium/types').PluginType} PluginType
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
*/
/**
* @template ExtSubCommand
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
*/
/**
* @template T
* @typedef {import('appium/types').ExtRecord<T>} ExtRecord
*/
+79 -1
View File
@@ -3,6 +3,7 @@
import {BaseDriver} from '@appium/base-driver';
import {fs, tempDir} from '@appium/support';
import axios from 'axios';
import {command} from 'webdriver';
import B from 'bluebird';
import _ from 'lodash';
import {createSandbox} from 'sinon';
@@ -131,7 +132,7 @@ describe('FakeDriver via HTTP', function () {
});
describe('cli args handling for empty args', function () {
it('should not recieve user cli args if none passed in', async function () {
it('should not receive user cli args if none passed in', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
const {sessionId} = driver;
try {
@@ -258,6 +259,43 @@ describe('FakeDriver via HTTP', function () {
}
});
it('should allow appium-prefixed caps sent via appium:options', async function () {
// Try with valid capabilities and check that it returns a session ID
const appiumOptsCaps = {
capabilities: {
alwaysMatch: {
'appium:options': {
automationName: 'Fake',
deviceName: 'Fake',
app: TEST_FAKE_APP,
},
platformName: 'Fake',
},
firstMatch: [{}],
},
};
// Create the session
const {status, value, sessionId} = (
await axios.post(testServerBaseSessionUrl, appiumOptsCaps)
).data;
try {
should.not.exist(status); // Test that it's a W3C session by checking that 'status' is not in the response
should.not.exist(sessionId);
value.sessionId.should.be.a.string;
value.should.exist;
value.capabilities.should.deep.equal({
automationName: 'Fake',
platformName: 'Fake',
deviceName: 'Fake',
app: TEST_FAKE_APP,
});
} finally {
// End session
await axios.delete(`${testServerBaseSessionUrl}/${value.sessionId}`);
}
});
it('should reject invalid W3C capabilities and respond with a 400 Bad Parameters error', async function () {
const badW3Ccaps = {
capabilities: {
@@ -407,6 +445,46 @@ describe('FakeDriver via HTTP', function () {
await driver.deleteSession();
}
});
it('should log a single deprecation warning if a deprecated method is used and not overridden by a newMethodMap', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
try {
driver.addCommand(
'deprecated',
command('POST', '/session/:sessionId/deprecated', {
command: 'deprecated',
description: 'Call a deprecated command',
parameters: [],
ref: '',
})
);
driver.addCommand(
'doubleClick',
command('POST', '/session/:sessionId/doubleclick', {
command: 'doubleClick',
description: 'Global double click',
parameters: [],
ref: '',
})
);
await driver
.executeScript('fake: getDeprecatedCommandsCalled', [])
.should.eventually.eql([]);
await driver.deprecated();
await driver.deprecated();
await driver.shake();
// this call should not trigger a deprecation even though deprecated by appium because it's
// overridden as not deprecated by fake driver
await driver.doubleClick();
await driver
.executeScript('fake: getDeprecatedCommandsCalled', [])
.should.eventually.eql(['callDeprecatedCommand', 'mobileShake']);
} finally {
await driver.deleteSession();
}
});
});
});
+1 -1
View File
@@ -84,7 +84,7 @@ export const runAppium = _.curry(_runAppium);
/**
* See {@link runAppiumRaw}.
* @type {AppiumOptsRunner<import('teen_process').TeenProcessExecResult>}
* @type {AppiumOptsRunner<import('teen_process').TeenProcessExecResult<string>>}
*/
async function _runAppiumRaw(appiumHome, args, opts) {
try {
+195
View File
@@ -0,0 +1,195 @@
// @ts-check
import {env, fs, npm, tempDir} from '@appium/support';
import path from 'node:path';
import resolveFrom from 'resolve-from';
import YAML from 'yaml';
import {
DRIVER_TYPE,
EXT_SUBCOMMAND_INSTALL as INSTALL,
EXT_SUBCOMMAND_LIST as LIST,
PKG_HASHFILE_RELATIVE_PATH,
} from '../../lib/constants';
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
const {MANIFEST_RELATIVE_PATH} = env;
const {expect} = chai;
const testDriverPath = path.dirname(resolveFixture('test-driver/package.json'));
describe('when Appium is a dependency of the current project', function () {
/** @type {string} */
let hashPath;
/** @type {string} */
let manifestPath;
/** @type {string} */
let appiumHomePkgPath;
/** @type {string} */
let appiumHome;
before(async function () {
appiumHome = await tempDir.openDir();
});
after(async function () {
await fs.rimraf(appiumHome);
});
/**
* Helper fn
* @returns {Promise<ManifestData>}
*/
async function readManifest() {
const manifest = await fs.readFile(manifestPath, 'utf8');
return YAML.parse(manifest);
}
/**
* Helper fn
* @returns {Promise<string>}
*/
async function readHash() {
return await fs.readFile(hashPath, 'utf8');
}
/**
* @template {CliExtensionSubcommand} ExtSubcommand
* @type {import('lodash').CurriedFunction1<CliExtArgs<ExtSubcommand> | CliArgs, Promise<unknown>>}
*/
let runJson;
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('when the project is not 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);
// an example package.json referencing appium dependency
await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath);
});
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('after a driver is installed', function () {
before(async function () {
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
});
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(LIST, function () {
/** @type {ExtensionListData} */
let res;
beforeEach(async function () {
res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST]));
});
it('should list the driver', function () {
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;
});
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;
});
});
});
});
});
});
/**
* @typedef {import('appium/types').ManifestData} ManifestData
* @typedef {import('./e2e-helpers').CliArgs} CliArgs
* @typedef {import('appium/lib/cli/extension-command').ExtensionListData} ExtensionListData
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
*/
/**
* @template ExtSubCommand
* @typedef {import('./e2e-helpers').CliExtArgs<ExtSubCommand>} CliExtArgs
*/
@@ -0,0 +1,106 @@
import {fs, tempDir} from '@appium/support';
import path from 'node:path';
import YAML from 'yaml';
import {
CACHE_DIR_RELATIVE_PATH,
CURRENT_SCHEMA_REV,
DRIVER_TYPE,
EXT_SUBCOMMAND_LIST as LIST,
} from '../../lib/constants';
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
import {installLocalExtension, runAppiumJson} from './e2e-helpers';
const {expect} = chai;
describe('manifest handling', function () {
/**
* @type {string}
*/
let appiumHome;
/**
* @type {string}
*/
let manifestPath;
/**
* @type {(args?: string[]) => Promise<ExtensionListData>}
*/
let runList;
async function resetAppiumHome() {
await fs.rimraf(appiumHome);
await fs.mkdirp(appiumHome);
}
before(async function () {
appiumHome = await tempDir.openDir();
manifestPath = path.join(appiumHome, CACHE_DIR_RELATIVE_PATH, 'extensions.yaml');
const run = runAppiumJson(appiumHome);
runList = async (args = []) =>
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
});
/**
* @returns {Promise<import('appium/types').AnyManifestDataVersion>}
*/
async function readManifest() {
return YAML.parse(await fs.readFile(manifestPath, 'utf8'));
}
after(async function () {
await fs.rimraf(appiumHome);
});
describe('migration', function () {
beforeEach(async function () {
await resetAppiumHome();
await fs.mkdirp(path.dirname(manifestPath));
});
describe('schema rev update', function () {
beforeEach(async function () {
await fs.copyFile(resolveFixture('manifest/v2-empty.yaml'), manifestPath);
});
it('should update the manifest file to the latest schema revision', async function () {
// note: all we need to do to run the migration is invoke appium, so any command
// would do.
await runList();
const manifest = await readManifest();
expect(manifest.schemaRev).to.equal(CURRENT_SCHEMA_REV);
});
});
describe('v3', function () {
/** @type {import('appium/types').AnyManifestDataVersion} */
let manifest;
// for these tests, we have to use a real extension, because the migration
// only does anything if the extension is actually installed.
before(async function () {
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
const list = await runList();
expect(list.fake).to.exist;
// but the manifest is now at v3, since it's brand new. we will manually downgrade.
let tmpManifest = await readManifest();
tmpManifest.schemaRev = 2;
delete tmpManifest.drivers.fake.installPath;
await fs.writeFile(manifestPath, YAML.stringify(tmpManifest));
tmpManifest = await readManifest();
expect(tmpManifest.schemaRev).to.equal(2);
expect(tmpManifest.drivers.fake.installPath).to.not.exist;
await runList();
manifest = await readManifest();
});
it('should add an "installPath" field to each extension', function () {
expect(manifest.drivers.fake?.installPath).to.be.a('string');
});
it('should update the manifest file to the latest schema revision', function () {
expect(manifest.schemaRev).to.equal(3);
});
});
});
});
+3
View File
@@ -0,0 +1,3 @@
drivers: {}
plugins: {}
schemaRev: 2
@@ -33,6 +33,8 @@ describe('ExtensionConfig', function () {
afterEach(function () {
sandbox.restore();
// avoids a warning about too many listeners, caused by an exit handler in base-driver
process.removeAllListeners('exit');
});
describe('constructor', function () {});
@@ -134,9 +136,8 @@ describe('ExtensionConfig', function () {
installType: 'npm',
appiumVersion: APPIUM_VER,
};
manifest.addExtension(DRIVER_TYPE, extData.pkgName, extData);
manifest.setExtension(DRIVER_TYPE, extData.pkgName, extData);
config = new ExtensionConfig(DRIVER_TYPE, manifest);
delete this._listDataCache;
});
describe('when the extension data is missing an `installSpec` field', function () {
@@ -294,7 +294,7 @@ describe('Manifest', function () {
});
});
describe('addExtension()', function () {
describe('setExtension()', function () {
/** @type {ExtManifest<DriverType>} */
const extData = {
automationName: 'derp',
@@ -309,7 +309,7 @@ describe('Manifest', function () {
};
it('should add a clone of the extension manifest to the internal data object', function () {
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
expect(manifest.getExtensionData(DRIVER_TYPE).foo)
.to.eql(extData)
.and.not.to.equal(extData);
@@ -319,7 +319,7 @@ describe('Manifest', function () {
/** @type {ExtManifest<DriverType>} */
beforeEach(function () {
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
});
it('should rewrite', function () {
@@ -327,7 +327,7 @@ describe('Manifest', function () {
...extData,
automationName: 'BLAAHAH',
};
manifest.addExtension(DRIVER_TYPE, 'foo', expected);
manifest.setExtension(DRIVER_TYPE, 'foo', expected);
expect(manifest.getExtensionData(DRIVER_TYPE).foo).to.eql(expected);
});
});
@@ -338,7 +338,7 @@ describe('Manifest', function () {
});
it('should work anyway', function () {
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
expect(manifest.getExtensionData(DRIVER_TYPE).foo).to.not.have.property('appiumVersion');
});
});
@@ -359,7 +359,7 @@ describe('Manifest', function () {
};
beforeEach(function () {
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
manifest.setExtension(DRIVER_TYPE, 'foo', extData);
});
it('should return all extension data for an extension type', function () {
@@ -575,7 +575,7 @@ describe('Manifest', function () {
describe('when the driver is registered', function () {
beforeEach(function () {
// @ts-expect-error
manifest.addExtension(DRIVER_TYPE, 'foo', {});
manifest.setExtension(DRIVER_TYPE, 'foo', {});
});
it('should return `true`', function () {
@@ -594,7 +594,7 @@ describe('Manifest', function () {
describe('when the plugin is registered', function () {
beforeEach(function () {
// @ts-expect-error
manifest.addExtension(PLUGIN_TYPE, 'foo', {});
manifest.setExtension(PLUGIN_TYPE, 'foo', {});
});
it('should return `true`', function () {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
+42
View File
@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [9.2.1](https://github.com/appium/appium/compare/@appium/base-driver@9.2.0...@appium/base-driver@9.2.1) (2023-01-13)
**Note:** Version bump only for package @appium/base-driver
# [9.2.0](https://github.com/appium/appium/compare/@appium/base-driver@9.1.0...@appium/base-driver@9.2.0) (2023-01-13)
### Bug Fixes
* **appium:** inability to find automationName inside appium:options ([#17966](https://github.com/appium/appium/issues/17966)) ([23224cf](https://github.com/appium/appium/commit/23224cf002b7dd9e4e5d8426b4bbe1cb28f62605))
* **base-driver:** add missing dependency @appium/types ([edde488](https://github.com/appium/appium/commit/edde4882f0633c032a077861599c337132092daf)), closes [#18006](https://github.com/appium/appium/issues/18006)
* **base-driver:** move setClipboard deprecation to correct place ([ad1dce5](https://github.com/appium/appium/commit/ad1dce5545acb61ce79e489783a95f2caebd753c))
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
### Features
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
# [9.1.0](https://github.com/appium/appium/compare/@appium/base-driver@9.0.0...@appium/base-driver@9.1.0) (2022-12-21)
### Bug Fixes
- add 'webSocketUrl' as standard cap for bidi support ([#17936](https://github.com/appium/appium/issues/17936)) ([0e195ca](https://github.com/appium/appium/commit/0e195caafefe911586ee2f8be4ae33d402b2ba40))
- **types:** update definitelytyped ([172fcb9](https://github.com/appium/appium/commit/172fcb9aff0afe5295650566c4fb92d0894bf879))
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
### Features
- **base:** add get computed role and label in W3C v2 ([#17928](https://github.com/appium/appium/issues/17928)) ([316ecca](https://github.com/appium/appium/commit/316ecca8b1f8e52806867a15ba8524a504751460))
# [9.0.0](https://github.com/appium/appium/compare/@appium/base-driver@8.7.3...@appium/base-driver@9.0.0) (2022-12-14)
### Bug Fixes
@@ -104,6 +104,7 @@ export const STANDARD_CAPS = Object.freeze(
'setWindowRect',
'timeouts',
'unhandledPromptBehavior',
'webSocketUrl',
])
)
);
@@ -224,7 +225,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
// In the future, reject 'firstMatch' argument if its array did not have one or more entries (#3.2)
if (allFirstMatchCaps.length === 0) {
log.warn(
`The firstMatch array in the given capabilities has no entries. Adding an empty entry fo rnow, ` +
`The firstMatch array in the given capabilities has no entries. Adding an empty entry for now, ` +
`but it will require one or more entries as W3C spec.`
);
allFirstMatchCaps.push({});
@@ -348,7 +349,9 @@ function promoteAppiumOptions(originalCaps) {
}
if (!_.isPlainObject(appiumOptions)) {
throw new errors.SessionNotCreatedError(`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`);
throw new errors.SessionNotCreatedError(
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
);
}
if (_.isEmpty(appiumOptions)) {
return obj;
@@ -377,7 +380,7 @@ function promoteAppiumOptions(originalCaps) {
};
const preprocessedOptions = _(appiumOptions)
.mapKeys((value, key) => verifyIfAcceptable(key))
.mapKeys((value, key) => shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key)
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
.value();
// warn if we are going to overwrite any keys on the base caps object
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
@@ -388,8 +391,8 @@ function promoteAppiumOptions(originalCaps) {
);
}
return _.cloneDeep({
...(_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)),
...preprocessedOptions
..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP),
...preprocessedOptions,
});
};
@@ -30,6 +30,7 @@ export function SessionMixin(Base) {
}
/**
* Returns capabilities for the session and event history (if applicable)
* @returns {Promise<SingularSessionData>}
*/
async getSession() {
@@ -1,12 +1,7 @@
/* eslint-disable require-await */
/* eslint-disable no-unused-vars */
import {
validateCaps,
PREFIXED_APPIUM_OPTS_CAP,
processCapabilities,
promoteAppiumOptions,
} from './capabilities';
import {validateCaps, PREFIXED_APPIUM_OPTS_CAP, processCapabilities} from './capabilities';
import {DriverCore} from './core';
import {util} from '@appium/support';
import B from 'bluebird';
@@ -298,7 +293,7 @@ export class BaseDriverCore extends DriverCore {
let caps;
try {
caps = processCapabilities(
promoteAppiumOptions(originalCaps),
originalCaps,
this._desiredCapConstraints,
this.shouldValidateCaps
);
+1
View File
@@ -34,6 +34,7 @@ export {
processCapabilities,
isStandardCap,
validateCaps,
promoteAppiumOptions,
} from './basedriver/capabilities';
// Web socket helpers
@@ -18,6 +18,9 @@ const CREATE_SESSION_COMMAND = 'createSession';
const DELETE_SESSION_COMMAND = 'deleteSession';
const GET_STATUS_COMMAND = 'getStatus';
/** type {Set<string>} */
const deprecatedCommandsLogged = new Set();
function determineProtocol(createSessionArgs) {
return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
}
@@ -262,6 +265,16 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
let currentProtocol = extractProtocol(driver, req.params.sessionId);
try {
// if the route accessed is deprecated, log a warning
if (spec.deprecated && !deprecatedCommandsLogged.has(spec.command)) {
deprecatedCommandsLogged.add(spec.command);
getLogger(driver, req.params.sessionId).warn(
`Command '${spec.command}' has been deprecated and will be removed in a future ` +
`version of Appium or your driver/plugin. Please use a different method or contact the ` +
`driver/plugin author to add explicit support for the command before it is removed`
);
}
// if this is a session command but we don't have a session,
// error out early (especially before proxying)
if (isSessCmd && !driver.sessionExists(req.params.sessionId)) {
@@ -512,4 +525,5 @@ export {
CREATE_SESSION_COMMAND,
DELETE_SESSION_COMMAND,
GET_STATUS_COMMAND,
deprecatedCommandsLogged,
};
+99 -97
View File
@@ -68,10 +68,10 @@ const METHOD_MAP = /** @type {const} */ ({
},
},
'/session/:sessionId/timeouts/async_script': {
POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}},
POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}, deprecated: true},
},
'/session/:sessionId/timeouts/implicit_wait': {
POST: {command: 'implicitWait', payloadParams: {required: ['ms']}},
POST: {command: 'implicitWait', payloadParams: {required: ['ms']}, deprecated: true},
},
// JSONWP
'/session/:sessionId/window_handle': {
@@ -117,25 +117,25 @@ const METHOD_MAP = /** @type {const} */ ({
GET: {command: 'getScreenshot'},
},
'/session/:sessionId/ime/available_engines': {
GET: {command: 'availableIMEEngines'},
GET: {command: 'availableIMEEngines', deprecated: true},
},
'/session/:sessionId/ime/active_engine': {
GET: {command: 'getActiveIMEEngine'},
GET: {command: 'getActiveIMEEngine', deprecated: true},
},
'/session/:sessionId/ime/activated': {
GET: {command: 'isIMEActivated'},
GET: {command: 'isIMEActivated', deprecated: true},
},
'/session/:sessionId/ime/deactivate': {
POST: {command: 'deactivateIMEEngine'},
POST: {command: 'deactivateIMEEngine', deprecated: true},
},
'/session/:sessionId/ime/activate': {
POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}},
POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}, deprecated: true},
},
'/session/:sessionId/frame': {
POST: {command: 'setFrame', payloadParams: {required: ['id']}},
},
'/session/:sessionId/frame/parent': {
POST: {},
POST: {command: 'switchToParentFrame'},
},
'/session/:sessionId/window': {
GET: {command: 'getWindowHandle'},
@@ -162,12 +162,11 @@ const METHOD_MAP = /** @type {const} */ ({
DELETE: {command: 'closeWindow'},
},
'/session/:sessionId/window/:windowhandle/size': {
GET: {command: 'getWindowSize'},
POST: {},
GET: {command: 'getWindowSize', deprecated: true},
},
'/session/:sessionId/window/:windowhandle/position': {
POST: {},
GET: {},
POST: {deprecated: true},
GET: {deprecated: true},
},
'/session/:sessionId/window/:windowhandle/maximize': {
POST: {command: 'maximizeWindow'},
@@ -222,7 +221,7 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {command: 'click'},
},
'/session/:sessionId/element/:elementId/submit': {
POST: {command: 'submit'},
POST: {command: 'submit', deprecated: true},
},
'/session/:sessionId/element/:elementId/text': {
GET: {command: 'getText'},
@@ -246,7 +245,7 @@ const METHOD_MAP = /** @type {const} */ ({
},
},
'/session/:sessionId/keys': {
POST: {command: 'keys', payloadParams: {required: ['value']}},
POST: {command: 'keys', payloadParams: {required: ['value']}, deprecated: true},
},
'/session/:sessionId/element/:elementId/name': {
GET: {command: 'getName'},
@@ -264,19 +263,19 @@ const METHOD_MAP = /** @type {const} */ ({
GET: {command: 'getAttribute'},
},
'/session/:sessionId/element/:elementId/equals/:otherId': {
GET: {command: 'equalsElement'},
GET: {command: 'equalsElement', deprecated: true},
},
'/session/:sessionId/element/:elementId/displayed': {
GET: {command: 'elementDisplayed'},
},
'/session/:sessionId/element/:elementId/location': {
GET: {command: 'getLocation'},
GET: {command: 'getLocation', deprecated: true},
},
'/session/:sessionId/element/:elementId/location_in_view': {
GET: {command: 'getLocationInView'},
GET: {command: 'getLocationInView', deprecated: true},
},
'/session/:sessionId/element/:elementId/size': {
GET: {command: 'getSize'},
GET: {command: 'getSize', deprecated: true},
},
'/session/:sessionId/element/:elementId/shadow': {
GET: {command: 'elementShadowRoot'},
@@ -303,6 +302,14 @@ const METHOD_MAP = /** @type {const} */ ({
payloadParams: {required: ['orientation']},
},
},
// w3c v2 https://www.w3.org/TR/webdriver2/#get-computed-role
'session/:sessionId/element/:elementId/computedrole': {
GET: {command: 'getComputedRole'},
},
// W3C v2 https://www.w3.org/TR/webdriver2/#get-computed-label
'session/:sessionId/element/:elementId/computedlabel': {
GET: {command: 'getComputedLabel'},
},
'/session/:sessionId/rotation': {
GET: {command: 'getRotation'},
POST: {command: 'setRotation', payloadParams: {required: ['x', 'y', 'z']}},
@@ -311,34 +318,35 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {
command: 'moveTo',
payloadParams: {optional: ['element', 'xoffset', 'yoffset']},
deprecated: true,
},
},
'/session/:sessionId/click': {
POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}},
POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}, deprecated: true},
},
'/session/:sessionId/buttondown': {
POST: {command: 'buttonDown', payloadParams: {optional: ['button']}},
POST: {command: 'buttonDown', payloadParams: {optional: ['button']}, deprecated: true},
},
'/session/:sessionId/buttonup': {
POST: {command: 'buttonUp', payloadParams: {optional: ['button']}},
POST: {command: 'buttonUp', payloadParams: {optional: ['button']}, deprecated: true},
},
'/session/:sessionId/doubleclick': {
POST: {command: 'doubleClick'},
POST: {command: 'doubleClick', deprecated: true},
},
'/session/:sessionId/touch/click': {
POST: {command: 'click', payloadParams: {required: ['element']}},
POST: {command: 'click', payloadParams: {required: ['element']}, deprecated: true},
},
'/session/:sessionId/touch/down': {
POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}},
POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}, deprecated: true},
},
'/session/:sessionId/touch/up': {
POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}},
POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}, deprecated: true},
},
'/session/:sessionId/touch/move': {
POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}},
POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}, deprecated: true},
},
'/session/:sessionId/touch/scroll': {
POST: {},
POST: {deprecated: true},
},
'/session/:sessionId/touch/doubleclick': {
POST: {},
@@ -348,7 +356,7 @@ const METHOD_MAP = /** @type {const} */ ({
DELETE: {command: 'releaseActions'},
},
'/session/:sessionId/touch/longclick': {
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}},
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}, deprecated: true},
},
'/session/:sessionId/touch/flick': {
POST: {
@@ -356,6 +364,7 @@ const METHOD_MAP = /** @type {const} */ ({
payloadParams: {
optional: ['element', 'xspeed', 'yspeed', 'xoffset', 'yoffset', 'speed'],
},
deprecated: true,
},
},
'/session/:sessionId/location': {
@@ -363,28 +372,28 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {command: 'setGeoLocation', payloadParams: {required: ['location']}},
},
'/session/:sessionId/local_storage': {
GET: {},
POST: {},
DELETE: {},
GET: {deprecated: true},
POST: {deprecated: true},
DELETE: {deprecated: true},
},
'/session/:sessionId/local_storage/key/:key': {
GET: {},
DELETE: {},
GET: {deprecated: true},
DELETE: {deprecated: true},
},
'/session/:sessionId/local_storage/size': {
GET: {},
GET: {deprecated: true},
},
'/session/:sessionId/session_storage': {
GET: {},
POST: {},
DELETE: {},
GET: {deprecated: true},
POST: {deprecated: true},
DELETE: {deprecated: true},
},
'/session/:sessionId/session_storage/key/:key': {
GET: {},
DELETE: {},
GET: {deprecated: true},
DELETE: {deprecated: true},
},
'/session/:sessionId/session_storage/size': {
GET: {},
GET: {deprecated: true},
},
// Selenium 4 clients
'/session/:sessionId/se/log': {
@@ -417,7 +426,7 @@ const METHOD_MAP = /** @type {const} */ ({
GET: {command: 'getContexts'},
},
'/session/:sessionId/element/:elementId/pageIndex': {
GET: {command: 'getPageIndex'},
GET: {command: 'getPageIndex', deprecated: true},
},
'/session/:sessionId/network_connection': {
GET: {command: 'getNetworkConnection'},
@@ -430,12 +439,14 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {
command: 'performTouch',
payloadParams: {wrap: 'actions', required: ['actions']},
deprecated: true,
},
},
'/session/:sessionId/touch/multi/perform': {
POST: {
command: 'performMultiAction',
payloadParams: {required: ['actions'], optional: ['elementId']},
deprecated: true,
},
},
'/session/:sessionId/receive_async_response': {
@@ -445,35 +456,37 @@ const METHOD_MAP = /** @type {const} */ ({
},
},
'/session/:sessionId/appium/device/shake': {
POST: {command: 'mobileShake'},
POST: {command: 'mobileShake', deprecated: true},
},
'/session/:sessionId/appium/device/system_time': {
GET: {command: 'getDeviceTime', payloadParams: {optional: ['format']}},
POST: {command: 'getDeviceTime', payloadParams: {optional: ['format']}},
},
'/session/:sessionId/appium/device/lock': {
POST: {command: 'lock', payloadParams: {optional: ['seconds']}},
POST: {command: 'lock', payloadParams: {optional: ['seconds']}, deprecated: true},
},
'/session/:sessionId/appium/device/unlock': {
POST: {command: 'unlock'},
POST: {command: 'unlock', deprecated: true},
},
'/session/:sessionId/appium/device/is_locked': {
POST: {command: 'isLocked'},
POST: {command: 'isLocked', deprecated: true},
},
'/session/:sessionId/appium/start_recording_screen': {
POST: {
command: 'startRecordingScreen',
payloadParams: {optional: ['options']},
deprecated: true,
},
},
'/session/:sessionId/appium/stop_recording_screen': {
POST: {
command: 'stopRecordingScreen',
payloadParams: {optional: ['options']},
deprecated: true,
},
},
'/session/:sessionId/appium/performanceData/types': {
POST: {command: 'getPerformanceDataTypes'},
POST: {command: 'getPerformanceDataTypes', deprecated: true},
},
'/session/:sessionId/appium/getPerformanceData': {
POST: {
@@ -482,70 +495,68 @@ const METHOD_MAP = /** @type {const} */ ({
required: ['packageName', 'dataType'],
optional: ['dataReadTimeout'],
},
deprecated: true,
},
},
'/session/:sessionId/appium/device/press_keycode': {
POST: {
command: 'pressKeyCode',
payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/long_press_keycode': {
POST: {
command: 'longPressKeyCode',
payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/finger_print': {
POST: {
command: 'fingerprint',
payloadParams: {required: ['fingerprintId']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/send_sms': {
POST: {
command: 'sendSMS',
payloadParams: {required: ['phoneNumber', 'message']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/gsm_call': {
POST: {
command: 'gsmCall',
payloadParams: {required: ['phoneNumber', 'action']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/gsm_signal': {
POST: {
command: 'gsmSignal',
payloadParams: {
validate: (jsonObj) =>
!util.hasValue(jsonObj.signalStrength) &&
!util.hasValue(jsonObj.signalStrengh) &&
'we require one of "signalStrength" or "signalStrengh" params',
optional: ['signalStrength', 'signalStrengh'],
// backward-compatible. sonObj.signalStrength can be 0
makeArgs: (jsonObj) => [
util.hasValue(jsonObj.signalStrength) ? jsonObj.signalStrength : jsonObj.signalStrengh,
],
},
payloadParams: {required: ['signalStrength']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/gsm_voice': {
POST: {command: 'gsmVoice', payloadParams: {required: ['state']}},
POST: {command: 'gsmVoice', payloadParams: {required: ['state']}, deprecated: true},
},
'/session/:sessionId/appium/device/power_capacity': {
POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}},
POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}, deprecated: true},
},
'/session/:sessionId/appium/device/power_ac': {
POST: {command: 'powerAC', payloadParams: {required: ['state']}},
POST: {command: 'powerAC', payloadParams: {required: ['state']}, deprecated: true},
},
'/session/:sessionId/appium/device/network_speed': {
POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}},
POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}, deprecated: true},
},
'/session/:sessionId/appium/device/keyevent': {
POST: {
command: 'keyevent',
payloadParams: {required: ['keycode'], optional: ['metastate']},
deprecated: true,
},
},
'/session/:sessionId/appium/device/rotate': {
@@ -555,13 +566,14 @@ const METHOD_MAP = /** @type {const} */ ({
required: ['x', 'y', 'radius', 'rotation', 'touchCount', 'duration'],
optional: ['element'],
},
deprecated: true,
},
},
'/session/:sessionId/appium/device/current_activity': {
GET: {command: 'getCurrentActivity'},
GET: {command: 'getCurrentActivity', deprecated: true},
},
'/session/:sessionId/appium/device/current_package': {
GET: {command: 'getCurrentPackage'},
GET: {command: 'getCurrentPackage', deprecated: true},
},
//region Applications Management
'/session/:sessionId/appium/device/install_app': {
@@ -620,6 +632,7 @@ const METHOD_MAP = /** @type {const} */ ({
payloadParams: {
required: [['appId'], ['bundleId']],
},
deprecated: true,
},
},
//endregion
@@ -642,19 +655,19 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {command: 'pullFolder', payloadParams: {required: ['path']}},
},
'/session/:sessionId/appium/device/toggle_airplane_mode': {
POST: {command: 'toggleFlightMode'},
POST: {command: 'toggleFlightMode', deprecated: true},
},
'/session/:sessionId/appium/device/toggle_data': {
POST: {command: 'toggleData'},
POST: {command: 'toggleData', deprecated: true},
},
'/session/:sessionId/appium/device/toggle_wifi': {
POST: {command: 'toggleWiFi'},
POST: {command: 'toggleWiFi', deprecated: true},
},
'/session/:sessionId/appium/device/toggle_location_services': {
POST: {command: 'toggleLocationServices'},
POST: {command: 'toggleLocationServices', deprecated: true},
},
'/session/:sessionId/appium/device/open_notifications': {
POST: {command: 'openNotifications'},
POST: {command: 'openNotifications', deprecated: true},
},
'/session/:sessionId/appium/device/start_activity': {
POST: {
@@ -671,77 +684,63 @@ const METHOD_MAP = /** @type {const} */ ({
'dontStopAppOnReset',
],
},
deprecated: true,
},
},
'/session/:sessionId/appium/device/system_bars': {
GET: {command: 'getSystemBars'},
GET: {command: 'getSystemBars', deprecated: true},
},
'/session/:sessionId/appium/device/display_density': {
GET: {command: 'getDisplayDensity'},
GET: {command: 'getDisplayDensity', deprecated: true},
},
'/session/:sessionId/appium/simulator/touch_id': {
POST: {command: 'touchId', payloadParams: {required: ['match']}},
POST: {command: 'touchId', payloadParams: {required: ['match']}, deprecated: true},
},
'/session/:sessionId/appium/simulator/toggle_touch_id_enrollment': {
POST: {
command: 'toggleEnrollTouchId',
payloadParams: {optional: ['enabled']},
deprecated: true,
},
},
'/session/:sessionId/appium/app/launch': {
POST: {command: 'launchApp'},
POST: {command: 'launchApp', deprecated: true},
},
'/session/:sessionId/appium/app/close': {
POST: {command: 'closeApp'},
POST: {command: 'closeApp', deprecated: true},
},
'/session/:sessionId/appium/app/reset': {
POST: {command: 'reset'},
POST: {command: 'reset', deprecated: true},
},
'/session/:sessionId/appium/app/background': {
POST: {command: 'background', payloadParams: {required: ['seconds']}},
POST: {command: 'background', payloadParams: {required: ['seconds']}, deprecated: true},
},
'/session/:sessionId/appium/app/end_test_coverage': {
POST: {
command: 'endCoverage',
payloadParams: {required: ['intent', 'path']},
deprecated: true,
},
},
'/session/:sessionId/appium/app/strings': {
POST: {
command: 'getStrings',
payloadParams: {optional: ['language', 'stringFile']},
deprecated: true,
},
},
'/session/:sessionId/appium/element/:elementId/value': {
POST: {
command: 'setValueImmediate',
payloadParams: {
validate: (jsonObj) =>
!util.hasValue(jsonObj.value) &&
!util.hasValue(jsonObj.text) &&
'we require one of "text" or "value" params',
optional: ['value', 'text'],
// We want to either a value (old JSONWP) or a text (new W3C) parameter,
// but only send one of them to the command (not both).
// Prefer 'value' since it's more backward-compatible.
makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text],
},
payloadParams: {required: ['text']},
deprecated: true,
},
},
'/session/:sessionId/appium/element/:elementId/replace_value': {
POST: {
command: 'replaceValue',
payloadParams: {
validate: (jsonObj) =>
!util.hasValue(jsonObj.value) &&
!util.hasValue(jsonObj.text) &&
'we require one of "text" or "value" params',
optional: ['value', 'text'],
// We want to either a value (old JSONWP) or a text (new W3C) parameter,
// but only send one of them to the command (not both).
// Prefer 'value' since it's more backward-compatible.
makeArgs: (jsonObj) => [jsonObj.value ?? jsonObj.text ?? ''],
},
payloadParams: {required: ['text']},
deprecated: true,
},
},
'/session/:sessionId/appium/settings': {
@@ -752,6 +751,7 @@ const METHOD_MAP = /** @type {const} */ ({
POST: {
command: 'receiveAsyncResponse',
payloadParams: {required: ['response']},
deprecated: true,
},
},
'/session/:sessionId/appium/events': {
@@ -849,6 +849,7 @@ const METHOD_MAP = /** @type {const} */ ({
required: ['content'],
optional: ['contentType', 'label'],
},
deprecated: true,
},
},
'/session/:sessionId/appium/device/get_clipboard': {
@@ -857,6 +858,7 @@ const METHOD_MAP = /** @type {const} */ ({
payloadParams: {
optional: ['contentType'],
},
deprecated: true,
},
},
+11 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "9.0.0",
"version": "9.2.1",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -32,25 +32,27 @@
"index.d.ts",
"lib",
"static",
"build"
"build",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
"test:e2e": "mocha --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
"test:e2e": "mocha -p --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
"test:smoke": "node ./index.js",
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^3.0.0",
"@appium/support": "^3.1.1",
"@appium/types": "^0.8.1",
"@colors/colors": "1.5.0",
"@types/async-lock": "1.3.0",
"@types/bluebird": "3.5.38",
"@types/express": "4.17.14",
"@types/express": "4.17.15",
"@types/method-override": "0.0.32",
"@types/serve-favicon": "2.5.3",
"async-lock": "1.4.0",
"asyncbox": "2.9.4",
"axios": "1.2.1",
"axios": "1.2.2",
"bluebird": "3.7.2",
"body-parser": "1.20.1",
"es6-error": "4.1.1",
@@ -62,7 +64,7 @@
"morgan": "1.10.0",
"serve-favicon": "2.5.0",
"source-map-support": "0.5.21",
"type-fest": "3.3.0",
"type-fest": "3.5.1",
"validate.js": "0.13.1"
},
"engines": {
@@ -72,8 +74,8 @@
"publishConfig": {
"access": "public"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
"typedoc": {
"entryPoint": "./build/lib/index.js"
"entryPoint": "./lib/index.js"
}
}
@@ -1,12 +1,5 @@
import {BaseDriver, server, routeConfiguringFunction, PREFIXED_APPIUM_OPTS_CAP} from '../../../lib';
import {
driverE2ETestSuite,
getTestPort,
TEST_HOST,
createSessionHelpers,
} from '@appium/driver-test-support';
const {expect} = chai;
import {BaseDriver} from '../../../lib';
import {driverE2ETestSuite} from '@appium/driver-test-support';
const DEFAULT_CAPS = {
platformName: 'iOS',
@@ -15,58 +8,6 @@ const DEFAULT_CAPS = {
driverE2ETestSuite(BaseDriver, DEFAULT_CAPS);
describe('BaseDriver', function () {
// only run this test on basedriver, not other drivers which also use these tests, since we
// don't want them to try and start sessions with these random capabilities that are
// necessary to test the appium options logic
describe('special appium:options capability', function () {
/** @type {SessionHelpers['startSession']} */
let startSession;
/** @type {SessionHelpers['endSession']} */
let endSession;
/** @type {import('@appium/types').AppiumServer} */
let baseServer;
/** @type {BaseDriver} */
let d;
before(async function () {
const port = await getTestPort();
d = new BaseDriver({port, address: TEST_HOST});
baseServer = await server({
routeConfiguringFunction: routeConfiguringFunction(d),
port,
hostname: TEST_HOST,
});
({startSession, endSession} = createSessionHelpers(port));
});
after(async function () {
await baseServer.close();
});
it('should be able to start a session with caps held in appium:options', async function () {
const ret = await startSession({
capabilities: {
alwaysMatch: {
platformName: 'iOS',
[PREFIXED_APPIUM_OPTS_CAP]: {
platformVersion: '11.4',
'appium:deviceName': 'iPhone 11',
},
},
},
});
try {
expect(d.opts.platformVersion).to.equal('11.4');
expect(d.opts.deviceName).to.equal('iPhone 11');
} finally {
await endSession(ret.sessionId);
}
});
});
});
/**
* @typedef {import('@appium/driver-test-support').SessionHelpers} SessionHelpers
*/
@@ -35,7 +35,7 @@ describe('Protocol', function () {
}
let hash = shasum.digest('hex').substring(0, 8);
// Modify the hash whenever the protocol has intentionally been modified.
hash.should.equal('736bf246');
hash.should.equal('484810b0');
});
});
+2 -5
View File
@@ -1,5 +1,5 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"paths": {
@@ -10,8 +10,5 @@
"checkJs": true
},
"include": ["lib"],
"references": [
{"path": "../support"},
{"path": "../types"}
]
"references": [{"path": "../support"}, {"path": "../types"}]
}
+23
View File
@@ -3,6 +3,29 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.1.0...@appium/base-plugin@2.1.1) (2023-01-13)
**Note:** Version bump only for package @appium/base-plugin
# [2.1.0](https://github.com/appium/appium/compare/@appium/base-plugin@2.0.1...@appium/base-plugin@2.1.0) (2023-01-13)
### Features
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
## [2.0.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.0.0...@appium/base-plugin@2.0.1) (2022-12-21)
**Note:** Version bump only for package @appium/base-plugin
# [2.0.0](https://github.com/appium/appium/compare/@appium/base-plugin@1.10.5...@appium/base-plugin@2.0.0) (2022-12-14)
### Bug Fixes
+20 -16
View File
@@ -1,7 +1,17 @@
{
"name": "@appium/base-plugin",
"version": "2.0.0",
"version": "2.1.1",
"description": "The base plugin used to create Appium 2.0 plugins",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"homepage": "https://appium.io",
"bugs": {
"url": "https://github.com/appium/appium/issues"
@@ -20,7 +30,8 @@
"files": [
"lib",
"build",
"index.js"
"index.js",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -28,27 +39,20 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^3.0.0"
"@appium/support": "^3.1.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"publishConfig": {
"access": "public"
},
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
"tags": [
"appium"
],
"typedoc": {
"entryPoint": "./build/lib/plugin.js"
},
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
]
"entryPoint": "./lib/plugin.js"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"checkJs": true,
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.0.3](https://github.com/appium/appium/compare/@appium/doctor@2.0.2...@appium/doctor@2.0.3) (2023-01-13)
**Note:** Version bump only for package @appium/doctor
## [2.0.2](https://github.com/appium/appium/compare/@appium/doctor@2.0.1...@appium/doctor@2.0.2) (2023-01-13)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.10.20 ([29bf77c](https://github.com/appium/appium/commit/29bf77cf5a623c7417d9c996814498f76cead2a3))
* **doctor:** update dependency appium-adb to v9.10.21 ([ac2c735](https://github.com/appium/appium/commit/ac2c73596810919dcd1c6d294e8c9496eb91e0b9))
## [2.0.1](https://github.com/appium/appium/compare/@appium/doctor@2.0.0...@appium/doctor@2.0.1) (2022-12-21)
### Bug Fixes
- **doctor:** update dependency appium-adb to v9.10.19 ([2cb1af4](https://github.com/appium/appium/commit/2cb1af4357e0e867c28c7045825140d45fa02393))
# [2.0.0](https://github.com/appium/appium/compare/@appium/doctor@1.16.28...@appium/doctor@2.0.0) (2022-12-14)
### Bug Fixes
-2
View File
@@ -2,8 +2,6 @@ appium-doctor
===================
Attempts to diagnose and fix common Node, iOS and Android configuration issues before starting Appium.
[![Build Status](https://travis-ci.org/appium/appium-doctor.svg?branch=master)](https://travis-ci.org/appium/appium-doctor)
### Install
```
+3
View File
@@ -7,6 +7,9 @@ import '@colors/colors';
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';
import log from './logger';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
const javaHome = system.isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME';
+3
View File
@@ -7,6 +7,9 @@ import {DoctorCheck, FixSkippedError} from './doctor';
import log from './logger';
import {fixIt} from './prompt';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
class DirCheck extends DoctorCheck {
+3
View File
@@ -4,6 +4,9 @@ import {fs, system} from '@appium/support';
import path from 'path';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
// Check PATH binaries
+30
View File
@@ -7,15 +7,29 @@ const {version} = fs.readPackageJsonFrom(__dirname);
class FixSkippedError extends Error {}
/**
* Create interface for other Doctors
*/
class DoctorCheck {
/**
* @param {DoctorOpts} opts
*/
constructor(opts = {}) {
this.autofix = !!opts.autofix;
}
/**
* Every doctor diagnose the symptoms
* @throws {Error}
*/
diagnose() {
throw new Error('Not Implemented!');
}
/**
* Every doctor suggest the solutions to fix the sickness
* @throws {Error}
*/
fix() {
// return string for manual fixes.
throw new Error('Not Implemented!');
@@ -24,17 +38,28 @@ class DoctorCheck {
class Doctor {
constructor() {
/**
* All the sub check goes here after register
* @type {DoctorCheck[]}
*/
this.checks = [];
this.checkOptionals = [];
this.toFix = [];
this.toFixOptionals = [];
}
/**
* Register all the sub check and combine them together
* @param {DoctorCheck[] | DoctorCheck} checks
*/
register(checks) {
checks = Array.isArray(checks) ? checks : [checks];
this.checks = this.checks.concat(checks);
}
/**
* The doctor shows the report
*/
async diagnose() {
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
this.toFix = [];
@@ -211,3 +236,8 @@ class Doctor {
}
export {Doctor, DoctorCheck, FixSkippedError};
/**
* @typedef DoctorOpts
* @property {boolean?} autofix
*/
+16
View File
@@ -6,6 +6,9 @@ import androidChecks from './android';
import devChecks from './dev';
import demoChecks from './demo';
/**
* @type {DoctorGroup}
*/
let checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks};
let newDoctor = (opts) => {
@@ -19,3 +22,16 @@ let newDoctor = (opts) => {
};
export default newDoctor;
/**
* @typedef {import('./doctor').DoctorCheck[]} DoctorCheckList
*/
/**
* @typedef DoctorGroup - Contain a group of Doctors
* @property {DoctorCheckList} generalChecks - Check AppiumHome, NodeBinary, NodeVersion, ffmpeg, mjpeg-consumer
* @property {DoctorCheckList} iosChecks - Check if iOS toolchains are installed
* @property {DoctorCheckList} androidChecks - Check if Android toolchains are installed
* @property {DoctorCheckList} devChecks - Check Path Binary and Android SDKs
* @property {DoctorCheckList} demoChecks - Check /tmp/appium-doctor/demo/*
*/
+20 -2
View File
@@ -6,9 +6,16 @@ import {util, env} from '@appium/support';
import {EOL} from 'os';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
class AppiumHomeCheck extends DoctorCheck {
/**
*
* @returns {UtilsResult}
*/
async diagnose() {
return ok(`APPIUM_HOME is ${await env.resolveAppiumHome()}`);
}
@@ -59,6 +66,10 @@ class NodeVersionCheck extends DoctorCheck {
checks.push(new NodeVersionCheck());
class OptionalFfmpegCommandCheck extends DoctorCheck {
/**
*
* @returns {Promise<UtilsResult>}
*/
async diagnose() {
const ffmpegPath = await resolveExecutablePath('ffmpeg');
return ffmpegPath
@@ -69,8 +80,11 @@ class OptionalFfmpegCommandCheck extends DoctorCheck {
)
: nokOptional('ffmpeg cannot be found');
}
// eslint-disable-next-line require-await
async fix() {
/**
*
* @returns {string}
*/
fix() {
return `${
'ffmpeg'.bold
} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`;
@@ -107,3 +121,7 @@ export {
OptionalMjpegConsumerCommandCheck,
};
export default checks;
/**
* @typedef {import('./utils').UtilsResult} UtilsResult
*/
+3
View File
@@ -7,6 +7,9 @@ import {fixIt} from './prompt';
import EnvVarAndPathCheck from './env';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
let fixes = {};
+8
View File
@@ -1,5 +1,8 @@
import {inquirer} from './utils';
/**
* @type {string}
*/
let persistentResponse;
const fixItQuestion = {
@@ -7,6 +10,11 @@ const fixItQuestion = {
name: 'confirmation',
message: 'Fix it:',
choices: ['yes', 'no', 'always', 'never'],
/**
*
* @param {string} val
* @returns {string}
*/
filter(val) {
return val.toLowerCase();
},
+26
View File
@@ -5,15 +5,34 @@ import {fs, system} from '@appium/support';
import {exec} from 'teen_process';
import {isFunction} from 'lodash';
/**
* @param {string} message
* @returns {UtilsResult}
*/
function ok(message) {
return {ok: true, optional: false, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function nok(message) {
return {ok: false, optional: false, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function okOptional(message) {
return {ok: true, optional: true, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function nokOptional(message) {
return {ok: false, optional: true, message};
}
@@ -122,3 +141,10 @@ export {
getNpmPackageInfo,
resetLog,
};
/**
* @typedef UtilsResult
* @property {boolean} ok
* @property {boolean} optional
* @property {string} message
*/
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/doctor",
"version": "2.0.0",
"version": "2.0.3",
"description": "Test environment for fitness to run Appium",
"keywords": [
"automation",
@@ -34,7 +34,8 @@
"appium-doctor.js",
"lib",
"bin",
"build"
"build",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -42,11 +43,11 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^3.0.0",
"@appium/support": "^3.1.1",
"@colors/colors": "1.5.0",
"@types/bluebird": "3.5.38",
"@types/teen_process": "2.0.0",
"appium-adb": "9.10.18",
"appium-adb": "9.10.21",
"bluebird": "3.7.2",
"inquirer": "8.2.5",
"lodash": "4.17.21",
@@ -61,5 +62,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
}
+4 -6
View File
@@ -1,13 +1,11 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"paths": {
"@appium/support": ["../support"],
},
"@appium/support": ["../support"]
}
},
"include": ["lib", "bin"],
"references": [
{"path": "../support"}
]
"references": [{"path": "../support"}]
}
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.3](https://github.com/appium/appium/compare/@appium/docutils@0.1.2...@appium/docutils@0.1.3) (2023-01-13)
**Note:** Version bump only for package @appium/docutils
## [0.1.2](https://github.com/appium/appium/compare/@appium/docutils@0.1.1...@appium/docutils@0.1.2) (2023-01-13)
**Note:** Version bump only for package @appium/docutils
## [0.1.1](https://github.com/appium/appium/compare/@appium/docutils@0.1.0...@appium/docutils@0.1.1) (2022-12-21)
### Bug Fixes
- **docutils:** package should publish the base mkdocs yml ([#17968](https://github.com/appium/appium/issues/17968)) ([aab4156](https://github.com/appium/appium/commit/aab415685ec5d5578cc8faae563f5cc5c0e4bb75))
# [0.1.0](https://github.com/appium/appium/compare/@appium/docutils@0.0.13...@appium/docutils@0.1.0) (2022-12-14)
- chore!: set engines to minimum Node.js v14.17.0 ([a1dbe6c](https://github.com/appium/appium/commit/a1dbe6c43efe76604943a607d402f4c8b864d652))
+9 -9
View File
@@ -6,11 +6,11 @@ const DEFAULT_BRANCH = 'gh-pages';
const MIKE_VER_STRING = 'mike 1.';
export class Mike {
/** @type string */ remote;
/** @type string */ branch;
/** @type string? */ prefix;
/** @type string */ configFile;
/** @type boolean */ _mikeVerified = false;
/** @type {string} */ remote;
/** @type {string} */ branch;
/** @type {string?} */ prefix;
/** @type {string} */ configFile;
/** @type {boolean} */ _mikeVerified = false;
constructor(/** @type MikeOpts */ opts) {
this.remote = opts.remote || DEFAULT_REMOTE;
@@ -22,7 +22,7 @@ export class Mike {
/**
* Throw an error if the 'mike' binary cannot be found
*
* @throws Error
* @throws {Error}
*/
async verifyMike() {
if (this._mikeVerified) {
@@ -46,7 +46,7 @@ export class Mike {
* @param {string} cmdName - the name of the mike command to run
* @param {string[]} cmdArgs - an array of command-specific arguments
*
* @returns string[]
* @returns {string[]}
*/
getMikeArgs(cmdName, cmdArgs) {
return [
@@ -70,7 +70,7 @@ export class Mike {
* @param {string[]} [mikeArgs=[]] - the arguments to pass to the mike command
* @param {boolean?} [verify=true] - whether to verify mike exists first
*
* @returns Promise<import('teen_process').ExecResult<string>>
* @returns {Promise<import('teen_process').ExecResult<string>>}
*/
async exec(mikeCmd, mikeArgs = [], verify = true) {
if (verify) {
@@ -84,7 +84,7 @@ export class Mike {
/**
* Return the list of mike deploys
*
* @returns string[]
* @returns {string[]}
*/
async list() {
const {stdout} = await this.exec('list');
+4 -4
View File
@@ -22,9 +22,9 @@ export async function verifyMkdocs() {
/**
* Run 'mkdocs build' on a project
*
* @param {string} configPath path to mkdocs config yml
* @param {string} outputDir directory mkdocs should build into
* @param {?string} theme theme name
* @param {string} configPath - path to mkdocs config yml
* @param {string} outputDir - directory mkdocs should build into
* @param {string?} theme - theme name
*/
export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
await verifyMkdocs();
@@ -34,7 +34,7 @@ export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
/**
* Run 'mkdocs serve' on a project
*
* @param {string} configPath path to mkdocs config yml
* @param {string} configPath - path to mkdocs config yml
*/
export async function mkdocsServe(configPath) {
await verifyMkdocs();
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/docutils",
"version": "0.1.0",
"version": "0.1.3",
"description": "Documentation generation utilities for Appium and related projects",
"keywords": [
"automation",
@@ -30,8 +30,10 @@
"files": [
"index.js",
"index.d.ts",
"base-mkdocs.yml",
"lib",
"build"
"build",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -40,7 +42,7 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^3.0.0",
"@appium/support": "^3.1.1",
"source-map-support": "0.5.21",
"teen_process": "2.0.2"
},
@@ -51,5 +53,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
}
+1 -1
View File
@@ -1,4 +1,4 @@
mkdocs==1.4.2
mkdocs-material==8.5.11
mkdocs-material==9.0.4
mkdocs-material-extensions==1.1.1
mike==1.1.2
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.3.3](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.2...@appium/driver-test-support@0.3.3) (2023-01-13)
**Note:** Version bump only for package @appium/driver-test-support
## [0.3.2](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.1...@appium/driver-test-support@0.3.2) (2023-01-13)
### Bug Fixes
* **support:** update dependency axios to v1.2.2 ([5291ca6](https://github.com/appium/appium/commit/5291ca672b3b47c5270e9fd85de3e4ed76a650e0))
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
## [0.3.1](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.0...@appium/driver-test-support@0.3.1) (2022-12-21)
### Bug Fixes
- **test-support:** update dependency sinon to v15.0.1 ([5d0d4f5](https://github.com/appium/appium/commit/5d0d4f5a44dff362430159f97f8569a9be7e378d))
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
# [0.3.0](https://github.com/appium/appium/compare/@appium/driver-test-support@0.2.1...@appium/driver-test-support@0.3.0) (2022-12-14)
### Bug Fixes
+9 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/driver-test-support",
"version": "0.3.0",
"version": "0.3.3",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
@@ -30,7 +30,8 @@
"files": [
"index.js",
"lib",
"build"
"build",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -39,16 +40,16 @@
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^0.6.0",
"@appium/types": "^0.8.1",
"@types/lodash": "4.14.191",
"axios": "1.2.1",
"axios": "1.2.2",
"bluebird": "3.7.2",
"chai": "4.3.7",
"get-port": "5.1.1",
"lodash": "4.17.21",
"sinon": "15.0.0",
"sinon": "15.0.1",
"source-map-support": "0.5.21",
"type-fest": "3.3.0"
"type-fest": "3.5.1"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
@@ -60,5 +61,6 @@
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
}
+4 -9
View File
@@ -1,20 +1,15 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"checkJs": true,
"paths": {
"@appium/types": ["../types"],
"appium/driver": ["../base-driver"],
"appium/driver": ["../base-driver"]
},
"types": [
"mocha", "chai", "chai-as-promised"
]
"types": ["mocha", "chai", "chai-as-promised"]
},
"include": ["./lib"],
"references": [
{"path": "../types"},
{"path": "../base-driver"}
]
"references": [{"path": "../types"}, {"path": "../base-driver"}]
}
@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.3](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.2...@appium/execute-driver-plugin@3.0.3) (2023-01-13)
**Note:** Version bump only for package @appium/execute-driver-plugin
## [3.0.2](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.1...@appium/execute-driver-plugin@3.0.2) (2023-01-13)
### Bug Fixes
* **execute-driver-plugin:** fix bad types in execute-child module ([697b442](https://github.com/appium/appium/commit/697b4427e4e60821943e8c05292e8f6eb43aa22f))
* **execute-driver-plugin:** update dependency webdriverio to v7.28.1 ([2bb4f32](https://github.com/appium/appium/commit/2bb4f32329938e15062e42b585dd760cca5ae17b))
* **execute-driver-plugin:** update dependency webdriverio to v7.29.0 ([a3fad3e](https://github.com/appium/appium/commit/a3fad3ed28822b1dfaae72cbfb8d7906ec35e158))
* **execute-driver-plugin:** update dependency webdriverio to v7.29.1 ([7c59380](https://github.com/appium/appium/commit/7c59380e6b97691ff16a8d98a912d67b7972593a))
* **execute-driver-plugin:** use correct return type annotation ([7add23e](https://github.com/appium/appium/commit/7add23e4183e6982048cd653b2a47eb052ccc38a))
## [3.0.1](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.0...@appium/execute-driver-plugin@3.0.1) (2022-12-21)
### Bug Fixes
- **execute-driver-plugin:** update dependency webdriverio to v7.28.0 ([f81e2e9](https://github.com/appium/appium/commit/f81e2e92eff25c33d36f767209f423227d288218))
# [3.0.0](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.6...@appium/execute-driver-plugin@3.0.0) (2022-12-14)
### Bug Fixes
@@ -5,6 +5,9 @@ import {logger, util} from 'appium/support';
import {attach} from 'webdriverio';
const log = logger.getLogger('ExecuteDriver Child');
/**
* @type {(res: ScriptResult) => Promise<void>}
*/
let send;
// duplicate defining these keys here so we don't need to re-load a huge appium
@@ -12,12 +15,22 @@ let send;
export const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
export const MJSONWP_ELEMENT_KEY = 'ELEMENT';
async function runScript(driverOpts, script, timeoutMs) {
/**
* Run the script in a VM.
* @param {DriverScriptMessageEvent} eventParams
* @returns {Promise<RunScriptResult>}
* @throws {TypeError}
*/
async function runScript(eventParams) {
const {driverOpts, script, timeoutMs} = eventParams;
if (!_.isNumber(timeoutMs)) {
throw new TypeError('Timeout parameter must be a number');
}
// set up fake logger
/**
* set up fake logger
* @type {string[]}
*/
const logLevels = ['error', 'warn', 'log'];
const logs = {};
const consoleFns = {};
@@ -122,11 +135,22 @@ function coerceScriptResult(obj) {
return obj;
}
async function main(driverOpts, script, timeoutMs) {
/**
* Entry point to runScript
* @param {DriverScriptMessageEvent} eventParams
*/
async function main(eventParams) {
/**
* keep the response of runScript
* @type {ScriptResult}
*/
let res;
log.info('Parameters received from parent process');
try {
res = {success: await runScript(driverOpts, script, timeoutMs)};
res = {success: await runScript(eventParams)};
log.info('runScript success');
} catch (error) {
log.info('runScript error');
res = {error: {message: error.message, stack: error.stack}};
}
await send(res);
@@ -136,8 +160,34 @@ async function main(driverOpts, script, timeoutMs) {
if (require.main === module && _.isFunction(process.send)) {
send = B.promisify(process.send, {context: process});
log.info('Running driver execution in child process');
process.on('message', ({driverOpts, script, timeoutMs}) => {
log.info('Parameters received from parent process');
main(driverOpts, script, timeoutMs);
});
process.on('message', main);
}
/**
* @typedef {import('webdriverio').AttachOptions} AttachOptions
*/
/**
* @typedef DriverScriptMessageEvent
* @property {AttachOptions} driverOpts - the driver options
* @property {string} script - the javascript to execute
* @property {number} timeoutMs - script timeout in milliseconds
*/
/**
* @typedef ScriptResult
* @property {any} [success]
* @property {ScriptResultError} [error]
*/
/**
* @typedef ScriptResultError
* @property {any} message
* @property {any} stack
*/
/**
* @typedef RunScriptResult
* @property {any} result
* @property {object} logs
*/
+5 -3
View File
@@ -23,15 +23,17 @@ export default class ExecuteDriverPlugin extends BasePlugin {
* a new nodejs VM, and which has available a webdriverio driver object, having
* already been attached to the currently running session.
*
* @param {function} next - standard behaviour for executeDriverScript
* @param {BaseDriver} driver - Appium driver handling this command
* @param {import('@appium/types').NextPluginCallback} next - standard behaviour for executeDriverScript
* @param {import('@appium/types').ExternalDriver} driver - Appium driver handling this command
* @param {string} script - the string representing the driver script to run
* @param {string} [scriptType='webdriverio'] - the name of the driver script
* library (currently only webdriverio is supported)
* @param {number} [timeoutMs=3600000] - timeout for the script process
*
* @returns {Object} - a JSONifiable object representing the return value of
* @returns {Promise<any>} - a JSONifiable object representing the return value of
* the script
* @type {import('@appium/types').PluginCommand<[string, string?, number?]>}
* @throws {Error}
*/
async executeDriverScript(
next,
+12 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/execute-driver-plugin",
"version": "3.0.0",
"version": "3.0.3",
"description": "Plugin for batching and executing driver commands with Appiums",
"keywords": [
"automation",
@@ -23,10 +23,12 @@
},
"license": "Apache-2.0",
"author": "https://github.com/appium",
"types": "./build/lib/plugin.d.ts",
"files": [
"build",
"lib",
"index.js"
"index.js",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -40,11 +42,15 @@
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"vm2": "3.9.13",
"webdriverio": "7.27.0"
"webdriverio": "7.29.1"
},
"peerDependencies": {
"appium": "^2.0.0-beta.35"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
},
"publishConfig": {
"access": "public"
},
@@ -52,9 +58,8 @@
"pluginName": "execute-driver",
"mainClass": "ExecuteDriverPlugin"
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
"typedoc": {
"entryPoint": "./lib/plugin.js"
}
}
+3 -2
View File
@@ -1,7 +1,8 @@
{
"compilerOptions": {
"outDir": "build"
"outDir": "build",
"checkJs": true
},
"extends": "../../config/tsconfig.plugin.json",
"extends": "@appium/tsconfig/tsconfig.plugin.json",
"include": ["lib"]
}
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.1](https://github.com/appium/appium/compare/@appium/fake-driver@5.1.0...@appium/fake-driver@5.1.1) (2023-01-13)
**Note:** Version bump only for package @appium/fake-driver
# [5.1.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.0.0...@appium/fake-driver@5.1.0) (2023-01-13)
### Features
* **base-driver:** deprecate non-standard routes ([7055a0b](https://github.com/appium/appium/commit/7055a0b28193f677b21541ddada3c4a314f90f5b))
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
# [5.0.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.2...@appium/fake-driver@5.0.0) (2022-12-14)
### Bug Fixes
@@ -26,9 +26,8 @@ export function ContextsMixin(Base) {
}
}
/**
* Get the current appium context
*
// do not add a description to this method's docstring
/*
* @returns {Promise<string>}
*/
async getCurrentContext() {
@@ -64,6 +64,10 @@ export function ElementMixin(Base) {
await this.setValue('', elementId);
}
/**
* This comment should be displayed instead of the one from ExternalDriver
* @param {string} elementId
*/
async click(elementId) {
this.assertNoAlert();
let el = this.getElement(elementId);
@@ -78,6 +78,11 @@ export function FindMixin(Base) {
return /** @type {Mult extends true ? Element[] : Element} */ (retval);
}
/**
* This should override whatever's in ExternalDriver
* @param {string} strategy Strategy
* @param {string} selector Selector
*/
async findElement(strategy, selector) {
return this.findElOrEls(strategy, selector, false);
}
@@ -79,6 +79,12 @@ export function GeneralMixin(Base) {
}
}
async mobileShake() {
this.shook = true;
}
async doubleClick() {}
async execute(script, args) {
return await this.executeMethod(script, args);
}
@@ -7,7 +7,6 @@ import {GeneralMixin} from './general';
/**
*
* @param {import('@appium/types').Class<import('../driver').FakeDriverCore>} Base
* @returns
*/
export function FakeDriverMixin(Base) {
return GeneralMixin(FindMixin(ElementMixin(AlertMixin(ContextsMixin(Base)))));
+41 -19
View File
@@ -1,5 +1,6 @@
import B from 'bluebird';
import {BaseDriver, errors} from 'appium/driver';
import {deprecatedCommandsLogged} from '@appium/base-driver/build/lib/protocol/protocol';
import {FakeApp} from './fake-app';
import {FakeDriverMixin} from './commands';
@@ -37,6 +38,7 @@ export class FakeDriverCore extends BaseDriver {
this.maxElId = 0;
this.fakeThing = null;
this._proxyActive = false;
this.shook = false;
}
proxyActive() {
@@ -65,25 +67,21 @@ export class FakeDriverCore extends BaseDriver {
}
/**
*
* @param {W3CFakeDriverCaps} jsonwpDesiredCapabilities
* @param {W3CFakeDriverCaps} [jsonwpRequiredCaps]
* @param {W3CFakeDriverCaps} [w3cCapabilities]
* @param {import('@appium/types').DriverData[]} [otherSessionData]
* @returns {Promise<[string,FakeDriverCaps]>}
* Comment for `createSession` in `FakeDriver`
* @param {W3CFakeDriverCaps} w3cCapabilities1 W3C Capabilities
* @param {W3CFakeDriverCaps} [w3cCapabilities2] W3C Capabilities
* @param {W3CFakeDriverCaps} [w3cCapabilities3] W3C Capabilities
* @param {import('@appium/types').DriverData[]} [driverData] Other session data
* @override
* @returns {Promise<[string,FakeDriverCaps]>} Session ID and normalized capabilities
*/
async createSession(
jsonwpDesiredCapabilities,
jsonwpRequiredCaps,
w3cCapabilities,
otherSessionData = []
) {
async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities3, driverData = []) {
// TODO add validation on caps.app that we will get for free from
// BaseDriver
// check to see if any other sessions have set uniqueApp. If so, emulate
// not being able to start a session because of system resources
for (let d of otherSessionData) {
for (let d of driverData) {
if (d.isUnique) {
throw new errors.SessionNotCreatedError(
'Cannot start session; another ' +
@@ -93,12 +91,7 @@ export class FakeDriverCore extends BaseDriver {
}
let [sessionId, caps] = /** @type {[string, FakeDriverCaps]} */ (
await super.createSession(
jsonwpDesiredCapabilities,
jsonwpRequiredCaps,
w3cCapabilities,
otherSessionData
)
await super.createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities3, driverData)
);
this.caps = caps;
await this.appModel.loadApp(caps.app);
@@ -138,6 +131,25 @@ export class FakeDriverCore extends BaseDriver {
return this.cliArgs;
}
/**
* This is a command that will return a list of deprecated command names called
*
* @returns {Promise<string[]>}
*/
async getDeprecatedCommandsCalled() {
await B.delay(1);
return Array.from(deprecatedCommandsLogged);
}
/**
* This is a command that exists just to be an example of a deprecated command
*
* @returns {Promise<void>}
*/
async callDeprecatedCommand() {
await B.delay(1);
}
static newMethodMap = /** @type {const} */ ({
'/session/:sessionId/fakedriver': {
GET: {command: 'getFakeThing'},
@@ -149,6 +161,13 @@ export class FakeDriverCore extends BaseDriver {
'/session/:sessionId/fakedriverargs': {
GET: {command: 'getFakeDriverArgs'},
},
'/session/:sessionId/deprecated': {
POST: {command: 'callDeprecatedCommand', deprecated: true},
},
// this next one exists to override a deprecated method
'/session/:sessionId/doubleclick': {
POST: {command: 'doubleClick'},
},
});
static executeMethodMap = /** @type {const} */ ({
@@ -166,6 +185,9 @@ export class FakeDriverCore extends BaseDriver {
command: 'setFakeThing',
params: {required: ['thing']},
},
'fake: getDeprecatedCommandsCalled': {
command: 'getDeprecatedCommandsCalled',
},
});
/**
+5
View File
@@ -15,3 +15,8 @@ async function main() {
}
export {FakeDriver, startServer, main};
/**
* @typedef {import('./types').W3CFakeDriverCaps} W3CFakeDriverCaps
* @typedef {import('./types').FakeDriverCaps} FakeDriverCaps
*/
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-driver",
"version": "5.0.0",
"version": "5.1.1",
"description": "Mock driver used internally by Appium for testing. Ignore",
"keywords": [
"automation",
@@ -33,7 +33,8 @@
"index.js",
"lib",
"build",
"test/fixtures"
"test/fixtures",
"tsconfig.json"
],
"scripts": {
"build": "cpy lib/screen.png build",
@@ -77,8 +78,8 @@
}
},
"typedoc": {
"entryPoint": "./build/lib/index.js"
"entryPoint": "./lib/index.js"
},
"types": "./build/lib/index.d.ts",
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../config/tsconfig.base.json",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"checkJs": true,
+23
View File
@@ -3,6 +3,29 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.1.1](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.0...@appium/fake-plugin@3.1.1) (2023-01-13)
**Note:** Version bump only for package @appium/fake-plugin
# [3.1.0](https://github.com/appium/appium/compare/@appium/fake-plugin@3.0.1...@appium/fake-plugin@3.1.0) (2023-01-13)
### Features
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
## [3.0.1](https://github.com/appium/appium/compare/@appium/fake-plugin@3.0.0...@appium/fake-plugin@3.0.1) (2022-12-21)
**Note:** Version bump only for package @appium/fake-plugin
# [3.0.0](https://github.com/appium/appium/compare/@appium/fake-plugin@2.1.3...@appium/fake-plugin@3.0.0) (2022-12-14)
### Bug Fixes
+22 -16
View File
@@ -1,7 +1,17 @@
{
"name": "@appium/fake-plugin",
"version": "3.0.0",
"version": "3.1.1",
"description": "A fake Appium 2.0 plugin",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"homepage": "https://appium.io",
"bugs": {
"url": "https://github.com/appium/appium/issues"
@@ -13,14 +23,14 @@
},
"license": "Apache-2.0",
"author": "https://github.com/appium",
"main": "./build/index.js",
"directories": {
"lib": "./lib"
},
"files": [
"index.js",
"lib",
"build"
"build",
"tsconfig.json"
],
"scripts": {
"test": "npm run test:unit",
@@ -28,8 +38,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-plugin": "^2.0.0",
"@appium/support": "^3.0.0",
"@appium/base-plugin": "^2.1.1",
"@appium/support": "^3.1.1",
"@types/bluebird": "3.5.38",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -50,18 +60,14 @@
"fake-success": "./build/lib/scripts/fake-success.js"
}
},
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"publishConfig": {
"access": "public"
},
"gitHead": "422142302f2f540a31c7eaf5178e2642b53a30d2",
"tags": [
"appium"
],
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
]
"typedoc": {
"entryPoint": "./lib/plugin.js"
}
}
+6 -4
View File
@@ -2,9 +2,11 @@
"compilerOptions": {
"outDir": "build"
},
"extends": "../../config/tsconfig.plugin.json",
"extends": "@appium/tsconfig/tsconfig.plugin.json",
"include": ["lib"],
"references": [{
"path": "../base-plugin"
}]
"references": [
{
"path": "../base-plugin"
}
]
}

Some files were not shown because too many files have changed in this diff Show More