Compare commits

...
Author SHA1 Message Date
Jonathan Lipps f7b20335ea chore: publish
- appium@3.2.0
 - @appium/base-driver@10.2.0
 - @appium/base-plugin@3.0.6
 - @appium/docutils@2.2.1
 - @appium/driver-test-support@1.0.6
 - @appium/eslint-config-appium-ts@3.0.0
 - @appium/execute-driver-plugin@5.1.0
 - @appium/fake-driver@6.0.2
 - @appium/fake-plugin@4.0.6
 - @appium/images-plugin@4.1.0
 - @appium/logger@2.0.4
 - @appium/opencv@4.1.0
 - @appium/plugin-test-support@1.0.5
 - @appium/relaxed-caps-plugin@2.0.2
 - @appium/schema@1.0.1
 - @appium/storage-plugin@1.0.5
 - @appium/strongbox@1.0.1
 - @appium/support@7.0.5
 - @appium/test-support@4.0.5
 - @appium/types@1.2.0
 - @appium/universal-xml-plugin@2.0.4
2026-01-27 07:59:26 +09:00
renovate[bot] 8b9597b0e5 chore(support): update dependency axios to v1.13.3 (#21889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 08:25:32 +01:00
renovate[bot] 556e90be7e chore(storage-plugin): update dependency lru-cache to v11.2.5 (#21888)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 08:25:23 +01:00
Mykola Mokhnach ab8ee41e79 feat(execute-driver-plugin): Migrate to typescript (#21887) 2026-01-25 22:52:21 +01:00
renovate[bot] 028a4274e1 chore(strongbox): update dependency env-paths to v4 (#21886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-25 12:13:20 +02:00
renovate[bot] 9a4e5c77ac chore(eslint-config-appium-ts): update dependency @stylistic/eslint-plugin to v5.7.1 (#21885)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 13:48:44 +00:00
renovate[bot] cafb9bf089 chore(support): update dependency teen_process to v4.0.8 (#21884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 14:45:37 +00:00
renovate[bot] cdfe8ddc48 chore(base-driver): update dependency asyncbox to v6.0.1 (#21879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 09:11:19 +00:00
Mykola Mokhnach 5ff00a0a5c chore: Make sure tests are never included into published dist (#21883) 2026-01-23 08:13:36 +01:00
Mykola Mokhnach fec5f3a377 feat(opencv): Migrate to typescript (#21881) 2026-01-23 06:54:29 +01:00
Edgars Eglītis 5e5b76cf6b chore(deps): update asyncbox to v6 (#21880) 2026-01-22 20:17:20 +02:00
renovate[bot] efd13358cc chore(workflows): update peter-evans/create-pull-request action to v8.1.0 (#21878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 09:40:28 +00:00
renovate[bot] 17eb3cac86 chore(deps): update dependency prettier to v3.8.1 (#21877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 06:43:28 +00:00
dependabot[bot] 4419664ffd chore(deps): bump the npm_and_yarn group across 16 directories with 1 update (#21875)
Bumps the npm_and_yarn group with 1 update in the / directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/appium directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/base-driver directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/docutils directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/driver-test-support directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/execute-driver-plugin directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/fake-driver directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/fake-plugin directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/images-plugin directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/logger directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/opencv directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/relaxed-caps-plugin directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/storage-plugin directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/support directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/test-support directory: [lodash](https://github.com/lodash/lodash).
Bumps the npm_and_yarn group with 1 update in the /packages/universal-xml-plugin directory: [lodash](https://github.com/lodash/lodash).


Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 08:36:09 +02:00
renovate[bot] 4be29e49b5 chore(fake-driver): update dependency asyncbox to v5 (#21874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-21 11:34:31 +02:00
renovate[bot] f115ebed49 chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.53.1 (#21871)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-20 12:41:13 +00:00
Edgars Eglītis beb9da3320 chore: run npm audit fix (#21869) 2026-01-19 11:29:00 +02:00
Mykola Mokhnach 7c9d1363a9 test(images-plugin): Migrate tests to typescript (#21868) 2026-01-18 20:53:54 +01:00
Kazuaki Matsuo 5e28714442 fix: apply npm run lint:fix (#21867) 2026-01-18 09:04:01 -08:00
Mykola Mokhnach 14507371f7 feat(images-plugin): Migrate to typescript (#21865) 2026-01-18 16:10:48 +01:00
renovate[bot] 384afc77ff chore(execute-driver-plugin): update dependency webdriverio to v9.23.2 (#21866)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-18 12:59:27 +00:00
Kazuaki Matsuo 77c9b58989 feat(eslint-config-appium-ts): add unicorn/prefer-node-protocol with warn (#21864)
* feat(eslint-config-appium-ts): add unicorn/prefer-node-protocol with warn

* chore: remvoe unicorn from test files block
2026-01-18 01:28:25 -08:00
renovate[bot] 9500bd17e3 chore(deps): update dependency @types/node to v24.10.9 (#21862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 08:49:10 +00:00
Edgars Eglītis 1f9e786df2 chore: update lockfile to v3 (#21861) 2026-01-16 09:12:21 +02:00
renovate[bot] a605032f2d chore(deps): update dependency prettier to v3.8.0 (#21860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 08:43:22 +00:00
dependabot[bot] bfa33c6bf7 chore(deps): bump undici in the npm_and_yarn group across 1 directory (#21859)
Bumps the npm_and_yarn group with 1 update in the / directory: [undici](https://github.com/nodejs/undici).


Updates `undici` from 6.21.3 to 6.23.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.21.3...v6.23.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.23.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 08:43:10 +02:00
renovate[bot] 99d663c930 chore(types): update dependency type-fest to v5.4.1 (#21858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 18:46:18 +00:00
renovate[bot] 70b383deaa chore(eslint-config-appium-ts): update dependency eslint-plugin-n to v17.23.2 (#21857)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 13:10:03 +00:00
renovate[bot] 43002e3286 chore(deps): update dependency @types/node to v24.10.8 (#21856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 09:12:05 +00:00
renovate[bot] 2f949e581e chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.53.0 (#21855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-13 21:13:39 +00:00
renovate[bot] 27419248e4 chore(docutils): update dependency diff to v8.0.3 (#21854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-13 16:35:44 +00:00
renovate[bot] a5bee22a07 chore(deps): update dependency @types/node to v24.10.7 (#21852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:24:04 +00:00
renovate[bot] cfaaf768a5 chore(deps): update definitelytyped (#21850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-10 17:49:01 +00:00
renovate[bot] 0508626518 chore(eslint-config-appium-ts): update dependency @eslint/compat to v2.0.1 (#21851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-10 12:54:41 +00:00
renovate[bot] 9dc0030fb9 chore(eslint-config-appium-ts): update dependency @stylistic/eslint-plugin to v5.7.0 (#21849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 13:51:33 +00:00
renovate[bot] 002f8fce7f chore(base-driver): update dependency body-parser to v2.2.2 (#21847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-08 08:29:58 +00:00
github-actions[bot]andmykola-mokhnach 57ae03f899 docs(appium): Update documentation translations (#21846)
Co-authored-by: mykola-mokhnach <7767781+mykola-mokhnach@users.noreply.github.com>
2026-01-08 07:48:22 +01:00
Edgars Eglītis df5a7da467 chore(deps): update qs to 6.14.1 (#21845) 2026-01-07 08:58:05 +02:00
renovate[bot] 8de688f020 chore(storage-plugin): update dependency ws to v8.19.0 (#21843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-06 16:50:47 +00:00
renovate[bot] d4fcd81aed chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.52.0 (#21842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-06 13:03:36 +00:00
renovate[bot] aa90b9a840 chore(execute-driver-plugin): update dependency webdriverio to v9.23.0 (#21841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-04 12:46:13 +00:00
Edgars Eglītis 1f6c82ce6d docs: add SeleniumConf & AppiumConf 2026 banner (#21840) 2026-01-01 16:57:16 +02:00
Kazuaki Matsuo 529aeb48f1 fix(appium): appium symlink behavior across drivers/plugins installations (#21837)
* fix(appium): appium symlink behavior with plugins

* add unit tests

* add invalid format case
2025-12-30 13:03:21 -08:00
renovate[bot] 04d9520f76 chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.51.0 (#21838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-30 14:52:30 +00:00
renovate[bot] 6098bdeb92 chore(execute-driver-plugin): update dependency webdriverio to v9.22.0 (#21834)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-24 12:40:57 +00:00
Kazuaki Matsuo a6c4fad26b chore: bump teen-process (#21831)
* chore: bump teen-process

* remove renovate

* update tests

* update mock in e2e

* remove the custom rule in renovate

* remove unused tpMock

* remove Custom Rules completely
2025-12-23 09:30:32 -08:00
renovate[bot] 8e36bfe56d chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.50.1 (#21833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 13:35:20 +00:00
renovate[bot] fdb95389b1 chore(driver-test-support): update dependency chai to v6.2.2 (#21832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 12:01:34 +01:00
renovate[bot] 30adda0092 chore(test-support): update dependency sinon to v21.0.1 (#21828)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-20 17:06:05 +00:00
renovate[bot] 441c4e5572 chore(docutils): update dependency mkdocs-material to v9.7.1 (#21827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 15:51:06 +00:00
renovate[bot] 7235392585 chore(fake-driver): update dependency asyncbox to v4 (#21826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 08:33:31 +02:00
Mykola Mokhnach ce91686852 feat(appium): Add repository links to verbose extensions list (#21813) 2025-12-17 16:01:38 +01:00
renovate[bot] cbf7e4dfd6 chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.50.0 (#21825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 10:56:05 +00:00
renovate[bot] a9a66d7cf5 chore(deps): update dependency serve-static to v2.2.1 (#21824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 07:47:48 +00:00
Edgars Eglītis 2bd2bb7667 chore(renovate): remove custom Appium groups (#21821) 2025-12-16 17:40:50 +02:00
Edgars Eglītis 56a45a5244 chore(deps): update read-pkg to v10 (#21820) 2025-12-16 11:54:31 +02:00
Edgars Eglītis d9e4437c55 chore(deps): remove wrap-ansi (#21818)
* chore(deps): remove wrap-ansi

* remove unused lodash import
2025-12-15 20:28:10 +02:00
Edgars Eglītis e9a6e5425c fix(scripts): properly import log-symbols in scripts (#21819) 2025-12-15 17:17:24 +02:00
Edgars Eglītis 053a88bac1 chore(deps): update log-symbols to v7 (#21816) 2025-12-15 14:13:29 +02:00
github-actions[bot]andmykola-mokhnach cae84898d5 docs(appium): Update documentation translations (#21815)
Co-authored-by: mykola-mokhnach <7767781+mykola-mokhnach@users.noreply.github.com>
2025-12-14 19:03:22 -08:00
Edgars Eglītis dcfaa88830 fix(docs): add missing verbose flag for CLI list command (#21814) 2025-12-15 00:02:39 +02:00
renovate[bot] 1143896b97 chore(deps): update dependency @types/node to v24.10.4 (#21812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-14 12:51:52 +00:00
renovate[bot] a90797683c chore(universal-xml-plugin): update dependency fast-xml-parser to v5.3.3 (#21809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-14 01:36:04 +00:00
renovate[bot] cc42206b4d chore(support): update dependency teen_process to v3.0.6 (#21808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 20:26:48 +00:00
renovate[bot] e9ca1f5dd1 chore(eslint-config-appium-ts): update eslint-related packages to v9.39.2 (#21811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 16:40:04 +00:00
Edgars Eglītis a5ba1c5efb chore: fix all lint issues (#21810)
* chore: fix all lint issues

* fix unit tests

* apply copilot suggestion
2025-12-13 15:24:16 +02:00
Edgars Eglītis b0b219aa01 feat(eslint-config-appium-ts)!: modernize ESLint config (#21781) 2025-12-12 09:07:14 +02:00
github-actions[bot]andmykola-mokhnach 204a07f5a4 docs(appium): Update documentation translations (#21807)
Co-authored-by: mykola-mokhnach <7767781+mykola-mokhnach@users.noreply.github.com>
2025-12-11 21:22:03 -08:00
Srinivasan SekarandSaikrishna321 aeef6afb8b docs(appium): Add LambdaTest as Development partner (#21783)
Co-authored-by: Saikrishna321 <saikrishna321@yahoo.com>
2025-12-11 08:58:15 -08:00
Edgars Eglītis 8093c2658c chore(deps): update pkg-dir to v8 and rename it (#21806)
* chore(deps): update pkg-dir to v8 and rename it

* address copilot comments

* remove uuid cache keys since we always pass cwd
2025-12-11 17:28:10 +02:00
Edgars Eglītis 5b74a7653c chore(deps): update get-stream to v9 (#21805) 2025-12-11 16:16:07 +02:00
Edgars Eglītis 671c37a89c chore(deps): update get-port to v7 (#21804) 2025-12-11 15:59:00 +02:00
Edgars Eglītis cb2d6eef6c chore(deps): update env-paths to v3 (#21803) 2025-12-11 15:41:17 +02:00
Edgars Eglītis 183a2683b6 chore(deps): update supports-color to v10 (#21802) 2025-12-11 15:06:57 +02:00
Edgars Eglītis ee3d935899 chore(docutils): remove chalk (#21801) 2025-12-11 13:26:24 +02:00
Edgars Eglītis 591e678e6f chore(deps): remove source-map-support (#21800) 2025-12-11 12:50:26 +02:00
renovate[bot] 0cc3acf5e3 chore(support): update dependency teen_process to v3.0.5 (#21799)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 12:37:36 +02:00
Edgars Eglītis b20d36f979 chore(renovate): update config (#21797)
* chore: remove unused packages from Renovate blocklist

* chore: other Renovate config adjustments
2025-12-11 12:30:42 +02:00
renovate[bot] 146bc98876 chore(deps): update dependency @types/node to v24.10.3 (#21796)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 11:21:29 +01:00
renovate[bot] 477b837b1c chore(workflows): update peter-evans/create-pull-request action to v8 (#21795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-10 13:09:12 +02:00
renovate[bot] ac78fedf43 chore(eslint-config-appium-ts): update dependency typescript-eslint to v8.49.0 (#21794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 18:31:24 +00:00
renovate[bot] b0e12852f0 chore(deps): update dependency @types/node to v24.10.2 (#21793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 12:59:31 +00:00
Mykola Mokhnach 274b28cb29 chore(test-support): Mark the module as deprecated (#21791) 2025-12-08 18:34:04 +01:00
renovate[bot] cb9324cda4 chore(types): update dependency type-fest to v5.3.1 (#21792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 14:48:55 +00:00
Karl BaumgartnerandKarl Baumgartner a4b47c02c6 feat(base-driver): add ability to supply custom headers for proxy (#21790)
Signed-off-by: Karl Baumgartner <178656887+karlbaumg@users.noreply.github.com>
Co-authored-by: Karl Baumgartner <178656887+karlbaumg@users.noreply.github.com>
2025-12-07 22:11:53 +01:00
renovate[bot] adf90ee746 chore(appium): update dependency winston to v3.19.0 (#21789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-07 10:42:25 +01:00
renovate[bot] 653b95965a chore(workflows): update peter-evans/create-pull-request action to v7.0.11 (#21788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 13:35:51 +00:00
github-actions[bot]andmykola-mokhnach 18864bfcac docs(appium): Update documentation translations (#21787)
Co-authored-by: mykola-mokhnach <7767781+mykola-mokhnach@users.noreply.github.com>
2025-12-05 20:50:08 -08:00
Edgars Eglītis 6ae5930346 docs: move Appium MCP to official tools (#21786) 2025-12-05 23:16:18 +02:00
210 changed files with 5531 additions and 19368 deletions
@@ -29,7 +29,7 @@ jobs:
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_DOCS_PROJECT_ID }}
CROWDIN_TOKEN: ${{ secrets.CROWDIN_DOCS_TOKEN }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.9
uses: peter-evans/create-pull-request@v8.1.0
with:
token: ${{ github.token }}
commit-message: 'docs(appium): Update documentation translations'
+27 -21
View File
@@ -1,24 +1,30 @@
import tsConfig from '@appium/eslint-config-appium-ts';
import appiumConfig from '@appium/eslint-config-appium-ts';
import {defineConfig, globalIgnores} from 'eslint/config';
export default [
...tsConfig,
{
...tsConfig.find(({name}) => name === 'Test Files'),
name: 'Test Support',
files: [
'packages/test-support/lib/**',
'packages/driver-test-support/lib/**',
'packages/plugin-test-support/lib/**',
],
},
{
name: 'Ignores',
ignores: [
'**/build-fixtures/**',
'packages/appium/docs/**/assets/**',
'packages/appium/docs/**/js/**',
'packages/appium/sample-code/**',
],
},
// Create a modified config subset for test support files:
// Extract the test file related configs (Mocha plugin and custom rules),
// then override their 'files' property
const testFileConfigItems = [appiumConfig[8], appiumConfig[9]];
const testSupportFiles = [
'packages/test-support/lib/**',
'packages/driver-test-support/lib/**',
'packages/plugin-test-support/lib/**',
];
const testSupportConfig = testFileConfigItems.map((item) => ({...item, files: testSupportFiles}));
export default defineConfig([
{
name: 'Base Config',
extends: [appiumConfig],
},
{
name: 'Test Support',
extends: [testSupportConfig],
},
globalIgnores([
'**/build-fixtures/**',
'packages/appium/docs/**/assets/**',
'packages/appium/docs/**/js/**',
'packages/appium/sample-code/**',
]),
]);
+1936 -16621
View File
File diff suppressed because it is too large Load Diff
+20 -21
View File
@@ -85,10 +85,11 @@
},
"devDependencies": {
"@colors/colors": "1.6.0",
"@eslint/js": "9.39.1",
"@eslint/js": "9.39.2",
"@stylistic/eslint-plugin": "5.7.1",
"@tsconfig/node20": "20.1.8",
"@types/argparse": "2.0.17",
"@types/archiver": "7.0.0",
"@types/argparse": "2.0.17",
"@types/async-lock": "1.4.2",
"@types/base64-stream": "1.0.5",
"@types/bluebird": "3.5.42",
@@ -99,61 +100,59 @@
"@types/json-schema": "7.0.15",
"@types/klaw": "3.0.7",
"@types/lockfile": "1.0.4",
"@types/lodash": "4.17.21",
"@types/lodash": "4.17.23",
"@types/method-override": "3.0.0",
"@types/mocha": "10.0.10",
"@types/mv": "2.1.4",
"@types/ncp": "2.0.8",
"@types/node": "24.10.1",
"@types/node": "24.10.9",
"@types/pluralize": "0.0.33",
"@types/semver": "7.7.1",
"@types/serve-favicon": "2.5.7",
"@types/shell-quote": "1.7.5",
"@types/sinon": "21.0.0",
"@types/sinon-chai": "4.0.0",
"@types/source-map-support": "0.5.10",
"@types/supports-color": "8.1.3",
"@types/teen_process": "2.0.4",
"@types/which": "3.0.4",
"@types/wrap-ansi": "3.0.0",
"@types/ws": "8.18.1",
"@types/xmldom": "0.1.34",
"@types/yargs": "17.0.35",
"asyncbox": "3.0.0",
"chai": "6.2.1",
"asyncbox": "6.0.1",
"chai": "6.2.2",
"chai-as-promised": "8.0.2",
"conventional-changelog-conventionalcommits": "7.0.2",
"cpy-cli": "6.0.0",
"cross-env": "10.1.0",
"eslint": "9.39.1",
"eslint": "9.39.2",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-import-x": "4.16.1",
"eslint-plugin-mocha": "11.2.0",
"eslint-plugin-n": "17.23.2",
"eslint-plugin-promise": "7.2.1",
"finalhandler": "2.1.1",
"get-port": "5.1.1",
"get-port": "7.1.0",
"json-schema-to-typescript": "15.0.4",
"lerna": "9.0.3",
"log-symbols": "4.1.0",
"log-symbols": "7.0.1",
"midnight-smoker": "8.0.0",
"mjpeg-consumer": "2.0.0",
"mjpeg-server": "0.3.1",
"mocha": "11.7.5",
"npm-run-all2": "8.0.4",
"prettier": "3.7.4",
"prettier": "3.8.1",
"rewiremock": "3.14.6",
"rimraf": "6.1.2",
"serve-static": "2.2.0",
"sinon": "21.0.0",
"serve-static": "2.2.1",
"sinon": "21.0.1",
"sinon-chai": "4.0.1",
"sync-monorepo-packages": "1.0.2",
"teen_process": "3.0.4",
"teen_process": "4.0.8",
"ts-node": "10.9.2",
"tsd": "0.33.0",
"typescript": "5.9.3",
"typescript-eslint": "8.48.1",
"webdriverio": "9.21.0",
"ws": "8.18.3",
"typescript-eslint": "8.53.1",
"webdriverio": "9.23.2",
"ws": "8.19.0",
"yaml-js": "0.3.1"
},
"engines": {
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.2.0](https://github.com/appium/appium/compare/appium@3.1.2...appium@3.2.0) (2026-01-26)
### Features
* **appium:** Add repository links to verbose extensions list ([#21813](https://github.com/appium/appium/issues/21813)) ([ce91686](https://github.com/appium/appium/commit/ce91686852b0baab5f46990f5d40db4747922924))
### Bug Fixes
* **appium:** appium symlink behavior across drivers/plugins installations ([#21837](https://github.com/appium/appium/issues/21837)) ([529aeb4](https://github.com/appium/appium/commit/529aeb48f10419e21555cec05835ff07bb0e2e51))
* apply npm run lint:fix ([#21867](https://github.com/appium/appium/issues/21867)) ([5e28714](https://github.com/appium/appium/commit/5e28714442cf59ee35b085e01e82c3f5483891fd))
* **docs:** add missing verbose flag for CLI list command ([#21814](https://github.com/appium/appium/issues/21814)) ([dcfaa88](https://github.com/appium/appium/commit/dcfaa88830f6a5c608c92f5ae72445e065261d38))
## [3.1.2](https://github.com/appium/appium/compare/appium@3.1.1...appium@3.1.2) (2025-12-04)
**Note:** Version bump only for package appium
+31 -27
View File
@@ -19,6 +19,36 @@ Appium has a graphical client which can be used to inspect application screensho
application hierarchy, search for elements, run Appium commands, record app interactions, and more.
It can be very useful for Appium test development.
=== "Install as an Appium Plugin"
```bash
appium plugin install inspector
```
=== "Install Standalone"
Download the app from the Appium Inspector repo's [GitHub Releases section](https://github.com/appium/appium-inspector/releases).
### [Appium MCP](https://github.com/appium/appium-mcp)
Appium MCP is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"appium-mcp": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["appium-mcp@latest"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk"
}
}
}
```
## Extension Tools
Appium driver or plugin developers can choose to include these tools in their driver/plugin:
@@ -33,7 +63,7 @@ details needed for the driver/plugin have been set up correctly. The tool can be
appium {driver|plugin} doctor <extension-name>
```
It shows no results if the driver/plugin author did not implement Doctor support.
It shows no results if the driver/plugin does not have such validation checks.
!!! note
@@ -55,29 +85,3 @@ Supported by: `@AppiumTestDistribution`
```sh title="Install This Tool"
npm install -g appium-installer
```
### [Jarvis Appium](https://github.com/AppiumTestDistribution/mcp-appium)
Jarvis Appium is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
Supported by: `@AppiumTestDistribution`
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"mcpServers": {
"jarvis-appium": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["jarvis-appium"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk",
"CAPABILITIES_CONFIG": "/path/to/your/capabilities.json"
}
}
}
}
```
@@ -134,6 +134,7 @@ appium {driver|plugin} list
|`--installed`|Only list all installed extensions|boolean|
|`--json`|Return the result in JSON format|boolean|
|`--updates`|List all extensions along with information on whether newer versions are available. Only supported for extensions installed via `npm`.|boolean|
|`--verbose`|Show additional details for each extension|boolean|
#### Example
@@ -22,6 +22,11 @@ Hub](https://opencollective.com/appium).
We are very grateful for the support of our Development Partners, who donate Appium development and
maintenance as part of their employees' core job duties!
<a href="https://lambdatest.com/?utm_source=appium.io&utm_medium=organic&utm_campaign=june_25&utm_term=sk&utm_content=webpage">
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-dark.png#only-dark" width="250" alt="LambdaTest" />
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-light.png#only-light" width="250" alt="LambdaTest" />
</a>
## Strategic Partners
We are very grateful for the financial and marketing support of our exclusive Strategic Partners,
+36 -27
View File
@@ -21,6 +21,41 @@ Appium has a graphical client which can be used to inspect application screensho
application hierarchy, search for elements, run Appium commands, record app interactions, and more.
It can be very useful for Appium test development.
\=== "Install as an Appium Plugin"
````
```bash
appium plugin install inspector
```
````
\=== "Install Standalone"
```
Download the app from the Appium Inspector repo's [GitHub Releases section](https://github.com/appium/appium-inspector/releases).
```
### [Appium MCP](https://github.com/appium/appium-mcp)
Appium MCP is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"appium-mcp": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["appium-mcp@latest"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk"
}
}
}
```
## Extension Tools
Appium driver or plugin developers can choose to include these tools in their driver/plugin:
@@ -35,7 +70,7 @@ details needed for the driver/plugin have been set up correctly. The tool can be
appium {driver|plugin} doctor <extension-name>
```
It shows no results if the driver/plugin author did not implement Doctor support.
It shows no results if the driver/plugin does not have such validation checks.
!!! note
@@ -59,29 +94,3 @@ Supported by: `@AppiumTestDistribution`
```sh title="Install This Tool"
npm install -g appium-installer
```
### [Jarvis Appium](https://github.com/AppiumTestDistribution/mcp-appium)
Jarvis Appium is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
Supported by: `@AppiumTestDistribution`
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"mcpServers": {
"jarvis-appium": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["jarvis-appium"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk",
"CAPABILITIES_CONFIG": "/path/to/your/capabilities.json"
}
}
}
}
```
@@ -135,6 +135,7 @@ appium {driver|plugin} list
| `--installed` | Only list all installed extensions | boolean |
| `--json` | Return the result in JSON format | boolean |
| `--updates` | List all extensions along with information on whether newer versions are available. Only supported for extensions installed via `npm`. | boolean |
| `--verbose` | Show additional details for each extension | boolean |
#### Example
@@ -22,6 +22,11 @@ Hub.
We are very grateful for the support of our Development Partners, who donate Appium development and
maintenance as part of their employees' core job duties!
<a href="https://lambdatest.com/?utm_source=appium.io&utm_medium=organic&utm_campaign=june_25&utm_term=sk&utm_content=webpage">
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-dark.png#only-dark" width="250" alt="LambdaTest" />
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-light.png#only-light" width="250" alt="LambdaTest" />
</a>
## Strategic Partners
We are very grateful for the financial and marketing support of our exclusive Strategic Partners,
+17 -17
View File
@@ -9,40 +9,40 @@ theme:
nav:
- 欢迎使用: index.md
- 博客: blog/index.md
- 简介:
- 简介:
- intro/index.md
- 背景:
- 背景:
- intro/appium.md
- intro/drivers.md
- intro/clients.md
- intro/history.md
- 快速开始:
- 快速开始:
- quickstart/index.md
- quickstart/requirements.md
- quickstart/install.md
- quickstart/uiauto2-driver.md
- 写一个测试:
- 写一个测试:
- quickstart/test-js.md
- quickstart/test-py.md
- quickstart/test-java.md
- quickstart/test-rb.md
- quickstart/test-dotnet.md
- quickstart/next-steps.md
- 生态系统:
- 生态系统:
- ecosystem/index.md
- ecosystem/drivers.md
- ecosystem/clients.md
- ecosystem/plugins.md
- ecosystem/tools.md
- 参考:
- 参考:
- reference/index.md
- 命令行界面:
- 命令行界面:
- reference/cli/index.md
- reference/cli/server.md
- reference/cli/extensions.md
- reference/cli/setup.md
- reference/cli/env-vars.md
- API 端点:
- API 端点:
- reference/api/index.md
- reference/api/webdriver.md
- reference/api/bidi.md
@@ -51,11 +51,11 @@ nav:
- reference/api/appium.md
- reference/api/others.md
- reference/api/plugins.md
- 指南:
- 迁移:
- 指南:
- 迁移:
- guides/migrating-2-to-3.md
- guides/migrating-1-to-2.md
- 服务器/驱动程序配置:
- 服务器/驱动程序配置:
- guides/managing-exts.md
- guides/branch-testing.md
- guides/config.md
@@ -65,24 +65,24 @@ nav:
- guides/grid.md
- guides/caching.md
- guides/tls.md
- 会话配置:
- 会话配置:
- guides/caps.md
- guides/settings.md
- guides/execute-methods.md
- guides/context.md
- guides/event-timing.md
- 更多资源:
- 更多资源:
- resources/index.md
- 开发:
- 开发:
- developing/index.md
- developing/build-drivers.md
- developing/build-plugins.md
- developing/build-docs.md
- developing/build-doctor-checks.md
- developing/sensitive.md
- 开发者参考:
- 开发者参考:
- developing/config-system.md
- 贡献:
- 贡献:
- contributing/index.md
- 赞助者:
- 赞助者:
- sponsors/index.md
+8
View File
@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block announce %}
<center>
SeleniumConf & AppiumConf returns on May 6-8, 2026! Join us to learn and connect with the community!
<a href="https://seleniumconf.com/">Register Now</a>
</center>
{% endblock %}
+1 -1
View File
@@ -20,7 +20,7 @@ const {
DOCS_REMOTE,
LATEST_ALIAS,
} = require('./utils');
const path = require('path');
const path = require('node:path');
const semver = require('semver');
const {version} = require('../../package.json');
@@ -3,7 +3,7 @@
/* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable promise/prefer-await-to-then */
const path = require('path');
const path = require('node:path');
const {fs} = require('@appium/support');
const {AppiumConfigJsonSchema} = require('@appium/schema');
+1 -1
View File
@@ -3,7 +3,7 @@
// for simplicity this file is not transpiled and is run directly via an npm script
//
const {logger} = require('@appium/support');
const path = require('path');
const path = require('node:path');
const log = logger.getLogger('Docs');
+36 -27
View File
@@ -21,6 +21,41 @@ Appium has a graphical client which can be used to inspect application screensho
application hierarchy, search for elements, run Appium commands, record app interactions, and more.
It can be very useful for Appium test development.
\=== "Install as an Appium Plugin"
````
```bash
appium plugin install inspector
```
````
\=== "Install Standalone"
```
Download the app from the Appium Inspector repo's [GitHub Releases section](https://github.com/appium/appium-inspector/releases).
```
### [Appium MCP](https://github.com/appium/appium-mcp)
Appium MCP is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"appium-mcp": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["appium-mcp@latest"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk"
}
}
}
```
## Extension Tools
Appium driver or plugin developers can choose to include these tools in their driver/plugin:
@@ -35,7 +70,7 @@ details needed for the driver/plugin have been set up correctly. The tool can be
appium {driver|plugin} doctor <extension-name>
```
It shows no results if the driver/plugin author did not implement Doctor support.
It shows no results if the driver/plugin does not have such validation checks.
!!! note
@@ -59,29 +94,3 @@ Supported by: `@AppiumTestDistribution`
```sh title="Install This Tool"
npm install -g appium-installer
```
### [Jarvis Appium](https://github.com/AppiumTestDistribution/mcp-appium)
Jarvis Appium is a Model Context Protocol (MCP) server that provides mobile test development and
automation capabilities using Appium.
Supported by: `@AppiumTestDistribution`
```json title="Install This Tool"
// Add the following to your MCP client configuration
{
"mcpServers": {
"jarvis-appium": {
"disabled": false,
"timeout": 100,
"type": "stdio",
"command": "npx",
"args": ["jarvis-appium"],
"env": {
"ANDROID_HOME": "/path/to/android/sdk",
"CAPABILITIES_CONFIG": "/path/to/your/capabilities.json"
}
}
}
}
```
+53 -140
View File
@@ -1,22 +1,14 @@
---
title: Intro to Appium Drivers
title: Appium 驱动程序介绍
---
As the [main Overview](./appium.md) makes clear, "drivers" are basically Appium's answer to the
question, "how do we support automation of multiple, unrelated platforms?" In this doc we'll get
into a little more detail about how drivers work. The specific details of how drivers work probably
don't matter too much for you, unless you're planning on writing your own driver or contributing to
an existing driver (things we hope you do!).
正如[概览](./appium.md)所阐明的那样,"驱动程序"基本上是 Appium 对"我们如何支持多个不相关平台的自动化?"这一问题的答案。 在本文档中,我们将更详细地了解驱动程序的工作原理。 驱动程序工作的具体细节对你来说可能并不那么重要,除非你打算编写自己的驱动程序或为现有驱动程序做贡献(我们希望你这样做!)。
The main benefit in understanding a bit more of how drivers work is that being aware of the typical
complexity or the typical driver architecture will inform your debugging process when you
inevitably run into an issue in one of your tests.
了解更多驱动程序工作原理的主要好处是,了解典型的复杂性或典型的驱动程序架构将有助于你在测试中遇到问题时进行调试。
## Interface Implementations
## 接口实现
At the most basic level, drivers are simply Node.js classes that extend a special class included in
Appium, called `BaseDriver`. You could have something very close to a "working" driver, with these
very simple lines of code:
在最基本的层面上,驱动程序只是扩展了 Appium 中包含的一个特殊类的 Node.js 类,这个类叫做 `BaseDriver`。 你可以用以下非常简单的几行代码就拥有一个非常接近"可工作"的驱动程序:
```js
import BaseDriver from '@appium/base-driver'
@@ -25,21 +17,11 @@ class MyNewDriver extends BaseDriver {
}
```
This empty driver doesn't _do_ anything, but you could wrap it up in a Node.js module, add a few
Appium-related fields to the module's manifest (`package.json`), and then install it using `appium
driver install`.
这个空驱动程序不会_做_任何事情,但你可以将它打包成一个 Node.js 模块,在模块的清单文件(`package.json`)中添加一些与 Appium 相关的字段,然后使用 `appium driver install` 安装它。
So, from a technical perspective, an Appium driver is just a bit of code that inherits from some
other Appium code. That's it! Now, inheriting from `BaseDriver` actually gives us a lot, because
`BaseDriver` is essentially an encapsulation of the entire WebDriver protocol. So all a driver
needs to do something useful is to _implement_ Node.js methods with names corresponding to
their WebDriver protocol equivalents.
因此,从技术角度来看,Appium 驱动程序只是一些继承自其他 Appium 代码的代码。 就是这样! 现在,继承自 `BaseDriver` 实际上给了我们很多东西,因为 `BaseDriver` 本质上是整个 WebDriver 协议的封装。 所以驱动程序要做一些有用的事情,只需要_实现_名称与其 WebDriver 协议等效的 Node.js 方法。
So let's say I wanted to do something with this empty driver; first I have to decide which
WebDriver command I want to implement. For our example, let's take the Navigate
To WebDriver command. Leave aside for the moment
what I want to have the driver _do_ when this command is executed. To tell Appium the driver can
handle the command, all we have to do is define a method like this in our driver class:[^1]
假设我想用这个空驱动程序做点什么;首先我必须决定要实现哪个 WebDriver 命令。 对于我们的例子,让我们选择[Navigate To](https://w3c.github.io/webdriver/#navigate-to) WebDriver 命令。 暂时不考虑当这个命令执行时我想让驱动程序_做_什么。 要告诉 Appium 驱动程序可以处理这个命令,我们所要做的就是在驱动程序类中定义一个像这样的方法:[^1]
```js
async setUrl(url) {
@@ -47,142 +29,73 @@ async setUrl(url) {
}
```
[^1]: You might notice that `setUrl` doesn't look anything like `Navigate To`, so how did we know
to use it rather than some other random string? Well, Appium's WebDriver-protocol-to-method-name
mapping is defined in a special file within the `@appium/base-driver` package called
[routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js).
So if you're writing a driver, this is where you would go to figure out what method names to use
and what parameters to expect. Or you could look at the source for any of the main Appium
drivers!
[^1]: 你可能会注意到 `setUrl` 看起来与 `Navigate To` 完全不同,那么我们怎么知道使用它而不是其他随机字符串呢? 其实,Appium 的 WebDriver 协议到方法名的映射是在 `@appium/base-driver` 包中的一个特殊文件中定义的,叫做 [routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js)。
所以如果你正在编写驱动程序,这就是你要去找出要使用什么方法名和期望什么参数的地方。 或者你可以查看任何主要 Appium 驱动程序的源代码!
That's it! How we actually implement the command is totally up to us, and depends on the
platform(s) we want to support. Here are some different example implementations of this command for
different platforms:
就是这样! 我们如何实际实现该命令完全取决于我们自己,并取决于我们想要支持的平台。 以下是针对不同平台的此命令的一些不同示例实现:
- Browsers: execute some JavaScript to set `window.location.href`
- iOS apps: launch an app using a deep link
- Android apps: launch an app using a deep link
- React apps: load a specific route
- Unity: go to a named scene
- 浏览器:执行一些 JavaScript 来设置 `window.location.href`
- iOS 应用:使用深度链接启动应用
- Android 应用:使用深度链接启动应用
- React 应用:加载特定路由
- Unity:转到指定场景
So you can see there can be a lot of differences between how drivers implement the same WebDriver
command across platforms.[^2] What is the _same_, though, is how they express that they can handle
a protocol command.
因此,你可以看到,不同平台上驱动程序实现相同 WebDriver 命令的方式可能存在很大差异。[^2] 然而,_相同_的是它们表达可以处理协议命令的方式。
[^2]: Of course, we want to keep the semantics as similar as possible, but in the world of iOS, for
example, launching an app via a deep link (a URL with a special app-specific scheme) is about as
close as we are going to get to navigating to a web URL.
[^2]: 当然,我们希望保持语义尽可能相似,但在 iOS 的世界里,例如,通过深度链接(具有特殊应用特定方案的 URL)启动应用是我们最接近导航到 Web URL 的方式。
We're going into this great amount of detail (which you don't need to remember, by the way),
because it's important to stress the point that an Appium driver is not inherently anything in
particular, other than a bit of JS code that can handle WebDriver protocol commands. Where you go
from there is up to you, the driver author!
我们之所以如此详细地讨论(顺便说一下,你不需要记住这些),是因为重要的是要强调一点,Appium 驱动程序本质上不是任何特定的东西,只是一些可以处理 WebDriver 协议命令的 JS 代码。 从那里去哪里取决于你,驱动程序作者!
## Automation mapping
## 自动化映射
But _typically_ what driver authors want to do is to provide automation behaviours for a given
platform(s) that are semantically very similar to the the WebDriver spec implementations for
browsers. When you want to find an element, you should get a reference to a UI element. When you
want to click or tap that element, the resulting behaviour should be the same as if a person were
to click or tap on the element. And so on.
但_通常_驱动程序作者想要做的是为给定的平台提供自动化行为,这些行为在语义上与浏览器的 WebDriver 规范实现非常相似。 当你想查找一个元素时,你应该得到一个 UI 元素的引用。 当你想点击或轻触该元素时,产生的行为应该与人点击或轻触该元素相同。 等等。
So the real challenge for driver authors is not how to work with the WebDriver protocol (because
`BaseDriver` encapsulates all that for you), but how to make the actual automation happen on the
target platform. Every driver relies on its own set of underlying technologies here. As mentioned
in the [Overview](index.md), the iOS driver uses an Apple technology called
[XCUITest](https://developer.apple.com/documentation/xctest/xcuielement). These underlying
automation technologies usually have proprietary or idiosyncratic APIs of their own. Writing
a driver becomes the task of mapping the WebDriver protocol to this underlying API (or sometimes
a set of different underlying APIs--for example, the UiAutomator2 driver relies not only on the
[UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator)
technology from Google, but also functions only available through
[ADB](https://developer.android.com/tools/adb), as well as functions only available
via the Android SDK inside a helper app). Tying it all together into a single, usable, WebDriver
interface is the incredibly useful (but incredibly challenging) art of driver development!
因此,驱动程序作者面临的真正挑战不是如何使用 WebDriver 协议(因为 `BaseDriver` 为你封装了所有这些),而是如何在目标平台上实现实际的自动化。 每个驱动程序都依赖于自己的一套底层技术。 正如[概览](index.md)中提到的,iOS 驱动程序使用 Apple 的一项名为 [XCUITest](https://developer.apple.com/documentation/xctest/xcuielement) 的技术。 这些底层自动化技术通常都有自己专有或特殊的 API。 编写驱动程序就是将 WebDriver 协议映射到这个底层 API 的任务(有时是一组不同的底层 API——例如,UiAutomator2 驱动程序不仅依赖于 Google 的 [UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator) 技术,还依赖于只能通过 [ADB](https://developer.android.com/tools/adb) 获得的功能,以及只能通过辅助应用内的 Android SDK 获得的功能)。 将所有这些整合到一个单一、可用的 WebDriver 接口中,是驱动程序开发这项非常有用(但极具挑战性)的艺术!
## Multi-level architecture
## 多层架构
In practice, this often results in a pretty complex architecture. Let's take iOS for example again.
The XCUITest framework (the one used by the Appium driver) expects code that calls it to be written
in Objective-C or Swift. Furthermore, XCUITest code can only be run in a special mode triggered by
Xcode (and directly or indirectly, the Xcode command line tools). In other words, there's no
straightforward way to go from a Node.js function implementation (like `setUrl()` above) to
XCUITest API calls.
在实践中,这通常会导致相当复杂的架构。 让我们再次以 iOS 为例。
XCUITest 框架(Appium 驱动程序使用的框架)期望调用它的代码用 Objective-C 或 Swift 编写。 此外,XCUITest 代码只能在由 Xcode(以及直接或间接的 Xcode 命令行工具)触发的特殊模式下运行。 换句话说,没有直接的方法从 Node.js 函数实现(如上面的 `setUrl()`)到 XCUITest API 调用。
What the XCUITest driver authors have done is instead to split the driver into two parts: one part
written in Node.js (the part which is incorporated into Appium and which initially handles the
WebDriver commands), and the other part written in Objective-C (the part which actually gets run on
an iOS device and makes XCUITest API calls). This makes interfacing with XCUITest possible, but
introduces the new problem of coordination between the two parts.
XCUITest 驱动程序作者所做的是将驱动程序分成两部分:一部分用 Node.js 编写(这部分被整合到 Appium 中并最初处理 WebDriver 命令),另一部分用 Objective-C 编写(这部分实际在 iOS 设备上运行并进行 XCUITest API 调用)。 这使得与 XCUITest 的接口成为可能,但引入了两部分之间协调的新问题。
The driver authors could have chosen any of a number of very different strategies to model the
communication between the Node.js side and the Objective-C side, but at the end of the day decided
to use ... the WebDriver protocol! That's right, the Objective-C side of the XCUITest driver is
itself a WebDriver implementation, called
[WebDriverAgent](https://github.com/appium/webdriveragent).[^3]
驱动程序作者本可以选择许多非常不同的策略来建模 Node.js 端和 Objective-C 端之间的通信,但最终决定使用……
WebDriver 协议! 没错,XCUITest 驱动程序的 Objective-C 端本身就是一个 WebDriver 实现,称为 [WebDriverAgent](https://github.com/appium/webdriveragent)。[^3]
[^3]: You could in theory, therefore, point your WebDriver client straight to WebDriverAgent and
bypass Appium entirely. This is usually not convenient, however, for a few reasons:
[^3]: 因此,理论上你可以将你的 WebDriver 客户端直接指向 WebDriverAgent 并完全绕过 Appium。 然而,这通常不方便,原因如下:
- The Appium XCUITest driver builds and manages WebDriverAgent for you, which can be a pain and
involves the use of Xcode.
- The XCUITest driver does lots more than what can be done by WebDriverAgent, for example working
with simulators or devices, installing apps, and the like.
- Appium XCUITest 驱动程序会为你构建和管理 WebDriverAgent,这可能很麻烦并涉及使用 Xcode。
- XCUITest 驱动程序做的事情远不止 WebDriverAgent 所能做的,例如使用模拟器或设备、安装应用等。
The moral of the story is that driver architectures can become quite complicated and multilayered,
due to the nature of the problem we're trying to solve. It also means it can be difficult sometimes
to tell where in this chain of technologies something has gone wrong, if you run into a problem
with a particular test. With the XCUITest world again, we have something like the following set of
technologies all in play at the same time:
这个故事的寓意是,由于我们试图解决的问题的性质,驱动程序架构可能会变得相当复杂和多层。 这也意味着,如果你在特定测试中遇到问题,有时很难判断这一系列技术中的哪个环节出了问题。 再以 XCUITest 为例,我们同时使用以下一组技术:
- Your test code (in its programming language) - owned by you
- The Appium client library - owned by Appium
- The Selenium client library - owned by Selenium
- The network (local or Internet)
- The Appium server - owned by Appium
- The Appium XCUITest driver - owned by Appium
- WebDriverAgent - owned by Appium
- Xcode - owned by Apple
- XCUITest - owned by Apple
- iOS itself - owned by Apple
- macOS (where Xcode and iOS simulators run) - owned by Apple
- 你的测试代码(使用其编程语言)- 由你拥有
- Appium 客户端库 - 由 Appium 拥有
- Selenium 客户端库 - 由 Selenium 拥有
- 网络(本地或互联网)
- Appium 服务器 - 由 Appium 拥有
- Appium XCUITest 驱动程序 - 由 Appium 拥有
- WebDriverAgent - 由 Appium 拥有
- Xcode - 由 Apple 拥有
- XCUITest - 由 Apple 拥有
- iOS 本身 - 由 Apple 拥有
- macOS(运行 Xcode 和 iOS 模拟器的地方)- 由 Apple 拥有
It's a pretty deep stack!
这是一个相当深的技术栈!
## Proxy mode
## 代理模式
There's one other important architectural aspect of drivers to understand. It can be exemplified
again by the XCUITest driver. Recall that we just discussed how the two "halves" of the XCUITest
driver both speak the WebDriver protocol---the Node.js half clicks right into Appium's WebDriver
server, and the Objective-c half (WebDriverAgent) is its own WebDriver implementation.
还有一个关于驱动程序的重要架构方面需要了解。 可以再次用 XCUITest 驱动程序来举例说明。 回想一下我们刚才讨论的 XCUITest 驱动程序的两个"部分"是如何都使用 WebDriver 协议的——Node.js 部分直接连接到 Appium 的 WebDriver 服务器,而 Objective-C 部分(WebDriverAgent)本身就是一个 WebDriver 实现。
This opens up the possibility of Appium taking a shortcut in certain cases. Let's imagine that the
XCUITest driver needs to implement the `Click Element` command. The internal code of this
implementation would look something like taking the appropriate parameters and constructing an HTTP
request to the WebDriverAgent server. In this case, we're basically just reconstructing the
client's original call to the Appium server\![^4] So there's really no need to even write a function
implementing the `Click Element` command. Instead, the XCUITest driver can just let Appium know
that this command should be proxied directly to some other WebDriver server.
这为 Appium 在某些情况下走捷径开辟了可能性。
让我们想象一下 XCUITest 驱动程序需要实现 `Click Element` 命令。 这个实现的内部代码看起来就像是获取适当的参数并构造一个到 WebDriverAgent 服务器的 HTTP 请求。 在这种情况下,我们基本上只是在重建客户端对 Appium 服务器的原始调用![^4] 所以实际上没有必要编写一个函数来实现 `Click Element` 命令。 相反,XCUITest 驱动程序可以让 Appium 知道这个命令应该直接代理到其他 WebDriver 服务器。
[^4]: It's not _exactly_ the same call, because the Appium server and the WebDriverAgent server
will generate different session IDs, but these differences will be handled transparently.
[^4]: 这不是_完全_相同的调用,因为 Appium 服务器和 WebDriverAgent 服务器会生成不同的会话 ID,但这些差异会被透明地处理。
If you're not familiar with the concept of "proxying," in this case it just means that the XCUITest
driver will not be involved at all in handling the command. Instead it will merely be repackaged
and forwarded to WebDriverAgent at the protocol level, and WebDriverAgent's response will likewise
be passed back directly to the client, without any XCUITest driver code seeing it or modifying it.
如果你不熟悉"代理"的概念,在这种情况下,它只是意味着 XCUITest 驱动程序根本不会参与处理该命令。 相反,它只会在协议级别被重新打包并转发到 WebDriverAgent,WebDriverAgent 的响应同样会直接传回客户端,而不会有任何 XCUITest 驱动程序代码看到或修改它。
This architectural pattern provides a nice bonus for driver authors who choose to deal with the
WebDriver protocol everywhere, rather than constructing bespoke protocols. It also means that
Appium can create wrapper drivers for any other existing WebDriver implementation very easily. If
you look at the [Appium Safari driver](https://github.com/appium/appium-safari-driver) code, for
example, you'll see that it implements basically no standard commands, because all of these are
proxied directly to an underlying SafariDriver process.
这种架构模式为选择在各处使用 WebDriver 协议而不是构建定制协议的驱动程序作者提供了一个很好的额外好处。 这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。 例如,如果你查看 [Appium Safari 驱动程序](https://github.com/appium/appium-safari-driver)代码,你会发现它基本上没有实现任何标准命令,因为所有这些命令都直接代理到底层的 SafariDriver 进程。
It's important to understand that this proxying business is sometimes happening under the hood,
because if you're ever diving into some open source driver code trying to figure out where
a command is implemented, you might be surprised to find no implementation at all in the Node.js
driver code itself! In that case, you'll need to figure out where commands are being proxied to so
you can look there for the appropriate implementation.
了解这种代理机制有时在幕后发生是很重要的,因为如果你曾经深入研究一些开源驱动程序代码试图找出命令的实现位置,你可能会惊讶地发现 Node.js 驱动程序代码本身根本没有任何实现! 在这种情况下,你需要找出命令被代理到哪里,以便你可以在那里查找相应的实现。
OK, that's enough for this very detailed introduction to drivers!
好了,对于这个非常详细的驱动程序介绍来说,这就足够了!
+18 -21
View File
@@ -2,38 +2,35 @@
hide:
- toc
title: Appium in a Nutshell
title: Appium 概览
---
As mentioned on the main page, Appium aims to support UI automation of many _different platforms_
(mobile, web, desktop, etc.). Not only that, but it also aims to support automation code written in
_different languages_ (JS, Java, Python, etc.). Combining all of this functionality in a single
program is a very daunting, if not impossible task!
如主页所述,Appium 旨在支持_多个不同平台_(移动端、Web、桌面等)的 UI 自动化。 不仅如此,它还旨在支持用_不同语言_(JS、Java、Python 等)编写的自动化代码。 将所有这些功能整合到一个程序中是一项非常艰巨的任务,甚至可以说是不可能完成的任务!
In order to achieve this, Appium is effectively split into four parts:
为了实现这一目标,Appium 实际上被分为四个部分:
<div class="grid cards" markdown>
- :material-image-filter-center-focus-strong: **Appium Core** - defines the core APIs
- :material-car: **Drivers** - implement connectivity to specific platforms
- :octicons-code-16: **Clients** - implement Appium's API in specific languages
- :fontawesome-solid-plug: **Plugins** - change or extend Appium's core functionality
- :material-image-filter-center-focus-strong: **Appium 核心** - 定义核心 API
- :material-car: **驱动程序** - 实现与特定平台的连接
- :octicons-code-16: **客户端库** - 用特定语言实现 Appium 的 API
- :fontawesome-solid-plug: **插件** - 改变或扩展 Appium 的核心功能
</div>
Therefore, in order to start automating something with Appium, you need to:
因此,要开始使用 Appium 进行自动化,你需要:
- Install Appium itself
- Install a driver for your target platform
- Install a client library for your target programming language
- (Optional) install one or more plugins
- 安装 Appium 本身
- 为你的目标平台安装驱动程序
- 为你的目标编程语言安装客户端库
- (可选)安装一个或多个插件
These are the basics! If you are ready to jump in, proceed with the [Quickstart](../quickstart/index.md)!
这些就是基础知识! 如果你已经准备好开始,请继续阅读[快速入门](../quickstart/index.md)!
If you wish to learn more details about how it all works, see these pages for background material:
如果你想了解更多关于其工作原理的详细信息,请参阅以下页面的背景材料:
- [Appium Core](./appium.md)
- [Appium Drivers](./drivers.md)
- [Appium Clients](./clients.md)
- [Appium 核心](./appium.md)
- [Appium 驱动程序](./drivers.md)
- [Appium 客户端](./clients.md)
Finally, to learn about the origins of Appium, check out the [Appium Project History](./history.md).
最后,要了解 Appium 的起源,请查看 [Appium 项目历史](./history.md)。
@@ -135,6 +135,7 @@ appium {driver|plugin} list
| `--installed` | Only list all installed extensions | boolean |
| `--json` | Return the result in JSON format | boolean |
| `--updates` | List all extensions along with information on whether newer versions are available. Only supported for extensions installed via `npm`. | boolean |
| `--verbose` | Show additional details for each extension | boolean |
#### Example
@@ -22,6 +22,11 @@ Hub.
We are very grateful for the support of our Development Partners, who donate Appium development and
maintenance as part of their employees' core job duties!
<a href="https://lambdatest.com/?utm_source=appium.io&utm_medium=organic&utm_campaign=june_25&utm_term=sk&utm_content=webpage">
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-dark.png#only-dark" width="250" alt="LambdaTest" />
<img src="/docs/en/latest/assets/images/sponsor-logo-lambdatest-light.png#only-light" width="250" alt="LambdaTest" />
</a>
## Strategic Partners
We are very grateful for the financial and marketing support of our exclusive Strategic Partners,
+1 -3
View File
@@ -1,11 +1,9 @@
#!/usr/bin/env node
const {asyncify} = require('asyncbox');
const appium = require('./build/lib/main.js');
if (require.main === module) {
asyncify(appium.main);
appium.main();
}
module.exports = appium;
+2 -2
View File
@@ -23,7 +23,7 @@ import type {
BiDiResultData
} from '@appium/types';
type ExtensionPlugin = Plugin & ExtensionCore
type ExtensionPlugin = Plugin & ExtensionCore;
type AnyDriver = ExternalDriver | AppiumDriver;
type SendData = (data: string | Buffer) => Promise<void>;
type LogSocketError = (err: Error) => void;
@@ -448,7 +448,7 @@ function initBidiEventListeners(
// sure the client is subscribed and then pass it on
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
const eventListenerFactory = (extType: 'driver'|'plugin', ext: ExtensionCore) => {
const eventListenerFactory = (extType: 'driver' | 'plugin', ext: ExtensionCore) => {
const eventListener = async ({context, method, params = {}}) => {
// if the driver didn't specify a context, use the empty context
if (!context) {
+279 -103
View File
@@ -1,6 +1,6 @@
import B from 'bluebird';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import {npm, util, env, console, fs, system} from '@appium/support';
import {spinWith, RingBuffer} from './utils';
import {
@@ -12,14 +12,15 @@ import {
} from '../extension/extension-config';
import {SubProcess} from 'teen_process';
import {packageDidChange} from '../extension/package-changed';
import {spawn} from 'child_process';
import {spawn} from 'node:child_process';
import {inspect} from 'node:util';
import {pathToFileURL} from 'url';
import {pathToFileURL} from 'node:url';
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
import {getAppiumModuleRoot, npmPackage} from '../utils';
import * as semver from 'semver';
const UPDATE_ALL = 'installed';
const MAX_CONCURRENT_REPO_FETCHES = 5;
class NotUpdatableError extends Error {}
class NoUpdatesAvailableError extends Error {}
@@ -96,6 +97,7 @@ class ExtensionCliCommand {
* For TS to understand that a function throws an exception, it must actually throw an exception--
* in other words, _calling_ a function which is guaranteed to throw an exception is not enough--
* nor is something like `@returns {never}` which does not imply a thrown exception.
*
* @param {string} message
* @protected
* @throws {Error}
@@ -121,18 +123,45 @@ class ExtensionCliCommand {
/**
* List extensions
*
* @template {ExtensionType} ExtType
* @param {ListOptions} opts
* @return {Promise<ExtensionList<ExtType>>} map of extension names to extension data
*/
async list({showInstalled, showUpdates, verbose = false}) {
let lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
if (verbose) {
lsMsg += ' (verbose mode)';
const listData = this._buildListData(showInstalled);
const lsMsg =
`Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s` +
(verbose ? ' (verbose mode)' : ' (rerun with --verbose for more info)');
await this._checkForUpdates(listData, showUpdates, lsMsg);
if (this.isJsonOutput) {
await this._addRepositoryUrlsToListData(listData);
return listData;
}
if (verbose) {
await this._addRepositoryUrlsToListData(listData);
this.log.log(inspect(listData, {colors: true, depth: null}));
return listData;
}
return await this._displayNormalListOutput(listData, showUpdates);
}
/**
* Build the initial list data structure from installed and known extensions
*
* @template {ExtensionType} ExtType
* @param {boolean} showInstalled
* @returns {ExtensionList<ExtType>}
* @private
*/
_buildListData(showInstalled) {
const installedNames = Object.keys(this.config.installedExtensions);
const knownNames = Object.keys(this.knownExtensions);
const listData = [...installedNames, ...knownNames].reduce((acc, name) => {
return [...installedNames, ...knownNames].reduce((acc, name) => {
if (!acc[name]) {
if (installedNames.includes(name)) {
acc[name] = {
@@ -148,101 +177,235 @@ class ExtensionCliCommand {
}
return acc;
}, /** @type {ExtensionList<ExtType>} */ ({}));
}
// if we want to show whether updates are available, put that behind a spinner
/**
* Check for available updates for installed extensions
*
* @template {ExtensionType} ExtType
* @param {ExtensionList<ExtType>} listData
* @param {boolean} showUpdates
* @param {string} lsMsg
* @returns {Promise<void>}
* @private
*/
async _checkForUpdates(listData, showUpdates, lsMsg) {
await spinWith(this.isJsonOutput, lsMsg, async () => {
// We'd like to still show lsMsg even if showUpdates is false
if (!showUpdates) {
return;
}
for (const [ext, data] of _.toPairs(listData)) {
if (!data.installed || data.installType !== INSTALL_TYPE_NPM) {
// don't need to check for updates on exts that aren't installed
// also don't need to check for updates on non-npm exts
continue;
}
try {
const updates = await this.checkForExtensionUpdate(ext);
data.updateVersion = updates.safeUpdate;
data.unsafeUpdateVersion = updates.unsafeUpdate;
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
} catch (e) {
data.updateError = e.message;
}
}
});
/**
* Type guard to narrow "installed" extensions, which have more data
* @param {any} data
* @returns {data is InstalledExtensionListData<ExtType>}
*/
const extIsInstalled = (data) => Boolean(data.installed);
// Filter to only extensions that need update checks (installed npm packages)
const extensionsToCheck = _.toPairs(listData).filter(
([, data]) => data.installed && data.installType === INSTALL_TYPE_NPM
);
// if we're just getting the data, short circuit return here since we don't need to do any
// formatting logic
if (this.isJsonOutput) {
return listData;
}
if (verbose) {
this.log.log(inspect(listData, {colors: true, depth: null}));
return listData;
}
for (const [name, data] of _.toPairs(listData)) {
let installTxt = ' [not installed]'.grey;
let updateTxt = '';
let upToDateTxt = '';
let unsafeUpdateTxt = '';
if (extIsInstalled(data)) {
const {
installType,
installSpec,
updateVersion,
unsafeUpdateVersion,
version,
upToDate,
updateError,
} = data;
let typeTxt;
switch (installType) {
case INSTALL_TYPE_GIT:
case INSTALL_TYPE_GITHUB:
typeTxt = `(cloned from ${installSpec})`.yellow;
break;
case INSTALL_TYPE_LOCAL:
typeTxt = `(linked from ${installSpec})`.magenta;
break;
case INSTALL_TYPE_DEV:
typeTxt = '(dev mode)';
break;
default:
typeTxt = '(npm)';
}
installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
if (showUpdates) {
if (updateError) {
updateTxt = ` [Cannot check for updates: ${updateError}]`.red;
} else {
if (updateVersion) {
updateTxt = ` [${updateVersion} available]`.magenta;
}
if (upToDate) {
upToDateTxt = ` [Up to date]`.green;
}
if (unsafeUpdateVersion) {
unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
}
await B.map(
extensionsToCheck,
async ([ext, data]) => {
try {
const updates = await this.checkForExtensionUpdate(ext);
data.updateVersion = updates.safeUpdate;
data.unsafeUpdateVersion = updates.unsafeUpdate;
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
} catch (e) {
data.updateError = e.message;
}
}
}
},
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
);
});
}
this.log.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
/**
* Add repository URLs to list data for all extensions
*
* @template {ExtensionType} ExtType
* @param {ExtensionList<ExtType>} listData
* @returns {Promise<void>}
* @private
*/
async _addRepositoryUrlsToListData(listData) {
await spinWith(this.isJsonOutput, 'Fetching repository information', async () => {
await B.map(
_.values(listData),
async (data) => {
const repoUrl = await this._getRepositoryUrl(data);
if (repoUrl) {
data.repositoryUrl = repoUrl;
}
},
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
);
});
}
/**
* Display normal formatted output
*
* @template {ExtensionType} ExtType
* @param {ExtensionList<ExtType>} listData
* @param {boolean} showUpdates
* @returns {Promise<ExtensionList<ExtType>>}
* @private
*/
async _displayNormalListOutput(listData, showUpdates) {
for (const [name, data] of _.toPairs(listData)) {
const line = await this._formatExtensionLine(name, data, showUpdates);
this.log.log(line);
}
return listData;
}
/**
* Format a single extension line for display
*
* @template {ExtensionType} ExtType
* @param {string} name
* @param {ExtensionListData<ExtType>} data
* @param {boolean} showUpdates
* @returns {Promise<string>}
* @private
*/
async _formatExtensionLine(name, data, showUpdates) {
if (data.installed) {
const installTxt = this._formatInstallText(/** @type {InstalledExtensionListData<ExtType>} */ (data));
const updateTxt = showUpdates ? this._formatUpdateText(/** @type {InstalledExtensionListData<ExtType>} */ (data)) : '';
return `- ${name.yellow}${installTxt}${updateTxt}`;
}
const installTxt = ' [not installed]'.grey;
return `- ${name.yellow}${installTxt}`;
}
/**
* Format installation status text
*
* @template {ExtensionType} ExtType
* @param {InstalledExtensionListData<ExtType>} data
* @returns {string}
* @private
*/
_formatInstallText(data) {
const {installType, installSpec, version} = data;
let typeTxt;
switch (installType) {
case INSTALL_TYPE_GIT:
case INSTALL_TYPE_GITHUB:
typeTxt = `(cloned from ${installSpec})`.yellow;
break;
case INSTALL_TYPE_LOCAL:
typeTxt = `(linked from ${installSpec})`.magenta;
break;
case INSTALL_TYPE_DEV:
typeTxt = '(dev mode)';
break;
default:
typeTxt = '(npm)';
}
return `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
}
/**
* Format update information text
*
* @template {ExtensionType} ExtType
* @param {InstalledExtensionListData<ExtType>} data
* @returns {string}
* @private
*/
_formatUpdateText(data) {
const {updateVersion, unsafeUpdateVersion, upToDate, updateError} = data;
if (updateError) {
return ` [Cannot check for updates: ${updateError}]`.red;
}
let txt = '';
if (updateVersion) {
txt += ` [${updateVersion} available]`.magenta;
}
if (upToDate) {
txt += ` [Up to date]`.green;
}
if (unsafeUpdateVersion) {
txt += ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
}
return txt;
}
/**
* Get repository URL from package data
*
* @template {ExtensionType} ExtType
* @param {ExtensionListData<ExtType>} data
* @returns {Promise<string|null>}
* @private
*/
async _getRepositoryUrl(data) {
if (data.installed && data.installPath) {
return await this._getRepositoryUrlFromInstalled(
/** @type {InstalledExtensionListData<ExtType>} */ (data)
);
}
if (data.pkgName && !data.installed) {
return await this._getRepositoryUrlFromNpm(data.pkgName);
}
return null;
}
/**
* Get repository URL from installed extension's package.json
*
* @template {ExtensionType} ExtType
* @param {InstalledExtensionListData<ExtType>} data
* @returns {Promise<string|null>}
* @private
*/
async _getRepositoryUrlFromInstalled(data) {
try {
const pkgJsonPath = path.join(data.installPath, 'package.json');
if (await fs.exists(pkgJsonPath)) {
const pkg = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
if (pkg.repository) {
if (typeof pkg.repository === 'string') {
return pkg.repository;
}
if (pkg.repository.url) {
return pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
}
}
}
} catch {
// Ignore errors reading package.json
}
return null;
}
/**
* Get repository URL from npm for a package name
*
* @param {string} pkgName
* @returns {Promise<string|null>}
* @private
*/
async _getRepositoryUrlFromNpm(pkgName) {
try {
const repoInfo = await npm.getPackageInfo(pkgName, ['repository']);
// When requesting only 'repository', npm.getPackageInfo returns the repository object directly
if (repoInfo) {
if (typeof repoInfo === 'string') {
return repoInfo;
}
if (repoInfo.url) {
return repoInfo.url.replace(/^git\+/, '').replace(/\.git$/, '');
}
}
} catch {
// Ignore errors fetching from npm
}
return null;
}
/**
* Checks whether the given extension is compatible with the currently installed server
*
@@ -454,18 +617,6 @@ class ExtensionCliCommand {
return {pkg, installPath};
});
/** @type {Promise<void>[]} */
const symlinkInjectionPromises = _.uniq([
...Object.values(this.config.installedExtensions).map((ext) => ext.installPath),
installPath,
]).map((instPath) => injectAppiumSymlink.bind(this)(path.join(instPath, 'node_modules')));
// After the extension is installed, we try to inject the appium module symlink
// into the extension's node_modules folder if it is not there yet.
// We also inject the symlink into other installed extensions' node_modules folders
// as these might be cleaned up unexpectedly by npm
// (see https://github.com/appium/python-client/pull/1177#issuecomment-3419826643).
await Promise.all(symlinkInjectionPromises);
return this.getInstallationReceipt({
pkg,
installPath,
@@ -971,11 +1122,35 @@ class ExtensionCliCommand {
* This is needed to ensure proper module resolution for installed extensions,
* especially ESM ones.
*
* @this {ExtensionCliCommand}
* @param {ExtensionConfig<ExtensionType>} driverConfig
* @param {ExtensionConfig<ExtensionType>} pluginConfig
* @param {import('@appium/types').AppiumLogger} logger
*/
export async function injectAppiumSymlinks(driverConfig, pluginConfig, logger) {
const installPaths = _.compact([
...Object.values(driverConfig.installedExtensions || {}),
...Object.values(pluginConfig.installedExtensions || {})
].filter((details) => details.installType === INSTALL_TYPE_NPM)
.map((details) => details.installPath));
// After the extension is installed, we try to inject the appium module symlink
// into the extension's node_modules folder if it is not there yet.
// We also inject the symlink into other installed extensions' node_modules folders
// as these might be cleaned up unexpectedly by npm
// (see https://github.com/appium/python-client/pull/1177#issuecomment-3419826643).
await Promise.all(
installPaths.map((installPath) => injectAppiumSymlink(path.join(installPath, 'node_modules'), logger))
);
}
/**
* This is needed to ensure proper module resolution for installed extensions,
* especially ESM ones.
*
* @param {string} dstFolder The destination folder where the symlink should be created
* @param {import('@appium/types').AppiumLogger} logger
* @returns {Promise<void>}
*/
async function injectAppiumSymlink(dstFolder) {
async function injectAppiumSymlink(dstFolder, logger) {
let appiumModuleRoot;
try {
appiumModuleRoot = getAppiumModuleRoot();
@@ -985,7 +1160,7 @@ async function injectAppiumSymlink(dstFolder) {
}
} catch (error) {
// This error is not fatal, we may still doing just fine if the module being loaded is a CJS one
this.log.info(
logger.info(
`Cannot create a symlink to the appium module '${appiumModuleRoot}' in '${dstFolder}'. ` +
`Original error: ${error.message}`
);
@@ -1013,6 +1188,7 @@ export {ExtensionCliCommand as ExtensionCommand};
* @property {string|null} unsafeUpdateVersion - Same as above, but a major version bump
* @property {string} [updateError] - Update check error message (if present)
* @property {boolean} [devMode] - If Appium is run from an extension's working copy
* @property {string} [repositoryUrl] - Repository URL for the extension (if available)
*/
/**
+1 -1
View File
@@ -1,7 +1,7 @@
import {fs} from '@appium/support';
import {ArgumentParser} from 'argparse';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import {
DRIVER_TYPE,
EXT_SUBCOMMAND_DOCTOR,
+1 -1
View File
@@ -1,4 +1,4 @@
import path from 'path';
import path from 'node:path';
/**
* The name of the extension type for drivers
@@ -1,7 +1,7 @@
import {util, fs, system} from '@appium/support';
import B from 'bluebird';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import resolveFrom from 'resolve-from';
import {satisfies} from 'semver';
import {commandClasses} from '../cli/extension';
@@ -12,7 +12,7 @@ import {
isAllowedSchemaFileExtension,
registerSchema,
} from '../schema/schema';
import { pathToFileURL } from 'url';
import { pathToFileURL } from 'node:url';
const DEFAULT_ENTRY_POINT = 'index.js';
/**
+1 -1
View File
@@ -5,7 +5,7 @@
import B from 'bluebird';
import {env, fs} from '@appium/support';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import * as YAML from 'yaml';
import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
import {INSTALL_TYPE_NPM, INSTALL_TYPE_DEV} from './extension-config';
@@ -1,6 +1,6 @@
import {fs} from '@appium/support';
import {isPackageChanged} from 'package-changed';
import path from 'path';
import path from 'node:path';
import {PKG_HASHFILE_RELATIVE_PATH} from '../constants';
import log from '../logger';
+10 -2
View File
@@ -9,7 +9,6 @@ import {
normalizeBasePath,
} from '@appium/base-driver';
import {util, env} from '@appium/support';
import {asyncify} from 'asyncbox';
import _ from 'lodash';
import {AppiumDriver} from './appium';
import {runExtensionCommand} from './cli/extension';
@@ -43,6 +42,7 @@ import {
isBroadcastIp,
} from './utils';
import net from 'node:net';
import { injectAppiumSymlinks } from './cli/extension-command';
const {resolveAppiumHome} = env;
/*
@@ -314,6 +314,14 @@ async function init(args) {
if (isPluginCommandArgs(preConfigArgs)) {
await runExtensionCommand(preConfigArgs, pluginConfig);
}
if (isDriverCommandArgs(preConfigArgs) || isPluginCommandArgs(preConfigArgs)) {
// @ts-ignore The linter suggest using dot
const cmd = preConfigArgs.driverCommand || preConfigArgs.pluginCommand;
if (cmd === 'install') {
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
}
}
}
return /** @type {InitResult<Cmd>} */ ({});
}
@@ -481,7 +489,7 @@ async function main(args) {
// (more specifically, `build/lib/main.js`)
// the executable is now `../index.js`, so that module will typically be `require.main`.
if (require.main === module) {
asyncify(main);
main();
}
// everything below here is intended to be a public API.
@@ -1,5 +1,5 @@
import {ArgumentTypeError} from 'argparse';
import {readFileSync, existsSync} from 'fs';
import {readFileSync, existsSync} from 'node:fs';
/**
* This module provides custom keywords for Appium schemas, as well as
+1 -1
View File
@@ -1,7 +1,7 @@
import {Ajv} from 'ajv';
import addFormats from 'ajv-formats';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
import {AppiumConfigJsonSchema} from '@appium/schema';
import {APPIUM_CONFIG_SCHEMA_ID, ArgSpec, SERVER_PROP_NAME} from './arg-spec';
+3 -3
View File
@@ -6,9 +6,9 @@ import {
errors,
isW3cCaps,
} from '@appium/base-driver';
import {inspect as dump} from 'util';
import {inspect as dump} from 'node:util';
import {node, fs} from '@appium/support';
import path from 'path';
import path from 'node:path';
import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE, SETUP_SUBCOMMAND} from './constants';
import os from 'node:os';
@@ -229,7 +229,7 @@ export function adjustNodePath() {
// See https://gist.github.com/branneman/8048520#7-the-hack
// @ts-ignore see above comment
require('module').Module._initPaths();
require('node:module').Module._initPaths();
return true;
} catch {
return false;
+17 -19
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "3.1.2",
"version": "3.2.0",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -40,7 +40,8 @@
"scripts/autoinstall-extensions.js",
"types",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo"
"!build/tsconfig.tsbuildinfo",
"!build/test"
],
"scripts": {
"build:docs": "node docs/scripts/build-docs.js",
@@ -60,34 +61,31 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^10.1.2",
"@appium/base-plugin": "^3.0.5",
"@appium/docutils": "^2.2.0",
"@appium/logger": "^2.0.3",
"@appium/schema": "^1.0.0",
"@appium/support": "^7.0.4",
"@appium/types": "^1.1.2",
"@appium/base-driver": "^10.2.0",
"@appium/base-plugin": "^3.0.6",
"@appium/docutils": "^2.2.1",
"@appium/logger": "^2.0.4",
"@appium/schema": "^1.0.1",
"@appium/support": "^7.0.5",
"@appium/types": "^1.2.0",
"@sidvind/better-ajv-errors": "4.0.1",
"ajv": "8.17.1",
"ajv-formats": "3.0.1",
"argparse": "2.0.1",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.13.2",
"axios": "1.13.3",
"bluebird": "3.7.2",
"lilconfig": "3.1.3",
"lodash": "4.17.21",
"lru-cache": "11.2.4",
"lodash": "4.17.23",
"lru-cache": "11.2.5",
"ora": "5.4.1",
"package-changed": "3.0.0",
"resolve-from": "5.0.0",
"semver": "7.7.3",
"source-map-support": "0.5.21",
"teen_process": "3.0.4",
"type-fest": "5.3.0",
"winston": "3.18.3",
"wrap-ansi": "7.0.0",
"ws": "8.18.3",
"teen_process": "4.0.8",
"type-fest": "5.4.1",
"winston": "3.19.0",
"ws": "8.19.0",
"yaml": "2.8.2"
},
"engines": {
@@ -1,5 +1,5 @@
{
"devDependencies": {
"webdriverio": "9.21.0"
"webdriverio": "9.23.2"
}
}
@@ -38,12 +38,6 @@ let PLUGIN_TYPE;
/** @type {typeof import('../lib/extension').loadExtensions} */
let loadExtensions;
const _ = require('lodash');
const wrap = _.partial(
require('wrap-ansi'),
_,
process.stderr.columns ?? process.stdout.columns ?? 80
);
const ora = require('ora');
/** @type {typeof import('@appium/support').env} */
@@ -54,7 +48,7 @@ let util;
let logger;
function log(message) {
console.error(wrap(`[Appium] ${message}`));
console.error(`[Appium] ${message}`);
}
/**
@@ -118,9 +112,12 @@ async function main() {
}).start();
if (!driverEnv && !pluginEnv) {
spinner.succeed(
wrap(`No drivers or plugins to automatically install.
If desired, provide arguments with comma-separated values "--drivers=<known_driver>[,known_driver...]" and/or "--plugins=<known_plugin>[,known_plugin...]" to the "npm install appium" command. The specified extensions will be installed automatically with Appium. Note: to see the list of known extensions, run "appium <driver|plugin> list".`)
spinner.succeed('No drivers or plugins to automatically install.');
log(
'If desired, provide the argument "--drivers=<driver_name>[,<driver_name>...]" and/or ' +
'"--plugins=<plugin_name>[,<plugin_name>...]" to the "npm install appium" command. ' +
'The specified extensions will be installed automatically alongside Appium. ' +
'For a list of known extensions, run "appium <driver|plugin> list".'
);
return;
}
@@ -1,7 +1,7 @@
#!/usr/bin/env node
const path = require('path');
const childProcess = require('child_process');
const path = require('node:path');
const childProcess = require('node:child_process');
const _ = require('lodash');
const res = JSON.parse(
@@ -2,13 +2,11 @@
// @ts-check
const path = require('path');
const path = require('node:path');
const yaml = require('yaml');
const {fs, util, logger} = require('@appium/support');
const Handlebars = require('handlebars');
const _ = require('lodash');
const {asyncify} = require('asyncbox');
const url = require('url');
const log = logger.getLogger('YamlParser');
@@ -192,7 +190,7 @@ Handlebars.registerHelper('if_eq', function ifEq(a, b, opts) {
});
function getBaseHostname(fullUrl) {
const baseUrl = url.parse(fullUrl);
const baseUrl = new URL(fullUrl);
return baseUrl.hostname;
}
@@ -418,4 +416,4 @@ async function main() {
await generateCommandIndex();
}
asyncify(main);
main();
@@ -2,7 +2,7 @@
import {exec} from 'teen_process';
import {fs, system, tempDir, util} from '@appium/support';
import path from 'path';
import path from 'node:path';
import resolveFrom from 'resolve-from';
import {
DRIVER_TYPE,
@@ -98,10 +98,12 @@ describe('Driver CLI', function () {
it('should list available drivers in json format', async function () {
const driverData = await runList();
for (const d of Object.keys(KNOWN_DRIVERS)) {
driverData[d].should.eql({
installed: false,
pkgName: KNOWN_DRIVERS[d],
});
driverData[d].should.have.property('installed', false);
driverData[d].should.have.property('pkgName', KNOWN_DRIVERS[d]);
// repositoryUrl may be present if available
if (driverData[d].repositoryUrl) {
driverData[d].repositoryUrl.should.be.a('string');
}
}
});
@@ -179,6 +181,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed']);
// @ts-ignore
delete list.uiautomator2.installed;
// @ts-ignore
delete list.uiautomator2.repositoryUrl;
list.should.eql(ret);
});
@@ -190,6 +194,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
// @ts-ignore
delete list.fake.repositoryUrl;
list.should.eql(ret);
});
@@ -223,6 +229,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
// @ts-ignore
delete list.fake.repositoryUrl;
list.should.eql(ret);
});
@@ -245,6 +253,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
// @ts-ignore
delete list.fake.repositoryUrl;
list.should.eql(ret);
});
@@ -262,6 +272,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed', '--json']);
// @ts-ignore
delete list.fake.installed;
// @ts-ignore
delete list.fake.repositoryUrl;
list.should.eql(ret);
});
@@ -284,6 +296,8 @@ describe('Driver CLI', function () {
const list = await runList(['--installed']);
// @ts-ignore
delete list.fake.installed;
// @ts-ignore
delete list.fake.repositoryUrl;
list.should.eql(ret);
});
+9 -5
View File
@@ -1,8 +1,8 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getGitRev, getBuildInfo, updateBuildInfo, APPIUM_VER} from '../../lib/config';
import axios from 'axios';
import * as teenProcess from 'teen_process';
import { expect } from 'chai';
describe('Config', function () {
@@ -14,6 +14,7 @@ describe('Config', function () {
});
afterEach(function () {
sandbox.verify();
sandbox.restore();
});
@@ -38,8 +39,10 @@ describe('Config', function () {
async function verifyBuildInfoUpdate(useLocalGit, {sha, built} = {}) {
const buildInfo = getBuildInfo();
const innerExecStub = sandbox.stub().throws();
if (!useLocalGit) {
mockTp.expects('exec').atLeast(1).throws();
sandbox.stub(teenProcess, 'exec').get(() => innerExecStub);
}
buildInfo['git-sha'] = undefined;
buildInfo.built = undefined;
@@ -56,17 +59,18 @@ describe('Config', function () {
should.exist(buildInfo.built);
}
should.exist(buildInfo.version);
if (!useLocalGit) {
expect(innerExecStub.callCount).to.be.at.least(1);
}
}
let getStub;
let mockTp;
beforeEach(function () {
getStub = sandbox.stub(axios, 'get');
mockTp = sandbox.mock(teenProcess);
});
afterEach(function () {
getStub.restore();
mockTp.restore();
});
it('should get a configuration object if the local git metadata is present', async function () {
@@ -6,7 +6,6 @@ import {fs, tempDir} from '@appium/support';
import axios from 'axios';
import B from 'bluebird';
import _ from 'lodash';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {remote as wdio} from 'webdriverio';
import {runExtensionCommand} from '../../lib/cli/extension';
+1 -1
View File
@@ -6,7 +6,7 @@
import {console, fs} from '@appium/support';
import '@colors/colors';
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import {exec} from 'teen_process';
import {APPIUM_ROOT, resolveFixture} from '../helpers';
+1 -1
View File
@@ -1,6 +1,6 @@
// @ts-check
import {fs, tempDir} from '@appium/support';
import path from 'path';
import path from 'node:path';
import {DRIVER_TYPE} from '../../lib/constants';
import {resolveFixture} from '../helpers';
import {installLocalExtension, runAppium} from './e2e-helpers';
+1 -1
View File
@@ -1,7 +1,7 @@
// @ts-check
import getPort from 'get-port';
import path from 'path';
import path from 'node:path';
import rewiremock, {addPlugin, overrideEntryPoint, plugins} from 'rewiremock';
import {insertAppiumPrefixes} from '../lib/utils';
@@ -6,7 +6,6 @@ import {BaseDriver} from '@appium/base-driver';
import {FakeDriver} from '@appium/fake-driver';
import {sleep} from 'asyncbox';
import _ from 'lodash';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {finalizeSchema, registerSchema, resetSchema} from '../../lib/schema/schema';
import {insertAppiumPrefixes, removeAppiumPrefixes} from '../../lib/utils';
@@ -2,7 +2,6 @@ import {tempDir, fs, npm} from '@appium/support';
import {loadExtensions} from '../../../lib/extension';
import {Manifest} from '../../../lib/extension/manifest';
import DriverCommand from '../../../lib/cli/driver-command';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
describe('DriverCommand', function () {
@@ -1,4 +1,3 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {finalizeSchema, resetSchema, SchemaFinalizationError} from '../../../lib/schema/schema';
import {rewiremock} from '../../helpers';
@@ -1,4 +1,3 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {system} from '@appium/support';
import {getPresetDrivers} from '../../../lib/cli/setup-command';
@@ -1,7 +1,6 @@
// @ts-check
import fs from 'fs';
// eslint-disable-next-line import/named
import fs from 'node:fs';
import {createSandbox} from 'sinon';
import * as YAML from 'yaml';
import * as schema from '../../lib/schema/schema';
-1
View File
@@ -1,7 +1,6 @@
// @ts-check
import _ from 'lodash';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getParser} from '../../lib/cli/parser';
import {
@@ -1,6 +1,6 @@
// @ts-check
import {promises as fs} from 'fs';
import {promises as fs} from 'node:fs';
import {Manifest} from '../../../lib/extension/manifest';
import {resetSchema} from '../../../lib/schema';
import {resolveFixture, rewiremock} from '../../helpers';
@@ -1,33 +1,32 @@
// @ts-check
import {DriverConfig} from '../../../lib/extension/driver-config';
import {ExtensionCommand} from '../../../lib/cli/extension-command';
import {ExtensionCommand, injectAppiumSymlinks} from '../../../lib/cli/extension-command';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import {FAKE_DRIVER_DIR} from '../../helpers';
import {Manifest} from '../../../lib/extension/manifest';
import {fs, system} from '@appium/support';
import * as utils from '../../../lib/utils';
import {expect} from 'chai';
import * as chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
/**
* Relative path from actual `package.json` of `FakeDriver` for the `fake-stdin` script
*/
const FAKE_STDIN_SCRIPT = require(`${FAKE_DRIVER_DIR}/package.json`).appium.scripts['fake-stdin'];
/** @type {sinon.SinonSandbox} */
let sandbox;
chai.use(chaiAsPromised);
chai.use(sinonChai);
describe('ExtensionCommand', function () {
describe('method', function () {
/** @type {ExtensionCommand} */
/** @type {ExtensionCommand<any>} */
let ec;
/** @type {sinon.SinonSandbox} */
let sandbox;
let expect;
before(async function () {
const chai = await import('chai');
const chaiAsPromised = await import('chai-as-promised');
chai.use(chaiAsPromised.default);
chai.should();
expect = chai.expect;
});
beforeEach(function () {
sandbox = sinon.createSandbox();
const driverConfig = DriverConfig.create(sandbox.createStubInstance(Manifest));
@@ -35,6 +34,7 @@ describe('ExtensionCommand', function () {
});
afterEach(function () {
sandbox.verify();
sandbox.restore();
});
@@ -46,18 +46,19 @@ describe('ExtensionCommand', function () {
// `Promise` + `ChildProcess`, but I didn't want to add the dep.
it('should respond to stdin', function (done) {
// we have to fake writing to STDIN because this is an automated test, after all.
const proc = ec
const proc = /** @type {import('child_process').ChildProcess} */ (ec
._runUnbuffered(FAKE_DRIVER_DIR, FAKE_STDIN_SCRIPT, [], {
stdio: ['pipe', 'inherit', 'inherit'],
})
.once('exit', (code) => {
try {
expect(code).to.equal(0);
done();
} catch (err) {
done(err);
}
});
}));
proc.once('exit', (/** @type {number | null} */ code) => {
try {
expect(code).to.equal(0);
done();
} catch (err) {
done(err);
}
});
setTimeout(() => {
// TS does not understand that `proc.stdin` is not `null`, because it is only a `Writable`
@@ -69,4 +70,349 @@ describe('ExtensionCommand', function () {
});
});
});
describe('injectAppiumSymlinks', function () {
/** @type {sinon.SinonStub} */
let fsExistsStub;
/** @type {sinon.SinonStub} */
let fsSymlinkStub;
/** @type {sinon.SinonStub} */
let getAppiumModuleRootStub;
/** @type {sinon.SinonStub} */
let isWindowsStub;
/** @type {import('@appium/types').AppiumLogger} */
let logger;
beforeEach(function () {
sandbox = sinon.createSandbox();
fsExistsStub = sandbox.stub(fs, 'exists');
fsSymlinkStub = sandbox.stub(fs, 'symlink');
getAppiumModuleRootStub = sandbox.stub(utils, 'getAppiumModuleRoot');
isWindowsStub = sandbox.stub(system, 'isWindows');
logger = /** @type {any} */ ({
info: sandbox.stub(),
warn: sandbox.stub(),
error: sandbox.stub(),
debug: sandbox.stub(),
});
getAppiumModuleRootStub.returns('/path/to/appium');
isWindowsStub.returns(false);
});
afterEach(function () {
sandbox.verify();
sandbox.restore();
});
describe('when there are no installed extensions', function () {
it('should not create any symlinks', async function () {
const driverConfig = {installedExtensions: {}};
const pluginConfig = {installedExtensions: {}};
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
});
describe('when there are npm-installed drivers', function () {
it('should create symlinks for npm-installed drivers', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules').resolves(true);
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsExistsStub).to.have.been.calledWith('/path/to/driver-for-test/node_modules');
expect(fsExistsStub).to.have.been.calledWith('/path/to/driver-for-test/node_modules/appium');
expect(fsSymlinkStub).to.have.been.calledOnce;
expect(fsSymlinkStub).to.have.been.calledWith(
'/path/to/appium',
'/path/to/driver-for-test/node_modules/appium',
'dir'
);
});
it('should create junction symlinks on Windows', async function () {
isWindowsStub.returns(true);
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules').resolves(true);
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.have.been.calledWith(
'/path/to/appium',
'/path/to/driver-for-test/node_modules/appium',
'junction'
);
});
it('should not create symlinks if node_modules directory does not exist', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
it('should not create symlinks if symlink already exists', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.resolves(true);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
});
describe('when there are npm-installed plugins', function () {
it('should create symlinks for npm-installed plugins', async function () {
const driverConfig = {installedExtensions: {}};
const pluginConfig = {
installedExtensions: {
'plugin-for-test': {
installType: 'npm',
installPath: '/path/to/plugin-for-test',
},
},
};
fsExistsStub.withArgs('/path/to/plugin-for-test/node_modules').resolves(true);
fsExistsStub.withArgs('/path/to/plugin-for-test/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.have.been.calledOnce;
expect(fsSymlinkStub).to.have.been.calledWith(
'/path/to/appium',
'/path/to/plugin-for-test/node_modules/appium',
'dir'
);
});
});
describe('when there are both drivers and plugins', function () {
it('should create symlinks for all npm-installed extensions', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {
installedExtensions: {
'plugin-for-test': {
installType: 'npm',
installPath: '/path/to/plugin-for-test',
},
},
};
fsExistsStub.resolves(true);
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules/appium').resolves(false);
fsExistsStub.withArgs('/path/to/plugin-for-test/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.have.been.calledTwice;
});
it('should not create symlinks for invalid format - no installPath', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
},
},
};
const pluginConfig = {
installedExtensions: {
'plugin-for-test': {
installType: 'npm',
},
},
};
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
});
describe('when there are non-npm installed extensions', function () {
for (const installType of ['git', 'local', 'github']) {
it(`should skip ${installType}-installed extensions`, async function () {
const driverConfig = {
installedExtensions: {
[`${installType}-driver`]: {
installType,
installPath: `/path/to/${installType}-driver`,
},
},
};
const pluginConfig = {installedExtensions: {}};
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
}
it('should only create symlinks for npm-installed extensions when mixed', async function () {
const driverConfig = {
installedExtensions: {
'npm-driver': {
installType: 'npm',
installPath: '/path/to/npm-driver',
},
'git-driver': {
installType: 'git',
installPath: '/path/to/git-driver',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.resolves(true);
fsExistsStub.withArgs('/path/to/npm-driver/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.have.been.calledOnce;
expect(fsSymlinkStub).to.have.been.calledWith(
'/path/to/appium',
'/path/to/npm-driver/node_modules/appium',
'dir'
);
});
});
describe('error handling', function () {
it('should log info message when symlink creation fails', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
fsExistsStub.resolves(true);
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules/appium').resolves(false);
fsSymlinkStub.rejects(new Error('Permission denied'));
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(logger.info).to.have.been.calledOnce;
// @ts-ignore
expect(logger.info.args[0][0]).to.match(/Cannot create a symlink/);
// @ts-ignore
expect(logger.info.args[0][0]).to.match(/Permission denied/);
});
it('should not throw when getAppiumModuleRoot fails', async function () {
const driverConfig = {
installedExtensions: {
'driver-for-test': {
installType: 'npm',
installPath: '/path/to/driver-for-test',
},
},
};
const pluginConfig = {installedExtensions: {}};
getAppiumModuleRootStub.throws(new Error('Module not found'));
fsExistsStub.resolves(true);
fsExistsStub.withArgs('/path/to/driver-for-test/node_modules/appium').resolves(false);
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(logger.info).to.have.been.calledOnce;
// @ts-ignore
expect(logger.info.args[0][0]).to.match(/Cannot create a symlink/);
});
});
describe('with null or undefined configs', function () {
it('should handle null installedExtensions', async function () {
const driverConfig = {installedExtensions: null};
const pluginConfig = {installedExtensions: null};
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
it('should handle undefined installedExtensions', async function () {
const driverConfig = {};
const pluginConfig = {};
// @ts-expect-error - partial config for testing
await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
expect(fsSymlinkStub).to.not.have.been.called;
});
});
});
});
@@ -1,5 +1,5 @@
import {DRIVER_TYPE} from '../../../lib/constants';
import path from 'path';
import path from 'node:path';
import {version as APPIUM_VER} from '../../../package.json';
import {FAKE_DRIVER_DIR, PROJECT_ROOT, rewiremock} from '../../helpers';
import {initMocks} from './mocks';
@@ -1,11 +1,11 @@
// @ts-check
import B from 'bluebird';
import {promises as fs} from 'fs';
import {promises as fs} from 'node:fs';
import {DRIVER_TYPE, PLUGIN_TYPE} from '../../../lib/constants';
import {resolveFixture, rewiremock} from '../../helpers';
import {initMocks} from './mocks';
import {version as APPIUM_VER} from '../../../package.json';
import EventEmitter from 'events';
import EventEmitter from 'node:events';
describe('Manifest', function () {
/**
+2 -3
View File
@@ -5,9 +5,8 @@
* A collection of mocks reused across unit tests.
*/
import {EventEmitter} from 'events';
import path from 'path';
// eslint-disable-next-line import/named
import {EventEmitter} from 'node:events';
import path from 'node:path';
import {createSandbox} from 'sinon';
import {version as APPIUM_VER} from '../../../package.json';
@@ -1,5 +1,5 @@
// @ts-check
import path from 'path';
import path from 'node:path';
import {PKG_HASHFILE_RELATIVE_PATH} from '../../../lib/constants';
import {rewiremock} from '../../helpers';
import {initMocks} from './mocks';
@@ -1,6 +1,6 @@
// @ts-check
import {promises as fs} from 'fs';
import {promises as fs} from 'node:fs';
import {Manifest} from '../../../lib/extension/manifest';
import {resetSchema} from '../../../lib/schema';
import {resolveFixture, rewiremock} from '../../helpers';
@@ -1,6 +1,5 @@
// @ts-check
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {rewiremock} from '../helpers';
-1
View File
@@ -1,5 +1,4 @@
import {init as logsinkInit, clear as logsinkClear} from '../../lib/logsink';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {logger} from '@appium/support';
@@ -1,7 +1,6 @@
// @ts-check
import _ from 'lodash';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {DRIVER_TYPE, PLUGIN_TYPE} from '../../../lib/constants';
import {AppiumConfigJsonSchema} from '@appium/schema';
-1
View File
@@ -10,7 +10,6 @@ import {
import {BASE_CAPS, W3C_CAPS} from '../helpers';
import _ from 'lodash';
import {stripColors} from '@colors/colors';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import logger from '../../lib/logger';
import {fs} from '@appium/support';
+1 -1
View File
@@ -26,7 +26,7 @@ export type CliCommandSetupSubcommand = 'mobile' | 'browser' | 'desktop' | 'rese
* Possible subcommands of {@linkcode CliCommandDriver} or
* {@linkcode CliCommandPlugin}.
*/
export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update'| 'doctor';
export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update' | 'doctor';
export interface CliExtensionSubcommandListArgs {
showInstalled?: boolean;
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [10.2.0](https://github.com/appium/appium/compare/@appium/base-driver@10.1.2...@appium/base-driver@10.2.0) (2026-01-26)
### Features
* **base-driver:** add ability to supply custom headers for proxy ([#21790](https://github.com/appium/appium/issues/21790)) ([a4b47c0](https://github.com/appium/appium/commit/a4b47c02c6e72381fa9d191d2a0bced106999705))
### Bug Fixes
* apply npm run lint:fix ([#21867](https://github.com/appium/appium/issues/21867)) ([5e28714](https://github.com/appium/appium/commit/5e28714442cf59ee35b085e01e82c3f5483891fd))
## [10.1.2](https://github.com/appium/appium/compare/@appium/base-driver@10.1.1...@appium/base-driver@10.1.2) (2025-12-04)
**Note:** Version bump only for package @appium/base-driver
@@ -28,7 +28,7 @@ export type ParsedCaps<C extends Constraints> = {
export type ValidateCapsOpts = {
/** if true, skip the presence constraint */
skipPresenceConstraint?: boolean;
}
};
/**
* Takes primary caps object and merges it into a secondary caps object.
+10 -7
View File
@@ -1,6 +1,5 @@
import _ from 'lodash';
import path from 'path';
import url from 'url';
import path from 'node:path';
import logger from './logger';
import {tempDir, fs, util, timing, node} from '@appium/support';
import { LRUCache } from 'lru-cache';
@@ -354,12 +353,16 @@ export function generateDriverLogPrefix(obj, sessionId = null) {
* @returns {Promise<RemoteAppData>}
*/
async function queryAppLink(appLink, reqHeaders) {
const {href, auth} = url.parse(appLink);
const axiosUrl = auth ? href.replace(`${auth}@`, '') : href;
const url = new URL(appLink);
// Extract credentials, then remove them from the URL for axios
const {username, password} = url;
url.username = '';
url.password = '';
const axiosUrl = url.href;
/** @type {import('axios').AxiosBasicCredentials|undefined} */
const axiosAuth = auth ? {
username: auth.substring(0, auth.indexOf(':')),
password: auth.substring(auth.indexOf(':') + 1),
const axiosAuth = username ? {
username,
password,
} : undefined;
/**
* @type {import('axios').RawAxiosRequestConfig}
@@ -1,7 +1,7 @@
import log from './logger';
import { LRUCache } from 'lru-cache';
import _ from 'lodash';
import {EventEmitter} from 'events';
import {EventEmitter} from 'node:events';
const IDEMPOTENT_RESPONSES = new LRUCache({
max: 64,
+2 -3
View File
@@ -1,7 +1,7 @@
import _ from 'lodash';
import path from 'path';
import path from 'node:path';
import express from 'express';
import http from 'http';
import http from 'node:http';
import favicon from 'serve-favicon';
import bodyParser from 'body-parser';
import methodOverride from 'method-override';
@@ -122,7 +122,6 @@ export function configureServer({
// set up static assets
app.use(favicon(path.resolve(STATIC_DIR, 'favicon.ico')));
// eslint-disable-next-line import/no-named-as-default-member
app.use(express.static(STATIC_DIR));
// crash routes, for testing
+1 -1
View File
@@ -1,4 +1,4 @@
import path from 'path';
import path from 'node:path';
import log from './logger';
import _ from 'lodash';
import {fs} from '@appium/support';
@@ -12,8 +12,8 @@ import {isSessionCommand, routeToCommandName} from '../protocol';
import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
import ProtocolConverter from './protocol-converter';
import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
import http from 'http';
import https from 'https';
import http from 'node:http';
import https from 'node:https';
import { match as pathToRegexMatch } from 'path-to-regexp';
import nodeUrl from 'node:url';
import { ProxyRequest } from './proxy-request';
@@ -34,6 +34,7 @@ const ALLOWED_OPTS = [
'timeout',
'log',
'keepAlive',
'headers',
];
export class JWProxy {
@@ -51,6 +52,8 @@ export class JWProxy {
sessionId;
/** @type {number} */
timeout;
/** @type {import('@appium/types').HTTPHeaders | undefined} */
headers;
/** @type {Protocol | null | undefined} */
_downstreamProtocol;
/** @type {ProxyRequest[]} */
@@ -192,6 +195,7 @@ export class JWProxy {
'content-type': 'application/json; charset=utf-8',
'user-agent': 'appium',
accept: 'application/json, */*',
...(this.headers ?? {}),
},
proxy: false,
timeout: this.timeout,
@@ -439,6 +443,7 @@ export class JWProxy {
* @returns {ParsedUrl}
*/
_parseUrl(url) {
// eslint-disable-next-line n/no-deprecated-api -- we need relative URL support
const parsedUrl = nodeUrl.parse(url || '/');
if (
_.isNil(parsedUrl.href) || _.isNil(parsedUrl.pathname)
+3 -3
View File
@@ -74,7 +74,7 @@ export class ProtocolError extends BaseError {
* @see https://w3c.github.io/webdriver-bidi/#protocol-definition
* @returns The object conforming to the shape of a BiDi error response
*/
bidiErrObject(id: string|number): ErrorBiDiCommandResponse {
bidiErrObject(id: string | number): ErrorBiDiCommandResponse {
// if we don't have an id, the client didn't send one, so we have nothing to send back.
// send back zero rather than making something up
const intId = (_.isInteger(id) ? id : (parseInt(`${id}`, 10) || 0)) as number;
@@ -1116,6 +1116,6 @@ interface W3CError {
}
interface ParameterRequirements {
required: string[]|string;
optional?: string[]|string;
required: string[] | string;
optional?: string[] | string;
}
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "10.1.2",
"version": "10.2.0",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -34,7 +34,8 @@
"static",
"build",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo"
"!build/tsconfig.tsbuildinfo",
"!build/test"
],
"scripts": {
"test": "run-p test:unit test:types",
@@ -44,25 +45,24 @@
"test:types": "tsd"
},
"dependencies": {
"@appium/support": "^7.0.4",
"@appium/types": "^1.1.2",
"@appium/support": "^7.0.5",
"@appium/types": "^1.2.0",
"@colors/colors": "1.6.0",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.13.2",
"asyncbox": "6.0.1",
"axios": "1.13.3",
"bluebird": "3.7.2",
"body-parser": "2.2.1",
"body-parser": "2.2.2",
"express": "5.2.1",
"fastest-levenshtein": "1.0.16",
"http-status-codes": "2.3.0",
"lodash": "4.17.21",
"lru-cache": "11.2.4",
"lodash": "4.17.23",
"lru-cache": "11.2.5",
"method-override": "3.0.0",
"morgan": "1.10.1",
"path-to-regexp": "8.3.0",
"serve-favicon": "2.5.1",
"source-map-support": "0.5.21",
"type-fest": "5.3.0"
"type-fest": "5.4.1"
},
"optionalDependencies": {
"spdy": "4.0.2"
@@ -1,8 +1,7 @@
import path from 'path';
import url from 'url';
import path from 'node:path';
import {fs} from '@appium/support';
import {configureApp} from '../../../lib/basedriver/helpers';
import http from 'http';
import http from 'node:http';
import finalhandler from 'finalhandler';
import serveStatic from 'serve-static';
import contentDisposition from 'content-disposition';
@@ -83,7 +82,7 @@ describe('app download and configuration', function () {
return;
}
// for testing zip file content types
const contentType = new URLSearchParams(url.parse(req.url).search).get('content-type');
const contentType = new URLSearchParams(new URL(req.url, 'http://localhost').search).get('content-type');
if (contentType !== null) {
res.setHeader('content-type', contentType);
}
@@ -1,6 +1,5 @@
import {server, routeConfiguringFunction} from '../../../lib';
import axios from 'axios';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getTestPort, TEST_HOST} from '@appium/driver-test-support';
import {MockExecuteDriver} from '../protocol/mock-execute-driver';
@@ -1,6 +1,5 @@
import {server} from '../../../lib';
import axios from 'axios';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import B from 'bluebird';
import _ from 'lodash';
@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-named-as-default
import Express from 'express';
import bodyParser from 'body-parser';
@@ -1,7 +1,6 @@
import {server, routeConfiguringFunction, errors, JWProxy, BaseDriver} from '../../../lib';
import {FakeDriver} from './fake-driver';
import axios from 'axios';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
import {createProxyServer} from './helpers';
@@ -3,7 +3,6 @@
import B from 'bluebird';
import {BaseDriver, errors} from '../../../lib/index';
import {validator} from '../../../lib/basedriver/validation';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
// TODO: we need module-level mocks for the logger
@@ -1,5 +1,4 @@
// @ts-check
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import _ from 'lodash';
import {BaseDriver} from '../../../../lib';
@@ -1,7 +1,6 @@
// @ts-check
import {BaseDriver} from '../../../lib';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
describe('timeout', function () {
@@ -2,7 +2,6 @@
import {server, routeConfiguringFunction} from '../../../lib';
import {configureServer, normalizeBasePath} from '../../../lib/express/server';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getTestPort} from '@appium/driver-test-support';
@@ -1,5 +1,4 @@
import {welcome} from '../../../lib/express/static';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
describe('welcome', function () {
@@ -149,6 +149,30 @@ describe('proxy', function () {
res.statusCode.should.equal(200);
body.should.eql({status: 0, value: {foo: 'bar'}});
});
it('should apply custom headers to downstream requests', async function () {
const customHeaders = {
'x-custom-header': 'foobar',
'user-agent': 'my-appium-client',
};
let capturedConfig;
const j = mockProxy({headers: customHeaders});
j.request = async function (config) {
capturedConfig = config;
return await request(config);
};
await j.proxy('/status', 'GET');
capturedConfig.should.have.property('headers');
capturedConfig.headers.should.have.property('x-custom-header', 'foobar');
capturedConfig.headers.should.have.property('user-agent', 'my-appium-client');
capturedConfig.headers.should.have.property(
'content-type',
'application/json; charset=utf-8'
);
capturedConfig.headers.should.have.property(
'accept',
'application/json, */*'
);
});
it('should pass along request errors', function () {
const j = mockProxy({sessionId: '123'});
j.proxy('/badurl', 'GET').should.eventually.be.rejectedWith('Could not proxy');
@@ -2,7 +2,7 @@ import {errors, errorFromMJSONWPStatusCode, errorFromW3CJsonCode, isErrorType} f
import {BadParametersError, getResponseForW3CError} from '../../../lib/protocol/errors';
import _ from 'lodash';
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
import path from 'path';
import path from 'node:path';
const basename = path.basename(__filename);
@@ -1,6 +1,6 @@
import _ from 'lodash';
import {METHOD_MAP, routeToCommandName} from '../../../lib/protocol';
import crypto from 'crypto';
import crypto from 'node:crypto';
describe('Routes', function () {
// TODO test against an explicit protocol rather than a hash of a previous
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.6](https://github.com/appium/appium/compare/@appium/base-plugin@3.0.5...@appium/base-plugin@3.0.6) (2026-01-26)
**Note:** Version bump only for package @appium/base-plugin
## [3.0.5](https://github.com/appium/appium/compare/@appium/base-plugin@3.0.4...@appium/base-plugin@3.0.5) (2025-12-04)
**Note:** Version bump only for package @appium/base-plugin
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-plugin",
"version": "3.0.5",
"version": "3.0.6",
"description": "The base plugin used to create Appium plugins",
"keywords": [
"automation",
@@ -32,7 +32,8 @@
"build",
"index.js",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo"
"!build/tsconfig.tsbuildinfo",
"!build/test"
],
"scripts": {
"test": "npm run test:unit",
@@ -40,8 +41,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^10.1.2",
"@appium/support": "^7.0.4"
"@appium/base-driver": "^10.2.0",
"@appium/support": "^7.0.5"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.1](https://github.com/appium/appium/compare/@appium/docutils@2.2.0...@appium/docutils@2.2.1) (2026-01-26)
**Note:** Version bump only for package @appium/docutils
## [2.2.0](https://github.com/appium/appium/compare/@appium/docutils@2.1.2...@appium/docutils@2.2.0) (2025-12-04)
-1
View File
@@ -2,7 +2,6 @@
// @ts-check
'use strict';
require('source-map-support').install();
const {main} = require('../build/lib/cli');
const {getLogger} = require('../build/lib/logger');
+2 -3
View File
@@ -10,8 +10,7 @@ import {getLogger} from '../logger';
import {fs} from '@appium/support';
import _ from 'lodash';
// eslint-disable-next-line import/named
import {sync as readPkg} from 'read-pkg';
import {readPackageSync} from 'read-pkg';
import {hideBin} from 'yargs/helpers';
import yargs from 'yargs/yargs';
import {DEFAULT_LOG_LEVEL, LogLevelMap, NAME_BIN} from '../constants';
@@ -19,7 +18,7 @@ import {DocutilsError} from '../error';
import {build, init, validate} from './command';
import {findConfig} from './config';
const pkg = readPkg({cwd: fs.findRoot(__dirname)});
const pkg = readPackageSync({cwd: fs.findRoot(__dirname)});
const log = getLogger('cli');
const IMPLICATIONS_FAILED_REGEX = /implications\s+failed:\n\s*(.+)\s->\s(.+)$/i;
+8 -8
View File
@@ -6,8 +6,8 @@
import {fs} from '@appium/support';
import _ from 'lodash';
import path from 'node:path';
import _pkgDir from 'pkg-dir';
import readPkg, {NormalizedPackageJson, PackageJson} from 'read-pkg';
import {packageDirectory} from 'package-directory';
import {readPackage, NormalizedPackageJson, PackageJson} from 'read-pkg';
import {JsonValue} from 'type-fest';
import * as YAML from 'yaml';
import {
@@ -30,7 +30,7 @@ const log = getLogger('fs');
*
* Caches result
*/
const findPkgDir = _.memoize(_pkgDir);
const findPkgDir = _.memoize(packageDirectory, (opts) => opts?.cwd);
/**
* Stringifies a thing into a YAML
@@ -75,7 +75,7 @@ export async function findInPkgDir(
filename: string,
cwd = process.cwd(),
): Promise<string | undefined> {
const pkgDir = await findPkgDir(cwd);
const pkgDir = await findPkgDir({cwd});
if (!pkgDir) {
return;
}
@@ -109,7 +109,7 @@ async function _readPkgJson(
cwd: string,
normalize?: boolean,
): Promise<{pkgPath: string; pkg: PackageJson | NormalizedPackageJson}> {
const pkgDir = await findPkgDir(cwd);
const pkgDir = await findPkgDir({cwd});
if (!pkgDir) {
throw new DocutilsError(
`Could not find a ${NAME_PACKAGE_JSON} near ${cwd}; please create it before using this utility`,
@@ -118,10 +118,10 @@ async function _readPkgJson(
const pkgPath = path.join(pkgDir, NAME_PACKAGE_JSON);
log.debug('Found `package.json` at %s', pkgPath);
if (normalize) {
const pkg = await readPkg({cwd: pkgDir, normalize});
const pkg = await readPackage({cwd: pkgDir, normalize});
return {pkg, pkgPath};
} else {
const pkg = await readPkg({cwd: pkgDir});
const pkg = await readPackage({cwd: pkgDir});
return {pkg, pkgPath};
}
}
@@ -151,7 +151,7 @@ export function writeFileString(filepath: string, content: JsonValue) {
});
}
type WhichFunction = (cmd: string, opts?: {nothrow: boolean}) => Promise<string|null>;
type WhichFunction = (cmd: string, opts?: {nothrow: boolean}) => Promise<string | null>;
/**
* `which` with memoization
+4 -10
View File
@@ -5,7 +5,6 @@
*/
import {fs, util} from '@appium/support';
import chalk from 'chalk';
import _ from 'lodash';
import {EventEmitter} from 'node:events';
import {exec} from 'teen_process';
@@ -21,12 +20,7 @@ import {
REQUIREMENTS_TXT_PATH,
} from './constants';
import {DocutilsError} from './error';
import {
findMkDocsYml,
isMkDocsInstalled,
readMkDocsYml,
findPython,
} from './fs';
import {findMkDocsYml, isMkDocsInstalled, readMkDocsYml, findPython} from './fs';
import {getLogger} from './logger';
import {MkDocsYml, PipPackage} from './model';
@@ -319,7 +313,7 @@ export class DocutilsValidator extends EventEmitter {
]));
} catch {
return this.fail(
`Could not find ${NAME_PIP} installation for Python at ${pythonPath}. Is it installed?`
`Could not find ${NAME_PIP} installation for Python at ${pythonPath}. Is it installed?`,
);
}
@@ -352,7 +346,7 @@ export class DocutilsValidator extends EventEmitter {
'package',
missingPackages.length,
)} could not be found:\n${missingPackages
.map((p) => chalk`- {yellow ${p.name}} @ {yellow ${p.version}}`)
.map((p) => `- ${p.name} @ ${p.version}`)
.join('\n')}`,
);
}
@@ -364,7 +358,7 @@ export class DocutilsValidator extends EventEmitter {
)} are installed, but at the wrong version:\n${invalidVersionPackages
.map(
([expected, actual]) =>
chalk`- {yellow ${expected.name}} @ {yellow ${expected.version}} (found {red ${actual.version}})`,
`- ${expected.name} @ ${expected.version} (found ${actual.version})`,
)
.join('\n')}`,
);
+9 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/docutils",
"version": "2.2.0",
"version": "2.2.1",
"description": "Documentation generation utilities for Appium and related projects",
"keywords": [
"automation",
@@ -38,6 +38,7 @@
"build",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo",
"!build/test",
"requirements.txt"
],
"scripts": {
@@ -48,17 +49,15 @@
"start": "node ./build/lib/cli/index.js"
},
"dependencies": {
"@appium/support": "^7.0.4",
"chalk": "4.1.2",
"@appium/support": "^7.0.5",
"consola": "3.4.2",
"diff": "8.0.2",
"diff": "8.0.3",
"lilconfig": "3.1.3",
"lodash": "4.17.21",
"pkg-dir": "5.0.0",
"read-pkg": "5.2.0",
"source-map-support": "0.5.21",
"teen_process": "3.0.4",
"type-fest": "5.3.0",
"lodash": "4.17.23",
"package-directory": "8.1.0",
"read-pkg": "10.0.0",
"teen_process": "4.0.8",
"type-fest": "5.4.1",
"yaml": "2.8.2",
"yargs": "18.0.0",
"yargs-parser": "22.0.0"
+1 -1
View File
@@ -1,5 +1,5 @@
mkdocs==1.6.1
mkdocs-git-revision-date-localized-plugin==1.5.0
mkdocs-material==9.7.0
mkdocs-material==9.7.1
mkdocs-redirects==1.2.2
mike==2.1.3
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.6](https://github.com/appium/appium/compare/@appium/driver-test-support@1.0.5...@appium/driver-test-support@1.0.6) (2026-01-26)
**Note:** Version bump only for package @appium/driver-test-support
## [1.0.5](https://github.com/appium/appium/compare/@appium/driver-test-support@1.0.4...@appium/driver-test-support@1.0.5) (2025-12-04)
**Note:** Version bump only for package @appium/driver-test-support
@@ -1,6 +1,5 @@
import _ from 'lodash';
import B from 'bluebird';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
// wrap these tests in a function so we can export the tests and re-use them
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/driver-test-support",
"version": "1.0.5",
"version": "1.0.6",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
@@ -32,7 +32,8 @@
"lib",
"build",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo"
"!build/tsconfig.tsbuildinfo",
"!build/test"
],
"scripts": {
"test": "npm run test:unit",
@@ -41,16 +42,15 @@
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^1.1.2",
"axios": "1.13.2",
"@appium/types": "^1.2.0",
"axios": "1.13.3",
"bluebird": "3.7.2",
"chai": "6.2.1",
"chai": "6.2.2",
"chai-as-promised": "8.0.2",
"get-port": "5.1.1",
"lodash": "4.17.21",
"sinon": "21.0.0",
"source-map-support": "0.5.21",
"type-fest": "5.3.0"
"get-port": "7.1.0",
"lodash": "4.17.23",
"sinon": "21.0.1",
"type-fest": "5.4.1"
},
"peerDependencies": {
"appium": "^3.0.0-beta.0",
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.0](https://github.com/appium/appium/compare/@appium/eslint-config-appium-ts@2.0.5...@appium/eslint-config-appium-ts@3.0.0) (2026-01-26)
### ⚠ BREAKING CHANGES
* **eslint-config-appium-ts:** modernize ESLint config (#21781)
### Features
* **eslint-config-appium-ts:** add unicorn/prefer-node-protocol with warn ([#21864](https://github.com/appium/appium/issues/21864)) ([77c9b58](https://github.com/appium/appium/commit/77c9b5898907d7380276d5982c7e175d10440c73))
* **eslint-config-appium-ts:** modernize ESLint config ([#21781](https://github.com/appium/appium/issues/21781)) ([b0b219a](https://github.com/appium/appium/commit/b0b219aa019d893bf30e90203a8265625777c47f))
## [2.0.5](https://github.com/appium/appium/compare/@appium/eslint-config-appium-ts@2.0.4...@appium/eslint-config-appium-ts@2.0.5) (2025-12-04)
+15 -1
View File
@@ -13,7 +13,21 @@ Install the package:
npm install @appium/eslint-config-appium-ts --save-dev
```
And then, in your `eslint.config.mjs` file, extend the configuration:
Then add it to your `eslint.config.mjs` file:
```js
import appiumConfig from '@appium/eslint-config-appium-ts';
import {defineConfig} from 'eslint/config';
export default defineConfig([
{
extends: [appiumConfig],
// add any other config changes
},
]);
```
Or for ESLint `< 9.22.0`:
```js
import appiumConfig from '@appium/eslint-config-appium-ts';
+83 -119
View File
@@ -3,29 +3,27 @@ import fs from 'node:fs';
import {includeIgnoreFile} from '@eslint/compat';
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import {defineConfig, globalIgnores} from 'eslint/config';
import eslintConfigPrettier from 'eslint-config-prettier';
import {createTypeScriptImportResolver} from 'eslint-import-resolver-typescript';
import globals from 'globals';
import pluginPromise from 'eslint-plugin-promise';
import importPlugin from 'eslint-plugin-import';
import {importX} from 'eslint-plugin-import-x';
import mochaPlugin from 'eslint-plugin-mocha';
import {configs as tsConfigs, parser as tsParser, plugin as tsPlugin} from 'typescript-eslint';
import nodePlugin from 'eslint-plugin-n';
import {configs as tsConfigs} from 'typescript-eslint';
import unicorn from 'eslint-plugin-unicorn';
const gitignorePath = path.resolve(process.cwd(), '.gitignore');
export default [
js.configs.recommended,
eslintConfigPrettier,
pluginPromise.configs['flat/recommended'],
importPlugin.flatConfigs.recommended,
...tsConfigs.recommended,
export default defineConfig([
{
name: 'Script Files',
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
parser: tsParser,
globals: {
...globals.node,
NodeJS: 'readonly',
@@ -33,22 +31,51 @@ export default [
},
},
plugins: {
'@typescript-eslint': tsPlugin,
'@stylistic': stylistic,
'import-x': importX,
js,
n: nodePlugin,
promise: pluginPromise,
unicorn
},
extends: [
'js/recommended',
'promise/flat/recommended',
'import-x/flat/recommended',
tsConfigs.recommended,
eslintConfigPrettier,
],
settings: {
/**
* This stuff enables `eslint-plugin-import` to resolve TS modules.
*/
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx', '.mtsx'],
},
'import/resolver': {
typescript: {
'import-x/resolver-next': [
createTypeScriptImportResolver({
project: ['tsconfig.json', './packages/*/tsconfig.json'],
},
},
})
],
},
rules: {
'@stylistic/array-bracket-spacing': 'error',
'@stylistic/arrow-parens': 'warn',
'@stylistic/arrow-spacing': 'error',
'@stylistic/comma-spacing': 'error',
'@stylistic/key-spacing': 'error',
'@stylistic/keyword-spacing': 'error',
'@stylistic/no-multi-spaces': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/no-whitespace-before-property': 'error',
'@stylistic/quotes': [
'error',
'single',
{
avoidEscape: true,
allowTemplateLiterals: 'always',
},
],
'@stylistic/semi': 'error',
'@stylistic/space-before-blocks': 'error',
'@stylistic/space-in-parens': 'error',
'@stylistic/space-infix-ops': 'error',
'@stylistic/space-unary-ops': 'error',
/**
* This rule is configured to warn if a `@ts-ignore` or `@ts-expect-error` directive is used
* without explanation.
@@ -71,7 +98,6 @@ export default [
* @remarks This is because empty interfaces have a use case in declaration merging. Otherwise,
* an empty interface can be a type alias, e.g., `type Foo = Bar` where `Bar` is an interface.
*/
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
/**
* Explicit `any` types are allowed.
@@ -93,76 +119,28 @@ export default [
* Sometimes we want unused variables to be present in base class method declarations.
*/
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-var-requires': 'off',
'import/export': 2,
'import/named': 'warn',
'import/no-duplicates': 2,
'import/no-unresolved': 2,
'import-x/named': 'warn',
'import-x/no-duplicates': 'error',
'promise/catch-or-return': 1,
/**
* Allow native `Promise`s.
* @remarks Originally, this was so that we could use [bluebird](https://npm.im/bluebird)
* everywhere, but this is not strictly necessary.
*/
'promise/no-native': 'off',
'promise/no-return-wrap': 1,
'promise/prefer-await-to-callbacks': 1,
'promise/prefer-await-to-then': 1,
'promise/param-names': 1,
'n/no-deprecated-api': 'warn',
'array-bracket-spacing': 2,
'arrow-body-style': [1, 'as-needed'],
'arrow-parens': [1, 'always'],
'arrow-spacing': 2,
/**
* Disables the `brace-style` rule.
* @remarks Due to the way `prettier` sometimes formats extremely verbose types, sometimes it is necessary
* to indent in a way that is not allowed by the default `brace-style` rule.
*/
'brace-style': 'off',
'comma-dangle': 0,
'comma-spacing': [
2,
{
before: false,
after: true,
},
],
'curly': [2, 'all'],
'dot-notation': 2,
'eqeqeq': [2, 'smart'],
'key-spacing': [
2,
{
mode: 'strict',
beforeColon: false,
afterColon: true,
},
],
'keyword-spacing': 2,
'no-buffer-constructor': 1,
'no-console': 2,
'no-dupe-class-members': 'off',
'no-empty': 0,
'no-multi-spaces': 2,
'no-prototype-builtins': 1,
'no-redeclare': 'off',
'no-trailing-spaces': 2,
'no-var': 2,
'no-whitespace-before-property': 2,
'object-shorthand': 2,
'quotes': [
2,
'single',
{
avoidEscape: true,
allowTemplateLiterals: true,
},
],
'radix': [2, 'always'],
'require-atomic-updates': 0,
'promise/catch-or-return': 'warn',
'promise/no-return-wrap': 'warn',
'promise/prefer-await-to-callbacks': 'warn',
'promise/prefer-await-to-then': 'warn',
'promise/param-names': 'warn',
'arrow-body-style': 'warn',
'curly': 'error',
'dot-notation': 'error',
'eqeqeq': ['error', 'smart'],
'no-console': 'error',
'no-empty': 'off',
'no-prototype-builtins': 'warn',
'object-shorthand': 'error',
'radix': 'error',
'require-atomic-updates': 'off',
/**
* Allow `async` functions without `await`.
* @remarks Originally, this was to be more clear about the return value of a function, but with
@@ -170,26 +148,18 @@ export default [
* `return await somePromise` have their own use-cases.
*/
'require-await': 'off',
'semi': [2, 'always'],
'space-before-blocks': [2, 'always'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [
2,
{
words: true,
nonwords: false,
},
],
'unicorn/prefer-node-protocol': 'warn'
}
},
{
...mochaPlugin.configs.recommended,
name: 'Test Files',
files: ['**/test/**', '*.spec.*js', '-specs.*js', '*.spec.ts'],
plugins: {
mocha: mochaPlugin,
},
extends: [mochaPlugin.configs.recommended],
rules: {
...mochaPlugin.configs.recommended.rules,
/**
* Both `@ts-expect-error` and `@ts-ignore` are allowed to be used with impunity in tests.
* @remarks We often test things which explicitly violate types.
@@ -202,27 +172,21 @@ export default [
*/
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'import/no-named-as-default-member': 'off',
'import-x/no-named-as-default-member': 'off',
'mocha/consistent-spacing-between-blocks': 'off',
'mocha/max-top-level-suites': 'off',
'mocha/no-exclusive-tests': 2,
'mocha/no-exclusive-tests': 'error',
'mocha/no-exports': 'off',
'mocha/no-mocha-arrows': 2,
'mocha/no-pending-tests': 'off',
'mocha/no-setup-in-describe': 'off',
},
},
{
name: 'Ignores',
ignores: [
...(fs.existsSync(gitignorePath) ? includeIgnoreFile(gitignorePath).ignores : []),
'**/.*',
'**/*-d.ts',
'**/*.min.js',
'**/build/**',
'**/coverage/**',
],
}
];
fs.existsSync(gitignorePath) ? includeIgnoreFile(gitignorePath) : {},
globalIgnores([
'**/.*',
'**/*-d.ts',
'**/*.min.js',
'**/build/**',
'**/coverage/**',
]),
]);
@@ -1,7 +1,7 @@
{
"name": "@appium/eslint-config-appium-ts",
"type": "module",
"version": "2.0.5",
"version": "3.0.0",
"description": "Shared ESLint config for Appium projects (TypeScript version)",
"keywords": [
"eslint",
@@ -29,14 +29,17 @@
"test:smoke": "exit 0"
},
"dependencies": {
"@eslint/compat": "2.0.0",
"@eslint/js": "9.39.1",
"@eslint/compat": "2.0.1",
"@eslint/js": "9.39.2",
"@stylistic/eslint-plugin": "5.7.1",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-import-x": "4.16.1",
"eslint-plugin-mocha": "11.2.0",
"eslint-plugin-n": "17.23.2",
"eslint-plugin-promise": "7.2.1",
"typescript-eslint": "8.48.1"
"eslint-plugin-unicorn": "62.0.0",
"typescript-eslint": "8.53.1"
},
"peerDependencies": {
"eslint": "^9.0.0"

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