Compare commits

...
Author SHA1 Message Date
Christopher Hiller 872b924a97 chore: publish
- appium@2.0.0-beta.57
 - @appium/base-driver@9.3.3
 - @appium/base-plugin@2.2.3
 - @appium/doctor@2.0.9
 - @appium/docutils@0.3.0
 - @appium/driver-test-support@0.4.1
 - @appium/eslint-config-appium-ts@0.2.0
 - @appium/eslint-config-appium@8.0.1
 - @appium/execute-driver-plugin@3.0.8
 - @appium/fake-driver@5.2.1
 - @appium/fake-plugin@3.1.7
 - @appium/images-plugin@2.0.8
 - @appium/opencv@2.0.8
 - @appium/plugin-test-support@0.3.9
 - @appium/schema@0.2.6
 - @appium/support@3.1.7
 - @appium/test-support@3.0.9
 - @appium/typedoc-plugin-appium@0.5.1
 - @appium/types@0.10.1
2023-03-08 11:43:18 -08:00
Dor Blayzer 0e164ba03d docs: Fix plugins link
fix broken officially-supported plugins link
2023-03-08 11:35:32 -08:00
Christopher Hiller 2359a2ba3b fix(support): remove all logging from env module
Currently there is no way to specify a log level when running extension commands (e.g., `appium driver list`).  Further, the log level used by the support module depends on whether or not Appium set it; used outside of an Appium context, there is no way to programmatically set the log level of the support module via an external API.
To that end, I just removed the log messages, because a "proper" fix will be quite involved.  See #16970.
2023-03-08 11:34:31 -08:00
renovate[bot] 49694f50a4 fix(execute-driver-plugin): update dependency webdriverio to v7.30.2 2023-03-08 11:05:39 +00:00
renovate[bot] a2cfb832ad chore(deps): update dependency json-schema-to-typescript to v12 2023-03-07 14:27:47 -08:00
Christopher Hiller 4cfad004db feat(docutils): add --upgrade flag to init
users can run `appium-docs init --upgrade` to upgrade python deps (e.g., mkdocs, mkdocs-material)
2023-03-07 14:27:16 -08:00
Christopher Hiller b159780fd6 chore(docutils): fix start script 2023-03-07 14:27:16 -08:00
renovate[bot] e2f7dd7154 chore(deps): update dependency eslint-config-prettier to v8.7.0 2023-03-07 10:55:56 +00:00
Christopher Hiller e9dae3f6d0 fix(appium,fake-driver): expose child process when running an extension script
This change makes it so when running an extension script (e.g., `appium driver foo run some-script`), unless the `--json` flag is present, the child process will do no buffering whatsoever and will inherit `process.stdin`, `process.stdout` and `process.stderr` from Appium.

This means that extension scripts can do stuff like prompt the user for input.  It _also_ means they can do stuff like display progress bars and spinners.

If the user wants JSON data, we revert to the old behavior, which is to buffer the output and dump it once the script has completed.
2023-03-06 15:46:50 -08:00
Christopher Hiller 58b4790b1d chore: lint all the typescript 2023-03-06 15:21:58 -08:00
Christopher Hiller 63eddb670f docs(eslint-config-appium): update some stuff in README.md 2023-03-06 15:21:58 -08:00
Christopher Hiller b7d86c0d04 chore(eslint-config-appium): fix broken keywords 2023-03-06 15:21:58 -08:00
Christopher Hiller dfe02ca98c feat(eslint-config-appium-ts): create new pkg @appium/eslint-config-appium-ts
This adds a new package which consumers having TS sources can use.  This does not affect current users of `@appium/eslint-config-appium`.

Due to the nature of TS, it can be a little more bold with fixes (e.g., changing let to const). I changed the following rules from the recommended settings:

no-empty-function: Reduced to a warning. It's likely we want the functions, but perhaps they should not be empty.
no-empty-interface: Disabled; we use these to merge mixin interfaces with driver classes.
no-explicit-any: Disabled; while transitioning from an untyped codebase to a typed one, we still need to use any in places. Eventually these should all be replaced.
no-non-null-assertion: Disabled; This allows us to continue use of the ! operator, which is handy when working with untyped or loosely-typed code (especially in tests).
no-var-requires: Reduced to a warning. Occasionally we may need to use require() instead of import.
Since the configuration extends our shared config, the following rules have been disabled:

import/no-unresolved: The eslint-plugin-import module cannot resolve TS modules.
no-unused-vars: This rule conflicts with typescript-eslint's rule, so it must be disabled per the documentation.
promise/no-native: In our TS-based packages, I haven't needed Bluebird, so it doesn't make sense to require it.
require-await: TS is much more aware of the return values of functions; this is not needed to avoid ambiguity (in TS code).
2023-03-06 15:21:58 -08:00
renovate[bot] 10e6815a39 chore(docutils): update dependency mkdocs-material to v9.1.1 2023-03-06 14:01:53 -08:00
Christopher Hiller cd76622aa9 fix(typedoc-plugin-appium): replace void responses with 'null'
The main thrust of this was to resolve #18269.

