mirror of
https://github.com/appium/appium.git
synced 2026-11-01 23:00:54 +08:00
Compare commits
60
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
872b924a97 | ||
|
|
0e164ba03d | ||
|
|
2359a2ba3b | ||
|
|
49694f50a4 | ||
|
|
a2cfb832ad | ||
|
|
4cfad004db | ||
|
|
b159780fd6 | ||
|
|
e2f7dd7154 | ||
|
|
e9dae3f6d0 | ||
|
|
58b4790b1d | ||
|
|
63eddb670f | ||
|
|
b7d86c0d04 | ||
|
|
dfe02ca98c | ||
|
|
10e6815a39 | ||
|
|
cd76622aa9 | ||
|
|
296cb2436c | ||
|
|
01bff49297 | ||
|
|
48c331157f | ||
|
|
9f8daf385b | ||
|
|
7bae977ca9 | ||
|
|
4ed745a95f | ||
|
|
85e4bd8925 | ||
|
|
cce27fcf88 | ||
|
|
b30b2cfb84 | ||
|
|
9ccbab5dc0 | ||
|
|
760bb5b8f3 | ||
|
|
cb14faae33 | ||
|
|
2a6a13ac89 | ||
|
|
4b43af0709 | ||
|
|
504415d0cd | ||
|
|
75defa7b48 | ||
|
|
be86627595 | ||
|
|
080eb8aa1b | ||
|
|
1b70551055 | ||
|
|
d262271b26 | ||
|
|
45b026de67 | ||
|
|
843b797750 | ||
|
|
b4c1403c22 | ||
|
|
e2aa3d3b32 | ||
|
|
b151e4c9bf | ||
|
|
5684c4e286 | ||
|
|
189e39d03c | ||
|
|
694f9c6a63 | ||
|
|
f80f4cb2bc | ||
|
|
908041c525 | ||
|
|
18c0b4b230 | ||
|
|
4e2423d0f0 | ||
|
|
ed1d3aae61 | ||
|
|
d1d5ecef87 | ||
|
|
b88f6e3e86 | ||
|
|
471a4b57e6 | ||
|
|
feebf72eb4 | ||
|
|
0f196bad4a | ||
|
|
431092dc87 | ||
|
|
935c70683f | ||
|
|
f8099dafa7 | ||
|
|
25c9728f07 | ||
|
|
ba6b312244 | ||
|
|
188ed161e4 | ||
|
|
96898c87d5 |
@@ -4,8 +4,5 @@ examples/javascript-wd
|
||||
**/build/**
|
||||
**/*.min.js
|
||||
sample-code
|
||||
**/*.d.ts
|
||||
**/build-fixtures/**
|
||||
**/*.ts
|
||||
packages/appium/docs/**/assets/**
|
||||
**/*.tsx
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@appium/eslint-config-appium",
|
||||
"extends": ["@appium/eslint-config-appium", "@appium/eslint-config-appium-ts"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "packages/fake-driver/**/*",
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
version: v1
|
||||
|
||||
labels:
|
||||
- label: 'Enhancement'
|
||||
sync: true
|
||||
matcher:
|
||||
commits: '^feat.*?:'
|
||||
title: '^feat.*?:'
|
||||
|
||||
- label: 'Bug'
|
||||
sync: true
|
||||
matcher:
|
||||
commits: '^fix.*?:'
|
||||
title: '^fix.*?:'
|
||||
|
||||
- label: 'Documentation'
|
||||
sync: true
|
||||
matcher:
|
||||
commits: '^docs.*?:'
|
||||
title: '^docs.*?:'
|
||||
files: ['packages/docutils/**', 'packages/appium/docs/**', '**/*.md']
|
||||
|
||||
- label: 'chore'
|
||||
sync: true
|
||||
matcher:
|
||||
commits: '^chore.*?:'
|
||||
title: '^chore.*?:'
|
||||
|
||||
- label: 'SemVer:Breaking'
|
||||
sync: true
|
||||
matcher:
|
||||
commits: '(^(.+?)!:)|(BREAKING CHANGE:)'
|
||||
|
||||
- label: 'Dependencies'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['**/package.json', '**/package-lock.json', '**/requirements.txt']
|
||||
author: 'renovate'
|
||||
|
||||
- label: 'appium core'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/appium/**']
|
||||
|
||||
- label: '@appium/base-driver'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/base-driver/**']
|
||||
|
||||
- label: '@appium/eslint-config-appium'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/eslint-config-appium/**']
|
||||
|
||||
- label: '@appium/base-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/base-plugin/**']
|
||||
|
||||
- label: '@appium/doctor'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/doctor/**']
|
||||
|
||||
- label: '@appium/docutils'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/docutils/**']
|
||||
|
||||
- label: '@appium/driver-test-support'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/driver-test-support/**']
|
||||
|
||||
- label: '@appium/execute-driver-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/execute-driver-plugin/**']
|
||||
|
||||
- label: '@appium/fake-driver'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/fake-driver/**']
|
||||
|
||||
- label: '@appium/fake-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/fake-plugin/**']
|
||||
|
||||
- label: '@appium/images-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/images-plugin/**']
|
||||
|
||||
- label: '@appium/opencv'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/opencv/**']
|
||||
|
||||
- label: '@appium/plugin-test-support'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/plugin-test-support/**']
|
||||
|
||||
- label: '@appium/relaxed-caps-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/relaxed-caps-plugin/**']
|
||||
|
||||
- label: '@appium/schema'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/schema/**']
|
||||
|
||||
- label: '@appium/support'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/support/**']
|
||||
|
||||
- label: '@appium/test-support'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/test-support/**']
|
||||
|
||||
- label: '@appium/tsconfig'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/tsconfig/**']
|
||||
|
||||
- label: '@appium/typedoc-plugin-appium'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/typedoc-plugin-appium/**']
|
||||
|
||||
- label: '@appium/types'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/types/**']
|
||||
|
||||
- label: '@appium/universal-xml-plugin'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/universal-xml-plugin/**']
|
||||
@@ -0,0 +1,22 @@
|
||||
name: 'Labeler'
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
# Setting up permissions in the workflow to limit the scope of what it can do. Optional!
|
||||
contents: read
|
||||
issues: read # change this if/when we want to label issues
|
||||
pull-requests: write
|
||||
statuses: read
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: Labeler
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# follows semantic versioning. Lock to different version: v1, v1.5, v1.5.0 or use a commit hash.
|
||||
- uses: fuxingloh/multi-labeler@6704db0bcba106d07482efabbc79d3092af74fa2 # v2
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}} # optional, default to '${{ github.token }}'
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright JS Foundation and other contributors, https://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -57,7 +57,7 @@ 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 appium/appium-xcuitest-driver
|
||||
appium driver install --source=github --package=appium-xcuitest-driver appium/appium-xcuitest-driver
|
||||
|
||||
# To list already installed drivers
|
||||
appium driver list --installed
|
||||
@@ -168,7 +168,7 @@ Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
automation, for mobile and beyond. Finally!
|
||||
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium Concepts](/docs/en/about-appium/intro.md).
|
||||
about, please read our [Intro to Appium](https://appium.github.io/appium/docs/en/latest/intro/).
|
||||
|
||||
### Quickstart
|
||||
|
||||
@@ -204,10 +204,7 @@ be sure to sign up!
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
We put together a [troubleshooting guide](/docs/en/writing-running-appium/other/troubleshooting.md).
|
||||
Please have a look here first if you run into any problems. It contains instructions for checking
|
||||
a lot of common errors and how to get in touch with the community if you're
|
||||
stumped.
|
||||
Please find the troubleshooting guide at the corresponding driver repository/documentation.
|
||||
|
||||
### License
|
||||
|
||||
|
||||
Generated
+661
-417
File diff suppressed because it is too large
Load Diff
+13
-12
@@ -30,7 +30,7 @@
|
||||
"build": "tsc -b",
|
||||
"build:all": "run-s build build:packages",
|
||||
"build:packages": "lerna run build",
|
||||
"clean": "run-s \"clean:artifacts:*\" clean:workspaces",
|
||||
"clean": "run-s clean:artifacts clean:workspaces",
|
||||
"clean:artifacts": "run-p \"clean:artifacts:*\"",
|
||||
"clean:artifacts:distfiles": "npx rimraf \"./packages/*/build\" \"./packages/*/.tsconfig.tsbuildinfo\"",
|
||||
"clean:artifacts:packages": "lerna run clean || true",
|
||||
@@ -40,12 +40,11 @@
|
||||
"doctor": "node ./packages/doctor",
|
||||
"husky-install": "husky install",
|
||||
"generate-docs": "npm run --workspace=packages/appium generate-docs",
|
||||
"prepare": "run-p husky-install build:all || true",
|
||||
"prepare": "run-p husky-install build:all",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:staged": "lint-staged",
|
||||
"pack-all": "npm pack --workspaces --include-workspace-root",
|
||||
"prepublishOnly": "run-s rebuild",
|
||||
"reinstall": "run-s clean ci",
|
||||
"rebuild": "run-s clean:artifacts build:all",
|
||||
@@ -53,7 +52,7 @@
|
||||
"sync-pkgs": "run-p sync-pkgs:*",
|
||||
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
|
||||
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,bugs,homepage,engines",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages=\"!packages/typedoc-plugin-appium\"",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages=\"!packages/typedoc-plugin-appium\" --packages=\"!packages/eslint-config-appium-ts\" ",
|
||||
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
|
||||
"stage-synced-pkgs": "git add -A packages/appium/README.md \"packages/*/package.json\"",
|
||||
"test": "npm-run-all -p lint build -s test:unit",
|
||||
@@ -63,7 +62,8 @@
|
||||
"test:unit": "lerna run test",
|
||||
"typedoc": "typedoc --logLevel Verbose",
|
||||
"upload": "gulp github-upload",
|
||||
"preversion": "run-s rebuild sync-pkgs stage-synced-pkgs",
|
||||
"preversion": "run-s sync-pkgs stage-synced-pkgs",
|
||||
"prepack": "npm run rebuild",
|
||||
"zip": "zip -qr ./appium.zip ./packages/appium",
|
||||
"zip-and-upload": "run-s zip upload"
|
||||
},
|
||||
@@ -81,20 +81,22 @@
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/diff": "5.0.2",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.14.1",
|
||||
"@types/node": "18.14.6",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/sinon-chai": "3.2.9",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/ws": "8.5.4",
|
||||
"@types/yargs": "17.0.22",
|
||||
"@typescript-eslint/eslint-plugin": "5.54.1",
|
||||
"@typescript-eslint/parser": "5.54.1",
|
||||
"asyncbox": "2.9.4",
|
||||
"chai": "4.3.7",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"cpy-cli": "4.2.0",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint": "8.35.0",
|
||||
"eslint-config-prettier": "8.7.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-mocha": "10.1.0",
|
||||
@@ -104,7 +106,7 @@
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.7",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "11.0.5",
|
||||
"json-schema-to-typescript": "12.0.0",
|
||||
"lerna": "6.5.1",
|
||||
"lint-staged": "13.1.2",
|
||||
"log-symbols": "4.1.0",
|
||||
@@ -122,12 +124,12 @@
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"typedoc": "0.23.25",
|
||||
"typedoc": "0.23.26",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.9.5",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.30.1",
|
||||
"webdriverio": "7.30.2",
|
||||
"ws": "8.12.1",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
@@ -139,7 +141,6 @@
|
||||
"access": "public"
|
||||
},
|
||||
"overrides": {
|
||||
"jpeg-js": "0.4.4",
|
||||
"@sidvind/better-ajv-errors": {
|
||||
"ajv": "8.12.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.57](https://github.com/appium/appium/compare/appium@2.0.0-beta.56...appium@2.0.0-beta.57) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,fake-driver:** expose child process when running an extension script ([e9dae3f](https://github.com/appium/appium/commit/e9dae3f6d006dcf89b6c0b6fb491be15acfed98b))
|
||||
* **appium,support:** fs.readPackageJsonFrom() returns proper type ([9ccbab5](https://github.com/appium/appium/commit/9ccbab5dc02d0019b0cad903772cda872d9fd5fb))
|
||||
* **docutils:** update dependency lilconfig to v2.1.0 ([4ed745a](https://github.com/appium/appium/commit/4ed745a95ffc6a43f76003eba62019c16a4c6cf2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.30.2 ([49694f5](https://github.com/appium/appium/commit/49694f50a4680138cc92aba1eee294c927d712cf))
|
||||
* **types:** update dependency type-fest to v3.6.1 ([471a4b5](https://github.com/appium/appium/commit/471a4b57e622ff077d59f577a78341268700c48d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.56](https://github.com/appium/appium/compare/appium@2.0.0-beta.55...appium@2.0.0-beta.56) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ 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 appium/appium-xcuitest-driver
|
||||
appium driver install --source=github --package=appium-xcuitest-driver appium/appium-xcuitest-driver
|
||||
|
||||
# To list already installed drivers
|
||||
appium driver list --installed
|
||||
@@ -168,7 +168,7 @@ Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
automation, for mobile and beyond. Finally!
|
||||
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium Concepts](/docs/en/about-appium/intro.md).
|
||||
about, please read our [Intro to Appium](https://appium.github.io/appium/docs/en/latest/intro/).
|
||||
|
||||
### Quickstart
|
||||
|
||||
@@ -204,10 +204,7 @@ be sure to sign up!
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
We put together a [troubleshooting guide](/docs/en/writing-running-appium/other/troubleshooting.md).
|
||||
Please have a look here first if you run into any problems. It contains instructions for checking
|
||||
a lot of common errors and how to get in touch with the community if you're
|
||||
stumped.
|
||||
Please find the troubleshooting guide at the corresponding driver repository/documentation.
|
||||
|
||||
### License
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<!---Current latest commit hash in the original English documentation is e4dfa52 --->
|
||||
|
||||
|
||||
---
|
||||
title: Appium 1.xからAppium 2.xへ移行する
|
||||
---
|
||||
@@ -39,15 +42,39 @@ Together these do introduce a few breaking changes to how Appium is installed, h
|
||||
## 破壊的変更
|
||||
|
||||
<!---
|
||||
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases) for the most comprehensive lists of changes. Here we call out the breaking changes and what you need to do to account for them.
|
||||
Here we call out the breaking changes and what you need to do to account for them.
|
||||
--->
|
||||
|
||||
リリース毎の変更一覧に関しては[Appium 2.0リリースノート](https://github.com/appium/appium/releases)を確認してください。ここでは破壊的変更と何をする必要があるのかを示します。
|
||||
以下では破壊的変更と、何をする必要があるのかを示します。
|
||||
|
||||
<!---
|
||||
### :bangbang: Installing drivers during setup
|
||||
--->
|
||||
|
||||
|
||||
### :bangbang: 初期設定のbase path
|
||||
|
||||
|
||||
<!---
|
||||
With Appium 1.x, the server would accept commands by default on `http://localhost:4723/wd/hub`. The
|
||||
`/wd/hub` base path was a legacy convention from the days of migrating from Selenium 1 to Selenium
|
||||
2, and is no longer relevant. As such the default base path for the server is now `/`. If you want
|
||||
to retain the old behaviour, you can set the base path via a command line argument as follows:
|
||||
--->
|
||||
|
||||
Appium 1.xでは、AppiumサーバーのURLは`http://localhost:4723/wd/hub`でした。この`/wd/hub`であるbase pathはSelenium 1からSelenium 2へ移行した時の名残であり、今となっては何ら意味を持ちません。そのため、初期設定のbase pathは`/`となります。もしあたなが以前の挙動を保持したいのであれば、以下の通りにAppiumサーバを起動してください。
|
||||
|
||||
```
|
||||
appium --base-path=/wd/hub
|
||||
```
|
||||
|
||||
<!---
|
||||
You can also set server arguments as [Config file](./config.md) properties.
|
||||
--->
|
||||
|
||||
この設定は[Config file](./config.md)からでも可能です。
|
||||
|
||||
|
||||
### :bangbang: ドライバーのインストール
|
||||
|
||||
<!---
|
||||
@@ -72,10 +99,10 @@ npm install --global appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
<!---
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
This will install Appium and the two drivers for you in one go. Please uninstall any existing Appium 1.x npm packages (with `npm uninstall -g appium`) if you get an installation or startup error.
|
||||
--->
|
||||
|
||||
これはAppiumと2つのドライバーを一つのコマンドでインストールします。
|
||||
これはAppiumと2つのドライバーを一つのコマンドでインストールします。もしセットアップで何らかの問題が発生した場合、既存のAppium 1.xを`npm uninstall -g appium`で削除してみてください。
|
||||
|
||||
### :bangbang: Drivers installation path
|
||||
|
||||
@@ -224,6 +251,17 @@ In summary, if you are using a JSON Appium config file, you can simply cut-and-p
|
||||
|
||||
The old iOS and Android (UiAutomator 1) drivers and related tools (e.g., `authorize-ios`) have been removed. They haven't been relevant for many years anyway.
|
||||
|
||||
|
||||
### :bangbang: Server can no longer be started with `--port 0`
|
||||
|
||||
In Appium 1.x, it was possible to specify `--port 0` during server startup. This had the effect of
|
||||
starting Appium on a random free port. In Appium 2.0, port values must be `1` or higher. The random
|
||||
port assignment was never an intentional feature of Appium 1.x, but a consequence of how Node's
|
||||
HTTP servers work and the fact that there was no port input validation in Appium 1.x. If you want
|
||||
to find a random free port to start Appium on, you must now take care of this on your own prior to
|
||||
starting Appium. Starting Appium on an explicit and known port is the correct practice moving
|
||||
forward.
|
||||
|
||||
### :warning: Internal packages renamed
|
||||
|
||||
Some Appium-internal NPM packages have been renamed (for example, `appium-base-driver` is now `@appium/base-driver`). This is not a breaking change for Appium users, only for people who have built software that directly incorporates Appium's code.
|
||||
|
||||
@@ -289,7 +289,7 @@ class AppiumDriver extends DriverCore {
|
||||
*/
|
||||
let otherPendingDriversData = [];
|
||||
|
||||
const driverInstance = new InnerDriver(this.args, true);
|
||||
const driverInstance = /** @type {ExternalDriver} */ (new InnerDriver(this.args, true));
|
||||
|
||||
// We want to assign security values directly on the driver. The driver
|
||||
// should not read security values from `this.opts` because those values
|
||||
|
||||
@@ -58,7 +58,12 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
* @return {Promise<import('./extension-command').RunOutput>}
|
||||
*/
|
||||
async run({driver, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: driver, scriptName, extraArgs});
|
||||
return await super._run({
|
||||
installSpec: driver,
|
||||
scriptName,
|
||||
extraArgs,
|
||||
bufferOutput: this.isJsonOutput,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,14 +4,15 @@ import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import {npm, util, env, console} from '@appium/support';
|
||||
import {spinWith, RingBuffer} from './utils';
|
||||
import {SubProcess} from 'teen_process';
|
||||
import {
|
||||
INSTALL_TYPE_NPM,
|
||||
INSTALL_TYPE_GIT,
|
||||
INSTALL_TYPE_GITHUB,
|
||||
INSTALL_TYPE_LOCAL,
|
||||
} from '../extension/extension-config';
|
||||
import {SubProcess} from 'teen_process';
|
||||
import {packageDidChange} from '../extension/package-changed';
|
||||
import {spawn} from 'child_process';
|
||||
|
||||
const UPDATE_ALL = 'installed';
|
||||
|
||||
@@ -660,17 +661,34 @@ class ExtensionCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a script cached inside the "scripts" field under "appium"
|
||||
* inside of the driver/plugins "package.json" file. Will throw
|
||||
* an error if the driver/plugin does not contain a "scripts" field
|
||||
* underneath the "appium" field in its package.json, if the
|
||||
* "scripts" field is not a plain object, or if the scriptName is
|
||||
* not found within "scripts" object.
|
||||
* Just wraps {@linkcode child_process.spawn} with some default options
|
||||
*
|
||||
* @param {string} cwd - CWD
|
||||
* @param {string} script - Path to script
|
||||
* @param {string[]} args - Extra args for script
|
||||
* @param {import('child_process').SpawnOptions} opts - Options
|
||||
* @returns {import('node:child_process').ChildProcess}
|
||||
*/
|
||||
_runUnbuffered(cwd, script, args = [], opts = {}) {
|
||||
return spawn(process.execPath, [script, ...args], {
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a script cached inside the `scripts` field under `appium`
|
||||
* inside of the extension's `package.json` file. Will throw
|
||||
* an error if the driver/plugin does not contain a `scripts` field
|
||||
* underneath the `appium` field in its `package.json`, if the
|
||||
* `scripts` field is not a plain object, or if the `scriptName` is
|
||||
* not found within `scripts` object.
|
||||
*
|
||||
* @param {RunOptions} opts
|
||||
* @return {Promise<RunOutput>}
|
||||
*/
|
||||
async _run({installSpec, scriptName, extraArgs = []}) {
|
||||
async _run({installSpec, scriptName, extraArgs = [], bufferOutput = false}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
||||
}
|
||||
@@ -699,26 +717,56 @@ class ExtensionCommand {
|
||||
);
|
||||
}
|
||||
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName], ...extraArgs], {
|
||||
cwd: this.config.getInstallPath(installSpec),
|
||||
});
|
||||
if (bufferOutput) {
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName], ...extraArgs], {
|
||||
cwd: this.config.getInstallPath(installSpec),
|
||||
});
|
||||
|
||||
const output = new RingBuffer(50);
|
||||
const output = new RingBuffer(50);
|
||||
|
||||
runner.on('stream-line', (line) => {
|
||||
output.enqueue(line);
|
||||
this.log.log(line);
|
||||
});
|
||||
runner.on('stream-line', (line) => {
|
||||
output.enqueue(line);
|
||||
this.log.log(line);
|
||||
});
|
||||
|
||||
await runner.start(0);
|
||||
await runner.start(0);
|
||||
|
||||
try {
|
||||
await runner.join();
|
||||
this.log.ok(`${scriptName} successfully ran`.green);
|
||||
return {output: output.getBuff()};
|
||||
} catch (err) {
|
||||
this.log.error(`Encountered an error when running '${scriptName}': ${err.message}`.red);
|
||||
return {error: err.message, output: output.getBuff()};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await runner.join();
|
||||
await new B((resolve, reject) => {
|
||||
this._runUnbuffered(
|
||||
this.config.getInstallPath(installSpec),
|
||||
extScripts[scriptName],
|
||||
extraArgs
|
||||
)
|
||||
.on('error', (err) => {
|
||||
// generally this is of the "I can't find the script" variety.
|
||||
// this is a developer bug: the extension is pointing to a script that is not where the
|
||||
// developer said it would be (in `appium.scripts` of the extension's `package.json`)
|
||||
reject(err);
|
||||
})
|
||||
.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Script "${scriptName}" exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
this.log.ok(`${scriptName} successfully ran`.green);
|
||||
return {output: output.getBuff()};
|
||||
return {};
|
||||
} catch (err) {
|
||||
this.log.error(`Encountered an error when running '${scriptName}': ${err.message}`.red);
|
||||
return {error: err.message, output: output.getBuff()};
|
||||
return {error: err.message};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -802,6 +850,7 @@ export {ExtensionCommand};
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
* @property {string[]} [extraArgs] - arguments to pass to the script
|
||||
* @property {boolean} [bufferOutput] - if true, will buffer the output of the script and return it
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -809,7 +858,7 @@ export {ExtensionCommand};
|
||||
*
|
||||
* @typedef RunOutput
|
||||
* @property {string} [error] - error message if script ran unsuccessfully, otherwise undefined
|
||||
* @property {string[]} output - script output
|
||||
* @property {string[]} [output] - script output if `bufferOutput` was `true` in {@linkcode RunOptions}
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,12 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
* @returns {Promise<import('./extension-command').RunOutput>}
|
||||
*/
|
||||
async run({plugin, scriptName, extraArgs}) {
|
||||
return await super._run({installSpec: plugin, scriptName, extraArgs});
|
||||
return await super._run({
|
||||
installSpec: plugin,
|
||||
scriptName,
|
||||
extraArgs,
|
||||
bufferOutput: this.isJsonOutput,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,8 +10,9 @@ import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
|
||||
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
const APPIUM_VER = npmPackage.version;
|
||||
const MIN_NODE_VERSION = npmPackage.engines.node;
|
||||
const MIN_NPM_VERSION = npmPackage.engines.npm;
|
||||
const ENGINES = /** @type {Record<string,string>} */ (npmPackage.engines);
|
||||
const MIN_NODE_VERSION = ENGINES.node;
|
||||
const MIN_NPM_VERSION = ENGINES.npm;
|
||||
|
||||
const GIT_META_ROOT = '.git';
|
||||
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.56",
|
||||
"version": "2.0.0-beta.57",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -59,12 +59,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.2",
|
||||
"@appium/base-plugin": "^2.2.2",
|
||||
"@appium/docutils": "^0.2.2",
|
||||
"@appium/schema": "^0.2.5",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@appium/base-driver": "^9.3.3",
|
||||
"@appium/base-plugin": "^2.2.3",
|
||||
"@appium/docutils": "^0.3.0",
|
||||
"@appium/schema": "^0.2.6",
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/types": "^0.10.1",
|
||||
"@sidvind/better-ajv-errors": "2.1.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.38",
|
||||
@@ -82,7 +82,7 @@
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "8.1.0",
|
||||
"lilconfig": "2.0.6",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"longjohn": "0.2.12",
|
||||
"npmlog": "7.0.1",
|
||||
@@ -92,7 +92,7 @@
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.6.0",
|
||||
"type-fest": "3.6.1",
|
||||
"winston": "3.8.2",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.2.1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "7.30.1"
|
||||
"webdriverio": "7.30.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
*/
|
||||
|
||||
const B = require('bluebird');
|
||||
const path = require('node:path');
|
||||
const {realpath} = require('node:fs/promises');
|
||||
|
||||
B.config({
|
||||
cancellation: true,
|
||||
@@ -55,11 +57,32 @@ function log(message) {
|
||||
console.error(wrap(`[Appium] ${message}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a naive attempt at determining whether or not we are in a dev environment; in other
|
||||
* words, is `postinstall` being run from within the `appium` monorepo?
|
||||
*
|
||||
* When we're in the monorepo, `npm_config_local_prefix` will be set to the root of the monorepo root
|
||||
* dir when running this lifecycle script from an `npm install` in the monorepo root.
|
||||
*
|
||||
* `realpath` is necessary due to macOS omitting `/private` from paths
|
||||
*/
|
||||
async function isDevEnvironment() {
|
||||
return (
|
||||
process.env.npm_config_local_prefix &&
|
||||
path.join(process.env.npm_config_local_prefix, 'packages', 'appium') ===
|
||||
(await realpath(path.join(__dirname, '..')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup / check environment if we should do anything here
|
||||
* @returns {Promise<boolean>} `true` if Appium is built and ready to go
|
||||
*/
|
||||
async function init() {
|
||||
if (await isDevEnvironment()) {
|
||||
log('Dev environment likely; skipping automatic installation of extensions');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
({env, util, logger} = require('@appium/support'));
|
||||
// @ts-ignore
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// @ts-check
|
||||
import {DriverConfig} from '../../../lib/extension/driver-config';
|
||||
import {ExtensionCommand} from '../../../lib/cli/extension-command';
|
||||
import sinon from 'sinon';
|
||||
import {FAKE_DRIVER_DIR} from '../../helpers';
|
||||
import {Manifest} from '../../../lib/extension/manifest';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
/**
|
||||
* Relative path from actual `package.json` of `FakeDriver` for the `fake-stdin` script
|
||||
*/
|
||||
const FAKE_STDIN_SCRIPT = require(`${FAKE_DRIVER_DIR}/package.json`).appium.scripts['fake-stdin'];
|
||||
|
||||
describe('ExtensionCommand', function () {
|
||||
describe('method', function () {
|
||||
/** @type {ExtensionCommand} */
|
||||
let ec;
|
||||
|
||||
/** @type {sinon.SinonSandbox} */
|
||||
let sandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.createSandbox();
|
||||
const driverConfig = DriverConfig.create(sandbox.createStubInstance(Manifest));
|
||||
ec = new ExtensionCommand({config: driverConfig, json: false});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('_runUnbuffered()', function () {
|
||||
// this test is low value and mostly just asserts that `child_process.spawn()` works.
|
||||
// the problem is that because `_run()` returns a `Promise`, a caller cannot reach the
|
||||
// underlying `ChildProcess` instance.
|
||||
// something like `execa` could work around this because it returns a frankenstein of a
|
||||
// `Promise` + `ChildProcess`, but I didn't want to add the dep.
|
||||
it('should respond to stdin', function (done) {
|
||||
// we have to fake writing to STDIN because this is an automated test, after all.
|
||||
const proc = ec
|
||||
._runUnbuffered(FAKE_DRIVER_DIR, FAKE_STDIN_SCRIPT, [], {
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
})
|
||||
.once('exit', (code) => {
|
||||
try {
|
||||
expect(code).to.equal(0);
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
// TS does not understand that `proc.stdin` is not `null`, because it is only a `Writable`
|
||||
// if STDIN is piped from the parent.
|
||||
const stdin = /** @type {import('node:stream').Writable} */ (proc.stdin);
|
||||
stdin.write('\n');
|
||||
stdin.end();
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.3.3](https://github.com/appium/appium/compare/@appium/base-driver@9.3.2...@appium/base-driver@9.3.3) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** remove needless static prop breaking the build ([1b70551](https://github.com/appium/appium/commit/1b70551055444cad97144e539da7be872b9f70f8))
|
||||
* **base-driver:** use new mixin strategy ([be86627](https://github.com/appium/appium/commit/be8662759586bb3c24c7635b60ed8c574f5e2fd4))
|
||||
* **images-plugin:** update dependency lru-cache to v7.17.2 ([d1d5ece](https://github.com/appium/appium/commit/d1d5ecef87e70afea6d9d59f69cae465460e930b))
|
||||
* **images-plugin:** update dependency lru-cache to v7.18.1 ([ed1d3aa](https://github.com/appium/appium/commit/ed1d3aae61539bc136b3f266911331387397b88a))
|
||||
* **images-plugin:** update dependency lru-cache to v7.18.2 ([48c3311](https://github.com/appium/appium/commit/48c331157f74b238a3fc8dc92775d0af3f9f3134))
|
||||
* **images-plugin:** update dependency lru-cache to v7.18.3 ([01bff49](https://github.com/appium/appium/commit/01bff49297ca5a1c92259f1f8f11f5944ef15e4d))
|
||||
* **types:** update dependency type-fest to v3.6.1 ([471a4b5](https://github.com/appium/appium/commit/471a4b57e622ff077d59f577a78341268700c48d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.2](https://github.com/appium/appium/compare/@appium/base-driver@9.3.1...@appium/base-driver@9.3.2) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/* eslint-disable require-await */
|
||||
// @ts-check
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./timeout').TimeoutBase<C>} Base
|
||||
* @returns {EventBase<C>}
|
||||
*/
|
||||
export function EventMixin(Base) {
|
||||
/**
|
||||
* @implements {IEventCommands}
|
||||
*/
|
||||
class EventCommands extends Base {
|
||||
/**
|
||||
* Log a user-defined event in the event log.
|
||||
*
|
||||
* @param {string} vendor - a vendor prefix for the user, to ensure namespace
|
||||
* separation
|
||||
* @param {string} event - the event name
|
||||
*/
|
||||
async logCustomEvent(vendor, event) {
|
||||
this.logEvent(`${vendor}:${event}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event log
|
||||
* @param {string|string[]} [type] - the event type to filter with.
|
||||
* It returns all events if the type is not provided or empty string/array.
|
||||
* @returns {Promise<import('@appium/types').EventHistory|Record<string,number>>} - the event history log object
|
||||
*/
|
||||
async getLogEvents(type) {
|
||||
if (_.isEmpty(type)) {
|
||||
return this.eventHistory;
|
||||
}
|
||||
|
||||
const typeList = _.castArray(type);
|
||||
|
||||
return _.reduce(
|
||||
this.eventHistory,
|
||||
(acc, eventTimes, eventType) => {
|
||||
if (typeList.includes(eventType)) {
|
||||
acc[eventType] = eventTimes;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return EventCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').IEventCommands} IEventCommands
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & IEventCommands>} EventBase
|
||||
*/
|
||||
@@ -0,0 +1,48 @@
|
||||
import _ from 'lodash';
|
||||
import {Constraints, IEventCommands} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
import {BaseDriver} from '../driver';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends IEventCommands {}
|
||||
}
|
||||
|
||||
const EventCommands: IEventCommands = {
|
||||
/**
|
||||
* Log a user-defined event in the event log.
|
||||
*
|
||||
* @param vendor - a vendor prefix for the user, to ensure namespace
|
||||
* separation
|
||||
* @param event - the event name
|
||||
*/
|
||||
async logCustomEvent<C extends Constraints>(this: BaseDriver<C>, vendor: string, event: string) {
|
||||
this.logEvent(`${vendor}:${event}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the event log
|
||||
* @param type - the event type to filter with.
|
||||
* It returns all events if the type is not provided or empty string/array.
|
||||
* @returns the event history log object
|
||||
*/
|
||||
async getLogEvents<C extends Constraints>(this: BaseDriver<C>, type: string | string[]) {
|
||||
if (_.isEmpty(type)) {
|
||||
return this.eventHistory;
|
||||
}
|
||||
|
||||
const typeList = _.castArray(type);
|
||||
|
||||
return _.reduce(
|
||||
this.eventHistory,
|
||||
(acc, eventTimes, eventType) => {
|
||||
if (typeList.includes(eventType)) {
|
||||
acc[eventType] = eventTimes;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(EventCommands);
|
||||
@@ -1,45 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {errors, validateExecuteMethodParams} from '../../protocol';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./session').SessionBase<C>} Base
|
||||
* @returns {ExecuteBase<C>}
|
||||
*/
|
||||
export function ExecuteMixin(Base) {
|
||||
/**
|
||||
* @implements {IExecuteCommands}
|
||||
*/
|
||||
class ExecuteCommands extends Base {
|
||||
/**
|
||||
* @param {string} script
|
||||
* @param {[Record<string, any>]|[]} protoArgs
|
||||
*/
|
||||
async executeMethod(script, protoArgs) {
|
||||
const Driver = /** @type {DriverClass} */ (this.constructor);
|
||||
const commandMetadata = {...Driver.executeMethodMap?.[script]};
|
||||
if (!commandMetadata.command) {
|
||||
const availableScripts = _.keys(Driver.executeMethodMap);
|
||||
throw new errors.UnsupportedOperationError(
|
||||
`Unsupported execute method '${script}'. Available methods ` +
|
||||
`are: ${availableScripts.join(', ')}`
|
||||
);
|
||||
}
|
||||
const args = validateExecuteMethodParams(protoArgs, commandMetadata.params);
|
||||
return await this[commandMetadata.command](...args);
|
||||
}
|
||||
}
|
||||
return ExecuteCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').IExecuteCommands} IExecuteCommands
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & import('@appium/types').ISettingsCommands & import('@appium/types').ISessionCommands & IExecuteCommands>} ExecuteBase
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
import _ from 'lodash';
|
||||
import {errors, validateExecuteMethodParams} from '../../protocol';
|
||||
import {
|
||||
Constraints,
|
||||
Driver,
|
||||
DriverClass,
|
||||
DriverCommand,
|
||||
IExecuteCommands,
|
||||
StringRecord,
|
||||
} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
import {BaseDriver} from '../driver';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends IExecuteCommands {}
|
||||
}
|
||||
|
||||
const ExecuteCommands: IExecuteCommands = {
|
||||
async executeMethod<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
script: string,
|
||||
protoArgs: [StringRecord] | any[]
|
||||
) {
|
||||
const Driver = this.constructor as DriverClass<Driver<C>>;
|
||||
const commandMetadata = {...Driver.executeMethodMap?.[script]};
|
||||
if (!commandMetadata.command) {
|
||||
const availableScripts = _.keys(Driver.executeMethodMap);
|
||||
throw new errors.UnsupportedOperationError(
|
||||
`Unsupported execute method '${script}'. Available methods ` +
|
||||
`are: ${availableScripts.join(', ')}`
|
||||
);
|
||||
}
|
||||
const args = validateExecuteMethodParams(protoArgs, commandMetadata.params);
|
||||
const command = this[commandMetadata.command] as DriverCommand;
|
||||
return await command.call(this, ...args);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(ExecuteCommands);
|
||||
@@ -1,108 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
// @ts-check
|
||||
import {errors} from '../../protocol';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./event').EventBase<C>} Base
|
||||
* @returns {FindBase<C>}
|
||||
*/
|
||||
export function FindMixin(Base) {
|
||||
/**
|
||||
* @implements {IFindCommands}
|
||||
*/
|
||||
class FindCommands extends Base {
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<Element>}
|
||||
*/
|
||||
async findElement(strategy, selector) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<Element[]>}
|
||||
*/
|
||||
async findElements(strategy, selector) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<Element>}
|
||||
*/
|
||||
async findElementFromElement(strategy, selector, elementId) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false, elementId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<Element[]>}
|
||||
*/
|
||||
async findElementsFromElement(strategy, selector, elementId) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true, elementId);
|
||||
}
|
||||
/**
|
||||
* Returns an object which adheres to the way the JSON Wire Protocol represents elements:
|
||||
*
|
||||
* Override this for your own driver!
|
||||
* @template {boolean} Mult
|
||||
* @template [Ctx=any]
|
||||
* @param {string} strategy
|
||||
* @param {string} selector
|
||||
* @param {Mult} mult
|
||||
* @param {Ctx} [context]
|
||||
* @returns {Promise<Mult extends true ? Element[] : Element>}
|
||||
*/
|
||||
async findElOrEls(strategy, selector, mult, context) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async getPageSource() {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
}
|
||||
/**
|
||||
* @template {boolean} Mult
|
||||
* @template [Ctx=any]
|
||||
* @param {string} strategy
|
||||
* @param {string} selector
|
||||
* @param {Mult} mult
|
||||
* @param {Ctx} [context]
|
||||
* @returns {Promise<Mult extends true ? Element[] : Element>}
|
||||
*/
|
||||
async findElOrElsWithProcessing(strategy, selector, mult, context) {
|
||||
this.validateLocatorStrategy(strategy);
|
||||
try {
|
||||
return await this.findElOrEls(strategy, selector, mult, context);
|
||||
} catch (err) {
|
||||
if (this.opts.printPageSourceOnFindFailure) {
|
||||
const src = await this.getPageSource();
|
||||
this.log.debug(`Error finding element${mult ? 's' : ''}: ${err.message}`);
|
||||
this.log.debug(`Page source requested through 'printPageSourceOnFindFailure':`);
|
||||
this.log.debug(src);
|
||||
}
|
||||
// still want the error to occur
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FindCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Element} Element
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').IFindCommands} IFindCommands
|
||||
* @typedef {import('@appium/types').ITimeoutCommands} ITimeoutCommands
|
||||
* @typedef {import('@appium/types').IEventCommands} IEventCommands
|
||||
*/
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, ITimeoutCommands & IEventCommands & IFindCommands>} FindBase
|
||||
*/
|
||||
@@ -0,0 +1,79 @@
|
||||
import {Constraints, IFindCommands} from '@appium/types';
|
||||
import {errors} from '../../protocol';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends IFindCommands {}
|
||||
}
|
||||
|
||||
const FindCommands: IFindCommands = {
|
||||
async findElement<C extends Constraints>(this: BaseDriver<C>, strategy, selector) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false);
|
||||
},
|
||||
|
||||
async findElements<C extends Constraints>(this: BaseDriver<C>, strategy, selector) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true);
|
||||
},
|
||||
|
||||
async findElementFromElement<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
elementId: string
|
||||
) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false, elementId);
|
||||
},
|
||||
|
||||
async findElementsFromElement<C extends Constraints>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
elementId: string
|
||||
) {
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true, elementId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object which adheres to the way the JSON Wire Protocol represents elements:
|
||||
*
|
||||
* Override this for your own driver!
|
||||
*/
|
||||
async findElOrEls<C extends Constraints, Mult extends boolean, Ctx = any>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: Mult,
|
||||
context: Ctx
|
||||
) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
},
|
||||
|
||||
async getPageSource<C extends Constraints>(this: BaseDriver<C>) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for find.');
|
||||
},
|
||||
|
||||
async findElOrElsWithProcessing<C extends Constraints, Mult extends boolean, Ctx = any>(
|
||||
this: BaseDriver<C>,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
mult: Mult,
|
||||
context?: Ctx
|
||||
) {
|
||||
this.validateLocatorStrategy(strategy);
|
||||
try {
|
||||
return await this.findElOrEls(strategy, selector, mult, context);
|
||||
} catch (err) {
|
||||
if (this.opts.printPageSourceOnFindFailure) {
|
||||
const src = await this.getPageSource();
|
||||
this.log.debug(`Error finding element${mult ? 's' : ''}: ${err.message}`);
|
||||
this.log.debug(`Page source requested through 'printPageSourceOnFindFailure':`);
|
||||
this.log.debug(src);
|
||||
}
|
||||
// still want the error to occur
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
mixin(FindCommands);
|
||||
@@ -1,35 +0,0 @@
|
||||
// @ts-check
|
||||
import _ from 'lodash';
|
||||
import {EventMixin} from './event';
|
||||
import {FindMixin} from './find';
|
||||
import {LogMixin} from './log';
|
||||
import {SessionMixin} from './session';
|
||||
import {SettingsMixin} from './settings';
|
||||
import {TimeoutMixin} from './timeout';
|
||||
import {ExecuteMixin} from './execute';
|
||||
|
||||
/**
|
||||
* Applies all the mixins to the `BaseDriverBase` class; returns a `BaseDriver` class definition.
|
||||
* Each mixin is applied in the order it is listed here, and each type is a union with the previous.
|
||||
*
|
||||
* @template {Constraints} C
|
||||
* @param {BaseDriverBase<C>} Base
|
||||
*/
|
||||
export const createBaseDriverClass = _.flow(
|
||||
TimeoutMixin,
|
||||
EventMixin,
|
||||
FindMixin,
|
||||
LogMixin,
|
||||
SettingsMixin,
|
||||
SessionMixin,
|
||||
ExecuteMixin
|
||||
);
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C>} BaseDriverBase
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './event';
|
||||
export * from './find';
|
||||
export * from './log';
|
||||
export * from './session';
|
||||
export * from './settings';
|
||||
export * from './timeout';
|
||||
export * from './execute';
|
||||
@@ -1,64 +0,0 @@
|
||||
/* eslint-disable require-await */
|
||||
// @ts-check
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./find').FindBase<C>} Base
|
||||
* @returns {LogBase<C>}
|
||||
*/
|
||||
export function LogMixin(Base) {
|
||||
/**
|
||||
* @implements {ILogCommands<C>}
|
||||
*/
|
||||
class LogCommands extends Base {
|
||||
/** @type {Readonly<import('@appium/types').LogDefRecord<C>>} */
|
||||
supportedLogTypes;
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.supportedLogTypes ??= {};
|
||||
}
|
||||
|
||||
async getLogTypes() {
|
||||
this.log.debug('Retrieving supported log types');
|
||||
return Object.keys(this.supportedLogTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {import('@appium/types').Driver<C>}
|
||||
* @param {keyof typeof this.supportedLogTypes} logType
|
||||
* @returns {Promise<import('type-fest').AsyncReturnType<typeof this.supportedLogTypes[keyof typeof this.supportedLogTypes]['getter']>>}
|
||||
*/
|
||||
async getLog(logType) {
|
||||
this.log.debug(`Retrieving '${String(logType)}' logs`);
|
||||
|
||||
if (!(logType in this.supportedLogTypes)) {
|
||||
const logsTypesWithDescriptions = _.mapValues(this.supportedLogTypes, 'description');
|
||||
throw new Error(
|
||||
`Unsupported log type '${String(logType)}'. ` +
|
||||
`Supported types: ${JSON.stringify(logsTypesWithDescriptions)}`
|
||||
);
|
||||
}
|
||||
|
||||
return await this.supportedLogTypes[logType].getter(this);
|
||||
}
|
||||
}
|
||||
return LogCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ILogCommands<C>} ILogCommands
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & ILogCommands<C>>} LogBase
|
||||
*/
|
||||
@@ -0,0 +1,36 @@
|
||||
import {Constraints, Driver, ILogCommands, LogDefRecord} from '@appium/types';
|
||||
import _ from 'lodash';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends ILogCommands {}
|
||||
}
|
||||
|
||||
const LogCommands: ILogCommands = {
|
||||
supportedLogTypes: <LogDefRecord>{},
|
||||
|
||||
async getLogTypes<C extends Constraints>(this: BaseDriver<C>) {
|
||||
this.log.debug('Retrieving supported log types');
|
||||
return Object.keys(this.supportedLogTypes);
|
||||
},
|
||||
|
||||
async getLog<C extends Constraints>(
|
||||
this: Driver<C>,
|
||||
logType: keyof ILogCommands['supportedLogTypes']
|
||||
) {
|
||||
this.log.debug(`Retrieving '${String(logType)}' logs`);
|
||||
|
||||
if (!(logType in this.supportedLogTypes)) {
|
||||
const logsTypesWithDescriptions = _.mapValues(this.supportedLogTypes, 'description');
|
||||
throw new Error(
|
||||
`Unsupported log type '${String(logType)}'. ` +
|
||||
`Supported types: ${JSON.stringify(logsTypesWithDescriptions)}`
|
||||
);
|
||||
}
|
||||
|
||||
return await this.supportedLogTypes[logType].getter(this);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(LogCommands);
|
||||
@@ -0,0 +1,14 @@
|
||||
import {Constraints} from '@appium/types';
|
||||
import {BaseDriver} from '../driver';
|
||||
|
||||
/**
|
||||
* This function assigns a mixin `T` to the `FakeDriver` class' prototype.
|
||||
* While each mixin has its own interface which is (in isolation) unrelated to `FakeDriver`, the constraint
|
||||
* on this generic type `T` is that it must be a partial of `FakeDriver`'s interface. This enforces
|
||||
* that it does not conflict with the existing interface of `FakeDriver`. In that way, you can
|
||||
* think of it as a type guard.
|
||||
* @param mixin Mixin implementation
|
||||
*/
|
||||
export function mixin<C extends Constraints, T extends Partial<BaseDriver<C>>>(mixin: T): void {
|
||||
Object.assign(BaseDriver.prototype, mixin);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
// @ts-check
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./settings').SettingsBase<C>} Base
|
||||
* @returns {SessionBase<C>}
|
||||
*/
|
||||
export function SessionMixin(Base) {
|
||||
/**
|
||||
* @implements {ISessionCommands}
|
||||
*/
|
||||
class SessionCommands extends Base {
|
||||
/**
|
||||
* @returns {Promise<MultiSessionData[]>}
|
||||
*/
|
||||
async getSessions() {
|
||||
let ret = [];
|
||||
|
||||
if (this.sessionId) {
|
||||
ret.push({
|
||||
id: this.sessionId,
|
||||
capabilities: this.caps,
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
* @returns {Promise<SingularSessionData>}
|
||||
*/
|
||||
async getSession() {
|
||||
if (this.caps.eventTimings) {
|
||||
return {...this.caps, events: this.eventHistory};
|
||||
}
|
||||
return this.caps;
|
||||
}
|
||||
}
|
||||
|
||||
return SessionCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ISessionCommands} ISessionCommands
|
||||
* @typedef {import('@appium/types').SingularSessionData} SingularSessionData
|
||||
* @typedef {import('@appium/types').MultiSessionData} MultiSessionData
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & import('@appium/types').ISettingsCommands & ISessionCommands>} SessionBase
|
||||
*/
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Constraints, ISessionCommands, MultiSessionData} from '@appium/types';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends ISessionCommands {}
|
||||
}
|
||||
|
||||
const SessionCommands: ISessionCommands = {
|
||||
async getSessions<C extends Constraints>(this: BaseDriver<C>) {
|
||||
const ret: MultiSessionData[] = [];
|
||||
|
||||
if (this.sessionId) {
|
||||
ret.push({
|
||||
id: this.sessionId,
|
||||
capabilities: this.caps,
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns capabilities for the session and event history (if applicable)
|
||||
*/
|
||||
async getSession<C extends Constraints>(this: BaseDriver<C>) {
|
||||
if (this.caps.eventTimings) {
|
||||
return {...this.caps, events: this.eventHistory};
|
||||
}
|
||||
return this.caps;
|
||||
},
|
||||
};
|
||||
|
||||
mixin(SessionCommands);
|
||||
@@ -1,38 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('./log').LogBase<C>} Base
|
||||
* @returns {SettingsBase<C>}
|
||||
*/
|
||||
export function SettingsMixin(Base) {
|
||||
/**
|
||||
* @implements {ISettingsCommands}
|
||||
*/
|
||||
class SettingsCommands extends Base {
|
||||
async updateSettings(newSettings) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('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');
|
||||
}
|
||||
return await this.settings.getSettings();
|
||||
}
|
||||
}
|
||||
|
||||
return SettingsCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').ISettingsCommands} ISettingsCommands
|
||||
*/
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, import('@appium/types').ITimeoutCommands & import('@appium/types').IEventCommands & import('@appium/types').IFindCommands & import('@appium/types').ILogCommands<C> & ISettingsCommands>} SettingsBase
|
||||
*/
|
||||
@@ -0,0 +1,25 @@
|
||||
import {BaseDriver} from '../driver';
|
||||
import {Constraints, ISettingsCommands, StringRecord} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends ISettingsCommands {}
|
||||
}
|
||||
|
||||
const SettingsCommands: ISettingsCommands = {
|
||||
async updateSettings<C extends Constraints>(this: BaseDriver<C>, newSettings: StringRecord) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
||||
}
|
||||
return await this.settings.update(newSettings);
|
||||
},
|
||||
|
||||
async getSettings<C extends Constraints>(this: BaseDriver<C>) {
|
||||
if (!this.settings) {
|
||||
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
||||
}
|
||||
return this.settings.getSettings();
|
||||
},
|
||||
};
|
||||
|
||||
mixin(SettingsCommands);
|
||||
@@ -1,168 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
import {waitForCondition} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {util} from '@appium/support';
|
||||
import {errors} from '../../protocol';
|
||||
|
||||
const MIN_TIMEOUT = 0;
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @param {import('../driver').BaseDriverBase<C>} Base
|
||||
* @returns {TimeoutBase<C>}
|
||||
*/
|
||||
export function TimeoutMixin(Base) {
|
||||
/**
|
||||
* @implements {ITimeoutCommands}
|
||||
*/
|
||||
class TimeoutCommands extends Base {
|
||||
async timeouts(type, ms, script, pageLoad, implicit) {
|
||||
if (util.hasValue(type) && util.hasValue(ms)) {
|
||||
this.log.debug(`MJSONWP timeout arguments: ${JSON.stringify({type, ms})}}`);
|
||||
|
||||
switch (type) {
|
||||
case 'command':
|
||||
await this.newCommandTimeout(ms);
|
||||
return;
|
||||
case 'implicit':
|
||||
await this.implicitWaitMJSONWP(ms);
|
||||
return;
|
||||
case 'page load':
|
||||
await this.pageLoadTimeoutMJSONWP(ms);
|
||||
return;
|
||||
case 'script':
|
||||
await this.scriptTimeoutMJSONWP(ms);
|
||||
return;
|
||||
default:
|
||||
throw new Error(`'${type}' type is not supported for MJSONWP timeout`);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise assume it is W3C protocol
|
||||
this.log.debug(
|
||||
`W3C timeout argument: ${JSON.stringify({
|
||||
script,
|
||||
pageLoad,
|
||||
implicit,
|
||||
})}}`
|
||||
);
|
||||
if (util.hasValue(script)) {
|
||||
await this.scriptTimeoutW3C(script);
|
||||
}
|
||||
if (util.hasValue(pageLoad)) {
|
||||
await this.pageLoadTimeoutW3C(pageLoad);
|
||||
}
|
||||
if (util.hasValue(implicit)) {
|
||||
await this.implicitWaitW3C(implicit);
|
||||
}
|
||||
}
|
||||
|
||||
async getTimeouts() {
|
||||
return {
|
||||
command: this.newCommandTimeoutMs,
|
||||
implicit: this.implicitWaitMs,
|
||||
};
|
||||
}
|
||||
|
||||
// implicit
|
||||
async implicitWaitW3C(ms) {
|
||||
await this.implicitWait(ms);
|
||||
}
|
||||
|
||||
async implicitWaitMJSONWP(ms) {
|
||||
await this.implicitWait(ms);
|
||||
}
|
||||
|
||||
async implicitWait(ms) {
|
||||
await this.setImplicitWait(this.parseTimeoutArgument(ms));
|
||||
}
|
||||
|
||||
// pageLoad
|
||||
async pageLoadTimeoutW3C(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
}
|
||||
|
||||
async pageLoadTimeoutMJSONWP(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
}
|
||||
|
||||
// script
|
||||
async scriptTimeoutW3C(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
}
|
||||
|
||||
async scriptTimeoutMJSONWP(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
}
|
||||
|
||||
// command
|
||||
async newCommandTimeout(ms) {
|
||||
this.setNewCommandTimeout(this.parseTimeoutArgument(ms));
|
||||
}
|
||||
|
||||
setImplicitWait(ms) {
|
||||
// eslint-disable-line require-await
|
||||
this.implicitWaitMs = ms;
|
||||
this.log.debug(`Set implicit wait to ${ms}ms`);
|
||||
if (this.managedDrivers && this.managedDrivers.length) {
|
||||
this.log.debug('Setting implicit wait on managed drivers');
|
||||
for (let driver of this.managedDrivers) {
|
||||
if (_.isFunction(driver.setImplicitWait)) {
|
||||
driver.setImplicitWait(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNewCommandTimeout(ms) {
|
||||
this.newCommandTimeoutMs = ms;
|
||||
this.log.debug(`Set new command timeout to ${ms}ms`);
|
||||
if (this.managedDrivers && this.managedDrivers.length) {
|
||||
this.log.debug('Setting new command timeout on managed drivers');
|
||||
for (let driver of this.managedDrivers) {
|
||||
if (_.isFunction(driver.setNewCommandTimeout)) {
|
||||
driver.setNewCommandTimeout(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async implicitWaitForCondition(condFn) {
|
||||
this.log.debug(`Waiting up to ${this.implicitWaitMs} ms for condition`);
|
||||
let wrappedCondFn = async (...args) => {
|
||||
// reset command timeout
|
||||
await this.clearNewCommandTimeout();
|
||||
|
||||
return await condFn(...args);
|
||||
};
|
||||
return await waitForCondition(wrappedCondFn, {
|
||||
waitMs: this.implicitWaitMs,
|
||||
intervalMs: 500,
|
||||
logger: this.log,
|
||||
});
|
||||
}
|
||||
|
||||
parseTimeoutArgument(ms) {
|
||||
let duration = parseInt(ms, 10);
|
||||
if (_.isNaN(duration) || duration < MIN_TIMEOUT) {
|
||||
throw new errors.UnknownError(`Invalid timeout value '${ms}'`);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
||||
return TimeoutCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').ITimeoutCommands} ITimeoutCommands
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('../driver').BaseDriverBase<C, ITimeoutCommands>} TimeoutBase
|
||||
*/
|
||||
@@ -0,0 +1,155 @@
|
||||
// @ts-check
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable require-await */
|
||||
import {waitForCondition} from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import {util} from '@appium/support';
|
||||
import {errors} from '../../protocol';
|
||||
import {BaseDriver} from '../driver';
|
||||
import {Constraints, ITimeoutCommands} from '@appium/types';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
declare module '../driver' {
|
||||
interface BaseDriver<C extends Constraints> extends ITimeoutCommands {}
|
||||
}
|
||||
|
||||
const MIN_TIMEOUT = 0;
|
||||
|
||||
const TimeoutCommands: ITimeoutCommands = {
|
||||
async timeouts(type, ms, script, pageLoad, implicit) {
|
||||
if (util.hasValue(type) && util.hasValue(ms)) {
|
||||
this.log.debug(`MJSONWP timeout arguments: ${JSON.stringify({type, ms})}}`);
|
||||
|
||||
switch (type) {
|
||||
case 'command':
|
||||
await this.newCommandTimeout(ms);
|
||||
return;
|
||||
case 'implicit':
|
||||
await this.implicitWaitMJSONWP(ms);
|
||||
return;
|
||||
case 'page load':
|
||||
await this.pageLoadTimeoutMJSONWP(ms);
|
||||
return;
|
||||
case 'script':
|
||||
await this.scriptTimeoutMJSONWP(ms);
|
||||
return;
|
||||
default:
|
||||
throw new Error(`'${type}' type is not supported for MJSONWP timeout`);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise assume it is W3C protocol
|
||||
this.log.debug(
|
||||
`W3C timeout argument: ${JSON.stringify({
|
||||
script,
|
||||
pageLoad,
|
||||
implicit,
|
||||
})}}`
|
||||
);
|
||||
if (util.hasValue(script)) {
|
||||
await this.scriptTimeoutW3C(script);
|
||||
}
|
||||
if (util.hasValue(pageLoad)) {
|
||||
await this.pageLoadTimeoutW3C(pageLoad);
|
||||
}
|
||||
if (util.hasValue(implicit)) {
|
||||
await this.implicitWaitW3C(implicit);
|
||||
}
|
||||
},
|
||||
|
||||
async getTimeouts() {
|
||||
return {
|
||||
command: this.newCommandTimeoutMs,
|
||||
implicit: this.implicitWaitMs,
|
||||
};
|
||||
},
|
||||
|
||||
// implicit
|
||||
async implicitWaitW3C(ms) {
|
||||
await this.implicitWait(ms);
|
||||
},
|
||||
|
||||
async implicitWaitMJSONWP(ms) {
|
||||
await this.implicitWait(ms);
|
||||
},
|
||||
|
||||
async implicitWait(ms) {
|
||||
await this.setImplicitWait(this.parseTimeoutArgument(ms));
|
||||
},
|
||||
|
||||
// pageLoad
|
||||
async pageLoadTimeoutW3C(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
},
|
||||
|
||||
async pageLoadTimeoutMJSONWP(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for pageLoad.');
|
||||
},
|
||||
|
||||
// script
|
||||
async scriptTimeoutW3C(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
},
|
||||
|
||||
async scriptTimeoutMJSONWP(ms) {
|
||||
throw new errors.NotImplementedError('Not implemented yet for script.');
|
||||
},
|
||||
|
||||
// command
|
||||
async newCommandTimeout(ms) {
|
||||
this.setNewCommandTimeout(this.parseTimeoutArgument(ms));
|
||||
},
|
||||
|
||||
setImplicitWait(ms) {
|
||||
// eslint-disable-line require-await
|
||||
this.implicitWaitMs = ms;
|
||||
this.log.debug(`Set implicit wait to ${ms}ms`);
|
||||
if (this.managedDrivers && this.managedDrivers.length) {
|
||||
this.log.debug('Setting implicit wait on managed drivers');
|
||||
for (const driver of this.managedDrivers) {
|
||||
if (_.isFunction(driver.setImplicitWait)) {
|
||||
driver.setImplicitWait(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setNewCommandTimeout(ms) {
|
||||
this.newCommandTimeoutMs = ms;
|
||||
this.log.debug(`Set new command timeout to ${ms}ms`);
|
||||
if (this.managedDrivers && this.managedDrivers.length) {
|
||||
this.log.debug('Setting new command timeout on managed drivers');
|
||||
for (const driver of this.managedDrivers) {
|
||||
if (_.isFunction(driver.setNewCommandTimeout)) {
|
||||
driver.setNewCommandTimeout(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async implicitWaitForCondition(condFn) {
|
||||
this.log.debug(`Waiting up to ${this.implicitWaitMs} ms for condition`);
|
||||
const wrappedCondFn = async (...args: any[]) => {
|
||||
// reset command timeout
|
||||
await this.clearNewCommandTimeout();
|
||||
|
||||
return await condFn(...args);
|
||||
};
|
||||
return await waitForCondition(wrappedCondFn, {
|
||||
waitMs: this.implicitWaitMs,
|
||||
intervalMs: 500,
|
||||
logger: this.log,
|
||||
});
|
||||
},
|
||||
|
||||
parseTimeoutArgument<C extends Constraints>(this: BaseDriver<C>, ms: number | string) {
|
||||
const duration = parseInt(String(ms), 10);
|
||||
if (_.isNaN(duration) || duration < MIN_TIMEOUT) {
|
||||
throw new errors.UnknownError(`Invalid timeout value '${ms}'`);
|
||||
}
|
||||
return duration;
|
||||
},
|
||||
};
|
||||
|
||||
mixin(TimeoutCommands);
|
||||
@@ -29,9 +29,6 @@ class DriverCore {
|
||||
*/
|
||||
static baseVersion = BASEDRIVER_VER;
|
||||
|
||||
/** @type {import('@appium/types').ExecuteMethodMap<DriverCore>} */
|
||||
static executeMethodMap = {};
|
||||
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,6 @@ import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
||||
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
||||
import {createBaseDriverClass} from './commands';
|
||||
import helpers from './helpers';
|
||||
import {BASE_DESIRED_CAP_CONSTRAINTS} from '@appium/types';
|
||||
|
||||
@@ -22,9 +21,10 @@ const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
||||
* @implements {SessionHandler<C>}
|
||||
* @template {Constraints} C
|
||||
* @template {StringRecord} [CArgs=StringRecord]
|
||||
* @implements {Driver<C, CArgs>}
|
||||
* @extends {DriverCore<C>}
|
||||
*/
|
||||
export class BaseDriverCore extends DriverCore {
|
||||
export class BaseDriver extends DriverCore {
|
||||
/**
|
||||
* @type {CArgs & ServerArgs}
|
||||
*/
|
||||
@@ -59,7 +59,7 @@ export class BaseDriverCore extends DriverCore {
|
||||
super(opts, shouldValidateCaps);
|
||||
|
||||
/**
|
||||
* This must be assigned here because the declaration of {@linkcode BaseDriverCore.opts} above
|
||||
* This must be assigned here because the declaration of {@linkcode BaseDriver.opts} above
|
||||
* blows away {@linkcode DriverCore.opts}.
|
||||
*/
|
||||
this.opts = opts;
|
||||
@@ -407,12 +407,9 @@ export class BaseDriverCore extends DriverCore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that all of the mixins correctly implement the interface described in {@linkcode Driver}.
|
||||
* @template {Constraints} [C={}]
|
||||
* @implements {Driver<C>}
|
||||
*/
|
||||
export class BaseDriver extends createBaseDriverClass(BaseDriverCore) {}
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from './commands';
|
||||
|
||||
export default BaseDriver;
|
||||
|
||||
/**
|
||||
@@ -433,11 +430,11 @@ export default BaseDriver;
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is used to extend {@linkcode BaseDriverCore} by the mixins and also external drivers.
|
||||
* This is used to extend {@linkcode BaseDriver} by the mixins and also external drivers.
|
||||
* @template {Constraints} C
|
||||
* @template [Proto={}]
|
||||
* @template [Static={}]
|
||||
* @typedef {import('@appium/types').Class<BaseDriverCore<C> & Proto,import('@appium/types').DriverStatic & Static>} BaseDriverBase
|
||||
* @typedef {import('@appium/types').Class<BaseDriver<C> & Proto,import('@appium/types').DriverStatic & Static>} BaseDriverBase
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.3.2",
|
||||
"version": "9.3.3",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,8 +43,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/types": "^0.10.1",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.4.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
@@ -60,12 +60,12 @@
|
||||
"express": "4.18.2",
|
||||
"http-status-codes": "2.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.17.0",
|
||||
"lru-cache": "7.18.3",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.6.0",
|
||||
"type-fest": "3.6.1",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
import {LogMixin} from '../../../../lib/basedriver/commands/log';
|
||||
import {createSandbox} from 'sinon';
|
||||
import _ from 'lodash';
|
||||
import BaseDriver from '../../../../lib';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
@@ -20,25 +20,15 @@ const SUPPORTED_LOG_TYPES = {
|
||||
|
||||
describe('log commands -', function () {
|
||||
let sandbox;
|
||||
/** @type {ReturnType<typeof LogMixin>} */
|
||||
let LogCommands;
|
||||
|
||||
let logCommands;
|
||||
let driver;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
// @ts-expect-error
|
||||
LogCommands = LogMixin(
|
||||
class {
|
||||
get log() {
|
||||
return this._log;
|
||||
}
|
||||
}
|
||||
);
|
||||
logCommands = new LogCommands();
|
||||
driver = new BaseDriver();
|
||||
// reset the supported log types
|
||||
logCommands.supportedLogTypes = {};
|
||||
logCommands._log = /** @type {import('@appium/types').AppiumLogger} */ ({
|
||||
driver.supportedLogTypes = {};
|
||||
driver._log = /** @type {import('@appium/types').AppiumLogger} */ ({
|
||||
debug: _.noop,
|
||||
});
|
||||
});
|
||||
@@ -49,11 +39,11 @@ describe('log commands -', function () {
|
||||
|
||||
describe('getLogTypes', function () {
|
||||
it('should return empty array when no supported log types', async function () {
|
||||
(await logCommands.getLogTypes()).should.eql([]);
|
||||
(await driver.getLogTypes()).should.eql([]);
|
||||
});
|
||||
it('should return keys to log type object', async function () {
|
||||
logCommands.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
(await logCommands.getLogTypes()).should.eql(['one', 'two']);
|
||||
driver.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
(await driver.getLogTypes()).should.eql(['one', 'two']);
|
||||
});
|
||||
});
|
||||
describe('getLog', function () {
|
||||
@@ -66,15 +56,15 @@ describe('log commands -', function () {
|
||||
two = sandbox.spy(SUPPORTED_LOG_TYPES.two, 'getter');
|
||||
});
|
||||
it('should throw error if log type not supported', async function () {
|
||||
await logCommands.getLog('one').should.eventually.be.rejected;
|
||||
await driver.getLog('one').should.eventually.be.rejected;
|
||||
one.called.should.be.false;
|
||||
two.called.should.be.false;
|
||||
});
|
||||
it('should throw an error with available log types if log type not supported', async function () {
|
||||
logCommands.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
driver.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
let err;
|
||||
try {
|
||||
await logCommands.getLog('three');
|
||||
await driver.getLog('three');
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
@@ -86,8 +76,8 @@ describe('log commands -', function () {
|
||||
two.called.should.be.false;
|
||||
});
|
||||
it('should call getter on appropriate log when found', async function () {
|
||||
logCommands.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
let logs = await logCommands.getLog('one');
|
||||
driver.supportedLogTypes = SUPPORTED_LOG_TYPES;
|
||||
let logs = await driver.getLog('one');
|
||||
logs.should.eql(FIRST_LOGS);
|
||||
one.called.should.be.true;
|
||||
two.called.should.be.false;
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.3](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.2...@appium/base-plugin@2.2.3) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.2](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.1...@appium/base-plugin@2.2.2) (2023-02-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
@@ -56,26 +56,26 @@ class BasePlugin {
|
||||
* `next` and `driver` objects since naturally we'd want to make sure to trigger the driver's own
|
||||
* `executeMethod` call if an execute method is not found on the plugin itself.
|
||||
*
|
||||
* @template {Driver} D
|
||||
* @template {Constraints} C
|
||||
* @param {NextPluginCallback} next
|
||||
* @param {D} driver
|
||||
* @param {Driver<C>} driver
|
||||
* @param {string} script
|
||||
* @param {[Record<string, any>]|[]} protoArgs
|
||||
* @this {Plugin}
|
||||
*/
|
||||
async executeMethod(next, driver, script, protoArgs) {
|
||||
const Plugin = /** @type {PluginClass} */ (this.constructor);
|
||||
const Plugin = /** @type {import('@appium/types').PluginClass<Plugin>} */ (this.constructor);
|
||||
const commandMetadata = {...Plugin.executeMethodMap?.[script]};
|
||||
|
||||
/** @type {import('@appium/types').PluginCommand<D>|undefined} */
|
||||
let command;
|
||||
|
||||
if (!commandMetadata.command || !(command = this[commandMetadata.command])) {
|
||||
if (!commandMetadata.command || !(commandMetadata.command in this)) {
|
||||
this.logger.info(
|
||||
`Plugin did not know how to handle method '${script}'. Passing control to next`
|
||||
);
|
||||
return await next();
|
||||
}
|
||||
|
||||
const command = /** @type {import('@appium/types').PluginCommand<Driver<C>>} */ (
|
||||
this[commandMetadata.command]
|
||||
);
|
||||
const args = validateExecuteMethodParams(protoArgs, commandMetadata.params);
|
||||
return await command.call(this, next, driver, ...args);
|
||||
}
|
||||
@@ -87,6 +87,10 @@ export {BasePlugin};
|
||||
/**
|
||||
* @typedef {import('@appium/types').Plugin} Plugin
|
||||
* @typedef {import('@appium/types').NextPluginCallback} NextPluginCallback
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').PluginClass} PluginClass
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Driver<C>} Driver
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"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.3.2",
|
||||
"@appium/support": "^3.1.6"
|
||||
"@appium/base-driver": "^9.3.3",
|
||||
"@appium/support": "^3.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.9](https://github.com/appium/appium/compare/@appium/doctor@2.0.8...@appium/doctor@2.0.9) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.8](https://github.com/appium/appium/compare/@appium/doctor@2.0.7...@appium/doctor@2.0.8) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,7 +44,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/support": "^3.1.7",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/teen_process": "2.0.0",
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.3.0](https://github.com/appium/appium/compare/@appium/docutils@0.2.2...@appium/docutils@0.3.0) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency lilconfig to v2.1.0 ([4ed745a](https://github.com/appium/appium/commit/4ed745a95ffc6a43f76003eba62019c16a4c6cf2))
|
||||
* **docutils:** update dependency typedoc to v0.23.26 ([431092d](https://github.com/appium/appium/commit/431092dc87c3146bfd3cd274ae5c279b7582ff55))
|
||||
* **docutils:** use material theme ([189e39d](https://github.com/appium/appium/commit/189e39d03c16551272d6c489aedc0d9fbb08a3a9))
|
||||
* **types:** update dependency type-fest to v3.6.1 ([471a4b5](https://github.com/appium/appium/commit/471a4b57e622ff077d59f577a78341268700c48d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **docutils:** add --upgrade flag to init ([4cfad00](https://github.com/appium/appium/commit/4cfad004dbdff4ae04ed7c87f23a3c9e913e4d4e))
|
||||
* **docutils:** allow "%s" as version number in commit msg arg ([f80f4cb](https://github.com/appium/appium/commit/f80f4cb2bca40347913a6ce39e3de58b485492a8))
|
||||
* **docutils:** better path validation ([b30b2cf](https://github.com/appium/appium/commit/b30b2cfb84b0f208d932d60082847d0d7323e108))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/appium/appium/compare/@appium/docutils@0.2.1...@appium/docutils@0.2.2) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {exec, SubProcess, TeenProcessExecOptions} from 'teen_process';
|
||||
import {exec, SubProcess, SubProcessOptions, TeenProcessExecOptions} from 'teen_process';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
DEFAULT_DEPLOY_BRANCH,
|
||||
@@ -35,8 +35,9 @@ async function doServe(
|
||||
) {
|
||||
mikePath = mikePath ?? (await whichMike());
|
||||
const finalArgs = ['serve', ...args];
|
||||
log.debug('Launching %s with args: %O', mikePath, finalArgs);
|
||||
const proc = new SubProcess(mikePath, finalArgs);
|
||||
const opts: SubProcessOptions = {stdio: 'inherit'};
|
||||
log.debug('Launching %s with args: %s', mikePath, finalArgs);
|
||||
const proc = new SubProcess(mikePath, finalArgs, opts);
|
||||
return await proc.start(startDetector, detach, timeoutMs);
|
||||
}
|
||||
|
||||
@@ -99,6 +100,10 @@ export async function deploy({
|
||||
);
|
||||
}
|
||||
version = version ?? (await findDeployVersion(packageJsonPath, cwd));
|
||||
|
||||
// substitute %s in message with version
|
||||
message = message?.replace('%s', version);
|
||||
|
||||
const mikeOpts = {
|
||||
'config-file': mkDocsYmlPath,
|
||||
push,
|
||||
@@ -118,6 +123,7 @@ export async function deploy({
|
||||
if (alias) {
|
||||
mikeArgs.push(alias);
|
||||
}
|
||||
stop(); // discard
|
||||
// unsure about how SIGHUP is handled here
|
||||
await doServe(mikeArgs, serveOpts);
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Exports APIs used by build-related commands
|
||||
* @module
|
||||
*/
|
||||
|
||||
export * from './deploy';
|
||||
export * from './site';
|
||||
export * from './reference';
|
||||
|
||||
@@ -197,7 +197,7 @@ async function getNavItemsForDir(
|
||||
const rootHeaderKeypath = getRootHeaderKeypath(navHeaderItems, navHeader);
|
||||
|
||||
for (const fileOrUrl of referenceOutputFilepaths) {
|
||||
let offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
|
||||
const offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
|
||||
const newOffset = offset >= 0 ? offset : navHeaderItems.length;
|
||||
const data = navHeaderItems[offset];
|
||||
log.warn(rootHeaderKeypath, newOffset, data);
|
||||
@@ -214,7 +214,7 @@ async function getNavItemsForDir(
|
||||
}
|
||||
} else {
|
||||
log.debug('No items found in header %s', navHeader);
|
||||
let navOffset = nav.length;
|
||||
const navOffset = nav.length;
|
||||
for (const [idx, newRefFilepath] of referenceOutputFilepaths.entries()) {
|
||||
newNavHeaderItems.push({
|
||||
keypath: `${navOffset}.${navHeader}.${idx}`,
|
||||
@@ -254,7 +254,6 @@ export async function updateNav({
|
||||
mkdocsYml: mkDocsYmlPath,
|
||||
typedocJson: typeDocJsonPath,
|
||||
all = false,
|
||||
dryRun = false,
|
||||
}: UpdateNavOpts = {}) {
|
||||
// we need `mkdocs.yml` to update
|
||||
// and we need `typedoc.json` to know where to look for the command docs
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import glob from 'glob';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import {Application, ArgumentsReader, TypeDocOptions, TypeDocReader} from 'typedoc';
|
||||
@@ -16,28 +15,12 @@ import {
|
||||
NAME_TYPEDOC_JSON,
|
||||
} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {findPkgDir, findTypeDocJsonPath, readTypedocJson} from '../fs';
|
||||
import {findTypeDocJsonPath, readTypedocJson} from '../fs';
|
||||
import logger from '../logger';
|
||||
import {argify, relative, stopwatch} from '../util';
|
||||
|
||||
const log = logger.withTag('builder:reference');
|
||||
|
||||
/**
|
||||
* Replaces TypeDoc's homebrew "glob" implementation with a real one
|
||||
*
|
||||
* This cannot be done via `require('typedoc')` or `import` due to the file being excluded
|
||||
* from the export map in its `package.json`.
|
||||
* @see https://github.com/TypeStrong/typedoc/issues/2151
|
||||
*/
|
||||
const monkeyPatchTypedoc = _.once(async () => {
|
||||
const typedocDir = await findPkgDir(require.resolve('typedoc'));
|
||||
if (!typedocDir) {
|
||||
throw new DocutilsError('Could not find TypeDoc package directory; is it installed?');
|
||||
}
|
||||
const tdFs = require(path.join(typedocDir, 'dist', 'lib', 'utils', 'fs.js'));
|
||||
tdFs.glob = glob.sync;
|
||||
});
|
||||
|
||||
/**
|
||||
* Executes TypeDoc _in the current process_
|
||||
*
|
||||
@@ -48,9 +31,6 @@ const monkeyPatchTypedoc = _.once(async () => {
|
||||
* @param opts - TypeDoc options
|
||||
*/
|
||||
export async function runTypedoc(typeDocJsonPath: string, opts: Record<string, string>) {
|
||||
await monkeyPatchTypedoc();
|
||||
log.debug('Monkeypatched TypeDoc');
|
||||
|
||||
const args = argify(opts);
|
||||
log.debug('TypeDoc args:', args);
|
||||
const app = new Application();
|
||||
@@ -179,7 +159,7 @@ export async function buildReferenceDocs({
|
||||
|
||||
try {
|
||||
await runTypedoc(typeDocJsonPath, extraTypedocOpts);
|
||||
let finalOut = (typeDocJson.out ?? out) as string;
|
||||
const finalOut = (typeDocJson.out ?? out) as string;
|
||||
log.success(
|
||||
'Reference docs built at %s (%dms)',
|
||||
path.isAbsolute(finalOut) ? relativePath(finalOut) : finalOut,
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
import {exec, SubProcess, TeenProcessExecOptions} from 'teen_process';
|
||||
import {NAME_BIN, NAME_MKDOCS, NAME_MKDOCS_YML} from '../constants';
|
||||
import {exec, SubProcess, SubProcessOptions, TeenProcessExecOptions} from 'teen_process';
|
||||
import {NAME_BIN, NAME_THEME, NAME_MKDOCS_YML} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {findMkDocsYml, readMkDocsYml, whichMkDocs} from '../fs';
|
||||
import logger from '../logger';
|
||||
@@ -28,8 +28,9 @@ async function doServe(
|
||||
) {
|
||||
mkDocsPath = mkDocsPath ?? (await whichMkDocs());
|
||||
const finalArgs = ['serve', ...args];
|
||||
log.debug('Launching %s with args: %O', mkDocsPath, finalArgs);
|
||||
const proc = new SubProcess(mkDocsPath, finalArgs);
|
||||
const opts: SubProcessOptions = {stdio: 'inherit'};
|
||||
log.debug('Launching %s with args: %s', mkDocsPath, finalArgs);
|
||||
const proc = new SubProcess(mkDocsPath, finalArgs, opts);
|
||||
return await proc.start(startDetector, detach, timeoutMs);
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ async function doBuild(
|
||||
export async function buildSite({
|
||||
mkdocsYml: mkDocsYmlPath,
|
||||
siteDir,
|
||||
theme = NAME_MKDOCS,
|
||||
theme = NAME_THEME,
|
||||
cwd = process.cwd(),
|
||||
serve = false,
|
||||
serveOpts,
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Provides functions to validate user-provided options as part of Yargs' post-parsing "check" phase
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {fs, util} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import type {Options} from 'yargs';
|
||||
import logger from '../logger';
|
||||
|
||||
const log = logger.withTag('check');
|
||||
|
||||
/**
|
||||
* Given a list of objects with `id` and `path` props, filters out the ones that do not exist
|
||||
* @param paths Filepaths
|
||||
* @returns Missing files
|
||||
*/
|
||||
async function filterMissing(paths: MissingFileData[]): Promise<MissingFileData[]> {
|
||||
const exists = await Promise.all(
|
||||
paths.map(async ({id, path}) => ({id, path, exists: await fs.exists(path)}))
|
||||
);
|
||||
const results = _.reject(exists, 'exists');
|
||||
return _.map(results, (result) => _.omit(result, 'exists'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data structure describing a missing file; returned by {@linkcode filterMissing}
|
||||
*/
|
||||
interface MissingFileData {
|
||||
/**
|
||||
* Arbitrary identifier; intent was to map to an option name
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Normalized filepath
|
||||
*/
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes user-provided paths and checks for existence.
|
||||
*
|
||||
* Filters options to consider based on group name only.
|
||||
*
|
||||
* Meant to be used as a "fail-fast" strategy on the CLI, so we don't go all the way through some
|
||||
* expensive behavior before realizing we're missing a path.
|
||||
* @param opts Options object for a yargs command
|
||||
* @param group Group name to filter on
|
||||
* @param argv User-provided args
|
||||
* @returns `true` if all paths exist or otherwise an error message
|
||||
*/
|
||||
export async function checkMissingPaths<T extends Record<string, Options>>(
|
||||
opts: T,
|
||||
group: string,
|
||||
argv: Record<string, unknown>
|
||||
): Promise<true | string> {
|
||||
const argsToCheck = _.keys(
|
||||
_.pickBy(opts, (opt: Options, id: string) => opt?.group === group && id in argv) as Partial<T>
|
||||
);
|
||||
|
||||
// yargs is pretty loose about allowing CLI args multiple times, and the value could potentially
|
||||
// be a `string[]` instead of `string`; we don't want to allow more than one path per arg.
|
||||
if (!argsToCheck.every((id) => _.isString(argv[id]))) {
|
||||
return 'Paths may only be provided once each';
|
||||
}
|
||||
|
||||
const pathsToCheck: MissingFileData[] = _.map(argsToCheck, (id) => ({
|
||||
id,
|
||||
path: String(argv[id]), // this must be a string per the above check
|
||||
}));
|
||||
|
||||
log.debug(
|
||||
'Checking for existence of %s: %s',
|
||||
util.pluralize('path', pathsToCheck.length),
|
||||
_.map(pathsToCheck, 'path')
|
||||
);
|
||||
|
||||
const missingPaths = await filterMissing(pathsToCheck);
|
||||
|
||||
if (missingPaths.length) {
|
||||
return missingPaths
|
||||
.map(({id, path}) => `Path specified using --${_.kebabCase(id)} (${path}) does not exist`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,53 +1,64 @@
|
||||
import {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
/**
|
||||
* Yargs command module for the `build` command.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
import type {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
import {buildReferenceDocs, buildSite, deploy, updateNav} from '../../builder';
|
||||
import {NAME_BIN} from '../../constants';
|
||||
import logger from '../../logger';
|
||||
import {stopwatch} from '../../util';
|
||||
import {checkMissingPaths} from '../check';
|
||||
|
||||
const log = logger.withTag('build');
|
||||
|
||||
const NAME_GROUP_BUILD = 'Build Options:';
|
||||
const NAME_GROUP_DEPLOY = 'Deployment Options:';
|
||||
const NAME_GROUP_SERVE = 'Serve Options:';
|
||||
const NAME_GROUP_BUILD_PATHS = 'Paths:';
|
||||
enum BuildCommandGroup {
|
||||
Build = 'Build Config:',
|
||||
Deploy = 'Deployment Config:',
|
||||
Serve = 'Dev Server Config:',
|
||||
BuildPaths = 'Custom Paths:',
|
||||
}
|
||||
|
||||
const opts = Object.freeze({
|
||||
const opts = {
|
||||
reference: {
|
||||
describe: 'Run TypeDoc command API reference build (Markdown)',
|
||||
group: NAME_GROUP_BUILD,
|
||||
group: BuildCommandGroup.Build,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
site: {
|
||||
describe: 'Run MkDocs build (HTML)',
|
||||
group: NAME_GROUP_BUILD,
|
||||
group: BuildCommandGroup.Build,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
'site-dir': {
|
||||
alias: 'd',
|
||||
describe: 'HTML output directory',
|
||||
group: NAME_GROUP_BUILD_PATHS,
|
||||
group: BuildCommandGroup.Build,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
normalize: true,
|
||||
coerce: path.resolve,
|
||||
implies: 'site',
|
||||
defaultDescription: '(from mkdocs.yml)',
|
||||
},
|
||||
'package-json': {
|
||||
defaultDescription: './package.json',
|
||||
describe: 'Path to package.json',
|
||||
group: NAME_GROUP_BUILD_PATHS,
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
coerce: path.resolve,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
defaultDescription: '(extension package name)',
|
||||
describe: 'Title of the API reference',
|
||||
group: NAME_GROUP_BUILD,
|
||||
group: BuildCommandGroup.Build,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
@@ -55,45 +66,48 @@ const opts = Object.freeze({
|
||||
'tsconfig-json': {
|
||||
defaultDescription: './tsconfig.json',
|
||||
describe: 'Path to tsconfig.json',
|
||||
group: NAME_GROUP_BUILD_PATHS,
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
'mkdocs-yml': {
|
||||
defaultDescription: './mkdocs.yml',
|
||||
description: 'Path to mkdocs.yml',
|
||||
group: NAME_GROUP_BUILD_PATHS,
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to typedoc.json',
|
||||
group: NAME_GROUP_BUILD_PATHS,
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
all: {
|
||||
describe: 'Output all reference docs (not just Appium comands)',
|
||||
group: NAME_GROUP_BUILD,
|
||||
group: BuildCommandGroup.Build,
|
||||
implies: 'site',
|
||||
type: 'boolean',
|
||||
},
|
||||
deploy: {
|
||||
describe: 'Commit HTML output',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
describe: 'Commit HTML output to a branch using mike',
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'boolean',
|
||||
implies: 'site',
|
||||
},
|
||||
push: {
|
||||
describe: 'Push after deploy',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'boolean',
|
||||
implies: 'deploy',
|
||||
},
|
||||
@@ -101,7 +115,7 @@ const opts = Object.freeze({
|
||||
alias: 'b',
|
||||
describe: 'Branch to commit to',
|
||||
implies: 'deploy',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
requiresArg: true,
|
||||
nargs: 1,
|
||||
@@ -111,7 +125,7 @@ const opts = Object.freeze({
|
||||
alias: 'r',
|
||||
describe: 'Remote to push to',
|
||||
implies: ['deploy', 'push'],
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
requiresArg: true,
|
||||
nargs: 1,
|
||||
@@ -120,16 +134,16 @@ const opts = Object.freeze({
|
||||
prefix: {
|
||||
describe: 'Subdirectory within <branch> to commit to',
|
||||
implies: ['deploy', 'branch'],
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
},
|
||||
message: {
|
||||
alias: 'm',
|
||||
describe: 'Commit message',
|
||||
describe: 'Commit message. Use "%s" for version placeholder',
|
||||
implies: 'deploy',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
@@ -137,7 +151,7 @@ const opts = Object.freeze({
|
||||
'deploy-version': {
|
||||
describe: 'Version (directory) to deploy build to',
|
||||
implies: 'deploy',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
@@ -146,7 +160,7 @@ const opts = Object.freeze({
|
||||
alias: {
|
||||
describe: 'Alias for the build (e.g., "latest"); triggers alias update',
|
||||
implies: 'deploy',
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
@@ -155,18 +169,18 @@ const opts = Object.freeze({
|
||||
rebase: {
|
||||
describe: 'Rebase <branch> with remote before deploy',
|
||||
implies: ['deploy', 'branch', 'remote'],
|
||||
group: NAME_GROUP_DEPLOY,
|
||||
group: BuildCommandGroup.Deploy,
|
||||
type: 'boolean',
|
||||
},
|
||||
serve: {
|
||||
describe: 'Start development server',
|
||||
group: NAME_GROUP_SERVE,
|
||||
group: BuildCommandGroup.Serve,
|
||||
type: 'boolean',
|
||||
},
|
||||
port: {
|
||||
alias: 'p',
|
||||
describe: 'Development server port',
|
||||
group: NAME_GROUP_SERVE,
|
||||
group: BuildCommandGroup.Serve,
|
||||
type: 'number',
|
||||
defaultDescription: '8000',
|
||||
implies: 'serve',
|
||||
@@ -176,31 +190,35 @@ const opts = Object.freeze({
|
||||
host: {
|
||||
alias: 'h',
|
||||
describe: 'Development server host',
|
||||
group: NAME_GROUP_SERVE,
|
||||
group: BuildCommandGroup.Serve,
|
||||
type: 'string',
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
implies: 'serve',
|
||||
defaultDescription: 'localhost',
|
||||
},
|
||||
}) satisfies Record<string, Options>;
|
||||
} as const satisfies Record<string, Options>;
|
||||
|
||||
type BuildOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
export default {
|
||||
command: 'build',
|
||||
describe: 'Build Appium extension documentation',
|
||||
builder: (yargs) =>
|
||||
yargs.options(opts).check((argv) => {
|
||||
// either this method doesn't provide camel-cased props, or the types are wrong.
|
||||
if (argv.deploy === true && argv['site-dir']) {
|
||||
log.error(
|
||||
`--site-dir is unsupported when running "${NAME_BIN} deploy"; use --prefix if needd, but remember that the default behavior is to deploy to the root of the branch (${argv.branch}) instead of a subdirectory`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
describe: 'Build Appium extension documentation using TypeDoc & MkDocs',
|
||||
builder(yargs) {
|
||||
return yargs
|
||||
.options(opts)
|
||||
.check(async (argv) => {
|
||||
// either this method doesn't provide camel-cased props, or the types are wrong.
|
||||
if (argv.deploy === true && argv['site-dir']) {
|
||||
return `--site-dir is unsupported when running "${NAME_BIN} deploy"; use --prefix if needed, but remember that the default behavior is to deploy to the root of the branch (${argv.branch}) instead of a subdirectory`;
|
||||
}
|
||||
|
||||
return await checkMissingPaths(opts, BuildCommandGroup.BuildPaths, argv);
|
||||
})
|
||||
.epilog(
|
||||
'For help with further configuration, see:\n - MkDocs: https://www.mkdocs.org\n - TypeDoc: https://typedoc.org\n - Mike: https://github.com/jimporter/mike'
|
||||
);
|
||||
},
|
||||
async handler(args) {
|
||||
const stop = stopwatch('build');
|
||||
log.debug('Build command called with args: %O', args);
|
||||
@@ -223,4 +241,4 @@ export default {
|
||||
}
|
||||
log.success('Done! (total: %dms)', stop());
|
||||
},
|
||||
} as CommandModule<{}, BuildOptions>;
|
||||
} as CommandModule<object, BuildOptions>;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Exports all command modules
|
||||
* @module
|
||||
*/
|
||||
|
||||
export {default as init} from './init';
|
||||
export {default as validate} from './validate';
|
||||
export {default as build} from './build';
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
/**
|
||||
* Yargs command module for the `init` command.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
import type {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
import {init} from '../../init';
|
||||
import logger from '../../logger';
|
||||
import {stopwatch} from '../../util';
|
||||
import {checkMissingPaths} from '../check';
|
||||
|
||||
const log = logger.withTag('init');
|
||||
|
||||
const NAME_GROUP_INIT_MKDOCS = 'MkDocs Config:';
|
||||
const NAME_GROUP_INIT_PATHS = 'Paths:';
|
||||
const NAME_GROUP_INIT_BEHAVIOR = 'Initialization Behavior:';
|
||||
enum InitCommandGroup {
|
||||
MkDocs = 'MkDocs Config:',
|
||||
Paths = 'Custom Paths:',
|
||||
Behavior = 'Initialization Behavior:',
|
||||
}
|
||||
|
||||
const opts = Object.freeze({
|
||||
/**
|
||||
* Note the groups here; _some_ opts are paths and would usually be checked via
|
||||
* {@linkcode checkMissingPaths}, but in this case we do not care if the path exists or not, because
|
||||
* we may create it.
|
||||
*/
|
||||
const opts = {
|
||||
copyright: {
|
||||
description: 'Copyright notice',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
@@ -22,19 +35,19 @@ const opts = Object.freeze({
|
||||
default: '.',
|
||||
defaultDescription: '(current directory)',
|
||||
description: 'Directory of package',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
group: InitCommandGroup.Paths,
|
||||
normalize: true,
|
||||
type: 'string',
|
||||
},
|
||||
'dry-run': {
|
||||
describe: 'Do not write any files; show what would be done',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
force: {
|
||||
alias: 'f',
|
||||
describe: 'Overwrite existing configurations',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
include: {
|
||||
@@ -43,19 +56,20 @@ const opts = Object.freeze({
|
||||
coerce: (value: string | string[]) => _.castArray(value),
|
||||
description: 'Files to include in compilation (globs OK)',
|
||||
nargs: 1,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
mkdocs: {
|
||||
default: true,
|
||||
description: 'Create mkdocs.yml if needed',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'mkdocs-yml': {
|
||||
defaultDescription: './mkdocs.yml',
|
||||
description: 'Path to mkdocs.yml',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
description: 'Path to new or existing mkdocs.yml',
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -63,8 +77,8 @@ const opts = Object.freeze({
|
||||
},
|
||||
'package-json': {
|
||||
defaultDescription: './package.json',
|
||||
describe: 'Path to package.json',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
describe: 'Path to existing package.json',
|
||||
group: InitCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -73,13 +87,13 @@ const opts = Object.freeze({
|
||||
python: {
|
||||
default: true,
|
||||
description: 'Install Python dependencies if needed',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'python-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to python 3 executable',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
group: InitCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -88,7 +102,7 @@ const opts = Object.freeze({
|
||||
'repo-name': {
|
||||
defaultDescription: '(derived from --repo-url)',
|
||||
description: 'Name of extension repository',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
@@ -96,7 +110,7 @@ const opts = Object.freeze({
|
||||
'repo-url': {
|
||||
defaultDescription: '(from package.json)',
|
||||
description: 'URL of extension repository',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
@@ -104,7 +118,7 @@ const opts = Object.freeze({
|
||||
'site-description': {
|
||||
defaultDescription: '(from package.json)',
|
||||
description: 'Site description',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
@@ -112,15 +126,15 @@ const opts = Object.freeze({
|
||||
'site-name': {
|
||||
defaultDescription: '(extension package name)',
|
||||
description: 'Name of site',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
group: InitCommandGroup.MkDocs,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
'tsconfig-json': {
|
||||
defaultDescription: './tsconfig.json',
|
||||
describe: 'Path to tsconfig.json',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
describe: 'Path to new or existing tsconfig.json',
|
||||
group: InitCommandGroup.Behavior,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -129,13 +143,13 @@ const opts = Object.freeze({
|
||||
typedoc: {
|
||||
default: true,
|
||||
description: 'Create typedoc.json if needed',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to typedoc.json',
|
||||
group: NAME_GROUP_INIT_PATHS,
|
||||
describe: 'Path to new or existing typedoc.json',
|
||||
group: InitCommandGroup.Behavior,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -144,20 +158,31 @@ const opts = Object.freeze({
|
||||
typescript: {
|
||||
default: true,
|
||||
description: 'Create tsconfig.json if needed',
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
}) satisfies Record<string, Options>;
|
||||
upgrade: {
|
||||
alias: 'up',
|
||||
describe: 'Only upgrade Python dependencies if out-of-date',
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
conflicts: 'force',
|
||||
},
|
||||
} as const satisfies Record<string, Options>;
|
||||
|
||||
type InitOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
export default {
|
||||
command: 'init',
|
||||
describe: 'Initialize package for doc generation',
|
||||
builder: opts,
|
||||
builder(yargs) {
|
||||
return yargs
|
||||
.options(opts)
|
||||
.check(async (argv) => checkMissingPaths(opts, InitCommandGroup.Paths, argv));
|
||||
},
|
||||
async handler(args) {
|
||||
const done = stopwatch('init');
|
||||
await init({...args, overwrite: args.force, cwd: args.dir});
|
||||
log.success('Done (%dms)', done());
|
||||
},
|
||||
} as CommandModule<{}, InitOptions>;
|
||||
} as CommandModule<object, InitOptions>;
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
import pluralize from 'pluralize';
|
||||
import {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
/**
|
||||
* Yargs command module for the `validate` command.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {util} from '@appium/support';
|
||||
import type {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
import {DocutilsError} from '../../error';
|
||||
import {DocutilsValidator, ValidationKind} from '../../validate';
|
||||
import logger from '../../logger';
|
||||
import {checkMissingPaths} from '../check';
|
||||
|
||||
const log = logger.withTag('validate');
|
||||
|
||||
const NAME_GROUP_VALIDATE = 'Validation Behavior:';
|
||||
const NAME_GROUP_VALIDATE_PATHS = 'Paths:';
|
||||
enum ValidateCommandGroup {
|
||||
Behavior = 'Validation Behavior:',
|
||||
Paths = 'Custom Paths:',
|
||||
}
|
||||
|
||||
const opts = Object.freeze({
|
||||
const opts = {
|
||||
mkdocs: {
|
||||
default: true,
|
||||
description: 'Validate MkDocs environment',
|
||||
group: NAME_GROUP_VALIDATE,
|
||||
group: ValidateCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'mkdocs-yml': {
|
||||
defaultDescription: './mkdocs.yml',
|
||||
description: 'Path to mkdocs.yml',
|
||||
group: NAME_GROUP_VALIDATE_PATHS,
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -28,7 +36,7 @@ const opts = Object.freeze({
|
||||
'npm-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to npm executable',
|
||||
group: NAME_GROUP_VALIDATE_PATHS,
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -37,13 +45,13 @@ const opts = Object.freeze({
|
||||
python: {
|
||||
default: true,
|
||||
description: 'Validate Python 3 environment',
|
||||
group: NAME_GROUP_VALIDATE,
|
||||
group: ValidateCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'python-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to python 3 executable',
|
||||
group: NAME_GROUP_VALIDATE_PATHS,
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -52,7 +60,7 @@ const opts = Object.freeze({
|
||||
'tsconfig-json': {
|
||||
defaultDescription: './tsconfig.json',
|
||||
describe: 'Path to tsconfig.json',
|
||||
group: NAME_GROUP_VALIDATE_PATHS,
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -61,13 +69,13 @@ const opts = Object.freeze({
|
||||
typedoc: {
|
||||
default: true,
|
||||
description: 'Validate TypoDoc environment',
|
||||
group: NAME_GROUP_VALIDATE,
|
||||
group: ValidateCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to typedoc.json',
|
||||
group: NAME_GROUP_VALIDATE_PATHS,
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
@@ -76,25 +84,25 @@ const opts = Object.freeze({
|
||||
typescript: {
|
||||
default: true,
|
||||
description: 'Validate TypeScript environment',
|
||||
group: NAME_GROUP_VALIDATE,
|
||||
group: ValidateCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
}) satisfies Record<string, Options>;
|
||||
} as const satisfies Record<string, Options>;
|
||||
|
||||
type ValidateOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
export default {
|
||||
command: 'validate',
|
||||
describe: 'Validate Environment',
|
||||
builder: opts,
|
||||
builder(yargs) {
|
||||
return yargs.options(opts).check(async (argv) => {
|
||||
if (!argv.python && !argv.typedoc && !argv.typescript && !argv.mkdocs) {
|
||||
return 'No validation targets specified; one or more of --python, --typescript, --typedoc or --mkdocs must be provided';
|
||||
}
|
||||
return checkMissingPaths(opts, ValidateCommandGroup.Paths, argv);
|
||||
});
|
||||
},
|
||||
async handler(args) {
|
||||
if (!args.python && !args.typedoc && !args.typescript && !args.mkdocs) {
|
||||
// specifically not a DocutilsError
|
||||
throw new Error(
|
||||
'No validation targets specified; one or more of --python, --typescript, --typedoc or --mkdocs must be provided'
|
||||
);
|
||||
}
|
||||
|
||||
let errorCount = 0;
|
||||
const validator = new DocutilsValidator(args)
|
||||
.once(DocutilsValidator.BEGIN, (kinds: ValidationKind[]) => {
|
||||
@@ -114,8 +122,8 @@ export default {
|
||||
|
||||
if (errorCount) {
|
||||
throw new DocutilsError(
|
||||
`Validation failed with ${errorCount} ${pluralize('error', errorCount)}`
|
||||
`Validation failed with ${errorCount} ${util.pluralize('error', errorCount)}`
|
||||
);
|
||||
}
|
||||
},
|
||||
} as CommandModule<{}, ValidateOptions>;
|
||||
} as CommandModule<object, ValidateOptions>;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
|
||||
/**
|
||||
* Main CLI entry point for `@appium/docutils`
|
||||
* @module
|
||||
*/
|
||||
|
||||
import logger from '../logger';
|
||||
|
||||
import _ from 'lodash';
|
||||
@@ -8,6 +15,10 @@ import {DEFAULT_LOG_LEVEL, LogLevelMap, NAME_BIN} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {build, init, validate} from './command';
|
||||
import {findConfig} from './config';
|
||||
import {fs} from '@appium/support';
|
||||
import {sync as readPkg} from 'read-pkg';
|
||||
|
||||
const pkg = readPkg({cwd: fs.findRoot(__dirname)});
|
||||
|
||||
const log = logger.withTag('cli');
|
||||
export async function main(argv = hideBin(process.argv)) {
|
||||
@@ -45,7 +56,7 @@ export async function main(argv = hideBin(process.argv)) {
|
||||
describe: 'Disable config file discovery',
|
||||
},
|
||||
})
|
||||
.middleware(
|
||||
.middleware([
|
||||
/**
|
||||
* Configures logging; `--verbose` implies `--log-level=debug`
|
||||
*/
|
||||
@@ -55,8 +66,15 @@ export async function main(argv = hideBin(process.argv)) {
|
||||
log.debug('Debug logging enabled via --verbose');
|
||||
}
|
||||
log.level = LogLevelMap[argv.logLevel];
|
||||
}
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Writes a startup message, if logging is enabled
|
||||
*/
|
||||
async () => {
|
||||
log.info(`${pkg.name} @ v${pkg.version} (Node.js ${process.version})`);
|
||||
},
|
||||
])
|
||||
.epilog(`Please report bugs at ${pkg.bugs?.url}`)
|
||||
.fail(
|
||||
/**
|
||||
* Custom failure handler so we can log nicely.
|
||||
@@ -67,6 +85,7 @@ export async function main(argv = hideBin(process.argv)) {
|
||||
log.error(error.message);
|
||||
} else {
|
||||
y.showHelp();
|
||||
console.log();
|
||||
log.error(msg ?? error.message);
|
||||
}
|
||||
y.exit(1, error);
|
||||
@@ -81,7 +100,7 @@ export async function main(argv = hideBin(process.argv)) {
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
// eslint-disable-next-line promise/prefer-await-to-then, promise/prefer-await-to-callbacks
|
||||
main().catch((err) => {
|
||||
log.error('Caught otherwise-unhandled rejection (this is probably a bug):', err);
|
||||
});
|
||||
|
||||
@@ -86,6 +86,11 @@ export const NAME_ERR_ENOENT = 'ENOENT';
|
||||
*/
|
||||
export const NAME_ERR_EEXIST = 'EEXIST';
|
||||
|
||||
/**
|
||||
* Name of the default theme
|
||||
*/
|
||||
export const NAME_THEME = 'material';
|
||||
|
||||
/**
|
||||
* Default log level
|
||||
*/
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
/**
|
||||
* A custom error class. This exists so we can use `instanceof` to differentiate between "expected"
|
||||
* exceptions and unexpected ones.
|
||||
* @module
|
||||
*/
|
||||
|
||||
export class DocutilsError extends Error {}
|
||||
|
||||
@@ -127,7 +127,10 @@ async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize: true
|
||||
): Promise<{pkgPath: string; pkg: NormalizedPackageJson}>;
|
||||
async function _readPkgJson(cwd: string): Promise<{pkgPath: string; pkg: PackageJson}>;
|
||||
async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize?: false
|
||||
): Promise<{pkgPath: string; pkg: PackageJson}>;
|
||||
async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize?: boolean
|
||||
@@ -236,14 +239,14 @@ export const whichMike = _.partial(cachedWhich, NAME_MIKE);
|
||||
*/
|
||||
export const readMkDocsYml = _.memoize(
|
||||
async (filepath: string, cwd = process.cwd()): Promise<MkDocsYml> => {
|
||||
let mkDocsYml = <MkDocsYml>await readYaml(filepath);
|
||||
let mkDocsYml = (await readYaml(filepath)) as MkDocsYml;
|
||||
if (mkDocsYml.site_dir) {
|
||||
mkDocsYml.site_dir = path.resolve(cwd, path.dirname(filepath), mkDocsYml.site_dir);
|
||||
}
|
||||
if (mkDocsYml.INHERIT) {
|
||||
let inheritPath: string | undefined = path.resolve(path.dirname(filepath), mkDocsYml.INHERIT);
|
||||
while (inheritPath) {
|
||||
const inheritYml = <MkDocsYml>await readYaml(inheritPath);
|
||||
const inheritYml = (await readYaml(inheritPath)) as MkDocsYml;
|
||||
if (inheritYml.site_dir) {
|
||||
inheritYml.site_dir = path.resolve(path.dirname(inheritPath), inheritYml.site_dir);
|
||||
log.debug('Resolved site_dir to %s', inheritYml.site_dir);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Main entry point
|
||||
* @module
|
||||
*/
|
||||
|
||||
export * from './mike';
|
||||
export * from './builder';
|
||||
export * from './validate';
|
||||
|
||||
@@ -126,7 +126,9 @@ export const initMkDocs = createScaffoldTask<InitMkDocsOptions, MkDocsYml>(
|
||||
if (repoUrl && !repoName) {
|
||||
let {pathname} = new URL(repoUrl);
|
||||
pathname = pathname.slice(1);
|
||||
let [owner, repo] = pathname.split('/');
|
||||
const pathparts = pathname.split('/');
|
||||
const owner = pathparts[0];
|
||||
let repo = pathparts[1];
|
||||
repo = repo.replace(/\.git$/, '');
|
||||
repoName = [owner, repo].join('/');
|
||||
if (repoName) {
|
||||
@@ -158,8 +160,12 @@ export const initMkDocs = createScaffoldTask<InitMkDocsOptions, MkDocsYml>(
|
||||
export async function initPython({
|
||||
pythonPath = NAME_PYTHON,
|
||||
dryRun = false,
|
||||
upgrade = false,
|
||||
}: InitPythonOptions = {}): Promise<void> {
|
||||
const args = ['-m', 'pip', 'install', '-r', REQUIREMENTS_TXT_PATH];
|
||||
if (upgrade) {
|
||||
args.push('--upgrade');
|
||||
}
|
||||
if (dryRun) {
|
||||
dryRunLog.info('Would execute command: %s %s', pythonPath, args.join(' '));
|
||||
} else {
|
||||
@@ -214,6 +220,7 @@ export async function init({
|
||||
dryRun,
|
||||
cwd,
|
||||
pythonPath,
|
||||
upgrade,
|
||||
typedocJson: typeDocJsonPath,
|
||||
}: InitOptions = {}): Promise<void> {
|
||||
if (!typescript && typedoc) {
|
||||
@@ -222,7 +229,7 @@ export async function init({
|
||||
);
|
||||
}
|
||||
|
||||
if (typescript) {
|
||||
if (typescript && !upgrade) {
|
||||
await initTsConfigJson({
|
||||
dest: tsconfigJsonPath,
|
||||
packageJson: packageJsonPath,
|
||||
@@ -233,7 +240,7 @@ export async function init({
|
||||
});
|
||||
}
|
||||
|
||||
if (typedoc) {
|
||||
if (typedoc && !upgrade) {
|
||||
await initTypeDocJson({
|
||||
dest: typeDocJsonPath,
|
||||
packageJson: packageJsonPath,
|
||||
@@ -244,10 +251,10 @@ export async function init({
|
||||
}
|
||||
|
||||
if (python) {
|
||||
await initPython({pythonPath, dryRun});
|
||||
await initPython({pythonPath, dryRun, upgrade});
|
||||
}
|
||||
|
||||
if (mkdocs) {
|
||||
if (mkdocs && !upgrade) {
|
||||
await initMkDocs({
|
||||
dest: mkdocsYmlPath,
|
||||
cwd,
|
||||
@@ -262,7 +269,7 @@ export async function init({
|
||||
}
|
||||
}
|
||||
|
||||
export interface InitTypeDocOptions extends ScaffoldTaskOptions {}
|
||||
export type InitTypeDocOptions = ScaffoldTaskOptions;
|
||||
export interface InitTsConfigOptions extends ScaffoldTaskOptions {
|
||||
/**
|
||||
* List of source files (globs supported); typically `src` or `lib`
|
||||
@@ -274,6 +281,11 @@ export interface InitPythonOptions extends ScaffoldTaskOptions {
|
||||
* Path to `python` (v3.x) executable
|
||||
*/
|
||||
pythonPath?: string;
|
||||
|
||||
/**
|
||||
* If true, upgrade only
|
||||
*/
|
||||
upgrade?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,5 +327,10 @@ export type InitOptions = Simplify<
|
||||
* Path to new or existing `mkdocs.yml` file
|
||||
*/
|
||||
mkdocsYml?: string;
|
||||
|
||||
/**
|
||||
* If `true`, upgrade only
|
||||
*/
|
||||
upgrade?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* API around `mike`, a tool for deploying multiple versions of MkDocs-built sites to GitHub Pages
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {exec} from 'teen_process';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import log from './logger';
|
||||
|
||||
@@ -32,6 +32,9 @@ export function stopwatch(id: string) {
|
||||
}
|
||||
stopwatch.cache = new Map<string, number>();
|
||||
|
||||
/**
|
||||
* Converts a tuple to an object; use for extracting parameter types from a function signature
|
||||
*/
|
||||
export type TupleToObject<
|
||||
T extends readonly any[],
|
||||
M extends Record<Exclude<keyof T, keyof any[]>, PropertyKey>
|
||||
@@ -69,7 +72,7 @@ export const argify: (obj: Record<string, string | number | boolean | undefined>
|
||||
);
|
||||
|
||||
/**
|
||||
* Conversion of the parameters of {@linkcode Subprocess.start} to an object.
|
||||
* Conversion of the parameters of {@linkcode SubProcess.start} to an object.
|
||||
*/
|
||||
export type TeenProcessSubprocessStartOpts = Partial<
|
||||
TupleToObject<Parameters<SubProcess['start']>, ['startDetector', 'detach', 'timeoutMs']>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
/**
|
||||
* Validates an environment for building documentation
|
||||
* Validates an environment for building documentation; used by `validate` command
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import {fs, util} from '@appium/support';
|
||||
import chalk from 'chalk';
|
||||
import _ from 'lodash';
|
||||
import {EventEmitter} from 'node:events';
|
||||
import path from 'node:path';
|
||||
import pluralize from 'pluralize';
|
||||
import {satisfies} from 'semver';
|
||||
import {exec} from 'teen_process';
|
||||
import {
|
||||
@@ -36,7 +35,6 @@ import {
|
||||
findMkDocsYml,
|
||||
readJson5,
|
||||
readTypedocJson,
|
||||
readYaml,
|
||||
whichMkDocs,
|
||||
whichNpm,
|
||||
whichPython,
|
||||
@@ -287,7 +285,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
return this.requirementsTxt;
|
||||
}
|
||||
|
||||
let requiredPackages: PipPackage[] = [];
|
||||
const requiredPackages: PipPackage[] = [];
|
||||
|
||||
try {
|
||||
let requirementsTxt = await fs.readFile(REQUIREMENTS_TXT_PATH, 'utf8');
|
||||
@@ -312,6 +310,9 @@ export class DocutilsValidator extends EventEmitter {
|
||||
this.emittedErrors.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the correct version of `mkdocs` is installed
|
||||
*/
|
||||
protected async validateMkDocs() {
|
||||
let mkDocsPath: string | undefined;
|
||||
try {
|
||||
@@ -466,7 +467,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const msgParts = [];
|
||||
if (missingPackages.length) {
|
||||
msgParts.push(
|
||||
`The following required ${pluralize(
|
||||
`The following required ${util.pluralize(
|
||||
'package',
|
||||
missingPackages.length
|
||||
)} could not be found:\n${missingPackages
|
||||
@@ -476,7 +477,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
}
|
||||
if (invalidVersionPackages.length) {
|
||||
msgParts.push(
|
||||
`The following required ${pluralize(
|
||||
`The following required ${util.pluralize(
|
||||
'package',
|
||||
invalidVersionPackages.length
|
||||
)} are installed, but at the wrong version:\n${invalidVersionPackages
|
||||
@@ -533,7 +534,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
}
|
||||
|
||||
if (rawTypeDocVersion) {
|
||||
let match = rawTypeDocVersion.match(TYPEDOC_VERSION_REGEX);
|
||||
const match = rawTypeDocVersion.match(TYPEDOC_VERSION_REGEX);
|
||||
if (match) {
|
||||
typeDocVersion = match[1];
|
||||
} else {
|
||||
@@ -621,7 +622,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
return this.fail(`Could not find TypeScript compiler ("tsc") from ${pkgDir}`);
|
||||
}
|
||||
|
||||
let match = rawTypeScriptVersion.match(TYPESCRIPT_VERSION_REGEX);
|
||||
const match = rawTypeScriptVersion.match(TYPESCRIPT_VERSION_REGEX);
|
||||
if (match) {
|
||||
typeScriptVersion = match[1];
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -46,31 +46,29 @@
|
||||
"test:e2e": "echo \"No e2e tests for this package\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\"",
|
||||
"start": "node ./build/lib/cli.js"
|
||||
"start": "node ./build/lib/cli/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/support": "^3.1.7",
|
||||
"@appium/tsconfig": "^0.2.4",
|
||||
"@appium/typedoc-plugin-appium": "^0.5.0",
|
||||
"@appium/typedoc-plugin-appium": "^0.5.1",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
"figures": "3.2.0",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "8.1.0",
|
||||
"json5": "2.2.3",
|
||||
"lilconfig": "2.0.6",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"log-symbols": "4.1.0",
|
||||
"pkg-dir": "5.0.0",
|
||||
"pluralize": "8.0.0",
|
||||
"read-pkg": "5.2.0",
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.6.0",
|
||||
"typedoc": "0.23.25",
|
||||
"type-fest": "3.6.1",
|
||||
"typedoc": "0.23.26",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.9.5",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mkdocs==1.4.2
|
||||
mkdocs-material==9.0.14
|
||||
mkdocs-material==9.1.1
|
||||
mkdocs-material-extensions==1.1.1
|
||||
mike==1.1.2
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.1](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.0...@appium/driver-test-support@0.4.1) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.6.1 ([471a4b5](https://github.com/appium/appium/commit/471a4b57e622ff077d59f577a78341268700c48d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.7...@appium/driver-test-support@0.4.0) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -99,8 +99,7 @@ export function createSessionHelpers(port, address = TEST_HOST) {
|
||||
|
||||
/**
|
||||
* Creates E2E test suites for a driver.
|
||||
* @template {Driver} P
|
||||
* @param {DriverClass<P>} DriverClass
|
||||
* @param {DriverClass} DriverClass
|
||||
* @param {Partial<BaseNSCapabilities>} [defaultCaps]
|
||||
*/
|
||||
export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
@@ -110,7 +109,7 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
|
||||
describe(`BaseDriver E2E (as ${className})`, function () {
|
||||
let baseServer;
|
||||
/** @type {P} */
|
||||
/** @type {Driver} */
|
||||
let d;
|
||||
/**
|
||||
* This URL creates a new session
|
||||
@@ -419,16 +418,9 @@ export function driverE2ETestSuite(DriverClass, defaultCaps = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@linkcode DriverClass}, except using the base {@linkcode Driver} type instead of `ExternalDriver`.
|
||||
* This allows the suite to work for `BaseDriver`.
|
||||
* @template {Driver} P
|
||||
* @typedef {import('@appium/types').DriverClass<P>} DriverClass
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').Driver} Driver
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').DriverStatic} DriverStatic
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
|
||||
@@ -626,12 +626,22 @@ export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {import('@appium/types').Constraints} [C=import('@appium/types').BaseDriverCapConstraints]
|
||||
* @template {Constraints} [C=import('@appium/types').BaseDriverCapConstraints]
|
||||
* @typedef {import('@appium/types').DriverClass<Driver<C>>} DriverClass
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Driver<C>} Driver
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} [C=import('@appium/types').BaseDriverCapConstraints]
|
||||
* @template {import('@appium/types').StringRecord|void} [Extra=void]
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.10.0",
|
||||
"@appium/types": "^0.10.1",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/stoppable": "1.1.1",
|
||||
"axios": "1.3.4",
|
||||
@@ -52,7 +52,7 @@
|
||||
"sinon": "15.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"stoppable": "1.1.0",
|
||||
"type-fest": "3.6.0"
|
||||
"type-fest": "3.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# 0.2.0 (2023-03-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **eslint-config-appium-ts:** create new pkg @appium/eslint-config-appium-ts ([dfe02ca](https://github.com/appium/appium/commit/dfe02ca98c73c0cf9863a7c7441d6a1a5d37ac33))
|
||||
@@ -0,0 +1,44 @@
|
||||
# @appium/eslint-config-appium
|
||||
|
||||
> 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)
|
||||
|
||||
## Motivation
|
||||
|
||||
**If your package has no TypeScript sources, you don't need this.** However, if your package _does_ have TypeScript sources, you can't lint those files without this.
|
||||
|
||||
## Usage
|
||||
|
||||
Install the package with **`npm` v8 or newer**:
|
||||
|
||||
```bash
|
||||
npm install @appium/eslint-config-appium-ts --save-dev
|
||||
```
|
||||
|
||||
And then, in your `.eslintrc` file, extend the configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "@appium/eslint-config-appium-ts"
|
||||
}
|
||||
```
|
||||
|
||||
## Peer Dependencies
|
||||
|
||||
This config requires the following packages be installed (as peer dependencies) in your project. See the `package.json` for the required versions.
|
||||
|
||||
- [eslint](https://www.npmjs.com/package/eslint)
|
||||
- [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
|
||||
- [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)
|
||||
- [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
|
||||
- [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
|
||||
- [@appium/eslint-config-appium](https://www.npmjs.com/package/@appium/eslint-config-appium)
|
||||
- [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin)
|
||||
- [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser)
|
||||
|
||||
## Notes
|
||||
|
||||
- This configuration is intended to be used alongside [Prettier](https://www.npmjs.com/package/prettier).
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2023 OpenJS Foundation. Licensed Apache-2.0
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/eslintrc",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["@appium/eslint-config-appium", "plugin:@typescript-eslint/recommended"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-function": 1,
|
||||
"@typescript-eslint/no-empty-interface": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-var-requires": 1,
|
||||
"import/no-duplicates": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"no-unused-vars": 0,
|
||||
"promise/no-native": 0,
|
||||
"require-await": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium-ts",
|
||||
"version": "0.2.0",
|
||||
"description": "Shared ESLint config for Appium projects (TypeScript version)",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslintconfig",
|
||||
"appium",
|
||||
"typescript",
|
||||
"typescript-eslint"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appium/appium.git",
|
||||
"directory": "packages/eslint-config-appium-ts"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"main": "index.json",
|
||||
"files": [
|
||||
"index.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test:smoke": "exit 0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@appium/eslint-config-appium": "^8.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
||||
"@typescript-eslint/parser": "^5.5.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.0.1](https://github.com/appium/appium/compare/@appium/eslint-config-appium@8.0.0...@appium/eslint-config-appium@8.0.1) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/eslint-config-appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0](https://github.com/appium/appium/compare/@appium/eslint-config-appium@7.0.0...@appium/eslint-config-appium@8.0.0) (2022-12-14)
|
||||
|
||||
- chore!: set engines to minimum Node.js v14.17.0 ([a1dbe6c](https://github.com/appium/appium/commit/a1dbe6c43efe76604943a607d402f4c8b864d652))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
Install the package with **`npm` v7 or newer** (for automatic installation of peer dependencies):
|
||||
Install the package with **`npm` v8 or newer**:
|
||||
|
||||
```bash
|
||||
npm install @appium/eslint-config-appium --save-dev
|
||||
@@ -20,7 +20,7 @@ And then, in your `.eslintrc` file, extend the configuration:
|
||||
|
||||
## Peer Dependencies
|
||||
|
||||
This config requires the following packages be installed (as dev dependencies) in your project. See the `package.json` for the required versions.
|
||||
This config requires the following packages be installed (as peer dependencies) in your project. See the `package.json` for the required versions.
|
||||
|
||||
- [eslint](https://www.npmjs.com/package/eslint)
|
||||
- [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium",
|
||||
"version": "8.0.0",
|
||||
"version": "8.0.1",
|
||||
"description": "Shared ESLint config for Appium projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
"eslint",
|
||||
"eslintconfig",
|
||||
"appium"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.8](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.7...@appium/execute-driver-plugin@3.0.8) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.30.2 ([49694f5](https://github.com/appium/appium/commit/49694f50a4680138cc92aba1eee294c927d712cf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.7](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.6...@appium/execute-driver-plugin@3.0.7) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "3.0.7",
|
||||
"version": "3.0.8",
|
||||
"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.14",
|
||||
"webdriverio": "7.30.1"
|
||||
"webdriverio": "7.30.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.1](https://github.com/appium/appium/compare/@appium/fake-driver@5.2.0...@appium/fake-driver@5.2.1) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium,fake-driver:** expose child process when running an extension script ([e9dae3f](https://github.com/appium/appium/commit/e9dae3f6d006dcf89b6c0b6fb491be15acfed98b))
|
||||
* **fake-driver:** fix a broken type ([b4c1403](https://github.com/appium/appium/commit/b4c1403c22c4ffd7a4358f2473917c33f8c0049e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.1.5...@appium/fake-driver@5.2.0) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import {errors} from 'appium/driver';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
assertNoAlert() {
|
||||
if (this.appModel.hasAlert()) {
|
||||
throw new errors.UnexpectedAlertOpenError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
assertAlert() {
|
||||
if (!this.appModel.hasAlert()) {
|
||||
throw new errors.NoAlertOpenError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the text of an alert
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getAlertText() {
|
||||
this.assertAlert();
|
||||
return this.appModel.alertText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the text of an alert
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Promise<void>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async setAlertText(text) {
|
||||
this.assertAlert();
|
||||
try {
|
||||
this.appModel.setAlertText(text);
|
||||
} catch (e) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept an alert
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async postAcceptAlert() {
|
||||
this.assertAlert();
|
||||
this.appModel.handleAlert();
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss an alert
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async postDismissAlert() {
|
||||
return this.postAcceptAlert();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('../driver').FakeDriver} FakeDriver
|
||||
*/
|
||||
@@ -0,0 +1,70 @@
|
||||
import {FakeDriver} from '../driver';
|
||||
import {errors} from 'appium/driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
interface FakeDriverAlertMixin {
|
||||
assertNoAlert(): void;
|
||||
assertAlert(): void;
|
||||
|
||||
getAlertText(): Promise<string>;
|
||||
|
||||
setAlertText(text: string): Promise<void>;
|
||||
|
||||
postAcceptAlert(): Promise<void>;
|
||||
postDismissAlert(): Promise<void>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
interface FakeDriver extends FakeDriverAlertMixin {}
|
||||
}
|
||||
|
||||
const AlertMixin: FakeDriverAlertMixin = {
|
||||
assertNoAlert(this: FakeDriver) {
|
||||
if (this.appModel.hasAlert()) {
|
||||
throw new errors.UnexpectedAlertOpenError();
|
||||
}
|
||||
},
|
||||
|
||||
assertAlert(this: FakeDriver) {
|
||||
if (!this.appModel.hasAlert()) {
|
||||
throw new errors.NoAlertOpenError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the text of an alert
|
||||
*/
|
||||
async getAlertText(this: FakeDriver) {
|
||||
this.assertAlert();
|
||||
return this.appModel.alertText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the text of an alert
|
||||
*/
|
||||
async setAlertText(this: FakeDriver, text: string) {
|
||||
this.assertAlert();
|
||||
try {
|
||||
this.appModel.setAlertText(text);
|
||||
} catch (e) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Accept an alert
|
||||
*/
|
||||
async postAcceptAlert(this: FakeDriver) {
|
||||
this.assertAlert();
|
||||
this.appModel.handleAlert();
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss an alert
|
||||
*/
|
||||
async postDismissAlert(this: FakeDriver) {
|
||||
return this.postAcceptAlert();
|
||||
},
|
||||
};
|
||||
|
||||
mixin(AlertMixin);
|
||||
+26
-34
@@ -1,55 +1,53 @@
|
||||
import _ from 'lodash';
|
||||
import {FakeDriver} from '../driver';
|
||||
import {errors} from 'appium/driver';
|
||||
import {mixin} from './mixin';
|
||||
interface FakeDriverContextsMixin {
|
||||
getRawContexts(): Record<string, any>;
|
||||
assertWebviewContext(): void;
|
||||
getCurrentContext(): Promise<string>;
|
||||
getContexts(): Promise<string[]>;
|
||||
setContext(context: string): Promise<void>;
|
||||
setFrame(frame: number | null): Promise<void>;
|
||||
}
|
||||
declare module '../driver' {
|
||||
interface FakeDriver extends FakeDriverContextsMixin {}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getRawContexts() {
|
||||
let contexts = {NATIVE_APP: null, PROXY: null};
|
||||
let wvs = this.appModel?.getWebviews() ?? [];
|
||||
const ContextsMixin: FakeDriverContextsMixin = {
|
||||
getRawContexts(this: FakeDriver) {
|
||||
const contexts = {NATIVE_APP: null, PROXY: null};
|
||||
const wvs = this.appModel.getWebviews() ?? [];
|
||||
for (let i = 1; i < wvs.length + 1; i++) {
|
||||
contexts[`WEBVIEW_${i}`] = wvs[i - 1];
|
||||
}
|
||||
return contexts;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
assertWebviewContext() {
|
||||
assertWebviewContext(this: FakeDriver) {
|
||||
if (this.curContext === 'NATIVE_APP') {
|
||||
throw new errors.InvalidContextError();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getCurrentContext() {
|
||||
async getCurrentContext(this: FakeDriver): Promise<string> {
|
||||
return this.curContext;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of available contexts
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getContexts() {
|
||||
async getContexts(this: FakeDriver) {
|
||||
return _.keys(this.getRawContexts());
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current context
|
||||
*
|
||||
* @param {string} context - name of the context
|
||||
* @returns {Promise<void>}
|
||||
* @this {FakeDriver}
|
||||
* @param context - name of the context
|
||||
*/
|
||||
async setContext(context) {
|
||||
let contexts = this.getRawContexts();
|
||||
async setContext(this: FakeDriver, context: string) {
|
||||
const contexts = this.getRawContexts();
|
||||
if (context in contexts) {
|
||||
this.curContext = context;
|
||||
if (context === 'NATIVE_APP') {
|
||||
@@ -68,17 +66,13 @@ export default {
|
||||
|
||||
/**
|
||||
* Set the active frame
|
||||
*
|
||||
* @param {number} frameId
|
||||
* @returns {Promise<void>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async setFrame(frameId) {
|
||||
async setFrame(this: FakeDriver, frameId: number | null) {
|
||||
this.assertWebviewContext();
|
||||
if (frameId === null) {
|
||||
this.appModel.deactivateFrame();
|
||||
} else {
|
||||
let nodes = this.appModel.xpathQuery(`//iframe[@id="${frameId}"]`);
|
||||
const nodes = this.appModel.xpathQuery(`//iframe[@id="${frameId}"]`);
|
||||
if (!nodes.length) {
|
||||
throw new errors.NoSuchFrameError();
|
||||
}
|
||||
@@ -87,6 +81,4 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('../driver').FakeDriver} FakeDriver
|
||||
*/
|
||||
mixin(ContextsMixin);
|
||||
@@ -1,161 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {errors} from 'appium/driver';
|
||||
export default {
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getElements(elIds) {
|
||||
for (let elId of elIds) {
|
||||
if (!_.has(this.elMap, elId)) {
|
||||
throw new errors.StaleElementReferenceError();
|
||||
}
|
||||
}
|
||||
return elIds.map((e) => this.elMap[e]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getElement(elId) {
|
||||
return this.getElements([elId])[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getName(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.tagName;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async elementDisplayed(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.isVisible();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async elementEnabled(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.isEnabled();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async elementSelected(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.isSelected();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async setValue(keys, elementId) {
|
||||
let value = keys;
|
||||
if (keys instanceof Array) {
|
||||
value = keys.join('');
|
||||
}
|
||||
let el = this.getElement(elementId);
|
||||
if (el.type !== 'MockInputField') {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
el.setAttr('value', value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getText(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.getAttr('value');
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async clear(elementId) {
|
||||
await this.setValue('', elementId);
|
||||
},
|
||||
|
||||
/**
|
||||
* This comment should be displayed instead of the one from ExternalDriver
|
||||
* @param {string} elementId
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async click(elementId) {
|
||||
this.assertNoAlert();
|
||||
let el = this.getElement(elementId);
|
||||
if (!el.isVisible()) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
el.click();
|
||||
this.focusedElId = elementId;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getAttribute(attr, elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.getAttr(attr);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getElementRect(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.getElementRect();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getSize(elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return el.getSize();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
equalsElement(el1Id, el2Id) {
|
||||
let el1 = this.getElement(el1Id);
|
||||
let el2 = this.getElement(el2Id);
|
||||
return el1.equals(el2);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getCssProperty(prop, elementId) {
|
||||
this.assertWebviewContext();
|
||||
let el = this.getElement(elementId);
|
||||
return el.getCss(prop);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} elementId
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getLocation(elementId) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getLocation();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getLocationInView(elementId) {
|
||||
return this.getLocation(elementId);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('../driver').FakeDriver} FakeDriver
|
||||
*/
|
||||
@@ -0,0 +1,138 @@
|
||||
import _ from 'lodash';
|
||||
import {errors} from 'appium/driver';
|
||||
import {FakeDriver} from '../driver';
|
||||
import {Position, Rect, Size} from '@appium/types';
|
||||
import {FakeElement} from '../fake-element';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
interface FakeDriverElementsMixin {
|
||||
getElements(elementIds: string[]): FakeElement[];
|
||||
getElement(elementId: string): FakeElement;
|
||||
getName(elementId: string): Promise<string>;
|
||||
elementDisplayed(elementId: string): Promise<boolean>;
|
||||
elementEnabled(elementId: string): Promise<boolean>;
|
||||
elementSelected(elementId: string): Promise<boolean>;
|
||||
setValue(keys: string | string[], value: string): Promise<void>;
|
||||
getText(elementId: string): Promise<string>;
|
||||
clear(elementId: string): Promise<void>;
|
||||
click(elementId: string): Promise<void>;
|
||||
|
||||
getAttribute(elementId: string, attributeName: string): Promise<string>;
|
||||
getElementRect(elementId: string): Promise<Rect>;
|
||||
getSize(elementId: string): Promise<Size>;
|
||||
equalsElement(elementId: string, otherElementId: string): Promise<boolean>;
|
||||
getCssProperty(elementId: string, propertyName: string): Promise<string>;
|
||||
getLocation(elementId: string): Promise<Position>;
|
||||
getLocationInView(elementId: string): Promise<Position>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
interface FakeDriver extends FakeDriverElementsMixin {}
|
||||
}
|
||||
|
||||
const ElementsMixin: FakeDriverElementsMixin = {
|
||||
getElements(this: FakeDriver, elementIds: string[]) {
|
||||
for (const elId of elementIds) {
|
||||
if (!_.has(this.elMap, elId)) {
|
||||
throw new errors.StaleElementReferenceError();
|
||||
}
|
||||
}
|
||||
return elementIds.map((e) => this.elMap[e]);
|
||||
},
|
||||
|
||||
getElement(this: FakeDriver, elementId: string) {
|
||||
return this.getElements([elementId])[0];
|
||||
},
|
||||
|
||||
async getName(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.tagName;
|
||||
},
|
||||
|
||||
async elementDisplayed(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.isVisible();
|
||||
},
|
||||
|
||||
async elementEnabled(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.isEnabled();
|
||||
},
|
||||
|
||||
async elementSelected(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.isSelected();
|
||||
},
|
||||
|
||||
async setValue(this: FakeDriver, keys: string | string[], elementId: string) {
|
||||
let value = keys;
|
||||
if (keys instanceof Array) {
|
||||
value = keys.join('');
|
||||
}
|
||||
const el = this.getElement(elementId);
|
||||
if (el.type !== 'MockInputField') {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
el.setAttr('value', value);
|
||||
},
|
||||
|
||||
async getText(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getAttr('value');
|
||||
},
|
||||
|
||||
async clear(this: FakeDriver, elementId: string) {
|
||||
await this.setValue('', elementId);
|
||||
},
|
||||
|
||||
/**
|
||||
* This comment should be displayed instead of the one from ExternalDriver
|
||||
*/
|
||||
async click(this: FakeDriver, elementId: string) {
|
||||
this.assertNoAlert();
|
||||
const el = this.getElement(elementId);
|
||||
if (!el.isVisible()) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
el.click();
|
||||
this.focusedElId = elementId;
|
||||
},
|
||||
|
||||
async getAttribute(this: FakeDriver, attr: string, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getAttr(attr);
|
||||
},
|
||||
|
||||
async getElementRect(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getElementRect();
|
||||
},
|
||||
|
||||
async getSize(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getSize();
|
||||
},
|
||||
|
||||
async equalsElement(this: FakeDriver, elementIdA: string, elementIdB: string) {
|
||||
const el1 = this.getElement(elementIdA);
|
||||
const el2 = this.getElement(elementIdB);
|
||||
return el1.equals(el2);
|
||||
},
|
||||
|
||||
async getCssProperty(this: FakeDriver, prop: string, elementId: string) {
|
||||
this.assertWebviewContext();
|
||||
const el = this.getElement(elementId);
|
||||
return el.getCss(prop);
|
||||
},
|
||||
|
||||
async getLocation(this: FakeDriver, elementId: string) {
|
||||
const el = this.getElement(elementId);
|
||||
return el.getLocation();
|
||||
},
|
||||
|
||||
async getLocationInView(this: FakeDriver, elementId: string) {
|
||||
return this.getLocation(elementId);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(ElementsMixin);
|
||||
@@ -1,118 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {errors} from 'appium/driver';
|
||||
import {FakeElement} from '../fake-element';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
getExistingElementForNode(node) {
|
||||
for (let [id, el] of _.toPairs(this.elMap)) {
|
||||
if (el.node === node) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
wrapNewEl(obj) {
|
||||
// first check and see if we already have a ref to this element
|
||||
let existingElId = this.getExistingElementForNode(obj);
|
||||
if (existingElId) {
|
||||
return {ELEMENT: existingElId};
|
||||
}
|
||||
|
||||
// otherwise add the element to the map
|
||||
this.maxElId++;
|
||||
this.elMap[this.maxElId.toString()] = new FakeElement(obj, this.appModel);
|
||||
return {ELEMENT: this.maxElId.toString()};
|
||||
},
|
||||
|
||||
/**
|
||||
* @template {boolean} Mult
|
||||
* @template [Ctx=any]
|
||||
* @param {string} strategy
|
||||
* @param {string} selector
|
||||
* @param {Mult} mult
|
||||
* @param {Ctx} [context]
|
||||
* @returns {Promise<Mult extends true ? Element[] : Element>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElOrEls(strategy, selector, mult, context) {
|
||||
let qMap = {
|
||||
xpath: 'xpathQuery',
|
||||
id: 'idQuery',
|
||||
'accessibility id': 'idQuery',
|
||||
'class name': 'classQuery',
|
||||
'tag name': 'classQuery',
|
||||
'css selector': 'cssQuery',
|
||||
};
|
||||
// TODO this error checking should probably be part of MJSONWP?
|
||||
if (!_.includes(_.keys(qMap), strategy)) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
if (selector === 'badsel') {
|
||||
throw new errors.InvalidSelectorError();
|
||||
}
|
||||
let els = this.appModel[qMap[strategy]](selector, context);
|
||||
|
||||
let retval;
|
||||
if (els.length) {
|
||||
if (mult) {
|
||||
let allEls = [];
|
||||
for (let el of els) {
|
||||
allEls.push(this.wrapNewEl(el));
|
||||
}
|
||||
retval = allEls;
|
||||
} else {
|
||||
retval = this.wrapNewEl(els[0]);
|
||||
}
|
||||
} else if (mult) {
|
||||
retval = [];
|
||||
} else {
|
||||
throw new errors.NoSuchElementError();
|
||||
}
|
||||
return /** @type {Mult extends true ? Element[] : Element} */ (retval);
|
||||
},
|
||||
|
||||
/**
|
||||
* This should override whatever's in ExternalDriver
|
||||
* @param {string} strategy Strategy
|
||||
* @param {string} selector Selector
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElement(strategy, selector) {
|
||||
return this.findElOrEls(strategy, selector, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElements(strategy, selector) {
|
||||
return this.findElOrEls(strategy, selector, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElementFromElement(strategy, selector, elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return this.findElOrEls(strategy, selector, false, el.xmlFragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElementsFromElement(strategy, selector, elementId) {
|
||||
let el = this.getElement(elementId);
|
||||
return this.findElOrEls(strategy, selector, true, el.xmlFragment);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Element} Element
|
||||
* @typedef {import('../driver').FakeDriver} FakeDriver
|
||||
*/
|
||||
@@ -0,0 +1,137 @@
|
||||
import _ from 'lodash';
|
||||
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 {mixin} from './mixin';
|
||||
|
||||
const {W3C_WEB_ELEMENT_IDENTIFIER} = util;
|
||||
interface FakeDriverFindMixin {
|
||||
getExistingElementForNode(node: FakeElement): string | null;
|
||||
wrapNewEl(obj: FakeElement): Element;
|
||||
|
||||
findElOrEls<Many extends boolean, Ctx = any>(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
many: Many,
|
||||
context?: Ctx
|
||||
): Promise<Many extends true ? Element[] : Element>;
|
||||
findElement(strategy: string, selector: string): Promise<Element>;
|
||||
findElements(strategy: string, selector: string): Promise<Element[]>;
|
||||
findElementFromElement(elementId: string, strategy: string, selector: string): Promise<Element>;
|
||||
findElementsFromElement(
|
||||
elementId: string,
|
||||
strategy: string,
|
||||
selector: string
|
||||
): Promise<Element[]>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
interface FakeDriver extends FakeDriverFindMixin {}
|
||||
}
|
||||
|
||||
const FindMixin: FakeDriverFindMixin = {
|
||||
getExistingElementForNode(this: FakeDriver, node) {
|
||||
for (const [id, el] of _.toPairs(this.elMap)) {
|
||||
if (el.node === node) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
wrapNewEl(this: FakeDriver, obj: FakeElement): Element {
|
||||
// first check and see if we already have a ref to this element
|
||||
const existingElId = this.getExistingElementForNode(obj);
|
||||
if (existingElId) {
|
||||
return {ELEMENT: existingElId, [W3C_WEB_ELEMENT_IDENTIFIER]: existingElId};
|
||||
}
|
||||
|
||||
// otherwise add the element to the map
|
||||
this.maxElId++;
|
||||
const maxElId = this.maxElId.toString();
|
||||
this.elMap[maxElId] = new FakeElement(obj, this.appModel);
|
||||
return {ELEMENT: maxElId, [W3C_WEB_ELEMENT_IDENTIFIER]: maxElId};
|
||||
},
|
||||
|
||||
async findElOrEls<Many extends boolean, Ctx = any>(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
many: Many,
|
||||
context: Ctx
|
||||
): Promise<Many extends true ? Element[] : Element> {
|
||||
const qMap = {
|
||||
xpath: 'xpathQuery',
|
||||
id: 'idQuery',
|
||||
'accessibility id': 'idQuery',
|
||||
'class name': 'classQuery',
|
||||
'tag name': 'classQuery',
|
||||
'css selector': 'cssQuery',
|
||||
} as Record<string, keyof FakeDriver['appModel']>;
|
||||
// TODO this error checking should probably be part of MJSONWP?
|
||||
if (!_.includes(_.keys(qMap), strategy)) {
|
||||
throw new errors.UnknownCommandError();
|
||||
}
|
||||
if (selector === 'badsel') {
|
||||
throw new errors.InvalidSelectorError();
|
||||
}
|
||||
const els = this.appModel[qMap[strategy]](selector, context);
|
||||
|
||||
let retval: Element | Element[];
|
||||
if (els.length) {
|
||||
if (many) {
|
||||
const allEls: Element[] = [];
|
||||
for (const el of els) {
|
||||
allEls.push(this.wrapNewEl(el));
|
||||
}
|
||||
retval = allEls;
|
||||
} else {
|
||||
retval = this.wrapNewEl(els[0]);
|
||||
}
|
||||
} else if (many) {
|
||||
retval = [];
|
||||
} else {
|
||||
throw new errors.NoSuchElementError();
|
||||
}
|
||||
return retval as Many extends true ? Element[] : Element;
|
||||
},
|
||||
|
||||
/**
|
||||
* This should override whatever's in ExternalDriver
|
||||
* @param {string} strategy Strategy
|
||||
* @param {string} selector Selector
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async findElement(this: FakeDriver, strategy: string, selector: string) {
|
||||
return this.findElOrEls(strategy, selector, false);
|
||||
},
|
||||
|
||||
async findElements(this: FakeDriver, strategy: string, selector: string) {
|
||||
return this.findElOrEls(strategy, selector, true);
|
||||
},
|
||||
|
||||
async findElementFromElement(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
elementId: string
|
||||
) {
|
||||
const el = this.getElement(elementId);
|
||||
return this.findElOrEls(strategy, selector, false, el.xmlFragment);
|
||||
},
|
||||
|
||||
async findElementsFromElement(
|
||||
this: FakeDriver,
|
||||
strategy: string,
|
||||
selector: string,
|
||||
elementId: string
|
||||
) {
|
||||
const el = this.getElement(elementId);
|
||||
return this.findElOrEls(strategy, selector, true, el.xmlFragment);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(FindMixin);
|
||||
@@ -1,147 +0,0 @@
|
||||
import {errors} from 'appium/driver';
|
||||
|
||||
const ORIENTATIONS = new Set(['LANDSCAPE', 'PORTRAIT']);
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async title() {
|
||||
this.assertWebviewContext();
|
||||
return this.appModel.title;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async keys(value) {
|
||||
if (!this.focusedElId) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
await this.setValue(value, this.focusedElId);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async setGeoLocation(location) {
|
||||
// TODO test this adequately once WD bug is fixed
|
||||
this.appModel.lat = location.latitude;
|
||||
this.appModel.long = location.longitude;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getGeoLocation() {
|
||||
return this.appModel.currentGeoLocation;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getPageSource() {
|
||||
return this.appModel.rawXml;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getOrientation() {
|
||||
return this.appModel.orientation;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../types').FakeDriverCaps['orientation']} o
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async setOrientation(o) {
|
||||
if (!ORIENTATIONS.has(o)) {
|
||||
throw new errors.UnknownError('Orientation must be LANDSCAPE or PORTRAIT');
|
||||
}
|
||||
this.appModel.orientation = o;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getScreenshot() {
|
||||
return this.appModel.getScreenshot();
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getWindowSize() {
|
||||
return {width: this.appModel.width, height: this.appModel.height};
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getWindowRect() {
|
||||
return {width: this.appModel.width, height: this.appModel.height, x: 0, y: 0};
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async performActions(actions) {
|
||||
this.appModel.actionLog.push(actions);
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async releaseActions() {},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async getLog(type) {
|
||||
switch (type) {
|
||||
case 'actions':
|
||||
return this.appModel.actionLog;
|
||||
default:
|
||||
throw new Error(`Don't understand log type '${type}'`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async mobileShake() {
|
||||
this.shook = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async doubleClick() {},
|
||||
|
||||
/**
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async execute(script, args) {
|
||||
return await this.executeMethod(script, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add two or maybe even three numbers
|
||||
*
|
||||
* @param {number} num1
|
||||
* @param {number} num2
|
||||
* @param {number} [num3]
|
||||
* @returns {Promise<number>}
|
||||
* @this {FakeDriver}
|
||||
*/
|
||||
async fakeAddition(num1, num2, num3 = 0) {
|
||||
return num1 + num2 + (num3 ?? 0);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('../driver').FakeDriver} FakeDriver
|
||||
*/
|
||||
@@ -0,0 +1,115 @@
|
||||
import {ActionSequence, Location, Rect, Size} from '@appium/types';
|
||||
import {errors} from 'appium/driver';
|
||||
import {FakeDriver, Orientation} from '../driver';
|
||||
import {mixin} from './mixin';
|
||||
|
||||
const ORIENTATIONS = new Set(['LANDSCAPE', 'PORTRAIT']);
|
||||
|
||||
interface FakeDriverGeneralMixin {
|
||||
title(): Promise<string>;
|
||||
keys(value: string | string[]): Promise<void>;
|
||||
setGeoLocation(location: Location): Promise<void>;
|
||||
getGeoLocation(): Promise<Location>;
|
||||
getPageSource(): Promise<string>;
|
||||
getOrientation(): Promise<string>;
|
||||
setOrientation(orientation: Orientation): Promise<void>;
|
||||
getScreenshot(): Promise<string>;
|
||||
getWindowSize(): Promise<Size>;
|
||||
getWindowRect(): Promise<Rect>;
|
||||
performActions(actions: ActionSequence[]): Promise<void>;
|
||||
releaseActions(): Promise<void>;
|
||||
mobileShake(): Promise<void>;
|
||||
doubleClick(): Promise<void>;
|
||||
execute(script: string, args: any[]): Promise<any>;
|
||||
fakeAddition(a: number, b: number, c?: number): Promise<number>;
|
||||
getLog(type: string): Promise<any[]>;
|
||||
}
|
||||
|
||||
declare module '../driver' {
|
||||
interface FakeDriver extends FakeDriverGeneralMixin {}
|
||||
}
|
||||
|
||||
const GeneralMixin: FakeDriverGeneralMixin = {
|
||||
async title(this: FakeDriver) {
|
||||
this.assertWebviewContext();
|
||||
return this.appModel.title;
|
||||
},
|
||||
|
||||
async keys(this: FakeDriver, value: string | string[]) {
|
||||
if (!this.focusedElId) {
|
||||
throw new errors.InvalidElementStateError();
|
||||
}
|
||||
await this.setValue(value, this.focusedElId);
|
||||
},
|
||||
|
||||
async setGeoLocation(this: FakeDriver, location: Location) {
|
||||
// TODO test this adequately once WD bug is fixed
|
||||
this.appModel.lat = location.latitude;
|
||||
this.appModel.long = location.longitude;
|
||||
},
|
||||
|
||||
async getGeoLocation(this: FakeDriver) {
|
||||
return this.appModel.currentGeoLocation;
|
||||
},
|
||||
|
||||
async getPageSource(this: FakeDriver) {
|
||||
return this.appModel.rawXml;
|
||||
},
|
||||
|
||||
async getOrientation(this: FakeDriver) {
|
||||
return this.appModel.orientation;
|
||||
},
|
||||
|
||||
async setOrientation(this: FakeDriver, o: Orientation) {
|
||||
if (!ORIENTATIONS.has(o)) {
|
||||
throw new errors.UnknownError('Orientation must be LANDSCAPE or PORTRAIT');
|
||||
}
|
||||
this.appModel.orientation = o;
|
||||
},
|
||||
|
||||
async getScreenshot(this: FakeDriver) {
|
||||
return this.appModel.getScreenshot();
|
||||
},
|
||||
|
||||
async getWindowSize(this: FakeDriver) {
|
||||
return {width: this.appModel.width, height: this.appModel.height};
|
||||
},
|
||||
|
||||
async getWindowRect(this: FakeDriver) {
|
||||
return {width: this.appModel.width, height: this.appModel.height, x: 0, y: 0};
|
||||
},
|
||||
|
||||
async performActions(this: FakeDriver, actions: ActionSequence[]) {
|
||||
this.appModel.actionLog.push(actions);
|
||||
},
|
||||
|
||||
async releaseActions(this: FakeDriver) {},
|
||||
|
||||
async getLog(this: FakeDriver, type: string): Promise<ActionSequence[][]> {
|
||||
switch (type) {
|
||||
case 'actions':
|
||||
return this.appModel.actionLog;
|
||||
default:
|
||||
throw new Error(`Don't understand log type '${type}'`);
|
||||
}
|
||||
},
|
||||
|
||||
async mobileShake(this: FakeDriver) {
|
||||
this.shook = true;
|
||||
},
|
||||
|
||||
async doubleClick(this: FakeDriver) {},
|
||||
|
||||
async execute(this: FakeDriver, script: string, args: any[]) {
|
||||
return await this.executeMethod(script, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add two or maybe even three numbers
|
||||
*/
|
||||
async fakeAddition(this: FakeDriver, num1: number, num2: number, num3 = 0) {
|
||||
return num1 + num2 + (num3 ?? 0);
|
||||
},
|
||||
};
|
||||
|
||||
mixin(GeneralMixin);
|
||||
+14
-5
@@ -1,8 +1,17 @@
|
||||
export {default as alert} from './alert';
|
||||
export {default as contexts} from './contexts';
|
||||
export {default as element} from './element';
|
||||
export {default as find} from './find';
|
||||
export {default as general} from './general';
|
||||
/**
|
||||
* Export all of the "stuff" from these mixin files. The mixins themselves are not exported,
|
||||
* but any types/interfaces that they export (e.g., options objects for some command) are exported.
|
||||
*
|
||||
* Mixins must not use `Object.assign()` and are expected to use the `mixin` function in the sibling
|
||||
* `mixin` module.
|
||||
* @module
|
||||
*/
|
||||
|
||||
export * from './alert';
|
||||
export * from './contexts';
|
||||
export * from './element';
|
||||
export * from './find';
|
||||
export * from './general';
|
||||
|
||||
/* // TODO:
|
||||
//rest.post('/wd/hub/session/:sessionId?/touch/click', controller.doClick);
|
||||
@@ -0,0 +1,13 @@
|
||||
import {FakeDriver} from '../driver';
|
||||
|
||||
/**
|
||||
* This function assigns a mixin `T` to the `FakeDriver` class' prototype.
|
||||
* While each mixin has its own interface which is (in isolation) unrelated to `FakeDriver`, the constraint
|
||||
* on this generic type `T` is that it must be a partial of `FakeDriver`'s interface. This enforces
|
||||
* that it does not conflict with the existing interface of `FakeDriver`. In that way, you can
|
||||
* think of it as a type guard.
|
||||
* @param mixin Mixin implementation
|
||||
*/
|
||||
export function mixin<T extends Partial<FakeDriver>>(mixin: T): void {
|
||||
Object.assign(FakeDriver.prototype, mixin);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import B from 'bluebird';
|
||||
import {BaseDriver, errors} from 'appium/driver';
|
||||
import {deprecatedCommandsLogged} from '@appium/base-driver/build/lib/protocol/protocol';
|
||||
import {FakeApp} from './fake-app';
|
||||
import {alert, contexts, element, find, general} from './commands';
|
||||
|
||||
const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
|
||||
app: {
|
||||
@@ -15,22 +14,50 @@ const FAKE_DRIVER_CONSTRAINTS = /** @type {const} */ ({
|
||||
});
|
||||
|
||||
/**
|
||||
* Constraints for {@linkcode FakeDriver}'s capabilites
|
||||
* @typedef {typeof FAKE_DRIVER_CONSTRAINTS} FakeDriverConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template [Thing=any]
|
||||
* @extends {BaseDriver<FakeDriverConstraints>}
|
||||
* @implements {ExternalDriver<FakeDriverConstraints>}
|
||||
*/
|
||||
export class FakeDriver extends BaseDriver {
|
||||
desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
|
||||
/**
|
||||
* @type {FakeDriverConstraints}
|
||||
* @readonly
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/** @type {string} */
|
||||
curContext;
|
||||
|
||||
appModel = new FakeApp();
|
||||
/** @type {FakeApp} */
|
||||
appModel;
|
||||
|
||||
constructor(opts = {}, shouldValidateCaps = true) {
|
||||
/** @type {boolean} */
|
||||
_proxyActive;
|
||||
|
||||
/** @type {boolean} */
|
||||
shook;
|
||||
|
||||
/** @type {string?} */
|
||||
focusedElId;
|
||||
|
||||
/** @type {Thing?} */
|
||||
fakeThing;
|
||||
|
||||
/** @type {number} */
|
||||
maxElId;
|
||||
|
||||
/** @type {Record<string,import('./fake-element').FakeElement>} */
|
||||
elMap;
|
||||
|
||||
constructor(
|
||||
opts = /** @type {import('@appium/types').DriverOpts<FakeDriverConstraints>} */ ({}),
|
||||
shouldValidateCaps = true
|
||||
) {
|
||||
super(opts, shouldValidateCaps);
|
||||
this.curContext = 'NATIVE_APP';
|
||||
this.elMap = {};
|
||||
@@ -39,6 +66,8 @@ export class FakeDriver extends BaseDriver {
|
||||
this.fakeThing = null;
|
||||
this._proxyActive = false;
|
||||
this.shook = false;
|
||||
this.appModel = new FakeApp();
|
||||
this.desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
|
||||
}
|
||||
|
||||
proxyActive() {
|
||||
@@ -112,7 +141,7 @@ export class FakeDriver extends BaseDriver {
|
||||
/**
|
||||
* Set the 'thing' value (so that it can be retrieved later)
|
||||
*
|
||||
* @param {any} thing
|
||||
* @param {Thing} thing
|
||||
* @returns {Promise<null>}
|
||||
*/
|
||||
async setFakeThing(thing) {
|
||||
@@ -201,86 +230,17 @@ export class FakeDriver extends BaseDriver {
|
||||
res.send(JSON.stringify(cliArgs));
|
||||
});
|
||||
}
|
||||
|
||||
/*********
|
||||
* ALERT *
|
||||
*********/
|
||||
assertNoAlert = alert.assertNoAlert;
|
||||
assertAlert = alert.assertAlert;
|
||||
getAlertText = alert.getAlertText;
|
||||
setAlertText = alert.setAlertText;
|
||||
postAcceptAlert = alert.postAcceptAlert;
|
||||
postDismissAlert = alert.postDismissAlert;
|
||||
|
||||
/************
|
||||
* CONTEXTS *
|
||||
************/
|
||||
getRawContexts = contexts.getRawContexts;
|
||||
assertWebviewContext = contexts.assertWebviewContext;
|
||||
getCurrentContext = contexts.getCurrentContext;
|
||||
getContexts = contexts.getContexts;
|
||||
setContext = contexts.setContext;
|
||||
setFrame = contexts.setFrame;
|
||||
|
||||
/************
|
||||
* ELEMENTS *
|
||||
************/
|
||||
getElements = element.getElements;
|
||||
getElement = element.getElement;
|
||||
getName = element.getName;
|
||||
elementDisplayed = element.elementDisplayed;
|
||||
elementEnabled = element.elementEnabled;
|
||||
elementSelected = element.elementSelected;
|
||||
setValue = element.setValue;
|
||||
getText = element.getText;
|
||||
clear = element.clear;
|
||||
click = element.click;
|
||||
getAttribute = element.getAttribute;
|
||||
getElementRect = element.getElementRect;
|
||||
getSize = element.getSize;
|
||||
equalsElement = element.equalsElement;
|
||||
getCssProperty = element.getCssProperty;
|
||||
getLocation = element.getLocation;
|
||||
getLocationInView = element.getLocationInView;
|
||||
|
||||
/********
|
||||
* FIND *
|
||||
********/
|
||||
getExistingElementForNode = find.getExistingElementForNode;
|
||||
wrapNewEl = find.wrapNewEl;
|
||||
findElOrEls = find.findElOrEls;
|
||||
findElement = find.findElement;
|
||||
findElements = find.findElements;
|
||||
findElementFromElement = find.findElementFromElement;
|
||||
findElementsFromElement = find.findElementsFromElement;
|
||||
|
||||
/***********
|
||||
* GENERAL *
|
||||
***********/
|
||||
title = general.title;
|
||||
keys = general.keys;
|
||||
setGeoLocation = general.setGeoLocation;
|
||||
getGeoLocation = general.getGeoLocation;
|
||||
getPageSource = general.getPageSource;
|
||||
getOrientation = general.getOrientation;
|
||||
setOrientation = general.setOrientation;
|
||||
getScreenshot = general.getScreenshot;
|
||||
getWindowSize = general.getWindowSize;
|
||||
getWindowRect = general.getWindowRect;
|
||||
performActions = general.performActions;
|
||||
getLog = general.getLog;
|
||||
mobileShake = general.mobileShake;
|
||||
doubleClick = general.doubleClick;
|
||||
execute = general.execute;
|
||||
fakeAddition = general.fakeAddition;
|
||||
releaseActions = general.releaseActions;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from './commands';
|
||||
|
||||
export default FakeDriver;
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').FakeDriverCaps} FakeDriverCaps
|
||||
* @typedef {import('./types').W3CFakeDriverCaps} W3CFakeDriverCaps
|
||||
* @typedef {import('@appium/types').Element} Element
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -292,3 +252,7 @@ export default FakeDriver;
|
||||
* @template {import('@appium/types').Constraints} C
|
||||
* @typedef {import('@appium/types').ExternalDriver<C>} ExternalDriver
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Orientation} Orientation
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@ import {FakeElement} from './fake-element';
|
||||
|
||||
const SCREENSHOT = path.join(__dirname, 'screen.png');
|
||||
|
||||
class FakeApp {
|
||||
export class FakeApp {
|
||||
constructor() {
|
||||
this.dom = null;
|
||||
this.activeDom = null;
|
||||
@@ -20,7 +20,9 @@ class FakeApp {
|
||||
this._width = null;
|
||||
this._height = null;
|
||||
this.rawXml = '';
|
||||
/** @type {import('./driver').Orientation} */
|
||||
this.currentOrientation = 'PORTRAIT';
|
||||
/** @type {import('@appium/types').ActionSequence[][]} */
|
||||
this.actionLog = [];
|
||||
}
|
||||
|
||||
@@ -176,8 +178,6 @@ class FakeWebView {
|
||||
}
|
||||
}
|
||||
|
||||
export {FakeApp};
|
||||
|
||||
/**
|
||||
* @typedef {Element & {data: any}} ElementWithData
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import _ from 'lodash';
|
||||
import XMLDom from '@xmldom/xmldom';
|
||||
|
||||
class FakeElement {
|
||||
export class FakeElement {
|
||||
/** @type {FakeApp} */
|
||||
app;
|
||||
|
||||
/** @type {string} */
|
||||
type;
|
||||
|
||||
/** @type {Record<string,string>} */
|
||||
nodeAttrs;
|
||||
|
||||
/**
|
||||
* @param {any} xmlNode
|
||||
* @param {FakeApp} app
|
||||
*/
|
||||
constructor(xmlNode, app) {
|
||||
this.app = app;
|
||||
this.node = xmlNode;
|
||||
@@ -53,8 +66,8 @@ class FakeElement {
|
||||
|
||||
getLocation() {
|
||||
return {
|
||||
x: parseFloat(this.nodeAttrs.left || 0),
|
||||
y: parseFloat(this.nodeAttrs.top || 0),
|
||||
x: parseFloat(this.nodeAttrs.left || '0'),
|
||||
y: parseFloat(this.nodeAttrs.top || '0'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,8 +77,8 @@ class FakeElement {
|
||||
|
||||
getSize() {
|
||||
return {
|
||||
width: parseFloat(this.nodeAttrs.width || 0),
|
||||
height: parseFloat(this.nodeAttrs.height || 0),
|
||||
width: parseFloat(this.nodeAttrs.width || '0'),
|
||||
height: parseFloat(this.nodeAttrs.height || '0'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,4 +112,6 @@ class FakeElement {
|
||||
}
|
||||
}
|
||||
|
||||
export {FakeElement};
|
||||
/**
|
||||
* @typedef {import('./fake-app').FakeApp} FakeApp
|
||||
*/
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
throw Error('Unsuccessfully ran the script');
|
||||
import ora from 'ora';
|
||||
|
||||
const spinner = ora('Running fake-error...').start();
|
||||
|
||||
setTimeout(() => {
|
||||
spinner.fail('Oh nooooooo!');
|
||||
throw Error('Unsuccessfully ran the script');
|
||||
}, 1000);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import readline from 'node:readline';
|
||||
|
||||
const rl = readline.createInterface({input: process.stdin, output: process.stderr});
|
||||
|
||||
rl.question('Press ENTER to continue: ', () => {
|
||||
rl.close();
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('You did it!');
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "5.2.0",
|
||||
"version": "5.2.1",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -75,7 +75,8 @@
|
||||
"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-success": "./build/lib/scripts/fake-success.js",
|
||||
"fake-stdin": "./build/lib/scripts/fake-stdin.js"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user