mirror of
https://github.com/appium/appium.git
synced 2026-11-01 06:45:05 +08:00
Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3467f32c8e | ||
|
|
6f27d36cf5 | ||
|
|
488e5dea82 | ||
|
|
13d8b03cc1 | ||
|
|
fe209ed660 | ||
|
|
93e4288253 | ||
|
|
fb839be6ea | ||
|
|
6742808345 | ||
|
|
bb4ba0e52b | ||
|
|
990a0667e3 | ||
|
|
389a3e83ac | ||
|
|
a034880a03 | ||
|
|
588381ecf4 | ||
|
|
3fc57a15b5 | ||
|
|
bbfc04026f | ||
|
|
c5443e9fc9 | ||
|
|
31c6759061 | ||
|
|
edd1c05d1e | ||
|
|
61b6689897 | ||
|
|
65658ccbdd | ||
|
|
25ea9f73bc | ||
|
|
4b3a8c2197 | ||
|
|
24abb385e5 | ||
|
|
861d060713 | ||
|
|
c57f81d134 | ||
|
|
51acf54fe5 | ||
|
|
aa880837da | ||
|
|
af7aba492d | ||
|
|
25b8483294 | ||
|
|
4474412a60 | ||
|
|
7c740f9b3a | ||
|
|
c765bc31e0 | ||
|
|
7abecaddd3 | ||
|
|
083f7e2328 | ||
|
|
aaef1d6962 | ||
|
|
64e768efb7 | ||
|
|
a5897dd25a | ||
|
|
66e3580731 | ||
|
|
16e9f94903 | ||
|
|
a917ec6ced | ||
|
|
342d6641ee | ||
|
|
04e2d1154b | ||
|
|
c032352fad | ||
|
|
e3774ef0c0 | ||
|
|
1d16ac6b45 | ||
|
|
6ea0830319 | ||
|
|
228b091edc | ||
|
|
81a5b57673 | ||
|
|
8d94f48bc5 | ||
|
|
f03468a153 | ||
|
|
6a78aa9cea | ||
|
|
9c5407ce64 | ||
|
|
2a99e43b88 | ||
|
|
dca88624a4 | ||
|
|
69f100ad3e | ||
|
|
f5e708ac42 | ||
|
|
c91683f50f | ||
|
|
4a2bfe9418 | ||
|
|
3008c0d7be | ||
|
|
9a3f990739 | ||
|
|
16c3ae25ea | ||
|
|
93fa5ccb06 | ||
|
|
8590432955 | ||
|
|
42ef4841f9 |
@@ -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/**
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)._'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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/**',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
Generated
+3463
-4675
File diff suppressed because it is too large
Load Diff
+14
-12
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}.`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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>} */
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
@@ -1,39 +0,0 @@
|
||||
# @appium/doctor
|
||||
|
||||
> Attempts to diagnose and fix common Appium configuration issues
|
||||
|
||||
[](https://npmjs.org/package/@appium/doctor)
|
||||
[](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
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
module.exports = require('./build/bin/appium-doctor');
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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]
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -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/*
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
@@ -1,4 +0,0 @@
|
||||
import newDoctor from './factory';
|
||||
import {Doctor, DoctorCheck} from './doctor';
|
||||
|
||||
export {newDoctor, Doctor, DoctorCheck};
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
import {logger} from '@appium/support';
|
||||
|
||||
const log = logger.getLogger('AppiumDoctor');
|
||||
|
||||
export default log;
|
||||
@@ -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;
|
||||
@@ -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
Reference in New Issue
Block a user