Compare commits

...
Author SHA1 Message Date
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
147 changed files with 5119 additions and 1776 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
+7 -9
View File
@@ -28,13 +28,7 @@ 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:
@@ -43,7 +37,9 @@ jobs:
steps:
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # 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
@@ -56,6 +52,8 @@ jobs:
steps:
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # 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
+6 -5
View File
@@ -13,11 +13,12 @@ jobs:
steps:
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # 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,
},
+6 -1
View File
@@ -13,7 +13,11 @@ 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).
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
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/).
### Drivers
@@ -130,6 +134,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
-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)
+10 -4
View File
@@ -1,8 +1,14 @@
# parameters: # No params yet, but may add later
parameters:
vmImage: 'macOS-latest'
bundleNodeJSVersion: '16.17.0'
nodeJSVersion: '16.x'
jobs:
- job: bundle_and_upload_appium_zip
pool:
vmImage: 'macOS 10.14'
vmImage: ${{ parameters.vmImage }}
variables:
CC: gcc
CXX: g++
@@ -11,7 +17,7 @@ jobs:
displayName: Print GCC Version
- task: NodeTool@0
inputs:
versionSpec: '12.x'
versionSpec: ${{ parameters.nodeJSVersion }}
- script: npm install
displayName: Install NPM Dependencies
- script: npm run build
@@ -19,13 +25,13 @@ jobs:
- 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
curl -o node-darwin.tar.gz https://nodejs.org/dist/v${{ parameters.bundleNodeJSVersion }}/node-v${{ parameters.bundleNodeJSVersion }}-darwin-x64.tar.gz
mkdir node-darwin-unpacked
tar -xzf node-darwin.tar.gz -C node-darwin-unpacked/ --strip-components 1
rm -f node-darwin.tar.gz
cp node-darwin-unpacked/bin/node node-darwin
rm -rf node-darwin-unpacked
displayName: Download NodeJS
displayName: Download NodeJS to bundle it
- script: npm run zip
displayName: Create the bundle zip
- script: npm install
+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.
+1518 -1277
View File
File diff suppressed because it is too large Load Diff
+18 -15
View File
@@ -53,9 +53,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 install-fake-driver 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",
@@ -83,6 +84,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 +92,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 +104,18 @@
},
"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.18.10",
"@babel/core": "7.19.0",
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/preset-env": "7.19.0",
"@babel/register": "7.18.9",
"@babel/runtime": "7.18.9",
"@babel/runtime": "7.19.0",
"@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/node": "16.11.57",
"@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.23.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.0.1",
"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",
@@ -154,13 +157,13 @@
"through2": "4.0.2",
"typescript": "4.7.4",
"validate.js": "0.13.1",
"webdriverio": "7.20.7",
"webdriverio": "7.24.0",
"ws": "8.8.1",
"yaml-js": "0.3.1"
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public"
+65
View File
@@ -3,6 +3,71 @@
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.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)
@@ -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,206 @@
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`), 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
```
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_
TODO
#### :tada: _Client Plugins_
TODO
### :tada: Install drivers and plugins from anywhere
TODO
### :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!
+4 -2
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
@@ -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;
+5
View File
@@ -783,6 +783,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() : [];
+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.`
);
}
}
+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;
@@ -94,8 +94,9 @@ export const transformers = {
json = readFileSync(jsonOrPath, 'utf8');
loadedFromFile = true;
} catch (err) {
// unreadable files don't count... but other problems do.
if (err.code !== 'ENOENT') {
// unreadable files don't count.
// also `ENAMETOOLONG` can happen if we try to open a file that's a huge JSON string.
if (err.code !== 'ENOENT' && err.code !== 'ENAMETOOLONG') {
throw err;
}
}
+60
View File
@@ -2,6 +2,8 @@ import _ from 'lodash';
import logger from './logger';
import {processCapabilities, PROTOCOLS} from '@appium/base-driver';
import {inspect as dump} from 'util';
import {node} from '@appium/support';
import path from 'path';
const W3C_APPIUM_PREFIX = 'appium';
@@ -209,6 +211,63 @@ function getPackageVersion(pkgName) {
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) {
logger.info(`Module init paths cannot be refreshed. Original error: ${e.message}`);
return false;
}
};
if (!process.env.NODE_PATH) {
process.env.NODE_PATH = nodeModulesRoot;
if (refreshRequirePaths()) {
logger.info(`Setting NODE_PATH to '${nodeModulesRoot}'`);
process.env.APPIUM_OMIT_PEER_DEPS = '1';
} else {
delete process.env.NODE_PATH;
}
return;
}
const nodePathParts = process.env.NODE_PATH.split(path.delimiter);
if (nodePathParts.includes(nodeModulesRoot)) {
logger.info(`NODE_PATH already includes '${nodeModulesRoot}'`);
process.env.APPIUM_OMIT_PEER_DEPS = '1';
return;
}
nodePathParts.push(nodeModulesRoot);
process.env.NODE_PATH = nodePathParts.join(path.delimiter);
if (refreshRequirePaths()) {
logger.info(`Adding '${nodeModulesRoot}' to NODE_PATH`);
process.env.APPIUM_OMIT_PEER_DEPS = '1';
} else {
process.env.NODE_PATH = _.without(nodePathParts, nodeModulesRoot).join(path.delimiter);
}
}
/**
* Pulls the initial values of Appium settings from the given capabilities argument.
* Each setting item must satisfy the following format:
@@ -250,6 +309,7 @@ export {
getPackageVersion,
pullSettings,
removeAppiumPrefixes,
adjustNodePath,
};
/**
+14 -15
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.0.0-beta.41",
"version": "2.0.0-beta.44",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -37,7 +37,6 @@
"driver.*",
"support.*",
"plugin.*",
"test.*",
"scripts/autoinstall-extensions.js",
"types"
],
@@ -54,26 +53,25 @@
"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",
"@appium/base-driver": "^8.7.1",
"@appium/base-plugin": "^1.10.3",
"@appium/docutils": "^0.0.11",
"@appium/schema": "^0.0.9",
"@appium/support": "^2.60.0",
"@appium/types": "^0.4.1",
"@babel/runtime": "7.19.0",
"@sidvind/better-ajv-errors": "2.0.0",
"@types/argparse": "2.0.10",
"@types/bluebird": "3.5.36",
"@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,6 +81,7 @@
"asyncbox": "2.9.2",
"axios": "0.27.2",
"bluebird": "3.7.2",
"cross-env": "7.0.3",
"find-up": "5.0.0",
"lilconfig": "2.0.6",
"lodash": "4.17.21",
@@ -94,14 +93,14 @@
"semver": "7.3.7",
"source-map-support": "0.5.21",
"teen_process": "1.16.0",
"type-fest": "2.17.0",
"type-fest": "2.19.0",
"winston": "3.8.1",
"wrap-ansi": "7.0.0",
"yaml": "2.1.1"
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public",
-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');
+101 -19
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;
@@ -152,7 +171,7 @@ describe('CLI behavior', function () {
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');
expect(newPkg).to.have.nested.property('devDependencies.@appium/test-driver');
});
it('should update the hash', async function () {
@@ -170,7 +189,7 @@ describe('CLI behavior', function () {
it('should actually install both drivers', function () {
expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw;
expect(() => resolveFrom(appiumHome, '@appium/test-driver')).not.to.throw;
});
});
});
@@ -267,6 +286,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 +465,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 +550,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
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "test-driver",
"name": "@appium/test-driver",
"version": "1.0.0",
"private": true,
"peerDependencies": {
+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},
+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[];
}
/**
+31
View File
@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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)
@@ -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;
}
/**
+5 -1
View File
@@ -27,6 +27,9 @@ class DriverCore {
*/
static baseVersion = BASEDRIVER_VER;
/** @type {ExecuteMethodMap} */
static executeMethodMap = {};
/**
* @type {string?}
*/
@@ -348,7 +351,7 @@ class DriverCore {
/**
*
* @param {string} sessionId
* @returns {[string, RegExp][]}
* @returns {import('@appium/types').RouteMatcher[]}
*/
getProxyAvoidList(sessionId) {
return [];
@@ -423,6 +426,7 @@ export {DriverCore};
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
* @typedef {import('@appium/types').Driver} Driver
* @typedef {import('@appium/types').Core} Core
* @typedef {import('@appium/types').ExecuteMethodMap} ExecuteMethodMap
* @typedef {import('@appium/types').ServerArgs} ServerArgs
* @typedef {import('@appium/types').EventHistory} EventHistory
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
+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]
+4
View File
@@ -37,3 +37,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
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "8.6.0",
"version": "8.7.1",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -45,8 +45,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^2.59.3",
"@babel/runtime": "7.18.9",
"@appium/support": "^2.60.0",
"@babel/runtime": "7.19.0",
"@colors/colors": "1.5.0",
"@types/async-lock": "1.1.5",
"@types/bluebird": "3.5.36",
@@ -62,17 +62,17 @@
"express": "4.18.1",
"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": "2.19.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;
@@ -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,5 +1,5 @@
import BaseDriver from '../../../lib';
import {driverUnitTestSuite} from '@appium/test-support';
import {driverUnitTestSuite} from '@appium/driver-test-support';
driverUnitTestSuite(BaseDriver, {
platformName: 'iOS',
@@ -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"}
]
}
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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.3",
"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.60.0"
},
"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"
]
}
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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)
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/doctor",
"version": "1.16.23",
"version": "1.16.26",
"description": "Test environment for fitness to run Appium",
"keywords": [
"automation",
@@ -49,12 +49,12 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^2.59.3",
"@babel/runtime": "7.18.9",
"@appium/support": "^2.60.0",
"@babel/runtime": "7.19.0",
"@colors/colors": "1.5.0",
"@types/bluebird": "3.5.36",
"@types/teen_process": "1.16.1",
"appium-adb": "9.10.1",
"appium-adb": "9.10.9",
"bluebird": "3.7.2",
"inquirer": "8.2.4",
"lodash": "4.17.21",
@@ -64,7 +64,7 @@
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public"
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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 -7
View File
@@ -1,10 +1,16 @@
{
"name": "@appium/docutils",
"version": "0.0.8",
"version": "0.0.11",
"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,17 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^2.59.3",
"@babel/runtime": "7.18.9",
"@appium/support": "^2.60.0",
"@babel/runtime": "7.19.0",
"docdash": "1.2.0",
"jsdoc": "3.6.11",
"jsdoc-plugin-typescript": "2.1.0",
"jsdoc-plugin-typescript": "2.1.1",
"source-map-support": "0.5.21",
"teen_process": "1.16.0"
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public"
+11
View File
@@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# 0.2.0 (2022-09-07)
### Features
* **driver-test-support:** create package ([4128972](https://github.com/appium/appium/commit/41289724dc860e28dbe9a34710fa2d07d4a46e61))
+112
View File
@@ -0,0 +1,112 @@
# @appium/driver-test-support
> Testing utilities for [Appium](https://appium.io) drivers
This package is for driver authors to help test their drivers.
[Mocha](https://mochajs.org) is the supported test framework.
## Usage
### For E2E Tests
The `driverE2ETestSuite` method creates a Mocha test suite which makes HTTP requests to an in-memory server leveraging your driver.
Note that this method must be run within a _suite callback_—not a _test callback_.
```js
import {driverE2ETestSuite} from '@appium/driver-test-support';
const defaultW3CCapabilities = {
// some capabilities
};
describe('MyDriverClass', function() {
driverE2ETestSuite(MyDriverClass, defaultW3CCapabilities);
describe('more tests', function() {
// ...
});
});
```
### For Unit Tests
The `driverUnitTestSuite` method creates a Mocha test suite which performs assertions on an isolated instance of your driver.
Note that this method must be run within a _suite callback_—not a _test callback_.
```js
import {driverUnitTestSuite} from '@appium/driver-test-support';
const defaultW3CCapabilities = {
// some capabilities
};
describe('MyDriverClass', function() {
driverUnitTestSuite(MyDriverClass, defaultW3CCapabilities);
describe('more tests', function() {
// ...
});
});
```
### Helpers
These are just some helpers (mainly for E2E tests):
```js
import {TEST_HOST, getTestPort, createAppiumURL} from '@appium/driver-test-support';
import assert from 'node:assert';
import _ from 'lodash';
describe('TEST_HOST', function() {
it('should be localhost', function() {
assert.strictEqual(TEST_HOST, '127.0.0.1');
});
});
describe('getTestPort()', function() {
it('should get a free test port', async function() {
const port = await getTestPort();
assert.ok(port > 0);
});
});
describe('createAppiumURL()', function() {
it('should create a "new session" URL', function() {
const actual = createAppiumURL(TEST_HOST, 31337, '', 'session');
const expected = `http://${TEST_HOST}:31337/session`;
assert.strictEqual(actual, expected);
});
it('should create a URL to get an existing session', function() {
const sessionId = '12345';
const createGetSessionURL = createAppiumURL(TEST_HOST, 31337, _, 'session');
const actual = createGetSessionURL(sessionId);
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/session`;
assert.strictEqual(actual, expected);
});
it('should create a URL for a command using an existing session', function() {
const sessionId = '12345';
const createURLWithPath = createAppiumURL('127.0.0.1', 31337, sessionId);
const actual = createURLWithPath('moocow');
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/moocow`;
assert.strictEqual(actual, expected);
});
});
```
## Installation
`appium` and `mocha` are peer dependencies.
```bash
npm install appium mocha @appium/driver-test-support --save-dev
```
## License
Apache-2.0
+1
View File
@@ -0,0 +1 @@
module.exports = require('./build/lib');
@@ -136,6 +136,8 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
routeConfiguringFunction: routeConfiguringFunction(d),
port,
hostname: address,
// @ts-expect-error
cliArgs: {},
});
({startSession, getSession, endSession, newSessionURL, getCommand, postCommand} =
createSessionHelpers(port, address));
@@ -0,0 +1,9 @@
export * from './e2e-suite';
export * from './unit-suite';
export * from './helpers';
/**
* @typedef {import('@appium/types').DriverClass} DriverClass
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
*/
@@ -1,6 +1,5 @@
import _ from 'lodash';
import B from 'bluebird';
import {DeviceSettings} from '@appium/base-driver';
import {createSandbox} from 'sinon';
import chai from 'chai';
@@ -448,6 +447,7 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
avoidStub.returns([
['POST', /^foo/],
// @ts-expect-error
['BAZETE', /^bar/],
]);
(() => {
@@ -581,16 +581,6 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
});
});
describe('DeviceSettings', function () {
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';
d1.getSettings().should.not.eql(d2.getSettings());
});
});
describe('.isFeatureEnabled', function () {
let d;
+69
View File
@@ -0,0 +1,69 @@
{
"name": "@appium/driver-test-support",
"version": "0.2.0",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"homepage": "https://appium.io",
"bugs": {
"url": "https://github.com/appium/appium/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/appium/appium.git",
"directory": "packages/driver-test-support"
},
"license": "Apache-2.0",
"author": "https://github.com/appium",
"main": "index.js",
"directories": {
"lib": "lib"
},
"files": [
"index.js",
"lib",
"build"
],
"scripts": {
"build": "babel lib --root-mode=upward --out-dir=build/lib",
"dev": "npm run build -- --watch",
"fix": "npm run lint -- --fix",
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
"prepare": "npm run build",
"test": "npm run test:unit",
"test:smoke": "node ./index.js",
"test:unit": "mocha \"test/unit/**/*.spec.js\""
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^0.4.1",
"@babel/runtime": "7.19.0",
"@types/lodash": "4.14.184",
"axios": "0.27.2",
"bluebird": "3.7.2",
"chai": "4.3.6",
"get-port": "5.1.1",
"lodash": "4.17.21",
"sinon": "14.0.0",
"source-map-support": "0.5.21"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
"mocha": "*"
},
"engines": {
"node": ">=14",
"npm": ">=8"
},
"publishConfig": {
"access": "public"
}
}
@@ -0,0 +1,41 @@
import {TEST_HOST, getTestPort, createAppiumURL} from '../../lib';
import _ from 'lodash';
const {expect} = chai;
describe('TEST_HOST', function () {
it('should be localhost', function () {
expect(TEST_HOST).to.equal('127.0.0.1');
});
});
describe('getTestPort()', function () {
it('should get a free test port', async function () {
const port = await getTestPort();
expect(port).to.be.a('number');
});
});
describe('createAppiumURL()', function () {
it('should create a "new session" URL', function () {
const actual = createAppiumURL(TEST_HOST, 31337, '', 'session');
const expected = `http://${TEST_HOST}:31337/session`;
expect(actual).to.equal(expected);
});
it('should create a URL to get an existing session', function () {
const sessionId = '12345';
const createGetSessionURL = createAppiumURL(TEST_HOST, 31337, _, 'session');
const actual = createGetSessionURL(sessionId);
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/session`;
expect(actual).to.equal(expected);
});
it('should create a URL for a command using an existing session', function () {
const sessionId = '12345';
const createURLWithPath = createAppiumURL('127.0.0.1', 31337, sessionId);
const actual = createURLWithPath('moocow');
const expected = `http://${TEST_HOST}:31337/session/${sessionId}/moocow`;
expect(actual).to.equal(expected);
});
});
@@ -0,0 +1,20 @@
{
"extends": "../../config/tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"checkJs": true,
"paths": {
"@appium/types": ["../types"],
"appium/driver": ["../base-driver"],
},
"types": [
"mocha", "chai", "chai-as-promised"
]
},
"include": ["./lib"],
"references": [
{"path": "../types"},
{"path": "../base-driver"}
]
}
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [7.0.0](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.4...@appium/eslint-config-appium@7.0.0) (2022-09-07)
### chore
* **eslint-config-appium:** upgrade to ESLint v8 ([887cb44](https://github.com/appium/appium/commit/887cb449e61be585c084af2f6422d2e02be5028b))
### BREAKING CHANGES
* **eslint-config-appium:** 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.
## [6.0.4](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.3...@appium/eslint-config-appium@6.0.4) (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))
## [6.0.3](https://github.com/appium/appium/compare/@appium/eslint-config-appium@6.0.2...@appium/eslint-config-appium@6.0.3) (2022-07-28)
**Note:** Version bump only for package @appium/eslint-config-appium
+20 -19
View File
@@ -1,18 +1,16 @@
## Appium ESLint Shared Configuation
# @appium/eslint-config-appium
This package works to provide [eslint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) to replace the need for a local `.eslintrc` file.
> Provides a reusable [ESLint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) for [Appium](https://github.com/appium/appium) and Appium-adjacent projects.
It uses [babel-eslint](https://github.com/babel/babel-eslint) as a parser.
## Usage
### Usage
Install the package
Install the package with **`npm` v7 or newer** (for automatic installation of peer dependencies):
```bash
npm install @appium/eslint-config-appium eslint-plugin-import eslint-plugin-mocha eslint-plugin-promise -D
npm install @appium/eslint-config-appium --save-dev
```
And then, in your `.eslintrc` file extend the configuration
And then, in your `.eslintrc` file, extend the configuration:
```json
{
@@ -20,18 +18,21 @@ And then, in your `.eslintrc` file extend the configuration
}
```
If you are using [@appium/gulp-plugins](https://www.npmjs.com/package/@appium/gulp-plugins) you can then run
## Peer Dependencies
```bash
gulp eslint
```
This config requires the following packages be installed (as dev dependencies) in your project. See the `package.json` for the required versions.
### Peer Dependencies
- [eslint](https://www.npmjs.com/package/eslint)
- [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
- [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)
- [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
- [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
This config requires the following packages be installed (as dev dependencies) in your project:
## Notes
* [babel-eslint](https://www.npmjs.com/package/babel-eslint)
* [eslint](https://www.npmjs.com/package/eslint)
* [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)
* [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
* [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
- This configuration is intended to be used alongside [Prettier](https://www.npmjs.com/package/prettier).
- This package was previously published as `eslint-config-appium`.
## License
Copyright © 2016 OpenJS Foundation. Licensed Apache-2.0
+1 -7
View File
@@ -1,18 +1,12 @@
module.exports = {
parser: '@babel/eslint-parser',
parserOptions: {
requireConfigFile: false,
ecmaVersion: 12,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
experimentalObjectRestSpread: true,
},
},
env: {
node: true,
mocha: true,
es6: true,
es2022: true,
},
plugins: ['import', 'mocha', 'promise'],
globals: {
+9 -12
View File
@@ -1,12 +1,11 @@
{
"name": "@appium/eslint-config-appium",
"version": "6.0.3",
"description": "Shared config for Appium's eslint style",
"version": "7.0.0",
"description": "Shared ESLint config for Appium projects",
"keywords": [
"eslint",
"eslintconfig",
"appium",
"es2015"
"appium"
],
"homepage": "https://appium.io",
"bugs": {
@@ -32,17 +31,15 @@
"test:smoke": "node ./index.js"
},
"peerDependencies": {
"@babel/core": "7.18.9",
"@babel/eslint-parser": "7.18.9",
"eslint": "7.32.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-mocha": "9.0.0",
"eslint-plugin-promise": "6.0.0"
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-promise": "^6.0.0"
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public"
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.0.5](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.4...@appium/execute-driver-plugin@2.0.5) (2022-09-07)
**Note:** Version bump only for package @appium/execute-driver-plugin
## [2.0.4](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.3...@appium/execute-driver-plugin@2.0.4) (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))
## [2.0.3](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@2.0.2...@appium/execute-driver-plugin@2.0.3) (2022-07-28)
+12 -7
View File
@@ -1,11 +1,16 @@
{
"name": "@appium/execute-driver-plugin",
"version": "2.0.3",
"version": "2.0.5",
"description": "Plugin for batching and executing driver commands with Appiums",
"keywords": [
"appium",
"automation",
"webdriver"
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"homepage": "https://appium.io",
"bugs": {
@@ -35,13 +40,13 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@babel/runtime": "7.18.9",
"@babel/runtime": "7.19.0",
"@types/bluebird": "3.5.36",
"bluebird": "3.7.2",
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"vm2": "3.9.10",
"webdriverio": "7.20.7"
"vm2": "3.9.11",
"webdriverio": "7.24.0"
},
"peerDependencies": {
"appium": "^2.0.0-beta.35"
@@ -56,6 +61,6 @@
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72",
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
}
}
@@ -2,7 +2,7 @@
import path from 'path';
import {pluginE2EHarness} from 'appium/test';
import {pluginE2EHarness} from '@appium/plugin-test-support';
import {remote as wdio} from 'webdriverio';
import {W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY} from '../../lib/execute-child';
import {fs} from 'appium/support';
@@ -39,7 +39,7 @@ describe('ExecuteDriverPlugin', function () {
const basicScript = `return 'foo'`;
/**
* @type {import('appium/test').E2ESetupOpts}
* @type {import('@appium/plugin-test-support').E2ESetupOpts}
*/
const e2eSetupOpts = {
before,
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.2.1](https://github.com/appium/appium/compare/@appium/fake-driver@4.2.0...@appium/fake-driver@4.2.1) (2022-09-07)
**Note:** Version bump only for package @appium/fake-driver
# [4.2.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.1.0...@appium/fake-driver@4.2.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))
# [4.1.0](https://github.com/appium/appium/compare/@appium/fake-driver@4.0.2...@appium/fake-driver@4.1.0) (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))
### 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)
## [4.0.2](https://github.com/appium/appium/compare/@appium/fake-driver@4.0.1...@appium/fake-driver@4.0.2) (2022-07-28)
@@ -69,6 +69,14 @@ commands.getLog = async function getLog(type) {
}
};
commands.execute = async function execute(script, args) {
return await this.executeMethod(script, args);
};
commands.fakeAddition = async function fakeAddition(num1, num2, num3) {
return num1 + num2 + (num3 ?? 0);
};
Object.assign(extensions, commands, helpers);
export {commands, helpers};
export default extensions;
+18 -1
View File
@@ -113,13 +113,30 @@ class FakeDriver extends BaseDriver {
},
};
static executeMethodMap = {
'fake: addition': {
command: 'fakeAddition',
params: {required: ['num1', 'num2'], optional: ['num3']},
},
'fake: getThing': {
command: 'getFakeThing',
},
'fake: setThing': {
command: 'setFakeThing',
params: {required: ['thing']},
},
};
static fakeRoute(req, res) {
res.send(JSON.stringify({fakedriver: 'fakeResponse'}));
}
static async updateServer(expressApp /*, httpServer*/) {
static async updateServer(expressApp, httpServer, cliArgs) {
// eslint-disable-line require-await
expressApp.all('/fakedriver', FakeDriver.fakeRoute);
expressApp.all('/fakedriverCliArgs', (req, res) => {
res.send(JSON.stringify(cliArgs));
});
}
}
-2
View File
@@ -1,5 +1,3 @@
// transpile:main
import * as driver from './driver';
import * as server from './server';
@@ -1,3 +1,5 @@
import log from '../logger';
log.info('Successfully ran the script');
log.info(process.argv.slice(2).join(' '));
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-driver",
"version": "4.0.2",
"version": "4.2.1",
"description": "Mock driver used internally by Appium for testing. Ignore",
"keywords": [
"automation",
@@ -47,7 +47,7 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@babel/runtime": "7.18.9",
"@babel/runtime": "7.19.0",
"@types/bluebird": "3.5.36",
"@xmldom/xmldom": "0.8.2",
"asyncbox": "2.9.2",
@@ -61,7 +61,7 @@
},
"engines": {
"node": ">=14",
"npm": ">=6"
"npm": ">=8"
},
"publishConfig": {
"access": "public"
@@ -80,5 +80,6 @@
"fake-success": "./build/lib/scripts/fake-success.js"
}
},
"types": "./build/lib/index.d.ts",
"gitHead": "5c7af8ee73078018e4ec52fccf19fe3f77249d72"
}
@@ -1,5 +1,5 @@
import axios from 'axios';
import {driverE2ETestSuite} from 'appium/test';
import {driverE2ETestSuite} from '@appium/driver-test-support';
import {FakeDriver, startServer} from '../../lib/index.js';
import {
BASE_CAPS,
@@ -106,6 +106,45 @@ function generalTests() {
res[0].type.should.eql('pointer');
res[0].actions.should.have.length(2);
});
it('should get and set a fake thing via execute overloads', async function () {
let thing = await driver.executeScript('fake: getThing', []);
should.not.exist(thing);
await driver.executeScript('fake: setThing', [{thing: 1234}]);
thing = await driver.executeScript('fake: getThing', []);
thing.should.eql(1234);
});
it('should add 2 numbers via execute overloads', async function () {
await driver.executeScript('fake: addition', [{num1: 2, num2: 3}]).should.eventually.eql(5);
await driver
.executeScript('fake: addition', [{num1: 2, num2: 3, num3: 4}])
.should.eventually.eql(9);
});
it('should throw not implemented if an execute overload isnt supported', async function () {
await driver
.executeScript('fake: blarg', [])
.should.be.rejectedWith(/Unsupported execute method/);
});
it('should throw an error if a required overload param is missing', async function () {
await driver
.executeScript('fake: addition', [{num3: 4}])
.should.be.rejectedWith(/Parameters were incorrect/);
});
it('should throw an error if sending in wrong types of params', async function () {
await driver
.executeScript('fake: addition', [4, 5])
.should.be.rejectedWith(/correct format of arg/);
await driver
.executeScript('fake: addition', [4])
.should.be.rejectedWith(/not receive an appropriate execute/);
await driver
.executeScript('fake: addition', [{num1: 2}, {extra: 'bad'}])
.should.be.rejectedWith(/correct format of arg/);
});
});
}

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