Compare commits

..
Author SHA1 Message Date
Jonathan Lipps ec6a90a797 Publish
- appium@2.0.0-beta.13
 - @appium/base-driver@8.0.0-beta.7
 - @appium/doctor@1.16.1
 - @appium/eslint-config-appium@4.7.1
 - @appium/fake-driver@3.0.0
 - @appium/gulp-plugins@5.5.1
 - @appium/support@2.54.0
 - @appium/test-support@1.3.4
2021-08-16 15:48:44 -07:00
Jonathan Lipps 2fb08bbd7f chore: set lerna release type to github 2021-08-16 15:48:00 -07:00
Jonathan Lipps c5bcd82263 docs(appium): add 2.x migration doc with all current known breaking changes 2021-08-16 12:59:23 -07:00
Jonathan Lipps 7f8e62e983 docs(appium): update appium 1.x eol doc 2021-08-16 12:59:23 -07:00
Mykola Mokhnach 8fa1cee8af docs(appium): create Appium 1 EOL plan 2021-08-16 12:59:23 -07:00
Christopher Hiller 07a854acd2 chore: remove unused scripts/ dir 2021-08-13 12:40:17 -07:00
Christopher Hiller cfb1401d0f docs(contributing): add new release/publishing instructions; closes #15432 2021-08-13 12:40:17 -07:00
Christopher Hiller 39b8fd5476 chore: update lerna config to automatically push tags & create a GH release 2021-08-13 12:40:17 -07:00
Christopher Hiller ee00285b38 chore: global chai
This PR makes `chai` and `should()` _globals_ when using Mocha:

- `chai` is configured with `chai-as-promised`
- A `.mocharc.js` requires `./test/setup.js` _from the monorepo root_.
- Updated the ESLint config to recognize the globals.
- Updated Wallaby config

All instances of `import chai...` and the like have been removed from all test files.
2021-08-13 10:40:52 -07:00
Renovate Bot b514971e99 chore(gulp-plugins): update dependency @types/mocha to ^8.2.3 2021-08-13 14:41:53 +00:00
Renovate Bot 549defda91 chore(gulp-plugins): update dependency @octokit/rest to ^18.9.0 2021-08-13 13:48:00 +00:00
Renovate Bot 11c2c802ed chore(gulp-plugins): update babel monorepo 2021-08-13 12:15:51 +00:00
Renovate Bot fc23e07348 chore(fake-driver): update dependency xmldom to ^0.6 2021-08-13 10:36:26 +00:00
Renovate Bot b2233e84a1 chore(fake-driver): update dependency asyncbox to ^2.8.0 2021-08-13 10:08:04 +00:00
Renovate Bot d8d799a19c chore(eslint-config-appium): update dependency eslint-plugin-promise to ^5.1.0 2021-08-13 08:43:48 +00:00
Renovate Bot c3d328cb81 chore(eslint-config-appium): update dependency eslint-plugin-import to ^2.24.0 2021-08-13 05:16:49 +00:00
Renovate Bot 7f57f4ec45 chore(doctor): update dependency inquirer to ^8.1.2 2021-08-13 04:00:48 +00:00
Renovate Bot 5ed2e36b47 chore(doctor): update dependency colors to ^1.4.0 2021-08-13 02:28:57 +00:00
Renovate Bot ab2245dcfb chore(doctor): update dependency authorize-ios to ^1.2.1 2021-08-13 02:01:05 +00:00
Renovate Bot 94bcad8570 chore(doctor): update dependency appium-adb to ^8.13.2 2021-08-13 00:29:18 +00:00
Renovate Bot a6f45e5529 chore(base-driver): update dependency ws to ^7.5.3 2021-08-12 23:23:26 +00:00
Renovate Bot 59222ca0d3 chore(base-driver): update dependency webdriverio to ^6.12.1 2021-08-12 21:58:57 +00:00
Renovate Bot 6b9c778d35 chore(base-driver): update dependency serve-favicon to ^2.5.0 2021-08-12 21:07:23 +00:00
Renovate Bot 8cb6df6952 chore(base-driver): update dependency morgan to ^1.10.0 2021-08-12 19:58:08 +00:00
Renovate Bot 26bcc8db70 chore(base-driver): update dependency express to ^4.17.1 2021-08-12 18:17:05 +00:00
Renovate Bot b81d43978a chore(base-driver): update dependency body-parser to ^1.19.0 2021-08-12 16:49:09 +00:00
Renovate Bot 7095d9e7e6 chore(base-driver): update dependency async-lock to ^1.3.0 2021-08-12 15:24:28 +00:00
Renovate Bot d414b6e8ed chore(appium): update dependency yaml to ^1.10.2 2021-08-12 14:44:10 +00:00
Renovate Bot 5a09fdf722 chore(appium): update dependency ora to ^5.4.1 2021-08-12 13:27:55 +00:00
Renovate Bot bdc9bcbd02 chore(): update dependency lint-staged to ^11.1.2 2021-08-12 12:50:36 +00:00
Renovate Bot f59e79d649 chore(support): update dependency plist to ^3.0.3 2021-08-12 11:34:38 +00:00
Renovate Bot 589d776ab1 chore(): update dependency mjpeg-server to ^0.3.1 2021-08-12 08:25:08 +00:00
Renovate Bot 07787168a7 chore(appium): update dependency @appium/base-plugin to ^1.7.0 2021-08-12 05:22:29 +00:00
Jonathan Lipps 4839419748 fix(appium): when updating extensions, make sure to actually remove the previous one first to avoid npm reinstall issues 2021-08-11 20:05:13 -07:00
Jonathan Lipps 756688bddb fix(appium): when updating extensions, package fields should also be updated in extensions.yaml 2021-08-11 20:05:13 -07:00
Renovate Bot 55a5f6eac5 chore(): update dependency serve-static to ^1.14.1 2021-08-12 01:27:51 +00:00
Renovate Bot 40208145c9 chore(): update dependency handlebars to ^4.7.7 2021-08-11 23:35:31 +00:00
Renovate Bot 4eae53f1b8 chore(): update dependency appium-base-driver to ^7.9.0 2021-08-11 23:06:44 +00:00
Renovate Bot ceb70e1950 chore(test-support): update dependency lodash to ^4.17.21 2021-08-11 21:57:56 +00:00
Renovate Bot 314c597e24 chore(support): update dependency rimraf to ^3.0.2 2021-08-11 20:12:45 +00:00
Christopher Hiller 2ad262b775 chore: unify package root detection & package.json reading
This replaces all of the ad-hoc ways to find the closest `package.json` file (usually for reading the `version` field) with utility functions in the `fs` module of `@appium/support`.

- add `fs.findRoot(<dir>)` - Finds the closest parent directory from `dir` containing a `package.json`
- add `fs.readPackageJsonFrom(<dir>)` - Like above, except reads and returns the parsed contents of `package.json`
- Remove `find-root` dep from `appium` and add it to `@appium/support` instead
- Leverage these functions where needed
- Remove unnecessary `pkgRoot` export in `@appium/doctor`
- `fs-specs.js`: fix some tests which make assumptions about CWD; remove a low-value test
2021-08-10 11:52:57 -07:00
Christopher Hiller 6c769a2b7c chore: housecleaning
Just a bunch of random (mostly path-related) fixes.

## Monorepo

