Compare commits

...
Author SHA1 Message Date
Jonathan Lipps 3467f32c8e chore: publish
- appium@2.14.1
 - @appium/base-driver@9.14.1
 - @appium/base-plugin@2.2.52
 - @appium/docutils@1.0.28
 - @appium/driver-test-support@0.7.3
 - @appium/execute-driver-plugin@4.0.1
 - @appium/fake-plugin@3.1.56
 - @appium/images-plugin@3.0.26
 - @appium/plugin-test-support@0.3.48
 - @appium/support@6.0.2
 - @appium/test-support@3.1.2
2025-01-06 14:50:32 -08:00
renovate[bot] 6f27d36cf5 fix(support): update dependency uuid to v11.0.4 (#20883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 12:24:23 +00:00
renovate[bot] 488e5dea82 fix(support): update dependency teen_process to v2.2.3 (#20882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 11:33:01 +00:00
Mykola Mokhnach 13d8b03cc1 fix(appium): Do not update BiDi commands for drivers which don't support it (#20879) 2025-01-05 15:04:26 +01:00
renovate[bot] fe209ed660 fix(driver-test-support): update dependency @types/lodash to v4.17.14 (#20877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-04 09:48:30 +00:00
renovate[bot] 93e4288253 fix(execute-driver-plugin): update dependency webdriverio to v9.5.1 (#20875)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 15:45:30 +00:00
renovate[bot] fb839be6ea chore(deps): update dependency @types/node to v22.10.5 (#20874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 12:40:58 +00:00
Jonathan Lipps 6742808345 chore: publish
- appium@2.14.0
 - @appium/base-driver@9.14.0
 - @appium/base-plugin@2.2.51
 - @appium/docutils@1.0.27
 - @appium/driver-test-support@0.7.2
 - @appium/eslint-config-appium-ts@1.0.1
 - @appium/execute-driver-plugin@4.0.0
 - @appium/fake-driver@5.6.0
 - @appium/fake-plugin@3.1.55
 - @appium/images-plugin@3.0.25
 - @appium/opencv@3.0.7
 - @appium/plugin-test-support@0.3.47
 - @appium/schema@0.7.1
 - @appium/support@6.0.1
 - @appium/test-support@3.1.1
 - @appium/types@0.23.0
 - @appium/universal-xml-plugin@1.0.27
2025-01-02 10:59:35 -08:00
renovate[bot] bb4ba0e52b chore(deps): update dependency @types/node to v22.10.3 (#20872)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-01 09:10:42 +00:00
renovate[bot] 990a0667e3 fix(execute-driver-plugin): update dependency webdriverio to v9.5.0 (#20871)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-31 19:27:42 +00:00
renovate[bot] 389a3e83ac fix(docutils): update dependency yaml to v2.7.0 (#20870)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-31 16:54:07 +00:00
renovate[bot] a034880a03 chore(deps): update eslint-related packages to v8.19.0 (#20869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-31 13:48:03 +00:00
Mykola Mokhnach 588381ecf4 test(appium): Delete obsolete todo 2024-12-31 09:46:23 +01:00
Mykola Mokhnach 3fc57a15b5 test(appium): Update Bidi over ssl test (#20868) 2024-12-31 09:44:25 +01:00
Mykola Mokhnach bbfc04026f feat(appium): Update entry point resolution logic for ESM extensions (#20866) 2024-12-30 10:44:34 +01:00
Kazuaki Matsuo c5443e9fc9 feat(fake-driver): add getWindowHandle and getWindowHandles to reduce wdio errors in CI (#20865) 2024-12-28 09:28:10 -08:00
renovate[bot] 31c6759061 chore(workflows): update peter-evans/create-pull-request action to v7.0.6 (#20862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-28 09:01:19 +00:00
renovate[bot] edd1c05d1e chore(deps): update dependency @eslint/js to v9.17.0 (#20863)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-28 09:59:34 +01:00
Edgars Eglītis 61b6689897 chore: adjust eslint-config-appium-ts related files (#20861) 2024-12-28 09:30:40 +01:00
Mykola Mokhnach 65658ccbdd fix: Reduce linter warnings (#20860) 2024-12-28 09:30:05 +01:00
Mykola Mokhnach 25ea9f73bc chore: Migrate to flat ESLint config (v9) (#20856) 2024-12-27 20:42:36 +01:00
renovate[bot] 4b3a8c2197 fix(docutils): update dependency consola to v3.3.3 (#20858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-27 09:53:16 +00:00
renovate[bot] 24abb385e5 fix(types): update dependency type-fest to v4.31.0 (#20857)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 10:46:20 +00:00
renovate[bot] 861d060713 fix(docutils): update dependency consola to v3.3.1 (#20855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 13:32:06 +00:00
Mykola Mokhnach c57f81d134 chore(appium): Tune BiDi logs (#20854) 2024-12-22 09:08:34 +01:00
Mykola Mokhnach 51acf54fe5 chore(appium): Move mixin BiDi-related methods into a separate module (#20852) 2024-12-21 11:02:36 +01:00
renovate[bot] aa880837da fix(docutils): update dependency consola to v3.3.0 (#20851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 12:07:58 +00:00
Mykola Mokhnach af7aba492d chore(appium): Do not log all BiDi events (#20849) 2024-12-20 08:38:20 +01:00
Kazuaki Matsuo 25b8483294 docs(appium): add webSocketUrl in the docs (#20840)
* docs: add webSocketUrl in the docs
2024-12-19 23:02:52 -05:00
renovate[bot] 4474412a60 fix(execute-driver-plugin): update dependency webdriverio to v9.4.5 (#20847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-19 12:20:12 +00:00
renovate[bot] 7c740f9b3a chore(js): update dependency webdriverio to v9 (#20832) 2024-12-18 22:00:22 +01:00
Mykola Mokhnach c765bc31e0 fix(base-driver): Return an empty object if the corresponding API response is undefined (#20845) 2024-12-18 20:28:19 +01:00
renovate[bot]andMykola Mokhnach 7abecaddd3 fix(types): update dependency type-fest to v4 (#20843)
* fix(types): update dependency type-fest to v4

* cast to string

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Mykola Mokhnach <mokhnach@gmail.com>
2024-12-18 08:24:57 +01:00
renovate[bot] 083f7e2328 chore(docutils): update dependency mkdocs-material to v9.5.49 (#20842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-17 14:34:28 +00:00
renovate[bot] aaef1d6962 fix(universal-xml-plugin): update dependency fast-xml-parser to v4.5.1 (#20841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-15 13:03:08 +00:00
Mykola Mokhnach 64e768efb7 feat(appium): Add session.status BiDi command (#20839) 2024-12-14 19:41:12 +01:00
renovate[bot] a5897dd25a fix(types): update dependency type-fest to v4 (#20838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 16:36:24 +01:00
renovate[bot] 66e3580731 chore(deps): update dependency @types/node to v22.10.2 (#20833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 10:59:21 +00:00
Mykola Mokhnach 16e9f94903 feat(execute-driver-plugin): Bump webdriverio to v9 (#20824)
BREAKING CHANGE: webdriverio major version bumped to 9
2024-12-10 08:35:41 +01:00
Jonathan Lipps a917ec6ced feat(appium): allow drivers to define their own bidi commands (#20828)
* feat: allow drivers to define their own bidi commands

* docs(appium): add docs about adding custom bidi commands in drivers
2024-12-10 12:02:23 +09:00
renovate[bot] 342d6641ee chore(docutils): update dependency mkdocs-material to v9.5.48 (#20829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 10:30:33 +00:00
Edgars Eglītis 04e2d1154b fix(appium): update CLI help messages (#20825)
* fix(appium): adjust some CLI output messages

* fix(appium): adjust more CLI output messages

* fix(appium): add reset command to CLI & update tests

* chore: revert name change for driver/plugin name argument
2024-12-06 20:14:24 +02:00
renovate[bot] c032352fad fix(base-driver): update dependency express to v4.21.2 (#20823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 10:01:24 +00:00
Mykola Mokhnach e3774ef0c0 ci(appium): Export mkdocs config to Crowdin as a plain text file (#20822) 2024-12-05 22:40:52 +01:00
Jonathan Lipps 1d16ac6b45 chore: publish
- appium@2.13.1
 - @appium/base-driver@9.13.1
 - @appium/base-plugin@2.2.50
 - @appium/docutils@1.0.26
 - @appium/driver-test-support@0.7.1
 - @appium/fake-driver@5.5.7
 - @appium/fake-plugin@3.1.54
 - @appium/images-plugin@3.0.24
 - @appium/plugin-test-support@0.3.46
 - @appium/support@6.0.0
 - @appium/test-support@3.1.0
 - @appium/types@0.22.3
 - @appium/universal-xml-plugin@1.0.26
2024-12-05 12:22:35 -08:00
Kazuaki Matsuo 6ea0830319 fix(base-driver): add * always for non-separator for backward compatibility (#20821)
* fix(base-driver): add * always for non-separator for backward compatibility

* simplify

* revert const

* add todo comment

* revert const
2024-12-05 11:49:55 -08:00
renovate[bot] 228b091edc fix(universal-xml-plugin): update dependency @xmldom/xmldom to v0.9.6 (#20819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 11:10:58 +02:00
Mykola Mokhnach 81a5b57673 feat(test-support): Remove obsolete helpers (#20817) 2024-12-04 22:12:39 +01:00
Edgars Eglītis 8d94f48bc5 chore(github): adjust newlines in bug issue form (#20818) 2024-12-04 22:00:02 +02:00
Edgars Eglītis f03468a153 chore(github): update issue templates (#20816)
* chore(github): update issue templates

* chore: add back checkbox for latest component updates
2024-12-04 21:37:17 +02:00
renovate[bot] 6a78aa9cea chore(deps): update dependency eslint-import-resolver-typescript to v3.7.0 (#20815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 14:00:00 +00:00
Edgars Eglītis 9c5407ce64 ci: add workflow to validate PR title (#20813)
* ci: add workflow to validate PR title

* ci: rerun job if title was edited
2024-12-04 13:11:34 +02:00
Mykola Mokhnach 2a99e43b88 docs(appium): Fix HTML formatting (#20814) 2024-12-04 12:05:57 +01:00
Edgars Eglītis dca88624a4 docs: update aggregate changelog (#20812) 2024-12-04 13:03:42 +02:00
renovate[bot] 69f100ad3e fix(support): update dependency axios to v1.7.9 (#20811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 09:16:44 +01:00
renovate[bot] f5e708ac42 chore(deps): update dependency prettier to v3.4.2 (#20810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 09:16:33 +01:00
renovate[bot] c91683f50f fix(support): update dependency teen_process to v2.2.2 (#20808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 08:58:50 +01:00
Mykola Mokhnach 4a2bfe9418 docs(appium): Enable the documentation to be translated via Crowdin (#20800) 2024-12-04 08:33:05 +01:00
renovate[bot] 3008c0d7be chore(deps): update dependency mocha to v11 (#20798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 08:32:08 +01:00
Mykola Mokhnach 9a3f990739 feat: Remove @appium/doctor from the monorepo (#20805) 2024-12-04 08:31:32 +01:00
Kazuaki Matsuo 16c3ae25ea chore(support): revert #20797 as a backward compatibility (#20801)
BREAKING CHANGE: revert #20797 which was used for a backward compatibility
2024-12-03 09:54:58 -08:00
renovate[bot] 93fa5ccb06 fix(docutils): update dependency lilconfig to v3.1.3 (#20803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 17:06:00 +00:00
renovate[bot] 8590432955 fix(types): update dependency type-fest to v4.30.0 (#20802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 14:27:51 +02:00
renovate[bot] 42ef4841f9 fix(base-driver): update dependency @types/method-override to v3 (#20799)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 09:23:00 +02:00
226 changed files with 6244 additions and 10241 deletions
-9
View File
@@ -1,9 +0,0 @@
**/coverage/**
**/node_modules/**
examples/javascript-wd
**/build/**
**/*.min.js
sample-code
**/build-fixtures/**
packages/appium/docs/**/assets/**
packages/appium/docs/**/js/**
-62
View File
@@ -1,62 +0,0 @@
{
"extends": ["@appium/eslint-config-appium-ts"],
"overrides": [
{
"files": "packages/fake-driver/**/*",
"rules": {"require-await": "off"}
},
{"files": "packages/support/**/*", "globals": {"BigInt": "readonly"}},
{
"files": "packages/*/test/**/*",
"rules": {"func-names": "off"}
},
{
"files": [
"packages/appium/support.js",
"packages/appium/driver.js",
"packages/appium/plugin.js"
],
"parserOptions": {
"sourceType": "script"
}
},
{
"files": [
"./test/setup.js",
"./**/scripts/**/*.js",
"./packages/*/index.js",
"./packages/docutils/bin/appium-docs.js"
],
"rules": {
"@typescript-eslint/no-var-requires": "off"
},
"parserOptions": {
"sourceType": "script"
}
},
{
"files": "./packages/*/test/**/*.js",
"rules": {
"@typescript-eslint/no-var-requires": "off",
"no-restricted-properties": [
"error",
{
"object": "sinon",
"property": "spy",
"message": "Use `sandbox = sinon.createSandbox()` and `sandbox.spy()` instead. Don't forget to call `sandbox.restore()` in `afterEach`"
},
{
"object": "sinon",
"property": "stub",
"message": "Use `sandbox = sinon.createSandbox()` and `sandbox.stub()` instead. Don't forget to call `sandbox.restore()` in `afterEach`"
},
{
"object": "sinon",
"property": "mock",
"message": "Use `sandbox = sinon.createSandbox()` and `sandbox.mock()` instead. Don't forget to call `sandbox.restore()` in `afterEach`"
}
]
}
}
]
}
+1 -3
View File
@@ -24,9 +24,7 @@ targets:
- type: npm
path: ./packages/base-driver
- type: npm
path: ./packages/doctor
- type: npm
path: ./packages/eslint-config-appium
path: ./packages/eslint-config-appium-ts
- type: npm
path: ./packages/fake-driver
- type: npm
+82 -63
View File
@@ -1,51 +1,97 @@
name: 🐞 Bug Report
description: File a new bug report
title: 'bug: <title>'
description: Create a new bug report.
title: '🐞 bug: <title>'
labels: [Bug, Needs Triage]
body:
- type: markdown
attributes:
value: ':stop_sign: _For support questions, please visit [http://discuss.appium.io](http://discuss.appium.io) instead._'
value: ':stop_sign: _Make sure you are reporting an issue. For general questions, please use [http://discuss.appium.io](http://discuss.appium.io)._'
- type: checkboxes
attributes:
label: 'Do I have the most recent component updates?'
description: 'Appium 1-related issues are going to be ignored as this version has reached its end of life.'
options:
- label: 'I use the most recent available driver/plugin and server versions'
required: true
- type: checkboxes
attributes:
label: 'Is the component officially supported by the Appium team?'
description: 'Make sure you report an issue for a component which is officially supported/maintained by the Appium team.'
options:
- label: 'I have verified the component repository is present under the Appium organization in GitHub'
required: true
- type: checkboxes
attributes:
label: 'Is there an existing issue for this?'
label: Is there an existing issue for this?
description: 'Please [search :mag: the issues](https://github.com/appium/appium/issues) to check if this bug has already been reported.'
options:
- label: 'I have searched the existing issues'
- label: I have searched the existing issues
required: true
- type: textarea
- type: input
attributes:
label: 'Current Behavior'
description: 'Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.'
label: Appium Version
description: What Appium server version are you running?
placeholder: Output of `appium --version`
validations:
required: true
- type: textarea
attributes:
label: 'Expected Behavior'
description: 'Describe what you expect to happen instead.'
validations:
required: true
- type: textarea
attributes:
label: 'Minimal Reproducible Example'
description: |
Please provide a the _smallest, complete code snippet_ that Appium's maintainers can run to reproduce the issue ([read more about what this entails](https://stackoverflow.com/help/minimal-reproducible-example)). Failing this, any sort of reproduction steps are better than nothing!
An easy way to get started is to copy [sample code](https://github.com/appium/appium/tree/master/packages/appium/sample-code) and modify it accordingly.
- type: dropdown
attributes:
label: Appium Host Type
description: |
How is your Appium server being hosted? For options other than "Command Line", please add more details under "Further Information".
options:
- Command Line
- Virtual Machine/Docker
- Cloud Provider (e.g. BrowserStack/Sauce Labs)
- Other
validations:
required: true
- type: input
attributes:
label: Are you using Appium components not maintained by the Appium team?
description: Refer to the [Appium Ecosystem](http://appium.io/docs/en/latest/ecosystem/) page for officially-supported components.
placeholder: Please provide the names of any third-party components.
- type: checkboxes
attributes:
label: Is this issue reproducible using the latest components?
description: |
You can retrieve the latest server/driver/plugin version number by running `npm info <package-name> version`. If you are not up to date, please try to reproduce your issue after updating all affected components.
options:
- label: I confirm the issue is still reproducible with the latest component versions
- type: textarea
attributes:
label: Current Behavior
description: Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: Describe what you expect to happen instead.
validations:
required: true
- type: input
attributes:
label: Appium Log
description: |
Please create a [Gist](https://gist.github.com) with your _full_ Appium server log and link it here. Alternatively, you can directly attach your logfile in the "Further Information" field below.
:warning: _Remember to redact or remove any sensitive information!_
placeholder: 'https://gist.github.com/...'
- type: textarea
attributes:
label: Environment Details
description: |
Please provide additional information about your environment; feel free to remove any items which are not relevant.
:warning: _Remember to redact or remove any sensitive information!_
value: |
- Debug details (`appium --show-debug-info`):
- Last component version(s) which did _not_ exhibit the problem:
- Platform and version of device under test:
- Real device or emulator/simulator:
- type: textarea
attributes:
label: Minimal Reproducible Example
description: |
Please provide [the smallest, complete code snippet](https://stackoverflow.com/help/minimal-reproducible-example) that Appium's maintainers can run to reproduce the issue. An easy way to get started is to copy [sample code](https://github.com/appium/appium/tree/master/packages/appium/sample-code) and modify it accordingly. Failing this, any sort of reproduction steps are better than nothing!
If the result is more than a screenful of text _or_ requires multiple files, please:
@@ -54,39 +100,12 @@ body:
- Provide a link to a new or existing public repository exhibiting the issue
validations:
required: true
- type: textarea
attributes:
label: 'Environment'
description: 'Please provide the following information about your environment; feel free to remove any items which are not relevant.'
value: |
- Operating system:
- Appium server version (output of `appium --version`):
- Appium driver(s) and their version(s) (`appium driver list`):
- Appium plugin(s) and their version(s) (`appium plugin list`):
- Node.js version (output of `node --version`):
- `npm` version (output of `npm --version`):
- Last component(s) version which did _not_ exhibit the problem:
- Platform and version under test:
- Real device or emulator/simulator:
validations:
required: false
- type: input
attributes:
label: 'Link to Appium Logs'
description: |
Create a [Gist](https://gist.github.com)—which contains your _full_ Appium logs—and link it here. Alternatively, you can attach a logfile to this issue (drag it into the "Further Information" field below).
:warning: _Remember to redact or remove any sensitive information!_
placeholder: 'https://gist.github.com/...'
- type: textarea
attributes:
label: Further Information
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
description: If needed, please provide additional information about your issue (attachments, links, references, etc.).
_Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in._
validations:
required: false
- type: markdown
attributes:
value: ':stop_sign: _For support questions, please visit [http://discuss.appium.io](http://discuss.appium.io) instead._'
value: ':stop_sign: _Make sure you are reporting an issue. For general questions, please use [http://discuss.appium.io](http://discuss.appium.io)._'
+1 -1
View File
@@ -2,4 +2,4 @@ blank_issues_enabled: true
contact_links:
- name: ❓ Question
url: https://discuss.appium.io/
about: Please use the Appium forum for questions
about: Please use the Appium forum for any questions.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feat]:"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,33 @@
name: 💡 Feature Request
description: Suggest an idea for this project.
title: "💡 feat: <title>"
labels: [Enhancement]
body:
- type: textarea
attributes:
label: Is your feature request related to a problem?
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...].
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like.
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered.
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false
-10
View File
@@ -53,11 +53,6 @@ labels:
matcher:
files: ['packages/base-plugin/**']
- label: '@appium/doctor'
sync: true
matcher:
files: ['packages/doctor/**']
- label: '@appium/docutils'
sync: true
matcher:
@@ -68,11 +63,6 @@ labels:
matcher:
files: ['packages/driver-test-support/**']
- label: '@appium/eslint-config-appium'
sync: true
matcher:
files: ['packages/eslint-config-appium/**']
- label: '@appium/eslint-config-appium-ts'
sync: true
matcher:
+3
View File
@@ -42,6 +42,9 @@ on:
- '!**/sample-code/**'
- '!packages/*/docs/**'
env:
CI: true
permissions:
contents: read
@@ -0,0 +1,38 @@
# Retrieves non-English translations from Crowdin and creates a PR with new changes
name: Sync Crowdin Docs Translations
on:
workflow_dispatch:
# TODO: Uncomment the scheduler as soon as we have existing translations
# imported to Crowdin
# schedule:
# - cron: 0 0 * * 0
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js LTS
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Crowdin Sync
run: npm run crowdin-sync-docs-translations
env:
# appium-documentation
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.6
with:
token: ${{ github.token }}
commit-message: 'docs(appium): Update documentation translations'
title: 'docs(appium): Update documentation translations'
branch: crowdin-sync-${{ github.run_id }}
body: 'Automated Update of Documentation Translations: https://crowdin.com/project/appium-documentation'
@@ -0,0 +1,31 @@
# Updates Crowdin with any changes in document files in English
name: Update Crowdin English Docs
on:
push:
branches: [master]
paths:
- 'packages/appium/docs/en/**.md'
- 'packages/appium/docs/mkdocs-en.yml'
- '.github/workflows/crowdin-update-docs-resources.yml' # this file
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js LTS
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Crowdin Update
run: npm run crowdin-update-docs
env:
# appium-documentation
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_DOCS_PROJECT_ID }}
CROWDIN_TOKEN: ${{ secrets.CROWDIN_DOCS_TOKEN }}
+15
View File
@@ -0,0 +1,15 @@
name: Conventional Commits
on:
pull_request:
types: [opened, edited, synchronize, reopened]
jobs:
conventional:
name: PR Title
runs-on: ubuntu-latest
steps:
- uses: beemojs/conventional-pr-action@v3
with:
config-preset: angular
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+19 -11
View File
@@ -1,36 +1,44 @@
# Aggregate Changelog
Appium contains many different modules and supports a variety of drivers, plugins and clients, most of which have their own changelogs. Links to these individual changelogs are aggregated here.
The Appium monorepo contains many different modules and supports a variety of drivers, plugins and clients,
most of which have their own changelogs. Links to these individual changelogs are aggregated here.
## Main Appium Module
* ### [Appium 2 Changelog](packages/appium/CHANGELOG.md)
* [Appium 1.x Changelog](https://github.com/appium/appium/blob/1.x/CHANGELOG.md)
## Appium Drivers / Clients / Plugins
* Refer to the [Appium Ecosystem](https://appium.io/docs/en/latest/ecosystem/) page for links to specific driver/client/plugin documentation
* Changelogs for plugins maintained by Appium Team are also linked in the Other Modules section below
## Drivers / Clients
Refer to the [Appium Ecosystem](https://appium.io/docs/en/latest/ecosystem/) page for links
to driver/client documentation
## Other Appium Modules
## Plugins
* [execute-driver-plugin](packages/execute-driver-plugin/CHANGELOG.md)
* [images-plugin](packages/images-plugin/CHANGELOG.md)
* [relaxed-caps-plugin](packages/relaxed-caps-plugin/CHANGELOG.md)
* [universal-xml-plugin](packages/universal-xml-plugin/CHANGELOG.md)
## Other Modules
* [base-driver](packages/base-driver/CHANGELOG.md)
* [base-plugin](packages/base-plugin/CHANGELOG.md)
* [doctor](packages/doctor/CHANGELOG.md)
* [docutils](packages/docutils/CHANGELOG.md)
* [driver-test-support](packages/driver-test-support/CHANGELOG.md)
* [eslint-config-appium](packages/eslint-config-appium/CHANGELOG.md)
* [eslint-config-appium-ts](packages/eslint-config-appium-ts/CHANGELOG.md)
* [execute-driver-plugin](packages/execute-driver-plugin/CHANGELOG.md)
* [fake-driver](packages/fake-driver/CHANGELOG.md)
* [fake-plugin](packages/fake-plugin/CHANGELOG.md)
* [images-plugin](packages/images-plugin/CHANGELOG.md)
* [logger](packages/logger/CHANGELOG.md)
* [opencv](packages/opencv/CHANGELOG.md)
* [plugin-test-support](packages/plugin-test-support/CHANGELOG.md)
* [relaxed-caps-plugin](packages/relaxed-caps-plugin/CHANGELOG.md)
* [schema](packages/schema/CHANGELOG.md)
* [strongbox](packages/strongbox/CHANGELOG.md)
* [support](packages/support/CHANGELOG.md)
* [test-support](packages/test-support/CHANGELOG.md)
* [tsconfig](packages/tsconfig/CHANGELOG.md)
* [types](packages/types/CHANGELOG.md)
* [universal-xml-plugin](packages/universal-xml-plugin/CHANGELOG.md)
## Removed Modules
The changelog links used for these modules lead to the last version of their changelog file.
* [doctor](https://github.com/appium/appium/blob/8daf5e123ac14c8325acf504fb33eb28e1a3dd78/packages/doctor/CHANGELOG.md) ([removal PR](https://github.com/appium/appium/pull/20805))
* [eslint-config-appium](https://github.com/appium/appium/blob/4b3a8c2197a587ec5a2606b662e4e7667a7852cb/packages/eslint-config-appium/CHANGELOG.md) ([removal PR](https://github.com/appium/appium/pull/20856))
* [gulp-plugins](https://github.com/appium/appium/blob/0823f0b60e40395cd1dc3b72cfa3c0092bc81302/packages/gulp-plugins/CHANGELOG.md) ([removal PR](https://github.com/appium/appium/pull/17943))
* [typedoc-plugin-appium](https://github.com/appium/appium/blob/d6204b6902074210943d7bbbf72d139b9b170a20/packages/typedoc-plugin-appium/CHANGELOG.md) ([removal PR](https://github.com/appium/appium/pull/19465))
+23
View File
@@ -0,0 +1,23 @@
import tsConfig from '@appium/eslint-config-appium-ts';
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/**',
],
},
{
ignores: [
'**/build-fixtures/**',
'packages/appium/docs/**/assets/**',
'packages/appium/docs/**/js/**',
'packages/appium/sample-code/**',
],
},
];
+3463 -4675
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -38,12 +38,14 @@
"clean:interactive": "git clean -d -x -i",
"clean:force": "git clean -d -x -f",
"clean:workspaces": "lerna run clean || true",
"crowdin-sync-docs-translations": "node ./scripts/crowdin-sync-docs-translations.mjs",
"crowdin-update-docs": "node ./scripts/crowdin-update-docs-resources.mjs",
"dev": "run-s build:compile \"build:workspaces\" \"build:compile -- --watch\"",
"dev:docs": "npm run -w packages/appium dev:docs",
"docs": "npm run -w packages/appium build:docs",
"docs:preview": "npm run -w packages/appium build:docs:preview",
"docs:publish": "npm run -w packages/appium publish:docs",
"doctor": "node ./packages/doctor",
"doctor": "node ./scripts/doctor-stub.mjs",
"install-docs-deps": "npm run -w packages/appium install-docs-deps",
"lint": "eslint .",
"lint:ci": "eslint --quiet .",
@@ -62,7 +64,7 @@
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
"sync-pkgs:common-fields:default": "sync-monorepo-packages --packages=\"!packages/logger\" --fields=author,license,bugs,homepage,engines",
"sync-pkgs:common-fields:logger": "sync-monorepo-packages --packages=\"packages/logger\" --fields=author,bugs,homepage,engines",
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages=\"!packages/eslint-config-appium-ts\" ",
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/types\" --packages=\"!packages/eslint-config-appium-ts\" ",
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json --packages=\"!packages/logger\" LICENSE",
"test": "run-s test:quick",
"test:ci": "run-s test:smoke test:unit test:types test:e2e",
@@ -83,12 +85,13 @@
},
"devDependencies": {
"@colors/colors": "1.6.0",
"@eslint/js": "9.17.0",
"@tsconfig/node14": "14.1.2",
"@types/chai": "5.0.1",
"@types/chai-as-promised": "8.0.1",
"@types/diff": "6.0.0",
"@types/mocha": "10.0.10",
"@types/node": "22.10.1",
"@types/node": "22.10.5",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/sinon-chai": "4.0.0",
@@ -96,20 +99,19 @@
"@types/teen_process": "2.0.4",
"@types/ws": "8.5.13",
"@types/yargs": "17.0.33",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"@typescript-eslint/eslint-plugin": "8.19.0",
"@typescript-eslint/parser": "8.19.0",
"asyncbox": "3.0.0",
"chai": "5.1.2",
"chai-as-promised": "8.0.1",
"conventional-changelog-conventionalcommits": "7.0.2",
"cpy-cli": "5.0.0",
"eslint": "8.57.1",
"eslint": "9.17.0",
"eslint-config-prettier": "9.1.0",
"eslint-find-rules": "4.2.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-import-resolver-typescript": "3.7.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-mocha": "10.5.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-promise": "7.2.1",
"finalhandler": "1.3.1",
"get-port": "5.1.1",
"json-schema-to-typescript": "15.0.3",
@@ -118,9 +120,9 @@
"midnight-smoker": "8.0.0",
"mjpeg-consumer": "2.0.0",
"mjpeg-server": "0.3.1",
"mocha": "10.8.2",
"mocha": "11.0.1",
"npm-run-all2": "6.2.6",
"prettier": "3.4.1",
"prettier": "3.4.2",
"rewiremock": "3.14.5",
"rimraf": "5.0.10",
"serve-static": "1.16.2",
@@ -130,7 +132,7 @@
"tsd": "0.31.2",
"typescript": "5.7.2",
"validate.js": "0.13.1",
"webdriverio": "8.40.6",
"webdriverio": "9.5.1",
"ws": "8.18.0",
"yaml-js": "0.3.1"
},
+46
View File
@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.14.1](https://github.com/appium/appium/compare/appium@2.14.0...appium@2.14.1) (2025-01-06)
### Bug Fixes
* **appium:** Do not update BiDi commands for drivers which don't support it ([#20879](https://github.com/appium/appium/issues/20879)) ([13d8b03](https://github.com/appium/appium/commit/13d8b03cc185c5f4e0631002614981ee6f86fab4))
* **execute-driver-plugin:** update dependency webdriverio to v9.5.1 ([#20875](https://github.com/appium/appium/issues/20875)) ([93e4288](https://github.com/appium/appium/commit/93e4288253c07a844e6e10f0dd040b1a40eb92ff))
* **support:** update dependency teen_process to v2.2.3 ([#20882](https://github.com/appium/appium/issues/20882)) ([488e5de](https://github.com/appium/appium/commit/488e5dea82d3c525acf483952570e32f319f3722))
## [2.14.0](https://github.com/appium/appium/compare/appium@2.13.1...appium@2.14.0) (2025-01-02)
### Features
* **appium:** Add session.status BiDi command ([#20839](https://github.com/appium/appium/issues/20839)) ([64e768e](https://github.com/appium/appium/commit/64e768efb7bebd6b5a24d55206d1cad00812777c))
* **appium:** allow drivers to define their own bidi commands ([#20828](https://github.com/appium/appium/issues/20828)) ([a917ec6](https://github.com/appium/appium/commit/a917ec6ceda2166fb3dcbff6b2768f700db9e103))
* **appium:** Update entry point resolution logic for ESM extensions ([#20866](https://github.com/appium/appium/issues/20866)) ([bbfc040](https://github.com/appium/appium/commit/bbfc04026f0629d89d8b313616b852896481d08d))
### Bug Fixes
* **appium:** update CLI help messages ([#20825](https://github.com/appium/appium/issues/20825)) ([04e2d11](https://github.com/appium/appium/commit/04e2d1154b67c34607ab7a1e2a44b9b4bcfb998f))
* **docutils:** update dependency yaml to v2.7.0 ([#20870](https://github.com/appium/appium/issues/20870)) ([389a3e8](https://github.com/appium/appium/commit/389a3e83ac0069504338f7182a4e938cd391de7e))
* **execute-driver-plugin:** update dependency webdriverio to v9.4.5 ([#20847](https://github.com/appium/appium/issues/20847)) ([4474412](https://github.com/appium/appium/commit/4474412a60a82556f43fd3f3fb4f4ea2e4bc11ee))
* **execute-driver-plugin:** update dependency webdriverio to v9.5.0 ([#20871](https://github.com/appium/appium/issues/20871)) ([990a066](https://github.com/appium/appium/commit/990a0667e3442bf01aaf67327066517d6310c371))
* Reduce linter warnings ([#20860](https://github.com/appium/appium/issues/20860)) ([65658cc](https://github.com/appium/appium/commit/65658ccbdde9144c45cb5aad6a9089a5d6f3a0a3))
* **types:** update dependency type-fest to v4 ([#20838](https://github.com/appium/appium/issues/20838)) ([a5897dd](https://github.com/appium/appium/commit/a5897dd25a277a42b0c650a52274ba2c891ac3b0))
* **types:** update dependency type-fest to v4 ([#20843](https://github.com/appium/appium/issues/20843)) ([7abecad](https://github.com/appium/appium/commit/7abecaddd3ed64c7be321650b2a17990e74a7222))
* **types:** update dependency type-fest to v4.31.0 ([#20857](https://github.com/appium/appium/issues/20857)) ([24abb38](https://github.com/appium/appium/commit/24abb385e54f57457c4fb3f2b654cb63645e7ccd))
## [2.13.1](https://github.com/appium/appium/compare/appium@2.13.0...appium@2.13.1) (2024-12-05)
### Bug Fixes
* **docutils:** update dependency lilconfig to v3.1.3 ([#20803](https://github.com/appium/appium/issues/20803)) ([93fa5cc](https://github.com/appium/appium/commit/93fa5ccb06eb5089bb3e8a8d9e67dad7f4446a0a))
* **support:** update dependency axios to v1.7.9 ([#20811](https://github.com/appium/appium/issues/20811)) ([69f100a](https://github.com/appium/appium/commit/69f100ad3e12030708dee4b8a74005dd41976e37))
* **support:** update dependency teen_process to v2.2.2 ([#20808](https://github.com/appium/appium/issues/20808)) ([c91683f](https://github.com/appium/appium/commit/c91683f50feea937ff8568eab6366465a6cf9a3e))
* **types:** update dependency type-fest to v4.30.0 ([#20802](https://github.com/appium/appium/issues/20802)) ([8590432](https://github.com/appium/appium/commit/8590432955eb7663e35847db541b9ead3f845a36))
## [2.13.0](https://github.com/appium/appium/compare/appium@2.12.2...appium@2.13.0) (2024-12-02)
+27
View File
@@ -121,3 +121,30 @@ npm run dev:docs
```
You can then view the documentation at `http://127.0.0.1:8000/docs/en`.
## Translating Appium Documentation
The process of Appium documents localization into languages other than English is automated and is done via
the [Crowdin Translations Management System](https://crowdin.com). Do not edit any translated documents
directly in the GitHub Appium repository as they are going to be replaced with ones exported from Crowdin
during an upcoming sync.
### Where To Start
If you would like to contribute to the translation of Appium documents into your language then simply join
the translators group for the [Appium Documentation](https://crowdin.com/project/appium-documentation)
Crowdin project, and start translating documents there. If you see that your language is missing from
the list of available Crowdin languages then simply let us know by creating an
[issue](https://github.com/appium/appium/issues).
### Source Language Updates
Changes in documents are synchronized to Crowdin automatically via the `Update Crowdin English Docs` GitHub action.
This action is triggered automatically as soon as there are any changes under `packages/appium/docs/en/**.md`
or `packages/appium/docs/mkdocs-en.yml`.
### Fetching Translated Documents
In order to fetch translated files from Crowdin to the GitHub repository it is necessary to trigger
the `Sync Crowdin Docs Translations` action. This action should also automatically create a PR with
corresponding translated resources included.
@@ -324,6 +324,10 @@ Currently, you also need to define a `doesSupportBidi` field on your driver inst
it is set to `true`. Appium will not turn on its Websocket servers for your driver and set up any
handlers unless your driver says that it supports BiDi in this way.
You are not limited to BiDi commands that are defined in the official BiDi specification. If you
wish to define new commands, you may do so; you just need to tell Appium about them! See
[below](#extend-the-existing-protocol-with-new-commands) for more information.
### Implement element finding
Element finding is a special command implementation case. You don't actually want to override
@@ -574,9 +578,10 @@ to the upstream connection.
You may find that the existing commands don't cut it for your driver. If you want to expose
behaviours that don't map to any of the existing commands, you can create new commands in one of
two ways:
three ways:
1. Extending the WebDriver protocol and creating client-side plugins to access the extensions
1. Extending the classic WebDriver protocol and creating client-side plugins to access the extensions via the classic HTTP interface
1. Extending the WebDriver BiDi protocol with new modules and methods, accessed from a client via the BiDi interface
1. Overloading the Execute Script command by defining [Execute
Methods](../guides/execute-methods.md)
@@ -606,7 +611,39 @@ won't have nice client-side functions designed to target these endpoints. So you
create and release client-side plugins for each language you want to support (directions or
examples can be found at the relevant client docs).
An alternative to this way of doing things is to overload a command which all WebDriver clients
The second way of adding new commands is adding them as BiDi commands (accessed via the BiDi
websocket interface, rather than the classic HTTP interface). BiDi commands come in two parts:
a "module", which is basically a container or namespace, and a "command", which is the name of your
new command.
As with the first method, you teach Appium to recognize your new BiDi commands by adding a static
field to your driver class, called `newBidiCommands`. It has a format similar to `newMethodMap`.
Basically it encapsulates information about the BiDi module, BiDi command name, reference to your
driver instance method that will handle the command, and required and optional parameters. Here's
an example of a `newBidiCommands` as implemented on an imaginary driver:
```js
static newBidiCommands = {
video: {
startFramerateCapture: {
command: 'startFrameCap',
params: {
required: ['videoSource'],
optional: ['showOnScreen'],
}
},
stopFramerateCapture: {
command: 'stopFrameCap',
},
}
};
```
In this imaginary example, we have defined two new BiDi commands: `video.startFramerateCapture` and
`video.stopFramerateCapture`. The first command takes a required and an optional parameter, and the
second does not. When combined with generic BiDi support in your driver (see [the section on BiDi](#implement-webdriver-bidi-commands) above), and given an implementation of the appropriate methods on your driver (e.g. `startFrameCap` and `stopFrameCap` in this example), clients would be able to send these BiDi commands using whatever mechanism normally exists for doing so in the client library.
An alternative to these other ways of doing things is to overload a command which all WebDriver clients
have access to already: Execute Script. Appium provides some a convenient tool for making this
easy. Let's say you are building a driver for stereo system called `soundz`, and you wanted to
create a command for playing a song by name. You could expose this to your users in such a way that
@@ -657,6 +694,10 @@ A couple notes about this system:
1. The `executeMethod` helper will reject with an error if a script name doesn't match one of the
script names defined as a command in `executeMethodMap`, or if there are missing parameters.
One of the nice things about the Execute Method strategy is that methods implemented in this way
will be available via the classic or BiDi interfaces (since they will result in the same Appium
handlers being called).
### Build Appium Doctor checks
+11 -1
View File
@@ -32,7 +32,7 @@ Here is a list of all the globally-recognized Appium capabilities:
!!! info
Individual drivers and plugins can support other capabilities, so refer to their documentation
for lists ofspecific capability names. Some drivers may also not support all of these capabilities
for lists of specific capability names. Some drivers may also not support all of these capabilities
| <div style="width:12em">Capability</div> | Type | Required? | Description |
|--------------------------------------------|-----------|-----------|----------------------------|
@@ -67,6 +67,16 @@ Each Appium client has its own way of constructing capabilities and starting a s
examples of doing this in each client library, head to the [Ecosystem](../ecosystem/index.md) page
and click through to the appropriate client documentation.
## BiDi Protocol Support
Appium supports [WebDriver BiDi](https://w3c.github.io/webdriver-bidi/) protocol since base–driver 9.5.0.
The actual behavior depends on individual drivers while the Appium and the baseーdriver support the protocol.
Please make sure if a driver supports the protocol and what kind of commands/events it supports in the documentation.
| Capability Name | Type | Description |
|---|---|−--|
| `webSocketUrl` | `boolean` | To enable BiDi protocol in the session. |
## Using `appium:options` to Group Capabilities
If you use a lot of `appium:` capabilities in your tests, it can get a little repetitive. You can
+1 -1
View File
@@ -14,7 +14,7 @@ title: Welcome
<div style="text-align:center;width:100%;background-color:#ec366d;margin-top:-3em;margin-bottom:2em;">
<h3 style="color:#ffffff;padding: 8px;">
AppiumConf 2025 is partnering with SeleniumConf<br/> <span style="font-size:0.8em;">March 26-28 in Valencia, Spain. Join us to learn and connect with the community!<span><br/>
AppiumConf 2025 is partnering with SeleniumConf<br/> <span style="font-size:0.8em;">March 26-28 in Valencia, Spain. Join us to learn and connect with the community!</span><br/>
<a style="color:#fcba12" href="https://seleniumconf.com/" target="_blank">Register Now</a>
</h3>
</div>
+18 -377
View File
@@ -1,6 +1,4 @@
/* eslint-disable no-unused-vars */
import _ from 'lodash';
import B from 'bluebird';
import {getBuildInfo, updateBuildInfo, APPIUM_VER} from './config';
import {
BaseDriver,
@@ -10,7 +8,6 @@ import {
CREATE_SESSION_COMMAND,
DELETE_SESSION_COMMAND,
GET_STATUS_COMMAND,
MAX_LOG_BODY_LENGTH,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
} from '@appium/base-driver';
@@ -19,20 +16,12 @@ import {
parseCapsForInnerDriver,
pullSettings,
makeNonW3cCapsError,
isBroadcastIp,
fetchInterfaces,
V4_BROADCAST_IP,
validateFeatures,
} from './utils';
import {util, node, logger} from '@appium/support';
import {getDefaultsForExtension} from './schema';
import {DRIVER_TYPE, BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
import WebSocket from 'ws';
import os from 'node:os';
const MIN_WS_CODE_VAL = 1000;
const MAX_WS_CODE_VAL = 1015;
const WS_FALLBACK_CODE = 1011; // server encountered an error while fulfilling request
import {DRIVER_TYPE, BIDI_BASE_PATH} from './constants';
import * as bidiHelpers from './bidi';
const desiredCapabilityConstraints = /** @type {const} */ ({
automationName: {
@@ -178,7 +167,6 @@ class AppiumDriver extends DriverCore {
return this.sessions[sessionId];
}
// eslint-disable-next-line require-await
async getStatus() {
// https://www.w3.org/TR/webdriver/#dfn-status
const statusObj = this._isShuttingDown
@@ -265,347 +253,6 @@ class AppiumDriver extends DriverCore {
}
}
/**
* Initialize a new bidi connection and set up handlers
* @param {import('ws').WebSocket} ws The websocket connection object
* @param {import('http').IncomingMessage} req The connection pathname, which might include the session id
*/
onBidiConnection(ws, req) {
// TODO put bidi-related functionality into a mixin/helper class
// wrap all of the handler logic with exception handling so if something blows up we can log
// and close the websocket
try {
const {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr} = this.initBidiSocket(
ws,
req,
);
this.initBidiSocketHandlers(
ws,
proxyClient,
send,
sendToProxy,
bidiHandlerDriver,
logSocketErr,
);
this.initBidiProxyHandlers(proxyClient, ws, send);
this.initBidiEventListeners(ws, bidiHandlerDriver, send);
} catch (err) {
this.log.error(err);
try {
ws.close();
} catch (ign) {}
}
}
/**
* Initialize a new bidi connection
* @param {import('ws').WebSocket} ws The websocket connection object
* @param {import('http').IncomingMessage} req The connection pathname, which might include the session id
*/
initBidiSocket(ws, req) {
let outOfBandErrorPrefix = '';
const pathname = req.url;
if (!pathname) {
throw new Error('Invalid connection request: pathname missing from request');
}
const bidiSessionRe = new RegExp(`${BIDI_BASE_PATH}/([^/]+)$`);
const bidiNoSessionRe = new RegExp(`${BIDI_BASE_PATH}/?$`);
const sessionMatch = bidiSessionRe.exec(pathname);
const noSessionMatch = bidiNoSessionRe.exec(pathname);
if (!sessionMatch && !noSessionMatch) {
throw new Error(
`Got websocket connection for path ${pathname} but didn't know what to do with it. ` +
`Ignoring and will close the connection`,
);
}
// Let's figure out which driver is going to handle this socket connection. It's either going
// to be a driver matching a session id appended to the bidi base path, or this umbrella driver
// (if no session id is included in the bidi connection request)
/** @type {import('@appium/types').ExternalDriver | AppiumDriver} */
let bidiHandlerDriver;
/** @type {import('ws').WebSocket | null} */
let proxyClient = null;
if (sessionMatch) {
// If we found a session id, see if it matches an active session
const sessionId = sessionMatch[1];
bidiHandlerDriver = this.sessions[sessionId];
if (!bidiHandlerDriver) {
// The session ID sent in doesn't match an active session; just ignore this socket
// connection in that case
throw new Error(
`Got bidi connection request for session with id ${sessionId} which is closed ` +
`or does not exist. Closing the socket connection.`,
);
}
const driverName = bidiHandlerDriver.constructor.name;
outOfBandErrorPrefix = `[session ${sessionId}] `;
this.log.info(`Bidi websocket connection made for session ${sessionId}`);
// store this socket connection for later removal on session deletion. theoretically there
// can be multiple sockets per session
if (!this.bidiSockets[sessionId]) {
this.bidiSockets[sessionId] = [];
}
this.bidiSockets[sessionId].push(ws);
const bidiProxyUrl = bidiHandlerDriver.bidiProxyUrl;
if (bidiProxyUrl) {
try {
new URL(bidiProxyUrl);
} catch (ign) {
throw new Error(
`Got request for ${driverName} to proxy bidi connections to upstream socket with ` +
`url ${bidiProxyUrl}, but this was not a valid url`,
);
}
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}`);
proxyClient = new WebSocket(bidiProxyUrl);
this.bidiProxyClients[sessionId] = proxyClient;
}
} else {
this.log.info('Bidi websocket connection made to main server');
// no need to store the socket connection if it's to the main server since it will just
// stay open as long as the server itself is and will close when the server closes.
bidiHandlerDriver = this; // eslint-disable-line @typescript-eslint/no-this-alias
}
const logSocketErr = (/** @type {Error} */ err) =>
this.log.error(`${outOfBandErrorPrefix}${err}`);
// This is a function which wraps the 'send' method on a web socket for two reasons:
// 1. Make it async-await friendly
// 2. Do some logging if there's a send error
const sendFactory = (/** @type {import('ws').WebSocket} */ socket) => {
const socketSend = B.promisify(socket.send, {context: socket});
return async (/** @type {string|Buffer} */ data) => {
try {
await socketSend(data);
} catch (err) {
logSocketErr(err);
}
};
};
// Construct our send method for sending messages to the client
const send = sendFactory(ws);
// Construct a conditional send method for proxying messages from the client to an upstream
// bidi socket server (e.g. on a browser)
const sendToProxy = proxyClient ? sendFactory(proxyClient) : null;
return {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr};
}
/**
* Set up handlers on upstream bidi socket we are proxying to/from
*
* @param {import('ws').WebSocket | null} proxyClient - the websocket connection to/from the
* upstream socket (the one we're proxying to/from)
* @param {import('ws').WebSocket} ws - the websocket connection to/from the client
* @param {(data: string | Buffer) => Promise<void>} send - a method used to send data to the
* client
*/
initBidiProxyHandlers(proxyClient, ws, send) {
// Set up handlers for events that might come from the upstream bidi socket connection if
// we're in proxy mode
if (proxyClient) {
// Here we're receiving a message from the upstream socket server. We want to pass it on to
// the client
proxyClient.on('message', async (/** @type {Buffer|string} */ data) => {
const logData = _.truncate(data.toString('utf8'), {length: MAX_LOG_BODY_LENGTH});
this.log.debug(
`<-- BIDI Received data from proxied bidi socket, sending to client. Data: ${logData}`,
);
await send(data);
});
// If the upstream socket server closes the connection, should close the connection to the
// client as well
proxyClient.on('close', (code, reason) => {
this.log.debug(
`Upstream bidi socket closed connection (code ${code}, reason: '${reason}'). ` +
`Closing proxy connection to client`,
);
if (!_.isNumber(code)) {
code = parseInt(code, 10);
}
if (_.isNaN(code) || code < MIN_WS_CODE_VAL || code > MAX_WS_CODE_VAL) {
this.log.warn(
`Received code ${code} from upstream socket, but this is not a valid ` +
`websocket code. Rewriting to ${WS_FALLBACK_CODE} for ws compatibility`,
);
code = WS_FALLBACK_CODE;
}
ws.close(code, reason);
});
proxyClient.on('error', (err) => {
this.log.error(`Got error on upstream bidi socket connection: ${err}`);
});
}
}
/**
* Set up handlers on the bidi socket connection to the client
*
* @param {import('ws').WebSocket} ws - the websocket connection to/from the client
* @param {import('ws').WebSocket | null} proxyClient - the websocket connection to/from the
* upstream socket (the one we're proxying to/from, if we're proxying)
* @param {(data: string | Buffer) => Promise<void>} send - a method used to send data to the
* client
* @param {((data: string | Buffer) => Promise<void>) | null} sendToProxy - a method used to send data to the
* upstream socket
* @param {import('@appium/types').ExternalDriver | AppiumDriver} bidiHandlerDriver - the driver
* handling the bidi commands
* @param {(err: Error) => void} logSocketErr - a special prefixed logger
*/
initBidiSocketHandlers(ws, proxyClient, send, sendToProxy, bidiHandlerDriver, logSocketErr) {
ws.on('error', (err) => {
// Can't do much with random errors on the connection other than log them
logSocketErr(err);
});
ws.on('open', () => {
this.log.info('Bidi websocket connection is now open');
});
// Now set up handlers for the various events that might happen on the websocket connection
// coming from the client
// First is incoming messages from the client
ws.on('message', async (/** @type {Buffer} */ data) => {
if (proxyClient) {
const logData = _.truncate(data.toString('utf8'), {length: MAX_LOG_BODY_LENGTH});
this.log.debug(
`--> BIDI Received data from client, sending to upstream bidi socket. Data: ${logData}`,
);
// if we're meant to proxy to an upstream bidi socket, just do that
// @ts-ignore sendToProxy is never null if proxyClient is truthy, but ts doesn't know
// that
await sendToProxy(data.toString('utf8'));
} else {
const res = await this.onBidiMessage(data, bidiHandlerDriver);
await send(JSON.stringify(res));
}
});
// Next consider if the client closes the socket connection on us
ws.on('close', (code, reason) => {
// Not sure if we need to do anything here if the client closes the websocket connection.
// Probably if a session was started via the socket, and the socket closes, we should end the
// associated session to free up resources. But otherwise, for sockets attached to existing
// sessions, doing nothing is probably right.
this.log.debug(`Bidi socket connection closed (code ${code}, reason: '${reason}')`);
// If we're proxying, might as well close the upstream connection and clean it up
if (proxyClient) {
this.log.debug('Also closing bidi proxy socket connection');
proxyClient.close(code, reason);
}
});
}
/**
* Set up bidi event listeners
*
* @param {import('ws').WebSocket} ws - the websocket connection to/from the client
* @param {import('@appium/types').ExternalDriver | AppiumDriver} bidiHandlerDriver - the driver
* handling the bidi commands
* @param {(data: string | Buffer) => Promise<void>} send - a method used to send data to the
* client
*/
initBidiEventListeners(ws, bidiHandlerDriver, send) {
// If the driver emits a bidi event that should maybe get sent to the client, check to make
// sure the client is subscribed and then pass it on
let eventListener = async ({context, method, params}) => {
// if the driver didn't specify a context, use the empty context
if (!context) {
context = '';
}
if (!method || !params) {
throw new Error(
`Driver emitted a bidi event that was malformed. Require method and params keys ` +
`(with optional context). But instead received: ${JSON.stringify({
context,
method,
params,
})}`,
);
}
if (ws.readyState !== WebSocket.OPEN) {
// if the websocket is not still 'open', then we can ignore sending these events
if (ws.readyState > WebSocket.OPEN) {
// if the websocket is closed or closing, we can remove this listener as well to avoid
// leaks
bidiHandlerDriver.eventEmitter.removeListener(BIDI_EVENT_NAME, eventListener);
}
return;
}
if (bidiHandlerDriver.bidiEventSubs[method]?.includes(context)) {
this.log.info(
`<-- BIDI EVENT ${method} (context: '${context}', params: ${JSON.stringify(params)})`,
);
// now we can send the event onto the socket
const ev = {type: 'event', context, method, params};
await send(JSON.stringify(ev));
}
};
bidiHandlerDriver.eventEmitter.on(BIDI_EVENT_NAME, eventListener);
}
/**
* @param {Buffer} data
* @param {ExternalDriver | AppiumDriver} driver
*/
async onBidiMessage(data, driver) {
let resMessage, id, method, params;
const dataTruncated = _.truncate(data.toString(), {length: 100});
try {
try {
({id, method, params} = JSON.parse(data.toString('utf8')));
} catch (err) {
throw new errors.InvalidArgumentError(
`Could not parse Bidi command '${dataTruncated}': ${err.message}`,
);
}
driver.log.info(`--> BIDI message #${id}`);
if (!method) {
throw new errors.InvalidArgumentError(
`Missing method for BiDi operation in '${dataTruncated}'`,
);
}
if (!params) {
throw new errors.InvalidArgumentError(
`Missing params for BiDi operation in '${dataTruncated}`,
);
}
const result = await driver.executeBidiCommand(method, params);
// https://w3c.github.io/webdriver-bidi/#protocol-definition
resMessage = {
id,
type: 'success',
result,
};
} catch (err) {
resMessage = err.bidiErrObject(id);
}
driver.log.info(`<-- BIDI message #${id}`);
return resMessage;
}
/**
* Log a bidi server error
* @param {Error} err
*/
onBidiServerError(err) {
this.log.error(`Error from bidi websocket server: ${err}`);
}
/**
* Create a new session
* @param {W3CAppiumDriverCaps} jsonwpCaps JSONWP formatted desired capabilities
@@ -695,6 +342,12 @@ class AppiumDriver extends DriverCore {
driverInstance.relaxedSecurityEnabled = true;
}
// We also want to assign any new Bidi Commands that the driver has specified, including all
// the standard bidi commands
if (_.isFunction(driverInstance.updateBidiCommands)) {
driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
}
if (!_.isEmpty(this.args.denyInsecure)) {
this.log.info('Explicitly preventing use of insecure features:');
this.args.denyInsecure.map((a) => this.log.info(` ${a}`));
@@ -783,7 +436,7 @@ class AppiumDriver extends DriverCore {
if (dCaps.webSocketUrl && driverInstance.doesSupportBidi) {
const {address, port, basePath} = this.args;
const scheme = `ws${this.server.isSecure() ? 's' : ''}`;
const host = determineBiDiHost(address);
const host = bidiHelpers.determineBiDiHost(address);
const bidiUrl = `${scheme}://${host}:${port}${basePath}${BIDI_BASE_PATH}/${innerSessionId}`;
this.log.info(
`Upstream driver responded with webSocketUrl ${dCaps.webSocketUrl}, will rewrite to ` +
@@ -1102,7 +755,9 @@ class AppiumDriver extends DriverCore {
// if we're running with plugins, make sure we log that the default behavior is actually
// happening so we can tell when the plugin call chain is unwrapping to the default behavior
// if that's what happens
plugins.length && this.log.info(`Executing default handling behavior for command '${cmd}'`);
if (plugins.length) {
this.log.info(`Executing default handling behavior for command '${cmd}'`);
}
// if we make it here, we know that the default behavior is handled
cmdHandledBy.default = true;
@@ -1184,8 +839,9 @@ class AppiumDriver extends DriverCore {
}
wrapCommandWithPlugins({driver, cmd, args, next, cmdHandledBy, plugins}) {
plugins.length &&
if (plugins.length) {
this.log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
}
// now we can go through each plugin and wrap `next` around its own handler, passing the *old*
// next in so that it can call it if it wants to
@@ -1276,6 +932,10 @@ class AppiumDriver extends DriverCore {
const dstSession = this.sessions[sessionId];
return dstSession && dstSession.canProxy(sessionId);
}
onBidiConnection = bidiHelpers.onBidiConnection;
onBidiMessage = bidiHelpers.onBidiMessage;
onBidiServerError = bidiHelpers.onBidiServerError;
}
// help decide which commands should be proxied to sub-drivers and which
@@ -1284,25 +944,6 @@ function isAppiumDriverCommand(cmd) {
return !isSessionCommand(cmd) || cmd === DELETE_SESSION_COMMAND;
}
/**
* Clients cannot use broadcast addresses, like 0.0.0.0 or ::
* to create connections. Thus we prefer a hostname if such
* address is provided or the actual address of a non-local interface,
* in case the host only has one such interface.
*
* @param {string} address
* @returns {string}
*/
function determineBiDiHost(address) {
if (!isBroadcastIp(address)) {
return address;
}
const nonLocalInterfaces = fetchInterfaces(address === V4_BROADCAST_IP ? 4 : 6)
.filter((iface) => !iface.internal);
return nonLocalInterfaces.length === 1 ? nonLocalInterfaces[0].address : os.hostname();
}
/**
* Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
* method did not exist
+436
View File
@@ -0,0 +1,436 @@
import _ from 'lodash';
import B from 'bluebird';
import {
errors,
} from '@appium/base-driver';
import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
import WebSocket from 'ws';
import os from 'node:os';
import {
isBroadcastIp,
fetchInterfaces,
V4_BROADCAST_IP,
} from './utils';
import type {IncomingMessage} from 'node:http';
import type {AppiumDriver} from './appium';
import type {
ErrorBiDiCommandResponse,
SuccessBiDiCommandResponse,
ExternalDriver,
StringRecord
} from '@appium/types';
type AnyDriver = ExternalDriver | AppiumDriver;
type SendData = (data: string | Buffer) => Promise<void>;
type LogSocketError = (err: Error) => void;
interface InitBiDiSocketResult {
bidiHandlerDriver: AnyDriver;
proxyClient: WebSocket | null;
send: SendData;
sendToProxy: SendData | null;
logSocketErr: LogSocketError;
}
const MIN_WS_CODE_VAL = 1000;
const MAX_WS_CODE_VAL = 1015;
const WS_FALLBACK_CODE = 1011; // server encountered an error while fulfilling request
const BIDI_EVENTS_MAP: WeakMap<AnyDriver, Record<string, number>> = new WeakMap();
const MAX_LOGGED_DATA_LENGTH = 300;
/**
* Clients cannot use broadcast addresses, like 0.0.0.0 or ::
* to create connections. Thus we prefer a hostname if such
* address is provided or the actual address of a non-local interface,
* in case the host only has one such interface.
*
* @param address
*/
export function determineBiDiHost(address: string): string {
if (!isBroadcastIp(address)) {
return address;
}
const nonLocalInterfaces = fetchInterfaces(address === V4_BROADCAST_IP ? 4 : 6)
.filter((iface) => !iface.internal);
return nonLocalInterfaces.length === 1 ? nonLocalInterfaces[0].address : os.hostname();
}
/**
* Initialize a new bidi connection and set up handlers
* @param ws The websocket connection object
* @param req The connection pathname, which might include the session id
*/
export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): void {
try {
const initBiDiSocketFunc: OmitThisParameter<typeof initBidiSocket> = initBidiSocket.bind(this);
const {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
ws,
req,
);
const initBidiSocketHandlersFunc: OmitThisParameter<typeof initBidiSocketHandlers> = initBidiSocketHandlers
.bind(this);
initBidiSocketHandlersFunc(
ws,
proxyClient,
send,
sendToProxy,
bidiHandlerDriver,
logSocketErr,
);
if (proxyClient) {
const initBidiProxyHandlersFunc: OmitThisParameter<typeof initBidiProxyHandlers> = initBidiProxyHandlers
.bind(bidiHandlerDriver);
initBidiProxyHandlersFunc(proxyClient, ws, send);
}
const initBidiEventListenersFunc: OmitThisParameter<typeof initBidiEventListeners> = initBidiEventListeners
.bind(this);
initBidiEventListenersFunc(ws, bidiHandlerDriver, send);
} catch (err) {
this.log.error(err);
try {
ws.close();
} catch {}
}
}
/**
* @param data
* @param driver
*/
export async function onBidiMessage(
this: AppiumDriver,
data: Buffer,
driver: AnyDriver
): Promise<SuccessBiDiCommandResponse | ErrorBiDiCommandResponse> {
let resMessage: SuccessBiDiCommandResponse | ErrorBiDiCommandResponse;
let id: number = 0;
const driverLog = driver.log;
const dataTruncated = _.truncate(data.toString(), {length: MAX_LOGGED_DATA_LENGTH});
try {
let method: string;
let params: StringRecord;
try {
({id, method, params} = JSON.parse(data.toString('utf8')));
} catch (err) {
throw new errors.InvalidArgumentError(
`Could not parse Bidi command '${dataTruncated}': ${err.message}`,
);
}
driverLog.info(`--> BIDI message #${id}`);
if (!method) {
throw new errors.InvalidArgumentError(
`Missing method for BiDi operation in '${dataTruncated}'`,
);
}
if (!params) {
throw new errors.InvalidArgumentError(
`Missing params for BiDi operation in '${dataTruncated}`,
);
}
const result = await driver.executeBidiCommand(method, params);
resMessage = {
id,
type: 'success',
result,
};
} catch (err) {
resMessage = _.has(err, 'bidiErrObject')
? err.bidiErrObject(id)
: {
id,
type: 'error',
error: errors.UnknownError.error(),
message: (err as Error).message,
stacktrace: (err as Error).stack,
};
}
driverLog.info(`<-- BIDI message #${id}`);
return resMessage;
}
/**
* Log a bidi server error
* @param err
*/
export function onBidiServerError(this: AppiumDriver, err: Error): void {
this.log.warn(`Error from bidi websocket server: ${err}`);
}
/**
* Initialize a new bidi connection
* @param ws The websocket connection object
* @param req The connection pathname, which might include the session id
*/
function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): InitBiDiSocketResult {
const pathname = req.url;
if (!pathname) {
throw new Error('Invalid connection request: pathname missing from request');
}
const bidiSessionRe = new RegExp(`${BIDI_BASE_PATH}/([^/]+)$`);
const bidiNoSessionRe = new RegExp(`${BIDI_BASE_PATH}/?$`);
const sessionMatch = bidiSessionRe.exec(pathname);
const noSessionMatch = bidiNoSessionRe.exec(pathname);
if (!sessionMatch && !noSessionMatch) {
throw new Error(
`Got websocket connection for path ${pathname} but didn't know what to do with it. ` +
`Ignoring and will close the connection`,
);
}
// Let's figure out which driver is going to handle this socket connection. It's either going
// to be a driver matching a session id appended to the bidi base path, or this umbrella driver
// (if no session id is included in the bidi connection request)
let bidiHandlerDriver: AnyDriver;
let proxyClient: WebSocket | null = null;
if (sessionMatch) {
// If we found a session id, see if it matches an active session
const sessionId = sessionMatch[1];
bidiHandlerDriver = this.sessions[sessionId];
if (!bidiHandlerDriver) {
// The session ID sent in doesn't match an active session; just ignore this socket
// connection in that case
throw new Error(
`Got bidi connection request for session with id ${sessionId} which is closed ` +
`or does not exist. Closing the socket connection.`,
);
}
const driverName = bidiHandlerDriver.constructor.name;
this.log.info(`Bidi websocket connection made for session ${sessionId}`);
// store this socket connection for later removal on session deletion. theoretically there
// can be multiple sockets per session
if (!this.bidiSockets[sessionId]) {
this.bidiSockets[sessionId] = [];
}
this.bidiSockets[sessionId].push(ws);
const bidiProxyUrl = bidiHandlerDriver.bidiProxyUrl;
if (bidiProxyUrl) {
try {
new URL(bidiProxyUrl);
} catch {
throw new Error(
`Got request for ${driverName} to proxy bidi connections to upstream socket with ` +
`url ${bidiProxyUrl}, but this was not a valid url`,
);
}
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}`);
proxyClient = new WebSocket(bidiProxyUrl);
this.bidiProxyClients[sessionId] = proxyClient;
}
} else {
this.log.info('Bidi websocket connection made to main server');
// no need to store the socket connection if it's to the main server since it will just
// stay open as long as the server itself is and will close when the server closes.
bidiHandlerDriver = this; // eslint-disable-line @typescript-eslint/no-this-alias
}
const driverLog = bidiHandlerDriver.log;
const logSocketErr: LogSocketError = (err: Error) => {
driverLog.warn(err.message);
};
// This is a function which wraps the 'send' method on a web socket for two reasons:
// 1. Make it async-await friendly
// 2. Do some logging if there's a send error
const sendFactory = (socket: WebSocket) => {
const socketSend = B.promisify(socket.send, {context: socket});
return async (data: string | Buffer) => {
try {
await socketSend(data);
} catch (err) {
logSocketErr(err);
}
};
};
// Construct our send method for sending messages to the client
const send: SendData = sendFactory(ws);
// Construct a conditional send method for proxying messages from the client to an upstream
// bidi socket server (e.g. on a browser)
const sendToProxy: SendData | null = proxyClient ? sendFactory(proxyClient) : null;
return {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr};
}
/**
* Set up handlers on upstream bidi socket we are proxying to/from
*
* @param proxyClient - the websocket connection to/from the
* upstream socket (the one we're proxying to/from)
* @param ws - the websocket connection to/from the client
* @param send - a method used to send data to the
* client
*/
function initBidiProxyHandlers(
this: AnyDriver,
proxyClient: WebSocket,
ws: WebSocket,
send: SendData,
): void {
// Set up handlers for events that might come from the upstream bidi socket connection if
// we're in proxy mode
const driverLog = this.log;
// Here we're receiving a message from the upstream socket server. We want to pass it on to
// the client
proxyClient.on('message', send);
// If the upstream socket server closes the connection, should close the connection to the
// client as well
proxyClient.on('close', (code, reason) => {
driverLog.debug(
`Upstream bidi socket closed connection (code ${code}, reason: '${reason}'). ` +
`Closing proxy connection to client`,
);
const intCode: number = _.isNumber(code) ? (code as number) : parseInt(code, 10);
if (_.isNaN(intCode) || intCode < MIN_WS_CODE_VAL || intCode > MAX_WS_CODE_VAL) {
driverLog.warn(
`Received code ${code} from upstream socket, but this is not a valid ` +
`websocket code. Rewriting to ${WS_FALLBACK_CODE} for ws compatibility`,
);
code = WS_FALLBACK_CODE;
}
ws.close(code, reason);
});
proxyClient.on('error', (err) => {
driverLog.warn(`Got error on upstream bidi socket connection: ${err.message}`);
});
}
/**
* Set up handlers on the bidi socket connection to the client
*
* @param ws - the websocket connection to/from the client
* @param proxyClient - the websocket connection to/from the
* upstream socket (the one we're proxying to/from, if we're proxying)
* @param send - a method used to send data to the
* client
* @param sendToProxy - a method used to send data to the
* upstream socket
* @param bidiHandlerDriver - the driver
* handling the bidi commands
* @param logSocketErr - a special prefixed logger
*/
function initBidiSocketHandlers(
this: AppiumDriver,
ws: WebSocket,
proxyClient: WebSocket | null,
send: SendData,
sendToProxy: SendData | null,
bidiHandlerDriver: AnyDriver,
logSocketErr: LogSocketError,
): void {
const driverLog = bidiHandlerDriver.log;
// Can't do much with random errors on the connection other than log them
ws.on('error', logSocketErr);
ws.on('open', () => {
driverLog.info('BiDi websocket connection is now open');
});
// Now set up handlers for the various events that might happen on the websocket connection
// coming from the client
// First is incoming messages from the client
ws.on('message', async (data: Buffer) => {
if (proxyClient && sendToProxy) {
// if we're meant to proxy to an upstream bidi socket, just do that
await sendToProxy(data.toString('utf8'));
} else {
const res = await this.onBidiMessage(data, bidiHandlerDriver);
await send(JSON.stringify(res));
}
});
// Next consider if the client closes the socket connection on us
ws.on('close', (code, reason) => {
// Not sure if we need to do anything here if the client closes the websocket connection.
// Probably if a session was started via the socket, and the socket closes, we should end the
// associated session to free up resources. But otherwise, for sockets attached to existing
// sessions, doing nothing is probably right.
driverLog.debug(`BiDi socket connection closed (code ${code}, reason: '${reason}')`);
// If we're proxying, might as well close the upstream connection and clean it up
if (proxyClient) {
driverLog.debug('Also closing BiDi proxy socket connection');
proxyClient.close(code, reason);
}
const eventLogCounts = BIDI_EVENTS_MAP.get(bidiHandlerDriver);
if (!_.isEmpty(eventLogCounts)) {
driverLog.debug(`BiDi events statistics: ${JSON.stringify(eventLogCounts, null, 2)}`);
}
});
}
/**
* Set up bidi event listeners
*
* @param ws - the websocket connection to/from the client
* @param bidiHandlerDriver - the driver
* handling the bidi commands
* @param send - a method used to send data to the
* client
*/
function initBidiEventListeners(
this: AppiumDriver,
ws: WebSocket,
bidiHandlerDriver: AnyDriver,
send: SendData,
): void {
// If the driver emits a bidi event that should maybe get sent to the client, check to make
// sure the client is subscribed and then pass it on
const driverLog = bidiHandlerDriver.log;
const driverEe = bidiHandlerDriver.eventEmitter;
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
const eventListener = async ({context, method, params = {}}) => {
// if the driver didn't specify a context, use the empty context
if (!context) {
context = '';
}
if (!method || !params) {
driverLog.warn(
`Driver emitted a bidi event that was malformed. Require method and params keys ` +
`(with optional context). But instead received: ${_.truncate(JSON.stringify({
context,
method,
params,
}), {length: MAX_LOGGED_DATA_LENGTH})}`,
);
return;
}
if (ws.readyState !== WebSocket.OPEN) {
// if the websocket is not still 'open', then we can ignore sending these events
if (ws.readyState > WebSocket.OPEN) {
// if the websocket is closed or closing, we can remove this listener as well to avoid
// leaks
driverEe.removeListener(BIDI_EVENT_NAME, eventListener);
}
return;
}
const eventSubs = bidiHandlerDriver.bidiEventSubs[method];
if (_.isArray(eventSubs) && eventSubs.includes(context)) {
if (method in eventLogCounts) {
++eventLogCounts[method];
} else {
driverLog.info(
`<-- BIDI EVENT ${method} (context: '${context}', ` +
`params: ${_.truncate(JSON.stringify(params), {length: MAX_LOGGED_DATA_LENGTH})}). ` +
`All further similar events won't be logged.`,
);
eventLogCounts[method] = 1;
}
// now we can send the event onto the socket
const ev = {type: 'event', context, method, params};
await send(JSON.stringify(ev));
}
};
driverEe.on(BIDI_EVENT_NAME, eventListener);
}
+22 -32
View File
@@ -12,7 +12,7 @@ import {
import {INSTALL_TYPES} from '../extension/extension-config';
import {toParserArgs} from '../schema/cli-args';
const DRIVER_EXAMPLE = 'xcuitest';
const PLUGIN_EXAMPLE = 'find_by_image';
const PLUGIN_EXAMPLE = 'images';
/**
* This is necessary because we pass the array into `argparse`. `argparse` is bad and mutates things. We don't want that.
@@ -32,7 +32,7 @@ const globalExtensionArgs = new Map([
required: false,
default: false,
action: 'store_true',
help: 'Use JSON for output format',
help: 'Return output in JSON format',
dest: 'json',
},
],
@@ -82,7 +82,7 @@ function makeListArgs(type) {
required: false,
default: false,
action: 'store_true',
help: 'Show information about newer versions',
help: `Show information about available ${type} updates`,
dest: 'showUpdates',
},
],
@@ -92,7 +92,7 @@ function makeListArgs(type) {
required: false,
default: false,
action: 'store_true',
help: 'Show more information about each extension',
help: `Show more information about each ${type}`,
dest: 'verbose',
},
],
@@ -112,9 +112,8 @@ function makeInstallArgs(type) {
{
type: 'str',
help:
`Name of the ${type} to install, for example: ` + type === DRIVER_TYPE
? DRIVER_EXAMPLE
: PLUGIN_EXAMPLE,
`Name of the ${type} to install, for example: ` +
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
},
],
[
@@ -124,7 +123,7 @@ function makeInstallArgs(type) {
default: null,
choices: INSTALL_TYPES_ARRAY,
help:
`Where to look for the ${type} if it is not one of Appium's verified ` +
`Where to look for the ${type} if it is not one of Appium's official ` +
`${type}s. Possible values: ${INSTALL_TYPES_ARRAY.join(', ')}`,
dest: 'installType',
},
@@ -136,9 +135,8 @@ function makeInstallArgs(type) {
default: null,
type: 'str',
help:
`If installing from Git or GitHub, the package name, as defined in the plugin's ` +
`package.json file in the "name" field, cannot be determined automatically, and ` +
`should be reported here, otherwise the install will probably fail.`,
`The Node.js package name of the ${type}. ` +
`Required if "source" is set to "git" or "github".`,
dest: 'packageName',
},
],
@@ -158,9 +156,8 @@ function makeUninstallArgs(type) {
{
type: 'str',
help:
`Name of the ${type} to uninstall, for example: ` + type === DRIVER_TYPE
? DRIVER_EXAMPLE
: PLUGIN_EXAMPLE,
`Name of the ${type} to uninstall, for example: ` +
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
},
],
]);
@@ -179,9 +176,8 @@ function makeDoctorArgs(type) {
{
type: 'str',
help:
`Name of the ${type} to run doctor checks for, for example: ` + type === DRIVER_TYPE
? DRIVER_EXAMPLE
: PLUGIN_EXAMPLE,
`Name of the ${type} to run doctor checks for, for example: ` +
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
},
],
]);
@@ -200,13 +196,10 @@ function makeUpdateArgs(type) {
{
type: 'str',
help:
`Name of the ${type} to update, or the word "installed" to update all installed ` +
`${type}s. To see available updates, run "appium ${type} list --installed --updates". ` +
'For example: ' +
type ===
DRIVER_TYPE
? DRIVER_EXAMPLE
: PLUGIN_EXAMPLE,
`Name of the ${type} to update, or "installed" to update all installed ${type}s. ` +
`To see available ${type} updates, run "appium ${type} list --installed --updates". ` +
'For example: ' +
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
},
],
[
@@ -216,8 +209,7 @@ function makeUpdateArgs(type) {
default: false,
action: 'store_true',
help:
`Include updates that might have a new major revision, and potentially include ` +
`breaking changes`,
'Include any available major revision updates, which may have breaking changes',
},
],
]);
@@ -236,9 +228,8 @@ function makeRunArgs(type) {
{
type: 'str',
help:
`Name of the ${type} to run a script from, for example: ` + type === DRIVER_TYPE
? DRIVER_EXAMPLE
: PLUGIN_EXAMPLE,
`Name of the ${type} to run a script from, for example: ` +
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
},
],
[
@@ -248,9 +239,8 @@ function makeRunArgs(type) {
nargs: '?',
type: 'str',
help:
`Name of the script to run from the ${type}. The script name must be a key ` +
`inside the "appium.scripts" field inside the ${type}'s "package.json" file. ` +
`If not provided then all available script names are going to be listed.`,
`Name of the ${type} script to run. If not provided, return a list ` +
`of available scripts for this ${type}.`,
},
],
]);
+4 -4
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import B from 'bluebird';
import _ from 'lodash';
import path from 'path';
@@ -18,7 +17,7 @@ import {inspect} from 'node:util';
import {pathToFileURL} from 'url';
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
import {npmPackage} from '../utils';
import semver from 'semver';
import * as semver from 'semver';
const UPDATE_ALL = 'installed';
@@ -486,10 +485,11 @@ class ExtensionCliCommand {
getInstallationReceipt({pkg, installPath, installType, installSpec}) {
const {appium, name, version, peerDependencies} = pkg;
const strVersion = /** @type {string} */ (version);
/** @type {import('appium/types').InternalMetadata} */
const internal = {
pkgName: name,
version,
pkgName: /** @type {string} */ (name),
version: strVersion,
installType,
installSpec,
installPath,
+33 -17
View File
@@ -18,9 +18,11 @@ import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
import {rootDir} from '../config';
import {getExtensionArgs, getServerArgs} from './args';
import {
DEFAULT_PLUGINS,
SUBCOMMAND_MOBILE,
SUBCOMMAND_DESKTOP,
SUBCOMMAND_BROWSER,
SUBCOMMAND_RESET,
getPresetDrivers,
determinePlatformName
} from './setup-command';
@@ -53,7 +55,8 @@ class ArgParser {
const parser = new ArgumentParser({
add_help: true,
description:
'A webdriver-compatible server that facilitates automation of web, mobile, and other types of apps across various platforms.',
'A webdriver-compatible server that facilitates automation of web, mobile, and other ' +
'types of apps across various platforms.',
prog,
});
@@ -84,9 +87,6 @@ class ArgParser {
const subParsers = parser.add_subparsers({dest: 'subcommand'});
// add the 'setup' command
ArgParser._addSetupToParser(subParsers);
// add the 'server' subcommand, and store the raw arguments on the parser
// object as a way for other parts of the code to work with the arguments
// conceptually rather than just through argparse
@@ -97,6 +97,9 @@ class ArgParser {
// add the 'driver' and 'plugin' subcommands
ArgParser._addExtensionCommandsToParser(subParsers);
// add the 'setup' command
ArgParser._addSetupToParser(subParsers);
// backwards compatibility / drop-in wrapper
/**
* @type {ArgParser['parseArgs']}
@@ -205,7 +208,8 @@ class ArgParser {
static _addServerToParser(subParser) {
const serverParser = subParser.add_parser('server', {
add_help: true,
help: 'Run an Appium server',
help: 'Start an Appium server',
description: 'Start an Appium server (the "server" subcommand is optional)',
});
ArgParser._patchExit(serverParser);
@@ -227,7 +231,8 @@ class ArgParser {
for (const type of /** @type {[DriverType, PluginType]} */ ([DRIVER_TYPE, PLUGIN_TYPE])) {
const extParser = subParsers.add_parser(type, {
add_help: true,
help: `Access the ${type} management CLI commands`,
help: `Manage Appium ${type}s`,
description: `Manage Appium ${type}s using various subcommands`,
});
ArgParser._patchExit(extParser);
@@ -259,19 +264,17 @@ class ArgParser {
{
command: EXT_SUBCOMMAND_UPDATE,
args: extensionArgs[type].update,
help: `Update installed ${type}s to the latest version`,
help: `Update one or more installed ${type}s to the latest version`,
},
{
command: EXT_SUBCOMMAND_RUN,
args: extensionArgs[type].run,
help:
`Run a script (defined inside the ${type}'s package.json under the ` +
`“scripts” field inside the “appium” field) from an installed ${type}`,
help: `Run a script (if available) from the given ${type}`,
},
{
command: EXT_SUBCOMMAND_DOCTOR,
args: extensionArgs[type].doctor,
help: `Run doctor checks (if any defined) for the given ${type}`,
help: `Run doctor checks (if available) for the given ${type}`,
},
];
@@ -299,9 +302,12 @@ class ArgParser {
static _addSetupToParser(subParser) {
const setupParser = subParser.add_parser('setup', {
add_help: true,
help: `Select a preset of official drivers/plugins to install ` +
`compatible with '${determinePlatformName()}' host platform. ` +
`Existing drivers/plugins will remain. The default preset is 'mobile'.`,
help: 'Batch install or uninstall Appium drivers and plugins',
description:
`Install a preset of official drivers/plugins compatible with the current host platform ` +
`(${determinePlatformName()}). Existing drivers/plugins will remain. The default preset ` +
`is "mobile". Providing the special "reset" subcommand will instead uninstall all ` +
`drivers and plugins, and remove their related manifest files.`,
});
@@ -313,15 +319,25 @@ class ArgParser {
const parserSpecs = [
{
command: SUBCOMMAND_MOBILE,
help: `The preset for mobile devices: ${_.join(getPresetDrivers(SUBCOMMAND_MOBILE), ',')}`
help:
`The preset for mobile devices ` +
`(drivers: ${_.join(getPresetDrivers(SUBCOMMAND_MOBILE), ',')}; plugins: ${DEFAULT_PLUGINS})`
},
{
command: SUBCOMMAND_BROWSER,
help: `The preset for desktop browser drivers: ${_.join(getPresetDrivers(SUBCOMMAND_BROWSER), ',')}`
help:
`The preset for desktop browsers ` +
`(drivers: ${_.join(getPresetDrivers(SUBCOMMAND_BROWSER), ',')}; plugins: ${DEFAULT_PLUGINS})`
},
{
command: SUBCOMMAND_DESKTOP,
help: `The preset for desktop application drivers: ${_.join(getPresetDrivers(SUBCOMMAND_DESKTOP), ',')}`
help:
`The preset for desktop applications ` +
`(drivers: ${_.join(getPresetDrivers(SUBCOMMAND_DESKTOP), ',')}; plugins: ${DEFAULT_PLUGINS})`
},
{
command: SUBCOMMAND_RESET,
help: 'Remove all installed drivers and plugins'
},
];
+3 -1
View File
@@ -27,7 +27,9 @@ export function errAndQuit(json, msg) {
* @param {string} msg - string to log
*/
export function log(json, msg) {
!json && console.log(msg);
if (!json) {
console.log(msg);
}
}
/**
+7 -7
View File
@@ -3,7 +3,7 @@ import _ from 'lodash';
import {system, fs, npm} from '@appium/support';
import axios from 'axios';
import {exec} from 'teen_process';
import semver from 'semver';
import * as semver from 'semver';
import os from 'node:os';
import {npmPackage} from './utils';
import B from 'bluebird';
@@ -115,7 +115,7 @@ export async function getGitRev(useGithubApiFallback = false) {
cwd: __dirname,
});
return stdout.trim();
} catch (ign) {}
} catch {}
}
if (!useGithubApiFallback) {
@@ -132,7 +132,7 @@ export async function getGitRev(useGithubApiFallback = false) {
},
})
).data?.object?.sha;
} catch (ign) {}
} catch {}
return null;
}
@@ -149,7 +149,7 @@ async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
cwd: __dirname,
});
return stdout.trim();
} catch (ign) {}
} catch {}
}
if (!useGithubApiFallback) {
@@ -164,7 +164,7 @@ async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
},
})
).data?.tagger?.date;
} catch (ign) {}
} catch {}
return null;
}
@@ -194,7 +194,7 @@ export function checkNodeOk() {
export async function showBuildInfo() {
await updateBuildInfo(true);
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
console.log(JSON.stringify(getBuildInfo()));
}
/**
@@ -356,7 +356,7 @@ export async function requireDir(root, requireWriteable = true, displayName = 'f
if (requireWriteable) {
try {
await fs.access(root, fs.constants.W_OK);
} catch (e) {
} catch {
throw new Error(
`The ${displayName} '${root}' must be ` +
`writeable for the current user account '${os.userInfo().username}'`
@@ -14,6 +14,7 @@ import {
} from '../schema/schema';
import { pathToFileURL } from 'url';
const DEFAULT_ENTRY_POINT = 'index.js';
/**
* "npm" install type
* Used when extension was installed by npm package name
@@ -567,23 +568,39 @@ export class ExtensionConfig {
const {mainClass} = this.installedExtensions[extName];
const moduleRoot = this.getInstallPath(extName);
const packageJsonPath = path.join(moduleRoot, 'package.json');
let entryPointPath;
let extensionManifest;
try {
entryPointPath = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')).main ?? 'index.js';
extensionManifest = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
} catch (e) {
throw new ReferenceError(`Could not find a ${this.extensionType} installed at ${moduleRoot}`);
throw new ReferenceError(
`Could not read the ${this.extensionType} manifest at ${packageJsonPath}: ${e.message}`
);
}
if (entryPointPath) {
entryPointPath = path.resolve(moduleRoot, entryPointPath);
} else {
throw new ReferenceError(`Cannot find a valid ${this.extensionType} main entry point in '${packageJsonPath}'`);
/** @type {string | undefined} */
let entryPointRelativePath;
try {
if (extensionManifest.type === 'module' && extensionManifest.exports) {
entryPointRelativePath = resolveEsmEntryPoint(extensionManifest.exports);
}
entryPointRelativePath = entryPointRelativePath ?? extensionManifest.main ?? DEFAULT_ENTRY_POINT;
} catch (e) {
throw new ReferenceError(
`Could not find the ${this.extensionType} installed at ${moduleRoot}: ${e.message}`
);
}
const entryPointFullPath = path.resolve(moduleRoot, /** @type {string} */ (entryPointRelativePath));
if (!await fs.exists(entryPointFullPath)) {
throw new ReferenceError(
`Cannot find a valid ${this.extensionType} main entry point in '${packageJsonPath}'. ` +
`Assumed entry point: '${entryPointFullPath}'`
);
}
// note: this will only reload the entry point
if (process.env.APPIUM_RELOAD_EXTENSIONS && require.cache[entryPointPath]) {
log.debug(`Removing ${entryPointPath} from require cache`);
delete require.cache[entryPointPath];
if (process.env.APPIUM_RELOAD_EXTENSIONS && require.cache[entryPointFullPath]) {
log.debug(`Removing ${entryPointFullPath} from require cache`);
delete require.cache[entryPointFullPath];
}
return [entryPointPath, mainClass];
return [entryPointFullPath, mainClass];
}
/**
@@ -672,6 +689,27 @@ export class ExtensionConfig {
}
}
/**
* https://nodejs.org/api/packages.html#package-entry-points
*
* @param {any} exportsValue
* @returns {string | undefined}
*/
export function resolveEsmEntryPoint(exportsValue) {
if (_.isString(exportsValue) && exportsValue) {
return exportsValue;
}
if (!_.isPlainObject(exportsValue)) {
return;
}
for (const key of ['.', 'import']) {
if (exportsValue[key]) {
return resolveEsmEntryPoint(exportsValue[key]);
}
}
}
/**
* An issue with the {@linkcode ExtManifest} for a particular extension.
*
+2 -2
View File
@@ -242,8 +242,8 @@ export class Manifest {
* @type {InternalMetadata}
*/
const internal = {
pkgName: pkgJson.name,
version: pkgJson.version,
pkgName: /** @type {string} */ (pkgJson.name),
version: /** @type {string} */ (pkgJson.version),
appiumVersion: pkgJson.peerDependencies?.appium,
installType,
installSpec: `${pkgJson.name}@${pkgJson.version}`,
+2 -2
View File
@@ -1,4 +1,4 @@
import Ajv from 'ajv';
import {Ajv} from 'ajv';
import addFormats from 'ajv-formats';
import _ from 'lodash';
import path from 'path';
@@ -453,7 +453,7 @@ class AppiumSchema {
let refSchema;
try {
refSchema = this.getSchema($ref);
} catch (err) {
} catch {
// this can happen if an extension schema supplies a $ref to a non-existent schema
throw new SchemaUnknownSchemaError($ref);
}
+2 -2
View File
@@ -247,10 +247,10 @@ export function adjustNodePath() {
// ! so it could break (maybe, eventually).
// See https://gist.github.com/branneman/8048520#7-the-hack
// @ts-ignore see above comment
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('module').Module._initPaths();
return true;
} catch (e) {
} catch {
return false;
}
};
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.13.0",
"version": "2.14.1",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -60,13 +60,13 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.13.0",
"@appium/base-plugin": "^2.2.49",
"@appium/docutils": "^1.0.25",
"@appium/base-driver": "^9.14.1",
"@appium/base-plugin": "^2.2.52",
"@appium/docutils": "^1.0.28",
"@appium/logger": "^1.6.1",
"@appium/schema": "^0.7.0",
"@appium/support": "^5.1.8",
"@appium/types": "^0.22.2",
"@appium/schema": "^0.7.1",
"@appium/support": "^6.0.2",
"@appium/types": "^0.23.0",
"@sidvind/better-ajv-errors": "3.0.1",
"@types/argparse": "2.0.17",
"@types/bluebird": "3.5.42",
@@ -78,10 +78,10 @@
"argparse": "2.0.1",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.7.8",
"axios": "1.7.9",
"bluebird": "3.7.2",
"cross-env": "7.0.3",
"lilconfig": "3.1.2",
"lilconfig": "3.1.3",
"lodash": "4.17.21",
"lru-cache": "10.4.3",
"ora": "5.4.1",
@@ -89,12 +89,12 @@
"resolve-from": "5.0.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.0",
"type-fest": "4.29.1",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"winston": "3.17.0",
"wrap-ansi": "7.0.0",
"ws": "8.18.0",
"yaml": "2.6.1"
"yaml": "2.7.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -1,5 +1,5 @@
{
"devDependencies": {
"webdriverio": "8.40.6"
"webdriverio": "9.5.1"
}
}
@@ -85,10 +85,10 @@ async function init() {
}
try {
({env, util, logger} = require('@appium/support'));
// @ts-ignore
// @ts-ignore This is OK
({runExtensionCommand} = require('../build/lib/cli/extension'));
({DRIVER_TYPE, PLUGIN_TYPE} = require('../build/lib/constants'));
// @ts-ignore
// @ts-ignore This is OK
({loadExtensions} = require('../build/lib/extension'));
logger.getLogger('Appium').level = 'error';
@@ -119,7 +119,7 @@ async function main() {
if (!driverEnv && !pluginEnv) {
spinner.succeed(
wrap(`No drivers or plugins to automatically install.
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".`)
);
return;
@@ -193,7 +193,7 @@ async function checkAndInstallExtension({
spinner,
}) {
const extList = await runExtensionCommand(
// @ts-ignore
// @ts-ignore This is OK
{
appiumHome,
subcommand: type,
@@ -209,7 +209,7 @@ async function checkAndInstallExtension({
}
spinner.start(`Installing ${type} "${ext}"...`);
await runExtensionCommand(
// @ts-ignore
// @ts-ignore this is OK
{
subcommand: type,
appiumHome,
+1 -1
View File
@@ -3,12 +3,12 @@ import {tempDir, fs} from '@appium/support';
import {exec} from 'teen_process';
import B from 'bluebird';
import {
APPIUM_ROOT,
readAppiumArgErrorFixture,
formatAppiumArgErrorOutput,
EXECUTABLE,
runAppiumRaw,
} from './e2e-helpers';
import {APPIUM_ROOT} from '../helpers';
import { stripColorCodes } from '../../lib/logsink';
describe('argument parsing', function () {
@@ -223,6 +223,11 @@ describe('Driver CLI', function () {
});
it('should install a driver from GitHub', async function () {
if (process.env.CI) {
// This test is too slow for CI env
return this.skip();
}
const ret = await runInstall([
'appium/appium-fake-driver',
'--source',
@@ -257,6 +262,11 @@ describe('Driver CLI', function () {
});
it('should install a driver from a remote git repo', async function () {
if (process.env.CI) {
// This test is too slow for CI env
return this.skip();
}
const ret = await runInstall([
'git+https://github.com/appium/appium-fake-driver.git',
'--source',
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getGitRev, getBuildInfo, updateBuildInfo, APPIUM_VER} from '../../lib/config';
import axios from 'axios';
+26 -50
View File
@@ -4,9 +4,9 @@ import {BaseDriver} from '@appium/base-driver';
import {exec} from 'teen_process';
import {fs, tempDir} from '@appium/support';
import axios from 'axios';
import {command} from 'webdriver';
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';
@@ -110,12 +110,14 @@ describe('FakeDriver via HTTP', function () {
* @param {Partial<import('appium/types').ParsedArgs>} [args]
*/
function withServer(args = {}) {
// eslint-disable-next-line mocha/no-sibling-hooks
before(async function () {
args = {...args, appiumHome, port, address: TEST_HOST};
if (shouldStartServer) {
server = await appiumServer(args);
}
});
// eslint-disable-next-line mocha/no-sibling-hooks
after(async function () {
if (server) {
await server.close();
@@ -251,8 +253,6 @@ describe('FakeDriver via HTTP', function () {
await B.delay(250);
await driver.getPageSource().should.eventually.be.rejectedWith(/terminated/);
await driver.getSessions().should.eventually.be.empty;
});
it('should not allow umbrella commands to prevent newCommandTimeout on inner driver', async function () {
@@ -264,6 +264,10 @@ describe('FakeDriver via HTTP', function () {
);
let driver = await wdio({...wdOpts, capabilities: localCaps});
should.exist(driver.sessionId);
driver.addCommand(
'getSessions',
async () => (await axios.get(`${testServerBaseUrl}/sessions`)).data.value
);
// get the session list 6 times over 300ms. each request will be below the new command
// timeout but since they are not received by the driver the session should still time out
@@ -506,51 +510,12 @@ describe('FakeDriver via HTTP', function () {
await driver.deleteSession();
}
});
it.skip('should log a single deprecation warning if a deprecated method is used and not overridden by a newMethodMap', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
try {
driver.addCommand(
'deprecated',
command('POST', '/session/:sessionId/deprecated', {
command: 'deprecated',
description: 'Call a deprecated command',
parameters: [],
ref: '',
}),
);
driver.addCommand(
'doubleClick',
command('POST', '/session/:sessionId/doubleclick', {
command: 'doubleClick',
description: 'Global double click',
parameters: [],
ref: '',
}),
);
await driver
.executeScript('fake: getDeprecatedCommandsCalled', [])
.should.eventually.eql([]);
await driver.deprecated();
await driver.deprecated();
await driver.shake();
// this call should not trigger a deprecation even though deprecated by appium because it's
// overridden as not deprecated by fake driver
await driver.doubleClick();
await driver
.executeScript('fake: getDeprecatedCommandsCalled', [])
.should.eventually.eql(['callDeprecatedCommand', 'mobileShake']);
} finally {
await driver.deleteSession();
}
});
});
describe('Bidi protocol', function () {
withServer();
const capabilities = {...caps, webSocketUrl: true, 'appium:runClock': true};
/** @type import('webdriverio').Browser **/
let driver;
beforeEach(async function () {
@@ -601,6 +566,14 @@ describe('FakeDriver via HTTP', function () {
await B.delay(750);
collectedEvents.should.be.empty;
});
it('should allow custom bidi commands', async function () {
let {result} = await driver.send({method: 'fake.getFakeThing', params: {}});
should.not.exist(result);
await driver.send({method: 'fake.setFakeThing', params: {thing: 'this is from bidi'}});
({result} = await driver.send({method: 'fake.getFakeThing', params: {}}));
result.should.eql('this is from bidi');
});
});
});
@@ -633,15 +606,14 @@ describe('Bidi over SSL', function () {
// since we update the FakeDriver.prototype below, make sure we update the FakeDriver which is
// actually going to be required by Appium
/** @type {import('@appium/types').DriverClass} */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let FakeDriver;
/** @type {string} */
let testServerBaseSessionUrl;
/** @type {import('sinon').SinonSandbox} */
let sandbox;
let certPath = 'certificate.cert';
let keyPath = 'certificate.key';
const capabilities = {...caps, webSocketUrl: true};
let should;
/** @type {string | undefined} */
let previousEnvValue;
before(async function () {
const chai = await import('chai');
@@ -657,7 +629,6 @@ describe('Bidi over SSL', function () {
}
return this.skip();
}
sandbox = createSandbox();
appiumHome = await tempDir.openDir();
wdOpts.port = port = await getTestPort();
testServerBaseUrl = `https://${TEST_HOST}:${port}`;
@@ -672,15 +643,20 @@ describe('Bidi over SSL', function () {
});
after(async function () {
await fs.rimraf(appiumHome);
await server.close();
if (server) {
await fs.rimraf(appiumHome);
await server.close();
}
});
beforeEach(async function () {
previousEnvValue = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
driver = await wdio({...wdOpts, protocol: 'https', strictSSL: false, capabilities});
});
afterEach(async function () {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = previousEnvValue;
if (driver) {
await driver.deleteSession();
}
@@ -6,11 +6,12 @@ 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';
import {rewiremock, BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS} from '../helpers';
import BasePlugin from '@appium/base-plugin';
import {BasePlugin} from '@appium/base-plugin';
const SESSION_ID = '1';
@@ -358,7 +359,7 @@ describe('AppiumDriver', function () {
_.keys(appium.sessions).should.not.contain(sessionId);
});
it('should remove session if inner driver unexpectedly exits with no error', async function () {
let [sessionId] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value; // eslint-disable-line comma-spacing
let [sessionId] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value;
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].eventEmitter.emit('onUnexpectedShutdown');
// let event loop spin so rejection is handled
@@ -2,6 +2,7 @@ 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,3 +1,4 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {finalizeSchema, resetSchema, SchemaFinalizationError} from '../../../lib/schema/schema';
import {rewiremock} from '../../helpers';
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {system} from '@appium/support';
import {getPresetDrivers} from '../../../lib/cli/setup-command';
@@ -1,6 +1,7 @@
// @ts-check
import fs from 'fs';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import YAML from 'yaml';
import * as schema from '../../lib/schema/schema';
@@ -212,14 +213,11 @@ describe('config-file', function () {
});
describe('when the config file is invalid', function () {
beforeEach(function () {
beforeEach(async function () {
lc.search.resolves({
config: {foo: 'bar'},
filepath: '/path/to/file.json',
});
});
beforeEach(async function () {
result = await readConfigFile();
});
+1
View File
@@ -1,6 +1,7 @@
// @ts-check
import _ from 'lodash';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getParser} from '../../lib/cli/parser';
import {
@@ -3,6 +3,7 @@ import path from 'path';
import {version as APPIUM_VER} from '../../../package.json';
import {FAKE_DRIVER_DIR, PROJECT_ROOT, rewiremock} from '../../helpers';
import {initMocks} from './mocks';
import {resolveEsmEntryPoint} from '../../../lib/extension/extension-config';
describe('ExtensionConfig', function () {
/** @type {import('sinon').SinonSandbox} */
@@ -45,7 +46,31 @@ describe('ExtensionConfig', function () {
process.removeAllListeners('exit');
});
describe('constructor', function () {});
describe('ESM module resolution', function () {
it('resolves ESM entry point with simple export', function () {
resolveEsmEntryPoint('./index.js').should.eql('./index.js');
});
it('resolves ESM entry point with dot export', function () {
resolveEsmEntryPoint({
'.': './index.js'
}).should.eql('./index.js');
});
it('resolves ESM entry point with import export', function () {
resolveEsmEntryPoint({
import: './index.js'
}).should.eql('./index.js');
});
it('resolves ESM entry point with complex import export', function () {
resolveEsmEntryPoint({
'.': {
import: './index.js'
}
}).should.eql('./index.js');
});
});
describe('instance method', function () {
/** @type {import('appium/lib/extension/extension-config').ExtensionConfig<DriverType>} */
+4 -1
View File
@@ -1,4 +1,4 @@
/* eslint-disable require-await */
// @ts-check
/**
@@ -7,6 +7,7 @@
import {EventEmitter} from 'events';
import path from 'path';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {version as APPIUM_VER} from '../../../package.json';
@@ -34,6 +35,7 @@ export function initMocks(sandbox = createSandbox()) {
findRoot: /** @type {MockAppiumSupportFs['findRoot']} */ (
sandbox.stub().returns(path.join(__dirname, '..', '..', '..'))
),
exists: /** @type {MockAppiumSupportFs['exists']} */ (sandbox.stub().resolves(true)),
},
env: {
resolveAppiumHome: /** @type {MockAppiumSupportEnv['resolveAppiumHome']} */ (
@@ -171,6 +173,7 @@ export function initMocks(sandbox = createSandbox()) {
* @property {SinonStubbedMember<SupportFs['mkdirp']>} mkdirp
* @property {SinonStubbedMember<SupportFs['readPackageJsonFrom']>} readPackageJsonFrom
* @property {SinonStubbedMember<SupportFs['findRoot']>} findRoot
* @property {SinonStubbedMember<SupportFs['exists']>} exists
*/
/**
@@ -1,5 +1,6 @@
// @ts-check
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {rewiremock} from '../helpers';
+1
View File
@@ -1,4 +1,5 @@
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,5 +1,3 @@
// @ts-check
import _ from 'lodash';
import {PLUGIN_TYPE} from '../../../lib/constants';
import {finalizeSchema, registerSchema, resetSchema} from '../../../lib/schema';
@@ -305,10 +303,6 @@ describe('cli-args', function () {
/`enum` is only supported for `type: 'string'`/i
);
});
it(
'should actually throw earlier by failing schema validation, but that would mean overriding the behavior of `enum` which sounds inadvisable'
);
});
describe('when used with a `string` type', function () {
@@ -1,6 +1,7 @@
// @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,6 +10,7 @@ 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';
+41
View File
@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [9.14.1](https://github.com/appium/appium/compare/@appium/base-driver@9.14.0...@appium/base-driver@9.14.1) (2025-01-06)
### Bug Fixes
* **driver-test-support:** update dependency @types/lodash to v4.17.14 ([#20877](https://github.com/appium/appium/issues/20877)) ([fe209ed](https://github.com/appium/appium/commit/fe209ed660e0f790c98cd51938d0c75712cfd8e4))
## [9.14.0](https://github.com/appium/appium/compare/@appium/base-driver@9.13.1...@appium/base-driver@9.14.0) (2025-01-02)
### Features
* **appium:** Add session.status BiDi command ([#20839](https://github.com/appium/appium/issues/20839)) ([64e768e](https://github.com/appium/appium/commit/64e768efb7bebd6b5a24d55206d1cad00812777c))
* **appium:** allow drivers to define their own bidi commands ([#20828](https://github.com/appium/appium/issues/20828)) ([a917ec6](https://github.com/appium/appium/commit/a917ec6ceda2166fb3dcbff6b2768f700db9e103))
### Bug Fixes
* **base-driver:** Return an empty object if the corresponding API response is undefined ([#20845](https://github.com/appium/appium/issues/20845)) ([c765bc3](https://github.com/appium/appium/commit/c765bc31e03d37ed194fcd52f4e4a2ca1128b260))
* **base-driver:** update dependency express to v4.21.2 ([#20823](https://github.com/appium/appium/issues/20823)) ([c032352](https://github.com/appium/appium/commit/c032352fade2146cdce5feb0906726ad07a30e4f))
* Reduce linter warnings ([#20860](https://github.com/appium/appium/issues/20860)) ([65658cc](https://github.com/appium/appium/commit/65658ccbdde9144c45cb5aad6a9089a5d6f3a0a3))
* **types:** update dependency type-fest to v4 ([#20838](https://github.com/appium/appium/issues/20838)) ([a5897dd](https://github.com/appium/appium/commit/a5897dd25a277a42b0c650a52274ba2c891ac3b0))
* **types:** update dependency type-fest to v4 ([#20843](https://github.com/appium/appium/issues/20843)) ([7abecad](https://github.com/appium/appium/commit/7abecaddd3ed64c7be321650b2a17990e74a7222))
* **types:** update dependency type-fest to v4.31.0 ([#20857](https://github.com/appium/appium/issues/20857)) ([24abb38](https://github.com/appium/appium/commit/24abb385e54f57457c4fb3f2b654cb63645e7ccd))
## [9.13.1](https://github.com/appium/appium/compare/@appium/base-driver@9.13.0...@appium/base-driver@9.13.1) (2024-12-05)
### Bug Fixes
* **base-driver:** add * always for non-separator for backward compatibility ([#20821](https://github.com/appium/appium/issues/20821)) ([6ea0830](https://github.com/appium/appium/commit/6ea0830319c204097233d0659e21b3447b624431))
* **base-driver:** update dependency @types/method-override to v3 ([#20799](https://github.com/appium/appium/issues/20799)) ([42ef484](https://github.com/appium/appium/commit/42ef4841f9a379d8e9ce531eb5375eab03902e3f))
* **support:** update dependency axios to v1.7.9 ([#20811](https://github.com/appium/appium/issues/20811)) ([69f100a](https://github.com/appium/appium/commit/69f100ad3e12030708dee4b8a74005dd41976e37))
* **types:** update dependency type-fest to v4.30.0 ([#20802](https://github.com/appium/appium/issues/20802)) ([8590432](https://github.com/appium/appium/commit/8590432955eb7663e35847db541b9ead3f845a36))
## [9.13.0](https://github.com/appium/appium/compare/@appium/base-driver@9.12.2...@appium/base-driver@9.13.0) (2024-12-02)
@@ -1,6 +1,7 @@
import type {Constraints, Driver, IBidiCommands} from '@appium/types';
import type {Constraints, DriverStatus, IBidiCommands} from '@appium/types';
import type {BaseDriver} from '../driver';
import {mixin} from './mixin';
import _ from 'lodash';
declare module '../driver' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -32,6 +33,19 @@ const BidiCommands: IBidiCommands = {
}
}
},
async bidiStatus<C extends Constraints>(this: BaseDriver<C>): Promise<DriverStatus> {
const result = await this.getStatus();
if (!_.has(result, 'ready')) {
//@ts-ignore This is OK
result.ready = true;
}
if (!_.has(result, 'message')) {
//@ts-ignore This is OK
result.message = `${this.constructor.name} is ready to accept commands`;
}
return result as DriverStatus;
}
};
mixin(BidiCommands);
@@ -5,7 +5,7 @@ import {BaseDriver} from '../driver';
import {mixin} from './mixin';
declare module '../driver' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface BaseDriver<C extends Constraints> extends IFindCommands {}
}
@@ -10,6 +10,5 @@ import {BaseDriver} from '../driver';
* @param mixin Mixin implementation
*/
export function mixin<C extends Constraints, T extends Partial<BaseDriver<C>>>(mixin: T): void {
// eslint-disable-next-line no-restricted-syntax
Object.assign(BaseDriver.prototype, mixin);
}
@@ -76,19 +76,23 @@ const TimeoutCommands: ITimeoutCommands = {
},
// pageLoad
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async pageLoadTimeoutW3C<C extends Constraints>(this: BaseDriver<C>, ms) {
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async pageLoadTimeoutMJSONWP<C extends Constraints>(this: BaseDriver<C>, ms) {
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
},
// script
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async scriptTimeoutW3C<C extends Constraints>(this: BaseDriver<C>, ms) {
throw new errors.NotImplementedError('Not implemented yet for script.');
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async scriptTimeoutMJSONWP<C extends Constraints>(this: BaseDriver<C>, ms) {
throw new errors.NotImplementedError('Not implemented yet for script.');
},
@@ -99,7 +103,7 @@ const TimeoutCommands: ITimeoutCommands = {
},
setImplicitWait<C extends Constraints>(this: BaseDriver<C>, ms: number) {
// eslint-disable-line require-await
this.implicitWaitMs = ms;
this.log.debug(`Set implicit wait to ${ms}ms`);
if (this.managedDrivers && this.managedDrivers.length) {
+42 -19
View File
@@ -1,6 +1,3 @@
/* eslint-disable no-unused-vars */
/* eslint-disable require-await */
import {logger} from '@appium/support';
import type {
AppiumLogger,
@@ -14,7 +11,8 @@ import type {
Protocol,
RouteMatcher,
StringRecord,
BidiMethodDef,
BidiModuleMap,
BiDiResultData,
} from '@appium/types';
import AsyncLock from 'async-lock';
import _ from 'lodash';
@@ -26,8 +24,8 @@ import {
MAX_LOG_BODY_LENGTH,
} from '../constants';
import {errors} from '../protocol';
import DeviceSettings from './device-settings';
import helpers, {BASEDRIVER_VER} from './helpers';
import {DeviceSettings} from './device-settings';
import * as helpers from './helpers';
import {BIDI_COMMANDS} from '../protocol/bidi-commands';
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
@@ -44,7 +42,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
* Make the basedriver version available so for any driver which inherits from this package, we
* know which version of basedriver it inherited from
*/
static baseVersion = BASEDRIVER_VER;
static baseVersion = helpers.BASEDRIVER_VER;
sessionId: string | null;
@@ -112,6 +110,8 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
doesSupportBidi: boolean;
bidiCommands: BidiModuleMap = BIDI_COMMANDS as BidiModuleMap;
constructor(opts: InitialOpts = <InitialOpts>{}, shouldValidateCaps = true) {
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this as Core<C>));
@@ -239,6 +239,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
* method required by MJSONWP in order to determine if the command should
* be proxied directly to the driver
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
driverForSession(sessionId: string): Core<Constraints> | null {
return this as Core<Constraints>;
}
@@ -272,11 +273,13 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
const parseFullName = (fullName: string) => {
const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
if (separatorPos < 0) {
// This should not happen as we preprocess corresponding server arguments in advance
throw new Error(
`The full feature name must include both the driver name/wildcard and the feature ` +
`name split by a colon, got '${fullName}' instead`
);
// TODO: This is for the backward compatibility with Appium 2.
// TODO: We should bring back to raise an Error below for Appium 3.
// throw new Error(
// `The full feature name must include both the driver name/wildcard and the feature ` +
// `name split by a colon, got '${fullName}' instead`
// );
return [ALL_DRIVERS_MATCH, fullName];
}
return [
_.toLower(fullName.substring(0, separatorPos)),
@@ -362,14 +365,17 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
proxyActive(sessionId: string): boolean {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getProxyAvoidList(sessionId: string): RouteMatcher[] {
return [];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
canProxy(sessionId: string): boolean {
return false;
}
@@ -387,6 +393,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
*
* @returns whether the route should be avoided
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
proxyRouteIsAvoided(sessionId: string, method: HTTPMethod, url: string, body?: any): boolean {
for (const avoidSchema of this.getProxyAvoidList(sessionId)) {
if (!_.isArray(avoidSchema) || avoidSchema.length !== 2) {
@@ -426,7 +433,18 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
}
}
async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord): Promise<any> {
updateBidiCommands(cmds: BidiModuleMap): void {
const overlappingKeys = _.intersection(Object.keys(cmds), Object.keys(this.bidiCommands));
if (overlappingKeys.length) {
this.log.warn(`Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`);
}
this.bidiCommands = {
...this.bidiCommands,
...cmds,
};
}
async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord): Promise<BiDiResultData> {
const [moduleName, methodName] = bidiCmd.split('.');
// if we don't get a valid format for bidi command name, reject
@@ -438,12 +456,13 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
);
}
// if the command module isn't part of our spec, reject
if (!BIDI_COMMANDS[moduleName]) {
// if the command module or method isn't part of our spec, reject
if (!this.bidiCommands[moduleName] || !this.bidiCommands[moduleName][methodName]) {
throw new errors.UnknownCommandError();
}
const {command, params} = BIDI_COMMANDS[moduleName][methodName] as BidiMethodDef;
const {command, params} = this.bidiCommands[moduleName][methodName];
// if the command method isn't part of our spec, also reject
if (!command) {
throw new errors.UnknownCommandError();
@@ -477,8 +496,12 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
`Executing bidi command '${bidiCmd}' with params ${logParams} by passing to driver ` +
`method '${command}'`,
);
const res = (await this[command](...args)) ?? null;
this.log.debug(`Responding to bidi command '${bidiCmd}' with ${JSON.stringify(res)}`);
return res;
const response = await this[command](...args);
const finalResponse = _.isUndefined(response) ? {} : response;
this.log.debug(
`Responding to bidi command '${bidiCmd}' with ` +
`${_.truncate(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`
);
return finalResponse;
}
}
@@ -24,7 +24,7 @@ import {calcSignature} from '../helpers/session';
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
import {processCapabilities, validateCaps} from './capabilities';
import {DriverCore} from './core';
import helpers from './helpers';
import * as helpers from './helpers';
const EVENT_SESSION_INIT = 'newSessionRequested';
const EVENT_SESSION_START = 'newSessionStarted';
@@ -389,8 +389,8 @@ export function parseCapsArray(cap) {
if (_.isArray(parsedCaps)) {
return parsedCaps;
}
} catch (ign) {
logger.warn(`Failed to parse capability as JSON array`);
} catch (e) {
logger.warn(`Failed to parse capability as JSON array: ${e.message}`);
}
if (_.isString(cap)) {
return [cap];
@@ -406,6 +406,7 @@ export function parseCapsArray(cap) {
* This parameter is deprecated and is not used.
* @returns {string}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function generateDriverLogPrefix(obj, sessionId = null) {
return `${obj.constructor.name}@${node.getObjectId(obj).substring(0, 4)}`;
}
@@ -49,7 +49,7 @@ const startLogFormatter = morgan(
reqBody = _.truncate(_.isString(req.body) ? req.body : JSON.stringify(req.body), {
length: MAX_LOG_BODY_LENGTH,
});
} catch (ign) {}
} catch {}
}
log.info(requestStartLoggingFormat(tokens, req, res), reqBody.grey);
},
+2 -1
View File
@@ -60,7 +60,7 @@ async function createServer (app, cliArgs) {
}
const [cert, key] = await B.all(certKey.map((p) => fs.readFile(p, 'utf8')));
log.debug('Enabling TLS/SPDY on the server using the provided certificate');
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('spdy').createServer({
cert,
key,
@@ -151,6 +151,7 @@ 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,4 +1,4 @@
/* eslint-disable require-await */
import _ from 'lodash';
import B from 'bluebird';
@@ -41,7 +41,7 @@ async function removeWebSocketHandler(handlerPathname) {
client.terminate();
}
return true;
} catch (ign) {
} catch {
// ignore
} finally {
delete this.webSocketsMapping[handlerPathname];
+1 -1
View File
@@ -4,7 +4,7 @@ try {
B.config({
cancellation: true,
});
} catch (ign) {
} catch {
// sometimes during testing this somehow gets required twice and results in an error about
// cancellation not being able to be enabled after promise has been configured
}
@@ -207,7 +207,7 @@ class JWProxy {
if (typeof body !== 'object') {
try {
reqOpts.data = JSON.parse(body);
} catch (e) {
} catch {
throw new Error(`Cannot interpret the request body as valid JSON: ${truncateBody(body)}`);
}
} else {
@@ -13,6 +13,10 @@ const BIDI_COMMANDS = /** @type {const} */ ({
command: 'bidiUnsubscribe',
params: SUBSCRIPTION_REQUEST_PARAMS,
},
status: {
command: 'bidiStatus',
params: {},
}
},
browsingContext: {
navigate: {
+4 -6
View File
@@ -60,16 +60,14 @@ export class ProtocolError extends BaseError {
* Get the Bidi protocol version of an error
* @param {string|number} id - the id used in the request for which this error forms the response
* @see https://w3c.github.io/webdriver-bidi/#protocol-definition
* @returns The object conforming to the shape of a BiDi error response
* @returns {import('@appium/types').ErrorBiDiCommandResponse} The object conforming to the shape of a BiDi error response
*/
bidiErrObject(id) {
// if we don't have an id, the client didn't send one, so we have nothing to send back.
// send back an empty string rather than making something up
if (_.isNil(id)) {
id = '';
}
// send back zero rather than making something up
const intId = /** @type {number} */ (_.isInteger(id) ? id : (parseInt(`${id}`, 10) || 0));
return {
id,
id: intId,
type: 'error',
error: this.error,
stacktrace: this.stacktrace,
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "9.13.0",
"version": "9.14.1",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -44,21 +44,21 @@
"test:types": "tsd"
},
"dependencies": {
"@appium/support": "^5.1.8",
"@appium/types": "^0.22.2",
"@appium/support": "^6.0.2",
"@appium/types": "^0.23.0",
"@colors/colors": "1.6.0",
"@types/async-lock": "1.4.2",
"@types/bluebird": "3.5.42",
"@types/express": "5.0.0",
"@types/lodash": "4.17.13",
"@types/method-override": "0.0.35",
"@types/lodash": "4.17.14",
"@types/method-override": "3.0.0",
"@types/serve-favicon": "2.5.7",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.7.8",
"axios": "1.7.9",
"bluebird": "3.7.2",
"body-parser": "1.20.3",
"express": "4.21.1",
"express": "4.21.2",
"http-status-codes": "2.3.0",
"lodash": "4.17.21",
"lru-cache": "10.4.3",
@@ -67,7 +67,7 @@
"path-to-regexp": "8.2.0",
"serve-favicon": "2.5.0",
"source-map-support": "0.5.21",
"type-fest": "4.29.1",
"type-fest": "4.31.0",
"validate.js": "0.13.1"
},
"optionalDependencies": {
@@ -1,5 +1,6 @@
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';
@@ -85,7 +86,11 @@ describe('server', function () {
}).should.be.rejectedWith(/EADDRINUSE/);
});
it('should not wait for the server close connections before finishing closing', async function () {
let bodyPromise = axios.get(`http://${TEST_HOST}:${port}/pause`).catch(() => {});
const bodyPromise = (async () => {
try {
return await axios.get(`http://${TEST_HOST}:${port}/pause`);
} catch {}
})();
// relinquish control so that we don't close before the request is received
await B.delay(100);
@@ -188,11 +193,11 @@ describe('server plugins', function () {
afterEach(async function () {
try {
await hwServer.close();
} catch (ign) {}
} catch {}
});
function updaterWithGetRoute(route, reply) {
// eslint-disable-next-line require-await
return async (app, httpServer) => {
app.get(`/${route}`, (req, res) => {
res.header['content-type'] = 'text/html';
@@ -1,4 +1,4 @@
/* eslint-disable require-await */
import {errors, BaseDriver, determineProtocol} from '../../../lib';
import {PROTOCOLS} from '../../../lib/constants';
import {util} from '@appium/support';
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-named-as-default
import Express from 'express';
import bodyParser from 'body-parser';
@@ -1,6 +1,7 @@
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';
@@ -917,7 +918,7 @@ describe('Protocol', function () {
});
it('should pass on any errors in proxying', async function () {
// eslint-disable-next-line require-await
driver.proxyReqRes = async function () {
throw new Error('foo');
};
@@ -938,7 +939,7 @@ describe('Protocol', function () {
});
it('should able to throw ProxyRequestError in proxying', async function () {
// eslint-disable-next-line require-await
driver.proxyReqRes = async function () {
let jsonwp = {
status: 35,
@@ -961,7 +962,7 @@ describe('Protocol', function () {
});
it('should let the proxy handle req/res', async function () {
// eslint-disable-next-line require-await
driver.proxyReqRes = async function (req, res) {
res.status(200).json({custom: 'data'});
};
@@ -3,6 +3,7 @@
import B from 'bluebird';
import {BaseDriver, errors} from '../../../lib/index';
import {validator} from '../../../lib/basedriver/desired-caps';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
// TODO: we need module-level mocks for the logger
@@ -1,7 +1,8 @@
// @ts-check
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import _ from 'lodash';
import BaseDriver from '../../../../lib';
import {BaseDriver} from '../../../../lib';
const FIRST_LOGS = ['first', 'logs'];
const SECOND_LOGS = ['second', 'logs'];
@@ -1,4 +1,4 @@
import BaseDriver from '../../../lib';
import {BaseDriver} from '../../../lib';
import {driverUnitTestSuite} from '@appium/driver-test-support';
driverUnitTestSuite(BaseDriver, {
@@ -1,6 +1,7 @@
// @ts-check
import BaseDriver from '../../../lib';
import {BaseDriver} from '../../../lib';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
describe('timeout', function () {
@@ -2,6 +2,7 @@
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';
@@ -31,6 +32,8 @@ describe('server configuration', function () {
const chaiAsPromised = await import('chai-as-promised');
chai.use(chaiAsPromised.default);
should = chai.should();
port = await getTestPort(true);
});
function fakeApp() {
@@ -50,10 +53,6 @@ describe('server configuration', function () {
return app;
}
before(async function () {
port = await getTestPort(true);
});
beforeEach(function () {
sandbox = createSandbox();
});
@@ -1,6 +1,5 @@
// transpile:mocha
import {welcome} from '../../../lib/express/static';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
describe('welcome', function () {
@@ -20,7 +20,7 @@ function resFixture(url, method) {
throw new Error("Can't handle url " + url);
}
// eslint-disable-next-line require-await
async function request(opts) {
const {url, method, json} = opts;
if (/badurl$/.test(url)) {
@@ -17,7 +17,7 @@ function buildReqRes(url, method, body) {
res.send = (body) => {
try {
body = JSON.parse(body);
} catch (e) {}
} catch {}
res.sentBody = body;
};
return [req, res];
@@ -32,6 +32,7 @@ describe('proxy', function () {
const chaiAsPromised = await import('chai-as-promised');
chai.use(chaiAsPromised.default);
should = chai.should();
port = await getTestPort();
});
function mockProxy(opts = {}) {
@@ -44,10 +45,6 @@ describe('proxy', function () {
return proxy;
}
before(async function () {
port = await getTestPort();
});
it('should override default params', function () {
let j = mockProxy({server: '127.0.0.2', port});
j.server.should.equal('127.0.0.2');
@@ -1,6 +1,4 @@
// transpile:mocha
import {_} from 'lodash';
import _ from 'lodash';
import {METHOD_MAP, routeToCommandName} from '../../../lib/protocol';
import crypto from 'crypto';
@@ -53,11 +51,6 @@ describe('Protocol', function () {
cmdName.should.equal('timeouts');
});
it('should properly lookup correct command name for endpoint with session', function () {
const cmdName = routeToCommandName('/timeouts/implicit_wait', 'POST');
cmdName.should.equal('implicitWait');
});
it('should properly lookup correct command name for endpoint without session', function () {
const cmdName = routeToCommandName('/status', 'GET');
cmdName.should.equal('getStatus');
+25
View File
@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.52](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.51...@appium/base-plugin@2.2.52) (2025-01-06)
**Note:** Version bump only for package @appium/base-plugin
## [2.2.51](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.50...@appium/base-plugin@2.2.51) (2025-01-02)
### Bug Fixes
* Reduce linter warnings ([#20860](https://github.com/appium/appium/issues/20860)) ([65658cc](https://github.com/appium/appium/commit/65658ccbdde9144c45cb5aad6a9089a5d6f3a0a3))
## [2.2.50](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.49...@appium/base-plugin@2.2.50) (2024-12-05)
**Note:** Version bump only for package @appium/base-plugin
## [2.2.49](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.48...@appium/base-plugin@2.2.49) (2024-12-02)
**Note:** Version bump only for package @appium/base-plugin
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-plugin",
"version": "2.2.49",
"version": "2.2.52",
"description": "The base plugin used to create Appium 2.0 plugins",
"keywords": [
"automation",
@@ -40,8 +40,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.13.0",
"@appium/support": "^5.1.8"
"@appium/base-driver": "^9.14.1",
"@appium/support": "^6.0.2"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -1,4 +1,4 @@
import BasePlugin from '../../lib/plugin';
import {BasePlugin} from '../../lib/plugin';
describe('base plugin', function () {
let should;
-702
View File
@@ -1,702 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.15](https://github.com/appium/appium/compare/@appium/doctor@2.1.14...@appium/doctor@2.1.15) (2024-12-02)
**Note:** Version bump only for package @appium/doctor
## [2.1.14](https://github.com/appium/appium/compare/@appium/doctor@2.1.13...@appium/doctor@2.1.14) (2024-11-29)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.6.1 ([7e19c88](https://github.com/appium/appium/commit/7e19c88b79ea8c79489db54fcb3d56d9b2ab56dd))
* **doctor:** update dependency appium-adb to v12.6.2 ([#20735](https://github.com/appium/appium/issues/20735)) ([d046657](https://github.com/appium/appium/commit/d04665777a0296e5d5bbc93ddc74329887cf9677))
* **doctor:** update dependency appium-adb to v12.7.0 ([#20738](https://github.com/appium/appium/issues/20738)) ([9d21f56](https://github.com/appium/appium/commit/9d21f56edb4081f09a1255a665e589103fafe781))
## [2.1.13](https://github.com/appium/appium/compare/@appium/doctor@2.1.12...@appium/doctor@2.1.13) (2024-10-24)
**Note:** Version bump only for package @appium/doctor
## [2.1.12](https://github.com/appium/appium/compare/@appium/doctor@2.1.11...@appium/doctor@2.1.12) (2024-10-15)
**Note:** Version bump only for package @appium/doctor
## [2.1.11](https://github.com/appium/appium/compare/@appium/doctor@2.1.10...@appium/doctor@2.1.11) (2024-09-16)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.5.2 ([db28605](https://github.com/appium/appium/commit/db286055324c980459f12163ec8253c0b90c1754))
* **doctor:** update dependency appium-adb to v12.5.3 ([048ce52](https://github.com/appium/appium/commit/048ce520d83f31ceab393f5fe46588274fd2ef56))
* **doctor:** update dependency appium-adb to v12.6.0 ([42d7ed8](https://github.com/appium/appium/commit/42d7ed8afe199584cb3850b4a5826d0cb1dc8dc2))
## [2.1.10](https://github.com/appium/appium/compare/@appium/doctor@2.1.9...@appium/doctor@2.1.10) (2024-08-07)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.5.0 ([b7b97a2](https://github.com/appium/appium/commit/b7b97a24d66e5c561b4c9559a4b0aea25503f39a))
* **doctor:** update dependency appium-adb to v12.5.1 ([82b7515](https://github.com/appium/appium/commit/82b7515867c10c20843dd02c37c7a2e9014c3703))
## [2.1.9](https://github.com/appium/appium/compare/@appium/doctor@2.1.8...@appium/doctor@2.1.9) (2024-07-10)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.4.6 ([5281f5a](https://github.com/appium/appium/commit/5281f5a01a63e12454093cb296286da02adc77ee))
* **doctor:** update dependency appium-adb to v12.4.8 ([177dff4](https://github.com/appium/appium/commit/177dff4f0ed84880ce9df55f94ff62b922a80ae5))
* **support:** update dependency teen_process to v2.1.10 ([86f843b](https://github.com/appium/appium/commit/86f843b9e1fc56576d7a08bf95838a58971e98b3))
* **support:** update dependency teen_process to v2.1.7 ([5226fa8](https://github.com/appium/appium/commit/5226fa897b90e9fec9be3e79a7d48fc7b4e0016d))
* **support:** update dependency teen_process to v2.2.0 ([06607da](https://github.com/appium/appium/commit/06607da92e93d015747fd6d6d5bdabe1b3ac58d2))
## [2.1.8](https://github.com/appium/appium/compare/@appium/doctor@2.1.7...@appium/doctor@2.1.8) (2024-06-28)
**Note:** Version bump only for package @appium/doctor
## [2.1.7](https://github.com/appium/appium/compare/@appium/doctor@2.1.6...@appium/doctor@2.1.7) (2024-06-27)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.4.3 ([4345a40](https://github.com/appium/appium/commit/4345a40a15d2473bb13524593ab66ff8ae3d8a7e))
* **doctor:** update dependency appium-adb to v12.4.4 ([15234bc](https://github.com/appium/appium/commit/15234bc05502157ebf847856d38e35da4d542df7))
* **support:** update dependency teen_process to v2.1.5 ([bc388db](https://github.com/appium/appium/commit/bc388db1116f411f4b241105f5feae30418c9a27))
* **support:** update dependency teen_process to v2.1.6 ([5412461](https://github.com/appium/appium/commit/541246184dc7521c825ec1902775e681ea6f0db3))
## [2.1.6](https://github.com/appium/appium/compare/@appium/doctor@2.1.5...@appium/doctor@2.1.6) (2024-06-11)
**Note:** Version bump only for package @appium/doctor
## [2.1.5](https://github.com/appium/appium/compare/@appium/doctor@2.1.4...@appium/doctor@2.1.5) (2024-06-11)
**Note:** Version bump only for package @appium/doctor
## [2.1.4](https://github.com/appium/appium/compare/@appium/doctor@2.1.3...@appium/doctor@2.1.4) (2024-06-11)
**Note:** Version bump only for package @appium/doctor
## [2.1.3](https://github.com/appium/appium/compare/@appium/doctor@2.1.2...@appium/doctor@2.1.3) (2024-06-10)
**Note:** Version bump only for package @appium/doctor
## [2.1.2](https://github.com/appium/appium/compare/@appium/doctor@2.1.1...@appium/doctor@2.1.2) (2024-06-06)
**Note:** Version bump only for package @appium/doctor
## [2.1.1](https://github.com/appium/appium/compare/@appium/doctor@2.1.0...@appium/doctor@2.1.1) (2024-06-06)
**Note:** Version bump only for package @appium/doctor
## [2.1.0](https://github.com/appium/appium/compare/@appium/doctor@2.0.37...@appium/doctor@2.1.0) (2024-06-06)
### Features
* **appium:** Replace npmlog with the local fork ([#20190](https://github.com/appium/appium/issues/20190)) ([8915934](https://github.com/appium/appium/commit/8915934270243bfb46c4d104a098ce1cc481b0ff))
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.4.1 ([e3e1757](https://github.com/appium/appium/commit/e3e1757c564cffd467f3e9931f9aaa5a973fa825))
* **doctor:** update dependency appium-adb to v12.4.2 ([bea47aa](https://github.com/appium/appium/commit/bea47aacca1b1a27cce48b027246513e674c2bb7))
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
## [2.0.37](https://github.com/appium/appium/compare/@appium/doctor@2.0.36...@appium/doctor@2.0.37) (2024-05-27)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.0.9 ([fd63e9b](https://github.com/appium/appium/commit/fd63e9bdbd0c941453963cfb9636972129a3b57a))
* **doctor:** update dependency appium-adb to v12.2.0 ([e961a9b](https://github.com/appium/appium/commit/e961a9b9c5347ed85317de9bfc5687ad74a711ff))
* **doctor:** update dependency appium-adb to v12.3.2 ([f764c38](https://github.com/appium/appium/commit/f764c38c29c657133dec1ea3f0e3c6c2663ee7df))
* **doctor:** update dependency appium-adb to v12.4.0 ([33be2ee](https://github.com/appium/appium/commit/33be2ee45d725e6a64fc24683dfbcbff29dfbe84))
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
## [2.0.36](https://github.com/appium/appium/compare/@appium/doctor@2.0.35...@appium/doctor@2.0.36) (2024-04-21)
**Note:** Version bump only for package @appium/doctor
## [2.0.35](https://github.com/appium/appium/compare/@appium/doctor@2.0.34...@appium/doctor@2.0.35) (2024-04-16)
**Note:** Version bump only for package @appium/doctor
## [2.0.34](https://github.com/appium/appium/compare/@appium/doctor@2.0.33...@appium/doctor@2.0.34) (2024-04-08)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.0.4 ([df07665](https://github.com/appium/appium/commit/df076655098db68c986079037a794ce3477314bd))
* **doctor:** update dependency appium-adb to v12.0.5 ([a6d49c1](https://github.com/appium/appium/commit/a6d49c1ac84c98ebe085abf32161d2f39c172d5a))
* **doctor:** update dependency appium-adb to v12.0.6 ([26ab0ef](https://github.com/appium/appium/commit/26ab0ef7595580722f9a63a1c1e5c3bbef3b18f1))
* **doctor:** update dependency appium-adb to v12.0.7 ([3936cb4](https://github.com/appium/appium/commit/3936cb43dceb45e1386c9418453a0dd734e32215))
* **doctor:** update dependency appium-adb to v12.0.8 ([1f36882](https://github.com/appium/appium/commit/1f368828e6b3093a7317853e94437bb79955c399))
## [2.0.33](https://github.com/appium/appium/compare/@appium/doctor@2.0.32...@appium/doctor@2.0.33) (2024-02-13)
**Note:** Version bump only for package @appium/doctor
## [2.0.32](https://github.com/appium/appium/compare/@appium/doctor@2.0.31...@appium/doctor@2.0.32) (2024-02-06)
### Bug Fixes
* **doctor:** update dependency appium-adb to v11.1.0 ([cbc1f50](https://github.com/appium/appium/commit/cbc1f500bc0721edcdf7f47af949ae113c038a46))
* **doctor:** update dependency appium-adb to v12 ([#19648](https://github.com/appium/appium/issues/19648)) ([9617974](https://github.com/appium/appium/commit/961797448116b6be7b500a68b4202b969805825c))
* **doctor:** update dependency appium-adb to v12.0.1 ([b85a6b9](https://github.com/appium/appium/commit/b85a6b939f688703522ebb6ddab19aa807c64dbe))
* **doctor:** update dependency appium-adb to v12.0.2 ([5936d53](https://github.com/appium/appium/commit/5936d53d7856e6a2aa31236eaa0ea9afee737c95))
* **doctor:** update dependency appium-adb to v12.0.3 ([#19722](https://github.com/appium/appium/issues/19722)) ([6cd2c54](https://github.com/appium/appium/commit/6cd2c547def390eb5d739d6245fe0bab1f99931a))
## [2.0.31](https://github.com/appium/appium/compare/@appium/doctor@2.0.30...@appium/doctor@2.0.31) (2024-01-03)
### Bug Fixes
* **doctor:** update dependency appium-adb to v11.0.9 ([3465495](https://github.com/appium/appium/commit/34654956a3cd5a76865444ab52bd905543dfcb2c))
## [2.0.30](https://github.com/appium/appium/compare/@appium/doctor@2.0.29...@appium/doctor@2.0.30) (2023-12-18)
### Bug Fixes
* **support:** update dependency teen_process to v2.0.109 ([85dce4a](https://github.com/appium/appium/commit/85dce4a72b21e017b4661ddf997c096817e5fd7f))
* **support:** update dependency teen_process to v2.0.110 ([b602693](https://github.com/appium/appium/commit/b60269371662de4c42ccd7586512c9d685d95d52))
* **support:** update dependency teen_process to v2.0.112 ([#19491](https://github.com/appium/appium/issues/19491)) ([d2d8ea7](https://github.com/appium/appium/commit/d2d8ea7a105eb93f59793fbc4d3438a66a191cd8))
* **support:** update dependency teen_process to v2.1.1 ([b79ed2b](https://github.com/appium/appium/commit/b79ed2bed5329b904b15fa5b1fbde7885087c9ad))
## [2.0.29](https://github.com/appium/appium/compare/@appium/doctor@2.0.28...@appium/doctor@2.0.29) (2023-12-04)
### Bug Fixes
* **doctor:** update dependency appium-adb to v11.0.8 ([610be26](https://github.com/appium/appium/commit/610be26cbb177f4f3b43a9f94166fd832b56476e))
* **support:** update dependency teen_process to v2.0.100 ([e50b53f](https://github.com/appium/appium/commit/e50b53fbea4aa3709adc3df4baf4945124d6a217))
* **support:** update dependency teen_process to v2.0.101 ([b837f8c](https://github.com/appium/appium/commit/b837f8ce14bd48edb6af62bca718a708d2b65f09))
* **support:** update dependency teen_process to v2.0.87 ([4df1712](https://github.com/appium/appium/commit/4df1712d9b0c3155e95e38a1aa552f036d37dcfe))
* **support:** update dependency teen_process to v2.0.89 ([c129111](https://github.com/appium/appium/commit/c1291116989159eb7e08d303354bc00262a9e784))
* **support:** update dependency teen_process to v2.0.91 ([6cedd48](https://github.com/appium/appium/commit/6cedd485d7007455151c6efc03dd6172d4a31259))
* **support:** update teen_process-related packages ([c637b05](https://github.com/appium/appium/commit/c637b052f45e0ae7ce68d29b9739718549302756))
## [2.0.28](https://github.com/appium/appium/compare/@appium/doctor@2.0.27...@appium/doctor@2.0.28) (2023-11-14)
### Bug Fixes
* **doctor:** update dependency appium-adb to v11.0.3 ([ee1df47](https://github.com/appium/appium/commit/ee1df47b172009717b81cce7b3465219791b1f97))
* **doctor:** update dependency appium-adb to v11.0.5 ([#19362](https://github.com/appium/appium/issues/19362)) ([b82f13b](https://github.com/appium/appium/commit/b82f13b893c44bcaf9908a9ebe96d3bb51c1f6b3))
* **doctor:** update dependency appium-adb to v11.0.6 ([c2f543f](https://github.com/appium/appium/commit/c2f543fc1009067141bc6cf50a2d0e47f1a455b2))
* **doctor:** update dependency appium-adb to v11.0.7 ([261cfc8](https://github.com/appium/appium/commit/261cfc8694f9eeab774712e67bbf46e72615efa7))
* **support:** update definitelytyped ([5ae8df3](https://github.com/appium/appium/commit/5ae8df3c36c7f03fbf3420087b532086f6742348))
* **support:** update dependency teen_process to v2.0.57 ([1fbd503](https://github.com/appium/appium/commit/1fbd503a66b965a9b32bbb6702062861a8480333))
* **support:** update dependency teen_process to v2.0.63 ([fa57192](https://github.com/appium/appium/commit/fa571925b7ac980cc99c8d23f659420eacd8546c))
* **support:** update dependency teen_process to v2.0.64 ([332bb59](https://github.com/appium/appium/commit/332bb59ee3fdd14c03f6b3c4fd6587a30319d568))
* **support:** update dependency teen_process to v2.0.66 ([3205b5d](https://github.com/appium/appium/commit/3205b5d06f073f1e5c7735c6e36ad08fdf091182))
* **support:** update dependency teen_process to v2.0.67 ([3b5ae2a](https://github.com/appium/appium/commit/3b5ae2ab17c74c9890b53c081aec146152f11c7a))
* **support:** update dependency teen_process to v2.0.73 ([8e453be](https://github.com/appium/appium/commit/8e453bed3f440c1dcf82f016f7d4431365b9cdff))
* **support:** update dependency teen_process to v2.0.75 ([fbb6fd9](https://github.com/appium/appium/commit/fbb6fd9a2780eb00c15584abeaf644a1fcdecb4d))
* **support:** update teen_process-related packages ([84d96db](https://github.com/appium/appium/commit/84d96dbae45dfe56218d32330f8ff2d26b750fb7))
* **support:** update teen_process-related packages ([3d0b97c](https://github.com/appium/appium/commit/3d0b97cae76c616da0aea5b92cebcd38d59263b7))
## [2.0.27](https://github.com/appium/appium/compare/@appium/doctor@2.0.26...@appium/doctor@2.0.27) (2023-10-19)
### Bug Fixes
* **doctor:** update dependency appium-adb to v11 ([#19321](https://github.com/appium/appium/issues/19321)) ([408885a](https://github.com/appium/appium/commit/408885a5b28f8691dbbb3716f3c1a409d662275b))
* **support:** update definitelytyped ([a306ce7](https://github.com/appium/appium/commit/a306ce741a806d21bc44f3b979803b8af5da99aa))
## [2.0.26](https://github.com/appium/appium/compare/@appium/doctor@2.0.25...@appium/doctor@2.0.26) (2023-10-18)
### Bug Fixes
* **doctor:** update dependency appium-adb to v10 ([#19308](https://github.com/appium/appium/issues/19308)) ([3315671](https://github.com/appium/appium/commit/33156712b142b1cb2ffdcf1cfdf65f5b504e7751))
* **doctor:** update dependency appium-adb to v9.14.10 ([809b2da](https://github.com/appium/appium/commit/809b2daea5304942c849b7015f0a74447fb7b57c))
* **doctor:** update dependency appium-adb to v9.14.11 ([c81e99f](https://github.com/appium/appium/commit/c81e99fd80739d97b3dc2099a43cc66245035bc5))
* **doctor:** update dependency appium-adb to v9.14.12 ([0220a9f](https://github.com/appium/appium/commit/0220a9f8e9a890f84031cfeaea026fceb0a303e9))
* **doctor:** update dependency appium-adb to v9.14.7 ([#19058](https://github.com/appium/appium/issues/19058)) ([f8039a7](https://github.com/appium/appium/commit/f8039a70ca851dcc120a2b217e14e3e3b922ba21))
* **doctor:** update dependency appium-adb to v9.14.9 ([9f700bf](https://github.com/appium/appium/commit/9f700bfdf9e09de3cea9f81029fadef84b8e956b))
* **opencv:** update definitelytyped ([d2a9a99](https://github.com/appium/appium/commit/d2a9a99418af9ce9b569bb9b98ee396faab932bb))
* **support:** update definitelytyped ([b6a76ce](https://github.com/appium/appium/commit/b6a76ce91e2765c22f84e389b93f780e0b4490c0))
* **support:** update dependency @types/teen_process to v2.0.1 ([d2f19aa](https://github.com/appium/appium/commit/d2f19aab935cc6ec00a51f042b5f02674a772198))
* **support:** update dependency teen_process to v2.0.10 ([bc1b56a](https://github.com/appium/appium/commit/bc1b56a3c162ed8686d3d7956c30eb1f7682b1a7))
* **support:** update dependency teen_process to v2.0.12 ([c5d6df7](https://github.com/appium/appium/commit/c5d6df7a000d64e8591a56ce5ef65baa2071a36b))
* **support:** update dependency teen_process to v2.0.13 ([a0c4b84](https://github.com/appium/appium/commit/a0c4b84fb7d3977b7a4ca0a07fadd49ee355791a))
* **support:** update dependency teen_process to v2.0.18 ([ffeebc7](https://github.com/appium/appium/commit/ffeebc74b72e5eadf658325e67708ae6ed5b8346))
* **support:** update dependency teen_process to v2.0.19 ([2ef40fe](https://github.com/appium/appium/commit/2ef40febdc717f54ea618ecfe1058b858fe2daf9))
* **support:** update dependency teen_process to v2.0.23 ([1b0459e](https://github.com/appium/appium/commit/1b0459edd656514d8706aab8e21cd4d40de2ec5d))
* **support:** update dependency teen_process to v2.0.24 ([bc72c66](https://github.com/appium/appium/commit/bc72c6676a5412b60e74bb143a223738ce059977))
* **support:** update dependency teen_process to v2.0.27 ([03b2aa6](https://github.com/appium/appium/commit/03b2aa6b6e45fa0ea24c4691b039255bc4399733))
* **support:** update dependency teen_process to v2.0.30 ([7cdb142](https://github.com/appium/appium/commit/7cdb1429a1104ad3699547f2a2dd9b237135891a))
* **support:** update dependency teen_process to v2.0.34 ([#19218](https://github.com/appium/appium/issues/19218)) ([c28fcef](https://github.com/appium/appium/commit/c28fcefa725fbce5a0464e57946051e3cb023d85))
* **support:** update dependency teen_process to v2.0.37 ([6d894be](https://github.com/appium/appium/commit/6d894bef6f377ebfdffdeb1adde7a72624844414))
* **support:** update dependency teen_process to v2.0.38 ([a32dc11](https://github.com/appium/appium/commit/a32dc1110125d0f1fac9c66c17f1309145a634c7))
* **support:** update dependency teen_process to v2.0.40 ([2837bcd](https://github.com/appium/appium/commit/2837bcdeb19532fe973a2902db5c8d730252d962))
* **support:** update dependency teen_process to v2.0.41 ([1a34396](https://github.com/appium/appium/commit/1a34396339f71d40353d323feaf78afcf2269fc9))
* **support:** update dependency teen_process to v2.0.42 ([e2a670e](https://github.com/appium/appium/commit/e2a670e1c4a0f061b0bfb08a6b534d9deb7bdd9c))
* **support:** update dependency teen_process to v2.0.45 ([ea731bf](https://github.com/appium/appium/commit/ea731bf117d680496fce453ac42cfee7f05ada60))
* **support:** update dependency teen_process to v2.0.48 ([eebc651](https://github.com/appium/appium/commit/eebc651f31c282baa7e77a6384d3ee8efc0072d6))
* **support:** update dependency teen_process to v2.0.50 ([ca3e5d9](https://github.com/appium/appium/commit/ca3e5d90e4c51fc1dda32154e1d77e65cb44fb54))
* **support:** update dependency teen_process to v2.0.9 ([6c7f3ef](https://github.com/appium/appium/commit/6c7f3ef670d441bdc4b693948a19dcbdc54e4764))
## [2.0.25](https://github.com/appium/appium/compare/@appium/doctor@2.0.24...@appium/doctor@2.0.25) (2023-08-22)
**Note:** Version bump only for package @appium/doctor
## [2.0.24](https://github.com/appium/appium/compare/@appium/doctor@2.0.23...@appium/doctor@2.0.24) (2023-08-21)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.14.5 ([d5f3a28](https://github.com/appium/appium/commit/d5f3a288c2497d4020b7eabc667814744e69ac48))
## [2.0.23](https://github.com/appium/appium/compare/@appium/doctor@2.0.22...@appium/doctor@2.0.23) (2023-08-17)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.14.4 ([#18844](https://github.com/appium/appium/issues/18844)) ([a9e86aa](https://github.com/appium/appium/commit/a9e86aabc657bf22df8b1c5f67ab93acc0ced934))
* **doctor:** update dependency inquirer to v8.2.6 ([#18940](https://github.com/appium/appium/issues/18940)) ([a8cc551](https://github.com/appium/appium/commit/a8cc551210c31b014025f7c56b091163f4d15df1))
* **test-support:** update dependency @colors/colors to v1.6.0 ([1358937](https://github.com/appium/appium/commit/1358937db2edf08ce1ebe3dff2f70ac6b07cd373))
## [2.0.22](https://github.com/appium/appium/compare/@appium/doctor@2.0.21...@appium/doctor@2.0.22) (2023-07-24)
### Bug Fixes
* **support:** update dependency teen_process to v2.0.4 ([151c19c](https://github.com/appium/appium/commit/151c19c1c3b4c9b94aba10033c2d863f567d849b))
## [2.0.21](https://github.com/appium/appium/compare/@appium/doctor@2.0.20...@appium/doctor@2.0.21) (2023-07-03)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.11.7 ([fdd2ac0](https://github.com/appium/appium/commit/fdd2ac098d1b6e0d17b1429defa0123660d85ca9))
## [2.0.20](https://github.com/appium/appium/compare/@appium/doctor@2.0.19...@appium/doctor@2.0.20) (2023-06-29)
**Note:** Version bump only for package @appium/doctor
## [2.0.19](https://github.com/appium/appium/compare/@appium/doctor@2.0.18...@appium/doctor@2.0.19) (2023-06-15)
**Note:** Version bump only for package @appium/doctor
## [2.0.18](https://github.com/appium/appium/compare/@appium/doctor@2.0.17...@appium/doctor@2.0.18) (2023-06-14)
**Note:** Version bump only for package @appium/doctor
## [2.0.17](https://github.com/appium/appium/compare/@appium/doctor@2.0.16...@appium/doctor@2.0.17) (2023-06-14)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.11.3 ([a0b434d](https://github.com/appium/appium/commit/a0b434dff7a4e425f66915e12266a6e943d10372))
* **doctor:** update dependency appium-adb to v9.11.4 ([f34c75c](https://github.com/appium/appium/commit/f34c75c65068a299ab4d3427233e771e796fc43a))
* **doctor:** update dependency appium-adb to v9.11.5 ([78c6794](https://github.com/appium/appium/commit/78c67942534705133093aa9fa54d84efb9fe2f69))
## [2.0.16](https://github.com/appium/appium/compare/@appium/doctor@2.0.15...@appium/doctor@2.0.16) (2023-05-19)
**Note:** Version bump only for package @appium/doctor
## [2.0.15](https://github.com/appium/appium/compare/@appium/doctor@2.0.14...@appium/doctor@2.0.15) (2023-05-17)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.11.2 ([e7df6fd](https://github.com/appium/appium/commit/e7df6fd4f054d6ad6eb4125f77e7e0d58bc05ebf))
* **docutils:** update dependency yargs to v17.7.2 ([3d1e4af](https://github.com/appium/appium/commit/3d1e4af858ea21bcf3ab1e1015aa7bdf1776f3ce))
## [2.0.14](https://github.com/appium/appium/compare/@appium/doctor@2.0.13...@appium/doctor@2.0.14) (2023-04-20)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.11.1 ([45cd434](https://github.com/appium/appium/commit/45cd43488ce3e7a3c32864912d8fb104934f90d8))
## [2.0.13](https://github.com/appium/appium/compare/@appium/doctor@2.0.12...@appium/doctor@2.0.13) (2023-04-14)
**Note:** Version bump only for package @appium/doctor
## [2.0.12](https://github.com/appium/appium/compare/@appium/doctor@2.0.11...@appium/doctor@2.0.12) (2023-04-10)
**Note:** Version bump only for package @appium/doctor
## [2.0.11](https://github.com/appium/appium/compare/@appium/doctor@2.0.10...@appium/doctor@2.0.11) (2023-04-03)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.11.0 ([d8b6334](https://github.com/appium/appium/commit/d8b633406a68e88cdd945906dde3f23a2a566a71))
## [2.0.10](https://github.com/appium/appium/compare/@appium/doctor@2.0.9...@appium/doctor@2.0.10) (2023-03-28)
**Note:** Version bump only for package @appium/doctor
## [2.0.9](https://github.com/appium/appium/compare/@appium/doctor@2.0.8...@appium/doctor@2.0.9) (2023-03-08)
**Note:** Version bump only for package @appium/doctor
## [2.0.8](https://github.com/appium/appium/compare/@appium/doctor@2.0.7...@appium/doctor@2.0.8) (2023-02-24)
### Bug Fixes
* **docutils:** update dependency yargs to v17.7.0 ([70d65b7](https://github.com/appium/appium/commit/70d65b7d6230dd415f26aaba6d097a3cbe876b47))
* **docutils:** update dependency yargs to v17.7.1 ([2602016](https://github.com/appium/appium/commit/2602016eb38c73d2d9a40f94e95277faf2444305))
## [2.0.7](https://github.com/appium/appium/compare/@appium/doctor@2.0.6...@appium/doctor@2.0.7) (2023-02-09)
**Note:** Version bump only for package @appium/doctor
## [2.0.6](https://github.com/appium/appium/compare/@appium/doctor@2.0.5...@appium/doctor@2.0.6) (2023-01-23)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.10.22 ([3b213d4](https://github.com/appium/appium/commit/3b213d40e72ccec850f5efb48777b0aee2918cc4))
* **doctor:** update dependency appium-adb to v9.10.23 ([34081b4](https://github.com/appium/appium/commit/34081b44c88a4b2d820eed864c1e9ab36c217d2c))
* **doctor:** update dependency appium-adb to v9.10.24 ([f76a5a7](https://github.com/appium/appium/commit/f76a5a71c96cddac5ad8b8f12d2c03270a28e330))
## [2.0.5](https://github.com/appium/appium/compare/@appium/doctor@2.0.4...@appium/doctor@2.0.5) (2023-01-13)
**Note:** Version bump only for package @appium/doctor
## [2.0.4](https://github.com/appium/appium/compare/@appium/doctor@2.0.3...@appium/doctor@2.0.4) (2023-01-13)
**Note:** Version bump only for package @appium/doctor
## [2.0.3](https://github.com/appium/appium/compare/@appium/doctor@2.0.2...@appium/doctor@2.0.3) (2023-01-13)
**Note:** Version bump only for package @appium/doctor
## [2.0.2](https://github.com/appium/appium/compare/@appium/doctor@2.0.1...@appium/doctor@2.0.2) (2023-01-13)
### Bug Fixes
* **doctor:** update dependency appium-adb to v9.10.20 ([29bf77c](https://github.com/appium/appium/commit/29bf77cf5a623c7417d9c996814498f76cead2a3))
* **doctor:** update dependency appium-adb to v9.10.21 ([ac2c735](https://github.com/appium/appium/commit/ac2c73596810919dcd1c6d294e8c9496eb91e0b9))
## [2.0.1](https://github.com/appium/appium/compare/@appium/doctor@2.0.0...@appium/doctor@2.0.1) (2022-12-21)
### Bug Fixes
- **doctor:** update dependency appium-adb to v9.10.19 ([2cb1af4](https://github.com/appium/appium/commit/2cb1af4357e0e867c28c7045825140d45fa02393))
# [2.0.0](https://github.com/appium/appium/compare/@appium/doctor@1.16.28...@appium/doctor@2.0.0) (2022-12-14)
### Bug Fixes
- **doctor:** update dependency appium-adb to v9.10.16 ([b3e8b47](https://github.com/appium/appium/commit/b3e8b470895e09d8933ad5c10dbbe17c3ad34941))
- **doctor:** update dependency appium-adb to v9.10.17 ([3e815d2](https://github.com/appium/appium/commit/3e815d2a358720fec3b072d92f125dad47be5ca0))
- **doctor:** update dependency appium-adb to v9.10.18 ([8a285cd](https://github.com/appium/appium/commit/8a285cdeceb044d88f657ccd4d5e6939bb15f4eb))
- **doctor:** update dependency inquirer to v8 ([4dd2a46](https://github.com/appium/appium/commit/4dd2a46338a0fb26421c39418c5e20282a400eb3))
- **gulp-plugins:** update dependency yargs to v17.6.1 ([ea1b6a3](https://github.com/appium/appium/commit/ea1b6a3334ee5330abd456a7d4467fc92d94270d))
- **gulp-plugins:** update dependency yargs to v17.6.2 ([a95b2e0](https://github.com/appium/appium/commit/a95b2e0fe6fc646ad5e1fb3479b448ad3a66c880))
- **opencv:** update definitelytyped ([32557f4](https://github.com/appium/appium/commit/32557f4bca5acc2f89cfd3a70f369cebeb94c588))
- chore!: set engines to minimum Node.js v14.17.0 ([a1dbe6c](https://github.com/appium/appium/commit/a1dbe6c43efe76604943a607d402f4c8b864d652))
### Features
- **doctor:** print APPIUM_HOME ([#17625](https://github.com/appium/appium/issues/17625)) ([22d5b2f](https://github.com/appium/appium/commit/22d5b2fd9e0b1f65d5cb78b4dcd45b095537cae1))
- experimental support for typedoc generation ([4746080](https://github.com/appium/appium/commit/4746080e54ed8bb494cbc7c6ce83db503bf6bb52))
### BREAKING CHANGES
- Appium now supports version range `^14.17.0 || ^16.13.0 || >=18.0.0`
## [1.16.28](https://github.com/appium/appium/compare/@appium/doctor@1.16.27...@appium/doctor@1.16.28) (2022-10-14)
**Note:** Version bump only for package @appium/doctor
## [1.16.27](https://github.com/appium/appium/compare/@appium/doctor@1.16.26...@appium/doctor@1.16.27) (2022-10-13)
**Note:** Version bump only for package @appium/doctor
## [1.16.26](https://github.com/appium/appium/compare/@appium/doctor@1.16.25...@appium/doctor@1.16.26) (2022-09-07)
**Note:** Version bump only for package @appium/doctor
## [1.16.25](https://github.com/appium/appium/compare/@appium/doctor@1.16.24...@appium/doctor@1.16.25) (2022-08-10)
**Note:** Version bump only for package @appium/doctor
## [1.16.24](https://github.com/appium/appium/compare/@appium/doctor@1.16.23...@appium/doctor@1.16.24) (2022-08-03)
### Bug Fixes
- **appium,base-driver,base-plugin,doctor,docutils,eslint-config-appium,execute-driver-plugin,fake-driver,fake-plugin,gulp-plugins,images-plugin,opencv,relaxed-caps-plugin,schema,support,test-support,types,universal-xml-plugin:** update engines ([d8d2382](https://github.com/appium/appium/commit/d8d2382327ba7b7db8a4d1cad987c0e60184c92d))
## [1.16.23](https://github.com/appium/appium/compare/@appium/doctor@1.16.22...@appium/doctor@1.16.23) (2022-07-28)
### Bug Fixes
- **doctor:** actually build bin dir, fix dev script ([10421fa](https://github.com/appium/appium/commit/10421fa5ad8c9d2e5916e5a701fefdeb3f5b9969))
- moved type packages to deps of specific packages ([f9129df](https://github.com/appium/appium/commit/f9129dfee32fcc3f89ffcfa69fb83b7c2419c24f))
## [1.16.22](https://github.com/appium/appium/compare/@appium/doctor@1.16.21...@appium/doctor@1.16.22) (2022-06-04)
**Note:** Version bump only for package @appium/doctor
## [1.16.21](https://github.com/appium/appium/compare/@appium/doctor@1.16.20...@appium/doctor@1.16.21) (2022-05-31)
**Note:** Version bump only for package @appium/doctor
## [1.16.20](https://github.com/appium/appium/compare/@appium/doctor@1.16.19...@appium/doctor@1.16.20) (2022-05-31)
### Bug Fixes
- **appium:** fix extension autoinstall postinstall script ([3e2c05d](https://github.com/appium/appium/commit/3e2c05d8a290072484afde34fe5fd968618f6359)), closes [#16924](https://github.com/appium/appium/issues/16924)
## [1.16.19](https://github.com/appium/appium/compare/@appium/doctor@1.16.18...@appium/doctor@1.16.19) (2022-05-03)
**Note:** Version bump only for package @appium/doctor
## [1.16.18](https://github.com/appium/appium/compare/@appium/doctor@1.16.17...@appium/doctor@1.16.18) (2022-05-02)
**Note:** Version bump only for package @appium/doctor
## [1.16.17](https://github.com/appium/appium/compare/@appium/doctor@1.16.16...@appium/doctor@1.16.17) (2022-04-20)
**Note:** Version bump only for package @appium/doctor
## [1.16.16](https://github.com/appium/appium/compare/@appium/doctor@1.16.15...@appium/doctor@1.16.16) (2022-04-20)
**Note:** Version bump only for package @appium/doctor
## [1.16.15](https://github.com/appium/appium/compare/@appium/doctor@1.16.14...@appium/doctor@1.16.15) (2022-04-20)
**Note:** Version bump only for package @appium/doctor
## [1.16.14](https://github.com/appium/appium/compare/@appium/doctor@1.16.13...@appium/doctor@1.16.14) (2022-04-12)
**Note:** Version bump only for package @appium/doctor
## [1.16.13](https://github.com/appium/appium/compare/@appium/doctor@1.16.12...@appium/doctor@1.16.13) (2022-04-12)
**Note:** Version bump only for package @appium/doctor
## [1.16.12](https://github.com/appium/appium/compare/@appium/doctor@1.16.11...@appium/doctor@1.16.12) (2022-04-07)
**Note:** Version bump only for package @appium/doctor
## [1.16.11](https://github.com/appium/appium/compare/@appium/doctor@1.16.10...@appium/doctor@1.16.11) (2022-03-23)
**Note:** Version bump only for package @appium/doctor
## [1.16.10](https://github.com/appium/appium/compare/@appium/doctor@1.16.9...@appium/doctor@1.16.10) (2022-03-22)
**Note:** Version bump only for package @appium/doctor
## [1.16.9](https://github.com/appium/appium/compare/@appium/doctor@1.16.8...@appium/doctor@1.16.9) (2022-01-21)
**Note:** Version bump only for package @appium/doctor
## [1.16.8](https://github.com/appium/appium/compare/@appium/doctor@1.16.7...@appium/doctor@1.16.8) (2022-01-11)
### Bug Fixes
- **support:** allow @u4/opencv4nodejs in addition to the old one, and promote it in doctor instead ([7d8d547](https://github.com/appium/appium/commit/7d8d547fbd5f234922a38c79239391c83d48aa5b))
- Switch colors package to a non-compomised repository ([#16317](https://github.com/appium/appium/issues/16317)) ([40a6f05](https://github.com/appium/appium/commit/40a6f054dca3d94fc88773af9c6336ba12ebfb81))
## [1.16.7](https://github.com/appium/appium/compare/@appium/doctor@1.16.6...@appium/doctor@1.16.7) (2021-11-19)
### Bug Fixes
- **doctor:** move index to support cjs wrapper ([69820ea](https://github.com/appium/appium/commit/69820eaf9369969ebbad038ca53accf973d18aa4))
## [1.16.6](https://github.com/appium/appium/compare/@appium/doctor@1.16.5...@appium/doctor@1.16.6) (2021-11-15)
**Note:** Version bump only for package @appium/doctor
## [1.16.5](https://github.com/appium/appium/compare/@appium/doctor@1.16.4...@appium/doctor@1.16.5) (2021-11-09)
**Note:** Version bump only for package @appium/doctor
## [1.16.4](https://github.com/appium/appium/compare/@appium/doctor@1.16.3...@appium/doctor@1.16.4) (2021-09-16)
**Note:** Version bump only for package @appium/doctor
## [1.16.3](https://github.com/appium/appium/compare/@appium/doctor@1.16.2...@appium/doctor@1.16.3) (2021-09-14)
**Note:** Version bump only for package @appium/doctor
## [1.16.2](https://github.com/appium/appium/compare/@appium/doctor@1.16.1...@appium/doctor@1.16.2) (2021-09-14)
**Note:** Version bump only for package @appium/doctor
## 1.16.1 (2021-08-16)
# 2.0.0-beta (2021-08-13)
### Bug Fixes
- **doctor:** update reference to @appium/idb README ([c20a13e](https://github.com/appium/appium/commit/c20a13ea95070c1fb705147f65254b50af761b95))
- **doctor:** use @appium/test-support ([80c9919](https://github.com/appium/appium/commit/80c991998540faad464ba764df612a443db35ff2))
-39
View File
@@ -1,39 +0,0 @@
# @appium/doctor
> Attempts to diagnose and fix common Appium configuration issues
[![NPM version](http://img.shields.io/npm/v/@appium/doctor.svg)](https://npmjs.org/package/@appium/doctor)
[![Downloads](http://img.shields.io/npm/dm/@appium/doctor.svg)](https://npmjs.org/package/@appium/doctor)
> [!WARNING]
> This package has been deprecated since the Appium server v 2.4.0 and will be removed in the future.
> Use doctor checks (if any exist) integrated into your installed driver or plugin by
> running `appium driver doctor <driver_name>` or `appium plugin doctor <plugin_name>`.
### Install
```
npm install @appium/doctor -g
```
### Usage
```
➜ appium-doctor -h
Usage: appium-doctor.js [options, defaults: --ios --android]
Options:
--ios Check iOS setup [boolean]
--android Check Android setup [boolean]
--dev Check dev setup [boolean]
--debug Show debug messages [boolean]
--yes Always respond yes [boolean]
--no Always respond no [boolean]
--demo Run appium-doctor demo (for dev). [boolean]
-h, --help Show help [boolean]
```
## License
Apache-2.0
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env node
module.exports = require('./build/bin/appium-doctor');
-51
View File
@@ -1,51 +0,0 @@
// transpile:main
import yargs from 'yargs';
import newDoctor from '../lib/factory';
import {configureBinaryLog} from '../lib/utils';
import {configure as configurePrompt} from '../lib/prompt';
import {system} from '@appium/support';
yargs
.strict()
.usage('Usage: $0 [options, defaults: --ios --android]')
.boolean('ios')
.describe('ios', 'Check iOS setup')
.boolean('android')
.describe('android', 'Check Android setup')
.boolean('dev')
.describe('dev', 'Check dev setup')
.boolean('debug')
.describe('debug', 'Show debug messages')
.boolean('yes')
.describe('yes', 'Always respond yes')
.boolean('no')
.describe('no', 'Always respond no')
.boolean('demo')
.describe('demo', 'Run appium-doctor demo (for dev).')
.help('h')
.alias('h', 'help')
.check(function (argv) {
if (!argv.ios && !argv.android && !argv.demo) {
argv.ios = system.isMac();
argv.android = true;
}
return true;
});
// make sure we use the general checks for every test
let opts = Object.assign(
{
general: true,
},
yargs.argv
);
configurePrompt(opts);
configureBinaryLog(opts);
newDoctor(opts)
.run()
.catch(function (e) {
console.error(e); // eslint-disable-line no-console
process.exit(1);
});
-165
View File
@@ -1,165 +0,0 @@
import {DoctorCheck} from './doctor';
import {ok, nok, okOptional, nokOptional, resolveExecutablePath} from './utils';
import {system, fs} from '@appium/support';
import path from 'path';
import EnvVarAndPathCheck from './env';
import '@colors/colors';
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';
import log from './logger';
/**
* @type {import('./factory').DoctorCheckList}
*/
const checks = [];
const javaHome = system.isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME';
checks.push(new EnvVarAndPathCheck('ANDROID_HOME'));
checks.push(new EnvVarAndPathCheck('JAVA_HOME'));
// Check that the PATH includes the jdk's bin directory
export class JavaOnPathCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
if (process.env.JAVA_HOME) {
const javaHomeBin = path.resolve(process.env.JAVA_HOME, 'bin');
return (await fs.exists(javaHomeBin))
? ok(`'bin' subfolder exists under '${process.env.JAVA_HOME}'`)
: nok(
`'bin' subfolder does not exist under '${process.env.JAVA_HOME}'. ` +
`Is ${javaHome} set to a proper value?`
);
}
return nok(
`Cannot check ${javaHome} requirements since the environment variable itself is not set`
);
}
/**
* @override
*/
async fix() {
return `Set ${javaHome} environment variable to the root folder path of your local JDK installation`;
}
}
// Check tools
export class AndroidToolCheck extends DoctorCheck {
constructor() {
super();
this.tools = ['adb', 'emulator', `apkanalyzer${system.isWindows() ? '.bat' : ''}`];
this.noBinaries = [];
}
/**
* @override
*/
async diagnose() {
const listOfTools = this.tools.join(', ');
const sdkRoot = getSdkRootFromEnv();
if (!sdkRoot) {
return nok(
`${listOfTools} could not be found because ANDROID_HOME is NOT set!`
);
}
log.info(` Checking ${listOfTools}`);
for (const binary of this.tools) {
try {
log.info(` '${binary}' is in ${await getAndroidBinaryPath(binary)}`);
} catch (e) {
this.noBinaries.push(binary);
}
}
if (this.noBinaries.length > 0) {
return nok(`${this.noBinaries.join(', ')} could NOT be found in ${sdkRoot}!`);
}
return ok(`${listOfTools} exist: ${sdkRoot}`);
}
/**
* @override
*/
async fix() {
if (typeof process.env.ANDROID_HOME === 'undefined') {
return `Manually configure ${'ANDROID_HOME'.bold} and run appium-doctor again.`;
}
return (
`Manually install ${this.noBinaries.join(', ').bold} and add it to ${'PATH'.bold}. ` +
'https://developer.android.com/studio#cmdline-tools and ' +
'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.'
);
}
}
checks.push(new AndroidToolCheck());
checks.push(new JavaOnPathCheck());
export class OptionalAppBundleCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const bundletoolPath = await resolveExecutablePath('bundletool.jar');
return bundletoolPath
? okOptional(`bundletool.jar is installed at: ${bundletoolPath}`)
: nokOptional('bundletool.jar cannot be found');
}
/**
* @override
*/
async fix() {
return (
`${
'bundletool.jar'.bold
} is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it` +
`${
system.isWindows()
? '. Also consider adding the ".jar" extension into your PATHEXT environment variable in order to fix the problem for Windows'
: ''
}`
);
}
}
checks.push(new OptionalAppBundleCheck());
export class OptionalGstreamerCheck extends DoctorCheck {
GSTREAMER_BINARY = `gst-launch-1.0${system.isWindows() ? '.exe' : ''}`;
GST_INSPECT_BINARY = `gst-inspect-1.0${system.isWindows() ? '.exe' : ''}`;
/**
* @override
*/
async diagnose() {
const gstreamerPath = await resolveExecutablePath(this.GSTREAMER_BINARY);
const gstInspectPath = await resolveExecutablePath(this.GST_INSPECT_BINARY);
return gstreamerPath && gstInspectPath
? okOptional(
`${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY} are installed at: ${gstreamerPath} and ${gstInspectPath}`
)
: nokOptional(`${this.GSTREAMER_BINARY} and/or ${this.GST_INSPECT_BINARY} cannot be found`);
}
/**
* @override
*/
async fix() {
return (
`${
`${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY}`.bold
} are used to stream the screen of the device under test. ` +
'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details'
);
}
}
checks.push(new OptionalGstreamerCheck());
export {EnvVarAndPathCheck};
export default checks;
-88
View File
@@ -1,88 +0,0 @@
// demo rule to test the gui
import {ok, nok} from './utils';
import {fs} from '@appium/support';
import {exec} from 'teen_process';
import {DoctorCheck, FixSkippedError} from './doctor';
import log from './logger';
import {fixIt} from './prompt';
/**
* @type {import('./factory').DoctorCheckList}
*/
const checks = [];
export class DirCheck extends DoctorCheck {
/**
* @param {string} path
*/
constructor(path) {
super({autofix: false});
this.path = path;
}
/**
* @override
*/
async diagnose() {
if (!(await fs.exists(this.path))) {
return nok(`Could NOT find directory at '${this.path}'!`);
}
let stats = await fs.lstat(this.path);
return stats.isDirectory()
? ok(`Found directory at: ${this.path}`)
: nok(`'${this.path}' is NOT a directory!`);
}
/**
* @override
*/
async fix() {
return `Manually create a directory at: ${this.path}`;
}
}
checks.push(new DirCheck('/tmp/appium-doctor'));
checks.push(new DirCheck('/tmp/appium-doctor/demo'));
export class FileCheck extends DoctorCheck {
/**
* @param {string} path
*/
constructor(path) {
super({autofix: true});
this.path = path;
}
/**
* @override
*/
async diagnose() {
return (await fs.exists(this.path))
? ok(`Found file at: ${this.path}`)
: nok(`Could NOT find file at '${this.path}'!`);
}
/**
* @override
*/
async fix() {
log.info(`The following command need be executed: touch '${this.path}'`);
let yesno = await fixIt();
if (yesno === 'yes') {
await exec('touch', [this.path]);
} else {
log.info(`Skipping. You will need to touch '${this.path}' manually.`);
throw new FixSkippedError('bbb');
}
return null;
}
}
checks.push(
new FileCheck('/tmp/appium-doctor/demo/apple.fruit'),
new FileCheck('/tmp/appium-doctor/demo/pear.fruit'),
new FileCheck('/tmp/appium-doctor/demo/orange.fruit'),
);
export default checks;
-87
View File
@@ -1,87 +0,0 @@
import {DoctorCheck} from './doctor';
import {ok, nok, resolveExecutablePath} from './utils';
import {fs, system} from '@appium/support';
import path from 'path';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
const checks = [];
// Check PATH binaries
export class BinaryIsInPathCheck extends DoctorCheck {
/**
* @param {string} binary
*/
constructor(binary) {
super();
this.binary = binary;
}
/**
* @override
*/
async diagnose() {
const resolvedPath = await resolveExecutablePath(this.binary);
if (!resolvedPath) {
return nok(`${this.binary} is MISSING in PATH: ${process.env.PATH}`);
}
return ok(`${this.binary} was found at ${resolvedPath}`);
}
/**
* @override
*/
async fix() {
return `Manually install the ${this.binary.bold} binary and add it to ${'PATH'.bold}.`;
}
}
checks.push(
new BinaryIsInPathCheck(system.isWindows() ? 'mvn.bat' : 'mvn'),
new BinaryIsInPathCheck(system.isWindows() ? 'ant.bat' : 'ant'),
new BinaryIsInPathCheck(system.isWindows() ? 'adb.exe' : 'adb'),
);
// Check Android SDKs
export class AndroidSdkExists extends DoctorCheck {
/**
* @param {string} sdk
*/
constructor(sdk) {
super();
this.sdk = sdk;
}
/**
* @override
*/
async diagnose() {
if (typeof process.env.ANDROID_HOME === 'undefined') {
return nok(`${this.sdk} could not be found because ANDROID_HOME is NOT set!`);
}
let sdkPath = path.resolve(process.env.ANDROID_HOME, path.join('platforms', this.sdk));
return (await fs.exists(sdkPath))
? ok(`${this.sdk} was found at: ${sdkPath}`)
: nok(`${this.sdk} could NOT be found at '${sdkPath}'!`);
}
/**
* @override
*/
async fix() {
if (typeof process.env.ANDROID_HOME === 'undefined') {
return `Manually configure ${'ANDROID_HOME'.bold}.`;
}
return `Manually install the ${this.sdk.bold} sdk.`;
}
}
checks.push(
new AndroidSdkExists('android-16'),
new AndroidSdkExists('android-19'),
);
export default checks;
-284
View File
@@ -1,284 +0,0 @@
import '@colors/colors';
import _ from 'lodash';
import log from './logger';
import {fs} from '@appium/support';
const {version} = fs.readPackageJsonFrom(__dirname);
class FixSkippedError extends Error {}
/**
* Create interface for other Doctors
*/
class DoctorCheck {
/**
* @param {DoctorOpts} [opts={}]
*/
constructor(opts = {}) {
this.autofix = !!opts.autofix;
}
/**
* Every doctor diagnose the symptoms
* @returns {Promise<import('./utils').CheckResult>}
* @throws {Error}
*/
async diagnose() {
throw new Error('Not Implemented!');
}
/**
* Every doctor suggest the solutions to fix the sickness
* @returns {Promise<string|null>}
* @throws {Error}
*/
async fix() {
// return string for manual fixes.
throw new Error('Not Implemented!');
}
}
class Doctor {
constructor() {
/**
* All the sub check goes here after register
* @type {DoctorCheck[]}
*/
this.checks = [];
/** @type {DoctorCheck[]} */
this.checkOptionals = [];
/** @type {DoctorIssue[]} */
this.toFix = [];
/** @type {DoctorIssue[]} */
this.toFixOptionals = [];
}
/**
* Register all the sub check and combine them together
* @param {DoctorCheck[] | DoctorCheck} checks
*/
register(checks) {
this.checks.push(...(Array.isArray(checks) ? checks : [checks]));
}
/**
* The doctor shows the report
*/
async diagnose() {
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
this.toFix = [];
for (const check of this.checks) {
const res = await check.diagnose();
if (res.optional) {
this.checkOptionals.push(check);
continue;
}
await this.diagnosticResultMessage(res, this.toFix, check);
}
log.info(
`### Diagnostic for necessary dependencies completed, ${await this.fixMessage(
this.toFix.length
)}. ###`
);
log.info('');
log.info(`### Diagnostic for ${'optional'.yellow} dependencies starting ###`);
this.toFixOptionals = [];
for (const checkOptional of this.checkOptionals) {
await this.diagnosticResultMessage(
await checkOptional.diagnose(),
this.toFixOptionals,
checkOptional
);
}
log.info(
`### Diagnostic for optional dependencies completed, ${await this.fixMessage(
this.toFixOptionals.length,
true
)}. ###`
);
log.info('');
}
/**
* @param {DoctorIssue[]} fixes
* @param {DoctorIssue[]} optionalFixes
*/
async reportManualFixes(fixes, optionalFixes) {
const manualFixes = _.filter(fixes, (f) => !f?.check?.autofix);
const manualFixesOptional = _.filter(optionalFixes, (f) => !f?.check?.autofix);
if (manualFixes.length > 0) {
log.info('### Manual Fixes Needed ###');
log.info('The configuration cannot be automatically fixed, please do the following first:');
/** @type {string[]} */
const fixMessages = [];
for (const f of manualFixes) {
const message = await f.check.fix();
if (message) {
fixMessages.push(message);
}
}
for (const m of _.uniq(fixMessages)) {
log.warn(` \u279C ${m}`);
}
log.info('');
}
if (manualFixesOptional.length > 0) {
log.info('### Optional Manual Fixes ###');
log.info('The configuration can install optionally. Please do the following manually:');
/** @type {string[]} */
const fixMessages = [];
for (const f of manualFixesOptional) {
const message = await f.check.fix();
if (message) {
fixMessages.push(message);
}
}
for (const m of _.uniq(fixMessages)) {
log.warn(` \u279C ${m}`);
}
log.info('');
}
if (manualFixes.length > 0 || manualFixesOptional.length > 0) {
log.info('###');
log.info('');
log.info('Bye! Run appium-doctor again when all manual fixes have been applied!');
log.info('');
return true;
}
return false;
}
/**
* @param {DoctorIssue} f
*/
async runAutoFix(f) {
log.info(`### Fixing: ${f.error} ###`);
try {
await f.check.fix();
} catch (err) {
if (err instanceof FixSkippedError) {
log.info(`### Skipped fix ###`);
return;
} else {
log.warn(`${err}`.replace(/\n$/g, ''));
log.info(`### Fix did not succeed ###`);
return;
}
}
log.info('Checking if this was fixed:');
const res = await f.check.diagnose();
if (res.ok) {
f.fixed = true;
log.info(` ${'\u2714'.green} ${res.message}`);
log.info(`### Fix was successfully applied ###`);
} else {
log.info(` ${'\u2716'.red} ${res.message}`);
log.info(`### Fix was applied but issue remains ###`);
}
}
async runAutoFixes() {
const autoFixes = _.filter(this.toFix, (f) => f?.check?.autofix);
for (const f of autoFixes) {
await this.runAutoFix(f);
log.info('');
}
if (_.find(autoFixes, (f) => !f.fixed)) {
// a few issues remain.
log.info('Bye! A few issues remain, fix manually and/or rerun appium-doctor!');
} else {
// nothing left to fix.
log.info('Bye! All issues have been fixed!');
}
log.info('');
}
async run() {
log.info(`Appium Doctor v.${version}`);
log.warn(`This package is deprecated since the Appium server version 2.4.0 and will be removed in the future.`);
log.warn(`Use doctor checks (if any exist) integrated into your installed driver or plugin by ` +
`running \`appium driver doctor <driver_name>\` or \`appium plugin doctor <plugin_name>\`.`);
await this.diagnose();
if (await this.reportSuccess(this.toFix.length, this.toFixOptionals.length)) {
return;
}
if (await this.reportManualFixes(this.toFix, this.toFixOptionals)) {
return;
}
await this.runAutoFixes();
}
/**
* Generating messages
*
* @param {import('./utils').CheckResult} result
* @param {DoctorIssue[]} toFixList
* @param {DoctorCheck} check
*/
async diagnosticResultMessage(result, toFixList, check) {
if (result.ok) {
log.info(` ${'\u2714'.green} ${result.message}`);
} else {
const errorMessage = result.optional
? ` ${'\u2716'.yellow} ${result.message}`
: ` ${'\u2716'.red} ${result.message}`;
log.warn(errorMessage);
toFixList.push({
error: errorMessage,
check,
});
}
}
/**
* @param {number} length
* @param {boolean} [optional=false]
* @returns {Promise<string>}
*/
async fixMessage(length, optional = false) {
let message;
switch (length) {
case 0:
message = 'no fix';
break;
case 1:
message = 'one fix';
break;
default:
message = `${length} fixes`;
}
return `${message} ${optional ? 'possible' : 'needed'}`;
}
/**
* @param {number} length
* @param {number} lengthOptional
* @returns {Promise<boolean>}
*/
async reportSuccess(length, lengthOptional) {
if (length === 0 && lengthOptional === 0) {
log.info('Everything looks good, bye!');
log.info('');
return true;
} else {
return false;
}
}
}
export {Doctor, DoctorCheck, FixSkippedError};
/**
* @typedef DoctorOpts
* @property {boolean?} [autofix]
*/
/**
* @typedef DoctorIssue
* @property {DoctorCheck} check
* @property {string} error
* @property {boolean} [fixed]
*/
-49
View File
@@ -1,49 +0,0 @@
import {fs, system} from '@appium/support';
import {DoctorCheck} from './doctor';
import {ok, nok} from './utils';
import '@colors/colors';
// Check env variables
export class EnvVarAndPathCheck extends DoctorCheck {
/**
* @param {string} varName
*/
constructor(varName) {
super();
this.varName = varName;
}
/**
* @override
*/
async diagnose() {
let varValue = process.env[this.varName];
if (typeof varValue === 'undefined') {
return nok(`${this.varName} environment variable is NOT set!`);
}
if (await fs.exists(varValue)) {
return ok(`${this.varName} is set to: ${varValue}`);
}
let err_msg = `${this.varName} is set to '${varValue}' but this is NOT a valid path!`;
// On Windows, when the env var has %LOCALAPPDATA%, fs.exists cannot resolve the path.
// Then, it would be safe to request the user to set the full path instead.
if (system.isWindows() && varValue.includes('%LOCALAPPDATA%')) {
err_msg += ` Please set '${process.env.LOCALAPPDATA}' instead of '%LOCALAPPDATA%' as the environment variable.`;
}
return nok(err_msg);
}
/**
* @override
*/
async fix() {
return (
`Make sure the environment variable ${this.varName.bold} is properly configured for the Appium process. ` +
`Refer https://github.com/appium/java-client/blob/master/docs/environment.md for more details.`
);
}
}
export default EnvVarAndPathCheck;
-37
View File
@@ -1,37 +0,0 @@
import _ from 'lodash';
import {Doctor} from './doctor';
import generalChecks from './general';
import iosChecks from './ios';
import androidChecks from './android';
import devChecks from './dev';
import demoChecks from './demo';
/**
* @type {DoctorGroup}
*/
const checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks};
const newDoctor = (/** @type {Record<string, any>} */ opts) => {
const doctor = new Doctor();
for (const [k, v] of _.toPairs(opts)) {
if (v) {
doctor.register(checks[`${k}Checks`] || []);
}
}
return doctor;
};
export default newDoctor;
/**
* @typedef {import('./doctor').DoctorCheck[]} DoctorCheckList
*/
/**
* @typedef DoctorGroup - Contain a group of Doctors
* @property {DoctorCheckList} generalChecks - Check AppiumHome, NodeBinary, NodeVersion, ffmpeg, mjpeg-consumer
* @property {DoctorCheckList} iosChecks - Check if iOS toolchains are installed
* @property {DoctorCheckList} androidChecks - Check if Android toolchains are installed
* @property {DoctorCheckList} devChecks - Check Path Binary and Android SDKs
* @property {DoctorCheckList} demoChecks - Check /tmp/appium-doctor/demo/*
*/
-140
View File
@@ -1,140 +0,0 @@
import {ok, nok, okOptional, nokOptional, resolveExecutablePath, getNpmPackageInfo} from './utils';
import {exec} from 'teen_process';
import {DoctorCheck} from './doctor';
import NodeDetector from './node-detector';
import {util, env} from '@appium/support';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
const checks = [];
export class AppiumHomeCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
return ok(`APPIUM_HOME is ${await env.resolveAppiumHome()}`);
}
/**
* @override
*/
async fix() {
return null;
}
}
checks.push(new AppiumHomeCheck());
// Node Binary
export class NodeBinaryCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
let nodePath = await NodeDetector.detect();
return nodePath
? ok(`The Node.js binary was found at: ${nodePath}`)
: nok('The Node.js binary was NOT found!');
}
/**
* @override
*/
async fix() {
return `Manually setup ${'Node.js'.bold}.`;
}
}
checks.push(new NodeBinaryCheck());
const REQUIRED_NODE_VERSION = '14.0.0';
// Node version
export class NodeVersionCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
let nodePath = await NodeDetector.detect();
if (!nodePath) {
return nok('Node is not installed, so no version to check!');
}
let {stdout} = await exec(nodePath, ['--version']);
let versionString = stdout.replace('v', '').trim();
try {
return util.compareVersions(REQUIRED_NODE_VERSION, '<=', versionString)
? ok(`Node version is ${versionString}`)
: nok(`Node version should be at least ${REQUIRED_NODE_VERSION}!`);
} catch {
return nok(`Unable to find node version (version = '${versionString}')`);
}
}
/**
* @override
*/
async fix() {
return `Manually upgrade ${'Node.js'.bold}.`;
}
}
checks.push(new NodeVersionCheck());
export class OptionalFfmpegCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const ffmpegPath = await resolveExecutablePath('ffmpeg');
return ffmpegPath
? okOptional(
`ffmpeg is installed at: ${ffmpegPath}. ${
(await exec('ffmpeg', ['-version'])).stdout.split('\n')[0]
}`
)
: nokOptional('ffmpeg cannot be found');
}
/**
* @override
*/
async fix() {
return `${
'ffmpeg'.bold
} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`;
}
}
checks.push(new OptionalFfmpegCommandCheck());
export class OptionalMjpegConsumerCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const packageName = 'mjpeg-consumer';
const packageInfo = await getNpmPackageInfo(packageName);
if (packageInfo) {
return okOptional(
`${packageName} is installed at: ${packageInfo.path}. Installed version is: ${packageInfo.version}`
);
}
return nokOptional(`${packageName} cannot be found.`);
}
/**
* @override
*/
async fix() {
return `${
'mjpeg-consumer'.bold
} module is required to use MJPEG-over-HTTP features. Please install it with 'npm i -g mjpeg-consumer'.`;
}
}
checks.push(new OptionalMjpegConsumerCommandCheck());
export default checks;
/**
* @typedef {import('./utils').CheckResult} UtilsResult
*/
-4
View File
@@ -1,4 +0,0 @@
import newDoctor from './factory';
import {Doctor, DoctorCheck} from './doctor';
export {newDoctor, Doctor, DoctorCheck};
-224
View File
@@ -1,224 +0,0 @@
import {ok, nok, okOptional, nokOptional, resolveExecutablePath} from './utils'; // eslint-disable-line
import {fs} from '@appium/support';
import {exec} from 'teen_process';
import {DoctorCheck, FixSkippedError} from './doctor';
import log from './logger';
import {fixIt} from './prompt';
import EnvVarAndPathCheck from './env';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
const checks = [];
export const fixes = {};
// Check for Xcode.
export class XcodeCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
let xcodePath;
try {
// https://github.com/appium/appium/issues/12093#issuecomment-459358120 can happen
await exec('xcrun', ['simctl', 'help']);
} catch (err) {
return nok('Error running xcrun simctl');
}
try {
const {stdout} = await exec('xcode-select', ['-p']);
xcodePath = (stdout || '').replace('\n', '');
} catch (err) {
return nok('Xcode is NOT installed!');
}
return xcodePath && (await fs.exists(xcodePath))
? ok(`Xcode is installed at: ${xcodePath}`)
: nok(`Xcode cannot be found at '${xcodePath}'!`);
}
/**
* @override
*/
async fix() {
return `Manually install ${
'Xcode'.bold
}, and make sure 'xcode-select -p' command shows proper path like '/Applications/Xcode.app/Contents/Developer'`;
}
}
checks.push(new XcodeCheck());
// Check for Xcode Command Line Tools.
export class XcodeCmdLineToolsCheck extends DoctorCheck {
constructor() {
super({autofix: true});
}
/**
* @override
*/
async diagnose() {
const errMess = 'Xcode Command Line Tools are NOT installed!';
try {
// https://stackoverflow.com/questions/15371925/how-to-check-if-command-line-tools-is-installed
const stdout = (await exec('xcode-select', ['-p'])).stdout;
return ok(`Xcode Command Line Tools are installed in: ${stdout.trim()}`);
} catch (err) {
log.debug(err);
return nok(errMess);
}
}
/**
* @override
*/
async fix() {
log.info(`The following command need be executed: xcode-select --install`);
let yesno = await fixIt();
if (yesno === 'yes') {
await exec('xcode-select', ['--install']);
} else {
log.info(`Skipping. You will need to install ${'Xcode'.bold} manually.`);
throw new FixSkippedError();
}
return null;
}
}
checks.push(new XcodeCmdLineToolsCheck());
// Dev Tools Security
export class DevToolsSecurityCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const errMess = 'DevToolsSecurity is NOT enabled!';
let stdout;
try {
stdout = (await exec('DevToolsSecurity', [])).stdout;
} catch (err) {
log.debug(err);
return nok(errMess);
}
return stdout && stdout.match(/enabled/) ? ok('DevToolsSecurity is enabled.') : nok(errMess);
}
}
checks.push(
new DevToolsSecurityCheck(),
new EnvVarAndPathCheck('HOME'),
);
export class OptionalLyftCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const lyftCmd = await resolveExecutablePath('set-simulator-location');
if (lyftCmd) {
return okOptional('set-simulator-location is installed');
}
return nokOptional('set-simulator-location is not installed');
}
/**
* @override
*/
async fix() {
return (
`${'set-simulator-location'.bold} is needed to set location for Simulator. ` +
'Please read https://github.com/lyft/set-simulator-location to install it'
);
}
}
checks.push(new OptionalLyftCommandCheck());
export class OptionalIdbCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const fbIdbPath = await resolveExecutablePath('idb');
const fbCompanionIdbPath = await resolveExecutablePath('idb_companion');
if (fbIdbPath && fbCompanionIdbPath) {
return okOptional('idb and idb_companion are installed');
}
if (!fbIdbPath && fbCompanionIdbPath) {
return nokOptional('idb is not installed');
} else if (fbIdbPath && !fbCompanionIdbPath) {
return nokOptional('idb_companion is not installed');
}
return nokOptional('idb and idb_companion are not installed');
}
/**
* @override
*/
async fix() {
return `Why ${'idb'.bold} is needed and how to install it: ${
OptionalIdbCommandCheck.idbReadmeURL
}`;
}
}
// link to idb README.md
// https://github.com/appium/appium-ios/blob/main/packages/idb/README.md
OptionalIdbCommandCheck.idbReadmeURL = 'https://git.io/JnxQc';
checks.push(new OptionalIdbCommandCheck());
export class OptionalApplesimutilsCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const applesimutilsPath = await resolveExecutablePath('applesimutils');
return applesimutilsPath
? okOptional(
`applesimutils is installed at: ${applesimutilsPath}. Installed versions are: ${(
await exec('brew', ['list', '--versions', 'applesimutils'])
).stdout.trim()}`
)
: nokOptional('applesimutils cannot be found');
}
/**
* @override
*/
async fix() {
return `Why ${
'applesimutils'.bold
} is needed and how to install it: ` +
`https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-setpermission`;
}
}
checks.push(new OptionalApplesimutilsCommandCheck());
export class OptionalIOSDeployCommandCheck extends DoctorCheck {
/**
* @override
*/
async diagnose() {
const iosDeployPath = await resolveExecutablePath('ios-deploy');
return iosDeployPath
? okOptional(
`ios-deploy is installed at: ${iosDeployPath}. Installed version is: ${(
await exec(iosDeployPath, ['-V'])
).stdout.trim()}`
)
: nokOptional('ios-deploy cannot be found');
}
/**
* @override
*/
async fix() {
return `${
'ios-deploy'.bold
} is used as a fallback command to install iOS applications to real device. ` +
`Please read https://github.com/ios-control/ios-deploy/ to install it`;
}
}
checks.push(new OptionalIOSDeployCommandCheck());
export default checks;
-5
View File
@@ -1,5 +0,0 @@
import {logger} from '@appium/support';
const log = logger.getLogger('AppiumDoctor');
export default log;
-35
View File
@@ -1,35 +0,0 @@
import log from './logger';
import {resolveExecutablePath} from './utils';
// Look for node
export class NodeDetector {
/**
* @returns {Promise<string?>}
*/
static async retrieveUsingSystemCall() {
const nodePath = await resolveExecutablePath('node');
if (!nodePath) {
log.debug(`Node binary not found in PATH: ${process.env.PATH}`);
return null;
}
log.debug(`Node binary found at: ${nodePath}`);
return nodePath;
}
/**
* @returns {Promise<string?>}
*/
static async detect() {
const nodePath = await NodeDetector.retrieveUsingSystemCall();
if (nodePath) {
return nodePath;
} else {
log.warn('The node binary could not be found.');
return null;
}
}
}
export default NodeDetector;
-53
View File
@@ -1,53 +0,0 @@
import { prompt } from './utils';
/**
* @type {string|undefined}
*/
let persistentResponse;
const fixItQuestion = {
type: 'list',
name: 'confirmation',
message: 'Fix it:',
choices: ['yes', 'no', 'always', 'never'],
/**
*
* @param {string} val
* @returns {string}
*/
filter(val) {
return val.toLowerCase();
},
};
/**
* @param {Record<string, any>} opts
*/
export function configure(opts) {
if (opts.yes) {
persistentResponse = 'yes';
}
if (opts.no) {
persistentResponse = 'no';
}
}
/**
* @returns {void}
*/
export function clear() {
persistentResponse = undefined;
}
/**
* @returns {Promise<string|undefined>}
*/
export async function fixIt() {
if (persistentResponse) {
return persistentResponse;
}
const resp = await prompt(fixItQuestion);
persistentResponse = resp.confirmation === 'always' ? 'yes' : persistentResponse;
persistentResponse = resp.confirmation === 'never' ? 'no' : persistentResponse;
return persistentResponse || resp.confirmation;
}

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