mirror of
https://github.com/appium/appium.git
synced 2026-11-11 12:54:55 +08:00
Compare commits
142
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42c0bd370e | ||
|
|
383fee4e11 | ||
|
|
5951027ce7 | ||
|
|
889a533eef | ||
|
|
d83d436d01 | ||
|
|
bd8a92f588 | ||
|
|
db5a6323e6 | ||
|
|
2f56b05619 | ||
|
|
1e5201afad | ||
|
|
9ef18310da | ||
|
|
db0eec05c5 | ||
|
|
fc9a93fee0 | ||
|
|
7b40a3225e | ||
|
|
9cc598a92c | ||
|
|
91320ddd02 | ||
|
|
23dc3d56ce | ||
|
|
35742f0962 | ||
|
|
52a6f96e08 | ||
|
|
d064dd1e8c | ||
|
|
7bf4d694d4 | ||
|
|
cdc7c09421 | ||
|
|
17c7a7cb29 | ||
|
|
3032b0009c | ||
|
|
6dacb5e70c | ||
|
|
5a8af1302f | ||
|
|
569db839f1 | ||
|
|
32e9191431 | ||
|
|
21b808d94f | ||
|
|
83cc668596 | ||
|
|
b92ab24a25 | ||
|
|
4c7661dd13 | ||
|
|
b14c03f38a | ||
|
|
0e1eb95006 | ||
|
|
edb490f359 | ||
|
|
202da83449 | ||
|
|
d10b4ba2c4 | ||
|
|
6cd2c547de | ||
|
|
95e3fd2612 | ||
|
|
795092a97f | ||
|
|
5936d53d78 | ||
|
|
5f75d8c8ce | ||
|
|
214e922c3e | ||
|
|
30425f9038 | ||
|
|
6313704ee5 | ||
|
|
fb0f8f776c | ||
|
|
faff8dc199 | ||
|
|
501395c948 | ||
|
|
759e06a940 | ||
|
|
966d305e5e | ||
|
|
f3b212ce05 | ||
|
|
6f0101eb63 | ||
|
|
9e4cff1595 | ||
|
|
63ef7eb2fb | ||
|
|
3dcbd6afe7 | ||
|
|
45abac485b | ||
|
|
1fdf117c63 | ||
|
|
d9711b4191 | ||
|
|
9b16f0d9ad | ||
|
|
da84efc42d | ||
|
|
7733b0de3b | ||
|
|
e8e0444bd2 | ||
|
|
c3bea4cf14 | ||
|
|
b85a6b939f | ||
|
|
0e94a33240 | ||
|
|
4efd0c5cad | ||
|
|
fcb42f0809 | ||
|
|
882bd06d1c | ||
|
|
a6d6480074 | ||
|
|
45f5660b94 | ||
|
|
7aabbc3115 | ||
|
|
2ef9cc5983 | ||
|
|
6ce45cf4f8 | ||
|
|
9a21dbacf2 | ||
|
|
9617974481 | ||
|
|
ca54b4f79a | ||
|
|
35f1dbabf6 | ||
|
|
c3dfade284 | ||
|
|
cbc1f500bc | ||
|
|
293c8031d9 | ||
|
|
43690a4c77 | ||
|
|
40873fb655 | ||
|
|
7d9065cf31 | ||
|
|
06218c0c6a | ||
|
|
348b297578 | ||
|
|
314e1b468a | ||
|
|
16a7899154 | ||
|
|
cc24a26c6a | ||
|
|
0f1c12d859 | ||
|
|
f4011f17d4 | ||
|
|
f7e47316c0 | ||
|
|
d2666f9eb5 | ||
|
|
ac73522351 | ||
|
|
5aaca9b11c | ||
|
|
9733c7715e | ||
|
|
5cd9ca5c21 | ||
|
|
7084d7cfdc | ||
|
|
b5460fc131 | ||
|
|
fc3ea5caa2 | ||
|
|
332cc48a09 | ||
|
|
83f24acd39 | ||
|
|
b08210a6f8 | ||
|
|
474f3ce092 | ||
|
|
9ef2533256 | ||
|
|
76df4d600d | ||
|
|
eff6d331f2 | ||
|
|
5070a34739 | ||
|
|
bd88198a9a | ||
|
|
8e83996eb6 | ||
|
|
122a4ec111 | ||
|
|
94b358022f | ||
|
|
34654956a3 | ||
|
|
ea8394a888 | ||
|
|
b11366c3fc | ||
|
|
38d9f92d1a | ||
|
|
fe5245a427 | ||
|
|
e77aa417a0 | ||
|
|
7de3c1d57a | ||
|
|
441b2848da | ||
|
|
523cf6fa59 | ||
|
|
f5fce7ced9 | ||
|
|
a304a1f78e | ||
|
|
88ab563fa0 | ||
|
|
923a40b5eb | ||
|
|
73bf68cc60 | ||
|
|
8c4646f3f3 | ||
|
|
92d1172984 | ||
|
|
dde74d98b6 | ||
|
|
96c19c7887 | ||
|
|
ac9b2bd4fc | ||
|
|
f88d68f295 | ||
|
|
2b21e66891 | ||
|
|
9b06b871eb | ||
|
|
893b9e15d6 | ||
|
|
9aae933581 | ||
|
|
dc2cedfecd | ||
|
|
a30286b6e7 | ||
|
|
3bdb8a4f4a | ||
|
|
ff767b7f25 | ||
|
|
cbf5e1e6de | ||
|
|
e41935fd64 | ||
|
|
73837f1063 | ||
|
|
b114030e94 |
@@ -6,3 +6,4 @@ examples/javascript-wd
|
||||
sample-code
|
||||
**/build-fixtures/**
|
||||
packages/appium/docs/**/assets/**
|
||||
packages/appium/docs/**/js/**
|
||||
|
||||
@@ -20,7 +20,7 @@ _Put an `x` in the boxes that apply. You can also fill these out after creating
|
||||
- [ ] I have signed the CLA
|
||||
- [ ] Lint and unit tests pass locally with my changes
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have added necessary documentation (if appropriate)
|
||||
- [ ] I have added the necessary documentation (if appropriate)
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
## Further comments
|
||||
|
||||
@@ -13,4 +13,4 @@ jobs:
|
||||
name: Auto-Label PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fuxingloh/multi-labeler@v2
|
||||
- uses: fuxingloh/multi-labeler@v4
|
||||
|
||||
@@ -45,6 +45,10 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for "On Hold" label
|
||||
uses: egmacke/action-check-label@v2
|
||||
uses: egmacke/action-check-label@v3
|
||||
with:
|
||||
label: On Hold
|
||||
state: absent
|
||||
|
||||
@@ -25,6 +25,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Validate Renovate Config
|
||||
uses: rinchsan/renovate-config-validator@v0.0.12
|
||||
uses: rinchsan/renovate-config-validator@v0.2.0
|
||||
with:
|
||||
pattern: '{.renovaterc.json,renovate/default.json}'
|
||||
|
||||
@@ -29,6 +29,8 @@ build/
|
||||
.idea
|
||||
*DerivedData
|
||||
__pycache__
|
||||
certificate.cert
|
||||
certificate.key
|
||||
|
||||
# csharp
|
||||
*.pyc
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint:commit -- --edit ${1}
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint:staged
|
||||
@@ -1,214 +1,185 @@
|
||||
## Appium
|
||||
[](https://vshymanskyy.github.io/StandWithUkraine/)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://appium.io/">
|
||||
<img alt="Appium" src="https://raw.githubusercontent.com/appium/appium/master/packages/appium/docs/assets/images/appium-logo-horiz.png" width="500">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
Cross-platform test automation for native, hybrid, mobile web and desktop apps.
|
||||
</p>
|
||||
|
||||
[](https://npmjs.org/package/appium)
|
||||
[](https://npmjs.org/package/appium)
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_shield)
|
||||
|
||||
[](https://github.com/vshymanskyy/StandWithUkraine/)
|
||||
|
||||
[](https://vshymanskyy.github.io/StandWithUkraine/)
|
||||
***
|
||||
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. Initially created to automate iOS and Android mobile
|
||||
applications Appium has grown to a full-featured platform that provides [WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for the whole set of different mobile and desktop platforms.
|
||||
See the full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers for more details.
|
||||
<p align="center"><b>
|
||||
<a href="https://appium.io">Documentation</a> |
|
||||
<a href="https://appium.io/docs/en/latest/intro/">Get Started</a> |
|
||||
<a href="https://appium.io/docs/en/latest/ecosystem/">Ecosystem</a> |
|
||||
<a href="https://github.com/appium/appium/blob/master/packages/appium/CHANGELOG.md">Changelog</a> |
|
||||
<a href="https://github.com/appium/appium/blob/master/ROADMAP.md">Roadmap</a> |
|
||||
<a href="https://discuss.appium.io">Discussion Forum</a>
|
||||
</b></p>
|
||||
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.io/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
|
||||
***
|
||||
|
||||
:bangbang: If you still use the deprecated Appium Desktop containing the built-in server then you won't be able to upgrade to Appium 2. Consider switching to [Appium Inspector](https://github.com/appium/appium-inspector) instead.
|
||||
Appium is an open-source automation framework that provides
|
||||
[WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for a wide range of
|
||||
different mobile, desktop and IoT platforms. Appium is modular and extensible, and supports multiple
|
||||
programming languages, which means there is an entire ecosystem of related software:
|
||||
* [__Drivers__](#drivers) add support for automating specific platforms
|
||||
* [__Clients__](#clients) allow writing Appium tests in your programming language of choice
|
||||
* [__Plugins__](#plugins) allow to further extend Appium functionality
|
||||
|
||||
### Documentation
|
||||
## Migrating From Appium 1 to Appium 2
|
||||
|
||||
The current Appium documentation is available at [__https://appium.io__](https://appium.io).
|
||||
As of January 1st, 2022, the Appium team no longer maintains or supports Appium 1. All officially
|
||||
supported platform drivers are only compatible with Appium 2.
|
||||
|
||||
### Requirements
|
||||
__[Please read the Migration Guide](https://appium.io/docs/en/latest/guides/migrating-1-to-2/) if you
|
||||
are still using Appium 1.__
|
||||
|
||||
- macOS, Linux or Windows operating system
|
||||
- Node.js 16+. Any relevant [LTS release](https://nodejs.dev/en/about/releases/) should work just fine.
|
||||
- NPM (Node Package Manager) 8+
|
||||
> [!WARNING]
|
||||
> If you use Appium Desktop or Appium Server GUI, you will not be able to upgrade to Appium 2, as
|
||||
both of these tools have been deprecated. Please use [Appium Inspector](https://github.com/appium/appium-inspector)
|
||||
in combination with a standalone Appium 2 server.
|
||||
|
||||
These are only server requirements. Each driver might have its own requirements. Consider checking the corresponding driver tutorial for more details.
|
||||
## Installation
|
||||
|
||||
### Server
|
||||
Appium can be installed using `npm` (other package managers are not currently supported). Please
|
||||
check the [installation docs](http://appium.io/docs/en/latest/quickstart/install/) for the
|
||||
system requirements and further information.
|
||||
|
||||
To install Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
If upgrading from Appium 1, make sure Appium 1 is fully uninstalled (`npm uninstall -g appium`).
|
||||
Unexpected errors might appear if this has not been done.
|
||||
|
||||
```bash
|
||||
npm i -g appium
|
||||
```
|
||||
|
||||
To update Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
Note that this will only install the core Appium server, which cannot automate anything on its own.
|
||||
Please install [drivers](#drivers) for your target platforms in order to automate them.
|
||||
|
||||
```bash
|
||||
npm i -g appium@latest
|
||||
```
|
||||
## Drivers
|
||||
|
||||
:bangbang: Make sure Appium 1 is fully uninstalled before upgrading it to Appium 2 (`npm uninstall -g appium`). Unexpected errors might appear if this has not been done.
|
||||
Appium supports app automation across a variety of platforms, like iOS, Android, macOS, Windows,
|
||||
and more. Each platform is supported by one or more "drivers", which know how to automate that
|
||||
particular platform. You can find a full list of officially-supported and third-party drivers in
|
||||
[Appium Ecosystem's Drivers page](http://appium.io/docs/en/latest/ecosystem/drivers/).
|
||||
|
||||
### Drivers
|
||||
|
||||
Appium supports app automation across a variety of platforms, like iOS,
|
||||
Android, and Windows. Each platform is supported by one or more "drivers",
|
||||
which know how to automate that particular platform. Since version 2.0
|
||||
all drivers have been isolated from the Appium server app and can
|
||||
be managed independently using the [appium driver](https://appiumpro.com/editions/122-installing-appium-20-and-the-driver-and-plugins-cli) command line interface.
|
||||
|
||||
In general, the drivers management in Appium 2 is as simple as:
|
||||
Driver management is done using [Appium's Extension command-line interface](http://appium.io/docs/en/latest/cli/extensions/):
|
||||
|
||||
```bash
|
||||
# To install a new driver from npm
|
||||
appium driver install --source=npm appium-xcuitest-driver[@<version>]
|
||||
# To install a driver from a local folder (useful for developers)
|
||||
appium driver install --source=local /Users/me/sources/appium-xcuitest-driver
|
||||
# To install a new driver from github (hm, maybe it's time to publish it to NPM?)
|
||||
appium driver install --source=github --package=appium-xcuitest-driver appium/appium-xcuitest-driver
|
||||
# Install an official driver from npm (see documentation for a list of such drivers)
|
||||
appium driver install <driver-name>
|
||||
# Install any driver from npm
|
||||
appium driver install --source=npm <driver-name>
|
||||
# See documentation for installation from other sources
|
||||
|
||||
# To list already installed drivers
|
||||
# List already installed drivers
|
||||
appium driver list --installed
|
||||
|
||||
# To update a driver (it must be already installed)
|
||||
appium driver update xcuitest
|
||||
|
||||
# To uninstall a driver (it won't last forever, wouldn't it?)
|
||||
appium driver uninstall xcuitest
|
||||
# Update a driver (it must be already installed)
|
||||
# This will NOT update the major version, in order to prevent breaking changes
|
||||
appium driver update <driver-name>
|
||||
# Update a driver to the most recent version (may include breaking changes)
|
||||
appium driver update <driver-name> --unsafe
|
||||
# Uninstall a driver (it won't last forever, will it?)
|
||||
appium driver uninstall <driver-name>
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
the current Appium 2.0 documentation.
|
||||
## Clients
|
||||
|
||||
### Plugins
|
||||
Client libraries enable writing Appium tests in different programming languages. There are
|
||||
officially-supported clients for Java, Python, Ruby, and .NET C#, as well as third-party clients
|
||||
for other languages. You can find a full list of clients in
|
||||
[Appium Ecosystem's Clients page](http://appium.io/docs/en/latest/ecosystem/clients/).
|
||||
|
||||
The concept of plugins is something new that has been added exclusively to Appium2. Plugins allow you to extend server functionality without changing the server code. Plugins could be managed similarly to drivers:
|
||||
## Plugins
|
||||
|
||||
Plugins allow you to extend server functionality without changing the server code. The main
|
||||
difference between drivers and plugins is that the latter must be explicitly enabled on
|
||||
Appium server startup (all installed drivers are enabled by default):
|
||||
|
||||
```bash
|
||||
# To install an officially supported plugin
|
||||
appium plugin install images
|
||||
# To install a plugin from a local folder (useful for developers)
|
||||
appium plugin install --source=local /Users/me/sources/images
|
||||
# To install a new plugin from npm
|
||||
appium plugin install --source=npm appium-device-farm
|
||||
appium --use-plugins=<plugin-name>
|
||||
```
|
||||
|
||||
# To list already installed plugins
|
||||
You can find a full list of officially-supported and third-party plugins in
|
||||
[Appium Ecosystem's Plugins page](http://appium.io/docs/en/latest/ecosystem/plugins/).
|
||||
|
||||
Similarly to drivers, plugin management is also done using
|
||||
[Appium's Extension command-line interface](http://appium.io/docs/en/latest/cli/extensions/):
|
||||
|
||||
```bash
|
||||
# Install an official plugin from npm (see documentation for a list of such plugins)
|
||||
appium plugin install <plugin-name>
|
||||
# Install any plugin from npm
|
||||
appium plugin install --source=npm <plugin-name>
|
||||
# See documentation for installation from other sources
|
||||
|
||||
# List already installed plugins
|
||||
appium plugin list --installed
|
||||
|
||||
# To update a plugins (it must be already installed)
|
||||
appium plugin update appium-device-farm
|
||||
|
||||
# To uninstall a plugin
|
||||
appium plugin uninstall appium-device-farm
|
||||
# Update a plugin (it must be already installed)
|
||||
# This will NOT update the major version, in order to prevent breaking changes
|
||||
appium plugin update <plugin-name>
|
||||
# Update a plugin to the most recent version (may include breaking changes)
|
||||
appium plugin update <plugin-name> --unsafe
|
||||
# Uninstall a plugin
|
||||
appium plugin uninstall <plugin-name>
|
||||
```
|
||||
|
||||
The main difference between drivers and plugins is that the latter must be explicitly enabled on server startup after it was installed (drivers are enabled by default after installation):
|
||||
## Server Command Line Interface
|
||||
|
||||
In order to start sending commands to the Appium server, it must be running on the URL and port
|
||||
where your client library expects it to listen. [Appium's command-line interface](http://appium.io/docs/en/latest/cli/args/)
|
||||
is used to launch and configure the server:
|
||||
|
||||
```bash
|
||||
appium server --use-plugins=device-farm,images
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
|
||||
### Server Command Line Interface
|
||||
|
||||
In order to start sending commands to Appium over the wire it must be listening
|
||||
on the URL where your client library expects it to listen.
|
||||
Use the following commands to run and configure Appium server:
|
||||
|
||||
```bash
|
||||
# Start the server on the default port and host (e.g. http://0.0.0.0:4723/)
|
||||
# Start the server on the default host (0.0.0.0) and port (4723)
|
||||
appium server
|
||||
# Start the server on the given port, host and use the base path prefix (the default prefix is /)
|
||||
appium server -p 9000 -a 127.0.0.1 -pa /wd/hub
|
||||
|
||||
# Get the list of all supported command line parameters.
|
||||
# This list would also include descriptions of driver-specific
|
||||
# command line arguments for all installed drivers.
|
||||
# Each driver and plugin must have their command line arguments
|
||||
# exposed in a special JSON schema declared as a part of the corresponding
|
||||
# package.json file.
|
||||
appium server --help
|
||||
# You can also omit the 'server' subcommand
|
||||
appium
|
||||
# Start the server on the given host, port and use a custom base path prefix (the default prefix is '/')
|
||||
appium --address 127.0.0.1 --port 9000 --base-path /wd/hub
|
||||
```
|
||||
|
||||
Appium supports execution of parallel server processes as well as parallel driver sessions within
|
||||
Appium supports execution of parallel server processes, as well as parallel driver sessions within a
|
||||
single server process. Refer the corresponding driver documentations regarding which mode is optimal
|
||||
for the particular driver or whether it supports parallel sessions.
|
||||
|
||||
### Why Appium?
|
||||
## Why Appium?
|
||||
|
||||
1. You usually don't have to recompile your app or modify it in any way, due
|
||||
to the use of standard automation APIs on all platforms.
|
||||
2. You can write tests with your favorite dev tools using any
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html)-compatible
|
||||
language such as [Java](https://github.com/appium/java-client),
|
||||
[JavaScript](https://webdriver.io/), [Python](https://github.com/appium/python-client),
|
||||
[Ruby](https://github.com/appium/ruby_lib), [C#](https://github.com/appium/dotnet-client)
|
||||
with the Selenium WebDriver API. There are also various third party
|
||||
client implementations for other languages.
|
||||
1. You usually don't have to recompile your app or modify it in any way, due to the use of standard
|
||||
automation APIs on all platforms.
|
||||
2. You can write tests with your favorite dev tools using any WebDriver-compatible language such as
|
||||
Java, Python, Ruby and C#. There are also third party client implementations for other languages.
|
||||
3. You can use any testing framework.
|
||||
4. Some drivers, like xcuitest and uiautomator2 ones have built-in mobile web and
|
||||
hybrid app support. Within the same script, you can switch seamlessly between native
|
||||
app automation and webview automation, all using the WebDriver model that's already
|
||||
the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple
|
||||
cloud providers that support various Appium drivers (mostly
|
||||
targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) allows
|
||||
visual debugging of automated tests and could be extremely useful for
|
||||
beginners.
|
||||
4. Some drivers like `xcuitest` and `uiautomator2` have built-in mobile web and hybrid app support.
|
||||
Within the same script, you can switch seamlessly between native app automation and webview
|
||||
automation, all using the WebDriver model that's already the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple cloud providers that
|
||||
support various Appium drivers (mostly targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) can be used to visually inspect
|
||||
the page source of applications across different platforms, facilitating easier test development.
|
||||
|
||||
Investing in the
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means
|
||||
you are betting on a single, free, and open protocol for testing that has become
|
||||
a web standard. Don't lock yourself into a proprietary stack.
|
||||
Investing in the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means you
|
||||
are betting on a single, free, and open protocol for testing that has become a web standard. Don't
|
||||
lock yourself into a proprietary stack.
|
||||
|
||||
For example, if you use Apple's XCUITest library without Appium you can only
|
||||
write tests using Obj-C/Swift, and you can only run tests through Xcode.
|
||||
Similarly, with Google's UiAutomator or Espresso, you can only write tests in
|
||||
Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
automation, for mobile and beyond. Finally!
|
||||
For example, if you use Apple's XCUITest library without Appium, you can only write tests using
|
||||
Obj-C/Swift, and you can only run tests through Xcode. Similarly, with Google's UiAutomator or
|
||||
Espresso, you can only write tests in Java/Kotlin. Appium opens up the possibility of true
|
||||
cross-platform native app automation, for mobile and beyond!
|
||||
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Intro to Appium](https://appium.io/docs/en/latest/intro/).
|
||||
If you are looking for a more comprehensive description of what this is all about, please read our
|
||||
documentation on [How Does Appium Work?](https://appium.io/docs/en/latest/intro/appium/).
|
||||
|
||||
### Quickstart
|
||||
## License
|
||||
|
||||
Check out our [Quickstart](https://appium.io/docs/en/latest/quickstart/) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.io).
|
||||
You can always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
|
||||
|
||||
### Contributing
|
||||
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test, and run Appium from the source.
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
Announcements and debates often take place on the [Discussion Group](https://discuss.appium.io),
|
||||
be sure to sign up!
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Please find the troubleshooting guide at the corresponding driver repository/documentation.
|
||||
|
||||
### License
|
||||
[Apache-2.0](./LICENSE)
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'body-leading-blank': [0],
|
||||
'body-max-line-length': [0],
|
||||
'footer-max-line-length': [0],
|
||||
'header-max-length': [0],
|
||||
'subject-case': [0],
|
||||
'subject-full-stop': [0],
|
||||
},
|
||||
};
|
||||
Generated
+1138
-3687
File diff suppressed because it is too large
Load Diff
+15
-22
@@ -44,13 +44,10 @@
|
||||
"docs:preview": "npm run -w packages/appium build:docs:preview",
|
||||
"doctor": "node ./packages/doctor",
|
||||
"generate-docs": "npm run --workspace=packages/appium generate-docs",
|
||||
"husky-install": "husky install",
|
||||
"lint": "eslint .",
|
||||
"lint:ci": "eslint --quiet .",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:staged": "lint-staged",
|
||||
"prepare": "run-p husky-install rebuild",
|
||||
"prepare": "run-p rebuild",
|
||||
"prepublishOnly": "run-s rebuild",
|
||||
"preversion": "run-s rebuild",
|
||||
"version": "run-s sync-pkgs stage-synced-pkgs",
|
||||
@@ -84,25 +81,23 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.6.0",
|
||||
"@commitlint/cli": "18.4.3",
|
||||
"@commitlint/config-conventional": "18.4.3",
|
||||
"@tsconfig/node14": "14.1.0",
|
||||
"@types/chai": "4.3.11",
|
||||
"@types/chai-as-promised": "7.1.8",
|
||||
"@types/diff": "5.0.9",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/sinon": "17.0.2",
|
||||
"@types/node": "20.11.17",
|
||||
"@types/semver": "7.5.7",
|
||||
"@types/sinon": "17.0.3",
|
||||
"@types/sinon-chai": "3.2.12",
|
||||
"@types/source-map-support": "0.5.10",
|
||||
"@types/teen_process": "2.0.4",
|
||||
"@types/ws": "8.5.10",
|
||||
"@types/yargs": "17.0.32",
|
||||
"@typescript-eslint/eslint-plugin": "6.14.0",
|
||||
"@typescript-eslint/parser": "6.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"asyncbox": "3.0.0",
|
||||
"chai": "4.3.10",
|
||||
"chai": "4.4.1",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.8.0",
|
||||
"conventional-changelog-conventionalcommits": "7.0.2",
|
||||
@@ -117,18 +112,16 @@
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "13.1.1",
|
||||
"lerna": "8.0.1",
|
||||
"lint-staged": "15.2.0",
|
||||
"json-schema-to-typescript": "13.1.2",
|
||||
"lerna": "8.1.2",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "8.0.0",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "10.2.0",
|
||||
"mocha": "10.3.0",
|
||||
"mocha-chai-jest-snapshot": "1.1.4",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "3.1.1",
|
||||
"npm-run-all2": "6.1.2",
|
||||
"prettier": "3.2.5",
|
||||
"rewiremock": "3.14.5",
|
||||
"rimraf": "5.0.5",
|
||||
"serve-static": "1.15.0",
|
||||
@@ -137,11 +130,11 @@
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.2",
|
||||
"tsd": "0.30.0",
|
||||
"tsd": "0.30.4",
|
||||
"typescript": "5.2.2",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "8.26.2",
|
||||
"ws": "8.15.1",
|
||||
"webdriverio": "8.31.1",
|
||||
"ws": "8.16.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,80 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.5.1](https://github.com/appium/appium/compare/appium@2.5.0...appium@2.5.1) (2024-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.31.1 ([db0eec0](https://github.com/appium/appium/commit/db0eec05c58fe970351a309a01507cad824bdad2))
|
||||
* **support:** update dependency @types/semver to v7.5.7 ([bd8a92f](https://github.com/appium/appium/commit/bd8a92f588d40c97fbe51f8bd3b7835c96e9fe8f))
|
||||
* **support:** update dependency semver to v7.6.0 ([7b40a32](https://github.com/appium/appium/commit/7b40a3225ebe0f5976a26d05db2f22ba7b13a130))
|
||||
|
||||
|
||||
|
||||
## [2.5.0](https://github.com/appium/appium/compare/appium@2.4.1...appium@2.5.0) (2024-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Set a proper exit code if any of required doctor checks fails ([#19617](https://github.com/appium/appium/issues/19617)) ([f4011f1](https://github.com/appium/appium/commit/f4011f17d4d03bc854c43462848a4c38427acb85))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** Tune warning messages about installed extensions ([#19612](https://github.com/appium/appium/issues/19612)) ([5cd9ca5](https://github.com/appium/appium/commit/5cd9ca5c21393c70d259f7525d2c9d35b7eeb2a2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.27.2 ([35f1dba](https://github.com/appium/appium/commit/35f1dbabf6e852c4ae84ec128879bc7e61321a16))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.0 ([e8e0444](https://github.com/appium/appium/commit/e8e0444bd28d94963090065a813a4b10be665caa))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.6 ([da84efc](https://github.com/appium/appium/commit/da84efc42dd62b2be07a52dd4379791e2cef6eb2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.8 ([1fdf117](https://github.com/appium/appium/commit/1fdf117c63f6e9871f4de3c0bd93ecbdb13a9235))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.0 ([6f0101e](https://github.com/appium/appium/commit/6f0101eb6343eb8b372e00a6b71d2b2ee909404c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.1 ([fb0f8f7](https://github.com/appium/appium/commit/fb0f8f776c6a03174f4569efa60d817a930809cf))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.3 ([6dacb5e](https://github.com/appium/appium/commit/6dacb5e70ca3529a080f629f5245c2b676746e2c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.7 ([52a6f96](https://github.com/appium/appium/commit/52a6f96e085ff13acdf8d5013673861b7e0da818))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.30.0 ([91320dd](https://github.com/appium/appium/commit/91320ddd02dca89a8cec258d0ab44a49d344d71c))
|
||||
* **support:** update dependency axios to v1.6.4 ([332cc48](https://github.com/appium/appium/commit/332cc48a09b5532a8d51f85f3a24785e2c754e00))
|
||||
* **support:** update dependency axios to v1.6.5 ([#19616](https://github.com/appium/appium/issues/19616)) ([ac73522](https://github.com/appium/appium/commit/ac73522351b31bd6c11972c61daa8b6b8d18fb91))
|
||||
* **support:** update dependency axios to v1.6.6 ([6313704](https://github.com/appium/appium/commit/6313704ee5a8ee3aee726eb512ef259b6fa1041c))
|
||||
* **support:** update dependency axios to v1.6.7 ([795092a](https://github.com/appium/appium/commit/795092a97f6d7569cccc4b5c166f52fef821514b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [2.4.1](https://github.com/appium/appium/compare/appium@2.4.0...appium@2.4.1) (2024-01-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** Properly handle version check for aliases ([474f3ce](https://github.com/appium/appium/commit/474f3ce092c68981655d9deba546a6202acea141))
|
||||
|
||||
|
||||
|
||||
## [2.4.0](https://github.com/appium/appium/compare/appium@2.3.0...appium@2.4.0) (2024-01-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add webdriver bidi support ([2b21e66](https://github.com/appium/appium/commit/2b21e66891e8ab8c3929f04f32e94eb4efdba691))
|
||||
* **appium,support:** Add common shortcuts for doctor checks ([#19562](https://github.com/appium/appium/issues/19562)) ([893b9e1](https://github.com/appium/appium/commit/893b9e15d64d2356ea339596e805543df51fa505))
|
||||
* **appium:** Make doctor extensible ([#19542](https://github.com/appium/appium/issues/19542)) ([a30286b](https://github.com/appium/appium/commit/a30286b6e7b9753f73e55a8f9db14211b3124578))
|
||||
* **appium:** Print the list of available extension scripts if no script name is provided ([#19539](https://github.com/appium/appium/issues/19539)) ([dc2cedf](https://github.com/appium/appium/commit/dc2cedfecd19581321f6947ef1c847065ba2d3d6))
|
||||
* Deny install/upgrade of packages which server dep does not meet the current Appium version ([#19575](https://github.com/appium/appium/issues/19575)) ([73bf68c](https://github.com/appium/appium/commit/73bf68cc602de0999e1956110be225c3fd9087c2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** update dependency ws to v8.15.1 ([92d1172](https://github.com/appium/appium/commit/92d117298442e72ae262f224733068ddf147f4c8))
|
||||
* **appium:** update dependency ws to v8.16.0 ([#19586](https://github.com/appium/appium/issues/19586)) ([fe5245a](https://github.com/appium/appium/commit/fe5245a42784181a9cd7a8ff6b095a622ae40920))
|
||||
* **base-driver:** update dependency async-lock to v1.4.1 ([a304a1f](https://github.com/appium/appium/commit/a304a1f78e658f6f70cbe8e1efd6d06b81d8d34e))
|
||||
* **docutils:** update dependency lilconfig to v3 ([#19489](https://github.com/appium/appium/issues/19489)) ([ea8394a](https://github.com/appium/appium/commit/ea8394a888c835bc54f7f68174a4b6cae85339ea))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.26.3 ([ff767b7](https://github.com/appium/appium/commit/ff767b7f25e97ec9643f691b57ca6ba159dcdcf9))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.27.0 ([9b06b87](https://github.com/appium/appium/commit/9b06b871eb3d9c60428130442ea7ab433ce4df31))
|
||||
* **support:** update dependency axios to v1.6.3 ([441b284](https://github.com/appium/appium/commit/441b2848dae28472356f37fc5d51ac27af7bbe29))
|
||||
* **types:** update dependency type-fest to v4 ([#19592](https://github.com/appium/appium/issues/19592)) ([94b3580](https://github.com/appium/appium/commit/94b358022fdba3050ef94c1f881895f07e24fb75))
|
||||
|
||||
|
||||
|
||||
## [2.3.0](https://github.com/appium/appium/compare/appium@2.2.3...appium@2.3.0) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
+130
-159
@@ -1,214 +1,185 @@
|
||||
## Appium
|
||||
[](https://vshymanskyy.github.io/StandWithUkraine/)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://appium.io/">
|
||||
<img alt="Appium" src="https://raw.githubusercontent.com/appium/appium/master/packages/appium/docs/assets/images/appium-logo-horiz.png" width="500">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
Cross-platform test automation for native, hybrid, mobile web and desktop apps.
|
||||
</p>
|
||||
|
||||
[](https://npmjs.org/package/appium)
|
||||
[](https://npmjs.org/package/appium)
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_shield)
|
||||
|
||||
[](https://github.com/vshymanskyy/StandWithUkraine/)
|
||||
|
||||
[](https://vshymanskyy.github.io/StandWithUkraine/)
|
||||
***
|
||||
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, mobile web and desktop apps. Initially created to automate iOS and Android mobile
|
||||
applications Appium has grown to a full-featured platform that provides [WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for the whole set of different mobile and desktop platforms.
|
||||
See the full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers for more details.
|
||||
<p align="center"><b>
|
||||
<a href="https://appium.io">Documentation</a> |
|
||||
<a href="https://appium.io/docs/en/latest/intro/">Get Started</a> |
|
||||
<a href="https://appium.io/docs/en/latest/ecosystem/">Ecosystem</a> |
|
||||
<a href="https://github.com/appium/appium/blob/master/packages/appium/CHANGELOG.md">Changelog</a> |
|
||||
<a href="https://github.com/appium/appium/blob/master/ROADMAP.md">Roadmap</a> |
|
||||
<a href="https://discuss.appium.io">Discussion Forum</a>
|
||||
</b></p>
|
||||
|
||||
:bangbang: Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. All recent versions of officially supported platform drivers are not compatible to Appium 1.x, and require Appium 2 to run. [Please read the migration guide from 1.x to 2.0](https://appium.io/docs/en/latest/guides/migrating-1-to-2/) to manage the Appium server.
|
||||
***
|
||||
|
||||
:bangbang: If you still use the deprecated Appium Desktop containing the built-in server then you won't be able to upgrade to Appium 2. Consider switching to [Appium Inspector](https://github.com/appium/appium-inspector) instead.
|
||||
Appium is an open-source automation framework that provides
|
||||
[WebDriver](https://www.w3.org/TR/webdriver/)-based automation possibilities for a wide range of
|
||||
different mobile, desktop and IoT platforms. Appium is modular and extensible, and supports multiple
|
||||
programming languages, which means there is an entire ecosystem of related software:
|
||||
* [__Drivers__](#drivers) add support for automating specific platforms
|
||||
* [__Clients__](#clients) allow writing Appium tests in your programming language of choice
|
||||
* [__Plugins__](#plugins) allow to further extend Appium functionality
|
||||
|
||||
### Documentation
|
||||
## Migrating From Appium 1 to Appium 2
|
||||
|
||||
The current Appium documentation is available at [__https://appium.io__](https://appium.io).
|
||||
As of January 1st, 2022, the Appium team no longer maintains or supports Appium 1. All officially
|
||||
supported platform drivers are only compatible with Appium 2.
|
||||
|
||||
### Requirements
|
||||
__[Please read the Migration Guide](https://appium.io/docs/en/latest/guides/migrating-1-to-2/) if you
|
||||
are still using Appium 1.__
|
||||
|
||||
- macOS, Linux or Windows operating system
|
||||
- Node.js 16+. Any relevant [LTS release](https://nodejs.dev/en/about/releases/) should work just fine.
|
||||
- NPM (Node Package Manager) 8+
|
||||
> [!WARNING]
|
||||
> If you use Appium Desktop or Appium Server GUI, you will not be able to upgrade to Appium 2, as
|
||||
both of these tools have been deprecated. Please use [Appium Inspector](https://github.com/appium/appium-inspector)
|
||||
in combination with a standalone Appium 2 server.
|
||||
|
||||
These are only server requirements. Each driver might have its own requirements. Consider checking the corresponding driver tutorial for more details.
|
||||
## Installation
|
||||
|
||||
### Server
|
||||
Appium can be installed using `npm` (other package managers are not currently supported). Please
|
||||
check the [installation docs](http://appium.io/docs/en/latest/quickstart/install/) for the
|
||||
system requirements and further information.
|
||||
|
||||
To install Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
If upgrading from Appium 1, make sure Appium 1 is fully uninstalled (`npm uninstall -g appium`).
|
||||
Unexpected errors might appear if this has not been done.
|
||||
|
||||
```bash
|
||||
npm i -g appium
|
||||
```
|
||||
|
||||
To update Appium 2 server using Node Package Manager (npm) run the following command:
|
||||
Note that this will only install the core Appium server, which cannot automate anything on its own.
|
||||
Please install [drivers](#drivers) for your target platforms in order to automate them.
|
||||
|
||||
```bash
|
||||
npm i -g appium@latest
|
||||
```
|
||||
## Drivers
|
||||
|
||||
:bangbang: Make sure Appium 1 is fully uninstalled before upgrading it to Appium 2 (`npm uninstall -g appium`). Unexpected errors might appear if this has not been done.
|
||||
Appium supports app automation across a variety of platforms, like iOS, Android, macOS, Windows,
|
||||
and more. Each platform is supported by one or more "drivers", which know how to automate that
|
||||
particular platform. You can find a full list of officially-supported and third-party drivers in
|
||||
[Appium Ecosystem's Drivers page](http://appium.io/docs/en/latest/ecosystem/drivers/).
|
||||
|
||||
### Drivers
|
||||
|
||||
Appium supports app automation across a variety of platforms, like iOS,
|
||||
Android, and Windows. Each platform is supported by one or more "drivers",
|
||||
which know how to automate that particular platform. Since version 2.0
|
||||
all drivers have been isolated from the Appium server app and can
|
||||
be managed independently using the [appium driver](https://appiumpro.com/editions/122-installing-appium-20-and-the-driver-and-plugins-cli) command line interface.
|
||||
|
||||
In general, the drivers management in Appium 2 is as simple as:
|
||||
Driver management is done using [Appium's Extension command-line interface](http://appium.io/docs/en/latest/cli/extensions/):
|
||||
|
||||
```bash
|
||||
# To install a new driver from npm
|
||||
appium driver install --source=npm appium-xcuitest-driver[@<version>]
|
||||
# To install a driver from a local folder (useful for developers)
|
||||
appium driver install --source=local /Users/me/sources/appium-xcuitest-driver
|
||||
# To install a new driver from github (hm, maybe it's time to publish it to NPM?)
|
||||
appium driver install --source=github --package=appium-xcuitest-driver appium/appium-xcuitest-driver
|
||||
# Install an official driver from npm (see documentation for a list of such drivers)
|
||||
appium driver install <driver-name>
|
||||
# Install any driver from npm
|
||||
appium driver install --source=npm <driver-name>
|
||||
# See documentation for installation from other sources
|
||||
|
||||
# To list already installed drivers
|
||||
# List already installed drivers
|
||||
appium driver list --installed
|
||||
|
||||
# To update a driver (it must be already installed)
|
||||
appium driver update xcuitest
|
||||
|
||||
# To uninstall a driver (it won't last forever, wouldn't it?)
|
||||
appium driver uninstall xcuitest
|
||||
# Update a driver (it must be already installed)
|
||||
# This will NOT update the major version, in order to prevent breaking changes
|
||||
appium driver update <driver-name>
|
||||
# Update a driver to the most recent version (may include breaking changes)
|
||||
appium driver update <driver-name> --unsafe
|
||||
# Uninstall a driver (it won't last forever, will it?)
|
||||
appium driver uninstall <driver-name>
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#drivers) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-drivers) drivers at
|
||||
the current Appium 2.0 documentation.
|
||||
## Clients
|
||||
|
||||
### Plugins
|
||||
Client libraries enable writing Appium tests in different programming languages. There are
|
||||
officially-supported clients for Java, Python, Ruby, and .NET C#, as well as third-party clients
|
||||
for other languages. You can find a full list of clients in
|
||||
[Appium Ecosystem's Clients page](http://appium.io/docs/en/latest/ecosystem/clients/).
|
||||
|
||||
The concept of plugins is something new that has been added exclusively to Appium2. Plugins allow you to extend server functionality without changing the server code. Plugins could be managed similarly to drivers:
|
||||
## Plugins
|
||||
|
||||
Plugins allow you to extend server functionality without changing the server code. The main
|
||||
difference between drivers and plugins is that the latter must be explicitly enabled on
|
||||
Appium server startup (all installed drivers are enabled by default):
|
||||
|
||||
```bash
|
||||
# To install an officially supported plugin
|
||||
appium plugin install images
|
||||
# To install a plugin from a local folder (useful for developers)
|
||||
appium plugin install --source=local /Users/me/sources/images
|
||||
# To install a new plugin from npm
|
||||
appium plugin install --source=npm appium-device-farm
|
||||
appium --use-plugins=<plugin-name>
|
||||
```
|
||||
|
||||
# To list already installed plugins
|
||||
You can find a full list of officially-supported and third-party plugins in
|
||||
[Appium Ecosystem's Plugins page](http://appium.io/docs/en/latest/ecosystem/plugins/).
|
||||
|
||||
Similarly to drivers, plugin management is also done using
|
||||
[Appium's Extension command-line interface](http://appium.io/docs/en/latest/cli/extensions/):
|
||||
|
||||
```bash
|
||||
# Install an official plugin from npm (see documentation for a list of such plugins)
|
||||
appium plugin install <plugin-name>
|
||||
# Install any plugin from npm
|
||||
appium plugin install --source=npm <plugin-name>
|
||||
# See documentation for installation from other sources
|
||||
|
||||
# List already installed plugins
|
||||
appium plugin list --installed
|
||||
|
||||
# To update a plugins (it must be already installed)
|
||||
appium plugin update appium-device-farm
|
||||
|
||||
# To uninstall a plugin
|
||||
appium plugin uninstall appium-device-farm
|
||||
# Update a plugin (it must be already installed)
|
||||
# This will NOT update the major version, in order to prevent breaking changes
|
||||
appium plugin update <plugin-name>
|
||||
# Update a plugin to the most recent version (may include breaking changes)
|
||||
appium plugin update <plugin-name> --unsafe
|
||||
# Uninstall a plugin
|
||||
appium plugin uninstall <plugin-name>
|
||||
```
|
||||
|
||||
The main difference between drivers and plugins is that the latter must be explicitly enabled on server startup after it was installed (drivers are enabled by default after installation):
|
||||
## Server Command Line Interface
|
||||
|
||||
In order to start sending commands to the Appium server, it must be running on the URL and port
|
||||
where your client library expects it to listen. [Appium's command-line interface](http://appium.io/docs/en/latest/cli/args/)
|
||||
is used to launch and configure the server:
|
||||
|
||||
```bash
|
||||
appium server --use-plugins=device-farm,images
|
||||
```
|
||||
|
||||
You can find a full list of
|
||||
[officially-supported](https://appium.io/docs/en/latest/ecosystem/#plugins) and
|
||||
[third-party](https://appium.io/docs/en/latest/ecosystem/#other-plugins) plugins at
|
||||
the current Appium 2.0 documentation.
|
||||
|
||||
|
||||
### Server Command Line Interface
|
||||
|
||||
In order to start sending commands to Appium over the wire it must be listening
|
||||
on the URL where your client library expects it to listen.
|
||||
Use the following commands to run and configure Appium server:
|
||||
|
||||
```bash
|
||||
# Start the server on the default port and host (e.g. http://0.0.0.0:4723/)
|
||||
# Start the server on the default host (0.0.0.0) and port (4723)
|
||||
appium server
|
||||
# Start the server on the given port, host and use the base path prefix (the default prefix is /)
|
||||
appium server -p 9000 -a 127.0.0.1 -pa /wd/hub
|
||||
|
||||
# Get the list of all supported command line parameters.
|
||||
# This list would also include descriptions of driver-specific
|
||||
# command line arguments for all installed drivers.
|
||||
# Each driver and plugin must have their command line arguments
|
||||
# exposed in a special JSON schema declared as a part of the corresponding
|
||||
# package.json file.
|
||||
appium server --help
|
||||
# You can also omit the 'server' subcommand
|
||||
appium
|
||||
# Start the server on the given host, port and use a custom base path prefix (the default prefix is '/')
|
||||
appium --address 127.0.0.1 --port 9000 --base-path /wd/hub
|
||||
```
|
||||
|
||||
Appium supports execution of parallel server processes as well as parallel driver sessions within
|
||||
Appium supports execution of parallel server processes, as well as parallel driver sessions within a
|
||||
single server process. Refer the corresponding driver documentations regarding which mode is optimal
|
||||
for the particular driver or whether it supports parallel sessions.
|
||||
|
||||
### Why Appium?
|
||||
## Why Appium?
|
||||
|
||||
1. You usually don't have to recompile your app or modify it in any way, due
|
||||
to the use of standard automation APIs on all platforms.
|
||||
2. You can write tests with your favorite dev tools using any
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html)-compatible
|
||||
language such as [Java](https://github.com/appium/java-client),
|
||||
[JavaScript](https://webdriver.io/), [Python](https://github.com/appium/python-client),
|
||||
[Ruby](https://github.com/appium/ruby_lib), [C#](https://github.com/appium/dotnet-client)
|
||||
with the Selenium WebDriver API. There are also various third party
|
||||
client implementations for other languages.
|
||||
1. You usually don't have to recompile your app or modify it in any way, due to the use of standard
|
||||
automation APIs on all platforms.
|
||||
2. You can write tests with your favorite dev tools using any WebDriver-compatible language such as
|
||||
Java, Python, Ruby and C#. There are also third party client implementations for other languages.
|
||||
3. You can use any testing framework.
|
||||
4. Some drivers, like xcuitest and uiautomator2 ones have built-in mobile web and
|
||||
hybrid app support. Within the same script, you can switch seamlessly between native
|
||||
app automation and webview automation, all using the WebDriver model that's already
|
||||
the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple
|
||||
cloud providers that support various Appium drivers (mostly
|
||||
targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) allows
|
||||
visual debugging of automated tests and could be extremely useful for
|
||||
beginners.
|
||||
4. Some drivers like `xcuitest` and `uiautomator2` have built-in mobile web and hybrid app support.
|
||||
Within the same script, you can switch seamlessly between native app automation and webview
|
||||
automation, all using the WebDriver model that's already the standard for web automation.
|
||||
5. You can run your automated tests locally and in a cloud. There are multiple cloud providers that
|
||||
support various Appium drivers (mostly targeting iOS and Android mobile automation).
|
||||
6. [Appium Inspector](https://github.com/appium/appium-inspector) can be used to visually inspect
|
||||
the page source of applications across different platforms, facilitating easier test development.
|
||||
|
||||
Investing in the
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means
|
||||
you are betting on a single, free, and open protocol for testing that has become
|
||||
a web standard. Don't lock yourself into a proprietary stack.
|
||||
Investing in the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means you
|
||||
are betting on a single, free, and open protocol for testing that has become a web standard. Don't
|
||||
lock yourself into a proprietary stack.
|
||||
|
||||
For example, if you use Apple's XCUITest library without Appium you can only
|
||||
write tests using Obj-C/Swift, and you can only run tests through Xcode.
|
||||
Similarly, with Google's UiAutomator or Espresso, you can only write tests in
|
||||
Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
automation, for mobile and beyond. Finally!
|
||||
For example, if you use Apple's XCUITest library without Appium, you can only write tests using
|
||||
Obj-C/Swift, and you can only run tests through Xcode. Similarly, with Google's UiAutomator or
|
||||
Espresso, you can only write tests in Java/Kotlin. Appium opens up the possibility of true
|
||||
cross-platform native app automation, for mobile and beyond!
|
||||
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Intro to Appium](https://appium.io/docs/en/latest/intro/).
|
||||
If you are looking for a more comprehensive description of what this is all about, please read our
|
||||
documentation on [How Does Appium Work?](https://appium.io/docs/en/latest/intro/appium/).
|
||||
|
||||
### Quickstart
|
||||
## License
|
||||
|
||||
Check out our [Quickstart](https://appium.io/docs/en/latest/quickstart/) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
of different languages](https://github.com/appium/appium/tree/1.x/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [Appium Documentation](https://appium.io).
|
||||
You can always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/packages/appium/docs) as well.
|
||||
|
||||
### Contributing
|
||||
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test, and run Appium from the source.
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](https://appium.io/docs/en/latest/intro/history/)
|
||||
|
||||
### User Forums
|
||||
|
||||
Announcements and debates often take place on the [Discussion Group](https://discuss.appium.io),
|
||||
be sure to sign up!
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Please find the troubleshooting guide at the corresponding driver repository/documentation.
|
||||
|
||||
### License
|
||||
[Apache-2.0](./LICENSE)
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
|
||||
|
||||
@@ -196,3 +196,24 @@ Example (run the `reset` script included with the UiAutomator2 driver):
|
||||
```
|
||||
appium driver run uiautomator2 reset
|
||||
```
|
||||
|
||||
### `doctor`
|
||||
|
||||
Run doctor checks for the given extension if it has any. Extension authors can include
|
||||
runnable scripts that assist with driver or plugin preconditions validations. These scripts
|
||||
must be valid Node.js sources, must be listed in the package manifest, and must export
|
||||
class instances that comply to the particular requirements.
|
||||
See the [Building Doctor Checks](../developing/build-doctor-checks.md) tutorial for more
|
||||
details on these requirements.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
appium <ext-type> doctor <ext-name>
|
||||
```
|
||||
|
||||
Example (run doctor checks for the UiAutomator2 driver):
|
||||
|
||||
```
|
||||
appium driver doctor uiautomator2
|
||||
```
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Building Doctor Checks
|
||||
---
|
||||
|
||||
The idea of Appium Doctor is to assist users with driver or plugin preconditions setup. Sometimes such
|
||||
preconditions might be quite complicated and require non-trivial technical knowledge. Doctor checks,
|
||||
which are vanilla Node.js class instances written by extension authors, simplify
|
||||
the setup process by automating diagnostics and possible fixes for the found issues. These checks
|
||||
might also be interactive to ensure better usage experience.
|
||||
|
||||
This tutorial is supposed to be used by plugin or driver authors that would like to help their users
|
||||
to deal with complicated setup or configuration steps.
|
||||
|
||||
## Adding Doctor Checks
|
||||
|
||||
### Typing Requirements
|
||||
|
||||
The term `Doctor Check` literally describes a single javascript class instance that implements the
|
||||
[IDoctorCheck interface](https://github.com/appium/appium/blob/master/packages/types/lib/doctor.ts).
|
||||
The interface defines the following methods and properties:
|
||||
|
||||
- `diagnose(): Promise<DoctorCheckResult>`: Contains the code to diagnose a possible issue
|
||||
- `fix(): Promise<string|null>`: Either fixes the actual problem if `hasAutofix()` returns true or
|
||||
returns a string description for possible manual fixes. If this method throws an exception named
|
||||
`FixSkippedError` and `hasAutofix()` returns true then the result of the method invocation
|
||||
is going to be ignored.
|
||||
- `hasAutofix(): boolean`: Whether calling `fix()` would resolve the found issue
|
||||
- `isOptional(): boolean`: Whether the found issue can be ignored and is not a showstopper
|
||||
- `log: AppiumLogger`: May be used for logging. This property may be assigned
|
||||
by the instance itself or by the Appium server if it is left unassigned.
|
||||
|
||||
The `DoctorCheckResult` object returned by the `diagnose()` method must contain the following properties:
|
||||
|
||||
- `ok: boolean`: Whether the diagnosis found no issues
|
||||
- `optional: boolean`: Whether the diagnosed issue is safe to ignore
|
||||
- `message: string`: The text message describing the diagnostic result
|
||||
|
||||
### Manifest Requirements
|
||||
|
||||
A single extension may export multiple Doctor checks to Appium. In order for these checks to be properly
|
||||
picked up by the server CLI after the corresponding extension is installed they might be listed in the
|
||||
package .json manifest under the `appium.doctor.checks` section similar to the definition below:
|
||||
|
||||
```json
|
||||
// ...
|
||||
"appium": {
|
||||
"driverName": "fake",
|
||||
"automationName": "Fake",
|
||||
"platformNames": [
|
||||
"Fake"
|
||||
],
|
||||
"mainClass": "FakeDriver",
|
||||
"schema": "./build/lib/fake-driver-schema.js",
|
||||
"scripts": {
|
||||
"fake-error": "./build/lib/scripts/fake-error.js",
|
||||
"fake-success": "./build/lib/scripts/fake-success.js",
|
||||
"fake-stdin": "./build/lib/scripts/fake-stdin.js"
|
||||
},
|
||||
"doctor": {
|
||||
"checks": [
|
||||
"./doctor/fake1.js",
|
||||
"./doctor/fake2.js"
|
||||
// ...
|
||||
]
|
||||
}
|
||||
},
|
||||
// ...
|
||||
```
|
||||
|
||||
Also, it makes sense to include the [@appium/types](https://www.npmjs.com/package/@appium/types) import
|
||||
to the package dev dependencies.
|
||||
|
||||
### Implementation Example
|
||||
|
||||
The below example is a "raw" Node.JS implementation that does not use any transpilation:
|
||||
|
||||
```js
|
||||
const {fs, doctor} = require('@appium/support');
|
||||
|
||||
/** @satisfies {import('@appium/types').IDoctorCheck} */
|
||||
class EnvVarAndPathCheck {
|
||||
/**
|
||||
* @param {string} varName
|
||||
*/
|
||||
constructor(varName) {
|
||||
this.varName = varName;
|
||||
}
|
||||
|
||||
async diagnose() {
|
||||
const varValue = process.env[this.varName];
|
||||
if (typeof varValue === 'undefined') {
|
||||
return doctor.nok(`${this.varName} environment variable is NOT set!`);
|
||||
}
|
||||
|
||||
if (await fs.exists(varValue)) {
|
||||
return doctor.ok(`${this.varName} is set to: ${varValue}`);
|
||||
}
|
||||
|
||||
return doctor.nok(`${this.varName} is set to '${varValue}' but this is NOT a valid path!`);
|
||||
}
|
||||
|
||||
async fix() {
|
||||
return (
|
||||
`Make sure the environment variable ${this.varName} is properly configured for the Appium server process`
|
||||
);
|
||||
}
|
||||
|
||||
hasAutofix() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isOptional() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const androidHomeCheck = new EnvVarAndPathCheck('ANDROID_HOME');
|
||||
|
||||
module.exports = {androidHomeCheck};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DoctorCheckResult} CheckResult
|
||||
*/
|
||||
```
|
||||
|
||||
This file could be saved as `doctor/android-home-check.js` and then added to the package.json manifest
|
||||
as
|
||||
|
||||
```json
|
||||
// ...
|
||||
"appium": {
|
||||
// ...
|
||||
"doctor": {
|
||||
"checks": [
|
||||
"./doctor/android-home-check.js",
|
||||
]
|
||||
}
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
```
|
||||
@@ -270,7 +270,7 @@ access the `this.cliArgs` and `this.caps` objects explicitly.
|
||||
In all cases, the `appium:` capability prefix will have been stripped away by the time you are
|
||||
accessing values here, for convenience.
|
||||
|
||||
### Implement WebDriver commands
|
||||
### Implement WebDriver classic commands
|
||||
|
||||
You handle WebDriver commands by implementing functions in your driver class. Each member of the
|
||||
WebDriver Protocol, plus the various Appium extensions, has a corresponding function that you
|
||||
@@ -309,6 +309,21 @@ A few notes:
|
||||
- if your driver doesn't implement a command, users can still try to access the command, and will
|
||||
get a `501 Not Yet Implemented` response error.
|
||||
|
||||
### Implement WebDriver BiDi commands
|
||||
|
||||
[WebDriver BiDi](https://w3c.github.io/webdriver-bidi) is a newer version of the WebDriver spec
|
||||
which is implemented over Websockets instead of HTTP. As an Appium driver author you can take
|
||||
advantage of Appium's BiDi support without having to know anything about the BiDi protocol or
|
||||
Websockets. Implementing handlers for BiDi commands works just the same as implementing handlers
|
||||
for WebDriver classic commands (described in the previous section). You simply define a method on
|
||||
your driver of the appropriate name, and it will be called when the BiDi command is requested by
|
||||
the client. To see which specific names you should use for BiDi commands, have a look at
|
||||
[bidi-commands.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.js)
|
||||
|
||||
Currently, you also need to define a `doesSupportBidi` field on your driver instances, and ensure
|
||||
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.
|
||||
|
||||
### Implement element finding
|
||||
|
||||
Element finding is a special command implementation case. You don't actually want to override
|
||||
@@ -540,6 +555,21 @@ proxyActive() {
|
||||
With those pieces in play, you won't have to reimplement anything that's already implemented by the
|
||||
remote endpoint you're proxying to. Appium will take care of all the proxying for you.
|
||||
|
||||
### Proxy BiDi commands to another BiDi implementation
|
||||
|
||||
All of the above about proxying WebDriver commands is conceptually also valid for proxying BiDi
|
||||
commands specifically. In order to enable BiDi proxying, you need to:
|
||||
|
||||
1. Set the `doesSupportBidi` field on your driver instances to `true`.
|
||||
1. Implement `get bidiProxyUrl` on your driver. This should return a Websocket URL which is the
|
||||
address of the upstream socket you want BiDi commands to be proxied to.
|
||||
|
||||
The intended pattern here is for you to start a session on the upstream implementation, check
|
||||
whether it has an active BiDi socket in the returned capabilities (e.g., the `webSocketUrl`
|
||||
capability), and then to set an internal field to that value, so that it can be returned by `get
|
||||
bidiProxyUrl`. Once all this is in place, Appium will proxy BiDi commands from the client straight
|
||||
to the upstream connection.
|
||||
|
||||
### Extend the existing protocol with new commands
|
||||
|
||||
You may find that the existing commands don't cut it for your driver. If you want to expose
|
||||
@@ -627,6 +657,13 @@ 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.
|
||||
|
||||
|
||||
### Build Appium Doctor checks
|
||||
|
||||
Your users can run `appium driver doctor <driverName>` to run installation and health checks. Visit
|
||||
the [Building Doctor Checks](./build-doctor-checks.md) guide for more information on this
|
||||
capability.
|
||||
|
||||
### Implement handling of Appium settings
|
||||
|
||||
Appium users can send parameters to your driver via CLI args as well as via capabilities. But these
|
||||
@@ -659,6 +696,29 @@ async onSettingsUpdate(key, value) {
|
||||
}
|
||||
```
|
||||
|
||||
### Emit BiDi events
|
||||
|
||||
With the WebDriver BiDi protocol, clients can subscribe to arbitrary events which can be sent
|
||||
asynchronously to the client over the BiDi socket connection. As an Appium driver author you don't
|
||||
need to worry about event subscription. If you want to emit an event with a certain method name and
|
||||
payload, it's as easy as using the built-in event emitter with the `bidiEvent` event.
|
||||
|
||||
As an
|
||||
example, let's say our driver wants to periodically emit CPU load information. We could define an
|
||||
event called `system.cpu`, and a payload that looks like `{load: 0.97}` to signify 97% CPU usage.
|
||||
Whenever we want, our driver can simply call the following code (assuming we have the current load
|
||||
in `this.currentCpuLoad`):
|
||||
|
||||
```js
|
||||
this.eventEmitter.emit('bidiEvent', {
|
||||
method: 'system.cpu',
|
||||
params: {load: this.currentCpuLoad},
|
||||
})
|
||||
```
|
||||
|
||||
Now, if the client has subscribed to the `system.cpu` event, it will be notified with the load
|
||||
whenever the driver emits it.
|
||||
|
||||
### Make itself aware of resources other concurrent drivers are using
|
||||
|
||||
Let's say your driver uses up some system resources, like ports. There are a few ways to make sure
|
||||
|
||||
@@ -412,6 +412,12 @@ Overriding Execute Methods from drivers works as you'd expect: if your plugin de
|
||||
Method with the same name as that of a driver, your command (in this case `plugMeIn`) will be
|
||||
called first. You can choose to run the driver's original behaviour via `next` if you want.
|
||||
|
||||
### Build Appium Doctor checks
|
||||
|
||||
Your users can run `appium plugin doctor <pluginName>` to run installation and health checks. Visit
|
||||
the [Building Doctor Checks](./build-doctor-checks.md) guide for more information on this
|
||||
capability.
|
||||
|
||||
### Update the Appium server object
|
||||
|
||||
You probably don't normally need to update the Appium server object (which is an
|
||||
|
||||
@@ -15,3 +15,4 @@ own Appium extension:
|
||||
* For creating a driver, see the [Build Drivers](./build-drivers.md) page
|
||||
* For creating a plugin, take a look at the [Build Plugins](build-plugins.md) page
|
||||
* Drivers and plugins both need documentation, so check out the [Build Documentation](./build-docs.md) page
|
||||
* For creating a doctor check, see the [Building Doctor Checks](./build-doctor-checks.md) page
|
||||
|
||||
@@ -31,6 +31,7 @@ These clients are not maintained by the Appium team and can be used with other l
|
||||
|[WebdriverIO](https://webdriver.io/docs/appium)|Node.js|
|
||||
|[Nightwatch.js](https://nightwatchjs.org/guide/mobile-app-testing/introduction.html)|Node.js|
|
||||
|[RobotFramework](https://github.com/serhatbolsu/robotframework-appiumlibrary)|DSL|
|
||||
|[multicatch's appium-client](https://github.com/multicatch/appium-client)|Rust|
|
||||
|
||||
In general, any W3C WebDriver spec-compatible client will also integrate well with Appium, though
|
||||
some Appium-specific commands may not be implemented in other clients.
|
||||
|
||||
@@ -28,7 +28,13 @@ appium
|
||||
You should see some output that starts with a line like this:
|
||||
|
||||
```
|
||||
[Appium] Welcome to Appium v2.0.0
|
||||
[Appium] Welcome to Appium v2.4.1
|
||||
```
|
||||
|
||||
In order to update Appium using `npm`:
|
||||
|
||||
```bash
|
||||
npm update -g appium
|
||||
```
|
||||
|
||||
That's it! If you see this, the Appium server is up and running. Go ahead and quit
|
||||
|
||||
@@ -47,6 +47,11 @@ contents:
|
||||
[WebdriverIO documentation](https://webdriver.io/docs/gettingstarted) for a fuller explanation
|
||||
of the various API commands you see and what their purpose is.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/js).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
1. Defining a set of "Capabilities" (parameters) to send to the Appium server so Appium knows what
|
||||
@@ -68,4 +73,3 @@ If all goes well, you'll see the Settings app open up and navigate to the "Batte
|
||||
app closes again.
|
||||
|
||||
Congratulations, you've started your Appium journey! Read on for some [next steps](./next-steps.md) to explore.
|
||||
|
||||
|
||||
@@ -26,11 +26,17 @@ You usually do not need to worry about the prefix.
|
||||
|
||||
It's not within the scope of this guide to give a complete run-down on the Python client
|
||||
library or everything that's happening here, so we'll leave the code itself unexplained in detail for now.
|
||||
|
||||
- You may want to read up particularly on Appium [Capabilities](../guides/caps.md).
|
||||
- [functional test code](https://github.com/appium/python-client/tree/master/test/functional) in Python Client GitHub repository should help to find more working example.
|
||||
- [Documentation](https://appium.github.io/python-client-sphinx/) also helps to find methods
|
||||
defined in the Appium Python Client.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/py).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
1. Defining a set of "Capabilities" (parameters) to send to the Appium server so Appium knows what
|
||||
|
||||
@@ -6,12 +6,32 @@ title: Write a Test (Ruby)
|
||||
---
|
||||
|
||||
|
||||
The [AppiumLib](https://github.com/appium/ruby_lib) and the [AppiumLibCore](https://github.com/appium/ruby_lib_core) (**recommended**) are official Appium client libraries in Ruby, which are available via gem under the [appium_lib](https://rubygems.org/gems/appium_lib) and the [appium_lib_core](https://rubygems.org/gems/appium_lib_core) package names. The appium_lib_core inherits from the Selenium Ruby Binding, and the appium_lib inherits from the appium_lib_core, so installing these libraries include the selenium binding. We recommend `appium_lib_core` if you need a less complex client-side solution. The `appium_lib` has some useful methods the core does not have, but for the cost of greater complexity.
|
||||
The [AppiumLib](https://github.com/appium/ruby_lib) and the [AppiumLibCore](https://github.com/appium/ruby_lib_core) (**recommended**) are official Appium client libraries in Ruby, which are available via gem under the [appium_lib](https://rubygems.org/gems/appium_lib) and the [appium_lib_core](https://rubygems.org/gems/appium_lib_core) package names. The appium_lib_core inherits from the Selenium Ruby Binding, and the appium_lib inherits from the appium_lib_core, so installing these libraries include the selenium binding. We recommend `appium_lib_core` if you need a less complex client-side solution. The `appium_lib` has some useful methods the core does not have, but for the cost of greater complexity and historical methods which may not work in the latest environment.
|
||||
|
||||
As the first step, let's initialize a Gemfile to manage the dependency:
|
||||
|
||||
```bash
|
||||
gem install appium_lib
|
||||
bundle init
|
||||
```
|
||||
|
||||
Then, you could add Appium Ruby Client dependency as below:
|
||||
|
||||
```bash
|
||||
bundle add appium_lib_core
|
||||
# or
|
||||
gem install appium_lib_core
|
||||
# bundle add appium_lib
|
||||
```
|
||||
|
||||
Test code example below uses `test-unit` module, thus please run:
|
||||
|
||||
```bash
|
||||
bundle add test-unit
|
||||
```
|
||||
|
||||
Once these steps has done, your `Gemfile` file should include:
|
||||
|
||||
```ruby title="Gemfile"
|
||||
--8<-- "./sample-code/quickstarts/rb/Gemfile"
|
||||
```
|
||||
|
||||
The `appium_lib_core` is the main part as an Appium client.
|
||||
@@ -21,7 +41,7 @@ The `appium_lib_core` is the main part as an Appium client.
|
||||
This example is by the `appium_lib_core` with `test-unit` gem module.
|
||||
Tes code in `appium_lib` should be similar.
|
||||
|
||||
```python title="test.py"
|
||||
```ruby title="test.rb"
|
||||
--8<-- "./sample-code/quickstarts/rb/test.rb"
|
||||
```
|
||||
|
||||
@@ -34,6 +54,10 @@ Tes code in `appium_lib` should be similar.
|
||||
- [functional test code](https://github.com/appium/ruby_lib_core/tree/master/test/functional) in the appium_lib_core GitHub repository should help to find more working example.
|
||||
- Documentation [appium_lib_core](https://www.rubydoc.info/github/appium/ruby_lib_core) and [appium_lib](https://www.rubydoc.info/github/appium/ruby_lib) also helps to find available methods.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/rb).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
@@ -49,7 +73,7 @@ running in another terminal session, otherwise you'll get an error about not bei
|
||||
to one. Then, you can execute the script:
|
||||
|
||||
```bash
|
||||
bundle install
|
||||
# Please run "bundle install" first if your environment has not run the installation command yet.
|
||||
bundle exec ruby test.rb
|
||||
```
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ nav:
|
||||
- developing/build-drivers.md
|
||||
- developing/build-plugins.md
|
||||
- developing/build-docs.md
|
||||
- developing/build-doctor-checks.md
|
||||
- Contributing:
|
||||
- contributing/index.md
|
||||
- contributing/develop.md
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
---
|
||||
title: Appium文档
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
|
||||
title: 欢迎
|
||||
---
|
||||
<style>
|
||||
.md-typeset h1 {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<div style="text-align: center">
|
||||
<img src="assets/images/appium-logo-horiz.png" style="max-width: 400px;" />
|
||||
</div>
|
||||
|
||||
欢迎访问 Appium 文档!Appium 是一个开源项目和相关软件的生态系统。
|
||||
软件,旨在促进许多应用程序平台的用户界面自动化,包括移动平台(iOS、
|
||||
Android、Tizen)、浏览器(Chrome、Firefox、Safari)、桌面(macOS、Windows)、电视(Roku、tvOS、
|
||||
Android TV、三星)等!
|
||||
|
||||
TODO
|
||||
## 探索文档
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- 查看 [__介绍__](./intro/index.md) 确保您理解关键概念
|
||||
- 通过 [__快速入门__](./quickstart/index.md) 来进行设置并运行基本的 Android 测试
|
||||
- 访问 [__生态系统__](./ecosystem/index.md) 页面,查看您可能需要使用的驱动程序、客户端和插件列表
|
||||
- 参考 [__CLI 参考__](./cli/index.md) 用于从命令行使用 Appium
|
||||
- 查看 [__命令说明__](./commands/index.md) Appium 和插件公开的命令列表
|
||||
- 阅读不同的 [__指南__](./guides/migrating-1-to-2.md) 来获取各种说明、技巧和窍门
|
||||
- 查阅各种第三方的 [__资源__](./resources.md) 来探索网上的Appium运用
|
||||
- 为了创建您自己的Appium拓展, 请查看 [__开发者__](./developing/index.md) 文档
|
||||
- 对 Appium 自身的贡献,请查阅 [__贡献__](./contributing/index.md) 页面
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
---
|
||||
title: 快速开始
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 快速入门介绍
|
||||
---
|
||||
|
||||
让我们开始使用 Appium!为了成功使用本快速入门介绍, 我们建议您
|
||||
首先阅读 [介绍](../intro/index.md), 以便您了解
|
||||
运行 Appium 和编写 Appium 脚本所涉及的概念。
|
||||
|
||||
TODO
|
||||
本快速入门课程的基本计划如下:
|
||||
|
||||
1. 安装 Appium
|
||||
1. 安装 Appium 驱动及其依赖项 (我们将使用 [UiAutomator2
|
||||
driver](https://github.com/appium/appium-uiautomator2-driver) 对于这些例子)
|
||||
1. 用你选择的语言安装 Appium 客户端库(本指南包含以下选项
|
||||
JavaScript、Python、Java、Ruby 和 .NET 的选项)。
|
||||
1. 使用示例应用程序编写并运行简单的 Appium 自动化脚本
|
||||
|
||||
### 要求
|
||||
|
||||
开始之前,请确保您的系统满足运行 Appium 服务器的
|
||||
[要求](../quickstart/requirements.md).其他要求
|
||||
将在安装 UiAutomator2 驱动程序时一并讨论。 本指南还假设
|
||||
您已熟练掌握您的操作系统平台上的基本命令行功能,例如可以运行命令行命令, 设置
|
||||
永久的系统变量, 等等...
|
||||
|
||||
现在你已经准备好开始了!请前往 [安装 Appium](./install.md)吧.
|
||||
|
||||
@@ -1,6 +1,41 @@
|
||||
---
|
||||
title: Install Appium
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 安装 Appium
|
||||
---
|
||||
|
||||
!!! 信息
|
||||
|
||||
TODO
|
||||
安装前,请务必检查[系统要求](./requirements.md).
|
||||
|
||||
您可以使用 `npm` 在全局范围内安装 Appium:
|
||||
|
||||
```bash
|
||||
npm i -g appium
|
||||
```
|
||||
|
||||
!!! 注意
|
||||
|
||||
目前不支持其他软件包管理器。
|
||||
|
||||
安装完成后,您应该可以从命令行运行 Appium:
|
||||
|
||||
```
|
||||
appium
|
||||
```
|
||||
|
||||
你应该会看到一些输出结果,开头一行是这样的:
|
||||
|
||||
```
|
||||
[Appium] 欢迎来到 Appium v2.4.1
|
||||
```
|
||||
|
||||
为了更新Appiums使用 `npm`:
|
||||
|
||||
```bash
|
||||
npm update -g appium
|
||||
```
|
||||
|
||||
就是这样!如果你看到这个,说明 Appium 服务器已经启动并运行。按 (Ctrl-C)
|
||||
继续退出并跳转到到 [下一步](./uiauto2-driver.md), 在这里我们将安装一个用于自动运行 Android 应用程序的驱动程序.
|
||||
@@ -1,6 +1,23 @@
|
||||
---
|
||||
title: Next Steps
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 下一步
|
||||
---
|
||||
|
||||
既然您已经成功配置了您的系统进行Android自动化并运行了一个简单的测试,
|
||||
您可能希望继续探索这份文档。特别是对于初学者,
|
||||
以下是一些很好的指南和参考资料:
|
||||
|
||||
TODO
|
||||
- 在 [生态系统](../ecosystem/index.md) 页面: 浏览可用的驱动程序, 客户端, 插件, 和工具
|
||||
- [管理Appium驱动程序与插件](../guides/managing-exts.md)
|
||||
- [能力](../guides/caps.md)
|
||||
- [设置](../guides/settings.md)
|
||||
|
||||
您还会发现[Appium 监测者](https://github.com/appium/appium-inspector) 编写 Appium 测试不可或缺的工具。
|
||||
是编写 Appium 测试不可或缺的工具,因为它可以对应用程序进行可视化检查,并
|
||||
帮助您发现可用于测试脚本的元素定位器。
|
||||
|
||||
您还可以利用为您提供的众多在线 Appium 课程之一。
|
||||
|
||||
祝你好运,玩得开心!
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
---
|
||||
title: 更多 Appium 资源
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
|
||||
title: 其他资源
|
||||
---
|
||||
|
||||
在这里,您可以找到网络上其他 Appium 资源的链接:
|
||||
|
||||
TODO
|
||||
## 网站
|
||||
|
||||
- [Appium Pro](https://appiumpro.com) - 是Appium其中一名维护者乔纳森-利普斯(Jonathan Lipps)撰写的博客和时事通讯。
|
||||
撰写的博客和时事通讯,其中包含大量实用指南
|
||||
|
||||
## 在线课程
|
||||
|
||||
- [Appium 和 Selenium 基础](https://ui.headspin.io/university/learn/appium-selenium-fundamentals-2020) - Jonathan Lipps 编写的学习 Python、Selenium 和 Appium 的综合视频课程
|
||||
- [使用 Appium 实现移动测试自动化](https://testautomationu.applitools.com/appium-java-tutorial/) - 由 Moataz Nabil 制作的视频课程
|
||||
- [高级Appoium](https://www.linkedin.com/learning/advanced-appium) - 乔纳森-利普斯(Jonathan Lipps)的视频课程
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import _ from 'lodash';
|
||||
import B from 'bluebird';
|
||||
import {getBuildInfo, updateBuildInfo, APPIUM_VER} from './config';
|
||||
import {
|
||||
BaseDriver,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
CREATE_SESSION_COMMAND,
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
MAX_LOG_BODY_LENGTH,
|
||||
promoteAppiumOptions,
|
||||
promoteAppiumOptionsForObject,
|
||||
} from '@appium/base-driver';
|
||||
@@ -16,7 +18,8 @@ import AsyncLock from 'async-lock';
|
||||
import {parseCapsForInnerDriver, pullSettings, makeNonW3cCapsError} from './utils';
|
||||
import {util, node, logger} from '@appium/support';
|
||||
import {getDefaultsForExtension} from './schema';
|
||||
import {DRIVER_TYPE} from './constants';
|
||||
import {DRIVER_TYPE, BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const desiredCapabilityConstraints = /** @type {const} */ ({
|
||||
automationName: {
|
||||
@@ -83,6 +86,12 @@ class AppiumDriver extends DriverCore {
|
||||
/** @type {AppiumServer} */
|
||||
server;
|
||||
|
||||
/** @type {Record<string, import('ws').WebSocket[]>} */
|
||||
bidiSockets;
|
||||
|
||||
/** @type {Record<string, import('ws').WebSocket>} */
|
||||
bidiProxyClients;
|
||||
|
||||
/**
|
||||
* @type {AppiumDriverConstraints}
|
||||
* @readonly
|
||||
@@ -113,6 +122,8 @@ class AppiumDriver extends DriverCore {
|
||||
this.pluginClasses = new Map();
|
||||
this.sessionPlugins = {};
|
||||
this.sessionlessPlugins = [];
|
||||
this.bidiSockets = {};
|
||||
this.bidiProxyClients = {};
|
||||
this.desiredCapConstraints = desiredCapabilityConstraints;
|
||||
this._isShuttingDown = false;
|
||||
|
||||
@@ -241,6 +252,337 @@ 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`,
|
||||
);
|
||||
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
|
||||
@@ -410,6 +752,21 @@ class AppiumDriver extends DriverCore {
|
||||
);
|
||||
await driverInstance.updateSettings(jwpSettings);
|
||||
}
|
||||
|
||||
// if the user has asked for bidi support, and the driver says that it supports bidi,
|
||||
// send our bidi url back to the user. The inner driver will need to have already saved any
|
||||
// internal bidi urls it might want to proxy to, cause we are going to overwrite that
|
||||
// information here!
|
||||
if (dCaps.webSocketUrl && driverInstance.doesSupportBidi) {
|
||||
const {address, port, basePath} = this.args;
|
||||
const isUsingSsl = this.args.sslCertificatePath && this.args.sslKeyPath;
|
||||
const scheme = isUsingSsl ? 'wss' : 'ws';
|
||||
const bidiUrl = `${scheme}://${address}:${port}${basePath}${BIDI_BASE_PATH}/${innerSessionId}`;
|
||||
// @ts-ignore webSocketUrl gets sent by the client as a boolean, but then it is supposed
|
||||
// to come back from the server as a string. TODO figure out how to express this in our
|
||||
// capability constraint system
|
||||
dCaps.webSocketUrl = bidiUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
protocol,
|
||||
@@ -515,6 +872,9 @@ class AppiumDriver extends DriverCore {
|
||||
// be in otherwise
|
||||
delete this.sessions[sessionId];
|
||||
delete this.sessionPlugins[sessionId];
|
||||
|
||||
this.cleanupBidiSockets(sessionId);
|
||||
|
||||
return dstSession;
|
||||
});
|
||||
// this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would
|
||||
@@ -535,6 +895,32 @@ class AppiumDriver extends DriverCore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sessionId
|
||||
*/
|
||||
cleanupBidiSockets(sessionId) {
|
||||
// clean up any bidi sockets associated with session
|
||||
if (this.bidiSockets[sessionId]) {
|
||||
try {
|
||||
this.log.debug(`Closing bidi socket(s) associated with session ${sessionId}`);
|
||||
for (const ws of this.bidiSockets[sessionId]) {
|
||||
// 1001 means server is going away
|
||||
ws.close(1001, 'Appium session is closing');
|
||||
}
|
||||
} catch {}
|
||||
delete this.bidiSockets[sessionId];
|
||||
const proxyClient = this.bidiProxyClients[sessionId];
|
||||
if (proxyClient) {
|
||||
this.log.debug(`Also closing proxy connection to upstream bidi server`);
|
||||
try {
|
||||
// 1000 means normal closure, which seems correct when Appium is acting as the client
|
||||
proxyClient.close(1000);
|
||||
} catch {}
|
||||
delete this.bidiProxyClients[sessionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllSessions(opts = {}) {
|
||||
const sessionsCount = _.size(this.sessions);
|
||||
if (0 === sessionsCount) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
EXT_SUBCOMMAND_RUN,
|
||||
EXT_SUBCOMMAND_UNINSTALL,
|
||||
EXT_SUBCOMMAND_UPDATE,
|
||||
EXT_SUBCOMMAND_DOCTOR,
|
||||
} from '../constants';
|
||||
import {INSTALL_TYPES} from '../extension/extension-config';
|
||||
import {toParserArgs} from '../schema/cli-args';
|
||||
@@ -49,6 +50,7 @@ const getExtensionArgs = _.memoize(function getExtensionArgs() {
|
||||
[EXT_SUBCOMMAND_UNINSTALL]: makeUninstallArgs(type),
|
||||
[EXT_SUBCOMMAND_UPDATE]: makeUpdateArgs(type),
|
||||
[EXT_SUBCOMMAND_RUN]: makeRunArgs(type),
|
||||
[EXT_SUBCOMMAND_DOCTOR]: makeDoctorArgs(type),
|
||||
};
|
||||
}
|
||||
return /** @type {Record<ExtensionType, Record<import('appium/types').CliExtensionSubcommand,ArgumentDefinitions>>} */ (
|
||||
@@ -156,7 +158,28 @@ function makeUninstallArgs(type) {
|
||||
{
|
||||
type: 'str',
|
||||
help:
|
||||
'Name of the driver to uninstall, for example: ' + type === DRIVER_TYPE
|
||||
`Name of the ${type} to uninstall, for example: ` + type === DRIVER_TYPE
|
||||
? DRIVER_EXAMPLE
|
||||
: PLUGIN_EXAMPLE,
|
||||
},
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the opts for the `doctor` subcommand for each extension type
|
||||
* @param {ExtensionType} type
|
||||
* @returns {ArgumentDefinitions}
|
||||
*/
|
||||
function makeDoctorArgs(type) {
|
||||
return new Map([
|
||||
...globalExtensionArgs,
|
||||
[
|
||||
[type],
|
||||
{
|
||||
type: 'str',
|
||||
help:
|
||||
`Name of the ${type} to run doctor checks for, for example: ` + type === DRIVER_TYPE
|
||||
? DRIVER_EXAMPLE
|
||||
: PLUGIN_EXAMPLE,
|
||||
},
|
||||
@@ -222,10 +245,12 @@ function makeRunArgs(type) {
|
||||
['scriptName'],
|
||||
{
|
||||
default: null,
|
||||
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`,
|
||||
`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.`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -66,6 +66,19 @@ export default class DriverCliCommand extends ExtensionCliCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs doctor checks for the given driver.
|
||||
*
|
||||
* @param {DriverDoctorOptions} opts
|
||||
* @returns {Promise<number>} The amount of executed doctor checks.
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async doctor({driver}) {
|
||||
return await super._doctor({
|
||||
installSpec: driver,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('./extension-command').ExtensionArgs} opts
|
||||
@@ -151,3 +164,9 @@ export default class DriverCliCommand extends ExtensionCliCommand {
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode DriverCliCommand.doctor}.
|
||||
* @typedef DriverDoctorOptions
|
||||
* @property {string} driver - name of the driver to run doctor checks for
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import {npm, util, env, console} from '@appium/support';
|
||||
import {npm, util, env, console, fs, system} from '@appium/support';
|
||||
import {spinWith, RingBuffer} from './utils';
|
||||
import {
|
||||
INSTALL_TYPE_NPM,
|
||||
@@ -15,6 +15,10 @@ import {SubProcess} from 'teen_process';
|
||||
import {packageDidChange} from '../extension/package-changed';
|
||||
import {spawn} from 'child_process';
|
||||
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';
|
||||
|
||||
const UPDATE_ALL = 'installed';
|
||||
|
||||
@@ -31,6 +35,23 @@ function receiptToManifest(receipt) {
|
||||
return /** @type {ExtManifest<ExtType>} */ (_.omit(receipt, 'driverName', 'pluginName'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the remote extension version requirements
|
||||
*
|
||||
* @param {string} pkgName Extension name
|
||||
* @param {string} [pkgVer] Extension version (if not provided then the latest is assumed)
|
||||
* @returns {Promise<[string, string|null]>}
|
||||
*/
|
||||
async function getRemoteExtensionVersionReq(pkgName, pkgVer) {
|
||||
const allDeps = await npm.getPackageInfo(
|
||||
`${pkgName}${pkgVer ? `@${pkgVer}` : ``}`,
|
||||
['peerDependencies', 'dependencies']
|
||||
);
|
||||
const requiredVersionPair = _.flatMap(_.values(allDeps).map(_.toPairs))
|
||||
.find(([name]) => name === 'appium');
|
||||
return [npmPackage.version, requiredVersionPair ? requiredVersionPair[1] : null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {ExtensionType} ExtType
|
||||
*/
|
||||
@@ -223,6 +244,29 @@ class ExtensionCliCommand {
|
||||
return listData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given extension is compatible with the currently installed server
|
||||
*
|
||||
* @param {InstallViaNpmArgs} installViaNpmOpts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async _checkInstallCompatibility({installSpec, pkgName, pkgVer, installType}) {
|
||||
if (INSTALL_TYPE_NPM !== installType) {
|
||||
return;
|
||||
}
|
||||
|
||||
await spinWith(this.isJsonOutput, `Checking if '${pkgName}' is compatible`, async () => {
|
||||
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(pkgName, pkgVer);
|
||||
if (serverVersion && extVersionRequirement && !semver.satisfies(serverVersion, extVersionRequirement)) {
|
||||
throw this._createFatalError(
|
||||
`'${installSpec}' cannot be installed because the server version it requires (${extVersionRequirement}) ` +
|
||||
`does not meet the currently installed one (${serverVersion}). Please install ` +
|
||||
`a compatible server version first.`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an extension
|
||||
*
|
||||
@@ -332,6 +376,8 @@ class ExtensionCliCommand {
|
||||
);
|
||||
}
|
||||
|
||||
await this._checkInstallCompatibility(installViaNpmOpts);
|
||||
|
||||
receipt = await this.installViaNpm(installViaNpmOpts);
|
||||
|
||||
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
||||
@@ -695,6 +741,90 @@ class ExtensionCliCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs doctor checks for the given extension.
|
||||
*
|
||||
* @param {DoctorOptions} opts
|
||||
* @returns {Promise<number>} The amount of Doctor checks that were
|
||||
* successfully loaded and executed for the given extension
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async _doctor({installSpec}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
||||
}
|
||||
|
||||
const moduleRoot = this.config.getInstallPath(installSpec);
|
||||
const packageJsonPath = path.join(moduleRoot, 'package.json');
|
||||
if (!await fs.exists(packageJsonPath)) {
|
||||
throw this._createFatalError(
|
||||
`No package.json could be found for "${installSpec}" ${this.type}`
|
||||
);
|
||||
}
|
||||
let doctorSpec;
|
||||
try {
|
||||
doctorSpec = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')).appium?.doctor;
|
||||
} catch (e) {
|
||||
throw this._createFatalError(
|
||||
`The manifest at '${packageJsonPath}' cannot be parsed: ${e.message}`
|
||||
);
|
||||
}
|
||||
if (!doctorSpec) {
|
||||
this.log.info(`The ${this.type} "${installSpec}" does not export any doctor checks`);
|
||||
return 0;
|
||||
}
|
||||
if (!_.isPlainObject(doctorSpec) || !_.isArray(doctorSpec.checks)) {
|
||||
throw this._createFatalError(
|
||||
`The 'doctor' entry in the package manifest '${packageJsonPath}' must be a proper object ` +
|
||||
`containing the 'checks' key with the array of script paths`
|
||||
);
|
||||
}
|
||||
const paths = doctorSpec.checks.map((/** @type {string} */ p) => {
|
||||
const scriptPath = path.resolve(moduleRoot, p);
|
||||
if (!path.normalize(scriptPath).startsWith(path.normalize(moduleRoot))) {
|
||||
this.log.error(
|
||||
`The doctor check script '${p}' from the package manifest '${packageJsonPath}' must be located ` +
|
||||
`in the '${moduleRoot}' root folder. It will be skipped`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return scriptPath;
|
||||
}).filter(Boolean);
|
||||
/** @type {Promise[]} */
|
||||
const loadChecksPromises = [];
|
||||
for (const p of paths) {
|
||||
const promise = (async () => {
|
||||
// https://github.com/nodejs/node/issues/31710
|
||||
const scriptPath = system.isWindows() ? pathToFileURL(p).href : p;
|
||||
try {
|
||||
return await import(scriptPath);
|
||||
} catch (e) {
|
||||
this.log.warn(`Unable to load doctor checks from '${p}': ${e.message}`);
|
||||
}
|
||||
})();
|
||||
loadChecksPromises.push(promise);
|
||||
}
|
||||
const isDoctorCheck = (/** @type {any} */ x) =>
|
||||
['diagnose', 'fix', 'hasAutofix', 'isOptional'].every((method) => _.isFunction(x?.[method]));
|
||||
/** @type {import('@appium/types').IDoctorCheck[]} */
|
||||
const checks = _.flatMap((await B.all(loadChecksPromises)).filter(Boolean).map(_.toPairs))
|
||||
.map(([, value]) => value)
|
||||
.filter(isDoctorCheck);
|
||||
if (_.isEmpty(checks)) {
|
||||
this.log.info(`The ${this.type} "${installSpec}" exports no valid doctor checks`);
|
||||
return 0;
|
||||
}
|
||||
this.log.debug(
|
||||
`Running ${util.pluralize('doctor check', checks.length, true)} ` +
|
||||
`for the "${installSpec}" ${this.type}`
|
||||
);
|
||||
const exitCode = await new Doctor(checks).run();
|
||||
if (exitCode !== DOCTOR_EXIT_CODE.SUCCESS) {
|
||||
throw this._createFatalError('Treatment required');
|
||||
}
|
||||
return checks.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a script cached inside the `scripts` field under `appium`
|
||||
* inside of the extension's `package.json` file. Will throw
|
||||
@@ -729,6 +859,24 @@ class ExtensionCliCommand {
|
||||
);
|
||||
}
|
||||
|
||||
if (!scriptName) {
|
||||
const allScripts = _.toPairs(extScripts);
|
||||
const root = this.config.getInstallPath(installSpec);
|
||||
const existingScripts = await B.filter(
|
||||
allScripts,
|
||||
async ([, p]) => await fs.exists(path.join(root, p))
|
||||
);
|
||||
if (_.isEmpty(existingScripts)) {
|
||||
this.log.info(`The ${this.type} named '${installSpec}' does not contain any scripts`);
|
||||
} else {
|
||||
this.log.info(`The ${this.type} named '${installSpec}' contains ` +
|
||||
`${util.pluralize('script', existingScripts.length, true)}:`);
|
||||
existingScripts.forEach(([name]) => this.log.info(` - ${name}`));
|
||||
}
|
||||
this.log.ok(`Successfully retrieved the list of scripts`.green);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!(scriptName in /** @type {Record<string,string>} */ (extScripts))) {
|
||||
throw this._createFatalError(
|
||||
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
||||
@@ -874,11 +1022,18 @@ export {ExtensionCliCommand as ExtensionCommand};
|
||||
* Options for {@linkcode ExtensionCliCommand._run}.
|
||||
* @typedef RunOptions
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string} [scriptName] - name of the script to run. If not provided
|
||||
* then all available script names will be printed
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
* @property {boolean} [bufferOutput] - if true, will buffer the output of the script and return it
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCliCommand.doctor}.
|
||||
* @typedef DoctorOptions
|
||||
* @property {string} installSpec - name of the extension to run doctor checks for
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return value of {@linkcode ExtensionCliCommand._run}
|
||||
*
|
||||
|
||||
@@ -2,7 +2,17 @@ import {fs} from '@appium/support';
|
||||
import {ArgumentParser} from 'argparse';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from '../constants';
|
||||
import {
|
||||
DRIVER_TYPE,
|
||||
EXT_SUBCOMMAND_DOCTOR,
|
||||
EXT_SUBCOMMAND_INSTALL,
|
||||
EXT_SUBCOMMAND_LIST,
|
||||
EXT_SUBCOMMAND_RUN,
|
||||
EXT_SUBCOMMAND_UNINSTALL,
|
||||
EXT_SUBCOMMAND_UPDATE,
|
||||
PLUGIN_TYPE,
|
||||
SERVER_SUBCOMMAND
|
||||
} from '../constants';
|
||||
import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
|
||||
import {rootDir} from '../config';
|
||||
import {getExtensionArgs, getServerArgs} from './args';
|
||||
@@ -191,8 +201,7 @@ class ArgParser {
|
||||
|
||||
const serverArgs = getServerArgs();
|
||||
for (const [flagsOrNames, opts] of serverArgs) {
|
||||
// TS doesn't like the spread operator here.
|
||||
// @ts-ignore
|
||||
// @ts-ignore TS doesn't like the spread operator here.
|
||||
serverParser.add_argument(...flagsOrNames, {...opts});
|
||||
}
|
||||
|
||||
@@ -221,33 +230,38 @@ class ArgParser {
|
||||
*/
|
||||
const parserSpecs = [
|
||||
{
|
||||
command: 'list',
|
||||
command: EXT_SUBCOMMAND_LIST,
|
||||
args: extensionArgs[type].list,
|
||||
help: `List available and installed ${type}s`,
|
||||
aliases: ['ls'],
|
||||
},
|
||||
{
|
||||
command: 'install',
|
||||
command: EXT_SUBCOMMAND_INSTALL,
|
||||
args: extensionArgs[type].install,
|
||||
help: `Install a ${type}`,
|
||||
},
|
||||
{
|
||||
command: 'uninstall',
|
||||
command: EXT_SUBCOMMAND_UNINSTALL,
|
||||
args: extensionArgs[type].uninstall,
|
||||
help: `Uninstall a ${type}`,
|
||||
},
|
||||
{
|
||||
command: 'update',
|
||||
command: EXT_SUBCOMMAND_UPDATE,
|
||||
args: extensionArgs[type].update,
|
||||
help: `Update installed ${type}s to the latest version`,
|
||||
},
|
||||
{
|
||||
command: 'run',
|
||||
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}`,
|
||||
},
|
||||
{
|
||||
command: EXT_SUBCOMMAND_DOCTOR,
|
||||
args: extensionArgs[type].doctor,
|
||||
help: `Run doctor checks (if any defined) for the given ${type}`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const {command, args, help, aliases} of parserSpecs) {
|
||||
|
||||
@@ -65,6 +65,19 @@ export default class PluginCliCommand extends ExtensionCliCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs doctor checks for the given plugin
|
||||
*
|
||||
* @param {PluginDoctorOptions} opts
|
||||
* @returns {Promise<number>} The amount of executed doctor checks.
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async doctor({plugin}) {
|
||||
return await super._doctor({
|
||||
installSpec: plugin,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('./extension-command').ExtensionArgs} opts
|
||||
@@ -141,3 +154,9 @@ export default class PluginCliCommand extends ExtensionCliCommand {
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for {@linkcode PluginCliCommand.doctor}.
|
||||
* @typedef PluginDoctorOptions
|
||||
* @property {string} plugin - name of the plugin to run doctor checks for
|
||||
*/
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import ora from 'ora';
|
||||
|
||||
const JSON_SPACES = 4;
|
||||
export const JSON_SPACES = 4;
|
||||
|
||||
/***
|
||||
* Log an error to the console and exit the process.
|
||||
* @param {boolean} json - whether we should log json or text
|
||||
* @param {any} msg - error message, object, Error instance, etc.
|
||||
*/
|
||||
function errAndQuit(json, msg) {
|
||||
export function errAndQuit(json, msg) {
|
||||
if (json) {
|
||||
console.log(JSON.stringify({error: `${msg}`}, null, JSON_SPACES));
|
||||
} else {
|
||||
@@ -26,7 +26,7 @@ function errAndQuit(json, msg) {
|
||||
* @param {boolean} json - whether we are in json mode (and should therefore not log)
|
||||
* @param {string} msg - string to log
|
||||
*/
|
||||
function log(json, msg) {
|
||||
export function log(json, msg) {
|
||||
!json && console.log(msg);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function log(json, msg) {
|
||||
* @param {string} msg - string to log
|
||||
* @param {function} fn - function to wrap with spinning
|
||||
*/
|
||||
async function spinWith(json, msg, fn) {
|
||||
export async function spinWith(json, msg, fn) {
|
||||
if (json) {
|
||||
return await fn();
|
||||
}
|
||||
@@ -52,7 +52,7 @@ async function spinWith(json, msg, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
class RingBuffer {
|
||||
export class RingBuffer {
|
||||
constructor(size = 50) {
|
||||
this.size = size;
|
||||
this.buffer = [];
|
||||
@@ -70,5 +70,3 @@ class RingBuffer {
|
||||
this.buffer.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
export {errAndQuit, log, spinWith, JSON_SPACES, RingBuffer};
|
||||
|
||||
@@ -4,17 +4,14 @@ import {system, fs} from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import {exec} from 'teen_process';
|
||||
import semver from 'semver';
|
||||
import findUp from 'find-up';
|
||||
import os from 'node:os';
|
||||
import {npmPackage} from './utils';
|
||||
import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
|
||||
|
||||
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
const APPIUM_VER = npmPackage.version;
|
||||
export const APPIUM_VER = npmPackage.version;
|
||||
const ENGINES = /** @type {Record<string,string>} */ (npmPackage.engines);
|
||||
const MIN_NODE_VERSION = ENGINES.node;
|
||||
|
||||
const GIT_META_ROOT = '.git';
|
||||
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
|
||||
const GITHUB_API = 'https://api.github.com/repos/appium/appium';
|
||||
|
||||
@@ -44,27 +41,25 @@ async function updateBuildInfo(useGithubApiFallback = false) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Git metadata dir (see `GIT_META_ROOT`)
|
||||
*
|
||||
* This is needed because Appium cannot assume `package.json` and `.git` are in the same
|
||||
* directory. Monorepos, see?
|
||||
* @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
|
||||
*/
|
||||
async function findGitRoot() {
|
||||
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
|
||||
}
|
||||
/** @type {() => Promise<string?>} */
|
||||
const getFullGitPath = _.memoize(async function getFullGitPath() {
|
||||
try {
|
||||
return await fs.which(GIT_BINARY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {boolean} [useGithubApiFallback]
|
||||
* @returns {Promise<string?>}
|
||||
*/
|
||||
async function getGitRev(useGithubApiFallback = false) {
|
||||
const gitRoot = await findGitRoot();
|
||||
if (gitRoot) {
|
||||
const fullGitPath = await getFullGitPath();
|
||||
if (fullGitPath) {
|
||||
try {
|
||||
const {stdout} = await exec(GIT_BINARY, ['rev-parse', 'HEAD'], {
|
||||
cwd: gitRoot,
|
||||
const {stdout} = await exec(fullGitPath, ['rev-parse', 'HEAD'], {
|
||||
cwd: __dirname,
|
||||
});
|
||||
return stdout.trim();
|
||||
} catch (ign) {}
|
||||
@@ -94,11 +89,11 @@ async function getGitRev(useGithubApiFallback = false) {
|
||||
* @returns {Promise<string?>}
|
||||
*/
|
||||
async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
|
||||
const gitRoot = await findGitRoot();
|
||||
if (gitRoot) {
|
||||
const fullGitPath = await getFullGitPath();
|
||||
if (fullGitPath) {
|
||||
try {
|
||||
const {stdout} = await exec(GIT_BINARY, ['show', '-s', '--format=%ci', commitSha], {
|
||||
cwd: gitRoot,
|
||||
const {stdout} = await exec(fullGitPath, ['show', '-s', '--format=%ci', commitSha], {
|
||||
cwd: __dirname,
|
||||
});
|
||||
return stdout.trim();
|
||||
} catch (ign) {}
|
||||
@@ -321,7 +316,6 @@ export {
|
||||
showBuildInfo,
|
||||
getNonDefaultServerArgs,
|
||||
getGitRev,
|
||||
APPIUM_VER,
|
||||
updateBuildInfo,
|
||||
showConfig,
|
||||
rootDir,
|
||||
|
||||
@@ -64,6 +64,7 @@ export const EXT_SUBCOMMAND_INSTALL = 'install';
|
||||
export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
|
||||
export const EXT_SUBCOMMAND_UPDATE = 'update';
|
||||
export const EXT_SUBCOMMAND_RUN = 'run';
|
||||
export const EXT_SUBCOMMAND_DOCTOR = 'doctor';
|
||||
|
||||
/**
|
||||
* Current revision of the manifest (`extensions.yaml`) schema
|
||||
@@ -77,3 +78,14 @@ export const CURRENT_SCHEMA_REV = 4;
|
||||
* memory usage, perf, and/or log output, and higher limits may be difficult to scan.
|
||||
*/
|
||||
export const LONG_STACKTRACE_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* Where should the bidi websocket handler live on the server?
|
||||
*/
|
||||
export const BIDI_BASE_PATH = '/bidi';
|
||||
|
||||
/**
|
||||
* The name of the event for drivers to emit when they want to send bidi events to a client over
|
||||
* a bidi socket
|
||||
*/
|
||||
export const BIDI_EVENT_NAME = 'bidiEvent';
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import '@colors/colors';
|
||||
import _ from 'lodash';
|
||||
import { util, doctor, logger } from '@appium/support';
|
||||
|
||||
export const EXIT_CODE = /** @type {const} */ Object.freeze({
|
||||
SUCCESS: 0,
|
||||
HAS_MAJOR_ISSUES: 127,
|
||||
});
|
||||
|
||||
export class Doctor {
|
||||
/**
|
||||
* @param {DoctorCheck[]} [checks=[]]
|
||||
*/
|
||||
constructor(checks = []) {
|
||||
this.log = logger.getLogger('Doctor');
|
||||
/** @type {DoctorCheck[]} */
|
||||
this.checks = checks;
|
||||
this.checks
|
||||
.filter((c) => _.isNil(c.log))
|
||||
.forEach((c) => { c.log = this.log; });
|
||||
/** @type {DoctorIssue[]} */
|
||||
this.foundIssues = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {DoctorIssue[]}
|
||||
*/
|
||||
get issuesRequiredToFix() {
|
||||
return this.foundIssues.filter((f) => !f.check.isOptional());
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {DoctorIssue[]}
|
||||
*/
|
||||
get issuesOptionalToFix() {
|
||||
return this.foundIssues.filter((f) => f.check.isOptional());
|
||||
}
|
||||
|
||||
/**
|
||||
* The doctor shows the report
|
||||
*/
|
||||
async diagnose() {
|
||||
this.log.info(`### Starting doctor diagnostics ###`);
|
||||
this.foundIssues = [];
|
||||
for (const check of this.checks) {
|
||||
const res = await check.diagnose();
|
||||
const issue = this.toIssue(res, check);
|
||||
if (issue) {
|
||||
this.foundIssues.push(issue);
|
||||
}
|
||||
}
|
||||
this.log.info(
|
||||
`### Diagnostic completed, ${this.buildFixMessage()}. ###`
|
||||
);
|
||||
this.log.info('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async reportManualIssues() {
|
||||
const manualIssues = _.filter(this.issuesRequiredToFix, (f) => !f.check.hasAutofix());
|
||||
const manualIssuesOptional = _.filter(this.issuesOptionalToFix, (f) => !f.check.hasAutofix());
|
||||
|
||||
const handleIssues = async (headerLogs, issues) => {
|
||||
if (_.isEmpty(issues)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const logMsg of headerLogs) {
|
||||
this.log.info(logMsg);
|
||||
}
|
||||
/** @type {string[]} */
|
||||
const fixMessages = [];
|
||||
for (const issue of issues) {
|
||||
let message;
|
||||
try {
|
||||
message = await issue.check.fix();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
if (message) {
|
||||
fixMessages.push(message);
|
||||
}
|
||||
}
|
||||
for (const m of _.uniq(fixMessages)) {
|
||||
this.log.warn(` \u279C ${m}`);
|
||||
}
|
||||
this.log.info('');
|
||||
};
|
||||
|
||||
await handleIssues([
|
||||
'### Manual Fixes Needed ###',
|
||||
'The configuration cannot be automatically fixed, please do the following first:',
|
||||
], manualIssues);
|
||||
await handleIssues([
|
||||
'### Optional Manual Fixes ###',
|
||||
'To fix these optional issues, please do the following manually:',
|
||||
], manualIssuesOptional);
|
||||
|
||||
if (manualIssues.length > 0) {
|
||||
this.log.info('###');
|
||||
this.log.info('');
|
||||
this.log.info('Bye! Run doctor again when all manual fixes have been applied!');
|
||||
this.log.info('');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DoctorIssue} f
|
||||
*/
|
||||
async runAutoFix(f) {
|
||||
this.log.info(`### Fixing: ${f.error} ###`);
|
||||
try {
|
||||
await f.check.fix();
|
||||
} catch (err) {
|
||||
if (err.constructor.name === doctor.FixSkippedError.name) {
|
||||
this.log.info(`### Skipped fix ###`);
|
||||
return;
|
||||
} else {
|
||||
this.log.warn(`${err}`.replace(/\n$/g, ''));
|
||||
this.log.info(`### Fix did not succeed ###`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.log.info('Checking if this was fixed:');
|
||||
const res = await f.check.diagnose();
|
||||
if (res.ok) {
|
||||
f.fixed = true;
|
||||
this.log.info(` ${'\u2714'.green} ${res.message}`);
|
||||
this.log.info(`### Fix was successfully applied ###`);
|
||||
} else {
|
||||
this.log.info(` ${'\u2716'.red} ${res.message}`);
|
||||
this.log.info(`### Fix was applied but issue remains ###`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async runAutoFixes() {
|
||||
const autoFixes = _.filter(this.foundIssues, (f) => f.check.hasAutofix());
|
||||
for (const f of autoFixes) {
|
||||
await this.runAutoFix(f);
|
||||
this.log.info('');
|
||||
}
|
||||
if (_.find(autoFixes, (f) => !f.fixed)) {
|
||||
// a few issues remain.
|
||||
this.log.info('Bye! A few issues remain, fix manually and/or rerun doctor!');
|
||||
this.log.info('');
|
||||
return false;
|
||||
}
|
||||
// nothing left to fix.
|
||||
this.log.info('Bye! All issues have been fixed!');
|
||||
this.log.info('');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<EXIT_CODE[keyof EXIT_CODE]>}
|
||||
*/
|
||||
async run() {
|
||||
await this.diagnose();
|
||||
if (this.reportSuccess()) {
|
||||
return EXIT_CODE.SUCCESS;
|
||||
}
|
||||
if (await this.reportManualIssues()) {
|
||||
return EXIT_CODE.HAS_MAJOR_ISSUES;
|
||||
}
|
||||
if (!await this.runAutoFixes()) {
|
||||
return EXIT_CODE.HAS_MAJOR_ISSUES;
|
||||
}
|
||||
return EXIT_CODE.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DoctorCheckResult} result
|
||||
* @param {DoctorCheck} check
|
||||
* @returns {DoctorIssue?}
|
||||
*/
|
||||
toIssue(result, check) {
|
||||
if (result.ok) {
|
||||
this.log.info(` ${'\u2714'.green} ${result.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorMessage = result.optional
|
||||
? ` ${'\u2716'.yellow} ${result.message}`
|
||||
: ` ${'\u2716'.red} ${result.message}`;
|
||||
this.log.warn(errorMessage);
|
||||
return {
|
||||
error: errorMessage,
|
||||
check,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
buildFixMessage() {
|
||||
return `${util.pluralize('required fix', this.issuesRequiredToFix.length, true)} needed, ` +
|
||||
`${util.pluralize('optional fix', this.issuesOptionalToFix.length, true)} possible`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
reportSuccess() {
|
||||
if (this.issuesRequiredToFix.length === 0 && this.issuesOptionalToFix.length === 0) {
|
||||
this.log.info('Everything looks good, bye!');
|
||||
this.log.info('');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DoctorIssue
|
||||
* @property {DoctorCheck} check
|
||||
* @property {string} error
|
||||
* @property {boolean} [fixed]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').IDoctorCheck} DoctorCheck
|
||||
* @typedef {import('@appium/types').DoctorCheckResult} DoctorCheckResult
|
||||
*/
|
||||
@@ -311,7 +311,9 @@ export class ExtensionConfig {
|
||||
const invalidFieldsText = invalidFields.map((field) => `"${field}"`).join(', ');
|
||||
|
||||
warnings.push(
|
||||
`${extTypeText} "${extName}" (package \`${pkgName}\`) has ${invalidFieldsEnumerationText} (${invalidFieldsText}) in \`extensions.yaml\`; this may cause upgrades done via the \`appium\` CLI tool to fail. Please reinstall with \`appium ${this.extensionType} uninstall ${extName}\` and \`appium ${this.extensionType} install ${extName}\` to attempt a fix.`
|
||||
`${extTypeText} "${extName}" (package \`${pkgName}\`) has ${invalidFieldsEnumerationText} (${invalidFieldsText}) in \`extensions.yaml\`; ` +
|
||||
`this may cause upgrades done via the \`appium\` CLI tool to fail. Please reinstall with \`appium ${this.extensionType} uninstall ` +
|
||||
`${extName}\` and \`appium ${this.extensionType} install ${extName}\` to attempt a fix.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -331,16 +333,16 @@ export class ExtensionConfig {
|
||||
);
|
||||
if (extListData?.installed) {
|
||||
const {updateVersion, upToDate} = extListData;
|
||||
if (!upToDate) {
|
||||
if (!upToDate && updateVersion) {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`its peer dependency on older Appium v${appiumVersion}. Please upgrade \`${pkgName}\` to v${updateVersion} or newer.`
|
||||
`its peer dependency on Appium ${appiumVersion}. Try to upgrade \`${pkgName}\` to v${updateVersion} or newer.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`its peer dependency on older Appium v${appiumVersion}. Please ask the developer of \`${pkgName}\` to update the peer dependency on Appium to v${APPIUM_VER}.`
|
||||
`its peer dependency on Appium ${appiumVersion}. Please install a compatible version of the ${_.toLower(extTypeText)}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -354,13 +356,15 @@ export class ExtensionConfig {
|
||||
if (!extListData?.upToDate && extListData?.updateVersion) {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`an invalid or missing peer dependency on Appium. A newer version of \`${pkgName}\` is available; please attempt to upgrade "${extName}" to v${extListData.updateVersion} or newer.`
|
||||
`an invalid or missing peer dependency on Appium. A newer version of \`${pkgName}\` is available; ` +
|
||||
`please attempt to upgrade "${extName}" to v${extListData.updateVersion} or newer.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`an invalid or missing peer dependency on Appium. Please ask the developer of \`${pkgName}\` to add a peer dependency on \`^appium@${APPIUM_VER}\`.`
|
||||
`an invalid or missing peer dependency on Appium. ` +
|
||||
`Please ask the developer of \`${pkgName}\` to add a peer dependency on \`^appium@${APPIUM_VER}\`.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,9 +99,12 @@ export async function getActivePlugins(pluginConfig, maxParallelImports, usePlug
|
||||
`The reserved plugin name '${pluginName}' cannot be combined with other names.`
|
||||
);
|
||||
} else {
|
||||
const suffix = _.isEmpty(pluginConfig.installedExtensions)
|
||||
? `You don't have any plugins installed yet.`
|
||||
: `Only the following ${_.size(pluginConfig.installedExtensions) === 1 ? `plugin is` : `plugins are`} ` +
|
||||
`available: ${_.keys(pluginConfig.installedExtensions)}`;
|
||||
throw new Error(
|
||||
`Could not load the plugin '${pluginName}' because it is not installed. ` +
|
||||
`Only the following plugins are available: ${_.keys(pluginConfig.installedExtensions)}`
|
||||
`Could not load the plugin '${pluginName}' because it is not installed. ${suffix}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -131,9 +134,12 @@ export async function getActiveDrivers(driverConfig, maxParallelImports, useDriv
|
||||
if (driverName in driverConfig.installedExtensions) {
|
||||
filteredDriverNames.push(driverName);
|
||||
} else {
|
||||
const suffix = _.isEmpty(driverConfig.installedExtensions)
|
||||
? `You don't have any drivers installed yet.`
|
||||
: `Only the following ${_.size(driverConfig.installedExtensions) === 1 ? `driver is` : `drivers are`} ` +
|
||||
`available: ${_.keys(driverConfig.installedExtensions)}`;
|
||||
throw new Error(
|
||||
`Could not load the driver '${driverName}' because it is not installed. ` +
|
||||
`Only the following drivers are available: ${_.keys(driverConfig.installedExtensions)}`
|
||||
`Could not load the driver '${driverName}' because it is not installed. ${suffix}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,9 @@ async function registerNode(data, addr, port, basePath) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (err) {
|
||||
logger.errorAndThrow(
|
||||
throw logger.errorWithException(
|
||||
`Syntax error in node configuration file ${configFilePath}: ${err.message}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {WebSocketServer} from 'ws';
|
||||
import {init as logsinkInit} from './logsink'; // this import needs to come first since it sets up global npmlog
|
||||
import logger from './logger'; // logger needs to remain second
|
||||
import {
|
||||
@@ -373,8 +374,13 @@ async function main(args) {
|
||||
serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
|
||||
}
|
||||
let server;
|
||||
const bidiServer = new WebSocketServer({noServer: true});
|
||||
bidiServer.on('connection', appiumDriver.onBidiConnection.bind(appiumDriver));
|
||||
bidiServer.on('error', appiumDriver.onBidiServerError.bind(appiumDriver));
|
||||
try {
|
||||
server = await baseServer(serverOpts);
|
||||
server.addWebSocketHandler('/bidi', bidiServer);
|
||||
server.addWebSocketHandler('/bidi/:sessionId', bidiServer);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Could not configure Appium server. It's possible that a driver or plugin tried ` +
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import {processCapabilities, PROTOCOLS, STANDARD_CAPS, errors} from '@appium/base-driver';
|
||||
import {inspect as dump} from 'util';
|
||||
import {node} from '@appium/support';
|
||||
import {node, fs} from '@appium/support';
|
||||
import path from 'path';
|
||||
import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE} from './constants';
|
||||
import os from 'node:os';
|
||||
@@ -11,6 +11,7 @@ const W3C_APPIUM_PREFIX = 'appium';
|
||||
const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
|
||||
export const V4_BROADCAST_IP = '0.0.0.0';
|
||||
export const V6_BROADCAST_IP = '::';
|
||||
export const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.3.0",
|
||||
"version": "2.5.1",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -60,40 +60,40 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.4.4",
|
||||
"@appium/base-plugin": "^2.2.25",
|
||||
"@appium/docutils": "^1.0.1",
|
||||
"@appium/schema": "^0.5.0",
|
||||
"@appium/support": "^4.1.11",
|
||||
"@appium/types": "^0.15.0",
|
||||
"@appium/base-driver": "^9.5.2",
|
||||
"@appium/base-plugin": "^2.2.28",
|
||||
"@appium/docutils": "^1.0.4",
|
||||
"@appium/schema": "~0.5.0",
|
||||
"@appium/support": "^4.2.2",
|
||||
"@appium/types": "^0.16.1",
|
||||
"@sidvind/better-ajv-errors": "2.1.3",
|
||||
"@types/argparse": "2.0.14",
|
||||
"@types/bluebird": "3.5.42",
|
||||
"@types/fancy-log": "2.0.2",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/semver": "7.5.7",
|
||||
"@types/teen_process": "2.0.4",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.4.0",
|
||||
"async-lock": "1.4.1",
|
||||
"asyncbox": "3.0.0",
|
||||
"axios": "1.6.2",
|
||||
"axios": "1.6.7",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
"lilconfig": "2.1.0",
|
||||
"lilconfig": "3.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"npmlog": "7.0.1",
|
||||
"ora": "5.4.1",
|
||||
"package-changed": "3.0.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.5.4",
|
||||
"semver": "7.6.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.1.1",
|
||||
"type-fest": "4.8.3",
|
||||
"type-fest": "4.10.1",
|
||||
"winston": "3.11.0",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"ws": "8.16.0",
|
||||
"yaml": "2.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "8.26.2"
|
||||
"webdriverio": "8.31.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
EXT_SUBCOMMAND_LIST as LIST,
|
||||
EXT_SUBCOMMAND_RUN as RUN,
|
||||
EXT_SUBCOMMAND_UNINSTALL as UNINSTALL,
|
||||
EXT_SUBCOMMAND_DOCTOR as DOCTOR,
|
||||
KNOWN_DRIVERS,
|
||||
} from '../../lib/constants';
|
||||
import {FAKE_DRIVER_DIR, resolveFixture} from '../helpers';
|
||||
@@ -51,6 +52,11 @@ describe('Driver CLI', function () {
|
||||
*/
|
||||
let runUninstall;
|
||||
|
||||
/**
|
||||
* @type {(args: string[]) => Promise<ExtRecord<DriverType>>}
|
||||
*/
|
||||
let runDoctor;
|
||||
|
||||
async function resetAppiumHome() {
|
||||
await fs.rimraf(appiumHome);
|
||||
await fs.mkdirp(appiumHome);
|
||||
@@ -67,6 +73,8 @@ describe('Driver CLI', function () {
|
||||
/** @type {ReturnType<typeof runList>} */ (await run([DRIVER_TYPE, LIST, ...args]));
|
||||
runRun = async (args) =>
|
||||
/** @type {ReturnType<typeof runRun>} */ (await run([DRIVER_TYPE, RUN, ...args]));
|
||||
runDoctor = async (args) =>
|
||||
/** @type {ReturnType<typeof runDoctor>} */ (await run([DRIVER_TYPE, DOCTOR, ...args]));
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
@@ -404,6 +412,22 @@ describe('Driver CLI', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('doctor', function () {
|
||||
const driverName = 'fake';
|
||||
|
||||
before(async function () {
|
||||
await resetAppiumHome();
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR);
|
||||
});
|
||||
|
||||
describe('when the driver defines doctor checks', function () {
|
||||
it('should load and run them', async function () {
|
||||
const checksLen = await runDoctor([driverName]);
|
||||
checksLen.should.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
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';
|
||||
@@ -43,6 +44,35 @@ const wdOpts = {
|
||||
connectionRetryCount: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} appiumHome
|
||||
*/
|
||||
async function initFakeDriver(appiumHome) {
|
||||
const {driverConfig} = await loadExtensions(appiumHome);
|
||||
const driverList = await runExtensionCommand(
|
||||
{
|
||||
driverCommand: 'list',
|
||||
subcommand: DRIVER_TYPE,
|
||||
suppressOutput: true,
|
||||
showInstalled: true,
|
||||
},
|
||||
driverConfig,
|
||||
);
|
||||
if (!_.has(driverList, 'fake')) {
|
||||
await runExtensionCommand(
|
||||
{
|
||||
driverCommand: 'install',
|
||||
driver: FAKE_DRIVER_DIR,
|
||||
installType: INSTALL_TYPE_LOCAL,
|
||||
subcommand: DRIVER_TYPE,
|
||||
},
|
||||
driverConfig,
|
||||
);
|
||||
}
|
||||
|
||||
return await driverConfig.requireAsync('fake');
|
||||
}
|
||||
|
||||
describe('FakeDriver via HTTP', function () {
|
||||
/** @type {AppiumServer} */
|
||||
let server;
|
||||
@@ -64,30 +94,7 @@ describe('FakeDriver via HTTP', function () {
|
||||
wdOpts.port = port = await getTestPort();
|
||||
testServerBaseUrl = `http://${TEST_HOST}:${port}`;
|
||||
testServerBaseSessionUrl = `${testServerBaseUrl}/session`;
|
||||
// first ensure we have fakedriver installed
|
||||
const {driverConfig} = await loadExtensions(appiumHome);
|
||||
const driverList = await runExtensionCommand(
|
||||
{
|
||||
driverCommand: 'list',
|
||||
subcommand: DRIVER_TYPE,
|
||||
suppressOutput: true,
|
||||
showInstalled: true,
|
||||
},
|
||||
driverConfig,
|
||||
);
|
||||
if (!_.has(driverList, 'fake')) {
|
||||
await runExtensionCommand(
|
||||
{
|
||||
driverCommand: 'install',
|
||||
driver: FAKE_DRIVER_DIR,
|
||||
installType: INSTALL_TYPE_LOCAL,
|
||||
subcommand: DRIVER_TYPE,
|
||||
},
|
||||
driverConfig,
|
||||
);
|
||||
}
|
||||
|
||||
FakeDriver = await driverConfig.requireAsync('fake');
|
||||
FakeDriver = await initFakeDriver(appiumHome);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
@@ -536,6 +543,149 @@ describe('FakeDriver via HTTP', function () {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bidi protocol', function () {
|
||||
withServer();
|
||||
const capabilities = {...caps, webSocketUrl: true, 'appium:runClock': true};
|
||||
let driver;
|
||||
|
||||
beforeEach(async function () {
|
||||
driver = await wdio({...wdOpts, capabilities});
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
if (driver) {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
|
||||
it('should respond with bidi specific capability when a driver supports it', async function () {
|
||||
should.exist(driver.capabilities.webSocketUrl);
|
||||
});
|
||||
|
||||
it('should interpret the bidi protocol and let the driver handle it by command', async function () {
|
||||
should.not.exist(await driver.getUrl());
|
||||
|
||||
await driver.browsingContextNavigate({
|
||||
context: 'foo',
|
||||
url: 'https://appium.io',
|
||||
wait: 'complete',
|
||||
});
|
||||
await driver.getUrl().should.eventually.eql('https://appium.io');
|
||||
});
|
||||
|
||||
it('should be able to subscribe and unsubscribe to bidi events', async function () {
|
||||
let collectedEvents = [];
|
||||
// asyncrhonously start our listener
|
||||
driver.on('clock.currentTime', ({time}) => {
|
||||
collectedEvents.push(time);
|
||||
});
|
||||
|
||||
// wait for some time to be sure clock events have happened, and assert we don't receive
|
||||
// any yet
|
||||
await B.delay(750);
|
||||
collectedEvents.should.be.empty;
|
||||
|
||||
// now subscribe and wait and assert that some events have been collected
|
||||
await driver.sessionSubscribe({events: ['clock.currentTime']});
|
||||
await B.delay(750);
|
||||
collectedEvents.should.not.be.empty;
|
||||
|
||||
// finally unsubscribe and wait and assert that some events have been collected
|
||||
await driver.sessionUnsubscribe({events: ['clock.currentTime']});
|
||||
collectedEvents = [];
|
||||
await B.delay(750);
|
||||
collectedEvents.should.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bidi over SSL', function () {
|
||||
/**
|
||||
* @param {string} certPath
|
||||
* @param {string} keyPath
|
||||
*/
|
||||
async function generateCertificate(certPath, keyPath) {
|
||||
await exec('openssl', [
|
||||
'req',
|
||||
'-nodes',
|
||||
'-new',
|
||||
'-x509',
|
||||
'-keyout',
|
||||
keyPath,
|
||||
'-out',
|
||||
certPath,
|
||||
'-subj',
|
||||
'/C=US/ST=State/L=City/O=company/OU=Com/CN=www.testserver.local',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @type {AppiumServer} */
|
||||
let server;
|
||||
/** @type {string} */
|
||||
let appiumHome;
|
||||
/** @type {import('webdriverio').Browser} */
|
||||
let driver;
|
||||
// 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} */
|
||||
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};
|
||||
|
||||
before(async function () {
|
||||
try {
|
||||
await generateCertificate(certPath, keyPath);
|
||||
} catch (e) {
|
||||
if (process.env.CI) {
|
||||
throw e;
|
||||
}
|
||||
return this.skip();
|
||||
}
|
||||
sandbox = createSandbox();
|
||||
appiumHome = await tempDir.openDir();
|
||||
wdOpts.port = port = await getTestPort();
|
||||
testServerBaseUrl = `https://${TEST_HOST}:${port}`;
|
||||
FakeDriver = await initFakeDriver(appiumHome);
|
||||
server = await appiumServer({
|
||||
address: TEST_HOST,
|
||||
port,
|
||||
appiumHome,
|
||||
sslCertificatePath: certPath,
|
||||
sslKeyPath: keyPath,
|
||||
});
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(appiumHome);
|
||||
await server.close();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
driver = await wdio({...wdOpts, protocol: 'https', strictSSL: false, capabilities});
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
if (driver) {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
|
||||
it('should still run bidi over ssl', async function () {
|
||||
should.not.exist(await driver.getUrl());
|
||||
|
||||
await driver.browsingContextNavigate({
|
||||
context: 'foo',
|
||||
url: 'https://appium.io',
|
||||
wait: 'complete',
|
||||
});
|
||||
await driver.getUrl().should.eventually.eql('https://appium.io');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO this test only works if the log has not previously been initialized in the same process.
|
||||
|
||||
@@ -239,7 +239,7 @@ describe('ExtensionConfig', function () {
|
||||
await expect(
|
||||
config.getGenericConfigWarnings(extData, extData.pkgName)
|
||||
).to.eventually.eql([
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on older Appium v${extData.appiumVersion}. Please upgrade \`${extData.pkgName}\` to v${updateVersion} or newer.`,
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on Appium ${extData.appiumVersion}. Try to upgrade \`${extData.pkgName}\` to v${updateVersion} or newer.`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -254,7 +254,7 @@ describe('ExtensionConfig', function () {
|
||||
await expect(
|
||||
config.getGenericConfigWarnings(extData, extData.pkgName)
|
||||
).to.eventually.eql([
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on older Appium v${extData.appiumVersion}. Please ask the developer of \`${extData.pkgName}\` to update the peer dependency on Appium to v${APPIUM_VER}.`,
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on Appium ${extData.appiumVersion}. Please install a compatible version of the driver.`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import {DRIVER_TYPE, PLUGIN_TYPE} from '../../lib/constants';
|
||||
import {getParser} from '../../lib/cli/parser';
|
||||
import {INSTALL_TYPES} from '../../lib/extension/extension-config';
|
||||
@@ -349,8 +350,13 @@ describe('parser', function () {
|
||||
it('should not allow an empty driver argument list', function () {
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no driver scriptName', function () {
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'run', 'foo'])).should.throw();
|
||||
it('should allow no driver scriptName', function () {
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'run', 'foo']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('run');
|
||||
args.driver.should.eql('foo');
|
||||
_.isNull(args.scriptName).should.be.true;
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should take a driverName and scriptName to run', function () {
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'run', 'foo', 'bar']);
|
||||
@@ -367,8 +373,13 @@ describe('parser', function () {
|
||||
it('should not allow an empty plugin argument list', function () {
|
||||
(() => p.parseArgs([PLUGIN_TYPE, 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no plugin scriptName', function () {
|
||||
(() => p.parseArgs([PLUGIN_TYPE, 'run', 'foo'])).should.throw();
|
||||
it('should allow no plugin scriptName', function () {
|
||||
const args = p.parseArgs([PLUGIN_TYPE, 'run', 'foo']);
|
||||
args.subcommand.should.eql(PLUGIN_TYPE);
|
||||
args.pluginCommand.should.eql('run');
|
||||
args.plugin.should.eql('foo');
|
||||
_.isNull(args.scriptName).should.be.true;
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should take a pluginName and scriptName to run', function () {
|
||||
const args = p.parseArgs([PLUGIN_TYPE, 'run', 'foo', 'bar']);
|
||||
|
||||
@@ -19,7 +19,7 @@ export type CliCommand = CliCommandServer | CliExtensionCommand;
|
||||
* Possible subcommands of {@linkcode CliCommandDriver} or
|
||||
* {@linkcode CliCommandPlugin}.
|
||||
*/
|
||||
export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update';
|
||||
export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update'| 'doctor';
|
||||
|
||||
export interface CliExtensionSubcommandListArgs {
|
||||
showInstalled?: boolean;
|
||||
|
||||
@@ -3,6 +3,45 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.5.2](https://github.com/appium/appium/compare/@appium/base-driver@9.5.1...@appium/base-driver@9.5.2) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.5.1](https://github.com/appium/appium/compare/@appium/base-driver@9.5.0...@appium/base-driver@9.5.1) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **images-plugin:** update dependency lru-cache to v10 ([#19723](https://github.com/appium/appium/issues/19723)) ([202da83](https://github.com/appium/appium/commit/202da83449073ddb3da8fb2cfbcfce482a5ecd40))
|
||||
* **support:** update dependency axios to v1.6.4 ([332cc48](https://github.com/appium/appium/commit/332cc48a09b5532a8d51f85f3a24785e2c754e00))
|
||||
* **support:** update dependency axios to v1.6.5 ([#19616](https://github.com/appium/appium/issues/19616)) ([ac73522](https://github.com/appium/appium/commit/ac73522351b31bd6c11972c61daa8b6b8d18fb91))
|
||||
* **support:** update dependency axios to v1.6.6 ([6313704](https://github.com/appium/appium/commit/6313704ee5a8ee3aee726eb512ef259b6fa1041c))
|
||||
* **support:** update dependency axios to v1.6.7 ([795092a](https://github.com/appium/appium/commit/795092a97f6d7569cccc4b5c166f52fef821514b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [9.5.0](https://github.com/appium/appium/compare/@appium/base-driver@9.4.4...@appium/base-driver@9.5.0) (2024-01-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add webdriver bidi support ([2b21e66](https://github.com/appium/appium/commit/2b21e66891e8ab8c3929f04f32e94eb4efdba691))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** update dependency async-lock to v1.4.1 ([a304a1f](https://github.com/appium/appium/commit/a304a1f78e658f6f70cbe8e1efd6d06b81d8d34e))
|
||||
* **support:** update dependency axios to v1.6.3 ([441b284](https://github.com/appium/appium/commit/441b2848dae28472356f37fc5d51ac27af7bbe29))
|
||||
* **types:** update dependency type-fest to v4 ([#19592](https://github.com/appium/appium/issues/19592)) ([94b3580](https://github.com/appium/appium/commit/94b358022fdba3050ef94c1f881895f07e24fb75))
|
||||
|
||||
|
||||
|
||||
## [9.4.4](https://github.com/appium/appium/compare/@appium/base-driver@9.4.3...@appium/base-driver@9.4.4) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
appium-base-driver
|
||||
===================
|
||||
[](https://npmjs.org/package/@appium/base-driver)
|
||||
# @appium/base-driver
|
||||
|
||||
> Base class for creating other Appium drivers
|
||||
|
||||
[](https://npmjs.org/package/@appium/base-driver)
|
||||
[](https://npmjs.org/package/@appium/base-driver)
|
||||
[](https://david-dm.org/appium/@appium/base-driver)
|
||||
[](https://david-dm.org/appium/@appium/base-driver#info=devDependencies)
|
||||
|
||||
[](https://travis-ci.org/appium/@appium/base-driver)
|
||||
This is the parent class that all Appium drivers inherit from. This driver should not be installed
|
||||
directly as it does nothing on its own. Instead, you should extend this driver when creating your
|
||||
*own* Appium drivers. Check out the [Building Drivers](https://appium.io/docs/en/latest/developing/build-drivers/)
|
||||
documentation for more details.
|
||||
|
||||
This is the parent class that all [appium](appium.io) drivers inherit from, along with a collection of globally-used Appium driver utilities. Each utility is documented in its own README in the code:
|
||||
Each included utility is documented in its own README:
|
||||
|
||||
* [BaseDriver](lib/basedriver)
|
||||
* [The Appium Express Server](lib/express)
|
||||
* [The Mobile JSON Wire Protocol Encapsulation](lib/mjsonwp)
|
||||
* [The JSONWP Proxy Library](lib/jsonwp-proxy)
|
||||
* [The JSONWP Status Library](lib/jsonwp-status)
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {Constraints, Driver, IBidiCommands} from '@appium/types';
|
||||
import type {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 IBidiCommands {}
|
||||
}
|
||||
|
||||
const BidiCommands: IBidiCommands = {
|
||||
async bidiSubscribe<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
events: string[],
|
||||
contexts: string[] = [''],
|
||||
) {
|
||||
for (const event of events) {
|
||||
this.bidiEventSubs[event] = contexts;
|
||||
}
|
||||
},
|
||||
|
||||
async bidiUnsubscribe<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
events: string[],
|
||||
contexts: string[] = [''],
|
||||
) {
|
||||
for (const event of events) {
|
||||
if (this.bidiEventSubs[event]) {
|
||||
this.bidiEventSubs[event] = this.bidiEventSubs[event].filter((c) => !contexts.includes(c));
|
||||
}
|
||||
if (this.bidiEventSubs[event].length === 0) {
|
||||
delete this.bidiEventSubs[event];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
mixin(BidiCommands);
|
||||
@@ -3,3 +3,4 @@ import './find';
|
||||
import './log';
|
||||
import './timeout';
|
||||
import './execute';
|
||||
import './bidi';
|
||||
|
||||
@@ -14,15 +14,17 @@ import type {
|
||||
Protocol,
|
||||
RouteMatcher,
|
||||
StringRecord,
|
||||
BidiMethodDef,
|
||||
} from '@appium/types';
|
||||
import AsyncLock from 'async-lock';
|
||||
import _ from 'lodash';
|
||||
import {EventEmitter} from 'node:events';
|
||||
import os from 'node:os';
|
||||
import {DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
||||
import {DEFAULT_BASE_PATH, PROTOCOLS, MAX_LOG_BODY_LENGTH} from '../constants';
|
||||
import {errors} from '../protocol';
|
||||
import DeviceSettings from './device-settings';
|
||||
import helpers, {BASEDRIVER_VER} from './helpers';
|
||||
import {BIDI_COMMANDS} from '../protocol/bidi-commands';
|
||||
|
||||
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
@@ -99,6 +101,10 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
||||
|
||||
protocol?: Protocol;
|
||||
|
||||
bidiEventSubs: Record<string, string[]>;
|
||||
|
||||
doesSupportBidi: boolean;
|
||||
|
||||
constructor(opts: InitialOpts = <InitialOpts>{}, shouldValidateCaps = true) {
|
||||
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this as Core<C>));
|
||||
|
||||
@@ -132,6 +138,8 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
||||
this.shutdownUnexpectedly = false;
|
||||
this.commandsQueueGuard = new AsyncLock();
|
||||
this.settings = new DeviceSettings();
|
||||
this.bidiEventSubs = {};
|
||||
this.doesSupportBidi = false;
|
||||
}
|
||||
|
||||
get log() {
|
||||
@@ -293,7 +301,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
||||
`Potentially insecure feature '${name}' has not been ` +
|
||||
`enabled. If you want to enable this feature and accept ` +
|
||||
`the security ramifications, please do so by following ` +
|
||||
`the documented instructions at http://appium.io/docs/en/2.0/guides/security/`
|
||||
`the documented instructions at http://appium.io/docs/en/2.0/guides/security/`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -308,11 +316,23 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
||||
|
||||
if (!_.includes(validStrategies, strategy)) {
|
||||
throw new errors.InvalidSelectorError(
|
||||
`Locator Strategy '${strategy}' is not supported for this session`
|
||||
`Locator Strategy '${strategy}' is not supported for this session`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this driver has requested proxying of bidi connections to an upstream bidi endpoint, this
|
||||
* method should be overridden to return the URL of that websocket, to indicate that bidi
|
||||
* proxying is enabled. Otherwise, a null return will indicate that bidi proxying should not be
|
||||
* active and bidi commands will be handled by this driver.
|
||||
*
|
||||
* @returns {string | null}
|
||||
*/
|
||||
get bidiProxyUrl(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
proxyActive(sessionId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
@@ -376,4 +396,60 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
|
||||
this.noCommandTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord): Promise<any> {
|
||||
const [moduleName, methodName] = bidiCmd.split('.');
|
||||
|
||||
// if we don't get a valid format for bidi command name, reject
|
||||
if (!moduleName || !methodName) {
|
||||
throw new errors.UnknownCommandError(
|
||||
`Did not receive a valid BiDi module and method name ` +
|
||||
`of the form moduleName.methodName. Instead received ` +
|
||||
`'${moduleName}.${methodName}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// if the command module isn't part of our spec, reject
|
||||
if (!BIDI_COMMANDS[moduleName]) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
|
||||
const {command, params} = BIDI_COMMANDS[moduleName][methodName] as BidiMethodDef;
|
||||
// if the command method isn't part of our spec, also reject
|
||||
if (!command) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
|
||||
// If the driver doesn't have this command, it must not be implemented
|
||||
if (!this[command]) {
|
||||
throw new errors.NotYetImplementedError();
|
||||
}
|
||||
|
||||
// TODO improve param parsing and error messages along the lines of what we have in the http
|
||||
// handlers
|
||||
const args: any[] = [];
|
||||
if (params?.required?.length) {
|
||||
for (const requiredParam of params.required) {
|
||||
if (_.isUndefined(bidiParams[requiredParam])) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`The ${requiredParam} parameter was required but you omitted it`,
|
||||
);
|
||||
}
|
||||
args.push(bidiParams[requiredParam]);
|
||||
}
|
||||
}
|
||||
if (params?.optional?.length) {
|
||||
for (const optionalParam of params.optional) {
|
||||
args.push(bidiParams[optionalParam]);
|
||||
}
|
||||
}
|
||||
const logParams = _.truncate(JSON.stringify(bidiParams), {length: MAX_LOG_BODY_LENGTH});
|
||||
this.log.debug(
|
||||
`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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export class BaseDriver<
|
||||
Settings extends StringRecord = StringRecord,
|
||||
CreateResult = DefaultCreateSessionResult<C>,
|
||||
DeleteResult = DefaultDeleteSessionResult,
|
||||
SessionData extends StringRecord = StringRecord
|
||||
SessionData extends StringRecord = StringRecord,
|
||||
>
|
||||
extends DriverCore<C, Settings>
|
||||
implements Driver<C, CArgs, Settings, CreateResult, DeleteResult, SessionData>
|
||||
@@ -55,7 +55,6 @@ export class BaseDriver<
|
||||
super(opts, shouldValidateCaps);
|
||||
|
||||
this.caps = {} as DriverCaps<C>;
|
||||
|
||||
this.cliArgs = {} as CArgs & ServerArgs;
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ export class BaseDriver<
|
||||
// This is needed to prevent memory leaks
|
||||
this.eventEmitter.removeListener(
|
||||
ON_UNEXPECTED_SHUTDOWN_EVENT,
|
||||
unexpectedShutdownListener
|
||||
unexpectedShutdownListener,
|
||||
);
|
||||
unexpectedShutdownListener = null;
|
||||
}
|
||||
@@ -147,7 +146,7 @@ export class BaseDriver<
|
||||
}
|
||||
|
||||
async startUnexpectedShutdown(
|
||||
err: Error = new errors.NoSuchDriverError('The driver was unexpectedly shut down!')
|
||||
err: Error = new errors.NoSuchDriverError('The driver was unexpectedly shut down!'),
|
||||
) {
|
||||
this.eventEmitter.emit(ON_UNEXPECTED_SHUTDOWN_EVENT, err); // allow others to listen for this
|
||||
this.shutdownUnexpectedly = true;
|
||||
@@ -170,7 +169,7 @@ export class BaseDriver<
|
||||
this.noCommandTimer = setTimeout(async () => {
|
||||
this.log.warn(
|
||||
`Shutting down because we waited ` +
|
||||
`${this.newCommandTimeoutMs / 1000.0} seconds for a command`
|
||||
`${this.newCommandTimeoutMs / 1000.0} seconds for a command`,
|
||||
);
|
||||
const errorMessage =
|
||||
`New Command Timeout of ` +
|
||||
@@ -234,23 +233,23 @@ export class BaseDriver<
|
||||
w3cCapabilities2?: W3CDriverCaps<C>,
|
||||
w3cCapabilities?: W3CDriverCaps<C>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
driverData?: DriverData[]
|
||||
driverData?: DriverData[],
|
||||
): Promise<CreateResult> {
|
||||
if (this.sessionId !== null) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
'Cannot create a new session while one is in progress'
|
||||
'Cannot create a new session while one is in progress',
|
||||
);
|
||||
}
|
||||
|
||||
this.log.debug();
|
||||
|
||||
const originalCaps = _.cloneDeep(
|
||||
[w3cCapabilities, w3cCapabilities1, w3cCapabilities2].find(isW3cCaps)
|
||||
[w3cCapabilities, w3cCapabilities1, w3cCapabilities2].find(isW3cCaps),
|
||||
);
|
||||
if (!originalCaps) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
'Appium only supports W3C-style capability objects. ' +
|
||||
'Your client is sending an older capabilities format. Please update your client library.'
|
||||
'Your client is sending an older capabilities format. Please update your client library.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,7 +257,7 @@ export class BaseDriver<
|
||||
|
||||
this.originalCaps = originalCaps;
|
||||
this.log.debug(
|
||||
`Creating session with W3C capabilities: ${JSON.stringify(originalCaps, null, 2)}`
|
||||
`Creating session with W3C capabilities: ${JSON.stringify(originalCaps, null, 2)}`,
|
||||
);
|
||||
|
||||
let caps: DriverCaps<C>;
|
||||
@@ -266,7 +265,7 @@ export class BaseDriver<
|
||||
caps = processCapabilities(
|
||||
originalCaps,
|
||||
this._desiredCapConstraints,
|
||||
this.shouldValidateCaps
|
||||
this.shouldValidateCaps,
|
||||
) as DriverCaps<C>;
|
||||
caps = fixCaps(caps, this._desiredCapConstraints, this.log) as DriverCaps<C>;
|
||||
} catch (e) {
|
||||
@@ -287,7 +286,7 @@ export class BaseDriver<
|
||||
throw new Error(
|
||||
"The 'noReset' and 'fullReset' capabilities are mutually " +
|
||||
'exclusive and should not both be set to true. You ' +
|
||||
"probably meant to just use 'fullReset' on its own"
|
||||
"probably meant to just use 'fullReset' on its own",
|
||||
);
|
||||
}
|
||||
if (this.opts.noReset === true) {
|
||||
@@ -354,9 +353,7 @@ export class BaseDriver<
|
||||
logExtraCaps(caps: Capabilities<C>) {
|
||||
const extraCaps = _.difference(_.keys(caps), _.keys(this._desiredCapConstraints));
|
||||
if (extraCaps.length) {
|
||||
this.log.warn(
|
||||
`The following provided capabilities were not recognized by this driver:`
|
||||
);
|
||||
this.log.warn(`The following provided capabilities were not recognized by this driver:`);
|
||||
for (const cap of extraCaps) {
|
||||
this.log.warn(` ${cap}`);
|
||||
}
|
||||
@@ -371,11 +368,11 @@ export class BaseDriver<
|
||||
try {
|
||||
validateCaps(caps, this._desiredCapConstraints);
|
||||
} catch (e) {
|
||||
this.log.errorAndThrow(
|
||||
throw this.log.errorWithException(
|
||||
new errors.SessionNotCreatedError(
|
||||
`The desiredCapabilities object was not valid for the ` +
|
||||
`following reason(s): ${e.message}`
|
||||
)
|
||||
`following reason(s): ${e.message}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -386,14 +383,14 @@ export class BaseDriver<
|
||||
|
||||
async updateSettings(newSettings: Settings) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
||||
throw this.log.errorWithException('Cannot update settings; settings object not found');
|
||||
}
|
||||
return await this.settings.update(newSettings);
|
||||
}
|
||||
|
||||
async getSettings() {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
||||
throw this.log.errorWithException('Cannot get settings; settings object not found');
|
||||
}
|
||||
return this.settings.getSettings();
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ async function unzipApp(zipPath, dstRoot, supportedAppExtensions) {
|
||||
})
|
||||
).sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
|
||||
if (_.isEmpty(sortedBundleItems)) {
|
||||
logger.errorAndThrow(
|
||||
throw logger.errorWithException(
|
||||
`App unzipped OK, but we could not find any '${supportedAppExtensions}' ` +
|
||||
util.pluralize('bundle', supportedAppExtensions.length, false) +
|
||||
` in it. Make sure your archive contains at least one package having ` +
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import B from 'bluebird';
|
||||
|
||||
B.config({
|
||||
cancellation: true,
|
||||
});
|
||||
try {
|
||||
B.config({
|
||||
cancellation: true,
|
||||
});
|
||||
} catch (ign) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// BaseDriver exports
|
||||
import {BaseDriver} from './basedriver/driver';
|
||||
@@ -11,11 +16,11 @@ export {DeviceSettings} from './basedriver/device-settings';
|
||||
|
||||
export {BaseDriver};
|
||||
export default BaseDriver;
|
||||
export {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS, W3C_ELEMENT_KEY} from './constants';
|
||||
|
||||
// MJSONWP exports
|
||||
export * from './protocol';
|
||||
export {errorFromMJSONWPStatusCode as errorFromCode} from './protocol';
|
||||
export {DEFAULT_BASE_PATH, PROTOCOLS, W3C_ELEMENT_KEY} from './constants';
|
||||
|
||||
// Express exports
|
||||
export {STATIC_DIR} from './express/static';
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
const SUBSCRIPTION_REQUEST_PARAMS = /** @type {const} */ ({
|
||||
required: ['events'],
|
||||
optional: ['contexts'],
|
||||
});
|
||||
|
||||
const BIDI_COMMANDS = /** @type {const} */ ({
|
||||
session: {
|
||||
subscribe: {
|
||||
command: 'bidiSubscribe',
|
||||
params: SUBSCRIPTION_REQUEST_PARAMS,
|
||||
},
|
||||
unsubscribe: {
|
||||
command: 'bidiUnsubscribe',
|
||||
params: SUBSCRIPTION_REQUEST_PARAMS,
|
||||
},
|
||||
},
|
||||
browsingContext: {
|
||||
navigate: {
|
||||
command: 'bidiNavigate',
|
||||
params: {
|
||||
required: ['context', 'url'],
|
||||
optional: ['wait'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// TODO add definitions for all bidi commands.
|
||||
// spec link: https://w3c.github.io/webdriver-bidi/
|
||||
|
||||
export {BIDI_COMMANDS};
|
||||
@@ -28,6 +28,27 @@ export class ProtocolError extends ES6Error {
|
||||
set stacktrace(value) {
|
||||
this._stacktrace = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 = '';
|
||||
}
|
||||
return {
|
||||
id,
|
||||
type: 'error',
|
||||
error: this.error,
|
||||
stacktrace: this.stacktrace,
|
||||
message: this.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/SeleniumHQ/selenium/blob/176b4a9e3082ac1926f2a436eb346760c37a5998/java/client/src/org/openqa/selenium/remote/ErrorCodes.java#L215
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.4.4",
|
||||
"version": "9.5.2",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,8 +44,8 @@
|
||||
"test:types": "tsd"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.1.11",
|
||||
"@appium/types": "^0.15.0",
|
||||
"@appium/support": "^4.2.2",
|
||||
"@appium/types": "^0.16.1",
|
||||
"@colors/colors": "1.6.0",
|
||||
"@types/async-lock": "1.4.2",
|
||||
"@types/bluebird": "3.5.42",
|
||||
@@ -53,22 +53,22 @@
|
||||
"@types/lodash": "4.14.202",
|
||||
"@types/method-override": "0.0.35",
|
||||
"@types/serve-favicon": "2.5.7",
|
||||
"async-lock": "1.4.0",
|
||||
"async-lock": "1.4.1",
|
||||
"asyncbox": "3.0.0",
|
||||
"axios": "1.6.2",
|
||||
"axios": "1.6.7",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.2",
|
||||
"es6-error": "4.1.1",
|
||||
"express": "4.18.2",
|
||||
"http-status-codes": "2.3.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "10.1.0",
|
||||
"lru-cache": "10.2.0",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "4.8.3",
|
||||
"type-fest": "4.10.1",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.28](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.27...@appium/base-plugin@2.2.28) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.27](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.26...@appium/base-plugin@2.2.27) (2024-02-06)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.26](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.25...@appium/base-plugin@2.2.26) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.25](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.24...@appium/base-plugin@2.2.25) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
# Appium Base Plugin
|
||||
# @appium/base-plugin
|
||||
|
||||
The base class used to create Appium plugins. This plugin should not be installed directly as it does nothing. Instead, you extend this plugin when creating your *own* Appium plugins. Check out the [Building Plugins](https://appium.io/docs/en/latest/ecosystem/build-plugins/) to learn how to make use of this base class to develop Appium plugins.
|
||||
> Base class for creating other Appium plugins
|
||||
|
||||
[](https://npmjs.org/package/@appium/base-plugin)
|
||||
[](https://npmjs.org/package/@appium/base-plugin)
|
||||
|
||||
This is the parent class that all Appium plugins inherit from. This plugin should not be installed
|
||||
directly as it does nothing on its own. Instead, you should extend this plugin when creating your
|
||||
*own* Appium plugins. Check out the [Building Plugins](https://appium.io/docs/en/latest/developing/build-plugins/)
|
||||
documentation for more details.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.25",
|
||||
"version": "2.2.28",
|
||||
"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.4.4",
|
||||
"@appium/support": "^4.1.11"
|
||||
"@appium/base-driver": "^9.5.2",
|
||||
"@appium/support": "^4.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
+13
-23
@@ -1,6 +1,14 @@
|
||||
appium-doctor
|
||||
===================
|
||||
Attempts to diagnose and fix common Node, iOS and Android configuration issues before starting Appium.
|
||||
# @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
|
||||
|
||||
@@ -26,24 +34,6 @@ Options:
|
||||
-h, --help Show help [boolean]
|
||||
```
|
||||
|
||||
## Dev
|
||||
## License
|
||||
|
||||
## Watch
|
||||
|
||||
```
|
||||
npm run watch
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Hack Cli
|
||||
|
||||
Use the `--demo` option to simulate the fix process.
|
||||
|
||||
```
|
||||
appium-doctor --demo
|
||||
```
|
||||
Apache-2.0
|
||||
|
||||
@@ -198,6 +198,9 @@ class Doctor {
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "2.0.30",
|
||||
"version": "2.0.33",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,11 +44,11 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.1.11",
|
||||
"@appium/support": "^4.2.2",
|
||||
"@colors/colors": "1.6.0",
|
||||
"@types/bluebird": "3.5.42",
|
||||
"@types/teen_process": "2.0.4",
|
||||
"appium-adb": "11.0.8",
|
||||
"appium-adb": "12.0.3",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "9.2.12",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -3,13 +3,57 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.1](https://github.com/appium/appium/compare/@appium/docutils@0.4.13...@appium/docutils@1.0.1) (2023-12-18)
|
||||
## [1.0.4](https://github.com/appium/appium/compare/@appium/docutils@1.0.3...@appium/docutils@1.0.4) (2024-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency diff to v5.2.0 ([d83d436](https://github.com/appium/appium/commit/d83d436d01232463a48902964e5ef97334d047f9))
|
||||
* **support:** update dependency semver to v7.6.0 ([7b40a32](https://github.com/appium/appium/commit/7b40a3225ebe0f5976a26d05db2f22ba7b13a130))
|
||||
|
||||
|
||||
|
||||
## [1.0.3](https://github.com/appium/appium/compare/@appium/docutils@1.0.2...@appium/docutils@1.0.3) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** change default alias type to `symlink` ([#19681](https://github.com/appium/appium/issues/19681)) ([d9711b4](https://github.com/appium/appium/commit/d9711b4191d19ed0b89944b2732a1a4f4c4d9c0b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [1.0.2](https://github.com/appium/appium/compare/@appium/docutils@1.0.1...@appium/docutils@1.0.2) (2024-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency lilconfig to v3 ([#19489](https://github.com/appium/appium/issues/19489)) ([ea8394a](https://github.com/appium/appium/commit/ea8394a888c835bc54f7f68174a4b6cae85339ea))
|
||||
* **types:** update dependency type-fest to v4 ([#19592](https://github.com/appium/appium/issues/19592)) ([94b3580](https://github.com/appium/appium/commit/94b358022fdba3050ef94c1f881895f07e24fb75))
|
||||
|
||||
|
||||
|
||||
## [1.0.1](https://github.com/appium/appium/compare/@appium/docutils@0.4.13...@appium/docutils@1.0.1) (2023-12-18)
|
||||
|
||||
This includes all changes from 1.0.0.
|
||||
|
||||
|
||||
|
||||
## 1.0.0 [Unreleased]
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* `@appium/typedoc-plugin-appium` integration and all other uses of `typedoc` have been removed
|
||||
* `--prefix` option has been renamed to `--deploy-prefix`
|
||||
* `--rebase` flag has been removed
|
||||
|
||||
### Features
|
||||
* **docutils:** enhance base mkdocs configuration ([#19493](https://github.com/appium/appium/issues/19493)) ([9de6f40](https://github.com/appium/appium/commit/9de6f409ec506017d6aa865cfafbd3ad5bc8d423))
|
||||
* **docutils:** further adjust base mkdocs configuration ([#19527](https://github.com/appium/appium/issues/19527)) ([3dff112](https://github.com/appium/appium/commit/3dff11253db9a4681b39794b60f44dce01a7ddd3))
|
||||
* **docutils:** add `--alias-type` option ([#19485](https://github.com/appium/appium/issues/19485)) ([0003304](https://github.com/appium/appium/commit/00033044e08f932b5daf1234cfb381c6f46c8bb8))
|
||||
|
||||
### Bug Fixes
|
||||
* **docutils:** remove `@appium/typedoc-plugin-appium` and all other uses of `typedoc` ([#19465](https://github.com/appium/appium/issues/19465)) ([7528fcf](https://github.com/appium/appium/commit/7528fcf890f79f4017f5e718bb1952bf907ee479))
|
||||
* **docutils:** update dependency mike to v2.0.0 ([#19485](https://github.com/appium/appium/issues/19485)) ([0003304](https://github.com/appium/appium/commit/00033044e08f932b5daf1234cfb381c6f46c8bb8))
|
||||
* **docutils:** update dependency typescript to v5.2.2 ([#19472](https://github.com/appium/appium/issues/19472)) ([cb950df](https://github.com/appium/appium/commit/cb950dffb82e114341d2e0e0faa5ce6d551ec267))
|
||||
|
||||
+15
-10
@@ -2,21 +2,26 @@
|
||||
|
||||
> Documentation-building utilities for Appium
|
||||
|
||||
## Prerequisites
|
||||
[](https://npmjs.org/package/@appium/docutils)
|
||||
[](https://npmjs.org/package/@appium/docutils)
|
||||
|
||||
- Node.js in the [semantic version range](https://semver.org) `^14.17.0 || ^16.13.0 || >=18.0.0`
|
||||
- Python v3+ if building HTML documentation
|
||||
## Setup for Main Appium Docs
|
||||
|
||||
## Running for Main Appium Docs
|
||||
|
||||
This package does not need to be run directly. Instead, execute this command in the `packages/appium` folder of [this monorepo](https://github.com/appium/appium):
|
||||
This package does not need to be run directly. Instead, execute this command in the
|
||||
`packages/appium` directory:
|
||||
|
||||
```bash
|
||||
npm run dev:docs:en
|
||||
````
|
||||
```
|
||||
|
||||
See the `scripts` field of the `package.json` for other languages.
|
||||
> [!NOTE]
|
||||
> This will only build the English docs. See the `scripts` field of the `package.json` for
|
||||
other languages.
|
||||
|
||||
## Using in Appium Extensions
|
||||
## Setup for Appium Drivers and Plugins
|
||||
|
||||
TODO: give an example of how this package is used in an extension outside the appium monorepo, for example when it's made a part of the XCUITest repo. Or link to the part of the Appium docs where this info will live.
|
||||
See the Appium docs on [Building Documentation](http://appium.io/docs/en/latest/developing/build-docs/).
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -139,12 +139,6 @@ export async function deploy({
|
||||
const mikeArgs = [
|
||||
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value))),
|
||||
];
|
||||
if (alias) {
|
||||
mikeArgs.push('--update-aliases', version, alias);
|
||||
mikeArgs.push('--alias-type', aliasType);
|
||||
} else {
|
||||
mikeArgs.push(version);
|
||||
}
|
||||
stop(); // discard
|
||||
// unsure about how SIGHUP is handled here
|
||||
await doServe(mikePath, mikeArgs, serveOpts);
|
||||
@@ -159,8 +153,8 @@ export async function deploy({
|
||||
),
|
||||
];
|
||||
if (alias) {
|
||||
mikeArgs.push('--update-aliases', version, alias);
|
||||
mikeArgs.push('--alias-type', aliasType);
|
||||
mikeArgs.push('--update-aliases', '--alias-type', aliasType);
|
||||
mikeArgs.push(version, alias);
|
||||
} else {
|
||||
mikeArgs.push(version);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import loadTs from '@sliphua/lilconfig-ts-loader';
|
||||
import {LogLevel} from 'consola';
|
||||
import {lilconfig, Loader} from 'lilconfig';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
@@ -46,9 +45,9 @@ export async function findConfig(argv: string[] = hideBin(process.argv)) {
|
||||
|
||||
// if --verbose is used, set the log level to debug.
|
||||
// otherwise use --log-level or the default.
|
||||
let logLevel: LogLevel | keyof typeof LogLevelMap;
|
||||
let logLevel: keyof typeof LogLevelMap;
|
||||
if (preArgs.verbose) {
|
||||
logLevel = LogLevel.Debug;
|
||||
logLevel = 'debug';
|
||||
} else {
|
||||
// if the loglevel is valid, use it, otherwise use the default
|
||||
logLevel = isLogLevelString(preArgs.logLevel) ? preArgs.logLevel : DEFAULT_LOG_LEVEL;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* Constants used across various modules in this package
|
||||
* @module
|
||||
*/
|
||||
import {LogLevel} from 'consola';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Consola 3 import call is ESM
|
||||
const {LogLevels} = require('consola');
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {fs} from '@appium/support';
|
||||
import path from 'node:path';
|
||||
@@ -108,7 +109,7 @@ export const REQUIREMENTS_TXT_PATH = path.join(PKG_ROOT_DIR, NAME_REQUIREMENTS_T
|
||||
* The default alias creation strategy to pass to `mike` when deploying
|
||||
* (`symlink`, `redirect` or `copy`)
|
||||
*/
|
||||
export const DEFAULT_DEPLOY_ALIAS_TYPE = 'redirect';
|
||||
export const DEFAULT_DEPLOY_ALIAS_TYPE = 'symlink';
|
||||
|
||||
/**
|
||||
* The default branch to deploy to
|
||||
@@ -134,11 +135,11 @@ export const DEFAULT_SERVE_HOST = 'localhost';
|
||||
* Mapping of `@appium/docutils`' log levels to `consola` log levels
|
||||
*/
|
||||
export const LogLevelMap = {
|
||||
silent: LogLevel.Silent,
|
||||
error: LogLevel.Error,
|
||||
warn: LogLevel.Warn,
|
||||
info: LogLevel.Info,
|
||||
debug: LogLevel.Debug,
|
||||
silent: LogLevels.silent,
|
||||
error: LogLevels.error,
|
||||
warn: LogLevels.warn,
|
||||
info: LogLevels.info,
|
||||
debug: LogLevels.debug,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
+15
-128
@@ -6,134 +6,17 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import chalk, {type BackgroundColor, type ForegroundColor} from 'chalk';
|
||||
import consola, {
|
||||
type Consola,
|
||||
type ConsolaReporterLogObject,
|
||||
FancyReporter,
|
||||
type FancyReporterOptions,
|
||||
type LogLevel,
|
||||
type logType as LogType,
|
||||
} from 'consola';
|
||||
import figures from 'figures';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Consola 3 import call is ESM
|
||||
const {ConsolaInstance, createConsola, LogLevel} = require('consola');
|
||||
import _ from 'lodash';
|
||||
import logSymbols from 'log-symbols';
|
||||
import {DEFAULT_LOG_LEVEL, LogLevelMap} from './constants';
|
||||
|
||||
/**
|
||||
* This is a reporter for `consola` which uses some extra/custom icons and colors.
|
||||
*
|
||||
* @privateRemarks
|
||||
* I did not like that the default `FancyReport` logs errors in _green_ without any sort of icon.
|
||||
* Both `log-symbols` and `consola` consume `chalk`, so we do too. `consola` also depends on `figures`.
|
||||
*/
|
||||
class DocutilsReporter extends FancyReporter {
|
||||
/**
|
||||
* Mapping of log types (the name of the logging method called) to chalk fg colors
|
||||
*/
|
||||
static readonly TYPE_COLOR_MAP = {
|
||||
info: 'cyan',
|
||||
success: 'green',
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
} satisfies {[k in LogType]?: typeof ForegroundColor};
|
||||
|
||||
/**
|
||||
* Mapping of log levels to chalk fg colors
|
||||
*/
|
||||
static readonly LEVEL_COLORS = {
|
||||
0: 'red',
|
||||
1: 'yellow',
|
||||
2: 'white',
|
||||
3: 'green',
|
||||
} satisfies {[k in LogLevel]?: typeof ForegroundColor};
|
||||
|
||||
/**
|
||||
* Mapping of log types to icons/symbols
|
||||
*/
|
||||
static readonly TYPE_ICONS = {
|
||||
info: logSymbols.info,
|
||||
success: logSymbols.success,
|
||||
error: logSymbols.error,
|
||||
warn: logSymbols.warning,
|
||||
debug: figures('›'),
|
||||
trace: figures('›'),
|
||||
} satisfies {[k in LogType]?: string};
|
||||
|
||||
/**
|
||||
* Default color to use if we can't find a color for the log type or level
|
||||
*/
|
||||
static readonly DEFAULT_COLOR = 'grey';
|
||||
|
||||
/**
|
||||
* Type guard to check if a log type has a color
|
||||
* @param type A log type
|
||||
*/
|
||||
static hasTypeColor(type: LogType): type is keyof typeof DocutilsReporter.TYPE_COLOR_MAP {
|
||||
return type in DocutilsReporter.TYPE_COLOR_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a log level has a color
|
||||
* @param level A log level
|
||||
*/
|
||||
static hasLevelColor(level: LogLevel): level is keyof typeof DocutilsReporter.LEVEL_COLORS {
|
||||
return level in DocutilsReporter.LEVEL_COLORS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a log type has an icon
|
||||
* @param type A log type
|
||||
*/
|
||||
static hasTypeIcon(type: LogType): type is keyof typeof DocutilsReporter.TYPE_ICONS {
|
||||
return type in DocutilsReporter.TYPE_ICONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes the logging output with colors and symbols, depending on contents of `logObj`.
|
||||
* @param logObj Consola's log object
|
||||
* @param isBadge {@linkcode FancyReporter} uses this; I think it depends on the terminal width
|
||||
* @returns
|
||||
*/
|
||||
protected override formatType(logObj: ConsolaReporterLogObject, isBadge?: boolean): string {
|
||||
const {
|
||||
TYPE_COLOR_MAP,
|
||||
LEVEL_COLORS,
|
||||
TYPE_ICONS,
|
||||
hasTypeColor,
|
||||
hasLevelColor,
|
||||
hasTypeIcon,
|
||||
DEFAULT_COLOR,
|
||||
} = DocutilsReporter;
|
||||
|
||||
let typeColor: typeof ForegroundColor;
|
||||
if (hasTypeColor(logObj.type)) {
|
||||
typeColor = TYPE_COLOR_MAP[logObj.type];
|
||||
} else if (hasLevelColor(logObj.level)) {
|
||||
typeColor = LEVEL_COLORS[logObj.level];
|
||||
} else {
|
||||
typeColor = <typeof ForegroundColor>(
|
||||
((this.options as FancyReporterOptions).secondaryColor ?? DEFAULT_COLOR)
|
||||
);
|
||||
}
|
||||
|
||||
if (isBadge) {
|
||||
return chalk[('bg' + _.capitalize(typeColor)) as typeof BackgroundColor].black(
|
||||
` ${_.toUpper(logObj.type)}`
|
||||
);
|
||||
}
|
||||
|
||||
const type = hasTypeIcon(logObj.type) ? TYPE_ICONS[logObj.type] : logObj.type;
|
||||
return type ? chalk[typeColor](type) : '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The global log level
|
||||
*
|
||||
* "Global" inasmuch as any logger created from the root logger will use this level.
|
||||
*/
|
||||
let globalLevel: LogLevel = LogLevelMap[DEFAULT_LOG_LEVEL];
|
||||
let globalLevel: typeof LogLevel = LogLevelMap[DEFAULT_LOG_LEVEL];
|
||||
|
||||
/**
|
||||
* Type guard to see if a string is a recognized log level
|
||||
@@ -144,20 +27,24 @@ export function isLogLevelString(level: any): level is keyof typeof LogLevelMap
|
||||
}
|
||||
|
||||
/**
|
||||
* The logger from which all loggers are created. This one uses a unique tag and our custom reporter.
|
||||
* The logger from which all loggers are created. This one uses a unique tag.
|
||||
*/
|
||||
const rootLogger = consola.create({
|
||||
const rootLogger = createConsola({
|
||||
defaults: {tag: 'docutils'},
|
||||
reporters: [new DocutilsReporter()],
|
||||
fancy: true,
|
||||
level: globalLevel,
|
||||
formatOptions: {
|
||||
colors: true,
|
||||
date: false,
|
||||
},
|
||||
});
|
||||
// this prevents logging before `initLogger` is called
|
||||
rootLogger.pause();
|
||||
rootLogger.pauseLogs();
|
||||
|
||||
/**
|
||||
* A map of tags to loggers
|
||||
*/
|
||||
const loggers: Map<string, WeakRef<Consola>> = new Map();
|
||||
const loggers: Map<string, WeakRef<typeof ConsolaInstance>> = new Map();
|
||||
|
||||
export function getLogger(tag: string, parent = rootLogger) {
|
||||
if (loggers.has(tag)) {
|
||||
@@ -179,15 +66,15 @@ export function getLogger(tag: string, parent = rootLogger) {
|
||||
*
|
||||
* @remarks Child loggers seem to inherit the "paused" state of the parent, so when this is called, we must resume all of them.
|
||||
*/
|
||||
export const initLogger = _.once((level: keyof typeof LogLevelMap | LogLevel) => {
|
||||
export const initLogger = _.once((level: keyof typeof LogLevelMap | typeof LogLevel) => {
|
||||
globalLevel = isLogLevelString(level) ? LogLevelMap[level] : level;
|
||||
rootLogger.level = globalLevel;
|
||||
rootLogger.resume();
|
||||
rootLogger.resumeLogs();
|
||||
for (const ref of loggers.values()) {
|
||||
const logger = ref.deref();
|
||||
if (logger) {
|
||||
logger.level = globalLevel;
|
||||
logger.resume();
|
||||
logger.resumeLogs();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -48,24 +48,22 @@
|
||||
"start": "node ./build/lib/cli/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.1.11",
|
||||
"@appium/support": "^4.2.2",
|
||||
"@appium/tsconfig": "^0.x",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"@types/which": "3.0.3",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
"figures": "3.2.0",
|
||||
"consola": "3.2.3",
|
||||
"diff": "5.2.0",
|
||||
"json5": "2.2.3",
|
||||
"lilconfig": "2.1.0",
|
||||
"lilconfig": "3.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"log-symbols": "4.1.0",
|
||||
"pkg-dir": "5.0.0",
|
||||
"read-pkg": "5.2.0",
|
||||
"semver": "7.5.4",
|
||||
"semver": "7.6.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.1.1",
|
||||
"type-fest": "4.8.3",
|
||||
"type-fest": "4.10.1",
|
||||
"typescript": "5.2.2",
|
||||
"yaml": "2.3.4",
|
||||
"yargs": "17.7.2",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.5.3
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.1
|
||||
mkdocs-material==9.5.2
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.4
|
||||
mkdocs-material==9.5.9
|
||||
mkdocs-redirects==1.2.1
|
||||
mike==2.0.0
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.21](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.20...@appium/driver-test-support@0.4.21) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **driver-test-support:** update dependency chai to v4.4.0 ([f7e4731](https://github.com/appium/appium/commit/f7e47316c0133a5bc6b658cfb97c1caa5d4272f7))
|
||||
* **driver-test-support:** update dependency chai to v4.4.1 ([ca54b4f](https://github.com/appium/appium/commit/ca54b4f79ac27f4c6bceaae5d97175afbdbea460))
|
||||
* **support:** update dependency axios to v1.6.4 ([332cc48](https://github.com/appium/appium/commit/332cc48a09b5532a8d51f85f3a24785e2c754e00))
|
||||
* **support:** update dependency axios to v1.6.5 ([#19616](https://github.com/appium/appium/issues/19616)) ([ac73522](https://github.com/appium/appium/commit/ac73522351b31bd6c11972c61daa8b6b8d18fb91))
|
||||
* **support:** update dependency axios to v1.6.6 ([6313704](https://github.com/appium/appium/commit/6313704ee5a8ee3aee726eb512ef259b6fa1041c))
|
||||
* **support:** update dependency axios to v1.6.7 ([795092a](https://github.com/appium/appium/commit/795092a97f6d7569cccc4b5c166f52fef821514b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [0.4.20](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.19...@appium/driver-test-support@0.4.20) (2024-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency axios to v1.6.3 ([441b284](https://github.com/appium/appium/commit/441b2848dae28472356f37fc5d51ac27af7bbe29))
|
||||
* **types:** update dependency type-fest to v4 ([#19592](https://github.com/appium/appium/issues/19592)) ([94b3580](https://github.com/appium/appium/commit/94b358022fdba3050ef94c1f881895f07e24fb75))
|
||||
|
||||
|
||||
|
||||
## [0.4.19](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.18...@appium/driver-test-support@0.4.19) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.4.19",
|
||||
"version": "0.4.21",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,18 +41,18 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.15.0",
|
||||
"@appium/types": "^0.16.1",
|
||||
"@types/lodash": "4.14.202",
|
||||
"@types/stoppable": "1.1.3",
|
||||
"axios": "1.6.2",
|
||||
"axios": "1.6.7",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.10",
|
||||
"chai": "4.4.1",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "17.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"stoppable": "1.1.0",
|
||||
"type-fest": "4.8.3"
|
||||
"type-fest": "4.10.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# @appium/eslint-config-appium
|
||||
# @appium/eslint-config-appium-ts
|
||||
|
||||
> Provides a reusable [ESLint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) for [Appium](https://github.com/appium/appium) and Appium-adjacent projects (for TypeScript)
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
> Provides a reusable [ESLint](http://eslint.org/) [shared configuration](http://eslint.org/docs/developer-guide/shareable-configs) for [Appium](https://github.com/appium/appium) and Appium-adjacent projects.
|
||||
|
||||
[](https://npmjs.org/package/@appium/eslint-config-appium)
|
||||
[](https://npmjs.org/package/@appium/eslint-config-appium)
|
||||
|
||||
## Usage
|
||||
|
||||
Install the package with **`npm` v8 or newer**:
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.25](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.24...@appium/execute-driver-plugin@3.0.25) (2024-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.31.1 ([db0eec0](https://github.com/appium/appium/commit/db0eec05c58fe970351a309a01507cad824bdad2))
|
||||
|
||||
|
||||
|
||||
## [3.0.24](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.23...@appium/execute-driver-plugin@3.0.24) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.27.2 ([35f1dba](https://github.com/appium/appium/commit/35f1dbabf6e852c4ae84ec128879bc7e61321a16))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.0 ([e8e0444](https://github.com/appium/appium/commit/e8e0444bd28d94963090065a813a4b10be665caa))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.6 ([da84efc](https://github.com/appium/appium/commit/da84efc42dd62b2be07a52dd4379791e2cef6eb2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.8 ([1fdf117](https://github.com/appium/appium/commit/1fdf117c63f6e9871f4de3c0bd93ecbdb13a9235))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.0 ([6f0101e](https://github.com/appium/appium/commit/6f0101eb6343eb8b372e00a6b71d2b2ee909404c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.1 ([fb0f8f7](https://github.com/appium/appium/commit/fb0f8f776c6a03174f4569efa60d817a930809cf))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.3 ([6dacb5e](https://github.com/appium/appium/commit/6dacb5e70ca3529a080f629f5245c2b676746e2c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.7 ([52a6f96](https://github.com/appium/appium/commit/52a6f96e085ff13acdf8d5013673861b7e0da818))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.30.0 ([91320dd](https://github.com/appium/appium/commit/91320ddd02dca89a8cec258d0ab44a49d344d71c))
|
||||
|
||||
|
||||
|
||||
## [3.0.23](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.22...@appium/execute-driver-plugin@3.0.23) (2024-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.26.3 ([ff767b7](https://github.com/appium/appium/commit/ff767b7f25e97ec9643f691b57ca6ba159dcdcf9))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.27.0 ([9b06b87](https://github.com/appium/appium/commit/9b06b871eb3d9c60428130442ea7ab433ce4df31))
|
||||
|
||||
|
||||
|
||||
## [3.0.22](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.21...@appium/execute-driver-plugin@3.0.22) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# @appium/execute-driver-plugin
|
||||
|
||||
> Appium plugin for running a driver script in a child process
|
||||
|
||||
[](https://npmjs.org/package/@appium/execute-driver-plugin)
|
||||
[](https://npmjs.org/package/@appium/execute-driver-plugin)
|
||||
|
||||
This plugin adds a new driver command that allows executing scripts in a child process. Currently,
|
||||
the only supported driver type is `webdriverio`, therefore the script must also be written in JS.
|
||||
|
||||
## Motivation
|
||||
|
||||
Running a driver script in a child process adds a degree of parallelisation, which may result in
|
||||
faster test execution.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
appium plugin install execute-driver
|
||||
```
|
||||
|
||||
The plugin must be explicitly activated when launching the Appium server. Since the input script
|
||||
can be arbitrary JavaScript, this is an insecure feature, and must also be explicitly enabled:
|
||||
|
||||
```
|
||||
appium --use-plugins=execute-driver --allow-insecure=execute_driver_script
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const script = `return await driver.getTimeouts();`;
|
||||
const {result, logs} = await driver.executeDriverScript(script);
|
||||
// 'result' contains the data returned by the script (in this case, the response to 'getTimeouts')
|
||||
// 'logs' contains everything logged to console during script execution
|
||||
```
|
||||
|
||||
Refer to your Appium client documentation for the exact syntax of this command.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "3.0.22",
|
||||
"version": "3.0.25",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,7 +43,7 @@
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.19",
|
||||
"webdriverio": "8.26.2"
|
||||
"webdriverio": "8.31.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
|
||||
@@ -3,6 +3,35 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.5.1](https://github.com/appium/appium/compare/@appium/fake-driver@5.5.0...@appium/fake-driver@5.5.1) (2024-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fake-driver:** fix dependencies ([#19782](https://github.com/appium/appium/issues/19782)) ([889a533](https://github.com/appium/appium/commit/889a533eef8b7a0805dd786d358f5729e03f0c40))
|
||||
|
||||
|
||||
|
||||
## [5.5.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.4.0...@appium/fake-driver@5.5.0) (2024-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Set a proper exit code if any of required doctor checks fails ([#19617](https://github.com/appium/appium/issues/19617)) ([f4011f1](https://github.com/appium/appium/commit/f4011f17d4d03bc854c43462848a4c38427acb85))
|
||||
|
||||
|
||||
|
||||
## [5.4.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.3.0...@appium/fake-driver@5.4.0) (2024-01-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add webdriver bidi support ([2b21e66](https://github.com/appium/appium/commit/2b21e66891e8ab8c3929f04f32e94eb4efdba691))
|
||||
* **appium,support:** Add common shortcuts for doctor checks ([#19562](https://github.com/appium/appium/issues/19562)) ([893b9e1](https://github.com/appium/appium/commit/893b9e15d64d2356ea339596e805543df51fa505))
|
||||
* **appium:** Make doctor extensible ([#19542](https://github.com/appium/appium/issues/19542)) ([a30286b](https://github.com/appium/appium/commit/a30286b6e7b9753f73e55a8f9db14211b3124578))
|
||||
|
||||
|
||||
|
||||
## [5.3.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.11...@appium/fake-driver@5.3.0) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
appium-fake-driver
|
||||
===================
|
||||
# @appium/fake-driver
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
> Fake Appium driver for internal testing
|
||||
|
||||
Issues for this repo are disabled. Log any issues at the [main Appium repo's issue tracker](https://github.com/appium/appium/issues).
|
||||
## License
|
||||
|
||||
Work in progress, stay tuned!
|
||||
|
||||
## Watch
|
||||
|
||||
```
|
||||
npm run watch
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
Apache-2.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const {doctor} = require('appium/support');
|
||||
|
||||
/** @satisfies {import('@appium/types').IDoctorCheck} */
|
||||
class EnvVarAndPathCheck {
|
||||
/**
|
||||
* @param {string} varName
|
||||
*/
|
||||
constructor(varName) {
|
||||
this.varName = varName;
|
||||
}
|
||||
|
||||
async diagnose() {
|
||||
return doctor.ok(`${this.varName} environment variable is always set because it's fake`);
|
||||
}
|
||||
|
||||
async fix() {
|
||||
return (
|
||||
`Make sure the environment variable ${this.varName} is properly configured for the Appium server process`
|
||||
);
|
||||
}
|
||||
|
||||
hasAutofix() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isOptional() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {EnvVarAndPathCheck};
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const {EnvVarAndPathCheck} = require('./common');
|
||||
|
||||
const fakeCheck1 = new EnvVarAndPathCheck('FAKE1');
|
||||
|
||||
module.exports = {fakeCheck1};
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const {EnvVarAndPathCheck} = require('./common');
|
||||
|
||||
const fakeCheck2 = new EnvVarAndPathCheck('FAKE2');
|
||||
|
||||
module.exports = {fakeCheck2};
|
||||
@@ -3,7 +3,7 @@ import {Element} from '@appium/types';
|
||||
import {errors} from 'appium/driver';
|
||||
import {FakeElement} from '../fake-element';
|
||||
import {FakeDriver} from '../driver';
|
||||
import {util} from '@appium/support';
|
||||
import {util} from 'appium/support';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
const {W3C_WEB_ELEMENT_IDENTIFIER} = util;
|
||||
|
||||
@@ -23,6 +23,9 @@ interface FakeDriverGeneralMixin {
|
||||
execute(script: string, args: any[]): Promise<any>;
|
||||
fakeAddition(a: number, b: number, c?: number): Promise<number>;
|
||||
getLog(type: string): Promise<any>;
|
||||
getUrl(): Promise<string>;
|
||||
|
||||
bidiNavigate(context: string, url: string): Promise<void>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
@@ -111,6 +114,14 @@ const GeneralMixin: FakeDriverGeneralMixin = {
|
||||
async fakeAddition(this: FakeDriver, num1: number, num2: number, num3 = 0) {
|
||||
return num1 + num2 + (num3 ?? 0);
|
||||
},
|
||||
|
||||
async getUrl() {
|
||||
return this.url;
|
||||
},
|
||||
|
||||
async bidiNavigate(context: string, url: string) {
|
||||
this.url = url;
|
||||
},
|
||||
};
|
||||
|
||||
mixin(GeneralMixin);
|
||||
|
||||
@@ -10,6 +10,9 @@ const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
|
||||
uniqueApp: {
|
||||
isBoolean: true,
|
||||
},
|
||||
runClock: {
|
||||
isBoolean: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -53,6 +56,12 @@ export class FakeDriver extends BaseDriver {
|
||||
/** @type {Record<string,import('./fake-element').FakeElement>} */
|
||||
elMap;
|
||||
|
||||
/** @type {string|null} */
|
||||
_bidiProxyUrl;
|
||||
|
||||
/** @type {boolean} */
|
||||
_clockRunning = false;
|
||||
|
||||
constructor(
|
||||
opts = /** @type {import('@appium/types').InitialOpts} */ ({}),
|
||||
shouldValidateCaps = true,
|
||||
@@ -67,6 +76,8 @@ export class FakeDriver extends BaseDriver {
|
||||
this.shook = false;
|
||||
this.appModel = new FakeApp();
|
||||
this.desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
|
||||
this.doesSupportBidi = true;
|
||||
this._bidiProxyUrl = null;
|
||||
}
|
||||
|
||||
proxyActive() {
|
||||
@@ -77,6 +88,10 @@ export class FakeDriver extends BaseDriver {
|
||||
return true;
|
||||
}
|
||||
|
||||
get bidiProxyUrl() {
|
||||
return this._bidiProxyUrl;
|
||||
}
|
||||
|
||||
proxyReqRes(req, res) {
|
||||
// fake implementation of jwp proxy req res
|
||||
res.set('content-type', 'application/json');
|
||||
@@ -123,9 +138,21 @@ export class FakeDriver extends BaseDriver {
|
||||
);
|
||||
this.caps = caps;
|
||||
await this.appModel.loadApp(caps.app);
|
||||
if (this.caps.runClock) {
|
||||
this.startClock();
|
||||
}
|
||||
return [sessionId, caps];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [sessionId]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deleteSession(sessionId) {
|
||||
this.stopClock();
|
||||
return await super.deleteSession(sessionId);
|
||||
}
|
||||
|
||||
get driverData() {
|
||||
return {
|
||||
isUnique: !!this.caps.uniqueApp,
|
||||
@@ -137,6 +164,21 @@ export class FakeDriver extends BaseDriver {
|
||||
return this.fakeThing;
|
||||
}
|
||||
|
||||
async startClock() {
|
||||
this._clockRunning = true;
|
||||
while (this._clockRunning) {
|
||||
await B.delay(500);
|
||||
this.eventEmitter.emit('bidiEvent', {
|
||||
method: 'clock.currentTime',
|
||||
params: {time: Date.now()},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stopClock() {
|
||||
this._clockRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'thing' value (so that it can be retrieved later)
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "5.3.0",
|
||||
"version": "5.5.1",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -51,7 +51,7 @@
|
||||
"xpath": "0.0.34"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
"appium": "^2.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
@@ -73,6 +73,12 @@
|
||||
"fake-error": "./build/lib/scripts/fake-error.js",
|
||||
"fake-success": "./build/lib/scripts/fake-success.js",
|
||||
"fake-stdin": "./build/lib/scripts/fake-stdin.js"
|
||||
},
|
||||
"doctor": {
|
||||
"checks": [
|
||||
"./doctor/fake1.js",
|
||||
"./doctor/fake2.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"main": "./build/lib/index.js",
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.32](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.31...@appium/fake-plugin@3.1.32) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.31](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.30...@appium/fake-plugin@3.1.31) (2024-02-06)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.30](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.29...@appium/fake-plugin@3.1.30) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.29](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.28...@appium/fake-plugin@3.1.29) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Appium Fake Plugin
|
||||
# @appium/fake-plugin
|
||||
|
||||
This is a "fake" Appium 2.0 plugin used for testing and examples. There's no point in documenting its functionality since it's not meant to be used. If you're here at all, it's probably because you want to peek at the source code to see how a plugin is designed. So go ahead and do that!
|
||||
> Fake Appium plugin for internal testing
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-plugin",
|
||||
"version": "3.1.29",
|
||||
"version": "3.1.32",
|
||||
"description": "A fake Appium 2.0 plugin",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -39,8 +39,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^2.2.25",
|
||||
"@appium/support": "^4.1.11",
|
||||
"@appium/base-plugin": "^2.2.28",
|
||||
"@appium/support": "^4.2.2",
|
||||
"@types/bluebird": "3.5.42",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [3.0.3](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.2...@appium/images-plugin@3.0.3) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.2](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.1...@appium/images-plugin@3.0.2) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **images-plugin:** update dependency lru-cache to v10 ([#19723](https://github.com/appium/appium/issues/19723)) ([202da83](https://github.com/appium/appium/commit/202da83449073ddb3da8fb2cfbcfce482a5ecd40))
|
||||
|
||||
|
||||
|
||||
## [3.0.1](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.0...@appium/images-plugin@3.0.1) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.0](https://github.com/appium/appium/compare/@appium/images-plugin@2.1.8...@appium/images-plugin@3.0.0) (2023-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
# Appium Images Plugin
|
||||
# @appium/images-plugin
|
||||
|
||||
This is an official Appium plugin designed to facilitate image comparison, visual testing, and image-based functional testing.
|
||||
> Appium plugin for image comparison, visual testing, and image-based functional testing
|
||||
|
||||
[](https://npmjs.org/package/@appium/images-plugin)
|
||||
[](https://npmjs.org/package/@appium/images-plugin)
|
||||
|
||||
## Features
|
||||
|
||||
1. **Image Comparison** ([docs](./docs/image-comparison.md)) - A new Appium command and route that allows sending in two different images and comparing them in various ways.
|
||||
2. **Finding Elements by Image** ([docs](./docs/find-by-image.md)) - Using a template image, find a matching screen region of an app and interact with it via standard Appium element semantics.
|
||||
|
||||
## Prerequisites
|
||||
* Appium Server 2.0+
|
||||
|
||||
## Installation - Server
|
||||
|
||||
Install the plugin using Appium's plugin CLI:
|
||||
## Installation
|
||||
|
||||
```
|
||||
appium plugin install images
|
||||
```
|
||||
|
||||
## Installation - Client
|
||||
|
||||
No special action is needed to make the features available in the various Appium clients, as this plugin used to be a core Appium feature and its commands are already supported in the official clients.
|
||||
|
||||
## Activation
|
||||
|
||||
The plugin will not be active unless turned on when invoking the Appium server:
|
||||
The plugin must be explicitly activated when launching the Appium server:
|
||||
|
||||
```
|
||||
appium --use-plugins=images
|
||||
```
|
||||
|
||||
## Usage
|
||||
## License
|
||||
|
||||
Check the docs links above for usage for each of the features provided.
|
||||
Apache-2.0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user