- Make `npm run clean` work cross-platform
- Sort root `package.json`
- Update Babel configs to suppress warning about `@babel/plugin-proposal-private-property-in-object`
- Add `.nyc_output` and `*.lerna_backup` to `.gitignore`
- Replace references to `appium-support`
- Sync `package.json` fields
- Add a [Wallaby](https://wallabyjs.com) configuration file

## appium

- `lib/config`: `package.json` resolution
- split some tests out of `config-specs.js` and into `config-e2e-specs.js` since they are very clearly not unit tests
- Improve `PROJECT_ROOT` resolution, which is still a hack
- Copy fixtures & fix paths to fixtures in tests

## @appium/base-driver

- `lib/basedriver/driver`: `package.json` resolution
- Fix broken tests when run in isolation (`chai.should()`)

## @appium/doctor

- `lib/doctor`: `package.json` resolution
- `lib/utils`: `package.json` resolution & exposed `resetLog()` which undoes what `configureBinaryLog()` does (for testing purposes)
- `utils-specs.js`: clean up after `onLogMessage` test
- copy fixtures into `build` upon transpile
- Upgrade `@appium/gulp-plugins`

## @appium/gulp-plugins

- Ensure `postTranspile` is run when `transpile` is run before tests
- Fix `.babelrc` like `babel.config.json`

## @appium/support

- Fixture resolution
- Fix broken tests when run in isolation (`chai-as-promised`)
- Copy fixtures when transpiling
2021-08-10 11:52:57 -07:00
Christopher Hiller 25cc27d161 feat(base-driver): prefer system unzip
Backport of https://github.com/appium/appium-base-driver/pull/503 to the `2.0` branch of `appium/appium`
2021-07-22 11:04:10 -07:00
Christopher Hiller 2955c172b1 feat(support): extractAllTo(): prefer system unzip
This is a straight-ahead backport of https://github.com/appium/appium-support/commit/873351aacc0402bcb229bad928abd1ad2f9c4a4b to the `2.0` branch of `appium/appium`.

Ref: https://github.com/appium/appium-support/pull/226
2021-07-22 11:03:36 -07:00
Ayan Khan d3c11e364d feat(appium): Add driver and plugin server arg injection feature (#15388)
* feat: add ability for drivers and plugins to recieve cli args

* chore: fix typo

* fix: make changes based on feedback

* feat: remove driver-specific server args from appium

* fix: resolve merge conflict

* fix: make changes based on feedback

* feat: add support for driver cli args in base driver

* feat: add support for testing driver args in fake-driver

* refactor: remove support for -custom locator strategy(should be handled by plugins instead)

* fix: makes changes based on feedback

* fix: remove outdated/unused args

* fix: fix docs

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make tests pass

* test: tests are passing locally but not in CI, try something different to get them to pass

* test: try uninstalling fake-driver and reinstalling fake driver from packages

* fix: revert driver-e2e-specs.js and update node.js.yml

* fix: update package.json

* fix: add special case to npm

* fix: add todo

* fix: how relative paths are resolved in linkPackage
2021-07-15 13:41:50 -07:00
Ayan Khan a2654762b6 feat: add "run" command to the client interface in order to allow running driver-defined scripts (#15356)
* feat: add "run" command to the client interface inorder to allow the running of scripts against drivers

* feat: add "run" command to the client interface inorder to allow the running of scripts against drivers

* chore: make changes based on feedback

* chore: make more changes based on feedback

* chore: make changes based on feedback, added e2e test for plugin scripts. Need https://github.com/appium/appium-plugins/pull/7 to be merged before the e2e tests will work.

* fix: make changes based on feedback

* fix: fix cli-e2e-specs to work with monorepo

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make changes based on feedback

* fix: make changes based on feedback

* test: log error

* test: add logging to install

* fix: readd clear() line

* test: add logging

* test: change up the order of cli e2e tests, maybe the tests will pass in CI now?

* test: try creating and clearing appium home between test sets

* test: try unlinking fake-driver before each test set

* test: shift aroud test sets

* test: try something new

* fix: runner to use process.execPath instead of node

* fix: remove a test

* fix: readd test

* fix: tests

* test: log test error

* fix: logging

* fix: test logging

* test: add aditional logging

* fix: log

* test: try npm cache verifying

* fix: add '--no-package-lock' to link command and refactor test

* fix: remove logs

* fix: make changes based on feedback
2021-07-15 09:33:28 -07:00
Christopher Hiller c20a13ea95 fix(doctor): update reference to @appium/idb README 2021-07-03 17:35:36 -07:00
Christopher Hiller de428c60c8 chore: add node.js v16 to build matrix
- run `generate_docs` build with default Node.js
- multiline yaml
2021-06-30 10:01:58 -07:00
Kazuaki Matsuo 77bf18def6 docs: update docs in get log types and get clipboard (#15583)
* docs: improve get-log-types

* docs: add description in get clipboard for ios

* copy in to md
2021-06-28 13:35:55 +09:00
youngfree fbe864f746 docs(appium): doc/cn drivers doc for appium 2.0 (#15568) 2021-06-24 08:55:33 -07:00
renovate[bot]andRenovate Bot 9b9f7e9acf chore(gulp-plugins): update dependency globby to ^11.0.4 (#15518)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:19:57 -07:00
renovate[bot]andRenovate Bot 023ec51a0c chore(gulp-plugins): update dependency vinyl-paths to ^3.0.1 (#15520)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:19:39 -07:00
renovate[bot]andRenovate Bot e512c87109 chore(support): update dependency get-stream to ^6.0.1 (#15524)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:19:25 -07:00
renovate[bot]andRenovate Bot eff94987da chore(support): update dependency mkdirp to ^1.0.4 (#15529)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:19:11 -07:00
renovate[bot]andRenovate Bot 256c2ad200 chore(gulp-plugins): update dependency mocha to v9.0.1 (#15530)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:18:57 -07:00
renovate[bot]andRenovate Bot 04d11619cb chore(support): update dependency plist to ^3.0.2 (#15531)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:18:43 -07:00
renovate[bot]andRenovate Bot 38ebd45b51 chore(support): update dependency sanitize-filename to ^1.6.3 (#15533)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:18:17 -07:00
renovate[bot]andRenovate Bot 487bfd30c2 chore(support): update dependency which to ^2.0.2 (#15534)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:17:45 -07:00
renovate[bot]andRenovate Bot 17a2b8cad0 chore(test-support): update dependency source-map-support to v0.5.19 (#15538)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:16:05 -07:00
renovate[bot]andRenovate Bot 5db7517cee chore(): update dependency @appium/fake-plugin to ^1.2.0 (#15539)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:15:47 -07:00
Christopher Hiller b74612cae8 chore: remove unused wd (#15542) 2021-06-18 15:15:32 -07:00
renovate[bot]andRenovate Bot 88de3b3906 chore(support): update dependency jsftp to ^2.1.3 (#15528)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-18 15:15:06 -07:00
renovate[bot]andRenovate Bot 1704eb5513 chore(): update dependency finalhandler to ^1.1.2 (#15508)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-17 15:56:11 -07:00
renovate[bot]andRenovate Bot 91d47496ef chore(gulp-plugins): update dependency gulp to ^4.0.2 (#15519)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-17 15:54:31 -07:00
renovate[bot]andRenovate Bot 47a0e66dbd chore(gulp-plugins): update dependency yargs to ^17.0.1 (#15521)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-17 15:54:03 -07:00
renovate[bot]andRenovate Bot a461c58f2b chore(support): update dependency glob to ^7.1.7 (#15525)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-17 15:53:30 -07:00
Christopher Hiller 5109e6852b chore: upgrade sync-monorepo-packages (#15506)
this fixes some problems with the command-line options, and maybe some security fixes too
2021-06-16 10:49:00 -07:00
renovate[bot]andRenovate Bot 1cdcba2e02 chore(): update dependency querystring to ^0.2.1 (#15509)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:48:25 -07:00
renovate[bot]andRenovate Bot 55efe2c411 chore(): update dependency yaml-js to ^0.2.3 (#15512)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:47:42 -07:00
renovate[bot]andRenovate Bot ad76ffc1a8 chore(base-driver): update dependency http-status-codes to ^2.1.4 (#15513)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:47:29 -07:00
renovate[bot]andRenovate Bot f11a34f7af chore(fake-driver): update dependency xpath to ^0.0 (#15514)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:47:14 -07:00
renovate[bot]andRenovate Bot b78a8f4deb chore(gulp-plugins): update dependency fancy-log to ^1.3.3 (#15516)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:46:09 -07:00
renovate[bot]andRenovate Bot 2bd49ef5fa chore(gulp-plugins): update dependency ansi-red to ^0.1 (#15515)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-16 10:45:53 -07:00
Christopher Hiller e00fe2644b chore(gulp-plugins): enable consumers to use mocha's "exit" flag
unit tests _should_ not need `--exit`, but this is real life, so things aren't perfect.

boilerplate configs can pass `{test: {exit: true}}` to cause Mocha to force-exit.

note that this is enabled by default for e2e tests (which _also_ shouldn't be necessary, but one thing at a time...)
2021-06-15 11:17:17 -07:00
Christopher Hiller 9295d0c682 fix(appium): update references to @appium/support 2021-06-14 13:57:03 -07:00
renovate[bot]andRenovate Bot 2ffe27730b chore(eslint-config-appium): update dependency eslint-plugin-promise to v5 (#15492)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:44:53 -07:00
renovate[bot]andRenovate Bot 81a2bfe141 chore(appium): update dependency ora to v5 (#15488)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:36:13 -07:00
renovate[bot]andRenovate Bot cece26cbb3 chore(doctor): update dependency inquirer to v8 (#15490)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:32:05 -07:00
renovate[bot]andRenovate Bot 993ad769ad chore(gulp-plugins): update dependency mocha to v9 (#15493)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:31:16 -07:00
renovate[bot]andRenovate Bot 7a7049fa51 chore(gulp-plugins): update dependency node-notifier to v10 (#15494)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:30:33 -07:00
renovate[bot]andRenovate Bot 4ce589030a chore(test-support): update dependency sinon to v11 (#15495)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:30:07 -07:00
renovate[bot]andRenovate Bot ce47f82d17 chore(workflows): update actions/setup-node action to v2 (#15496)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:29:47 -07:00
renovate[bot]andRenovate Bot 1e7026a532 chore(eslint-config-appium): update dependency eslint-plugin-mocha to v9 (#15491)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:21:29 -07:00
Ayan Khan 9331969c23 refactor(appium)!: remove support for -custom locator strategy(should be handled by plugins instead) (#15452) 2021-06-09 16:28:29 -07:00
Christopher Hiller 950372c1bd fix(fake-driver): copy non-JS files into build dir at transpile time; closes #15471 2021-06-08 11:32:20 -07:00
Christopher Hiller c66144d62b chore: update @appium/fake-driver to use @appium/base-driver (#15436)
* chore: update @appium/fake-driver to use @appium/base-driver

BREAKING CHANGE: `fake-driver` now depends upon `@appium/base-driver@8.x`

## `@appium/fake-driver`

- need to use w3c capabilities only
- fix: find `app.xml` fixture properly when running tests via `mocha --require=@babel/register`

## `@appium/base-driver`

- fixed a dead URL in a comment
- updated the "logging" tests to manually supply w3c capabilities.  `createSession()` does it for you, but `executeCommand('createSession')` does not.
- display the name of the driver under test when executing base driver's test suite with other drivers

Ref: #15425

* fix(fake-driver): add naive css selector support

* fix(fake-driver): helpers now use wdio instead of wd

- exported more constants as base-driver helpers do
- `deleteSession()` now expects a wdio `browser` (`driver`) object; removed closure

* fix(fake-driver): find-element-tests and driver-e2e-specs members passing

- alert-tests, context-tests, element-interaction-tests, general-tests all failing still

* fix(fake-driver): update interaction tests

replace unsupported `getLocation` with `getElementRect`

* fix(fake-driver): update alert tests

* fix(fake-driver): udpate context tests

* fix(fake-driver): update unit tests

* fix(base-driver): implement missing route for releaseActions

is the call to `this.proxyReleaseActions` even necessary?

* fix(fake-driver): update generic actions tests

also add a stub implementation of `releaseActions`, which wdio calls

* chore(fake-driver): remove redundant tests from find-element-tests

covered by other strategies

* fix(fake-driver): update generic actions tests

also fix path to screenshot (`screen.png`) when running tests outside of `build`

* chore(fake-driver): use fs module from @appium/support

* chore(base-driver): update hash in protocol consistency test

* fix(base-driver): update e2e test fn to prefer provided address/port over default

* chore: formatting
2021-06-07 15:01:34 -07:00
Kazuaki Matsuo 385e5c5b23 chore(base-driver): remove unused swipe related methods from base-driver (#15455) 2021-06-01 20:06:37 -07:00
Kazuaki Matsuo 254cc638c5 revert(base-driver): "chore: merge base-driver master (de7e41b) to base-driver for 2.0" (#15454)
This reverts commit 01abeac864.
2021-06-02 11:14:46 +09:00
Ayan Khan 21e8d60a5c feat(appium): expose validateCaps in basedriver index (#15451) 2021-06-01 15:53:11 -07:00
Dharin Shah 52f4fc6a2c fix(appium): load plugins before main server to allow plugin monkeypatching (#15441) 2021-06-01 11:10:48 -07:00
Kazuaki Matsuo 01abeac864 chore: merge base-driver master (de7e41b) to 2.0 (#15440) 2021-06-01 05:42:31 +09:00
Christopher Hiller 7b38ac9417 chore(base-driver): use 'beta' dist tag 2021-05-24 15:16:15 -07:00
Christopher Hiller 4def2e926a chore: configure lerna for publishing
1. Uses `sync-monorepo-packages` to keep `README.md` and `packages/appium/README.md` in sync.  Since `README` gets published to npm but _also_ shows in the GitHub repo, both places need a `README`. In lieu of a custom `README`, we can just sync it for now.  This is called via a `preversion` script.
1. Various fields are synced in `package.json`.  This does not include dependencies—but those _in common_ should probably be kept in sync as well.
1. Lerna uses conventional commits and Git tags to figure out what to do.  It should automatically determine what needs releasing and what version it should be released as.
1. Tags have been pushed to the repo for Lerna to use.  In the case where the changesets are functionally identical to the latest code in the old repositories, the version number has been retained, and the tag is of the format `[@scope/]<package-name>@<version>`.  In the case where the old repos had _unreleased_ changesets, I’ve tagged with a new patch release and adopted the `-rc.0` suffix.  So if we were at v5.5.0 in `appium-gulp-plugins`, `@appium/gulp-plugins` is tagged as `@appium/gulp-plugins@5.5.1-rc.0`.  I have not modified the version in `package.json`.
 1. As with adopting any new release/publishing strategy, we should be meticulous about what we’re doing until we’re comfortable with how it works.  Lerna supports dry runs; we should use them.
1. Documentation on usage will arrive in next changes to this PR.
2021-05-24 15:16:15 -07:00
Jonathan Lipps 9471f13270 feat!(fake-driver): wrap fake app source with <AppiumAUT> like other drivers
this will improve appium inspector compatibility
2021-05-24 11:48:03 -07:00
202 changed files with 2964 additions and 1740 deletions
+10 -12
View File
@@ -10,18 +10,19 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
- run: npm run install-fake-driver
- run: npm run e2e-test
- run: |
npm install
npm test
npm run install-fake-driver
npm run e2e-test
generate_docs:
needs: [build]
@@ -29,9 +30,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm install
- run: npm run generate-docs
- run: |
npm install
npm run generate-docs
+2
View File
@@ -54,3 +54,5 @@ sample-code/csharp/test/bin
sample-code/csharp/packages
appium.zip
.vscode/settings.json
.nyc_output
*.lerna_backup
+7
View File
@@ -0,0 +1,7 @@
// @ts-check
'use strict';
module.exports = {
require: require.resolve('./test/setup.js')
};
+60
View File
@@ -0,0 +1,60 @@
'use strict';
module.exports = (wallaby) => {
return {
compilers: {
'**/*.js': wallaby.compilers.babel(),
},
debug: true,
env: {
type: 'node',
},
files: [
'./packages/**/*.js',
'./packages/**/*.json',
'!./packages/**/build/**',
'!./packages/**/test/**/*-specs.js',
'!./packages/*/node_modules/**',
'!./packages/*/gulpfile.js',
// below this are fixtures
{
binary: true,
pattern: './packages/support/test/assets/sample_binary.plist',
},
{
instrument: false,
pattern: './packages/support/test/assets/sample_text.plist',
},
{
instrument: false,
pattern: './packages/*/test/**/fixtures/**/*',
},
{
instrument: false,
pattern: './packages/base-driver/static/**/*',
},
{
instrument: false,
pattern: './packages/gulp-plugins/build/**/*',
},
{
instrument: false,
pattern: './test/setup.js'
}
],
testFramework: 'mocha',
tests: [
'./packages/*/test/**/*-specs.js',
'!./packages/*/test/**/*-e2e-specs.js',
'!./packages/*/node_modules/**',
// this is more of an E2E test and it's tedious to run
'!./packages/gulp-plugins/test/transpile-specs.js',
],
workers: {
restart: true,
},
setup() {
require('./test/setup');
}
};
};
+3 -5
View File
@@ -17,7 +17,8 @@
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-optional-chaining",
["@babel/plugin-proposal-private-methods", {"loose": true}],
["@babel/plugin-proposal-class-properties", {"loose": true}]
["@babel/plugin-proposal-class-properties", {"loose": true}],
["@babel/plugin-proposal-private-property-in-object", {"loose": true}]
],
"comments": false,
"sourceMaps": "both",
@@ -27,10 +28,7 @@
[
"istanbul",
{
"exclude": [
"test",
"build"
]
"exclude": ["test", "build"]
}
]
]
+102
View File
@@ -0,0 +1,102 @@
## Android 的 UiAutomator2 驱动
Appium 目前官方推荐使用 `UiAutomator2` 作为安卓app在自动化测试中的驱动。
_(如果你是Appium新手? 可以参阅 [introduction to Appium drivers(TODO)](#TODO))_.
这个驱动使用了谷歌的 [UiAutomator2](https://developer.android.com/training/testing/ui-automator.html) 技术,让自动化测试可以更方便地在安卓真机或者模拟器上运行。
UiAutomator2 驱动的开发过程,记录在了[appium-uiautomator2-driver](https://github.com/appium/appium-uiautomator2-driver) 代码仓库中。
在更早期的安卓设备驱动中,包括了以下驱动:
* [UiAutomator 驱动](/docs/en/drivers/android-uiautomator.md)
### 需求与依赖
在 Appium 常规依赖之外:
* 需要在您的设备上,安装并配置好 Java 8
* 在 Mac,Windows 或者 Linux OS 设备上,需要安装好 Android SDK
* Android SDK 中 Build Tools 的版本需要大于等于 24
此外,UiAutomator2 驱动是不支持对 Android 5.0 (Lollipop, API level 21)以下的设备进行自动化测试的,如果对这类版本的设备进行自动化测试,建议使用:[UiAutomator 驱动](/docs/en/drivers/android-uiautomator.md);
### 使用方式
通过 UiAutomator2 驱动启动一个测试任务时,需要传递一个`automationName` [capability] 的参数在 [新会话的请求](#TODO) 中,并且 `automationName` 的值需要是:`UiAutomator2`。当然,你至少也需要再传递这些参数:`platformName` (=`Android`), `platformVersion`, `deviceName`, 与 `app`。
同时,强烈建议您传递:`appPackage` 与 `appActivity` 这两个参数,以便让 Appium 知道您想要拉起哪个 App 和哪个 activity。如果没有的话,Appium 将会自己从 App 的 manifest 文件中自动去查找。
### 功能参数
UiAutomator2 驱动支持许多通用的功能参数:[Appium
capabilities](/docs/en/writing-running-appium/caps.md),同时,有许多特有的功能参数,可以参考这篇文档查找这些功能参数:[Android
section](/docs/en/writing-running-appium/caps.md#android-only)。
对于 web 测试,是测试 Chrome 而不是去测试您自己的应用程序,需要保证 `app` 参数为空,并且设置 `browserName` 为 `Chrome`。需要提醒您的是:您需要保证 Chrome 是在安卓模拟器/真机上运行,并且与 [version compatible with
Chromedriver](/docs/en/writing-running-appium/web/chromedriver.md) 兼容。
### 命令
如果需要查看 Appium 支持的命令,及它们是如何映射到 UiAutomator2 驱动的,可以参考:[API
Reference](#TODO)。
### 基础设置
1. 确保您拥有 Appium 的基础依赖(例如:Node 与 NPM)来安装和配置 Appium。
1. 确保 Java ( 这里指的是 JDK 而不是 JRE )已经成功安装并且在环境变量中配置好了 Java 的路径。这一步的具体操作,在 Mac/Linux 与 Windows 等不同平台上操作不同,这里为您提供一个在 Windows 平台上设置路径的方法:[点击查看](https://www.java.com/en/download/help/path.xml)。
1. 确保环境变量: `JAVA_HOME` 已经成功设置为 JDK 的路径。举一个 Mac/Linux 的例子(这个路径的具体地址,根据您安装的实际情况有所不同):
```
export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home"
```
在 Windows 电脑上,可以通过 「控制面板」来配置。
[Android Studio](https://developer.android.com/studio/index.html) 同样提供了一个 JDK,像这样的一个路径: `/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home` (示例为Mac电脑版本)。您同样可以指定这个路径。
1. 安装 [Android SDK](http://developer.android.com/sdk/index.html)。当前最常用的方法是通过:[Android
Studio](https://developer.android.com/studio/index.html) 来安装配置。可以在桌面应用程序中,通过GUI界面操作,选中一个路径来下载 Android SDK。
1. 设置 `ANDROID_HOME` 环境变量。举个例子,如果您下载 Android SDK 在 `/usr/local/adt` 这个路径下,您将在这个路径下,找到一个包含 SDK 文件的、名称为: `sdk` 的文件夹。在这种情况下,打开
Mac 和 Linux 的配置文件(如:`~/.bashrc`, `~/.bash_profile` 及其他类型的文件),将这个环境变量与路径记录在其中。
```
export ANDROID_HOME="/usr/local/adt/sdk"
```
在 Windows 机器上,按照相同的步骤,将环境变量设置在「控制面板」中。
1. 通过 SDK manager,确保您安装了您所需要的、对应 Android API 版本的 SDK(例如:24)。
1. 在 Windows 电脑上,需确保您的 Appium 是通过管理员模式来运行的。
到此,您基础的系统配置已经完成了。接下来的步骤,需要看您是选择在模拟器运行测试还是在真机上运行测试。需要准备好待测 App (最好是Debug 版本)APK 的本地路径或下载链接,这个路径或链接,在运行测试时,需要设置为 `app` 参数的内容。
### 模拟器设置
使用模拟器运行测试是,需要使用 Android Studio 或 SDK 提供的 AVD Manager。通过使用这个工具,来创建您所需要的模拟器。在模拟器启动时,Appium 将会自动找到并使用它进行测试。同时,如果您指定了 `avd` 这个功能参数为您所制定的设备名称时,Appium 会启动您指定的模拟器来运行测试。
关于模拟器的一些小提示:
* Android 模拟器可以设置硬件加速,尽管它有一些局限性。可以通过 Intel 网站下载,或者通过 Android SDK manager。了解更多信息,[可以点击这里查看](https://github.com/intel/haxm)。
* 如果您想在 Appium 测试中使用电源相关的命令,请确保在 AVD 的 `config.ini` 文件中,设置了:`hw.battery=yes` (在 Android 5.0 上,为默认设置)。
### 真机设置
对于 Android 自动化测试,除了以下一些简单操作外,不需要其他复杂的设置:
* 确保打开了设备的 [开发者模式](https://developer.android.com/studio/debug/dev-options.html)。
* 确保设备通过 USB 连接到 Appium 服务启动的设备上,并且可以被 [ADB](https://developer.android.com/studio/command-line/adb.html) 找到
( 执行 `adb devices` 确认 )。
* 确保设置中的“验证应用程序”是关闭的,以允许 Appium 安装一些辅助软件,而不用手动点击确认。
( 对于一些特殊的指令,设备可能需要被 root ,尽管这并不是经常会遇到的。 )
Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 KiB

@@ -0,0 +1,200 @@
## Appium XCUITest 驱动在真机上的设置
### 安装依赖
自 Appium 1.15.0 起,Appium 与真机设备通过 [appium-ios-device](https://github.com/appium/appium-ios-device) 进行联通。您不需要再额外安装依赖项。XCUITest 驱动会安装一个名为 `WebDriverAgent-Runner` 的辅助软件在设备上,并通过这个辅助软件控制被测应用。虽然这听起来很简单,但在苹果软件的代码签名和配置应用程序上,在开发和测试中可能会有些头痛。
应该可以从Xcode中找到待测设备。请确保被测设备连接Xcode万无一失。你可以通过打开 _Devices and Simulators_ 这个选项栏来确认这件事。
这篇文章也有利于解决环境依赖安装的问题 [appium-xcuitest-driver](https://github.com/appium/appium-xcuitest-driver) 。
### 基础 (自动) 配置
在 iOS 真机上使用 XCUITest 运行自动化测试最简单的方法,就是使用自动配置来运行。这里有两条路:
* 使用功能参数 `xcodeOrgId` 与 `xcodeSigningId` :
```json
{
"xcodeOrgId": "<Team ID>",
"xcodeSigningId": "iPhone Developer"
}
```
* 在任意路径下,创建一个 `.xcconfig` 文件,并添加如下配置:
```
DEVELOPMENT_TEAM = <Team ID>
CODE_SIGN_IDENTITY = iPhone Developer
```
无论哪种情况,苹果公司会生成一个 10 位字符的唯一字符串作为您的 Team ID,分配给您的开发团队。您可以您的开发者账号找到这个 Team ID,您可以登录[developer.apple.com/account](http://developer.apple.com/account) 并点击侧边栏的 「开发者资格( Membership )」。您可以在 「开发者资格( Membership )」 详细信息的团队名称下找到您的 Team ID。您也可以在您电脑「钥匙串」中的 iPhone 开发者证书中的 「组织单元("Organizational Unit")」字段中,找到您的 Team ID。
需要注意的是,使用 `xcodeConfigFile` 这种方式,与使用 `xcodeOrgId` 和 `xcodeSigningId` 的方式是互斥的。
这些配置完成后,您就只剩下将您的真机设备配置在参数 `udid` 中就可以了。
如果配置完无法运行,一般会在 Appium 服务日志中有对应的报错信息,比如有些会提示:`info XCUITest xcodebuild exited with code '65' and
signal 'null'`。这通常意味着代码签名配置的有问题。继续回看上一部分[基础 (自动) 配置](#basic-manual-configuration)来解决这个问题。
如果 `WebDriverAgentRunner` 在真机设备上安装成功,但是 Appium 服务日志有如下报错:
```
2017-01-24 09:02:18.358 xcodebuild[30385:339674] Error Domain=com.apple.platform.iphoneos Code=-12 "Unable to launch com.apple.test.WebDriverAgentRunner-Runner" UserInfo={NSLocalizedDescription=Unable to launch com.apple.test.WebDriverAgentRunner-Runner, NSUnderlyingError=0x7fa839cadc60 {Error Domain=DTXMessage Code=1 "(null)" UserInfo={DTXExceptionKey=The operation couldn’t be completed. Unable to launch com.apple.test.WebDriverAgentRunner-Runner because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user. : Failed to launch process with bundle identifier 'com.apple.test.WebDriverAgentRunner-Runner'}}}
2017-01-24 09:02:18.358 xcodebuild[30385:339674] Error Domain=IDETestOperationsObserverErrorDomain Code=5 "Early unexpected exit, operation never finished bootstrapping - no restart will be attempted" UserInfo={NSLocalizedDescription=Early unexpected exit, operation never finished bootstrapping - no restart will be attempted}
Testing failed:
Test target WebDriverAgentRunner encountered an error (Early unexpected exit, operation never finished bootstrapping - no restart will be attempted)
```
这个问题是由于开发者在设备上不受信任,当你手动尝试将 `WebDriverAgentRunner` 安装在您的真机设备上时,会看到如下弹窗信息:
![Untrusted developer](ios-xcuitest-img/untrusted-dev.png)
您可以通过 设置(Settings) => 通用(General) => 设备管理(Device Management)来信任开发者,以使 `WebDriverAgentRunner` 能够成功运行 (更多详细信息见: [苹果官方支持文档](https://support.apple.com/en-us/HT204460))。
### 基础 (手动) 配置
在很多情况下,基本的自动配置是不满足使用需要的。通常在自动化运行时,会有代码签名和项目配置的问题。当正在使用的开发帐户是“免费”帐户时,无法创建出通用的配置文件。
这通常表现为 Xcode 报错为:**failed to create
provisioning profile**:
![No provisioning profile](ios-xcuitest-img/no-prov-prof.png)
解决此问题的最简单方法是通过打开 [Xcode](https://developer.apple.com/xcode/) 并创建一个新项目来创建配置文件 :
![Create new project](ios-xcuitest-img/create-new-project.png)
只要选择了 "iOS",具体的应用类型无关紧要,一般使用单页面应用 ("Single View Application")最简单也最方便:
![Create single page](ios-xcuitest-img/create-single-page.png)
重要的是,使用一个唯一的 产品名称("Product Name")与 组织名称("Organization Name"),并且,在这时候,请选择你的团队("Team")。
![Setup bundle](ios-xcuitest-img/set-up-bundle.png)
您可以通过查看 "项目" 选项,来确认配置文件是否已创建:
![Project pane](ios-xcuitest-img/project-prov-prof.png)
或者通过进入您的帐户首选项并查看配置文件:
![Check provisioning profile](ios-xcuitest-img/check-prov-prof.png)
此时,您有一个有效的配置文件。记住 bundle id 并添加到测试的 `updatedWDABundleId` 功能参数中。然后按照 [如何初始化自动配置](#basic-automatic-configuration).
### 完全手工配置
或者,配置文件可以完全通过手工在项目中配置(需要知道的是,每次WDA更新、Appium更新时,都需要操作一次,_不推荐_ ):
* 找到您 Appium 的安装路径:
```
$ which appium
/path/where/installed/bin/appium
```
* 由于路径在: `/path/where/installed/bin/appium`,那么,`WebDriverAgent` 可以在这个路径的下级路径中被找到: `/path/where/installed/lib/node_modules/appium/node_modules/appium-webdriveragent`。
打开终端(terminal),移动到该路径下执行如下命令:
```
mkdir -p Resources/WebDriverAgent.bundle
```
* ( 如果 Appium 版本低于 1.20 ) 在终端(terminal)执行如下命令:
```
./Scripts/bootstrap.sh -d
```
* Xcode 打开 `WebDriverAgent.xcodeproj`。同时选中 `WebDriverAgentLib`
and `WebDriverAgentRunner` ,在 通用("General")页签下,都选择为自动管理签名("Automatically manage signing"),然后选择您所在的开发组织(`Development Team`),它将自动选择开发者签名证书(`Signing Ceritificate`)。结果应如下所示:
![WebDriverAgent in Xcode project](ios-xcuitest-img/xcode-config.png)
* Xcode 可能无法为 `WebDriverAgentRunner` 创建配置文件:
![Xcode provisioning fail](ios-xcuitest-img/xcode-facebook-fail.png)
* 这需要通过进入构建设置("Build Settings")页签下,手动更改目标的bundle id,并且将构建产物标识符("Product Bundle Identifier")`com.facebook.WebDriverAgentRunner` 进行一些修改,以让 Xcode 通过:
![Xcode bundle id](ios-xcuitest-img/xcode-bundle-id.png)
* Going back to the "General" tab for the `WebDriverAgentRunner` target, you
should now see that it has created a provisioning profile and all is well:
返回 `WebDriverAgentRunner` 的通用("General")页签,您将看到配置文件已经创建完成,并且所有配置都是正常的了:
![Xcode provisioning profile](ios-xcuitest-img/xcode-facebook-succeed.png)
* 最后,您可以验证一切是否正常。构建项目:
```
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id=<udid>' test
```
如果一切正常,输出信息一般以此结尾:
```
Test Suite 'All tests' started at 2017-01-23 15:49:12.585
Test Suite 'WebDriverAgentRunner.xctest' started at 2017-01-23 15:49:12.586
Test Suite 'UITestingUITests' started at 2017-01-23 15:49:12.587
Test Case '-[UITestingUITests testRunner]' started.
t = 0.00s Start Test at 2017-01-23 15:49:12.588
t = 0.00s Set Up
```
* 要完全验证,您可以尝试访问 WebDriverAgent 服务器状态
(**提示:** 您 _必须_ 保证 WebDriverAgent 服务与设备在同一网络下,可以通过以下方式获取 IP, Settings => Wi-Fi => Current Network):
```
export DEVICE_URL='http://<device IP>:8100'
export JSON_HEADER='-H "Content-Type: application/json;charset=UTF-8, accept: application/json"'
curl -X GET $JSON_HEADER $DEVICE_URL/status
```
您将获得类似如下的返回信息:
```
{
"value" : {
"state" : "success",
"os" : {
"name" : "iOS",
"version" : "10.2"
},
"ios" : {
"simulatorVersion" : "10.2",
"ip" : "192.168.0.7"
},
"build" : {
"time" : "Jan 23 2017 14:59:57"
}
},
"sessionId" : "8951A6DD-F3AD-410E-A5DB-D042F42F68A7",
"status" : 0
}
```
### 对被测应用的设置
不仅是 WDA 可以在您的设备上运行,被测应用也需要可以在您的设备上运行。它们之间的规则是相同的:一个拥有签名的被测应用( 一个 `.ipa` 文件)。 可以在 这里 [这里](https://medium.com/ios-os-x-development/ios-code-signing-provisioning-in-a-nutshell-d5b247760bef#.5hirl92tn)
与
[这里](https://www.nodesagency.com/understanding-code-signing-for-ios-apps) 找到更详细的描述。
更详细地说,要使用真机运行自动化测试,您需要以下内容:
* 一个 [苹果开发者ID](https://developer.apple.com/programs/ios/) 和一个有效的开发者帐户,以及一个配置好的开发证书和配置文件。(请确保配置文件具有[iOS Distribution](https://developer.apple.com/support/certificates/)类型)
* 如果需要在真机运行测试,一个合法的 iOS 开发者证书和配置文件是必须有的。您的 App 也需要签名。您可以在这里看到更多信息:[苹果官方文档](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/TestingYouriOSApp/TestingYouriOSApp.html)。
* 一台 iPad 或 iPhone设备。请确保在 Xcode 中可以找到它们。
* 一个拥有签名的 `.app` 或 `.ipa` 被测 App 应用,或者是它们可以被构建的源代码。
* 一台拥有 [Xcode](https://developer.apple.com/xcode/) 与 Xcode 开发者命令行工具的 Mac 系统的电脑。
Appium 将通过 `ideviceinstaller` (`libimobiledevice` 的一部分功能) 将应用安装到设备上,但有时候,通过 Xcode 预安装会更加方便(再看看这个:[苹果官方文档](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/TestingYouriOSApp/TestingYouriOSApp.html))
### 问题排查思路
1. 请通过 Xcode Organizer 或 iTunes 确保设备 UDID 是正确的。 它是一个 20+ 的字符串。
1. 请确保相同操作在模拟器上是可以运行的。
1. 请确保在设备上 **启动** 了以下设置:
1. 设置(Settings) -> 开发者(Developer) -> **启动 UI Automation (Enable UI Automation)**
1. 设置(Settings) -> Safari -> 高级(Advanced) -> **Web Inspector** 与 **Remote Automation**
1. 请阅读 [Automating mobile web apps](/writing-running-appium/web/mobile-web) 获取关于 WebView 运行的详细信息。
1. 如果您不想为手动配置配置文件,请在自动生成时带上 `.xctrunner` 标识。`.xctrunner` 在 Xcode 11. [A reference](https://github.com/appium/appium/issues/13610) 起,将被自动配置。
1. 请确保设备没有越狱
- `com.apple.mobile.installation_proxy` 服务在管理 App 上[不起作用](https://github.com/appium/appium-desktop/issues/1447)
1. 确保配置文件具有:[_iOS 分发证书_](https://developer.apple.com/support/certificates/)
- 由于 Apple 的安全设计,活动 Xcode/xcodebuild 连接/会话是与 WebDriverAgentRunner 交互所必需需要的。 证书限制的影响: ([issue](https://github.com/appium/appium/issues/14577#issuecomment-660997827))
+118
View File
@@ -0,0 +1,118 @@
## iOS 的 XCUITest 驱动
iOS 应用的自动化测试,Appium 主要通过 `XCUITest` 驱动进行驱动。_(如果您是 Appium 新手? 可以参阅: [introduction to Appium drivers](#TODO))_。这个驱动通过苹果公司的 [XCUITest](https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html) 库,让自动化测试可以更方便地执行 App 自动化测试。它是通过 [WebDriverAgent](https://github.com/facebook/webdriveragent) 服务对 XCUITest 进行访问。WebDriverAgent(也称之为 “ WDA ”)是 Facebook 公司的一个项目,Appium 的核心团队为其做出了许多贡献。WDA 是一个 WebDriver兼容的服务,服务可以运行在 iOS 模拟器或真机上,并且调用 XCUITest API。WDA 作为 Appium 的 XCUITest 驱动管理器,对用户而言是一个黑盒进程,通过 WDA 对往来的通信进行代理,并提供许多额外的能力(举个例子:如管理模拟器等方法)。
XCUITest 驱动的开发过程,记录在了 [appium-xcuitest-driver](https://github.com/appium/appium-xcuitest-driver) 仓库中。
### 需求与依赖
在 Appium 常规依赖之外:
* 苹果公司从的 XCUITest 库只适用于 iOS 版本大于等于 iOS 9.3 的模拟器或真机设备。
* 需要一台 macOS 10.11 或 10.12 的 Mac 电脑。
* Xcode 7 或更高版本的 Xcode.
* XCUITest 驱动从 Appium 1.6 开始在 Appium 中可用。
* 为了驱动使用正确,附加的系统库是必需的(请参阅下面的安装部分)。
### 迁移自 UIAutomation 驱动
如果您正在从 Appium 的旧驱动程序:[UIAutomation-based driver](/docs/en/drivers/ios-uiautomation.md) 迁移到XCUITest驱动,您可能想知道这个:[迁移指南](/docs/en/advanced-concepts/migrating-to-xcuitest.md)。
### 使用
在使用 XCUITest 驱动来创建一个会话链接时,需要包含 `automationName` [capability](#TODO) 参数在您的 [创建新会话请求](#TODO)中,且其值应该是:`XCUITest`。当然,您也至少传递这些必传参数:`platformName`, `platformVersion`, `deviceName` 与 `app`。
在 iPhone 与 iPad 设备上,参数 `platformName` 需要是 `iOS`。在 tvOS 设备上,参数 `platformName` 需要是 `tvOS`。
- iOS
```json
{
"automationName": "XCUITest",
"platformName": "iOS",
"platformVersion": "12.2",
"deviceName": "iPhone 8",
...
}
```
- tvOS
```json
{
"automationName": "XCUITest",
"platformName": "tvOS",
"platformVersion": "12.2",
"deviceName": "Apple TV",
...
}
```
### 功能参数
XCUITest 驱动支持许多通用的功能参数 [Appium
capabilities](/docs/en/writing-running-appium/caps.md), 同时,有许多特有的功能参数,可以参考这篇文档查找这些功能参数:[appium-xcuitest-driver
README](https://github.com/appium/appium-xcuitest-driver#desired-capabilities).
如果要在 Safari 上测试,而不是在您的 App 上测试的话,请设置 `app` 为空,并且设置 `browserName` 为 `Safari`。
### 命令
如果需要查看 Appium 支持的命令,及它们是如何映射到 XCUITest 驱动的,可以参考:[API
Reference](#TODO).
### 基础设置
_(我们推荐使用 [Homebrew](https://brew.sh) 来安装系统依赖)_
1. 确保您拥有 Appium 的基础依赖(例如:Node 与 NPM)来安装和配置 Appium。
如果您的自动化测试不使用真机运行,那么您已经完成设置了! 使用模拟器运行一个自动化测试app, 参数 `app` 需要设置为 App 的绝对路径或下载地址,它需要是一个后缀为 `.app` 或 `.app.zip` 这样的,为模拟器构建的 App 文件。
### 真机设备设置
Automating a real device with XCUITest is considerably more complicated,
在真机上使用 XCUITest 进行自动化要复杂得多,原因是
苹果对在真实设备上运行应用程序的限制。请查看 [XCUITest real device setup doc](ios-xcuitest-real-devices.md) 获取介绍 。
一旦设置完成,在真机上运行会话可以使用这些功能:
* `app` 或 `bundleId` - 一个是:指定应用程序 (您使用的 `.ipa` 文件的本地路径或下载地址), 另一个是:如果您已经安装了 App, 用以标示您需要让 Appium 拉起哪一个应用。
* `udid` - 指定要在哪个设备上运行测试。 如果只有一个设备的话,它可以设置为 `auto` , Appium 将会自动找到并使用该设备。
### 可选设置
* 安装 idb 可以更好地处理各种iOS模拟器操作,
例如:生物特征、地理位置设置和窗口聚焦。
* 参考 https://github.com/appium/appium-idb#installation 以安装一些必要的库 (自 Appium 1.14.0 起 )
* 安装 [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils)
以设置 [权限相关参数](https://github.com/appium/appium-xcuitest-driver#desired-capabilities)
### 运行测试生成的文件
在 iOS 上测试生成的文件有时会很大。 它包括:日志文件、临时文件以及Xcode运行时产生的数据。如果需要删除这些文件,一般来说,可以从以下地址找到它们:
```
$HOME/Library/Logs/CoreSimulator/*
$HOME/Library/Developer/Xcode/DerivedData/*
```
### 键盘设置
高于 Appium 1.14.0,Appium 默认配置键盘首选项以让测试运行地更加稳定。您可以通过这些 API 来设置。
- 可以在 _Keyboards_ 关闭 `Auto-Correction`
- 可以在 _Keyboards_ 关闭 `Predictive`
- Mark keyboard tutorial as complete
- (仅模拟器生效) 打开虚拟键盘
### 可访问偏好设置调整
在某些情况下,启用以下首选项有助于使某些视图元素可访问。
Appium不会自动修改这些设置,因为它们可能会影响被测试应用程序的执行方式。
如果需要这些设置,需要您手动设置。
- 可以在 _Settings > Accessibility_ 打开 `Spoken Content`
- 可以在 _Settings > Accessibility_ 打开 `Speak Selection`
+41
View File
@@ -0,0 +1,41 @@
# Appium version 1 End Of Life (EOL) Plan
Appium 1 has been around for quite a while and now is the time to move
project development forward and to provide even better automation
experience with Appium version 2.
## Upgrading to Appium 2.x
While Appium 2.0 leaves most of the fundamental automation behaviors from the various platfrom
drivers untouched, it does rework the way that drivers are installed and accessed, and enforces
stricter protocol usage, among other changes. For that reason, it's important to fully read,
understand, and implement the steps discussed in the [Appium 1.x to 2.x Migration
Guide](../advanced-concepts/migrating-to-appium-2.0.md). There you will find a full list of the
changes, their rationale, and what you will need to do to keep your tests running well.
## Transition Plan
As of May 2020, Appium 2.0 has been in beta release and ready for your use and testing. Please
begin the process of ensuring your test suites, or any other software built on Appium, is
compatible with Appium 2.0.
The tentative release date of Appium 2.0 is *September 15, 2021*. After the release of
Appium 2.0 (whether it is on that date or not), we will not be making any feature additions to the
Appium 1.x server. We will however make the following kinds of updates for 6 months following the
release of Appium 2.0 (until March 15, 2022):
* Feature updates and bug fixes to core drivers (XCUITest, UiAutomator2, Espresso). For the first
6 months of Appium 2.x's life, the core drivers will be supported on both Appium 1.x and 2.x.
* Bug fixes to the Appium 1.x server.
Once the 6 months of transition have passed, we will publish new versions of the core drivers that
will only be compatible with Appium 2.x. From that point forward, no features, platform support, or
patches will be added to 1.x.
Based on the current release schedule, it is likely that Appium 1.22.x will be the last in the set
of Appium 1.x releases.
## Documentation
Since Appium 2.0's documentation may not be published on the website until its release, you may
want to check out the `docs` folders in this repo, under the `2.0` git branch.
@@ -0,0 +1,129 @@
# Migrating to Appium 2.x from Appium 1.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/tag/v2.0.0-beta) for a more comprehensive list of changes. Here we call out the breaking changes and what you need to do do account for them.
#### 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](../drivers/driver-cli.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 -g appium --drivers=xcuitest,uiautomator2
```
This will install Appium and the two drivers for you in one go.
#### 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:
```
appium driver list --updates
```
If any updates are available, you can then run the `update` command for any given driver:
```
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.
#### 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 "MSJONWP" (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.
*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).
*Removed Commands*
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.
#### 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](#TODO). 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-plugins/tree/master/packages/images).
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 --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.
#### 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.
#### 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.
#### "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).
#### 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
*Server Plugins*
TODO
*Client Plugins*
TODO
#### Install drivers and plugins from anywhere
TODO
#### Driver and Plugin CLI args
TODO
@@ -57,7 +57,12 @@ get_clipboard
Get the content of the system clipboard
(For iOS real devices) Apple security preferences require the WebDriverAgentRunner application to be in [foreground](https://github.com/appium/WebDriverAgent/issues/330) in order to be able to receive the system clipboard content. Otherwise an empty string is always returned. Consider using [Activate App](/docs/en/commands/device/app/activate-app.md) and [Background App](/docs/en/commands/device/app/background-app.md) to change the foreground application.
Activate App command cannot launch the WebDriverAgent properly on some environments. It depends on XCTest framework by Apple. Then, you should launch the WebDriverAgent using its Springboard's application icon. Follow the example below.
1. Open springboard, `@driver.activate_app 'com.apple.springboard'`.
2. Find the WDA icon with `@driver.find_element :accessibility_id, 'WebDriverAgentRunner-Runner'` (It should be visible on the screen) and click it.
3. Call the get clipboard API.
4. Open the application under test with the [Activate App](/docs/en/commands/device/app/activate-app.md).
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/device/clipboard/get-clipboard.yml)
## Support
@@ -1,7 +1,7 @@
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/session/logs/get-log-types.yml)
# Get available log types
Get the log for a given log type. Log buffer is reset after each request
Get available log types as a list of strings
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/session/logs/get-log-types.yml)
## Example Usage
@@ -1,23 +1,70 @@
# Releasing Appium
Appium follows the GitLab flow approach. Releases are made on release branches that have the format `releases/x.y` where `x` is a major version and `y` is a minor version. The main Appium repository doesn't strictly follow semver (unlike the dependencies of Appium). A minor release (e.g.: `1.20.0`) means that we're creating a new release that has a newly created set of dependencies that are shrinkwrapped to the latest. A patch release (e.g.: `1.20.4`) isn't strictly a "patch", but just means that we're making an intermediary release that brings in small changes. For example, suppose I have a new version of `appium-xcuitest-driver` that I want to publish and it can't wait until the next minor release. To bring in that change, I would checkout the release branch (e.g.: `git checkout releases/1.20`); install the dependency (`npm install appium-xcuitest-driver@version && git commit -a -m 'bump appium-xcuitest-driver to version $version`); release a "release candidate" (see below) and then when it's ready, graduate the "release candidate" to general availability
# Releasing an Appium Core Project
## Create a release branch
1. `bash ./scripts/release-branch.sh x.y` where `x` is the major version and `y` is the minor version (example: `bash ./scripts/release-branch.sh 1.20`)
* This will create:
* A release branch `releases/1.20`
* A git tag `v1.20.0-rc.0`
* Publish a NPM package `1.20.0-rc.0` as a release candidate
1. Create a changelog pull request
This document explains how to release a new version of one or more packages in [this monorepo](https://github.com/appium/appium).
## Create a release candidate
1. Checkout the release branch (e.g.: `git checkout releases/1.21 && git pull origin releases/1.21`)
1. `bash ./scripts/release.sh rc`
Note: the shell commands herein expect `lerna` in your `PATH`; you can also use `npx lerna`.
> See the [`lerna publish`](https://github.com/lerna/lerna/tree/main/commands/publish) docs for more info.
## Automatic Releases
This project uses [Lerna](https://lerna.js.org) to manage the versioning and publishing of its packages. Because we use [conventional commit messages](https://conventionalcommits.org), Lerna can parse these messages to determine which packages need releasing and with what version.
There's really only one step here. To version and publish _potentially all_ packages automatically, navigate to the branch to be published, and issue:
```bash
lerna publish
```
You will then see an interactive prompt, which lists the proposed new package versions to confirm. It will look a bit like this:
```bash
lerna notice cli v4.0.0
lerna info versioning independent
lerna info Looking for changed packages since @appium/fake-driver@2.2.0
lerna info ignoring diff in paths matching [ '**/test/**', '**/*.md' ]
lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular"
Changes:
- appium: 2.0.0-beta.12 => 2.0.0-beta.13
- @appium/base-driver: 8.0.0-beta.6 => 8.0.0-beta.7
- @appium/doctor: 1.16.0 => 1.16.1
- @appium/eslint-config-appium: 4.7.0 => 4.7.1
- @appium/fake-driver: 2.2.0 => 2.2.1
- @appium/gulp-plugins: 5.5.0 => 5.5.1
- @appium/support: 2.53.0 => 2.53.1
- @appium/test-support: 1.3.3 => 1.3.4
? Are you sure you want to publish these packages? (ynH)
```
The information shown above:
1. The current version of the `lerna` executable
1. A notice that this project uses "independent" versioning; i.e., all packages have their own version numbers.
1. The _most recent Git tag_ Lerna found (`@appium/fake-driver@2.2.0`); if a changeset contains multiple tags, it only display one.
1. Globs which are ignored when computing versions, e.g., a commit message beginning with `fix(scope)` will ignore any changesets consisting of _only_ modifications to files matching the globs--if any other files are modified, `fix(scope)` would cause a "patch" version bump, as per the conventional commits specification.
1. Confirmation that Lerna is using conventional commit messages to determine versions
1. A list of all packages that have changed and their new proposed versions
In this example, _every package_ has had changes since we last published. Lerna is also configured to use prerelease versions, where appropriate.
If you are happy with what it's going to do, confirm with `y` and continue to bump the package versions, then publish each. This does the same thing as running `npm version` then `npm publish` in each package, in series--all relevant lifecycle scripts are executed. In addition, any new tags will be pushed to GitHub, and releases will be created corresponding to the tags. The tags are of the format `[@scope/]<package-name>@<new-version>`.
> _TBD: update w/ Lerna's behavior around changelogs and GH releases is determined or further defined_
## Releasing a Single Package
**This is not recommended.** Lerna relies heavily on Git tags to do its thing, and a mistake would be costly. It is only _possible_ because we use independent versioning.
1. Navigate to a package directory and call `npm version --no-git-tag-version <newversion>`.
1. You **must** tag the version manually using the format `[@scope/]<package-name>@<new-version>`.
1. Push the new changeset and tag to GitHub.
1. Issue `npm publish` from this package directory.
1. You will need to edit `CHANGELOG.md` and create the GitHub release yourself, which sucks for you.
## Publish to GA
1. Checkout the release branch (e.g.: `git checkout releases/1.21 && git pull origin releases/1.21`)
1. `bash ./scripts/release.sh latest`
1. Update the site docs by going to https://github.com/appium/appium.io/pulls and merging the latest pull request that was opened by the Triager bot. Close any other pull requests opened by Triager bot.
1. Create a new release on GitHub: go to `https://github.com/appium/appium/releases/tag/v<VERSION>` and hit "Edit Tag". Make the release name `<VERSION>` (e.g., `2.0.5`), then paste in the changelog (but not the changelog header for this version). If it's a beta release, mark as pre-release.
1. Create a new post on discuss.appium.io announcing the release. Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
1. Begin process of releasing `appium-desktop`.
1. Create a new post on <https://discuss.appium.io> announcing the release of the package(s). Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
1. Begin process of releasing [`appium-desktop`](https://github.com/appium/appium-desktop).
1. Notify @jlipps to so he can tweet a link to the discuss post.
+2 -57
View File
@@ -15,83 +15,28 @@ All flags are optional, but some are required in conjunction with certain others
|----|-------|-----------|-------|
|`--shell`|null|Enter REPL mode||
|`--allow-cors`|false|Turn on CORS compatibility mode, which will allow connections to the Appium server from within websites hosted on any domain. Be careful when enabling this feature, since there is a potential security risk if you visit a website that uses a cross-domain request to initiate or introspect sessions on your running Appium server.||
|`--ipa`|null|(IOS-only) abs path to compiled .ipa file|`--ipa /abs/path/to/my.ipa`|
|`-a`, `--address`|0.0.0.0|IP Address to listen on|`--address 0.0.0.0`|
|`-p`, `--port`|4723|port to listen on|`--port 4723`|
|`-pa`, `--base-path`|null|Initial path segment where the Appium/WebDriver API will be hosted. Every endpoint will be behind this segment.|`--base-path /wd/hub`|
|`-ca`, `--callback-address`|null|callback IP Address (default: same as --address)|`--callback-address 127.0.0.1`|
|`-cp`, `--callback-port`|null|callback port (default: same as port)|`--callback-port 4723`|
|`-bp`, `--bootstrap-port`|4724|(Android-only) port to use on device to talk to Appium|`--bootstrap-port 4724`|
|`-r`, `--backend-retries`|3|(iOS-only) How many times to retry launching Instruments before saying it crashed or timed out|`--backend-retries 3`|
|`--session-override`|false|Enables session override (clobbering)||
|`-l`, `--pre-launch`|false|Pre-launch the application before allowing the first session (Requires --app and, for Android, --app-pkg and --app-activity)||
|`-g`, `--log`|null|Also send log output to this file|`--log /path/to/appium.log`|
|`--log-level`|debug|Set the server log level for console and logfile (specified as `console-level:logfile-level`, with both being the same if only one value is supplied). Possible values are `debug`, `info`, `warn`, `error`, which are progressively less verbose.|`--log-level error:debug`|
|`--log-timestamp`|false|Show timestamps in console output||
|`--local-timezone`|false|Use local timezone for timestamps||
|`--log-no-colors`|false|Do not use colors in console output||
|`-G`, `--webhook`|null|Also send log output to this HTTP listener|`--webhook localhost:9876`|
|`--safari`|false|(IOS-Only) Use the safari app||
|`--default-device`, `-dd`|false|(IOS-Simulator-only) use the default simulator that instruments launches on its own||
|`--force-iphone`|false|(IOS-only) Use the iPhone Simulator no matter what the app wants||
|`--force-ipad`|false|(IOS-only) Use the iPad Simulator no matter what the app wants||
|`--tracetemplate`|null|(IOS-only) .tracetemplate file to use with Instruments|`--tracetemplate /Users/me/Automation.tracetemplate`|
|`--instruments`|null|(IOS-only) path to instruments binary|`--instruments /path/to/instruments`|
|`--nodeconfig`|null|Configuration JSON file to register appium with selenium grid|`--nodeconfig /abs/path/to/nodeconfig.json`|
|`-ra`, `--robot-address`|0.0.0.0|IP Address of robot|`--robot-address 0.0.0.0`|
|`-rp`, `--robot-port`|-1|port for robot|`--robot-port 4242`|
|`--chromedriver-port`|9515|Port upon which ChromeDriver will run|`--chromedriver-port 9515`|
|`--chromedriver-executable`|null|ChromeDriver executable full path||
|`--show-config`|false|Show info about the appium server configuration and exit||
|`--no-perms-check`|false|Bypass Appium's checks to ensure we can read/write necessary files||
|`--strict-caps`|false|Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device||
|`--isolate-sim-device`|false|Xcode 6 has a bug on some platforms where a certain simulator can only be launched without error if all other simulator devices are first deleted. This option causes Appium to delete all devices other than the one being used by Appium. Note that this is a permanent deletion, and you are responsible for using simctl or xcode to manage the categories of devices used with Appium.||
|`--tmp`|null|Absolute path to directory Appium can use to manage temporary files, like built-in iOS apps it needs to move around. On *nix/Mac defaults to /tmp, on Windows defaults to C:\Windows\Temp||
|`--trace-dir`|null|Absolute path to directory Appium use to save ios instruments traces, defaults to <tmp dir>/appium-instruments||
|`--debug-log-spacing`|false|Add exaggerated spacing in logs to help with visual inspection||
|`--suppress-adb-kill-server`|false|(Android-only) If set, prevents Appium from killing the adb server instance||
|`--async-trace`|false|Add long stack traces to log entries. Recommended for debugging only.||
|`--webkit-debug-proxy-port`|27753|(IOS-only) Local port used for communication with ios-webkit-debug-proxy|`--webkit-debug-proxy-port 27753`|
|`-dc`, `--default-capabilities`|{}|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities.|`--default-capabilities [ '{"app": "myapp.app", "deviceName": "iPhone Simulator"}' | /path/to/caps.json ]`|
|`--reboot`|false| - (Android-only) reboot emulator after each session and kill it at the end||
|`--command-timeout`|60|[DEPRECATED] No effect. This used to be the default command timeout for the server to use for all sessions (in seconds and should be less than 2147483). Use newCommandTimeout cap instead||
|`-k`, `--keep-artifacts`|false|[DEPRECATED] - no effect, trace is now in tmp dir by default and is cleared before each run. Please also refer to the --trace-dir flag.||
|`--platform-name`|null|[DEPRECATED] - Name of the mobile platform: iOS, Android, or FirefoxOS|`--platform-name iOS`|
|`--platform-version`|null|[DEPRECATED] - Version of the mobile platform|`--platform-version 7.1`|
|`--automation-name`|null|[DEPRECATED] - Name of the automation tool: Appium, XCUITest, etc.|`--automation-name Appium`|
|`--device-name`|null|[DEPRECATED] - Name of the mobile device to use|`--device-name iPhone Retina (4-inch), Android Emulator`|
|`--browser-name`|null|[DEPRECATED] - Name of the mobile browser: Safari or Chrome|`--browser-name Safari`|
|`--app`|null|[DEPRECATED] - IOS: abs path to simulator-compiled .app file or the bundle_id of the desired target on device; Android: abs path to .apk file|`--app /abs/path/to/my.app`|
|`-lt`, `--launch-timeout`|90000|[DEPRECATED] - (iOS-only) how long in ms to wait for Instruments to launch||
|`--language`|null|[DEPRECATED] - Language for the iOS simulator / Android Emulator|`--language en`|
|`--locale`|null|[DEPRECATED] - Locale for the iOS simulator / Android Emulator|`--locale en_US`|
|`-U`, `--udid`|null|[DEPRECATED] - Unique device identifier of the connected physical device|`--udid 1adsf-sdfas-asdf-123sdf`|
|`--orientation`|null|[DEPRECATED] - (IOS-only) use LANDSCAPE or PORTRAIT to initialize all requests to this orientation|`--orientation LANDSCAPE`|
|`--no-reset`|false|[DEPRECATED] - Do not reset app state between sessions (IOS: do not delete app plist files; Android: do not uninstall app before new session)||
|`--full-reset`|false|[DEPRECATED] - (iOS) Delete the entire simulator folder. (Android) Reset app state by uninstalling app instead of clearing app data. On Android, this will also remove the app after the session is complete.||
|`--app-pkg`|null|[DEPRECATED] - (Android-only) Java package of the Android app you want to run (e.g., com.example.android.myApp)|`--app-pkg com.example.android.myApp`|
|`--app-activity`|null|[DEPRECATED] - (Android-only) Activity name for the Android activity you want to launch from your package (e.g., MainActivity)|`--app-activity MainActivity`|
|`--app-wait-package`|false|[DEPRECATED] - (Android-only) Package name for the Android activity you want to wait for (e.g., com.example.android.myApp)|`--app-wait-package com.example.android.myApp`|
|`--app-wait-activity`|false|[DEPRECATED] - (Android-only) Activity name for the Android activity you want to wait for (e.g., SplashActivity)|`--app-wait-activity SplashActivity`|
|`--device-ready-timeout`|5|[DEPRECATED] - (Android-only) Timeout in seconds while waiting for device to become ready|`--device-ready-timeout 5`|
|`--android-coverage`|false|[DEPRECATED] - (Android-only) Fully qualified instrumentation class. Passed to -w in adb shell am instrument -e coverage true -w |`--android-coverage com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation`|
|`--avd`|null|[DEPRECATED] - (Android-only) Name of the avd to launch|`--avd @default`|
|`--avd-args`|null|[DEPRECATED] - (Android-only) Additional emulator arguments to launch the avd|`--avd-args -no-snapshot-load`|
|`--use-keystore`|false|[DEPRECATED] - (Android-only) When set the keystore will be used to sign apks.||
|`--keystore-path`|&lt;user&gt;/.android/debug.keystore|[DEPRECATED] - (Android-only) Path to keystore||
|`--keystore-password`|android|[DEPRECATED] - (Android-only) Password to keystore||
|`--key-alias`|androiddebugkey|[DEPRECATED] - (Android-only) Key alias||
|`--key-password`|android|[DEPRECATED] - (Android-only) Key password||
|`--intent-action`|android.intent.action.MAIN|[DEPRECATED] - (Android-only) Intent action which will be used to start activity|`--intent-action android.intent.action.MAIN`|
|`--intent-category`|android.intent.category.LAUNCHER|[DEPRECATED] - (Android-only) Intent category which will be used to start activity|`--intent-category android.intent.category.APP_CONTACTS`|
|`--intent-flags`|0x10200000|[DEPRECATED] - (Android-only) Flags that will be used to start activity|`--intent-flags 0x10200000`|
|`--intent-args`|null|[DEPRECATED] - (Android-only) Additional intent arguments that will be used to start activity|`--intent-args 0x10200000`|
|`--dont-stop-app-on-reset`|false|[DEPRECATED] - (Android-only) When included, refrains from stopping the app before restart||
|`--calendar-format`|null|[DEPRECATED] - (IOS-only) calendar format for the iOS simulator|`--calendar-format gregorian`|
|`--native-instruments-lib`|false|[DEPRECATED] - (IOS-only) IOS has a weird built-in unavoidable delay. We patch this in appium. If you do not want it patched, pass in this flag.||
|`--keep-keychains`|false|[DEPRECATED] - (iOS-only) Whether to keep keychains (Library/Keychains) when reset app between sessions||
|`--localizable-strings-dir`|en.lproj|[DEPRECATED] - (IOS-only) the relative path of the dir where Localizable.strings file resides |`--localizable-strings-dir en.lproj`|
|`--show-ios-log`|false|[DEPRECATED] - (IOS-only) if set, the iOS system log will be written to the console||
|`--relaxed-security`|false|Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it is not the case if a client could potentially break out of the session sandbox. Can override enabling of specific features with --deny-insecure. See also the [security doc](/docs/en/writing-running-appium/security.md)||
|`--allow-insecure`|[]|Allow a list of features which are considered insecure and must be turned on explicitly by system administrators. Feature names are documented by the relevant server/driver. Should be a comma-separated list, or a path to a filename containing one feature name per line. Features listed in --deny-insecure will override anything listed here. Does not make sense to use in conjunction with --relaxed-security. See also the [security doc](/docs/en/writing-running-appium/security.md)|`--allow-insecure=foo,bar`|
|`--deny-insecure`|[]|Specify a list of features which will never be allowed to run, even if --relaxed-security is turned on, and even if feature names are listed with --allow-insecure. Should be a comma-separated list, or a path to a filename containing one feature name per line. See also the [security doc](/docs/en/writing-running-appium/security.md)|`--deny-insecure=foo,bar`|
+11 -8
View File
@@ -7,6 +7,7 @@ module.exports = {
['Getting Started', 'getting-started.md'],
['Supported Platforms', 'platform-support.md'],
['API Documentation', 'api.md'],
['Appium 1.x EOL Plan', '1.x-eol.md'],
]],
['Drivers', ['drivers',
['Driver CLI', 'driver-cli.md'],
@@ -300,6 +301,7 @@ module.exports = {
]],
]],
['Advanced', ['advanced-concepts',
['Migrating to Appium 2.x', 'migrating-to-appium-2.0.md'],
['Finding Image Elements', 'image-elements.md'],
['Using Element Finding Plugins', 'element-finding-plugins.md'],
['Migrating to XCUITest', 'migrating-to-xcuitest.md'],
@@ -330,17 +332,18 @@ module.exports = {
['已支持的平台', 'platform-support.md'],
// ['API 文档', 'api.md'],
]],
/*['驱动程序', ['drivers',
['驱动程序', ['drivers',
// ['Driver CLI', 'driver-cli.md'],
['XCUITest (iOS)', 'ios-xcuitest.md'],
['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'],
['UIAutomation (iOS)', 'ios-uiautomation.md'],
['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'],
['UIAutomator (Android)', 'android-uiautomator.md'],
// ['UIAutomation (iOS)', 'ios-uiautomation.md'],
// ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'],
// ['UIAutomator (Android)', 'android-uiautomator.md'],
['UIAutomator2 (Android)', 'android-uiautomator2.md'],
['Espresso (Android)', 'android-espresso.md'],
['Windows', 'windows.md'],
['Mac', 'mac.md'],
]],*/
// ['Espresso (Android)', 'android-espresso.md'],
// ['Windows', 'windows.md'],
// ['Mac', 'mac.md'],
]],
['编写 & 运行测试', ['writing-running-appium',
['运行测试', 'running-tests.md'],
['预期功能', 'caps.md'],
+18 -25
View File
@@ -1,30 +1,23 @@
{
"ci": false,
"command": {
"exec": {
"ignore": "@appium/eslint-config-appium"
},
"version": {
"conventionalCommits": true,
"createRelease": "github"
}
},
"ignoreChanges": [
"**/test/**",
"**/*.md"
],
"npmClientArgs": [
"--no-package-lock"
],
"packages": [
"packages/*"
],
"version": "2.0.0-beta.1",
"command": {
"bootstrap": {
"npmClientArgs": [
"--no-package-lock"
],
"ci": false
},
"exec": {
"ignore": "@appium/eslint-config-appium"
}
},
"changelog": {
"repo": "appium/appium",
"cacheDir": ".changelog",
"labels": {
"PR: Spec Compliancy :eyeglasses:": ":eyeglasses: Spec Compliancy",
"PR: Breaking Change :boom:": ":boom: Breaking Change",
"PR: New Feature :rocket:": ":rocket: New Feature",
"PR: Bug Fix :bug:": ":bug: Bug Fix",
"PR: Polish :nail_care:": ":nail_care: Polish",
"PR: Docs :memo:": ":memo: Documentation",
"PR: Internal :house:": ":house: Internal"
}
}
"version": "independent"
}
+49 -29
View File
@@ -3,6 +3,17 @@
"version": "0.0.0",
"private": true,
"description": "Automation for Apps.",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"homepage": "https://appium.io",
"bugs": {
"url": "https://github.com/appium/appium/issues"
},
@@ -14,7 +25,7 @@
"author": "https://github.com/appium",
"scripts": {
"build": "lerna exec gulp transpile",
"clean": "lerna clean -y && rm -rf node_modules && rm -f package-lock.json && rm -rf packages/*/build",
"clean": "lerna clean -y && npx rimraf node_modules package-lock.json \"packages/*/build\"",
"coverage": "lerna exec gulp coveralls",
"doctor": "node ./packages/doctor",
"e2e-test": "lerna exec gulp e2e-test",
@@ -27,6 +38,10 @@
"precommit-msg": "echo 'Pre-commit checks...' && exit 0",
"reinstall": "npm run clean && npm install",
"start": "node ./packages/appium",
"sync-pkgs": "npm run sync-pkgs:appium-readme && npm run sync-pkgs:common-fields && npm run sync-pkgs:keywords",
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,repository,bugs,homepage,engines",
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages='packages/*' --packages='!packages/eslint-config-appium'",
"test": "lerna exec gulp once",
"test:appium": "lerna exec --scope=appium gulp once",
"test:base-driver": "lerna exec --scope=@appium/base-driver gulp once",
@@ -40,6 +55,7 @@
"test:gulp-plugins": "lerna exec --scope=@appium/gulp-plugins gulp once",
"test:support": "lerna exec --scope=@appium/support gulp once",
"test:test-support": "lerna exec --scope=@appium/test-support gulp once",
"preversion": "npm run sync-pkgs && git add -A packages/appium/README.md packages/*/package.json",
"watch": "lerna exec watch"
},
"pre-commit": [
@@ -51,48 +67,52 @@
},
"devDependencies": {
"@appium/eslint-config-appium": "file:./packages/eslint-config-appium",
"@appium/fake-plugin": "^1.0.0",
"@babel/core": "^7.0.0",
"@babel/eslint-parser": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-private-methods": "^7.0.0",
"@babel/plugin-syntax-bigint": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/register": "^7.0.0",
"@babel/runtime": "^7.0.0",
"appium-base-driver": "^7.6.0",
"@appium/fake-plugin": "^1.2.0",
"@babel/core": "^7.15.0",
"@babel/eslint-parser": "^7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-private-methods": "^7.14.5",
"@babel/plugin-syntax-bigint": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/register": "^7.15.3",
"@babel/runtime": "^7.15.3",
"appium-base-driver": "^7.9.0",
"asyncbox": "^2.8.0",
"babel-eslint": "^10.0.3",
"babel-eslint": "^10.1.0",
"babel-plugin-istanbul": "^6.0.0",
"babel-plugin-source-map-support": "^2.0.1",
"chai": "4.x",
"chai-as-promised": "7.x",
"chai-webdriverio-async": "^2.6.0",
"colors": "^1.4.0",
"eslint-find-rules": "^3.6.1",
"fancy-log": "^1.3.2",
"finalhandler": "^1.1.1",
"fancy-log": "^1.3.3",
"finalhandler": "^1.1.2",
"get-port": "^5.1.1",
"gulp": "^4.0.0",
"handlebars": "^4.2.0",
"gulp": "^4.0.2",
"handlebars": "^4.7.7",
"lerna": "^4.0.0",
"lint-staged": "^11.0.0",
"mjpeg-server": "^0.3.0",
"mocha": "^8.0.1",
"lint-staged": "^11.1.2",
"mjpeg-server": "^0.3.1",
"mocha": "9.0.1",
"pre-commit": "1.x",
"querystring": "^0.2.0",
"serve-static": "^1.13.1",
"sinon": "^9.0.0",
"querystring": "^0.2.1",
"serve-static": "^1.14.1",
"sinon": "11.1.1",
"sync-monorepo-packages": "^0.3.4",
"validate.js": "^0.13.0",
"wd": "^1.14.0",
"webdriverio": "^6.12.0",
"yaml-js": "^0.2.0"
"webdriverio": "^6.12.1",
"yaml-js": "^0.2.3"
},
"engines": {
"node": ">=12",
"npm": ">=6"
},
"publishConfig": {
"access": "public"
}
}
+29
View File
@@ -0,0 +1,29 @@
# Change Log
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.13](https://github.com/appium/appium/compare/appium@2.0.0-beta.12...appium@2.0.0-beta.13) (2021-08-16)
# 2.0.0-beta (2021-08-13)
### Bug Fixes
* **appium:** load plugins before main server to allow plugin monkeypatching ([#15441](https://github.com/appium/appium/issues/15441)) ([52f4fc6](https://github.com/appium/appium/commit/52f4fc6a2cbdb612888ab2a23a704be32594a006))
* **appium:** update references to @appium/support ([9295d0c](https://github.com/appium/appium/commit/9295d0c68284443ecd73add1f8fe0ff7910a7d7d))
* **appium:** use proper base driver package & random ports ([e14d4fb](https://github.com/appium/appium/commit/e14d4fbd0fce16f208569b0cb9149b6307f78a5f))
* **appium:** when updating extensions, make sure to actually remove the previous one first to avoid npm reinstall issues ([4839419](https://github.com/appium/appium/commit/483941974881bf2ad362c6e6cc35883743abcd2a))
* **appium:** when updating extensions, package fields should also be updated in extensions.yaml ([756688b](https://github.com/appium/appium/commit/756688bddb624a6e9a5b5b6403db76eac65774f3))
* do not assume git root and package.json have the same parent dir ([3c5fba7](https://github.com/appium/appium/commit/3c5fba7b38e02f4216d3a26340948d070f7ea9d9))
* doc generation ([9e33c7a](https://github.com/appium/appium/commit/9e33c7ac1135306c6f0d1ff83b8076aecb54c554))
* e2e-tests ([2499b49](https://github.com/appium/appium/commit/2499b49936660280eefaeb26cb3e4e9f82e36c20))
* use random test port ([935b1f8](https://github.com/appium/appium/commit/935b1f80a47e89ccdf81781f35be5123bf8673d5))
### Features
* add "run" command to the client interface in order to allow running driver-defined scripts ([#15356](https://github.com/appium/appium/issues/15356)) ([a265476](https://github.com/appium/appium/commit/a2654762b6a9156380bcdf53df4cb0a8deb061fa))
* **appium:** Add driver and plugin server arg injection feature ([#15388](https://github.com/appium/appium/issues/15388)) ([d3c11e3](https://github.com/appium/appium/commit/d3c11e364dffff87ac38ac8dc3ad65a1e4534a9a))
+119 -3
View File
@@ -1,5 +1,121 @@
# appium
## Appium
## TODO
[![NPM version](https://badge.fury.io/js/appium.svg)](https://npmjs.org/package/appium)
[![Dependency Status](https://david-dm.org/appium/appium.svg)](https://david-dm.org/appium/appium)
[![devDependency Status](https://david-dm.org/appium/appium/dev-status.svg)](https://david-dm.org/appium/appium#info=devDependencies)
- Either add a specific `README.md` here or copy it from the root as a prepublish step.
[![Monthly Downloads](https://img.shields.io/npm/dm/appium.svg)](https://npmjs.org/package/appium)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_shield)
Appium is an open-source, cross-platform test automation tool for native,
hybrid, and 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.
### Supported Platforms
Appium supports app automation across a variety of platforms, like iOS,
Android, and Windows. Each platform is supported by one or more "drivers",
which know how to automate that particular platform. Choose a driver below for
specific information about how that driver works and how to set it up:
* iOS
* The [XCUITest Driver](/docs/en/drivers/ios-xcuitest.md)
* (DEPRECATED) The [UIAutomation Driver](/docs/en/drivers/ios-uiautomation.md)
* Android
* The [Espresso Driver](/docs/en/drivers/android-espresso.md)
* The [UiAutomator2 Driver](/docs/en/drivers/android-uiautomator2.md)
* (DEPRECATED) The [UiAutomator Driver](/docs/en/drivers/android-uiautomator.md)
* The [Windows Driver](/docs/en/drivers/windows.md) (for Windows Desktop apps)
* The [Mac Driver](/docs/en/drivers/mac.md) (for Mac Desktop apps)
### Why Appium?
1. You don't have to recompile your app or modify it in any way, due
to the use of standard automation APIs on all platforms.
2. You can write tests with your favorite dev tools using any
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html)-compatible
language such as Java, Objective-C, JavaScript (Node), PHP, Python, Ruby,
C#, Clojure, or Perl with the Selenium WebDriver API and [language-specific
client libraries](/docs/en/about-appium/appium-clients.md).
3. You can use any testing framework.
4. Appium has built-in mobile web and hybrid app support. Within the same
script, you can switch seamlessly between native app automation and webview
automation, all using the WebDriver model that's already the standard for
web automation.
Investing in the
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means
you are betting on a single, free, and open protocol for testing that has become
a web standard. Don't lock yourself into a proprietary stack.
For example, if you use Apple's XCUITest library without Appium you can only
write tests using Obj-C/Swift, and you can only run tests through Xcode.
Similarly, with Google's UiAutomator or Espresso, you can only write tests in
Java/Kotlin. Appium opens up the possibility of true cross-platform native app
automation, for mobile and beyond. Finally!
If you're new to Appium or want a more comprehensive description of what this is all
about, please read our [Introduction to Appium
Concepts](/docs/en/about-appium/intro.md).
### Requirements
Your environment needs to be set up for the particular platforms that you want
to run tests on. Each of the drivers above documents the requirements for their
particular brand of automation. At a minimum, you will need to be able to run
Node.js 10+.
### Get Started
Check out our [Getting Started](/docs/en/about-appium/getting-started.md) guide
to get going with Appium.
There is also a sample code that contains [many examples of tests in a variety
of different languages](https://github.com/appium/appium/tree/master/sample-code)!
### Documentation
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
always find the full list of Appium doc pages at [Appium's GitHub
Repo](https://github.com/appium/appium/tree/master/docs/en/) as well.
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
in the appium.io repository with the documentation update.
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
### Contributing
Please take a look at our [contribution documentation](CONTRIBUTING.md)
for instructions on how to build, test, and run Appium from the source.
### Roadmap
Interested in where Appium is heading in the future? Check out the [Roadmap](ROADMAP.md)
### Project History, Credits & Inspiration
* [History](http://appium.io/history)
* [Credits](/docs/en/contributing-to-appium/credits.md)
### User Forums
Announcements and debates often take place on the [Discussion
Group](https://discuss.appium.io), be sure to sign up!
### Troubleshooting
We put together a [troubleshooting
guide](/docs/en/writing-running-appium/other/troubleshooting.md). Please have a look
here first if you run into any problems. It contains instructions for checking
a lot of common errors and how to get in touch with the community if you're
stumped.
### License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
+1 -1
View File
@@ -1,6 +1,6 @@
const _ = require('lodash');
const { asyncify } = require('asyncbox');
const { fs, logger } = require('appium-support');
const { fs, logger } = require('@appium/support');
const path = require('path');
const log = new logger.getLogger('ShrinkwrapValidator');
@@ -8,6 +8,12 @@ description:
(For iOS real devices)
Apple security preferences require the WebDriverAgentRunner application to be in [foreground](https://github.com/appium/WebDriverAgent/issues/330) in order to be able to receive the system clipboard content. Otherwise an empty string is always returned. Consider using [Activate App](/docs/en/commands/device/app/activate-app.md) and [Background App](/docs/en/commands/device/app/background-app.md) to change the foreground application.
Activate App command cannot launch the WebDriverAgent properly on some environments. It depends on XCTest framework by Apple. Then, you should launch the WebDriverAgent using its Springboard's application icon. Follow the example below.
1. Open springboard, `@driver.activate_app 'com.apple.springboard'`.
2. Find the WDA icon with `@driver.find_element :accessibility_id, 'WebDriverAgentRunner-Runner'` (It should be visible on the screen) and click it.
3. Call the get clipboard API.
4. Open the application under test with the [Activate App](/docs/en/commands/device/app/activate-app.md).
example_usage:
java:
@@ -1,6 +1,6 @@
---
name: Get available log types
short_description: Get the log for a given log type. Log buffer is reset after each request
short_description: Get available log types as a list of strings
example_usage:
java:
+1 -1
View File
@@ -1,6 +1,6 @@
import path from 'path';
import yaml from 'yaml-js';
import { fs, mkdirp, util } from 'appium-support';
import { fs, mkdirp, util } from '@appium/support';
import validate from 'validate.js';
import Handlebars from 'handlebars';
import _ from 'lodash';
+4 -1
View File
@@ -12,6 +12,8 @@ const path = require('path');
const fs = require('fs');
const log = require('fancy-log');
gulp.task('copy-fixtures', () => gulp.src('./test/fixtures/*').pipe(gulp.dest('./build/test/fixtures/')));
// remove 'fsevents' from shrinkwrap, since it causes errors on non-Mac hosts
// see https://github.com/npm/npm/issues/2679
@@ -46,7 +48,8 @@ boilerplate({
test: {
files: ['${testDir}/**/*-specs.js']
},
testTimeout: 160000
testTimeout: 160000,
postTranspile: ['copy-fixtures']
});
// generates server arguments readme
+11 -6
View File
@@ -5,8 +5,8 @@ import { findMatchingDriver } from './drivers';
import { BaseDriver, errors, isSessionCommand } from '@appium/base-driver';
import B from 'bluebird';
import AsyncLock from 'async-lock';
import { parseCapsForInnerDriver, pullSettings } from './utils';
import { util } from 'appium-support';
import { parseExtensionArgs, parseCapsForInnerDriver, pullSettings } from './utils';
import { util } from '@appium/support';
const desiredCapabilityConstraints = {
automationName: {
@@ -27,7 +27,7 @@ class AppiumDriver extends BaseDriver {
// It is necessary to set `--tmp` here since it should be set to
// process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
// The process argument will be referenced by BaseDriver.
// Please call appium-support.tempDir module to apply this benefit.
// Please call @appium/support.tempDir module to apply this benefit.
if (args.tmpDir) {
process.env.APPIUM_TMP_DIR = args.tmpDir;
}
@@ -155,7 +155,8 @@ class AppiumDriver extends BaseDriver {
const {
driver: InnerDriver,
version: driverVersion
version: driverVersion,
driverName
} = this._findMatchingDriver(this.driverConfig, desiredCaps);
this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion);
@@ -164,8 +165,9 @@ class AppiumDriver extends BaseDriver {
}
let runningDriversData, otherPendingDriversData;
const d = new InnerDriver(this.args);
const parsedInnerDriverArgs = parseExtensionArgs(this.args.driverArgs, driverName);
const d = new InnerDriver(this.args, true, parsedInnerDriverArgs);
// We want to assign security values directly on the driver. The driver
// should not read security values from `this.opts` because those values
// could have been set by a malicious user via capabilities, whereas we
@@ -405,7 +407,10 @@ class AppiumDriver extends BaseDriver {
sessionId = _.truncate(sessionId, {length: 11});
return this.pluginClasses.map((PluginClass) => {
const name = `${PluginClass.pluginName} (${sessionId})`;
return new PluginClass(name);
const opts = {
pluginArgs: parseExtensionArgs(this.args.pluginArgs, PluginClass.pluginName),
};
return new PluginClass(name, opts);
});
}
+43 -51
View File
@@ -1,6 +1,6 @@
import { DEFAULT_BASE_PATH } from '@appium/base-driver';
import {
parseSecurityFeatures, parseDefaultCaps,
parseSecurityFeatures, parseJsonStringOrFile,
parsePluginNames, parseInstallTypes, parseDriverNames
} from './parser-helpers';
import {
@@ -8,7 +8,7 @@ import {
DRIVER_TYPE, PLUGIN_TYPE
} from '../extension-config';
import {
DEFAULT_CAPS_ARG, StoreDeprecatedDefaultCapabilityAction
DEFAULT_CAPS_ARG
} from './argparse-actions';
const DRIVER_EXAMPLE = 'xcuitest';
@@ -69,13 +69,6 @@ const serverArgs = [
dest: 'allowCors',
}],
[['--reboot'], {
default: false,
dest: 'reboot',
action: StoreDeprecatedDefaultCapabilityAction,
required: false,
help: '(Android-only) reboot emulator after each session and kill it at the end',
}],
[['-a', '--address'], {
default: '0.0.0.0',
@@ -190,23 +183,6 @@ const serverArgs = [
help: 'Configuration JSON file to register appium with selenium grid',
}],
[['--chromedriver-port'], {
default: null,
dest: 'chromeDriverPort',
required: false,
action: StoreDeprecatedDefaultCapabilityAction,
type: 'int',
help: 'Port upon which ChromeDriver will run. If not given, Android driver will pick a random available port.',
}],
[['--chromedriver-executable'], {
default: null,
dest: 'chromedriverExecutable',
action: StoreDeprecatedDefaultCapabilityAction,
required: false,
help: 'ChromeDriver executable full path',
}],
[['--show-config'], {
default: false,
dest: 'showConfig',
@@ -257,13 +233,6 @@ const serverArgs = [
help: 'Add exaggerated spacing in logs to help with visual inspection',
}],
[['--suppress-adb-kill-server'], {
dest: 'suppressKillServer',
default: false,
action: 'store_true',
required: false,
help: '(Android-only) If set, prevents Appium from killing the adb server instance',
}],
[['--long-stacktrace'], {
dest: 'longStacktrace',
@@ -273,28 +242,11 @@ const serverArgs = [
help: 'Add long stack traces to log entries. Recommended for debugging only.',
}],
[['--webkit-debug-proxy-port'], {
default: 27753,
dest: 'webkitDebugProxyPort',
action: StoreDeprecatedDefaultCapabilityAction,
required: false,
type: 'int',
help: '(IOS-only) Local port used for communication with ios-webkit-debug-proxy'
}],
[['--webdriveragent-port'], {
default: 8100,
dest: 'wdaLocalPort',
action: StoreDeprecatedDefaultCapabilityAction,
required: false,
type: 'int',
help: '(IOS-only, XCUITest-only) Local port used for communication with WebDriverAgent'
}],
[['-dc', DEFAULT_CAPS_ARG], {
dest: 'defaultCapabilities',
default: {},
type: parseDefaultCaps,
type: parseJsonStringOrFile,
required: false,
help: 'Set the default desired capabilities, which will be set on each ' +
'session unless overridden by received capabilities. For example: ' +
@@ -338,6 +290,28 @@ const serverArgs = [
'enabled even if also listed in --allow-insecure, and even if --relaxed-security ' +
'is turned on. For example: execute_driver_script,adb_shell',
}],
[['--driver-args'], {
dest: 'driverArgs',
default: {},
type: parseJsonStringOrFile,
required: false,
help: 'Set the default desired client arguments for a driver, ' +
'For example: ' +
'[ \'{"xcuitest": {"foo1": "bar1", "foo2": "bar2"}}\' ' +
'| /path/to/driverArgs.json ]'
}],
[['--plugin-args'], {
dest: 'pluginArgs',
default: {},
type: parseJsonStringOrFile,
required: false,
help: 'Set the default desired client arguments for a plugin, ' +
'For example: ' +
'[ \'{"images": {"foo1": "bar1", "foo2": "bar2"}}\' ' +
'| /path/to/pluginArgs.json ]'
}],
];
// this set of args works for both drivers and plugins ('extensions')
@@ -432,11 +406,29 @@ function makeUpdateArgs (type) {
];
}
function makeRunArgs (type) {
return [
...globalExtensionArgs,
[[type], {
type: 'str',
help: `Name of the ${type} to run a script from, for example: ` +
type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE,
}],
[['scriptName'], {
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`
}],
];
}
for (const type of [DRIVER_TYPE, PLUGIN_TYPE]) {
extensionArgs[type].list = makeListArgs(type);
extensionArgs[type].install = makeInstallArgs(type);
extensionArgs[type].uninstall = makeUninstallArgs(type);
extensionArgs[type].update = makeUpdateArgs(type);
extensionArgs[type].run = makeRunArgs(type);
}
export {
@@ -24,6 +24,10 @@ export default class DriverCommand extends ExtensionCommand {
return await super.update({ext: driver, unsafe});
}
async run ({driver, scriptName}) {
return await super.run({ext: driver, scriptName});
}
getPostInstallText ({extName, extData}) {
return `Driver ${extName}@${extData.version} successfully installed\n`.green +
`- automationName: ${extData.automationName.green}\n` +
+69 -5
View File
@@ -3,8 +3,9 @@
import _ from 'lodash';
import NPM from './npm';
import path from 'path';
import { fs, util } from 'appium-support';
import { log, spinWith } from './utils';
import { fs, util } from '@appium/support';
import { log, spinWith, RingBuffer } from './utils';
import { SubProcess} from 'teen_process';
import { INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
INSTALL_TYPE_LOCAL } from '../extension-config';
@@ -482,8 +483,71 @@ export default class ExtensionCommand {
*/
async updateExtension (ext, version) {
const {pkgName} = this.config.installedExtensions[ext];
await this.installViaNpm({ext, pkgName, pkgVer: version});
this.config.installedExtensions[ext].version = version;
await this.config.write();
await fs.rimraf(this.config.getInstallPath(ext));
const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
delete extData[`${this.type}Name`];
await this.config.updateExtension(ext, extData);
}
/**
* @typedef RunOutput
* @property {?string|undefined} error - error message if script ran unsuccessfuly, otherwise undefined
* @property {string[]} output - script output
*/
/**
* Runs a script cached inside the "scripts" field under "appium"
* inside of the driver/plugins "package.json" file. Will throw
* an error if the driver/plugin does not contain a "scripts" field
* underneath the "appium" field in its package.json, if the
* "scripts" field is not a plain object, or if the scriptName is
* not found within "scripts" object.
*
* @param {string} ext - name of the extension to run a script from
* @param {string} scriptName - name of the script to run
* @return {RunOutput}
*/
async run ({ext, scriptName}) {
if (!_.has(this.config.installedExtensions, ext)) {
throw new Error(`please install the ${this.type} first`);
}
const extConfig = this.config.installedExtensions[ext];
if (!_.has(extConfig, 'scripts')) {
throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
`"scripts" field underneath the "appium" field in its package.json`);
}
const extScripts = extConfig.scripts;
if (!_.isPlainObject(extScripts)) {
throw new Error(`The ${this.type} named '${ext}' "scripts" field must be a plain object`);
}
if (!_.has(extScripts, scriptName)) {
throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
}
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
cwd: this.config.getExtensionRequirePath(ext)
});
const output = new RingBuffer(50);
runner.on('stream-line', (line) => {
output.enqueue(line);
log(this.isJsonOutput, line);
});
await runner.start(0);
try {
await runner.join();
log(this.isJsonOutput, `${scriptName} successfuly ran`.green);
return {output: output.getBuff()};
} catch (err) {
log(this.isJsonOutput, `Encountered an error when running '${scriptName}': ${err.message}`.red);
return {error: err.message, output: output.getBuff()};
}
}
}
+8 -2
View File
@@ -1,7 +1,7 @@
import path from 'path';
import semver from 'semver';
import { exec } from 'teen_process';
import { fs, mkdirp, util, system } from 'appium-support';
import { fs, mkdirp, util, system } from '@appium/support';
const INSTALL_LOCKFILE = '.appium.install.lock';
const LINK_LOCKFILE = '.appium.link.lock';
@@ -159,8 +159,14 @@ export default class NPM {
`provided: ${pkgPath}`);
}
// this is added to handle commands with relative paths
// ie: "node . driver install --source=local ../fake-driver"
pkgPath = path.resolve(process.cwd(), pkgPath);
const pkgHome = path.resolve(this.appiumHome, pkgName);
const res = await this.exec('link', [pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
// call link with --no-package-lock to ensure no corruption while installing local packages
const res = await this.exec('link', ['--no-package-lock', pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
if (res.json && res.json.error) {
throw new Error(res.json.error);
}
+4 -4
View File
@@ -58,7 +58,7 @@ function parsePluginNames (names) {
}
}
function parseDefaultCaps (capsOrPath) {
function parseJsonStringOrFile (capsOrPath) {
let caps = capsOrPath;
let loadedFromFile = false;
try {
@@ -82,8 +82,8 @@ function parseDefaultCaps (capsOrPath) {
return result;
} catch (e) {
const msg = loadedFromFile
? `Default capabilities in '${capsOrPath}' must be a valid JSON`
: `Default capabilities must be a valid JSON`;
? `The provided value of '${capsOrPath}' must be a valid JSON`
: `The provided value must be a valid JSON`;
throw new TypeError(`${msg}. Original error: ${e.message}`);
}
}
@@ -99,7 +99,7 @@ function parseInstallTypes (source) {
export {
parseSecurityFeatures,
parseDefaultCaps,
parseJsonStringOrFile,
parseInstallTypes,
parsePluginNames,
parseDriverNames,
+5 -1
View File
@@ -4,6 +4,7 @@ import { ArgumentParser } from 'argparse';
import { sharedArgs, serverArgs, extensionArgs } from './args';
import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
import { rootDir } from '../utils';
import { fs } from '@appium/support';
function makeDebugParser (parser) {
@@ -23,7 +24,7 @@ function getParser (debug = false) {
}
parser.add_argument('-v', '--version', {
action: 'version',
version: require(path.resolve(rootDir, 'package.json')).version
version: fs.readPackageJsonFrom(rootDir).version
});
const subParsers = parser.add_subparsers({dest: 'subcommand'});
@@ -98,6 +99,9 @@ function addExtensionsToParser (sharedArgs, subParsers, debug = false) {
help: `Uninstall a ${type}`},
{command: 'update', args: extensionArgs[type].update,
help: `Update installed ${type}s to the latest version`},
{command: 'run', args: extensionArgs[type].run,
help: `Run a script (defined inside the ${type}'s package.json under the ` +
`“scripts” field inside the “appium” field) from an installed ${type}`}
];
for (const {command, args, help} of parserSpecs) {
@@ -24,6 +24,10 @@ export default class PluginCommand extends ExtensionCommand {
return await super.update({ext: plugin, unsafe});
}
async run ({plugin, scriptName}) {
return await super.run({ext: plugin, scriptName});
}
getPostInstallText ({extName, extData}) {
return `Plugin ${extName}@${extData.version} successfully installed`.green;
}
+21 -1
View File
@@ -52,9 +52,29 @@ async function spinWith (json, msg, fn) {
}
}
class RingBuffer {
constructor (size = 50) {
this.size = size;
this.buffer = [];
}
getBuff () {
return this.buffer;
}
dequeue () {
this.buffer.shift();
}
enqueue (item) {
if (this.buffer.length >= this.size) {
this.dequeue();
}
this.buffer.push(item);
}
}
export {
errAndQuit,
log,
spinWith,
JSON_SPACES
JSON_SPACES,
RingBuffer
};
+3 -3
View File
@@ -1,6 +1,5 @@
import _ from 'lodash';
import path from 'path';
import { mkdirp, system } from 'appium-support';
import { mkdirp, system, fs } from '@appium/support';
import axios from 'axios';
import { exec } from 'teen_process';
import { rootDir } from './utils';
@@ -11,7 +10,8 @@ import {
} from './cli/argparse-actions';
import findUp from 'find-up';
const npmPackage = require(path.resolve(rootDir, 'package.json'));
const npmPackage = fs.readPackageJsonFrom(__dirname);
const APPIUM_VER = npmPackage.version;
const MIN_NODE_VERSION = npmPackage.engines.node;
+1 -1
View File
@@ -67,7 +67,7 @@ function findMatchingDriver (config, {automationName, platformName}) {
if (!driver) {
throw new Error(`MainClass ${mainClass} did not result in a driver object`);
}
return {driver, version};
return {driver, version, driverName};
} catch (err) {
const msg = `Could not find a driver for automationName ` +
`'${automationName}' and platformName ${platformName}'. ` +
+9 -1
View File
@@ -1,6 +1,6 @@
import _ from 'lodash';
import log from './logger';
import { fs, mkdirp } from 'appium-support';
import { fs, mkdirp } from '@appium/support';
import path from 'path';
import os from 'os';
import YAML from 'yaml';
@@ -160,6 +160,14 @@ export default class ExtensionConfig {
await this.write();
}
async updateExtension (extName, extData) {
this.installedExtensions[extName] = {
...this.installedExtensions[extName],
...extData,
};
await this.write();
}
async removeExtension (extName) {
delete this.installedExtensions[extName];
await this.write();
+1 -1
View File
@@ -1,5 +1,5 @@
import axios from 'axios';
import { fs } from 'appium-support';
import { fs } from '@appium/support';
import logger from './logger';
import _ from 'lodash';
+1 -1
View File
@@ -1,4 +1,4 @@
import { logger } from 'appium-support';
import { logger } from '@appium/support';
let log = logger.getLogger('Appium');
+1 -1
View File
@@ -1,6 +1,6 @@
import npmlog from 'npmlog';
import { createLogger, format, transports } from 'winston';
import { fs, logger } from 'appium-support';
import { fs, logger } from '@appium/support';
import _ from 'lodash';
+4 -5
View File
@@ -8,7 +8,7 @@ import { server as baseServer, routeConfiguringFunction as makeRouter } from '@a
import { asyncify } from 'asyncbox';
import { default as getParser, getDefaultServerArgs } from './cli/parser';
import { USE_ALL_PLUGINS } from './cli/args';
import { logger as logFactory, util } from 'appium-support';
import { logger as logFactory, util } from '@appium/support';
import {
showConfig, checkNodeOk, validateServerArgs,
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
@@ -200,14 +200,13 @@ async function main (args = null) {
appiumDriver.driverConfig = driverConfig;
const pluginConfig = new PluginConfig(args.appiumHome);
await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
const pluginClasses = getActivePlugins(args, pluginConfig);
// set the active plugins on the umbrella driver so it can use them for commands
appiumDriver.pluginClasses = pluginClasses;
await logStartupInfo(parser, args);
let routeConfiguringFunction = makeRouter(appiumDriver);
const driverClasses = getActiveDrivers(args, driverConfig);
const pluginClasses = getActivePlugins(args, pluginConfig);
// set the active plugins on the umbrella driver so it can use them for commands
appiumDriver.pluginClasses = pluginClasses;
const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
+16 -3
View File
@@ -1,7 +1,8 @@
import _ from 'lodash';
import logger from './logger';
import { processCapabilities, PROTOCOLS } from '@appium/base-driver';
import findRoot from 'find-root';
import { parseJsonStringOrFile } from './cli/parser-helpers';
import { fs } from '@appium/support';
const W3C_APPIUM_PREFIX = 'appium';
@@ -33,6 +34,18 @@ function inspectObject (args) {
}
}
function parseExtensionArgs (extensionArgs, extensionName) {
if (!_.isString(extensionArgs)) {
return {};
}
const parsedExtensionArgs = parseJsonStringOrFile(extensionArgs);
const extensionSpecificArgs = parsedExtensionArgs[extensionName];
if (!_.isPlainObject(extensionSpecificArgs)) {
throw new Error(`Driver or plugin arguments must be plain objects`);
}
return extensionSpecificArgs;
}
/**
* Takes the caps that were provided in the request and translates them
* into caps that can be used by the inner drivers.
@@ -216,9 +229,9 @@ function pullSettings (caps) {
return result;
}
const rootDir = findRoot(__dirname);
const rootDir = fs.findRoot(__dirname);
export {
inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,
getPackageVersion, pullSettings, removeAppiumPrefixes,
getPackageVersion, pullSettings, removeAppiumPrefixes, parseExtensionArgs,
};
+26 -21
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.0.0-beta.12",
"version": "2.0.0-beta.13",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -36,49 +36,54 @@
"postinstall.js"
],
"scripts": {
"backup-shrinkwrap": "mv npm-shrinkwrap.json npm-shrinkwrap-backup.json",
"check-pruned-shrinkwrap": "node check-pruned-shrinkwrap.js",
"generate-docs": "gulp transpile && node ./build/commands-yml/parse.js",
"postinstall": "node ./postinstall.js",
"install-fake-driver": "node . driver install --source=npm appium-fake-driver",
"prepublishOnly": "npm run prune-shrinkwrap && gulp fixShrinkwrap",
"prune-shrinkwrap": "!(test -e npm-shrinkwrap.json) || (npm ci --production --ignore-scripts && npm run backup-shrinkwrap && npm shrinkwrap)",
"postpublish": "npm run restore-shrinkwrap",
"restore-shrinkwrap": "!(test -e npm-shrinkwrap.json) || (mv npm-shrinkwrap-backup.json npm-shrinkwrap.json)",
"install-fake-driver": "node . driver install --source=local ../fake-driver",
"prepublishOnly": "npm run shrinkwrap:prune && npm run shrinkwrap:fix",
"postpublish": "npm run shrinkwrap:restore",
"shrinkwrap:backup": "mv npm-shrinkwrap.json npm-shrinkwrap-backup.json",
"shrinkwrap:check-pruned": "node check-pruned-shrinkwrap.js",
"shrinkwrap:fix": "gulp fixShrinkwrap",
"shrinkwrap:prune": "!(test -e npm-shrinkwrap.json) || (npm ci --production --ignore-scripts && npm run backup-shrinkwrap && npm shrinkwrap)",
"shrinkwrap:restore": "(test -e npm-shrinkwrap.json) || (mv npm-shrinkwrap-backup.json npm-shrinkwrap.json)",
"upload": "gulp github-upload",
"zip": "zip -qr appium.zip .",
"zip-and-upload": "npm run zip && npm run upload"
},
"dependencies": {
"@appium/base-plugin": "^1.0.0",
"@babel/runtime": "^7.6.0",
"@appium/base-driver": "^8.0.0-beta.6",
"@appium/support": "2.x",
"@appium/base-driver": "^8.0.0-beta.7",
"@appium/base-plugin": "^1.7.0",
"@appium/support": "^2.54.0",
"@babel/runtime": "^7.15.3",
"argparse": "^2.0.1",
"async-lock": "^1.0.0",
"async-lock": "^1.3.0",
"asyncbox": "2.x",
"axios": "^0.21.0",
"bluebird": "3.x",
"continuation-local-storage": "3.x",
"find-root": "^1.1.0",
"find-up": "^5.0.0",
"lodash": "^4.17.11",
"lodash": "^4.17.21",
"longjohn": "^0.2.12",
"npmlog": "4.x",
"ora": "^4.0.4",
"ora": "^5.4.1",
"semver": "^7.0.0",
"source-map-support": "0.x",
"teen_process": "1.x",
"winston": "3.x",
"word-wrap": "^1.2.3",
"yaml": "^1.7.2"
"yaml": "^1.10.2"
},
"devDependencies": {
"@appium/fake-driver": "^3.0.0",
"@appium/gulp-plugins": "^5.5.1"
},
"engines": {
"node": ">=12",
"npm": ">=6"
},
"devDependencies": {
"@appium/fake-driver": "^2.2.0",
"@appium/gulp-plugins": "^5.5.0"
}
"publishConfig": {
"access": "public",
"tag": "next"
},
"homepage": "https://appium.io"
}
+162 -109
View File
@@ -1,19 +1,16 @@
import path from 'path';
import { exec } from 'teen_process';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { tempDir, fs, mkdirp, util } from 'appium-support';
import { tempDir, fs, mkdirp, util } from '@appium/support';
import { KNOWN_DRIVERS } from '../lib/drivers';
import { PROJECT_ROOT as cwd } from './helpers';
chai.should();
chai.use(chaiAsPromised);
// cannot use `require.resolve()` here (w/o acrobatics) due to the ESM context.
// could also derive it from the `package.json` if we wanted
const executable = path.join(cwd, 'packages', 'appium', 'build', 'lib', 'main.js');
describe('Driver CLI', function () {
describe('CLI', function () {
let appiumHome;
before(async function () {
@@ -29,121 +26,177 @@ describe('Driver CLI', function () {
await mkdirp(appiumHome);
}
async function run (driverCmd, args = [], raw = false) {
async function run (driverCmd, args = [], raw = false, ext = 'driver') {
args = [...args, '--appium-home', appiumHome];
const ret = await exec('node', [executable, 'driver', driverCmd, ...args], {cwd});
const ret = await exec('node', [executable, ext, driverCmd, ...args], {cwd});
if (raw) {
return ret;
}
return ret.stdout;
}
describe('Driver CLI', function () {
const localFakeDriverPath = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
describe('list', function () {
it('should list available drivers', async function () {
const stdout = await run('list');
for (const d of Object.keys(KNOWN_DRIVERS)) {
stdout.should.match(new RegExp(`${d}.+[not installed]`));
}
});
it('should list available drivers in json format', async function () {
const driverData = JSON.parse(await run('list', ['--json']));
for (const d of Object.keys(KNOWN_DRIVERS)) {
driverData[d].should.eql({installed: false, pkgName: KNOWN_DRIVERS[d]});
}
});
it('should allow filtering by installed drivers', async function () {
const out = await run('list', ['--installed', '--json']);
JSON.parse(out).should.eql({});
});
it('should show updates for installed drivers with --updates', async function () {
await clear();
await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']);
const {fake} = JSON.parse(await run('list', ['--updates', '--json']));
util.compareVersions(fake.updateVersion, '>', '0.9.0').should.be.true;
const stdout = await run('list', ['--updates']);
stdout.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
});
});
describe('list', function () {
it('should list available drivers', async function () {
const stdout = await run('list');
for (const d of Object.keys(KNOWN_DRIVERS)) {
stdout.should.match(new RegExp(`${d}.+[not installed]`));
}
describe('install', function () {
it('should install a driver from the list of known drivers', async function () {
await clear();
const ret = JSON.parse(await run('install', ['uiautomator2', '--json']));
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
ret.uiautomator2.installType.should.eql('npm');
ret.uiautomator2.installSpec.should.eql('uiautomator2');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.uiautomator2.installed;
list.should.eql(ret);
});
it('should install a driver from npm', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from npm with a specific version/tag', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('appium-fake-driver@0.9.0');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from github', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium/appium-fake-driver', '--source',
'github', '--package', 'appium-fake-driver', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('github');
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from git', async function () {
await clear();
const ret = JSON.parse(await run('install', ['git+https://github.com/appium/appium-fake-driver.git',
'--source', 'git', '--package', 'appium-fake-driver', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a local npm module', async function () {
await clear();
// take advantage of the fact that we know we have fake driver installed as a dependency in
// this module, so we know its local path on disk
const ret = JSON.parse(await run('install', [localFakeDriverPath, '--source', 'local', '--json']));
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('local');
ret.fake.installSpec.should.eql(localFakeDriverPath);
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
});
it('should list available drivers in json format', async function () {
const driverData = JSON.parse(await run('list', ['--json']));
for (const d of Object.keys(KNOWN_DRIVERS)) {
driverData[d].should.eql({installed: false, pkgName: KNOWN_DRIVERS[d]});
}
describe('uninstall', function () {
it('should uninstall a driver based on its driver name', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
const installPath = path.resolve(appiumHome, ret.fake.installPath);
await fs.exists(installPath).should.eventually.be.true;
let list = JSON.parse(await run('list', ['--installed', '--json']));
list.fake.installed.should.be.true;
const uninstall = JSON.parse(await run('uninstall', ['fake', '--json']));
uninstall.should.not.have.key('fake');
await fs.exists(installPath).should.eventually.be.false;
});
});
it('should allow filtering by installed drivers', async function () {
const out = await run('list', ['--installed', '--json']);
JSON.parse(out).should.eql({});
});
it('should show updates for installed drivers with --updates', async function () {
await clear();
await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']);
const {fake} = JSON.parse(await run('list', ['--updates', '--json']));
util.compareVersions(fake.updateVersion, '>', '0.9.0').should.be.true;
const stdout = await run('list', ['--updates']);
stdout.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
describe('run', function () {
before(async function () {
await clear();
await run('install', [localFakeDriverPath, '--source', 'local', '--json']);
});
it('should run a valid driver, valid script, and result in success', async function () {
const driverName = 'fake';
const scriptName = 'fake-success';
const out = JSON.parse(await run('run', [driverName, scriptName, '--json']));
out.should.not.include.key('error');
});
it('should run a valid driver, valid error prone script, and return error in json', async function () {
const driverName = 'fake';
const out = JSON.parse(await run('run', [driverName, 'fake-error', '--json']));
out.should.include.key('error');
});
it('should take a valid driver, invalid script, and throw an error', async function () {
const driverName = 'fake';
await chai.expect(run('run', [driverName, 'foo', '--json'])).to.eventually.be.rejectedWith(Error);
});
it('should take an invalid driver, invalid script, and throw an error', async function () {
const driverName = 'foo';
await chai.expect(run('run', [driverName, 'bar', '--json'])).to.eventually.be.rejectedWith(Error);
});
});
});
describe('install', function () {
it('should install a driver from the list of known drivers', async function () {
await clear();
const ret = JSON.parse(await run('install', ['uiautomator2', '--json']));
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
ret.uiautomator2.installType.should.eql('npm');
ret.uiautomator2.installSpec.should.eql('uiautomator2');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.uiautomator2.installed;
list.should.eql(ret);
});
it('should install a driver from npm', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from npm with a specific version/tag', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('npm');
ret.fake.installSpec.should.eql('appium-fake-driver@0.9.0');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from github', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium/appium-fake-driver', '--source',
'github', '--package', 'appium-fake-driver', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('github');
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from git', async function () {
await clear();
const ret = JSON.parse(await run('install', ['git+https://github.com/appium/appium-fake-driver.git',
'--source', 'git', '--package', 'appium-fake-driver', '--json']));
ret.fake.pkgName.should.eql('appium-fake-driver');
ret.fake.installType.should.eql('git');
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
it('should install a driver from a local npm module', async function () {
await clear();
// take advantage of the fact that we know we have fake driver installed as a dependency in
// this module, so we know its local path on disk
const localFakeDriverPath = path.resolve(cwd, 'packages', 'fake-driver');
const ret = JSON.parse(await run('install', [localFakeDriverPath, '--source', 'local', '--json']));
ret.fake.pkgName.should.eql('@appium/fake-driver');
ret.fake.installType.should.eql('local');
ret.fake.installSpec.should.eql(localFakeDriverPath);
const list = JSON.parse(await run('list', ['--installed', '--json']));
delete list.fake.installed;
list.should.eql(ret);
});
});
describe('uninstall', function () {
it('should uninstall a driver based on its driver name', async function () {
await clear();
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
const installPath = path.resolve(appiumHome, ret.fake.installPath);
await fs.exists(installPath).should.eventually.be.true;
let list = JSON.parse(await run('list', ['--installed', '--json']));
list.fake.installed.should.be.true;
const uninstall = JSON.parse(await run('uninstall', ['fake', '--json']));
uninstall.should.not.have.key('fake');
await fs.exists(installPath).should.eventually.be.false;
describe('Plugin CLI', function () {
const fakePluginDir = path.resolve(__dirname, '..', '..', '..', '..', 'node_modules', '@appium', 'fake-plugin');
const ext = 'plugin';
describe('run', function () {
before(async function () {
await clear();
await run('install', [fakePluginDir, '--source', 'local', '--json'], false, ext);
});
it('should run a valid plugin, valid script, and result in success', async function () {
const pluginName = 'fake';
const scriptName = 'fake-success';
const out = JSON.parse(await run('run', [pluginName, scriptName, '--json'], false, ext));
out.should.not.include.key('error');
});
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
const pluginName = 'fake';
const out = JSON.parse(await run('run', [pluginName, 'fake-error', '--json'], false, ext));
out.should.include.key('error');
});
it('should take a valid plugin, invalid script, and throw an error', async function () {
const pluginName = 'fake';
await chai.expect(run('run', [pluginName, 'foo', '--json'], false, ext)).to.eventually.be.rejectedWith(Error);
});
it('should take an invalid plugin, invalid script, and throw an error', async function () {
await chai.expect(run('run', ['foo', 'bar', '--json'], false, ext)).to.eventually.be.rejectedWith(Error);
});
});
});
});
-4
View File
@@ -1,14 +1,10 @@
// transpile:mocha
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { DEFAULT_APPIUM_HOME } from '../lib/extension-config';
import DriverConfig from '../lib/driver-config';
import DriverCommand from '../lib/cli/driver-command';
import sinon from 'sinon';
chai.should();
chai.use(chaiAsPromised);
describe('DriverCommand', function () {
const config = new DriverConfig(DEFAULT_APPIUM_HOME);
+117
View File
@@ -0,0 +1,117 @@
import sinon from 'sinon';
import {
getGitRev,
getBuildInfo,
updateBuildInfo,
APPIUM_VER,
} from '../lib/config';
import axios from 'axios';
import { fs } from '@appium/support';
describe('Config', function () {
describe('getGitRev', function () {
it('should get a reasonable git revision', async function () {
let rev = await getGitRev();
rev.should.be.a('string');
rev.length.should.be.equal(40);
rev.match(/[0-9a-f]+/i)[0].should.eql(rev);
});
});
describe('getBuildInfo', function () {
async function verifyBuildInfoUpdate (useLocalGit) {
const buildInfo = getBuildInfo();
mockFs.expects('exists').atLeast(1).returns(useLocalGit);
buildInfo['git-sha'] = undefined;
buildInfo.built = undefined;
await updateBuildInfo(true);
buildInfo.should.be.an('object');
should.exist(buildInfo['git-sha']);
should.exist(buildInfo.built);
should.exist(buildInfo.version);
}
let mockFs;
let getStub;
beforeEach(function () {
mockFs = sinon.mock(fs);
getStub = sinon.stub(axios, 'get');
});
afterEach(function () {
getStub.restore();
mockFs.restore();
});
it('should get a configuration object if the local git metadata is present', async function () {
await verifyBuildInfoUpdate(true);
});
it('should get a configuration object if the local git metadata is not present', async function () {
getStub.onCall(0).returns({
data: [
{
name: `v${APPIUM_VER}`,
zipball_url:
'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1',
tarball_url:
'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1',
commit: {
sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
url: 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
},
node_id: 'MDM6UmVmNzUzMDU3MDp2MS45LjAtYmV0YS4x',
},
{
name: 'v1.8.2-beta',
zipball_url:
'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta',
tarball_url:
'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta',
commit: {
sha: '5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
url: 'https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
},
node_id: 'MDM6UmVmNzUzMDU3MDp2MS44LjItYmV0YQ==',
},
],
});
getStub.onCall(1).returns({
data: {
sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
node_id:
'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj',
commit: {
author: {
name: 'Isaac Murchie',
email: 'isaac@saucelabs.com',
date: '2018-08-17T19:48:00Z',
},
committer: {
name: 'Isaac Murchie',
email: 'isaac@saucelabs.com',
date: '2018-08-17T19:48:00Z',
},
message: 'v1.9.0-beta.1',
tree: {
sha: '2c0974727470eba419ea0b9951c52f72f8036b18',
url: 'https://api.github.com/repos/appium/appium/git/trees/2c0974727470eba419ea0b9951c52f72f8036b18',
},
url: 'https://api.github.com/repos/appium/appium/git/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
comment_count: 0,
verification: {
verified: false,
reason: 'unsigned',
signature: null,
payload: null,
},
},
url: 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
html_url:
'https://github.com/appium/appium/commit/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
comments_url:
'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c/comments',
},
});
await verifyBuildInfoUpdate(false);
});
});
});
+2 -105
View File
@@ -1,118 +1,15 @@
// transpile:mocha
import _ from 'lodash';
import chai from 'chai';
import sinon from 'sinon';
import chaiAsPromised from 'chai-as-promised';
import { getGitRev, getBuildInfo, checkNodeOk, warnNodeDeprecations,
import { getBuildInfo, checkNodeOk, warnNodeDeprecations,
getNonDefaultArgs, validateServerArgs,
validateTmpDir, showConfig, checkValidPort, updateBuildInfo,
APPIUM_VER } from '../lib/config';
validateTmpDir, showConfig, checkValidPort } from '../lib/config';
import getParser from '../lib/cli/parser';
import logger from '../lib/logger';
import { fs } from 'appium-support';
import axios from 'axios';
let should = chai.should();
chai.use(chaiAsPromised);
describe('Config', function () {
describe('getGitRev', function () {
it('should get a reasonable git revision', async function () {
let rev = await getGitRev();
rev.should.be.a('string');
rev.length.should.be.equal(40);
rev.match(/[0-9a-f]+/i)[0].should.eql(rev);
});
});
describe('Appium config', function () {
describe('getBuildInfo', function () {
async function verifyBuildInfoUpdate (useLocalGit) {
const buildInfo = getBuildInfo();
mockFs.expects('exists').atLeast(1).returns(useLocalGit);
buildInfo['git-sha'] = undefined;
buildInfo.built = undefined;
await updateBuildInfo(true);
buildInfo.should.be.an('object');
should.exist(buildInfo['git-sha']);
should.exist(buildInfo.built);
should.exist(buildInfo.version);
}
let mockFs;
let getStub;
beforeEach(function () {
mockFs = sinon.mock(fs);
getStub = sinon.stub(axios, 'get');
});
afterEach(function () {
getStub.restore();
mockFs.restore();
});
it('should get a configuration object if the local git metadata is present', async function () {
await verifyBuildInfoUpdate(true);
});
it('should get a configuration object if the local git metadata is not present', async function () {
getStub.onCall(0).returns({data: [
{
'name': `v${APPIUM_VER}`,
'zipball_url': 'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1',
'tarball_url': 'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1',
'commit': {
'sha': '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
'url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c'
},
'node_id': 'MDM6UmVmNzUzMDU3MDp2MS45LjAtYmV0YS4x'
},
{
'name': 'v1.8.2-beta',
'zipball_url': 'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta',
'tarball_url': 'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta',
'commit': {
'sha': '5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
'url': 'https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f'
},
'node_id': 'MDM6UmVmNzUzMDU3MDp2MS44LjItYmV0YQ=='
}
]});
getStub.onCall(1).returns({data: {
'sha': '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
'node_id': 'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj',
'commit': {
'author': {
'name': 'Isaac Murchie',
'email': 'isaac@saucelabs.com',
'date': '2018-08-17T19:48:00Z'
},
'committer': {
'name': 'Isaac Murchie',
'email': 'isaac@saucelabs.com',
'date': '2018-08-17T19:48:00Z'
},
'message': 'v1.9.0-beta.1',
'tree': {
'sha': '2c0974727470eba419ea0b9951c52f72f8036b18',
'url': 'https://api.github.com/repos/appium/appium/git/trees/2c0974727470eba419ea0b9951c52f72f8036b18'
},
'url': 'https://api.github.com/repos/appium/appium/git/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
'comment_count': 0,
'verification': {
'verified': false,
'reason': 'unsigned',
'signature': null,
'payload': null
}
},
'url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
'html_url': 'https://github.com/appium/appium/commit/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
'comments_url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c/comments',
}});
await verifyBuildInfoUpdate(false);
});
});
describe('showConfig', function () {
before(function () {
sinon.spy(console, 'log');
+48 -13
View File
@@ -3,8 +3,6 @@
import _ from 'lodash';
import path from 'path';
import B from 'bluebird';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import axios from 'axios';
import { remote as wdio } from 'webdriverio';
import { main as appiumServer } from '../lib/main';
@@ -16,13 +14,11 @@ import { runExtensionCommand } from '../lib/cli/extension';
import { removeAppiumPrefixes } from '../lib/utils';
import sinon from 'sinon';
chai.use(chaiAsPromised);
let TEST_SERVER;
let TEST_PORT;
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
const should = chai.should();
const FAKE_ARGS = {'sillyWebServerPort': 1234, 'host': 'hey'};
const FAKE_DRIVER_ARGS = JSON.stringify({'fake': FAKE_ARGS});
const shouldStartServer = process.env.USE_RUNNING_SERVER !== '0';
const caps = W3C_PREFIXED_CAPS;
const wdOpts = {
@@ -38,6 +34,7 @@ describe('FakeDriver - via HTTP', function () {
// actually going to be required by Appium
let FakeDriver = null;
let baseUrl;
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
before(async function () {
wdOpts.port = TEST_PORT = await getTestPort();
TEST_SERVER = `http://${TEST_HOST}:${TEST_PORT}`;
@@ -60,19 +57,26 @@ describe('FakeDriver - via HTTP', function () {
const config = new DriverConfig(appiumHome);
await config.read();
FakeDriver = config.require('fake');
// then start server if we need to
if (shouldStartServer) {
let args = {port: TEST_PORT, host: TEST_HOST, appiumHome};
server = await appiumServer(args);
}
await serverStart();
});
after(async function () {
await serverClose();
});
async function serverStart (args = {}) {
args = {port: TEST_PORT, host: TEST_HOST, appiumHome, ...args};
if (shouldStartServer) {
server = await appiumServer(args);
}
}
async function serverClose () {
if (server) {
await server.close();
}
});
}
describe('server updating', function () {
it('should allow drivers to update the server in arbitrary ways', async function () {
@@ -81,6 +85,35 @@ describe('FakeDriver - via HTTP', function () {
});
});
describe('cli args handling for empty args', function () {
it('should not recieve user cli args if none passed in', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
const {sessionId} = driver;
try {
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakedriverargs`);
data.value.should.eql({});
} finally {
await driver.deleteSession();
}
});
});
describe('cli args handling for passed in args', function () {
it('should recieve user cli args from a driver if arguments were passed in', async function () {
await serverClose();
const args = {driverArgs: FAKE_DRIVER_ARGS};
await serverStart(args);
let driver = await wdio({...wdOpts, capabilities: caps});
const {sessionId} = driver;
try {
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakedriverargs`);
data.value.should.eql(FAKE_ARGS);
} finally {
await driver.deleteSession();
}
});
});
describe('session handling', function () {
it('should start and stop a session and not allow commands after session stopped', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
@@ -292,7 +325,9 @@ describe('FakeDriver - via HTTP', function () {
});
});
describe('Logsink', function () {
// TODO this test only works if the log has not previously been initialized in the same process.
// there seems to be some global state that is not cleaned up between tests.
describe.skip('Logsink', function () {
let server = null;
let logs = [];
let logHandler = function (level, message) {
-4
View File
@@ -5,13 +5,9 @@ import { FakeDriver } from '@appium/fake-driver';
import { BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS } from './helpers';
import _ from 'lodash';
import sinon from 'sinon';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { sleep } from 'asyncbox';
import { insertAppiumPrefixes, removeAppiumPrefixes } from '../lib/utils';
chai.should();
chai.use(chaiAsPromised);
const SESSION_ID = 1;
+6
View File
@@ -0,0 +1,6 @@
{
"fake":{
"sillyWebServerPort":1234,
"host":"hey"
}
}
+3 -6
View File
@@ -1,15 +1,12 @@
import path from 'path';
import {insertAppiumPrefixes} from '../lib/utils';
import findUp from 'find-up';
import getPort from 'get-port';
const TEST_HOST = 'localhost';
// monorepo root. this cannot be hardcoded because:
// 1. we may be in 'build/test' or 'test', depending on our config
// 2. Node.js does not support `__dirname` in an ESM context & Babel doesn't fake it well
const PROJECT_ROOT = path.dirname(findUp.sync('.git', {type: 'directory'}));
const TEST_FAKE_APP = path.join(PROJECT_ROOT, 'packages', 'fake-driver', 'test', 'fixtures', 'app.xml');
const fakeDriverPath = path.dirname(require.resolve('@appium/fake-driver/package.json'));
const PROJECT_ROOT = path.join(fakeDriverPath, '..', '..');
const TEST_FAKE_APP = path.join(fakeDriverPath, 'test', 'fixtures', 'app.xml');
const BASE_CAPS = {
automationName: 'Fake',
+1 -1
View File
@@ -2,7 +2,7 @@
import { init as logsinkInit, clear as logsinkClear } from '../lib/logsink';
import sinon from 'sinon';
import { logger } from 'appium-support';
import { logger } from '@appium/support';
// temporarily turn on logging to stdio, so we can catch and query
-2
View File
@@ -1,8 +1,6 @@
// transpile:mocha
import chai from 'chai';
import NPM from '../lib/cli/npm';
chai.should();
describe('getLatestSafeUpgradeFromVersions', function () {
const versions1 = ['0.1.0', '0.1.1', '0.2.0', '0.2.5', '1.0.0', '1.0.1', '1.1.5', '1.2.7',
+62 -11
View File
@@ -2,12 +2,13 @@
import { getParser } from '../lib/cli/parser';
import { INSTALL_TYPES, DEFAULT_APPIUM_HOME } from '../lib/extension-config';
import chai from 'chai';
import path from 'path';
const should = chai.should();
const ALLOW_FIXTURE = 'test/fixtures/allow-feat.txt';
const DENY_FIXTURE = 'test/fixtures/deny-feat.txt';
const FIXTURE_DIR = path.join(__dirname, 'fixtures');
const ALLOW_FIXTURE = path.join(FIXTURE_DIR, 'allow-feat.txt');
const DENY_FIXTURE = path.join(FIXTURE_DIR, 'deny-feat.txt');
const FAKE_DRIVER_ARGS_FIXTURE = path.join(FIXTURE_DIR, 'driverArgs.json');
const CAPS_FIXTURE = path.join(FIXTURE_DIR, 'caps.json');
describe('Main Parser', function () {
let p = getParser(true);
@@ -48,7 +49,7 @@ describe('Server Parser', function () {
});
it('should parse default capabilities correctly from a file', function () {
let defaultCapabilities = {a: 'b'};
let args = p.parse_args(['--default-capabilities', 'test/fixtures/caps.json']);
let args = p.parse_args(['--default-capabilities', CAPS_FIXTURE]);
args.defaultCapabilities.should.eql(defaultCapabilities);
});
it('should throw an error with invalid arg to default capabilities', function () {
@@ -57,11 +58,6 @@ describe('Server Parser', function () {
(() => {p.parse_args(['-dc', 'null']);}).should.throw();
(() => {p.parse_args(['-dc', 'does/not/exist.json']);}).should.throw();
});
it('should parse args that are caps into default capabilities', function () {
let defaultCapabilities = {chromedriverExecutable: '/my/dir'};
let args = p.parse_args(['--chromedriver-executable', '/my/dir']);
args.defaultCapabilities.should.eql(defaultCapabilities);
});
it('should parse --allow-insecure correctly', function () {
p.parse_args([]).allowInsecure.should.eql([]);
p.parse_args(['--allow-insecure', '']).allowInsecure.should.eql([]);
@@ -83,6 +79,21 @@ describe('Server Parser', function () {
parsed.allowInsecure.should.eql(['feature1', 'feature2', 'feature3']);
parsed.denyInsecure.should.eql(['nofeature1', 'nofeature2', 'nofeature3']);
});
it('should parse default driver args correctly from a string', function () {
let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
let args = p.parse_args(['--driver-args', JSON.stringify(fakeDriverArgs)]);
args.driverArgs.should.eql(fakeDriverArgs);
});
it('should parse default driver args correctly from a file', function () {
let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
let args = p.parse_args(['--driver-args', FAKE_DRIVER_ARGS_FIXTURE]);
args.driverArgs.should.eql(fakeDriverArgs);
});
it('should parse default plugin args correctly from a string', function () {
let fakePluginArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
let args = p.parse_args(['--plugin-args', JSON.stringify(fakePluginArgs)]);
args.pluginArgs.should.eql(fakePluginArgs);
});
});
describe('Driver Parser', function () {
@@ -190,4 +201,44 @@ describe('Driver Parser', function () {
args.appiumHome.should.eql('/foo/bar');
});
});
describe('run', function () {
it('should not allow an empty driver argument list', function () {
(() => p.parse_args(['driver', 'run'])).should.throw();
});
it('should not allow no driver scriptName', function () {
(() => p.parse_args(['driver', 'run', 'foo'])).should.throw();
});
it('should take a driverName and scriptName to run', function () {
const args = p.parse_args(['driver', 'run', 'foo', 'bar']);
args.subcommand.should.eql('driver');
args.driverCommand.should.eql('run');
args.driver.should.eql('foo');
args.scriptName.should.eql('bar');
args.appiumHome.should.eql(DEFAULT_APPIUM_HOME);
args.json.should.eql(false);
});
it('should allow json format for driver', function () {
const args = p.parse_args(['driver', 'run', 'foo', 'bar', '--json']);
args.json.should.eql(true);
});
it('should not allow an empty plugin argument list', function () {
(() => p.parse_args(['plugin', 'run'])).should.throw();
});
it('should not allow no plugin scriptName', function () {
(() => p.parse_args(['plugin', 'run', 'foo'])).should.throw();
});
it('should take a pluginName and scriptName to run', function () {
const args = p.parse_args(['plugin', 'run', 'foo', 'bar']);
args.subcommand.should.eql('plugin');
args.pluginCommand.should.eql('run');
args.plugin.should.eql('foo');
args.scriptName.should.eql('bar');
args.appiumHome.should.eql(DEFAULT_APPIUM_HOME);
args.json.should.eql(false);
});
it('should allow json format for plugin', function () {
const args = p.parse_args(['plugin', 'run', 'foo', 'bar', '--json']);
args.json.should.eql(true);
});
});
});
+52 -5
View File
@@ -1,9 +1,6 @@
// transpile:mocha
import _ from 'lodash';
import path from 'path';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { remote as wdio } from 'webdriverio';
import axios from 'axios';
import { main as appiumServer } from '../lib/main';
@@ -11,8 +8,9 @@ import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_LOCAL, DRIVER_TYPE, PLUGIN_TYPE } fro
import { W3C_PREFIXED_CAPS, TEST_HOST, getTestPort, PROJECT_ROOT } from './helpers';
import { runExtensionCommand } from '../lib/cli/extension';
chai.should();
chai.use(chaiAsPromised);
const FAKE_ARGS = {'sillyWebServerPort': 1234, 'host': 'hey'};
const FAKE_PLUGIN_ARGS = JSON.stringify({'fake': FAKE_ARGS});
const wdOpts = {
hostname: TEST_HOST,
@@ -25,6 +23,7 @@ describe('FakePlugin', function () {
const fakePluginDir = path.join(PROJECT_ROOT, 'node_modules', '@appium', 'fake-plugin');
const fakeDriverDir = path.join(PROJECT_ROOT, 'packages', 'fake-driver');
const appiumHome = DEFAULT_APPIUM_HOME;
let baseArgs;
let testServer;
let testPort;
let baseUrl;
@@ -61,6 +60,7 @@ describe('FakePlugin', function () {
installType: INSTALL_TYPE_LOCAL,
}, PLUGIN_TYPE);
}
baseArgs = {port: testPort, host: TEST_HOST, appiumHome, plugins: ['fake']};
});
describe('without plugin registered', function () {
@@ -151,4 +151,51 @@ describe('FakePlugin', function () {
});
});
}
describe('cli args handling for plugin args', function () {
let server = null;
before(async function () {
// then start server if we need to
const args = {...baseArgs, pluginArgs: FAKE_PLUGIN_ARGS};
server = await appiumServer(args);
});
after(async function () {
if (server) {
await server.close();
}
});
it('should recieve user cli args for plugin if passed in', async function () {
const driver = await wdio(wdOpts);
const {sessionId} = driver;
try {
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakepluginargs`);
data.value.should.eql(FAKE_ARGS);
} finally {
await driver.deleteSession();
}
});
});
describe('cli args handling for empty plugin args', function () {
let server = null;
before(async function () {
// then start server if we need to
server = await appiumServer(baseArgs);
});
after(async function () {
if (server) {
await server.close();
}
});
it('should not recieve user cli args for plugin if none were passed in', async function () {
const driver = await wdio(wdOpts);
const {sessionId} = driver;
try {
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakepluginargs`);
data.value.should.eql({});
} finally {
await driver.deleteSession();
}
});
});
});
-3
View File
@@ -1,11 +1,8 @@
// these are extra unit tests to ensure that appium is set up correctly for publishing
import _ from 'lodash';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
const expect = chai.expect;
describe.skip('shrinkwrap checks', function () {
+42 -6
View File
@@ -1,15 +1,13 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {
parseCapsForInnerDriver, insertAppiumPrefixes, pullSettings,
removeAppiumPrefixes,
removeAppiumPrefixes, parseExtensionArgs,
} from '../lib/utils';
import { BASE_CAPS, W3C_CAPS } from './helpers';
import _ from 'lodash';
const should = chai.should();
chai.use(chaiAsPromised);
const FAKE_ARGS = `{"sillyWebServerPort":1234,"host":"hey"}`;
const FAKE_DRIVER_NAME = `fake`;
const FAKE_DRIVER_ARGS = `{"${FAKE_DRIVER_NAME}": ${FAKE_ARGS}}`;
describe('utils', function () {
describe('parseCapsForInnerDriver()', function () {
@@ -214,4 +212,42 @@ describe('utils', function () {
caps.should.eql({});
});
});
describe('parseExtensionArgs()', function () {
it('should take a valid json string with a driver name and return a valid json object for that driver', function () {
parseExtensionArgs(
FAKE_DRIVER_ARGS,
FAKE_DRIVER_NAME,
).should.eql(JSON.parse(FAKE_DRIVER_ARGS)[FAKE_DRIVER_NAME]);
});
it('should take a valid json string with an invalid driver name and throw an error', function () {
const fakeDriverArgs = `{"xcuitest": ${FAKE_ARGS}}`;
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME)).should.throw();
});
it('should take an empty json object and return an empty object', function () {
const fakeDriverArgs = {};
parseExtensionArgs(
fakeDriverArgs,
FAKE_DRIVER_NAME,
).should.eql({});
});
it('should take an invalid json string and throw an error', function () {
const fakeDriverArgs = `blah`;
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME)).should.throw();
});
it('should take a valid json file with a driver name and return a valid json object for that driver', function () {
const fakeDriverArgsPath = require.resolve('./fixtures/driverArgs.json');
parseExtensionArgs(
fakeDriverArgsPath,
FAKE_DRIVER_NAME,
).should.eql(JSON.parse(FAKE_DRIVER_ARGS)[FAKE_DRIVER_NAME]);
});
it('should throw an error if arg is not a plain object', function () {
const fakeDriverArgs = '{"fake": 1234}';
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME,)).should.throw();
});
});
});
+43
View File
@@ -0,0 +1,43 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [8.0.0-beta.7](https://github.com/appium/appium/compare/@appium/base-driver@8.0.0-beta.7...@appium/base-driver@8.0.0-beta.7) (2021-08-16)
# 2.0.0-beta (2021-08-13)
### chore
* update @appium/fake-driver to use @appium/base-driver ([#15436](https://github.com/appium/appium/issues/15436)) ([c66144d](https://github.com/appium/appium/commit/c66144d62b23681f91b45c45648dddf51f0ea991)), closes [#15425](https://github.com/appium/appium/issues/15425)
### Features
* **appium:** Add driver and plugin server arg injection feature ([#15388](https://github.com/appium/appium/issues/15388)) ([d3c11e3](https://github.com/appium/appium/commit/d3c11e364dffff87ac38ac8dc3ad65a1e4534a9a))
* **appium:** expose validateCaps in basedriver index ([#15451](https://github.com/appium/appium/issues/15451)) ([21e8d60](https://github.com/appium/appium/commit/21e8d60a5c768762ebaa7a3232962b0dec385bd0))
* **base-driver:** prefer system unzip ([25cc27d](https://github.com/appium/appium/commit/25cc27d161e9425a2ce7420d4417f85d03984921))
### Reverts
* **base-driver:** "chore: merge base-driver master (de7e41b) to base-driver for 2.0" ([#15454](https://github.com/appium/appium/issues/15454)) ([254cc63](https://github.com/appium/appium/commit/254cc638c52063149878866c2abdfe83c5dbee7b))
### BREAKING CHANGES
* `fake-driver` now depends upon `@appium/base-driver@8.x`
## `@appium/fake-driver`
- need to use w3c capabilities only
- fix: find `app.xml` fixture properly when running tests via `mocha --require=@babel/register`
## `@appium/base-driver`
- fixed a dead URL in a comment
- updated the "logging" tests to manually supply w3c capabilities. `createSession()` does it for you, but `executeCommand('createSession')` does not.
- display the name of the driver under test when executing base driver's test suite with other drivers
+2 -2
View File
@@ -53,8 +53,8 @@ export { statusCodes, getSummaryByCode };
// W3C capabilities parser
import * as caps from './lib/basedriver/capabilities';
const { processCapabilities, isStandardCap } = caps;
export { processCapabilities, isStandardCap };
const { processCapabilities, isStandardCap, validateCaps } = caps;
export { processCapabilities, isStandardCap, validateCaps };
// Web socket helpers
import * as ws from './lib/express/websocket';
@@ -1,6 +1,6 @@
import _ from 'lodash';
import { validator } from './desired-caps';
import { util } from 'appium-support';
import { util } from '@appium/support';
import log from './logger';
import { errors } from '../protocol/errors';
@@ -1,12 +1,8 @@
import log from '../logger';
import { logger } from 'appium-support';
import _ from 'lodash';
import { errors } from '../../..';
const commands = {}, helpers = {}, extensions = {};
const CUSTOM_STRATEGY = '-custom';
// Override the following function for your own driver, and the rest is taken
// care of!
@@ -37,18 +33,10 @@ helpers.findElOrElsWithProcessing = async function findElOrElsWithProcessing (st
};
commands.findElement = async function findElement (strategy, selector) {
if (strategy === CUSTOM_STRATEGY) {
return await this.findByCustom(selector, false);
}
return await this.findElOrElsWithProcessing(strategy, selector, false);
};
commands.findElements = async function findElements (strategy, selector) {
if (strategy === CUSTOM_STRATEGY) {
return await this.findByCustom(selector, true);
}
return await this.findElOrElsWithProcessing(strategy, selector, true);
};
@@ -60,115 +48,7 @@ commands.findElementsFromElement = async function findElementsFromElement (strat
return await this.findElOrElsWithProcessing(strategy, selector, true, elementId);
};
/**
* Find an element using a custom plugin specified by the customFindModules cap.
*
* @param {string} selector - the selector which the plugin will use to find
* elements
* @param {boolean} multiple - whether we want one element or multiple
*
* @returns {WebElement} - WebDriver element or list of elements
*/
commands.findByCustom = async function findByCustom (selector, multiple) {
const plugins = this.opts.customFindModules;
// first ensure the user has registered one or more find plugins
if (!plugins) {
// TODO this info should go in docs instead; update when docs for this
// feature exist
throw new Error('Finding an element using a plugin is currently an ' +
'incubating feature. To use it you must manually install one or more ' +
'plugin modules in a way that they can be required by Appium, for ' +
'example installing them from the Appium directory, installing them ' +
'globally, or installing them elsewhere and passing an absolute path as ' +
'the capability. Then construct an object where the key is the shortcut ' +
'name for this plugin and the value is the module name or absolute path, ' +
'for example: {"p1": "my-find-plugin"}, and pass this in as the ' +
"'customFindModules' capability.");
}
// then do some basic checking of the type of the capability
if (!_.isPlainObject(plugins)) {
throw new Error("Invalid format for the 'customFindModules' capability. " +
'It should be an object with keys corresponding to the short names and ' +
'values corresponding to the full names of the element finding plugins');
}
// get the name of the particular plugin used for this invocation of find,
// and separate it from the selector we will pass to the plugin
let [plugin, realSelector] = selector.split(':');
// if the user didn't specify a plugin for this find invocation, and we had
// multiple plugins registered, that's a problem
if (_.size(plugins) > 1 && !realSelector) {
throw new Error(`Multiple element finding plugins were registered ` +
`(${_.keys(plugins)}), but your selector did not indicate which plugin ` +
`to use. Ensure you put the short name of the plugin followed by ':' as ` +
`the initial part of the selector string.`);
}
// but if they did not specify a plugin and we only have one plugin, just use
// that one
if (_.size(plugins) === 1 && !realSelector) {
realSelector = plugin;
plugin = _.keys(plugins)[0];
}
if (!plugins[plugin]) {
throw new Error(`Selector specified use of element finding plugin ` +
`'${plugin}' but it was not registered in the 'customFindModules' ` +
`capability.`);
}
let finder;
try {
log.debug(`Find plugin '${plugin}' requested; will attempt to use it ` +
`from '${plugins[plugin]}'`);
finder = require(plugins[plugin]);
} catch (err) {
throw new Error(`Could not load your custom find module '${plugin}'. Did ` +
`you put it somewhere Appium can 'require' it? Original error: ${err}`);
}
if (!finder || !_.isFunction(finder.find)) {
throw new Error('Your custom find module did not appear to be constructed ' +
'correctly. It needs to export an object with a `find` method.');
}
const customFinderLog = logger.getLogger(plugin);
let elements;
const condition = async () => {
// get a list of matched elements from the custom finder, which can
// potentially use the entire suite of methods the current driver provides.
// the finder should always return a list of elements, but may use the
// knowledge of whether we are looking for one or many to perform internal
// optimizations
elements = await finder.find(this, customFinderLog, realSelector, multiple);
// if we're looking for multiple elements, or if we're looking for only
// one and found it, we're done
if (!_.isEmpty(elements) || multiple) {
return true;
}
// otherwise we should retry, so return false to trigger the retry loop
return false;
};
try {
// make sure we respect implicit wait
await this.implicitWaitForCondition(condition);
} catch (err) {
if (err.message.match(/Condition unmet/)) {
throw new errors.NoSuchElementError();
}
throw err;
}
return multiple ? elements : elements[0];
};
Object.assign(extensions, commands, helpers);
export { commands, helpers, CUSTOM_STRATEGY };
export { commands, helpers };
export default extensions;
@@ -2,7 +2,7 @@
import _ from 'lodash';
import log from '../logger';
import { errors } from '../../protocol';
import { util } from 'appium-support';
import { util } from '@appium/support';
import { processCapabilities } from '../capabilities';
let commands = {};
@@ -1,7 +1,7 @@
import log from '../logger';
import { waitForCondition } from 'asyncbox';
import _ from 'lodash';
import { util } from 'appium-support';
import { util } from '@appium/support';
import { errors } from '../../protocol';
+7 -32
View File
@@ -1,9 +1,9 @@
import {
Protocol, errors, determineProtocol
} from '../protocol';
import { fs } from '@appium/support';
import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
import os from 'os';
import path from 'path';
import commands from './commands';
import * as helpers from './helpers';
import log from './logger';
@@ -12,16 +12,16 @@ import { desiredCapabilityConstraints } from './desired-caps';
import { validateCaps } from './capabilities';
import B from 'bluebird';
import _ from 'lodash';
import { util } from 'appium-support';
import AsyncLock from 'async-lock';
import { EventEmitter } from 'events';
// for compat with running tests transpiled and in-place
const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
B.config({
cancellation: true,
});
const BASEDRIVER_VER = require(path.resolve(__dirname, '..', '..', '..', 'package.json')).version;
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
const EVENT_SESSION_INIT = 'newSessionRequested';
@@ -38,7 +38,7 @@ class BaseDriver extends Protocol {
*/
static baseVersion = BASEDRIVER_VER;
constructor (opts = {}, shouldValidateCaps = true) {
constructor (opts = {}, shouldValidateCaps = true, driverArgs = {}) {
super();
// setup state
@@ -48,6 +48,9 @@ class BaseDriver extends Protocol {
this.originalCaps = null; // To give the original capabilities to reset
this.helpers = helpers;
// allow for cli args to be used by drivers
this.driverArgs = driverArgs;
// basePath is used for several purposes, for example in setting up
// proxying to other drivers, since we need to know what the base path
// of any incoming request might look like. We set it to the default
@@ -421,34 +424,6 @@ class BaseDriver extends Protocol {
this.clearNewCommandTimeout();
}
async getSwipeOptions (gestures, touchCount = 1) {
let startX = this.helpers.getCoordDefault(gestures[0].options.x),
startY = this.helpers.getCoordDefault(gestures[0].options.y),
endX = this.helpers.getCoordDefault(gestures[2].options.x),
endY = this.helpers.getCoordDefault(gestures[2].options.y),
duration = this.helpers.getSwipeTouchDuration(gestures[1]),
element = gestures[0].options.element,
destElement = gestures[2].options.element || gestures[0].options.element;
// there's no destination element handling in bootstrap and since it applies to all platforms, we handle it here
if (util.hasValue(destElement)) {
let locResult = await this.getLocationInView(destElement);
let sizeResult = await this.getSize(destElement);
let offsetX = (Math.abs(endX) < 1 && Math.abs(endX) > 0) ? sizeResult.width * endX : endX;
let offsetY = (Math.abs(endY) < 1 && Math.abs(endY) > 0) ? sizeResult.height * endY : endY;
endX = locResult.x + offsetX;
endY = locResult.y + offsetY;
// if the target element was provided, the coordinates for the destination need to be relative to it.
if (util.hasValue(element)) {
let firstElLocation = await this.getLocationInView(element);
endX -= firstElLocation.x;
endY -= firstElLocation.y;
}
}
// clients are responsible to use these options correctly
return {startX, startY, endX, endY, duration, touchCount, element};
}
proxyActive (/* sessionId */) {
return false;
}
+11 -26
View File
@@ -2,12 +2,11 @@ import _ from 'lodash';
import path from 'path';
import url from 'url';
import logger from './logger';
import { tempDir, fs, util, zip, net, timing } from 'appium-support';
import { system, tempDir, fs, util, zip, net, timing } from '@appium/support';
import LRU from 'lru-cache';
import AsyncLock from 'async-lock';
import axios from 'axios';
const IPA_EXT = '.ipa';
const ZIP_EXTS = ['.zip', IPA_EXT];
const ZIP_MIME_TYPES = [
@@ -323,7 +322,15 @@ async function unzipApp (zipPath, dstRoot, supportedAppExtensions) {
try {
logger.debug(`Unzipping '${zipPath}'`);
const timer = new timing.Timer().start();
const extractionOpts = {};
/**
* Attempt to use use the system `unzip` (e.g., `/usr/bin/unzip`) due
* to the significant performance improvement it provides over the native
* JS "unzip" implementation.
* @type {import('@appium/support/lib/zip').ExtractAllOptions}
*/
const extractionOpts = {
useSystemUnzip: !system.isWindows(),
};
// https://github.com/appium/appium/issues/14100
if (path.extname(zipPath) === IPA_EXT) {
logger.debug(`Enforcing UTF-8 encoding on the extracted file names for '${path.basename(zipPath)}'`);
@@ -359,28 +366,6 @@ function isPackageOrBundle (app) {
return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
}
function getCoordDefault (val) {
// going the long way and checking for undefined and null since
// we can't be assured `elId` is a string and not an int. Same
// thing with destElement below.
return util.hasValue(val) ? val : 0.5;
}
function getSwipeTouchDuration (waitGesture) {
// the touch action api uses ms, we want seconds
// 0.8 is the default time for the operation
let duration = 0.8;
if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {
duration = waitGesture.options.ms / 1000;
if (duration === 0) {
// set to a very low number, since they wanted it fast
// but below 0.1 becomes 0 steps, which causes errors
duration = 0.1;
}
}
return duration;
}
/**
* Finds all instances 'firstKey' and create a duplicate with the key 'secondKey',
* Do the same thing in reverse. If we find 'secondKey', create a duplicate with the key 'firstKey'.
@@ -443,5 +428,5 @@ function parseCapsArray (cap) {
}
export {
configureApp, isPackageOrBundle, getCoordDefault, getSwipeTouchDuration, duplicateKeys, parseCapsArray
configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray
};
@@ -1,4 +1,4 @@
import { logger } from 'appium-support';
import { logger } from '@appium/support';
const log = logger.getLogger('BaseDriver');
export default log;
+1 -1
View File
@@ -1,4 +1,4 @@
import { util } from 'appium-support';
import { util } from '@appium/support';
// The default maximum length of a single log record
// containing http request/response body
@@ -1,6 +1,6 @@
import log from './logger';
import LRU from 'lru-cache';
import { fs, util } from 'appium-support';
import { fs, util } from '@appium/support';
import os from 'os';
import path from 'path';
import { EventEmitter } from 'events';
+1 -1
View File
@@ -1,4 +1,4 @@
import { logger } from 'appium-support';
import { logger } from '@appium/support';
const log = logger.getLogger('HTTP');
export default log;
+1 -1
View File
@@ -1,7 +1,7 @@
import path from 'path';
import log from './logger';
import _ from 'lodash';
import { fs } from 'appium-support';
import { fs } from '@appium/support';
import B from 'bluebird';
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { logger, util } from 'appium-support';
import { logger, util } from '@appium/support';
import { duplicateKeys } from '../basedriver/helpers';
import {
MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS
@@ -190,6 +190,10 @@ class ProtocolConverter {
: await this.proxyFunc(url, method, body);
}
async proxyReleaseActions (url, method) {
return await this.proxyFunc(url, method);
}
/**
* Handle "crossing" endpoints for the case
* when upstream and downstream drivers operate different protocols
@@ -218,6 +222,8 @@ class ProtocolConverter {
return await this.proxySetValue(url, method, body);
case 'performActions':
return await this.proxyPerformActions(url, method, body);
case 'releaseActions':
return await this.proxyReleaseActions(url, method, body);
case 'setFrame':
return await this.proxySetFrame(url, method, body);
default:
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { logger, util } from 'appium-support';
import { logger, util } from '@appium/support';
import axios from 'axios';
import { getSummaryByCode } from '../jsonwp-status/status';
import {
@@ -41,7 +41,6 @@ class JWProxy {
keepAlive: opts.keepAlive ?? true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 60000,
};
this.httpAgent = new http.Agent(agentOpts);
this.httpsAgent = new https.Agent(agentOpts);
+1 -1
View File
@@ -1,6 +1,6 @@
import ES6Error from 'es6-error';
import _ from 'lodash';
import { util, logger } from 'appium-support';
import { util, logger } from '@appium/support';
import { StatusCodes as HTTPStatusCodes } from 'http-status-codes';
const mjsonwpLog = logger.getLogger('MJSONWP');
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { util } from 'appium-support';
import { util } from '@appium/support';
import { validators } from './validators';
import {
errors, isErrorType, getResponseForW3CError, getResponseForJsonwpError,
+2 -1
View File
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { util } from 'appium-support';
import { util } from '@appium/support';
import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
@@ -280,6 +280,7 @@ const METHOD_MAP = {
},
'/session/:sessionId/actions': {
POST: {command: 'performActions', payloadParams: {required: ['actions']}},
DELETE: {command: 'releaseActions'}
},
'/session/:sessionId/touch/longclick': {
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}}
@@ -1,5 +1,5 @@
import LRU from 'lru-cache';
import { logger } from 'appium-support';
import { logger } from '@appium/support';
import { PROTOCOLS } from '../constants';
+36 -23
View File
@@ -2,10 +2,17 @@
"name": "@appium/base-driver",
"description": "Base driver class for Appium drivers",
"keywords": [
"appium"
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
],
"version": "8.0.0-beta.6",
"author": "appium",
"version": "8.0.0-beta.7",
"author": "https://github.com/appium",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,7 +22,8 @@
"url": "https://github.com/appium/appium/issues"
},
"engines": {
"node": ">=12"
"node": ">=12",
"npm": ">=6"
},
"main": "./build/index.js",
"directories": {
@@ -33,29 +41,34 @@
"!build/test/basedriver/fixtures"
],
"dependencies": {
"@babel/runtime": "^7.0.0",
"@appium/support": "^2.48.0",
"async-lock": "^1.0.0",
"asyncbox": "^2.3.1",
"axios": "^0.21.0",
"@appium/support": "^2.54.0",
"@babel/runtime": "^7.15.3",
"async-lock": "^1.3.0",
"asyncbox": "^2.8.0",
"axios": "^0.x",
"bluebird": "^3.5.3",
"body-parser": "^1.18.2",
"colors": "^1.1.2",
"body-parser": "^1.19.0",
"colors": "^1.4.0",
"es6-error": "^4.1.1",
"express": "^4.16.2",
"http-status-codes": "^2.1.1",
"lodash": "^4.0.0",
"express": "^4.17.1",
"http-status-codes": "^2.1.4",
"lodash": "^4.17.21",
"lru-cache": "^6.0.0",
"method-override": "^3.0.0",
"morgan": "^1.9.0",
"serve-favicon": "^2.4.5",
"source-map-support": "^0.5.5",
"morgan": "^1.10.0",
"serve-favicon": "^2.5.0",
"source-map-support": "^0.5",
"validate.js": "^0.13.0",
"webdriverio": "^6.0.2",
"ws": "^7.0.0"
},
"webdriverio": "^6.12.1",
"ws": "^7.5.3"
},
"devDependencies": {
"@appium/gulp-plugins": "^5.4.0",
"@appium/eslint-config-appium": "^4.7.0"
}
"@appium/eslint-config-appium": "^4.7.1",
"@appium/gulp-plugins": "^5.5.1"
},
"publishConfig": {
"access": "public",
"tag": "beta"
},
"homepage": "https://appium.io"
}
@@ -1,11 +1,7 @@
import { parseCaps, validateCaps, mergeCaps, processCapabilities, findNonPrefixedCaps } from '../../lib/basedriver/capabilities';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import _ from 'lodash';
import { desiredCapabilityConstraints } from '../../lib/basedriver/desired-caps';
chai.use(chaiAsPromised);
const should = chai.should();
describe('caps', function () {
@@ -99,7 +95,7 @@ describe('caps', function () {
});
});
// Tests based on: https://www.w3.org/TR/webdriver/#processing-caps
// Tests based on: https://www.w3.org/TR/webdriver/#processing-capabilities
describe('#parseCaps', function () {
let caps;
@@ -1,11 +1,7 @@
import { default as BaseDriver, errors } from '../..';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import logger from '../../lib/basedriver/logger';
import sinon from 'sinon';
const should = chai.should();
chai.use(chaiAsPromised);
describe('Desired Capabilities', function () {
let d;
@@ -1,10 +1,7 @@
import _ from 'lodash';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { BaseDriver } from '../../..';
chai.use(chaiAsPromised);
describe('logging custom events', function () {
it('should allow logging of events', async function () {
@@ -1,83 +0,0 @@
import chai from 'chai';
import path from 'path';
import chaiAsPromised from 'chai-as-promised';
import { BaseDriver } from '../../..';
import { CUSTOM_STRATEGY } from '../../../lib/basedriver/commands/find';
chai.use(chaiAsPromised);
const CUSTOM_FIND_MODULE = path.resolve(__dirname, '..', '..', '..', '..',
'test', 'basedriver', 'fixtures', 'custom-element-finder');
const BAD_CUSTOM_FIND_MODULE = path.resolve(__dirname, '..', '..', '..', '..',
'test', 'basedriver', 'fixtures', 'custom-element-finder-bad');
describe('custom element finding plugins', function () {
// happys
it('should find a single element using a custom finder', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.eql('bar');
});
it('should not require selector prefix if only one find plugin is registered', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'foo').should.eventually.eql('bar');
});
it('should find multiple elements using a custom finder', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElements(CUSTOM_STRATEGY, 'f:foos').should.eventually.eql(['baz1', 'baz2']);
});
it('should give a hint to the plugin about whether multiple are requested', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:foos').should.eventually.eql('bar1');
});
it('should be able to use multiple find modules', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.eql('bar');
await d.findElement(CUSTOM_STRATEGY, 'g:foo').should.eventually.eql('bar');
});
// errors
it('should throw an error if customFindModules is not set', async function () {
const d = new BaseDriver();
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/customFindModules/);
});
it('should throw an error if customFindModules is the wrong shape', async function () {
const d = new BaseDriver();
d.opts.customFindModules = CUSTOM_FIND_MODULE;
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/customFindModules/);
});
it('should throw an error if customFindModules is size > 1 and no selector prefix is used', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'foo').should.eventually.be.rejectedWith(/multiple element finding/i);
});
it('should throw an error in attempt to use unregistered plugin', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'z:foo').should.eventually.be.rejectedWith(/was not registered/);
});
it('should throw an error if plugin cannot be loaded', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: './foo.js'};
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/could not load/i);
});
it('should throw an error if plugin is not the right shape', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: BAD_CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/constructed correctly/i);
});
it('should pass on an error thrown by the finder itself', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:error').should.eventually.be.rejectedWith(/plugin error/i);
});
it('should throw no such element error if element not found', async function () {
const d = new BaseDriver();
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
await d.findElement(CUSTOM_STRATEGY, 'f:nope').should.eventually.be.rejectedWith(/could not be located/);
});
});
@@ -1,12 +1,8 @@
import logCommands from '../../../lib/basedriver/commands/log';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import _ from 'lodash';
chai.should();
chai.use(chaiAsPromised);
const expect = chai.expect;
const FIRST_LOGS = ['first', 'logs'];
@@ -71,4 +67,4 @@ describe('log commands -', function () {
SUPPORTED_LOG_TYPES.two.getter.called.should.be.false;
});
});
});
});
@@ -4,26 +4,20 @@ import {
MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY
} from '../../lib/constants';
import axios from 'axios';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import B from 'bluebird';
import getPort from 'get-port';
const should = chai.should();
const DEFAULT_ARGS = {
address: 'localhost',
port: null
};
chai.use(chaiAsPromised);
function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let port;
describe('BaseDriver (e2e)', function () {
let address = defaultCaps['appium:address'] ?? '127.0.0.1';
let port = defaultCaps['appium:port'];
const className = DriverClass.name || '(unknown driver)';
describe(`BaseDriver E2E (as ${className})`, function () {
let baseServer, d;
before(async function () {
port = await getPort();
DEFAULT_ARGS.port = port;
d = new DriverClass(DEFAULT_ARGS);
port = port ?? await getPort();
defaultCaps = {...defaultCaps, 'appium:port': port};
d = new DriverClass({port, address});
baseServer = await server({
routeConfiguringFunction: routeConfiguringFunction(d),
port
@@ -35,7 +29,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
async function startSession (caps) {
return (await axios({
url: `http://localhost:${port}/session`,
url: `http://${address}:${port}/session`,
method: 'POST',
data: {capabilities: {alwaysMatch: caps, firstMatch: [{}]}},
})).data.value;
@@ -43,7 +37,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
async function endSession (id) {
return (await axios({
url: `http://localhost:${port}/session/${id}`,
url: `http://${address}:${port}/session/${id}`,
method: 'DELETE',
validateStatus: null,
})).data.value;
@@ -51,7 +45,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
async function getSession (id) {
return (await axios({
url: `http://localhost:${port}/session/${id}`,
url: `http://${address}:${port}/session/${id}`,
})).data.value;
}
@@ -61,7 +55,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let times = 0;
do {
const {sessionId} = (await axios({
url: `http://localhost:${port}/session`,
url: `http://${address}:${port}/session`,
headers: {
'X-Idempotency-Key': '123456',
},
@@ -77,7 +71,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
_.uniq(sessionIds).length.should.equal(1);
const {status, data} = await axios({
url: `http://localhost:${port}/session/${sessionIds[0]}`,
url: `http://${address}:${port}/session/${sessionIds[0]}`,
method: 'DELETE',
});
status.should.equal(200);
@@ -89,7 +83,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let times = 0;
do {
reqs.push(axios({
url: `http://localhost:${port}/session`,
url: `http://${address}:${port}/session`,
headers: {
'X-Idempotency-Key': '12345',
},
@@ -102,7 +96,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
_.uniq(sessionIds).length.should.equal(1);
const {status, data} = await axios({
url: `http://localhost:${port}/session/${sessionIds[0]}`,
url: `http://${address}:${port}/session/${sessionIds[0]}`,
method: 'DELETE',
});
status.should.equal(200);
@@ -111,18 +105,18 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
it('should create session and retrieve a session id, then delete it', async function () {
let {status, data} = await axios({
url: `http://localhost:${port}/session`,
url: `http://${address}:${port}/session`,
method: 'POST',
data: {capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}},
});
status.should.equal(200);
should.exist(data.value.sessionId);
data.value.capabilities.platformName.should.equal('iOS');
data.value.capabilities.deviceName.should.equal('Delorean');
data.value.capabilities.platformName.should.equal(defaultCaps.platformName);
data.value.capabilities.deviceName.should.equal(defaultCaps['appium:deviceName']);
({status, data} = await axios({
url: `http://localhost:${port}/session/${d.sessionId}`,
url: `http://${address}:${port}/session/${d.sessionId}`,
method: 'DELETE',
}));
@@ -172,13 +166,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let newSession = await startTimeoutSession(0.25);
await axios({
url: `http://localhost:${port}/session/${d.sessionId}/element`,
url: `http://${address}:${port}/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
const {data} = await axios({
url: `http://localhost:${port}/session/${d.sessionId}`,
url: `http://${address}:${port}/session/${d.sessionId}`,
validateStatus: null,
});
should.equal(data.value.error, 'invalid session id');
@@ -191,7 +185,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let newSession = await startTimeoutSession(0.1);
let start = Date.now();
const {value} = (await axios({
url: `http://localhost:${port}/session/${d.sessionId}/elements`,
url: `http://${address}:${port}/session/${d.sessionId}/elements`,
method: 'POST',
data: {using: 'name', value: 'foo'},
})).data;
@@ -205,15 +199,15 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
let newSession = await startTimeoutSession(0);
await axios({
url: `http://localhost:${port}/session/${d.sessionId}/element`,
url: `http://${address}:${port}/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
const {value} = (await axios({
url: `http://localhost:${port}/session/${d.sessionId}`,
url: `http://${address}:${port}/session/${d.sessionId}`,
})).data;
value.platformName.should.equal('iOS');
value.platformName.should.equal(defaultCaps.platformName);
const resp = await endSession(newSession.sessionId);
should.equal(resp, null);
@@ -223,13 +217,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
it('should not timeout if its just the command taking awhile', async function () {
let newSession = await startTimeoutSession(0.25);
await axios({
url: `http://localhost:${port}/session/${d.sessionId}/element`,
url: `http://${address}:${port}/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
const {value} = (await axios({
url: `http://localhost:${port}/session/${d.sessionId}`,
url: `http://${address}:${port}/session/${d.sessionId}`,
validateStatus: null,
})).data;
value.error.should.equal('invalid session id');
@@ -274,7 +268,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
await B.delay(5000);
}.bind(d);
const reqPromise = axios({
url: `http://localhost:${port}/status`,
url: `http://${address}:${port}/status`,
validateStatus: null,
});
// make sure that the request gets to the server before our shutdown
@@ -347,7 +341,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
d.allowInsecure = [];
const script = `return 'foo'`;
await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'wd'},
}).should.eventually.be.rejected;
@@ -364,7 +358,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return [timeouts, status];
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'webdriverio'},
})).data;
@@ -376,7 +370,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
it('should fail with any script type other than webdriverio currently', async function () {
const script = `return 'foo'`;
await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'wd'},
}).should.eventually.be.rejected;
@@ -387,7 +381,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return await driver.$("~amazing");
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
@@ -403,7 +397,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return {element: el, elements: [el, el]};
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
@@ -423,7 +417,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return null;
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
@@ -435,7 +429,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return typeof driver.lock;
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
@@ -447,7 +441,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return await driver.$("~notfound");
`;
const {data} = await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script},
@@ -464,7 +458,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return {;
`;
const {data} = await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script},
@@ -479,7 +473,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
return true;
`;
const {value} = (await axios({
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script, timeout: 50},
@@ -1,18 +1,16 @@
import _ from 'lodash';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import B from 'bluebird';
import { DeviceSettings } from '../..';
import sinon from 'sinon';
const should = chai.should();
chai.use(chaiAsPromised);
// wrap these tests in a function so we can export the tests and re-use them
// for actual driver implementations
function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
describe('BaseDriver', function () {
// to display the driver under test in report
const className = DriverClass.name || '(unknown driver)';
describe(`BaseDriver (as ${className})`, function () {
let d, w3cCaps;
beforeEach(function () {
@@ -75,12 +73,12 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
let sessions = await d.getSessions();
sessions.length.should.equal(1);
sessions[0].should.eql({
id: d.sessionId,
capabilities: {
'deviceName': 'Commodore 64',
'platformName': 'Fake'
}
sessions[0].should.include({
id: d.sessionId
});
sessions[0].capabilities.should.include({
deviceName: 'Commodore 64',
platformName: 'Fake'
});
});
@@ -393,7 +391,7 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
beforeEach(async function () {
beforeStartTime = Date.now();
d.shouldValidateCaps = false;
await d.executeCommand('createSession', null, null, defaultCaps);
await d.executeCommand('createSession', null, null, {alwaysMatch: {...defaultCaps}, firstMatch: [{}]});
});
describe('#eventHistory', function () {
it('should have an eventHistory property', function () {
@@ -1,8 +1,6 @@
import chai from 'chai';
import path from 'path';
import url from 'url';
import chaiAsPromised from 'chai-as-promised';
import { fs } from 'appium-support';
import { fs } from '@appium/support';
import { configureApp } from '../../lib/basedriver/helpers';
import http from 'http';
import finalhandler from 'finalhandler';
@@ -11,8 +9,6 @@ import contentDisposition from 'content-disposition';
import B from 'bluebird';
chai.should();
chai.use(chaiAsPromised);
function getFixture (file) {
// XXX: __dirname disallowed in native ESM
@@ -1,11 +1,8 @@
import { isPackageOrBundle, duplicateKeys, parseCapsArray } from '../../lib/basedriver/helpers';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { zip, fs, tempDir } from '@appium/support';
import { configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray } from '../../lib/basedriver/helpers';
import sinon from 'sinon';
chai.use(chaiAsPromised);
const should = chai.should();
describe('helpers', function () {
describe('#isPackageOrBundle', function () {
it('should accept packages and bundles', function () {
@@ -92,6 +89,31 @@ describe('helpers', function () {
duplicateKeys(input, 'foo', 'FOO').should.deep.equal(expectedOutput);
});
});
describe('#configureApp', function () {
let sandbox;
beforeEach(function () {
sandbox = sinon.createSandbox();
sandbox.stub(zip, 'extractAllTo').resolves();
sandbox.stub(zip, 'assertValidZip').resolves();
sandbox.stub(fs, 'mv').resolves();
sandbox.stub(fs, 'exists').resolves(true);
sandbox.stub(fs, 'hash').resolves('0xDEADBEEF');
sandbox.stub(fs, 'glob').resolves(['/path/to/an.apk']);
sandbox.stub(fs, 'rimraf').resolves();
sandbox.stub(tempDir, 'openDir').resolves('/some/dir');
});
afterEach(function () {
sandbox.restore();
});
it('should pass "useSystemUnzip" flag through to @appium/support', async function () {
await configureApp('/path/to/an.apk.zip', '.apk');
zip.extractAllTo.getCall(0).lastArg.useSystemUnzip.should.be.true;
});
});
});
describe('parseCapsArray', function () {
@@ -1,11 +1,7 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import BaseDriver from '../..';
import sinon from 'sinon';
chai.should();
chai.use(chaiAsPromised);
describe('timeout', function () {
@@ -2,13 +2,10 @@ import _ from 'lodash';
import { server, routeConfiguringFunction,
DEFAULT_WS_PATHNAME_PREFIX } from '../..';
import { FakeDriver } from '../protocol/fake-driver';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import WebSocket from 'ws';
import B from 'bluebird';
import getPort from 'get-port';
chai.use(chaiAsPromised);
describe('Websockets (e2e)', function () {
let baseServer;
@@ -2,15 +2,11 @@
import { server } from '../..';
import axios from 'axios';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import B from 'bluebird';
import _ from 'lodash';
import getPort from 'get-port';
chai.should();
chai.use(chaiAsPromised);
describe('server', function () {
let hwServer;
@@ -2,14 +2,9 @@
import { server, routeConfiguringFunction } from '../../index.js';
import { configureServer, normalizeBasePath } from '../../lib/express/server';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import getPort from 'get-port';
const should = chai.should();
chai.use(chaiAsPromised);
function fakeApp () {
const app = {
use: sinon.spy(),

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