Implementation-wise, we have to modify an `IntrinsicType` of which both `void` and `undefined` become.  To make it `null`, we must create a new `LiteralType` (`null` is not an "intrinsic" type; I don't know why).  I would think that TypeDoc wouldn't go around creating new `LiteralType`s every time it sees `null`, but I don't see it doing anything else. Anyway, if there is a "type cache" somewhere, I'm not sure how to get at it.

Additionally, the new reflections created for parameters and the call signature were not being "registered" properly with TypeDoc.  So this adds a `static create` to both `CommandData` and `ExecMethodData` which wraps the constructor for each, respectively, then performs the registration.  This keeps a `Context` object _out_ of `CommandData`/`ExecMethodData` instances (it would only be used once at time of instantiation anyway).

Un-memoized a couple methods in `BaseCommandData` since not necessary
2023-03-06 13:18:23 -08:00
Christopher Hiller 296cb2436c chore: add auto-labeler workflow
This will automatically label PRs. We can update it to further automatically label issues at a later time, if necessary.

I have added labels for each package.
2023-03-06 13:12:32 -08:00
renovate[bot] 01bff49297 fix(images-plugin): update dependency lru-cache to v7.18.3 2023-03-06 13:34:00 +00:00
renovate[bot] 48c331157f fix(images-plugin): update dependency lru-cache to v7.18.2 2023-03-05 12:47:18 +00:00
Kazuaki Matsuo 9f8daf385b docs(appium): update migration guide in Japanese (#18297)
* docs(appium): update migration gude in js

* Update migrating-1-to-2.md
2023-03-05 17:00:32 +09:00
renovate[bot] 7bae977ca9 chore(deps): update dependency @types/node to v18.14.6 2023-03-04 12:04:54 +00:00
renovate[bot] 4ed745a95f fix(docutils): update dependency lilconfig to v2.1.0 2023-03-04 06:38:14 +00:00
renovate[bot] 85e4bd8925 fix(support): update dependency pngjs to v7 2023-03-03 11:38:15 -08:00
Christopher Hiller cce27fcf88 chore(docutils): review updates
- Use `util.pluralize()` from `@appium/support` instead of `pluralize` directly
- Write some more docstrings
2023-03-03 11:36:00 -08:00
Christopher Hiller b30b2cfb84 feat(docutils): better path validation
This adds up-front checking of user-provided custom paths to avoid doing a bunch of work and then failing later ("fail fast")
2023-03-03 11:36:00 -08:00
Christopher Hiller 9ccbab5dc0 fix(appium,support): fs.readPackageJsonFrom() returns proper type
This function always returns a `NormalizedPackageJson`, so let's be explicit about that.
2023-03-03 11:36:00 -08:00
Christopher Hiller 760bb5b8f3 chore(appium): skip auto-install of extensions if monorepo detected 2023-03-03 10:51:52 -08:00
renovate[bot] cb14faae33 chore(deps): update dependency @types/node to v18.14.5 2023-03-03 12:08:20 +00:00
Mykola MokhnachandChristopher Hiller 2a6a13ac89 fix(support): Improve error description on image load (#18284)
Co-authored-by: Christopher Hiller <boneskull@boneskull.com>
2023-03-03 06:06:52 +01:00
Christopher Hiller 4b43af0709 chore: fix dupe in clean script 2023-03-02 15:49:33 -08:00
Christopher Hiller 504415d0cd chore: build must pass on local npm install 2023-03-02 15:49:33 -08:00
Christopher Hiller 75defa7b48 chore: add prepack script
sometimes (when?) the smoke tests can pack the workspaces before they have actually been built, which will of course cause an error. this adds a prepack script to avoid the problem

removes pack-all script, which I added for reasons
2023-03-02 15:49:33 -08:00
Christopher Hiller be86627595 fix(base-driver): use new mixin strategy
also fix a broken type in fake-driver
2023-03-02 15:47:24 -08:00
Christopher Hiller 080eb8aa1b chore(opencv): increase timeout for unit test
this can take awhile when exercising many cores
2023-03-02 15:01:13 -08:00
Christopher Hiller 1b70551055 fix(base-driver): remove needless static prop breaking the build 2023-03-02 15:01:13 -08:00
Christopher Hiller d262271b26 chore(appium): InnerDriver is actually an ExternalDriver
While `findMatchingDriver()` returns a `MatchedDriver`--which is a wrapper around `DriverClass`--the instance of `DriverClass`' constructor is a `Driver`.  But in the case of `AppiumDriver`, we know `InnerDriver` is actually an `ExternalDriver`; this is why we can assign to `server`, `serverHost`, etc.
2023-03-02 15:01:13 -08:00
Christopher Hiller 45b026de67 chore(base-plugin): better type handling in executeMethod 2023-03-02 15:01:13 -08:00
Christopher Hiller 843b797750 chore(driver-test-support): fix broken types in some e2e tests 2023-03-02 15:01:13 -08:00
Christopher Hiller b4c1403c22 fix(fake-driver): fix a broken type 2023-03-02 15:01:13 -08:00
Christopher Hiller e2aa3d3b32 fix(types): type fixes & improvements
moved method-map/execute-method-map-related stuff into its own file, and made the types more expressive.  changed some default parameters to hopefully avoid errors about conflicting constraints
2023-03-02 15:01:13 -08:00
Christopher Hiller b151e4c9bf chore: add dummy test script 2023-03-02 15:01:13 -08:00
Christopher Hiller 5684c4e286 fix(types): callback to implicitWaitForCondition accepts args 2023-03-02 15:01:13 -08:00
Christopher Hiller 189e39d03c fix(docutils): use material theme 2023-03-02 15:00:50 -08:00
renovate[bot] 694f9c6a63 chore(deps): update dependency @types/node to v18.14.4 2023-03-02 15:00:57 +00:00
Christopher Hiller f80f4cb2bc feat(docutils): allow "%s" as version number in commit msg arg 2023-03-01 16:32:00 -08:00
Mykola Mokhnach 908041c525 chore: Remove obsolete jpeg-js version override (#18278) 2023-03-01 22:43:21 +01:00
Christopher Hiller 18c0b4b230 chore(docutils): remove monkeypatching of typedoc 2023-03-01 13:19:39 -08:00
Mykola Mokhnach 4e2423d0f0 docs: Update install driver from github command 2023-03-01 20:36:02 +01:00
renovate[bot] ed1d3aae61 fix(images-plugin): update dependency lru-cache to v7.18.1 2023-03-01 17:36:54 +00:00
renovate[bot] d1d5ecef87 fix(images-plugin): update dependency lru-cache to v7.17.2 2023-03-01 12:30:17 +00:00
Kazuaki Matsuo b88f6e3e86 docs: update Copyright in LCIENSE
from JS foundation to openjs foundation
2023-02-28 13:30:25 -08:00
renovate[bot] 471a4b57e6 fix(types): update dependency type-fest to v3.6.1 2023-02-28 05:28:06 +00:00
Christopher Hiller feebf72eb4 chore(fake-driver): avoid public class field assignment
This changes the `fake-driver` implementation to use declaration merging instead of other alternatives to mixins.

History:

The original code simply assigned the mixin methods to `FakeDriver.prototype` after the `class` definition. In TS, this doesn't fly; you need to be explicit about what class members exist.

To get around this, we've tried multiple different approaches:

1. Use a "mixin pattern" as explained in the official TypeScript documentation, which involves _class factories_.  This is tedious and difficult to reason about; each mixin must be passed to the next in-line, and we must be explicit about the base class and which _previous_ mixins any given mixin expects.
2. Use public class fields (a recent addition to JS).  Basically, these do not work the way you want them to; if a subclass overrides a public field, a method in the superclass which refers to this public field _will use the field as defined in the superclass_--not the subclass. This is different than how methods and accessors work.  This inhibits use of inheritance.
3. Add a method _for each method in each mixin_ to the base class, which simply calls `Function.prototype.apply` on the mixin with the context as the base class' `this`.  This is too verbose.

There may be a better way to do this, but where we've landed is using _declaration merging_ in TypeScript.  It allows us to add (but not remove, and I'm not sure about "modify") properties to the "FakeDriver interface", which seems to be the implicit TS interface created by the `class` keyword.  Since the declaration merging syntax is not expressible in JavaScript, this requires a `.ts` file.  To avoid creating _n_ extra files, it was easier to just make the mixins `.ts` files (you may note that we can mix-and-match JS and TS in our projects at will).

The implementation of each mixin uses `Object.assign()` to put new methods on the prototype, which are previously defined in the merged declaration above.  Finally, a `satisfies` clause (which is a transient type-check; it does not change any types but it _will_ generate a compiler error) ensures the props being added to the prototype match what we've defined.

You'll also note that each method has a `this` parameter, which is not a "real" parameter--it's just an annotation for TS which tells the method what the  context is.  If we hate that, an alternative is to use `FakeDriver.prototype.method = <method>` instead of the `Object.assign` call.  This feels marginally more tedious, but I could go either way.
2023-02-27 14:15:11 -08:00
Christopher Hiller 0f196bad4a chore(types,base-driver): move/remove generics and constraints in Driver
These types were "wrong" in that it is exceedingly difficult to successfully implement `getLog()`, `getLogTypes()`, `supportLogTypes`, etc., in a third-party driver because you'd need to implement _all of it_ or _none of it_.  It's especially bad because it does this without prescribing where the logs should be stored (which is by design).  Oops.

This also moves some generics in the "find" mixins.  This may change again later, but it's unclear that any given driver must declare what a "context" is, and that there can only be one per driver.
2023-02-27 14:15:11 -08:00
renovate[bot] 431092dc87 fix(docutils): update dependency typedoc to v0.23.26 2023-02-27 20:25:05 +00:00
renovate[bot] 935c70683f chore(docutils): update dependency mkdocs-material to v9.0.15 2023-02-27 15:06:51 +00:00
renovate[bot] f8099dafa7 chore(deps): update dependency @types/node to v18.14.2 2023-02-27 10:31:03 +00:00
Kazuaki Matsuo 25c9728f07 docs: update readme (#18251)
* docs: update readme

* Update README.md
2023-02-26 17:24:49 -08:00
renovate[bot] ba6b312244 chore(deps): update dependency eslint to v8.35.0 2023-02-26 13:02:12 +00:00
renovate[bot] 188ed161e4 fix(support): update dependency jimp to v0.22.7 2023-02-25 09:50:30 +00:00
renovate[bot] 96898c87d5 fix(support): update dependency @types/glob to v8.1.0 2023-02-25 02:14:49 +00:00
148 changed files with 3376 additions and 2316 deletions
-3
View File
@@ -4,8 +4,5 @@ examples/javascript-wd
**/build/**
**/*.min.js
sample-code
**/*.d.ts
**/build-fixtures/**
**/*.ts
packages/appium/docs/**/assets/**
**/*.tsx
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "@appium/eslint-config-appium",
"extends": ["@appium/eslint-config-appium", "@appium/eslint-config-appium-ts"],
"overrides": [
{
"files": "packages/fake-driver/**/*",
+143
View File
@@ -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/**']
+22
View File
@@ -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 }}'
+1 -1
View File
@@ -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.
+3 -6
View File
@@ -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
+661 -417
View File
File diff suppressed because it is too large Load Diff
+13 -12
View File
@@ -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"
},
+15
View File
@@ -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)
+3 -6
View File
@@ -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.
+1 -1
View File
@@ -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
+6 -1
View File
@@ -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,
});
}
/**
+70 -21
View File
@@ -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}
*/
/**
+6 -1
View File
@@ -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,
});
}
/**
+3 -2
View File
@@ -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' : ''}`;
+9 -9
View File
@@ -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);
});
});
});
});
+17
View File
@@ -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 -11
View File
@@ -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
*/
/**
+5 -5
View File
@@ -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;
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.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
+14 -10
View File
@@ -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
*/
+3 -3
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.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)
+2 -2
View File
@@ -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",
+21
View File
@@ -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)
+9 -3
View File
@@ -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 {
+5
View File
@@ -1,3 +1,8 @@
/**
* Exports APIs used by build-related commands
* @module
*/
export * from './deploy';
export * from './site';
export * from './reference';
+2 -3
View File
@@ -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
+2 -22
View File
@@ -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 -5
View File
@@ -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,
+87
View File
@@ -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;
}
+61 -43
View File
@@ -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';
+54 -29
View File
@@ -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>;
+33 -25
View File
@@ -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>;
+23 -4
View File
@@ -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);
});
+5
View File
@@ -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
*/
+6
View File
@@ -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 {}
+6 -3
View File
@@ -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);
+5
View File
@@ -1,3 +1,8 @@
/**
* Main entry point
* @module
*/
export * from './mike';
export * from './builder';
export * from './validate';
+23 -6
View File
@@ -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;
}
>;
+5
View File
@@ -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';
+4 -1
View File
@@ -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']>
+10 -9
View File
@@ -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 {
+7 -9
View File
@@ -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 -1
View File
@@ -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
+11
View File
@@ -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)
+3 -11
View File
@@ -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
+12 -2
View File
@@ -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
*/
+3 -3
View File
@@ -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))
+2 -2
View File
@@ -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)
+4 -9
View File
@@ -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)
+2 -2
View File
@@ -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"
+12
View File
@@ -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);
@@ -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);
-118
View File
@@ -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
*/
+137
View File
@@ -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);
@@ -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);
}
+42 -78
View File
@@ -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
*/
+3 -3
View File
@@ -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
*/
+21 -6
View File
@@ -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!');
});
+3 -2
View File
@@ -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