Compare commits

...
Author SHA1 Message Date
Christopher Hiller 6b3cc1a874 Publish
- appium@2.0.0-beta.46
 - @appium/base-driver@8.7.3
 - @appium/base-plugin@1.10.5
 - @appium/doctor@1.16.28
 - @appium/docutils@0.0.13
 - @appium/fake-plugin@2.1.3
 - @appium/gulp-plugins@7.0.8
 - @appium/support@2.61.1
 - @appium/test-support@2.0.2
2022-10-14 14:02:09 -07:00
Christopher Hiller 51d6d69ff1 fix(basedriver): ensure "opts" is defined at time of construction 2022-10-14 14:01:07 -07:00
renovate[bot] 8c092b1172 chore(support): update dependency shell-quote to v1.7.4 2022-10-14 11:45:38 +00:00
renovate[bot] b7bd6b426a chore(workflows): update actions/setup-node digest to 8c91899 2022-10-14 08:08:01 +00:00
Dor Blayzer 8c6f8fff44 docs: update clients.md (#17606)
Add .NET client set of Appium command example
2022-10-13 22:13:33 -07:00
renovate[bot] 55f4bdca0e chore(gulp-plugins): update dependency eslint-plugin-promise to v6.1.0 2022-10-14 02:47:23 +00:00
Jonathan Lipps f545a6cde5 Publish
- appium@2.0.0-beta.45
 - @appium/base-driver@8.7.2
 - @appium/base-plugin@1.10.4
 - @appium/doctor@1.16.27
 - @appium/docutils@0.0.12
 - @appium/driver-test-support@0.2.1
 - @appium/execute-driver-plugin@2.0.6
 - @appium/fake-driver@4.2.2
 - @appium/fake-plugin@2.1.2
 - @appium/gulp-plugins@7.0.7
 - @appium/images-plugin@1.3.7
 - @appium/opencv@1.0.13
 - @appium/plugin-test-support@0.2.1
 - @appium/relaxed-caps-plugin@1.0.0-beta.13
 - @appium/support@2.61.0
 - @appium/test-support@2.0.1
 - @appium/types@0.5.0
 - @appium/universal-xml-plugin@1.0.0-beta.15
2022-10-13 16:25:16 -07:00
renovate[bot] c7072027d6 chore(workflows): update actions/checkout digest to 93ea575 2022-10-11 19:04:38 +00:00
renovate[bot] ed8b65ce72 chore(gulp-plugins): update dependency @babel/preset-env to v7.19.4 2022-10-11 18:43:03 +00:00
Mykola Mokhnach 9399654c8a docs: Delete the absolute requirements section 2022-10-11 15:19:21 +02:00
Mykola Mokhnach 1547c7535e docs: Update Appium 2 installation tutorial (#17590) 2022-10-11 15:15:32 +02:00
renovate[bot] b18ba50f8e chore(fake-driver): update dependency @xmldom/xmldom to v0.8.3 2022-10-11 09:58:42 +00:00
renovate[bot] 24a0efd695 chore(types): update dependency type-fest to v3.1.0 2022-10-10 10:13:17 +00:00
renovate[bot] 2a452cec4b chore(base-driver): update dependency express to v4.18.2 2022-10-09 08:06:46 +00:00
renovate[bot] 8c7acd52a5 chore(gulp-plugins): update dependency eslint to v8.25.0 2022-10-08 07:34:35 +00:00
renovate[bot] 5bca642efa chore(base-driver): update dependency body-parser to v1.20.1 2022-10-07 13:18:13 +00:00
Mykola Mokhnach 6c451e1f7d chore(appium): Remove logging from adjustNodePath helper (#17578) 2022-10-07 10:58:56 +02:00
Christopher Hiller 9ce09845c8 chore(): replaced a ternary 2022-10-06 15:30:22 -07:00
Christopher Hiller 4d7d8e4d38 fix(types): fix driverForSession declaration 2022-10-06 15:30:22 -07:00
Christopher Hiller a3ad768802 chore(): review fixes 2022-10-06 15:30:22 -07:00
Christopher Hiller b20d0b82a2 chore(appium,base-driver,driver-test-support,types): overhaul types for constraints and caps
The big idea here is:

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

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

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

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

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

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

Bonus:

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

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

```js

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

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

* chore: add ruby example

Co-authored-by: Kazuaki Matsuo <fly.49.89.over@gmail.com>
2022-09-08 09:22:50 -07:00
renovate[bot] 18c408dc72 chore(): update dependency @types/node to v16.11.58 2022-09-08 15:08:44 +00:00
Christopher Hiller c26af8f852 Publish
- appium@2.0.0-beta.44
 - @appium/base-driver@8.7.1
 - @appium/base-plugin@1.10.3
 - @appium/doctor@1.16.26
 - @appium/docutils@0.0.11
 - @appium/driver-test-support@0.2.0
 - @appium/eslint-config-appium@7.0.0
 - @appium/execute-driver-plugin@2.0.5
 - @appium/fake-driver@4.2.1
 - @appium/fake-plugin@2.1.1
 - @appium/gulp-plugins@7.0.6
 - @appium/images-plugin@1.3.6
 - @appium/opencv@1.0.12
 - @appium/plugin-test-support@0.2.0
 - @appium/relaxed-caps-plugin@1.0.0-beta.12
 - @appium/support@2.60.0
 - @appium/test-support@2.0.0
 - @appium/types@0.4.1
 - @appium/universal-xml-plugin@1.0.0-beta.14
2022-09-07 12:40:03 -07:00
renovate[bot] bfef2db517 chore(gulp-plugins): update babel monorepo to v7.19.0 2022-09-06 19:52:27 +00:00
renovate[bot] 4c0181fb81 chore(): update dependency @types/node to v16.11.57 2022-09-06 15:23:04 +00:00
renovate[bot] 49d6a81973 chore(types): update webdriverio monorepo to v7.24.0 2022-09-01 18:33:17 +00:00
renovate[bot] ce44fc0fcf chore(doctor): update dependency appium-adb to v9.10.9 2022-09-01 14:04:20 +00:00
Mykola Mokhnach 5ab78297e1 feat(support): Move module root detection utility into support package (#17427) 2022-08-30 07:54:36 +02:00
renovate[bot] 3764149753 chore(execute-driver-plugin): update dependency vm2 to v3.9.11 2022-08-29 18:22:34 +00:00
renovate[bot] 21b7aa9b93 chore(doctor): update dependency appium-adb to v9.10.8 2022-08-29 14:35:18 +00:00
Kazuaki Matsuo b67d72e4f3 ci: update the base os version to make it work (#17424) 2022-08-28 10:39:52 -07:00
renovate[bot] e916a58d93 chore(doctor): update dependency appium-adb to v9.10.7 2022-08-28 12:09:02 +00:00
renovate[bot] 4090b56931 chore(doctor): update dependency appium-adb to v9.10.6 2022-08-27 21:22:51 +00:00
renovate[bot] d48283cdca chore(gulp-plugins): update dependency eslint to v8.23.0 2022-08-27 12:31:24 +00:00
renovate[bot] b2ad5a49db chore(gulp-plugins): update dependency eslint-plugin-promise to v6.0.1 2022-08-25 13:36:03 +00:00
Christopher Hiller 89cf01fda3 fix(support): update checks eat errors from non-existent packages 2022-08-24 15:00:34 -07:00
Christopher Hiller 7d7bb7abc3 fix(appium): eat update info errors
If a user runs e.g., "appium driver list --updates" and one of the installed drivers is _not_ published to npm, an error will no longer be emitted.

Closes #17357
2022-08-24 15:00:34 -07:00
Dileep B 337241b8f6 docs(appium): Add custom plugin 'appium-reporter-plugin' link in readme (#17412) 2022-08-24 12:25:39 -07:00
Christopher Hiller 0c62fc00e4 chore(renovate): add pkg-dir to ignored packages (#17407) 2022-08-24 12:12:18 -07:00
renovate[bot] f0608a5d01 chore(types): update dependency type-fest to v2.19.0 2022-08-24 17:32:45 +00:00
renovate[bot] 04c44260ac chore(): update dependency @types/node to v16.11.56 2022-08-24 13:56:09 +00:00
renovate[bot] 9bf9cc70fa chore(gulp-plugins): update dependency @babel/core to v7.18.13 2022-08-24 00:51:12 +00:00
Christopher Hiller 0c70d27679 chore(ci): fix FOSSA workflow 2022-08-23 13:54:22 -07:00
Christopher Hiller 44422a1e58 chore(ci): use npm caches across jobs
I think this was just an oversight
2022-08-23 13:51:23 -07:00
Christopher Hiller 21dc828306 chore(ci): use midnight-smoker
This replaces use of GH action `boneskull/nodejs-production-test-action` with direct use of `midnight-smoker`. It's now trivial to run all the smoke tests in a development environment.
2022-08-23 13:51:23 -07:00
Jonathan Lipps f3a393aed8 docs(appium): add a readme note about docs flux (close #17347) 2022-08-23 13:51:08 -07:00
Christopher Hiller 12cdbb7fdd chore(types): better types for w3c actions
The previous types were very loose.  I found some decent ones and stole them.
2022-08-23 13:50:11 -07:00
Christopher Hiller d14a0c5c0b chore(): add RouteMatcher type
Replaces the bare tuple `[string, RegExp]` with a named type.  Used by `Driver.getProxyAvoidList()`

Narrows the type def to only allow known HTTP methods as the first item in the tuple.
2022-08-23 13:50:11 -07:00
Christopher Hiller 74378cca66 fix(types): add inclusionCaseInsensitive to Constraint type 2022-08-23 13:50:11 -07:00
Christopher Hiller 92f4677b94 docs(plugin-test-support): fix typo 2022-08-23 13:42:47 -07:00
Christopher Hiller e2b3bdb09d chore(): update package-lock.json 2022-08-23 13:42:47 -07:00
Christopher Hiller 6820f5ec4e chore(appium): remove test export
BREAKING CHANGE:

This removes the `test` export from `appium`.  `import * from 'appium/test'` is no longer supported.

Use `@appium/test-support`, `@appium/driver-test-support` or `@appium/plugin-test-support` instead.

See #17398
2022-08-23 13:42:47 -07:00
Christopher Hiller 743c764d5e chore(test-support): remove driver/plugin-specific methods
BREAKING CHANGE:

This removes plugin/driver specific methods from `@appium/test-support`, and also removes the peer dep on `appium`.
The methods can now be found in `@appium/plugin-test-support` and `@appium/driver-test-support`.

Closes #17398
2022-08-23 13:42:47 -07:00
Christopher Hiller ca1480adcf chore(docutils): sync package.json 2022-08-23 13:42:47 -07:00
Christopher Hiller 8ea98b1fe9 chore(base-plugin): sync package.json 2022-08-23 13:42:47 -07:00
Christopher Hiller e09e5051c8 chore(fake-plugin): sync package.json 2022-08-23 13:42:47 -07:00
Christopher Hiller 44e5f1ba05 test(execute-driver-plugin): use @appium/plugin-test-support 2022-08-23 13:42:47 -07:00
Christopher Hiller 0f735774af test(images-plugin): use @appium/plugin-test-support
also sync package.json
2022-08-23 13:42:47 -07:00
Christopher Hiller 61de659616 chore(relaxed-caps-plugin): sync package.json 2022-08-23 13:42:47 -07:00
Christopher Hiller 07d4a81d27 chore(universal-xml-plugin): sync package.json 2022-08-23 13:42:47 -07:00
Christopher Hiller d2a92c8bc1 test(fake-driver): use @appium/driver-test-support
also publish types, since that wasn't happening
2022-08-23 13:42:47 -07:00
Christopher Hiller c37f4500c2 test(base-driver): use @appium/driver-test-support 2022-08-23 13:42:47 -07:00
Christopher Hiller b6c7533513 chore(): update plugin base tsconfig to reference @appium/plugin-test-support 2022-08-23 13:42:47 -07:00
Christopher Hiller 108b8c0209 feat(plugin-test-support): create @appium/plugin-test-support
This provides setup/teardown functions to plugins for their E2E tests

Extracted from `@appium/base-plugin` by way of `@appium/test-support`
2022-08-23 13:42:47 -07:00
Christopher Hiller 41289724dc feat(driver-test-support): create package
This package contains driver-specific test utilities extracted from `@appium/base-driver` by way of `@appium/test-support`
2022-08-23 13:42:47 -07:00
renovate[bot] 58d359d944 chore(): update dependency @types/node to v16.11.54 2022-08-23 13:03:31 +00:00
renovate[bot] cf4f533022 chore(types): update webdriverio monorepo to v7.23.0 2022-08-22 14:39:25 +00:00
renovate[bot] 0eb56a055e chore(): update dependency @types/node to v16.11.52 2022-08-22 11:46:13 +00:00
renovate[bot] 94dd88fc98 chore(execute-driver-plugin): update dependency webdriverio to v7.22.0 2022-08-20 15:39:08 +00:00
Mykola Mokhnach 39d5cee1b7 feat(appium): Adjust NODE_PATH so NPM could properly resolve component peer dependencies (#17325) 2022-08-20 13:35:04 +02:00
renovate[bot] 17a803da27 chore(): update dependency @types/node to v16.11.51 2022-08-20 11:11:02 +00:00
renovate[bot] 3470ec5101 chore(types): update dependency type-fest to v2.18.1 2022-08-19 10:16:14 +00:00
Christopher Hiller f5bc702c9a chore(ci): update to latest nodejs-production-test-action 2022-08-17 11:33:26 -07:00
renovate[bot] 0668bc45dc chore(images-plugin): update dependency lru-cache to v7.14.0 2022-08-17 10:48:31 +00:00
renovate[bot] a815e3be9d chore(types): update webdriverio monorepo to v7.21.0 2022-08-17 02:53:06 +00:00
Christopher Hiller b5947991cf fix(types): Driver extends ExecuteCommands (#17363)
Closes #17359
2022-08-16 13:35:21 -07:00
renovate[bot] afc15599c0 chore(): update dependency @types/node to v16.11.49 2022-08-16 10:33:08 +00:00
renovate[bot] 75a9154366 chore(gulp-plugins): update dependency eslint to v8.22.0 2022-08-14 09:51:20 +00:00
renovate[bot] 8eea316484 chore(support): update dependency @types/semver to v7.3.12 2022-08-12 14:07:44 +00:00
renovate[bot] ef9b99feee chore(): update dependency @types/node to v16.11.48 2022-08-12 11:44:07 +00:00
renovate[bot] 7dbf3c2127 chore(support): update dependency @types/semver to v7.3.11 2022-08-11 12:05:02 +00:00
renovate[bot] d3c4d40b44 chore(gulp-plugins): update dependency eslint-plugin-mocha to v10.1.0 2022-08-11 09:11:47 +00:00
renovate[bot] 6357b30051 chore(gulp-plugins): update dependency eslint to v8.21.0 2022-08-11 02:41:19 +00:00
Christopher Hiller cfdf898e04 fix(appium): do not throw ENAMETOOLONG on long cli arg
Some CLI args can be interpreted as filenames or JSON/CSV blobs.  Appium first assumes the value is a file and attempts to open it.  But if a blob is given and it is of some _problematic length_, the OS will throw an `ENAMETOOLONG` exception trying to open a filepath with a huge blob.  If the JSON is reasonably small, an `ENOENT` will be thrown instead.  Previous to this change, we were only looking for `ENOENT`; now we treat `ENAMETOOLONG` errors as JSON blobs.  If the problem is that the filename _is_ actually too long, the user will get an "invalid JSON" error, which will probably be confusing, but is less likely to happen than the more-common error this addresses.

If someone complains about it in the future, we can take another look, but it's going to take more effort than it's worth atm to fix it (imo).
2022-08-10 15:51:31 -07:00
Christopher Hiller 159703744f fix(types): add missing @types/ws dep 2022-08-10 14:54:51 -07:00
Jonathan Lipps 7ae4b5cb6a docs(appium): fix env var typo in docs publishing (#17318) 2022-08-10 11:43:28 -07:00
Christopher Hiller 887cb449e6 chore(eslint-config-appium): upgrade to ESLint v8
BREAKING CHANGE:

This change upgrades to ESLint v8 and removes `@babel/eslint-parser`, which is only necessary if we're using mid-stage TC39 proposals (which we aren't).  I think `@babel/core` was a peer dep of `@babel/eslint-parser`, so I removed that too.  And removed cruft from the main configuration.

ESLint rules _very likely_ had breaking changes, but I didn't experience any on our codebase.  However, this version of ESLint seems to be incompatible with `gulp-eslint`, so `@appium/gulp-plugins` should be held back from upgrading.

In addition:

- Updated `README.md`
- Updated some fields in `package.json`
- Loosened `peerDependencies`, as they're supposed to be loose.
2022-08-10 11:36:28 -07:00
Jonathan Lipps c58f32441d Publish
- appium@2.0.0-beta.43
 - @appium/base-driver@8.7.0
 - @appium/base-plugin@1.10.2
 - @appium/doctor@1.16.25
 - @appium/docutils@0.0.10
 - @appium/fake-driver@4.2.0
 - @appium/fake-plugin@2.1.0
 - @appium/gulp-plugins@7.0.5
 - @appium/images-plugin@1.3.5
 - @appium/support@2.59.5
 - @appium/test-support@1.5.0
 - @appium/types@0.4.0
2022-08-10 07:45:24 -07:00
Jonathan Lipps 325f9900cc chore(appium): don't lint autogenerated docs assets js 2022-08-10 07:42:44 -07:00
Jonathan LippsandChristopher Hiller 337ec3e7ba feat(base-driver,fake-driver,appium): add convenience methods for defining execute script overloads (#17321)
* feat(base-driver,fake-driver,appium): add convenience methods for defining execute script overloads

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* review

* review

Co-authored-by: Christopher Hiller <boneskull@boneskull.com>
2022-08-09 20:47:47 -07:00
Christopher Hiller 888aac85ff fix(support): add missing @appium/types
This package was missing `@appium/types`, which means packages consuming `@appium/support` which _don't_ also consume `appium` in some capacity will have types that won't compile, e.g., `teen_process`.
2022-08-09 15:26:20 -07:00
Christopher Hiller ee0a579ddc chore(gulp-plugins): remove unused babel-eslint 2022-08-09 10:49:44 -07:00
Christopher Hiller 8d6ffe0932 fix(support): path to APPIUM_HOME must always be absolute
Closes #17338
2022-08-09 10:49:32 -07:00
Christopher Hiller d4b983328a feat(appium,base-driver,fake-driver,fake-plugin,test-support,types): updateServer receives cliArgs param
This PR passes a `cliArgs` parameter to the static method `updateServer` of drivers and plugins so they can take action on the server configuration based on the content of the arguments.

Note that `cliArgs` is a misnomer, because these args can also come from configuration files.

Closes #17304
2022-08-08 14:00:58 -07:00
renovate[bot] ce7b4520a4 chore(): update dependency @types/chai to v4.3.3 2022-08-08 11:54:58 +00:00
Christopher Hiller dc4835f293 fix(appium): fix busted config file normalization algorithm
I'm not sure _what_ happened here other than "the types weren't strict enough" otherwise I would have noticed this earlier.  Anyway; the check to see if we should dig into the config object to normalize the keys was getting passed _something_ but that _something_ wasn't anything it expected.

As a result, I needed to update some tests which assumed `readConfigFile()` resolved w/ an identity w/r/t the config file itself.  It doesn't give the config file back verbatim any longer; the result will be normalized to camel-case keys (or whatever the `appiumCliDest` property is, if it exists)
2022-08-04 15:38:52 -07:00
Christopher Hiller dc730e7307 chore(): ignore local_appium_home in wallaby tests 2022-08-04 15:38:52 -07:00
Christopher Hiller ec79486df0 chore(types): enable ESM interop by default
for stuff that is type-checked but not part of the actual build. this was driving me nuts, sorry.
2022-08-04 15:38:52 -07:00
ian zhang b5ea03ae85 docs: Update Edit URL and Fix typo of MJSONWP (#17315)
As branch 2.0 won't there any more , just replace with master as latest resource
2022-08-04 14:10:51 -07:00
Jonathan Lipps 3c6633eaa1 docs(appium): restore config developer docs (#17312) 2022-08-03 16:56:08 -07:00
Jonathan LippsandChristopher Hiller bf53db9fac docs(appium): reorganize migration doc info (#17311)
* docs(appium): reorganize migration doc info so that appropriate bits are in their own docs

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* Update packages/appium/docs/en/ecosystem/build-drivers.md

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

* Update packages/appium/docs/en/guides/caps.md

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

* Update packages/appium/docs/en/guides/caps.md

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

* Update packages/appium/docs/en/guides/config.md

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

* Update packages/appium/docs/en/guides/config.md

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

* Update packages/appium/docs/en/guides/config.md

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

* Update packages/appium/docs/en/guides/config.md

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

* Update packages/appium/docs/en/guides/config.md

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

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

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

* add header for extensions

Co-authored-by: Christopher Hiller <boneskull@boneskull.com>
2022-08-03 15:45:01 -07:00
Jonathan Lipps fd57ff03a4 docs(appium): actually fix link to new docs 2022-08-03 13:13:14 -07:00
Jonathan Lipps 167fdb18f7 docs(appium): fix link to new docs in old docs dir 2022-08-03 13:12:48 -07:00
Jonathan LippsandKazuaki Matsuo 50964e0690 docs(appium): migrate the migration docs and fix urls (#17306)
* docs(appium): migrate the migration docs and fix urls

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

Co-authored-by: Kazuaki Matsuo <fly.49.89.over@gmail.com>

* docs(appium): migrate the migration docs and fix urls

Co-authored-by: Kazuaki Matsuo <fly.49.89.over@gmail.com>
2022-08-03 11:46:48 -07:00
renovate[bot] a2385f08d5 chore(images-plugin): update dependency lru-cache to v7.13.2 2022-08-03 13:39:58 +00:00
Jonathan Lipps 500c08f392 Publish
- appium@2.0.0-beta.42
 - @appium/base-driver@8.6.1
 - @appium/base-plugin@1.10.1
 - @appium/doctor@1.16.24
 - @appium/docutils@0.0.9
 - @appium/eslint-config-appium@6.0.4
 - @appium/execute-driver-plugin@2.0.4
 - @appium/fake-driver@4.1.0
 - @appium/fake-plugin@2.0.5
 - @appium/gulp-plugins@7.0.4
 - @appium/images-plugin@1.3.4
 - @appium/opencv@1.0.11
 - @appium/relaxed-caps-plugin@1.0.0-beta.11
 - @appium/schema@0.0.9
 - @appium/support@2.59.4
 - @appium/test-support@1.4.1
 - @appium/types@0.3.1
 - @appium/universal-xml-plugin@1.0.0-beta.13
2022-08-02 20:21:17 -07:00
Christopher Hiller 4655302239 test(appium): add smoke test for driver installation
This adds a test (which currently only runs automatically in CI) which installs the uiautomator2 driver.  This will ensure that driver installation does not only work in a dev environment!

Due to needing to support Windows and not wishing to befoul the default `APPIUM_HOME`, we need `cross-env` as a production dependency (otherwise the smoke test would fail) to set a temporary `APPIUM_HOME` (`local_appium_home`) which is relative to the temp dir used by the smoke test (see https://github.com/boneskull/nodejs-production-test-action)
2022-08-02 17:21:14 -07:00
Christopher Hiller 5efd62af08 chore(ci): fix target branch for FOSSA job 2022-08-02 16:18:39 -07:00
Christopher Hiller 27ac2fa125 fix(appium): add npm version check
Also fix duplication of error messages about engines.

Unfortunately this adds another startup delay, but I'm unsure of a better way around it.  I guess bundling our own npm might work!
2022-08-02 14:43:25 -07:00
Christopher Hiller d8d2382327 fix(appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin): update engines
also add a missing dep in `@appium/schema`
2022-08-02 14:22:44 -07:00
Christopher Hiller faff3ce347 feat(appium): pass unknown args to extension scripts
Resolves #17250

This change passes any extra unknown arguments to extension scripts _if and only if_ `appium <extension> <name> run` is executed.  In all other cases, unknown arguments result in an error and the `appium` CLI will abort as before.

Example:

```bash
appium driver fake run fake-success --foo --bar`
```

In the `fake-success` script of the `fake` driver, `process.argv.slice(2)` will be `['--foo', '--bar']`.  Extra arguments can appear anywhere after the `appium` executable in the command and do not need to appear after the script name.

- In addition, added some missing arguments for extension subcommands to the types (`json`, `suppressOutput`).  Extra arguments are stored in property `extraArgs`.
- Fixed a type problem surfaced by the previous item
- Changed help text for the `run` subcommand to hopefully read more clearly
- Reorganized E2E tests for the `run` subcommand
- Slightly changed error output when unknown argument is provided (given that we now need to DIY this)
2022-08-02 10:55:11 -07:00
renovate[bot] 05b981afa2 chore(): update babel monorepo to v7.18.10 2022-08-02 13:51:09 +00:00
Christopher Hiller 5e047485ee chore(renovate): add "del", "get-port", "inquirer", "vinyl-paths" to exclusion list
these packages are now ESM-only
2022-08-01 14:36:28 -07:00
Christopher Hiller b7e905eaf1 fix(appium): partially revert 2a6a056187
This removes the symlink we were creating to avoid an extra install of `appium` in `APPIUM_HOME`.  Using a symlink obviously breaks stuff, because (for whatever reason) `npm` wants to run lthe `prepare` lifecycle script in the symlinked `appium`, which will not work outside of a dev environment due to missing dev depenencies.

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

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