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
Christopher Hiller 7b49356322 chore: publish
- appium@2.0.0-beta.56
 - @appium/base-driver@9.3.2
 - @appium/base-plugin@2.2.2
 - @appium/doctor@2.0.8
 - @appium/docutils@0.2.2
 - @appium/driver-test-support@0.4.0
 - @appium/execute-driver-plugin@3.0.7
 - @appium/fake-driver@5.2.0
 - @appium/fake-plugin@3.1.6
 - @appium/images-plugin@2.0.7
 - @appium/opencv@2.0.7
 - @appium/plugin-test-support@0.3.8
 - @appium/schema@0.2.5
 - @appium/support@3.1.6
 - @appium/test-support@3.0.8
 - @appium/typedoc-plugin-appium@0.5.0
 - @appium/types@0.10.0
 - @appium/universal-xml-plugin@1.0.6
2023-02-24 13:43:18 -08:00
renovate[bot] bfc3134968 chore(docutils): update dependency mkdocs-material to v9.0.14 2023-02-24 15:37:44 +00:00
renovate[bot] dbaf8d4cae chore(deps): update dependency @types/node to v18.14.1 2023-02-24 10:13:05 +00:00
renovate[bot] eb73be8303 fix(images-plugin): update dependency lru-cache to v7.17.0 2023-02-23 22:18:02 +00:00
renovate[bot] 998514fcce fix(support): update dependency jimp to v0.22.5 2023-02-23 19:21:23 +00:00
renovate[bot] 49f157d63e fix(support): update dependency axios to v1.3.4 2023-02-23 16:23:10 +00:00
renovate[bot] 43b115bb3d chore(deps): update dependency json-schema-to-typescript to v11.0.5 2023-02-23 09:44:48 +00:00
Christopher Hiller 56cf504a2b chore(appium): avoid public class field assignment
This changes `AppiumDriver` to avoid assignment to class properties, and changes the properties on `Schema` to be actually-private instead of pseudoprivate.
2023-02-22 13:40:55 -08:00
Christopher Hiller 2ebb16b077 chore(base-driver): avoid public class field assignment
this should avert potential future bugs when drivers attempt to override these properties
2023-02-22 13:40:55 -08:00
Christopher Hiller c321441451 chore(fake-plugin): avoid public class field assignment 2023-02-22 13:40:55 -08:00
Christopher Hiller 6be7488eef chore(universal-xml-plugin): remove dead code; typecheck all js sources 2023-02-22 13:40:55 -08:00
Christopher Hiller ab5b285714 feat(types): add orientation type; add optional ELEMENT prop of Element 2023-02-22 13:40:55 -08:00
Kazuaki Matsuo 8dc47ae922 docs: add more account in Project Committers (#18236)
* docs: add more
2023-02-22 13:24:20 -08:00
renovate[bot] 91831cf82d fix(support): update dependency @types/uuid to v9.0.1 2023-02-22 21:02:56 +00:00
renovate[bot] 4b10322b80 fix(images-plugin): update dependency lru-cache to v7.16.2 2023-02-22 17:43:23 +00:00
renovate[bot] 2602016eb3 fix(docutils): update dependency yargs to v17.7.1 2023-02-22 14:08:20 +00:00
renovate[bot] 1cb01a5e85 fix(base-driver): update dependency body-parser to v1.20.2 2023-02-22 11:20:27 +00:00
Kazuaki Matsuo e78448dc65 docs: update governance (#18222) 2023-02-21 12:04:52 -08:00
Christopher Hiller 34e0b70982 chore(typedoc-plugin-appium): add a fallback logger
due to the architecture of TypeDoc, it is not straightforward for a random module in a plugin to have access to the logger of the current `Application`.  This change abuses TypeDoc to enable logging for random modules.
2023-02-20 10:05:30 -08:00
Christopher Hiller ebe9477a3c feat(typedoc-plugin-appium): extract descriptions of return values from builtins
If a command is not documented with a `@returns` tag, but a builtin method _does_ have this tag (e.g., `ExternalDriver.createSession()`), then the plugin will use the tag from the builtin.

If a command _does_ have a `@returns` tag, but it provides no description, then the plugin will use the original `@returns` tag (which may have a type) and combine it with the content/description from the builtin (if any).

This requires further inspection of `SignatureReflection` data, which needed a new set of "comment finders", which prompted me to refactor the comment finders into their own files (because it was becoming unwieldy).  The call to `deriveComment` happens in the static `BaseCommandData.rewriteSignature()` method (whereas before it was just a straightforward clone operation).

- Fixed the block tag aggregation to prefer contentful over contentless tags in all cases.
- Modified the `@returns` tag of `FakeDriver.createSession()` for testing
- Moved some types around that were used outside of their sibling modules (e.g. `CommentSource` which used to be called `CommentSourceType`)
- pruned dead imports
2023-02-20 10:05:30 -08:00
Christopher Hiller 3b82c9f1a1 chore(docutils): smol type upgrade
`type-fest`@3.6.0 has a `TsConfigJson` type which no longer contains `unknown` (courtesy of yours truly), so we can use it directly instead of wrapping it in `Jsonify`.
2023-02-20 10:05:13 -08:00
renovate[bot] a9476a67ff chore(deps): update dependency json-schema-to-typescript to v11.0.4 2023-02-20 12:30:24 +00:00
renovate[bot] 24ad9f8741 chore(deps): update dependency @types/node to v18.14.0 2023-02-19 14:01:21 +00:00
renovate[bot] 639d475627 chore(docutils): update dependency mkdocs-material to v9.0.13 2023-02-19 10:43:21 +00:00
renovate[bot] 2fa6bf1e11 fix(images-plugin): update dependency lru-cache to v7.16.1 2023-02-18 15:41:23 +00:00
renovate[bot] d3dc9ce733 chore(deps): update commitlint monorepo to v17.4.4 2023-02-18 14:40:18 +00:00
renovate[bot] 08a6f3a308 fix(types): update dependency type-fest to v3.6.0 2023-02-17 13:47:51 -08:00
Christopher Hiller 24fb515a52 fix(types): base cap webSocketUrl is now a boolean instead of a string.
note that it is a string in the server response, but it is expected to be a boolean in the request.  punting on that for now
2023-02-17 11:03:11 -08:00
Christopher Hiller 893bd888cf feat(driver-test-support): add startStoppableAppium()
This adds a function `startStoppableAppium()` which starts an Appium server (via its `main()` entry point), but resolves with an object having a `stop() => Promise<boolean>` method. This method force-quits the server, even if there are active connections.
2023-02-17 11:02:44 -08:00
renovate[bot] d54901a9c3 fix(images-plugin): update dependency lru-cache to v7.16.0 2023-02-17 18:03:38 +00:00
renovate[bot] 70d65b7d62 fix(docutils): update dependency yargs to v17.7.0 2023-02-17 14:57:25 +00:00
renovate[bot] 33442bd61b chore(deps): update dependency rewiremock to v3.14.5 2023-02-17 11:22:10 +00:00
renovate[bot] 4cf8dbc0d3 fix(images-plugin): update dependency lru-cache to v7.15.0 2023-02-16 12:03:52 +00:00
Christopher Hiller 943dce5eec fix(schema): move output files due to tsc's confusion
It seemed to think I wanted to overwrite the root `index.js` file, and I don't know why. It doesn't do that anymore.
2023-02-15 16:19:30 -08:00
Christopher Hiller 04633e3e6d chore: clean:distfiles script now blasts everything 2023-02-15 16:19:30 -08:00
Christopher Hiller d171219938 chore: cleanup root tsconfig.json 2023-02-15 16:19:30 -08:00
Christopher Hiller 61a0275e29 fix(base-driver): fix type problem w/r/t axios headers 2023-02-15 16:19:30 -08:00
Christopher Hiller 8f9de63e4a fix: update axios to v1.3.3
because renovate cannot seem to do it
2023-02-15 16:19:30 -08:00
Christopher Hiller 90c0d36f2a chore(ci): add cache cleanup workflow on PR close
I noticed we were filling up our allotted cache space.  Reading about it, I found this example to cleanup caches we no longer need (specifically, from closed PRs)
2023-02-15 10:50:36 -08:00
Christopher Hiller 988c088873 chore: force http-cache-semantics to v4.1.1
for https://github.com/advisories/GHSA-rc47-6667-2j5j

this override should be removed when upgrading webdriver to v8+.  unfortunately "npm audit" is so stupid that it does not understand overrides, so we will see warnings anyway.
2023-02-14 15:43:28 -08:00
Christopher Hiller 7c2c642ce7 chore(typedoc-plugin-appium): blast entire build dir on clean due to stale artifacts 2023-02-14 14:31:38 -08:00
Christopher Hiller 6b8eb2cbfe fix(docutils): fix typescript version 2023-02-14 14:31:38 -08:00
Christopher Hiller 242c2ccbdc fix(typedoc-plugin-appium): fix broken references during clone operations
= moves example extraction to conversion phase (from rendering phase)
- add `cloneTypeParameterReflection()` util
- add support for passing explicit comments to `deriveComment` when `refl` is a `ParameterReflection`
- some tests
2023-02-14 14:31:38 -08:00
Christopher Hiller c5dadbbd36 chore(docutils): fix old mike test for wallaby 2023-02-14 14:31:38 -08:00
Christopher Hiller 74800cdf61 chore(docutils): tweaky refactors for new ts 2023-02-14 14:31:38 -08:00
Christopher Hiller 70c98ea12f chore(typedoc-plugin-appium): isolate e2e tests due to memory pressure in CI 2023-02-14 14:31:38 -08:00
Christopher Hiller 592f9adcb1 fix(typedoc-plugin-appium): parameter descriptions work
In #18132, I found that parameter descriptions of commands were being omitted from the output. Those descriptions have to come from a method, because they cannot be expressed in their original location (a method map) via JS or a docstring.

In addition, if an extension's implementation did not describe the parameters (e.g., in `@param` tags), we need to pull those descriptions out of `ExternalDriver` (assuming they exist).

Furthermore, we were executing the conversion _too early_ in TypeDoc's lifecycle, since its internal plugins haven't run yet. One of those plugins actually moves the `@param` comments from the _signature_ into the appropriate parameter. Once those are there, we can find them.

- Added a new list of "comment finders" which are parameter-specific (could probably do the same thing for return statements)
- Moved some functions into different modules and renamed a couple things to be more accurate.
- Update VSCode launch config
- `CommandData` now requires a reflection of a method. This was true before, but not formalized
2023-02-14 14:31:38 -08:00
Christopher Hiller fac31a3d68 chore(typedoc-plugin-appium): move kind-related utils into the kind module
Nothing else will ever use them, so in they go
2023-02-14 14:31:38 -08:00
renovate[bot] 2bbf42ff6b chore(deps): update dependency lerna to v6.5.1 2023-02-14 19:47:29 +00:00
renovate[bot] 1f65161e56 chore(deps): update dependency ws to v8.12.1 2023-02-14 14:36:45 +00:00
renovate[bot] 355bf8e5da chore(deps): update dependency lint-staged to v13.1.2 2023-02-14 11:23:36 +00:00
renovate[bot] 0ca5e7e21b chore(deps): update commitlint monorepo to v17.4.3 2023-02-14 07:37:19 +00:00
Jonathan Lipps 11e7ad0cd4 fix: appium:options should work via --default-capabilities
fix #18191
2023-02-13 16:20:21 -08:00
Christopher Hiller c69b602d1b fix(renovate): do not upgrade "figures" package to a major 2023-02-13 12:24:11 -08:00
Christopher Hiller 4cf13c5bfa chore(deps): upgrade typescript to v4.9.5
anything newer than v4.7.4 fails to ignore an optional arg in `@param {T} [foo]` if `foo` has a default assignment in the function signature.  removing the square brackets fixes the issue.

whether or not this is a bug, I am not entirely sure: it is marked as a bug, but it is also not something the TS team is in a hurry to fix, so whatevs.
2023-02-13 12:09:20 -08:00
renovate[bot] a4c58cc561 fix(docutils): update dependency typedoc to v0.23.25 2023-02-12 14:48:52 +00:00
renovate[bot] 64098c5f1a chore(deps): update dependency eslint to v8.34.0 2023-02-11 15:35:02 +00:00
renovate[bot] b4416c5c0f fix(types): update dependency type-fest to v3.5.7 2023-02-11 09:08:44 +00:00
renovate[bot] 547b8a45c4 fix(execute-driver-plugin): update dependency webdriverio to v7.30.1 2023-02-10 20:21:13 +00:00
renovate[bot] 1bf841736b chore(docutils): update dependency mkdocs-material to v9.0.12 2023-02-10 16:27:51 +00:00
Christopher Hiller 29a5bdac9a chore: publish
- appium@2.0.0-beta.55
 - @appium/docutils@0.2.1
2023-02-09 14:49:28 -08:00
Christopher Hiller ab366f3a1f fix(docutils): fix bad offset in nav updates
Also warn if no TypeDoc output files found
2023-02-09 14:48:20 -08:00
Christopher Hiller 5446e5c7a7 fix(docutils): fix bad option name and ignore most falsy args
- in type `DeployOpts`, `mkDocsYml` should have been `mkdocsYml`
- arguments were only being omitted from being passed to `mike` if they were `false`, but it needed to also consider `undefined` values and empty string values.  `0` is falsy, but we should not disallow this.  thus... "most" falsy values get ignored.
2023-02-09 14:48:20 -08:00
193 changed files with 7067 additions and 5702 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/**']
+36
View File
@@ -0,0 +1,36 @@
name: Cache Cleanup
# from https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
echo "Fetching list of cache keys"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+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 }}'
+4 -1
View File
@@ -39,6 +39,7 @@
"skipFiles": [
"<node_internals>/**",
"**/node_modules/mocha/**",
"**/node_modules/lodash/**",
"**/node_modules/ts-node/**"
],
"sourceMaps": true,
@@ -53,7 +54,9 @@
"args": ["--logLevel", "Verbose"],
"request": "launch",
"skipFiles": [
"<node_internals>/**"
"<node_internals>/**",
"**/node_modules/lodash/**",
"**/node_modules/**/handlebars/**"
],
"sourceMaps": true,
"type": "node"
+6
View File
@@ -78,6 +78,12 @@ The current committers are (in addition to the TC members above):
* [@ddkjin](https://github.com/ddkjin)
* [@stuartbrussell-intuit](https://github.com/stuartbrussell-intuit)
* [@vmaxim](https://github.com/vmaxim)
* [@Dan-Maor](https://github.com/Dan-Maor)
* [@laolubenson](https://github.com/laolubenson)
* [@Dor-bl](https://github.com/Dor-bl)
* [@boneskull](https://github.com/boneskull)
* [@mwakizaka](https://github.com/mwakizaka)
* [@rerorero](https://github.com/rerorero)
### Contributors
+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
+2320 -2797
View File
File diff suppressed because it is too large Load Diff
+23 -21
View File
@@ -30,9 +30,9 @@
"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": "run-s \"build -- --clean\"",
"clean:artifacts:distfiles": "npx rimraf \"./packages/*/build\" \"./packages/*/.tsconfig.tsbuildinfo\"",
"clean:artifacts:packages": "lerna run clean || true",
"clean:workspaces": "lerna clean -y",
"ci": "npm ci",
@@ -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"
},
@@ -74,27 +74,29 @@
},
"devDependencies": {
"@colors/colors": "1.5.0",
"@commitlint/cli": "17.4.2",
"@commitlint/config-conventional": "17.4.2",
"@commitlint/cli": "17.4.4",
"@commitlint/config-conventional": "17.4.4",
"@tsconfig/node14": "1.0.3",
"@types/chai": "4.3.4",
"@types/chai-as-promised": "7.1.5",
"@types/diff": "5.0.2",
"@types/mocha": "10.0.1",
"@types/node": "18.13.0",
"@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.33.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,9 +106,9 @@
"get-port": "5.1.1",
"handlebars": "4.7.7",
"husky": "8.0.3",
"json-schema-to-typescript": "11.0.3",
"lerna": "6.4.1",
"lint-staged": "13.1.1",
"json-schema-to-typescript": "12.0.0",
"lerna": "6.5.1",
"lint-staged": "13.1.2",
"log-symbols": "4.1.0",
"midnight-smoker": "2.0.1",
"mjpeg-consumer": "2.0.0",
@@ -114,7 +116,7 @@
"mocha": "10.2.0",
"npm-run-all": "4.1.5",
"prettier": "2.8.4",
"rewiremock": "3.14.4",
"rewiremock": "3.14.5",
"rimraf": "3.0.2",
"serve-static": "1.15.0",
"sinon": "15.0.1",
@@ -122,13 +124,13 @@
"sync-monorepo-packages": "1.0.2",
"through2": "4.0.2",
"ts-node": "10.9.1",
"typedoc": "0.23.24",
"typedoc": "0.23.26",
"typedoc-plugin-markdown": "3.14.0",
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
"typescript": "4.7.4",
"typescript": "4.9.5",
"validate.js": "0.13.1",
"webdriverio": "7.30.0",
"ws": "8.12.0",
"webdriverio": "7.30.2",
"ws": "8.12.1",
"yaml-js": "0.3.1"
},
"engines": {
@@ -139,12 +141,12 @@
"access": "public"
},
"overrides": {
"jpeg-js": "0.4.4",
"@sidvind/better-ajv-errors": {
"ajv": "8.12.0"
},
"ajv-formats": {
"ajv": "8.12.0"
}
},
"http-cache-semantics": "4.1.1"
}
}
+42
View File
@@ -3,6 +3,48 @@
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)
### Bug Fixes
* appium:options should work via --default-capabilities ([11e7ad0](https://github.com/appium/appium/commit/11e7ad0cd403ab1dc100f581cdf93772e3449db3)), closes [#18191](https://github.com/appium/appium/issues/18191)
* **execute-driver-plugin:** update dependency webdriverio to v7.30.1 ([547b8a4](https://github.com/appium/appium/commit/547b8a45c4b5629720b0f4bdb0485a861aecbebf))
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
# [2.0.0-beta.55](https://github.com/appium/appium/compare/appium@2.0.0-beta.54...appium@2.0.0-beta.55) (2023-02-09)
### Bug Fixes
* **docutils:** fix bad option name and ignore most falsy args ([5446e5c](https://github.com/appium/appium/commit/5446e5c7a755be081f46f1ed1ca8c13665d9a772))
# [2.0.0-beta.54](https://github.com/appium/appium/compare/appium@2.0.0-beta.53...appium@2.0.0-beta.54) (2023-02-09)
+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
@@ -52,7 +52,7 @@ async function main() {
log.info(`Building docs for language '${lang}' and version ${majMinVer}`);
const configFile = path.join(DOCS_DIR, `mkdocs-${lang}.yml`);
await deploy({
mkDocsYml: configFile,
mkdocsYml: configFile,
branch,
prefix: path.join(basePrefix, lang),
remote,
+20 -8
View File
@@ -10,6 +10,7 @@ import {
DELETE_SESSION_COMMAND,
GET_STATUS_COMMAND,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
} from '@appium/base-driver';
import AsyncLock from 'async-lock';
import {parseCapsForInnerDriver, pullSettings} from './utils';
@@ -44,7 +45,7 @@ class AppiumDriver extends DriverCore {
* It is not recommended to access this property directly from the outside
* @type {Record<string,ExternalDriver>}
*/
sessions = {};
sessions;
/**
* Access to pending drivers list must be guarded with a Semaphore, because
@@ -52,14 +53,14 @@ class AppiumDriver extends DriverCore {
* It is not recommended to access this property directly from the outside
* @type {Record<string,ExternalDriver[]>}
*/
pendingDrivers = {};
pendingDrivers;
/**
* Note that {@linkcode AppiumDriver} has no `newCommandTimeout` method.
* `AppiumDriver` does not set and observe its own timeouts; individual
* sessions (managed drivers) do instead.
*/
newCommandTimeoutMs = 0;
newCommandTimeoutMs;
/**
* List of active plugins
@@ -71,13 +72,13 @@ class AppiumDriver extends DriverCore {
* map of sessions to actual plugin instances per session
* @type {Record<string,InstanceType<PluginClass>[]>}
*/
sessionPlugins = {};
sessionPlugins;
/**
* some commands are sessionless, so we need a set of plugins for them
* @type {InstanceType<PluginClass>[]}
*/
sessionlessPlugins = [];
sessionlessPlugins;
/** @type {DriverConfig} */
driverConfig;
@@ -85,7 +86,11 @@ class AppiumDriver extends DriverCore {
/** @type {AppiumServer} */
server;
desiredCapConstraints = desiredCapabilityConstraints;
/**
* @type {AppiumDriverConstraints}
* @readonly
*/
desiredCapConstraints;
/** @type {DriverOpts} */
args;
@@ -105,6 +110,13 @@ class AppiumDriver extends DriverCore {
super(opts);
this.args = {...opts};
this.sessions = {};
this.pendingDrivers = {};
this.newCommandTimeoutMs = 0;
this.pluginClasses = new Map();
this.sessionPlugins = {};
this.sessionlessPlugins = [];
this.desiredCapConstraints = desiredCapabilityConstraints;
// allow this to happen in the background, so no `await`
(async () => {
@@ -243,7 +255,7 @@ class AppiumDriver extends DriverCore {
jsonwpCaps,
promoteAppiumOptions(w3cCapabilities),
this.desiredCapConstraints,
defaultCapabilities
defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
);
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
@@ -277,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,
});
}
/**
+71 -22
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`);
}
@@ -693,32 +711,62 @@ class ExtensionCommand {
);
}
if (!(scriptName in extScripts)) {
if (!(scriptName in /** @type {Record<string,string>} */ (extScripts))) {
throw this._createFatalError(
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
);
}
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' : ''}`;
@@ -435,7 +435,7 @@ export class ExtensionConfig {
/**
* @param {string} extName
* @param {ExtManifest<ExtType>} extManifest
* @param {ExtensionConfigMutationOpts} [opts]
* @param {ExtensionConfigMutationOpts} opts
* @returns {Promise<void>}
*/
async addExtension(extName, extManifest, {write = true} = {}) {
@@ -448,7 +448,7 @@ export class ExtensionConfig {
/**
* @param {ExtName<ExtType>} extName
* @param {ExtManifest<ExtType>} extManifest
* @param {ExtensionConfigMutationOpts} [opts]
* @param {ExtensionConfigMutationOpts} opts
* @returns {Promise<void>}
*/
async updateExtension(extName, extManifest, {write = true} = {}) {
@@ -465,7 +465,7 @@ export class ExtensionConfig {
* Remove an extension from the list of installed extensions, and optionally avoid a write to the manifest file.
*
* @param {ExtName<ExtType>} extName
* @param {ExtensionConfigMutationOpts} [opts]
* @param {ExtensionConfigMutationOpts} opts
* @returns {Promise<void>}
*/
async removeExtension(extName, {write = true} = {}) {
+1 -1
View File
@@ -94,7 +94,7 @@ export class ArgSpec {
* The _constructor_ is private. Use {@link ArgSpec.create} instead.
* @private
* @param {string} name
* @param {ArgSpecOptions<D>} [opts]
* @param {ArgSpecOptions<D>} opts
*/
constructor(name, {extType, extName, dest, defaultValue} = {}) {
// we must normalize the extension name to fit into our convention for CLI
+42 -39
View File
@@ -41,7 +41,9 @@ export class RoachHotelMap extends Map {
/**
* Extensions that an extension schema file can have.
*/
export const ALLOWED_SCHEMA_EXTENSIONS = new Set(['.json', '.js', '.cjs']);
export const ALLOWED_SCHEMA_EXTENSIONS = Object.freeze(
new Set(/** @type {AllowedSchemaExtension[]} */ (['.json', '.js', '.cjs']))
);
const SCHEMA_KEY = '$schema';
@@ -57,44 +59,39 @@ class AppiumSchema {
* An "argument" is a CLI argument or a config property.
*
* Used to provide easy lookups of argument metadata when converting between different representations of those arguments.
* @private
* @type {RoachHotelMap<string,ArgSpec>}
*/
_argSpecs = new RoachHotelMap();
#argSpecs = new RoachHotelMap();
/**
* A map of extension types to extension names to schema objects.
*
* This data structure is used to ensure there are no naming conflicts. The schemas
* are stored here in memory until the instance is _finalized_.
* @private
* @type {Record<ExtensionType,Map<string,SchemaObject>>}
*/
_registeredSchemas = {[DRIVER_TYPE]: new Map(), [PLUGIN_TYPE]: new Map()};
#registeredSchemas = {[DRIVER_TYPE]: new Map(), [PLUGIN_TYPE]: new Map()};
/**
* Ajv instance
*
* @private
* @type {Ajv}
*/
_ajv;
#ajv;
/**
* Singleton instance.
* @private
* @type {AppiumSchema}
*/
static _instance;
static #instance;
/**
* Lookup of schema IDs to finalized schemas.
*
* This does not include references, but rather the root schemas themselves.
* @private
* @type {Record<string,StrictSchemaObject>?}
*/
_finalizedSchemas = null;
#finalizedSchemas = null;
/**
* Initializes Ajv, adds standard formats and our custom keywords.
@@ -102,7 +99,7 @@ class AppiumSchema {
* @private
*/
constructor() {
this._ajv = AppiumSchema._instantiateAjv();
this.#ajv = AppiumSchema._instantiateAjv();
}
/**
@@ -113,9 +110,9 @@ class AppiumSchema {
* @returns {AppiumSchema}
*/
static create() {
if (!AppiumSchema._instance) {
if (!AppiumSchema.#instance) {
const instance = new AppiumSchema();
AppiumSchema._instance = instance;
AppiumSchema.#instance = instance;
_.bindAll(instance, [
'finalize',
'flatten',
@@ -133,7 +130,7 @@ class AppiumSchema {
]);
}
return AppiumSchema._instance;
return AppiumSchema.#instance;
}
/**
@@ -145,7 +142,7 @@ class AppiumSchema {
* @returns {boolean} If registered
*/
hasRegisteredSchema(extType, extName) {
return this._registeredSchemas[extType].has(extName);
return this.#registeredSchemas[extType].has(extName);
}
/**
@@ -154,11 +151,11 @@ class AppiumSchema {
* @returns {boolean} If finalized
*/
isFinalized() {
return Boolean(this._finalizedSchemas);
return Boolean(this.#finalizedSchemas);
}
getAllArgSpecs() {
return this._argSpecs;
return this.#argSpecs;
}
/**
@@ -182,10 +179,10 @@ class AppiumSchema {
*/
finalize() {
if (this.isFinalized()) {
return /** @type {NonNullable<typeof this._finalizedSchemas>} */ (this._finalizedSchemas);
return /** @type {Record<string,StrictSchemaObject>} */ (this.#finalizedSchemas);
}
const ajv = this._ajv;
const ajv = this.#ajv;
// Ajv will _mutate_ the schema, so we need to clone it.
const baseSchema = _.cloneDeep(AppiumConfigJsonSchema);
@@ -205,7 +202,7 @@ class AppiumSchema {
extName,
});
const {arg} = argSpec;
this._argSpecs.set(arg, argSpec);
this.#argSpecs.set(arg, argSpec);
}
};
@@ -217,7 +214,7 @@ class AppiumSchema {
const finalizedSchemas = {};
const finalSchema = _.reduce(
this._registeredSchemas,
this.#registeredSchemas,
/**
* @param {typeof baseSchema} baseSchema
* @param {Map<string,SchemaObject>} extensionSchemas
@@ -246,7 +243,7 @@ class AppiumSchema {
finalizedSchemas[APPIUM_CONFIG_SCHEMA_ID] = finalSchema;
ajv.validateSchema(finalSchema, true);
this._finalizedSchemas = finalizedSchemas;
this.#finalizedSchemas = finalizedSchemas;
return Object.freeze(finalizedSchemas);
}
@@ -283,18 +280,18 @@ class AppiumSchema {
* @returns {void}
*/
reset() {
for (const schemaId of Object.keys(this._finalizedSchemas ?? {})) {
this._ajv.removeSchema(schemaId);
for (const schemaId of Object.keys(this.#finalizedSchemas ?? {})) {
this.#ajv.removeSchema(schemaId);
}
this._argSpecs = new RoachHotelMap();
this._registeredSchemas = {
this.#argSpecs = new RoachHotelMap();
this.#registeredSchemas = {
[DRIVER_TYPE]: new Map(),
[PLUGIN_TYPE]: new Map(),
};
this._finalizedSchemas = null;
this.#finalizedSchemas = null;
// Ajv seems to have an over-eager cache, so we have to dump the object entirely.
this._ajv = AppiumSchema._instantiateAjv();
this.#ajv = AppiumSchema._instantiateAjv();
}
/**
@@ -318,14 +315,14 @@ class AppiumSchema {
}
const normalizedExtName = _.kebabCase(extName);
if (this.hasRegisteredSchema(extType, normalizedExtName)) {
if (_.isEqual(this._registeredSchemas[extType].get(normalizedExtName), schema)) {
if (_.isEqual(this.#registeredSchemas[extType].get(normalizedExtName), schema)) {
return;
}
throw new SchemaNameConflictError(extType, extName);
}
this._ajv.validateSchema(schema, true);
this.#ajv.validateSchema(schema, true);
this._registeredSchemas[extType].set(normalizedExtName, schema);
this.#registeredSchemas[extType].set(normalizedExtName, schema);
}
/**
@@ -336,7 +333,7 @@ class AppiumSchema {
* @returns {ArgSpec|undefined} ArgSpec or `undefined` if not found
*/
getArgSpec(name, extType, extName) {
return this._argSpecs.get(ArgSpec.toArg(name, extType, extName));
return this.#argSpecs.get(ArgSpec.toArg(name, extType, extName));
}
/**
@@ -347,7 +344,7 @@ class AppiumSchema {
* @returns {boolean} `true` if such an {@link ArgSpec} exists
*/
hasArgSpec(name, extType, extName) {
return this._argSpecs.has(ArgSpec.toArg(name, extType, extName));
return this.#argSpecs.has(ArgSpec.toArg(name, extType, extName));
}
/**
@@ -391,7 +388,7 @@ class AppiumSchema {
/** @type {DefaultValues<Flattened>} */
const retval = {};
return [...this._argSpecs.values()].reduce(reducer, retval);
return [...this.#argSpecs.values()].reduce(reducer, retval);
}
/**
@@ -405,7 +402,7 @@ class AppiumSchema {
if (!this.isFinalized()) {
throw new SchemaFinalizationError();
}
const specs = [...this._argSpecs.values()].filter(
const specs = [...this.#argSpecs.values()].filter(
(spec) => spec.extType === extType && spec.extName === extName
);
return specs.reduce((defaults, {defaultValue, rawDest}) => {
@@ -506,7 +503,7 @@ class AppiumSchema {
* @returns {import('ajv').ValidateFunction}
*/
_getValidator(id = APPIUM_CONFIG_SCHEMA_ID) {
const validator = this._ajv.getSchema(id);
const validator = this.#ajv.getSchema(id);
if (!validator) {
if (id === APPIUM_CONFIG_SCHEMA_ID) {
throw new SchemaFinalizationError();
@@ -532,11 +529,13 @@ class AppiumSchema {
/**
* Returns `true` if `filename`'s file extension is allowed (in {@link ALLOWED_SCHEMA_EXTENSIONS}).
* @param {string} filename
* @param {import('type-fest').LiteralUnion<AllowedSchemaExtension, string>} filename
* @returns {boolean}
*/
static isAllowedSchemaFileExtension(filename) {
return ALLOWED_SCHEMA_EXTENSIONS.has(path.extname(filename));
return ALLOWED_SCHEMA_EXTENSIONS.has(
/** @type {AllowedSchemaExtension} */ (path.extname(filename))
);
}
/**
@@ -720,3 +719,7 @@ export const {isAllowedSchemaFileExtension} = AppiumSchema;
* @template {boolean|undefined} Flattened
* @typedef {Record<string,Flattened extends true ? ArgSpecDefaultValue : ArgSpecDefaultValue | NestedArgSpecDefaultValue>} DefaultValues
*/
/**
* @typedef {'.json'|'.js'|'.cjs'} AllowedSchemaExtension
*/
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.0.0-beta.54",
"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.1",
"@appium/base-plugin": "^2.2.1",
"@appium/docutils": "^0.2.0",
"@appium/schema": "^0.2.4",
"@appium/support": "^3.1.5",
"@appium/types": "^0.9.1",
"@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",
@@ -77,12 +77,12 @@
"argparse": "2.0.1",
"async-lock": "1.4.0",
"asyncbox": "2.9.4",
"axios": "1.2.3",
"axios": "1.3.4",
"bluebird": "3.7.2",
"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.5.6",
"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.0"
"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
+54 -26
View File
@@ -88,35 +88,32 @@ describe('FakeDriver via HTTP', function () {
}
FakeDriver = driverConfig.require('fake');
// then start server if we need to
await serverStart(port, {appiumHome});
});
after(async function () {
await serverClose();
await fs.rimraf(appiumHome);
sandbox.restore();
});
/**
*
* @param {number} port
* @param {Partial<import('appium/types').ParsedArgs>} [args]
*/
async function serverStart(port, args = {}) {
args = {...args, port, address: TEST_HOST};
if (shouldStartServer) {
server = await appiumServer(args);
}
}
async function serverClose() {
if (server) {
await server.close();
}
function withServer(args = {}) {
before(async function () {
args = {...args, appiumHome, port, address: TEST_HOST};
if (shouldStartServer) {
server = await appiumServer(args);
}
});
after(async function () {
if (server) {
await server.close();
}
});
}
describe('server updating', function () {
withServer();
it('should allow drivers to update the server in arbitrary ways', async function () {
const {data} = await axios.get(`${testServerBaseUrl}/fakedriver`);
data.should.eql({fakedriver: 'fakeResponse'});
@@ -132,6 +129,7 @@ describe('FakeDriver via HTTP', function () {
});
describe('cli args handling for empty args', function () {
withServer();
it('should not receive user cli args if none passed in', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
const {sessionId} = driver;
@@ -146,16 +144,7 @@ describe('FakeDriver via HTTP', function () {
});
describe('cli args handling for passed in args', function () {
before(async function () {
await serverClose();
await serverStart(port, {appiumHome, ...FAKE_DRIVER_ARGS});
});
after(async function () {
await serverClose();
// this weirdness here is to restart the same server which was originally started in the parent suite's
// "before all" hook. another way of doing this would be to just...not nest suites this way.
await serverStart(port, {appiumHome});
});
withServer(FAKE_DRIVER_ARGS);
it('should receive user cli args from a driver if arguments were passed in', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
const {sessionId} = driver;
@@ -169,7 +158,46 @@ describe('FakeDriver via HTTP', function () {
});
});
describe('default capabilities via cli', function () {
withServer({
defaultCapabilities: {
'appium:options': {
automationName: 'Fake',
deviceName: 'Fake',
app: TEST_FAKE_APP,
},
platformName: 'Fake',
},
});
it('should allow appium-prefixed caps sent via appium:options through --default-capabilities', async function () {
const appiumOptsCaps = {
capabilities: {
alwaysMatch: {},
firstMatch: [{}],
},
};
// Create the session
const {value} = (await axios.post(testServerBaseSessionUrl, appiumOptsCaps)).data;
try {
value.sessionId.should.be.a.string;
value.should.exist;
value.capabilities.should.deep.equal({
automationName: 'Fake',
platformName: 'Fake',
deviceName: 'Fake',
app: TEST_FAKE_APP,
});
} finally {
// End session
await axios.delete(`${testServerBaseSessionUrl}/${value.sessionId}`);
}
});
});
describe('session handling', function () {
withServer();
it('should start and stop a session and not allow commands after session stopped', async function () {
let driver = await wdio({...wdOpts, capabilities: caps});
should.exist(driver.sessionId);
@@ -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);
});
});
});
});
+39
View File
@@ -3,6 +3,45 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [9.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)
### Bug Fixes
* appium:options should work via --default-capabilities ([11e7ad0](https://github.com/appium/appium/commit/11e7ad0cd403ab1dc100f581cdf93772e3449db3)), closes [#18191](https://github.com/appium/appium/issues/18191)
* **base-driver:** fix type problem w/r/t axios headers ([61a0275](https://github.com/appium/appium/commit/61a0275e29e7a705785282415fd670a3ad617f18))
* **base-driver:** update dependency body-parser to v1.20.2 ([1cb01a5](https://github.com/appium/appium/commit/1cb01a5e85e7aee54ce1edc751e104138852d59a))
* **images-plugin:** update dependency lru-cache to v7.15.0 ([4cf8dbc](https://github.com/appium/appium/commit/4cf8dbc0d34769e3167bceed65facabe71b9cbde))
* **images-plugin:** update dependency lru-cache to v7.16.0 ([d54901a](https://github.com/appium/appium/commit/d54901a9c3982dd5595ffd54010e5029b60c4754))
* **images-plugin:** update dependency lru-cache to v7.16.1 ([2fa6bf1](https://github.com/appium/appium/commit/2fa6bf1e11e66ba8256d0641e345359bd108bc7d))
* **images-plugin:** update dependency lru-cache to v7.16.2 ([4b10322](https://github.com/appium/appium/commit/4b10322b80e0032dd4585ac7766edd1ddf798139))
* **images-plugin:** update dependency lru-cache to v7.17.0 ([eb73be8](https://github.com/appium/appium/commit/eb73be8303c517dbaa965ae99bac3381522d939a))
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
## [9.3.1](https://github.com/appium/appium/compare/@appium/base-driver@9.3.0...@appium/base-driver@9.3.1) (2023-02-09)
@@ -333,6 +333,68 @@ function processCapabilities(
return /** @type {Capabilities<C>} */ (matchedCaps ?? {});
}
/**
* Return a copy of a "bare" (single-level, non-W3C) capabilities object which has taken everything
* within the 'appium:options' capability and promoted it to the top level.
*
* @template {Constraints} C
* @param {NSCapabilities<C>} obj
* @return {NSCapabilities<C>} the capabilities with 'options' promoted if necessary
*/
function promoteAppiumOptionsForObject(obj) {
const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP];
if (!appiumOptions) {
return obj;
}
if (!_.isPlainObject(appiumOptions)) {
throw new errors.SessionNotCreatedError(
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
);
}
if (_.isEmpty(appiumOptions)) {
return obj;
}
log.debug(
`Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`
);
/**
* @param {string} capName
*/
const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
const verifyIfAcceptable = (/** @type {string} */ capName) => {
if (!_.isString(capName)) {
throw new errors.SessionNotCreatedError(
`Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected`
);
}
if (isStandardCap(capName)) {
throw new errors.SessionNotCreatedError(
`${PREFIXED_APPIUM_OPTS_CAP} must only contain vendor-specific capabilties. '${capName}' is unexpected`
);
}
return capName;
};
const preprocessedOptions = _(appiumOptions)
.mapKeys((value, /** @type {string} */ key) => verifyIfAcceptable(key))
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
.value();
// warn if we are going to overwrite any keys on the base caps object
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
if (overwrittenKeys.length > 0) {
log.warn(
`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` +
`capabilities at the top level: ${JSON.stringify(overwrittenKeys)}`
);
}
return _.cloneDeep({
.../** @type {NSCapabilities<C>} */ (_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)),
...preprocessedOptions,
});
}
/**
* Return a copy of a capabilities object which has taken everything within the 'options'
* capability and promoted it to the top level.
@@ -342,69 +404,15 @@ function processCapabilities(
* @return {import('@appium/types').W3CCapabilities<C>} the capabilities with 'options' promoted if necessary
*/
function promoteAppiumOptions(originalCaps) {
const promoteForObject = (obj) => {
const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP];
if (!appiumOptions) {
return obj;
}
if (!_.isPlainObject(appiumOptions)) {
throw new errors.SessionNotCreatedError(
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
);
}
if (_.isEmpty(appiumOptions)) {
return obj;
}
log.debug(
`Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`
);
/**
* @param {string} capName
*/
const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
const verifyIfAcceptable = (capName) => {
if (!_.isString(capName)) {
throw new errors.SessionNotCreatedError(
`Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected`
);
}
if (isStandardCap(capName)) {
throw new errors.SessionNotCreatedError(
`${PREFIXED_APPIUM_OPTS_CAP} must only contain vendor-specific capabilties. '${capName}' is unexpected`
);
}
return capName;
};
const preprocessedOptions = _(appiumOptions)
.mapKeys((value, key) => verifyIfAcceptable(key))
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
.value();
// warn if we are going to overwrite any keys on the base caps object
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
if (overwrittenKeys.length > 0) {
log.warn(
`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` +
`capabilities at the top level: ${JSON.stringify(overwrittenKeys)}`
);
}
return _.cloneDeep({
..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP),
...preprocessedOptions,
});
};
const {alwaysMatch, firstMatch} = originalCaps;
const result = {};
const {alwaysMatch, firstMatch} = originalCaps;
if (_.isPlainObject(alwaysMatch)) {
result.alwaysMatch = promoteForObject(alwaysMatch);
result.alwaysMatch = promoteAppiumOptionsForObject(alwaysMatch);
} else if ('alwaysMatch' in originalCaps) {
result.alwaysMatch = alwaysMatch;
}
if (_.isArray(firstMatch)) {
result.firstMatch = firstMatch.map(promoteForObject);
result.firstMatch = firstMatch.map(promoteAppiumOptionsForObject);
} else if ('firstMatch' in originalCaps) {
result.firstMatch = firstMatch;
}
@@ -421,6 +429,7 @@ export {
isStandardCap,
stripAppiumPrefixes,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
PREFIXED_APPIUM_OPTS_CAP,
};
@@ -431,6 +440,11 @@ export {
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
*/
/**
* @template {Constraints} C
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
*/
/**
* @typedef ValidateCapsOpts
* @property {boolean} [skipPresenceConstraint] - if true, skip the presence constraint
@@ -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);
+41 -20
View File
@@ -29,13 +29,10 @@ class DriverCore {
*/
static baseVersion = BASEDRIVER_VER;
/** @type {import('@appium/types').ExecuteMethodMap<DriverCore>} */
static executeMethodMap = {};
/**
* @type {string?}
*/
sessionId = null;
sessionId;
/**
* @type {import('@appium/types').DriverOpts<C>}
@@ -47,7 +44,8 @@ class DriverCore {
*/
initialOpts;
helpers = helpers;
/** @type {typeof helpers} */
helpers;
/**
* basePath is used for several purposes, for example in setting up
@@ -56,39 +54,43 @@ class DriverCore {
* initially but it is automatically updated during any actual program
* execution by the routeConfiguringFunction, which is necessarily run as
* the entrypoint for any Appium server
* @type {string}
*/
basePath = DEFAULT_BASE_PATH;
basePath;
relaxedSecurityEnabled = false;
/** @type {boolean} */
relaxedSecurityEnabled;
/** @type {string[]} */
allowInsecure = [];
allowInsecure;
/** @type {string[]} */
denyInsecure = [];
denyInsecure;
newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
/** @type {number} */
newCommandTimeoutMs;
implicitWaitMs = 0;
/** @type {number} */
implicitWaitMs;
/** @type {string[]} */
locatorStrategies = [];
locatorStrategies;
/** @type {string[]} */
webLocatorStrategies = [];
webLocatorStrategies;
/** @type {Driver[]} */
managedDrivers = [];
managedDrivers;
/** @type {NodeJS.Timeout?} */
noCommandTimer = null;
noCommandTimer;
/** @type {EventHistory} */
_eventHistory = {commands: []};
_eventHistory;
// used to handle driver events
/** @type {NodeJS.EventEmitter} */
eventEmitter = new EventEmitter();
eventEmitter;
/**
* @type {AppiumLogger}
@@ -97,8 +99,9 @@ class DriverCore {
/**
* @protected
* @type {boolean}
*/
shutdownUnexpectedly = false;
shutdownUnexpectedly;
/**
* @type {boolean}
@@ -107,16 +110,18 @@ class DriverCore {
/**
* @protected
* @type {AsyncLock}
*/
commandsQueueGuard = new AsyncLock();
commandsQueueGuard;
/**
* settings should be instantiated by drivers which extend BaseDriver, but
* we set it to an empty DeviceSettings instance here to make sure that the
* default settings are applied even if an extending driver doesn't utilize
* the settings functionality itself
* @type {DeviceSettings}
*/
settings = new DeviceSettings();
settings;
/**
* @param {DriverOpts<C>} opts
@@ -139,6 +144,22 @@ class DriverCore {
this.initialOpts = _.cloneDeep(opts);
this.sessionId = null;
this.helpers = helpers;
this.basePath = DEFAULT_BASE_PATH;
this.relaxedSecurityEnabled = false;
this.allowInsecure = [];
this.denyInsecure = [];
this.newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
this.implicitWaitMs = 0;
this.locatorStrategies = [];
this.webLocatorStrategies = [];
this.managedDrivers = [];
this.noCommandTimer = null;
this._eventHistory = {commands: []};
this.eventEmitter = new EventEmitter();
this.shutdownUnexpectedly = false;
this.commandsQueueGuard = new AsyncLock();
this.settings = new DeviceSettings();
}
get log() {
+9 -12
View File
@@ -1,14 +1,13 @@
/* eslint-disable require-await */
/* eslint-disable no-unused-vars */
import {validateCaps, PREFIXED_APPIUM_OPTS_CAP, processCapabilities} from './capabilities';
import {validateCaps, processCapabilities} from './capabilities';
import {DriverCore} from './core';
import {util} from '@appium/support';
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
*/
/**
@@ -52,17 +52,22 @@ process.on('exit', () => {
}
});
async function retrieveHeaders(link) {
/**
*
* @param {string} url
* @returns {Promise<import('axios').AxiosResponse['headers']>}
*/
async function retrieveHeaders(url) {
try {
return (
await axios({
url: link,
url,
method: 'HEAD',
timeout: 5000,
})
).headers;
} catch (e) {
logger.info(`Cannot send HEAD request to '${link}'. Original error: ${e.message}`);
logger.info(`Cannot send HEAD request to '${url}'. Original error: ${e.message}`);
}
return {};
}
@@ -228,6 +233,7 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
let newApp = app;
let shouldUnzipApp = false;
let packageHash = null;
/** @type {import('axios').AxiosResponse['headers']?} */
let headers = null;
/** @type {RemoteAppProps} */
const remoteAppProps = {
+1
View File
@@ -35,6 +35,7 @@ export {
isStandardCap,
validateCaps,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
} from './basedriver/capabilities';
// Web socket helpers
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "9.3.1",
"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.5",
"@appium/types": "^0.9.1",
"@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",
@@ -53,19 +53,19 @@
"@types/serve-favicon": "2.5.3",
"async-lock": "1.4.0",
"asyncbox": "2.9.4",
"axios": "1.2.3",
"axios": "1.3.4",
"bluebird": "3.7.2",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"es6-error": "4.1.1",
"express": "4.18.2",
"http-status-codes": "2.2.0",
"lodash": "4.17.21",
"lru-cache": "7.14.1",
"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.5.6",
"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;
+16
View File
@@ -3,6 +3,22 @@
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
## [2.2.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.0...@appium/base-plugin@2.2.1) (2023-02-09)
**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.1",
"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.1",
"@appium/support": "^3.1.5"
"@appium/base-driver": "^9.3.3",
"@appium/support": "^3.1.7"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+20
View File
@@ -3,6 +3,26 @@
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)
### Bug Fixes
* **docutils:** update dependency yargs to v17.7.0 ([70d65b7](https://github.com/appium/appium/commit/70d65b7d6230dd415f26aaba6d097a3cbe876b47))
* **docutils:** update dependency yargs to v17.7.1 ([2602016](https://github.com/appium/appium/commit/2602016eb38c73d2d9a40f94e95277faf2444305))
## [2.0.7](https://github.com/appium/appium/compare/@appium/doctor@2.0.6...@appium/doctor@2.0.7) (2023-02-09)
**Note:** Version bump only for package @appium/doctor
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/doctor",
"version": "2.0.7",
"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.5",
"@appium/support": "^3.1.7",
"@colors/colors": "1.5.0",
"@types/bluebird": "3.5.38",
"@types/teen_process": "2.0.0",
@@ -54,7 +54,7 @@
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"teen_process": "2.0.2",
"yargs": "17.6.2"
"yargs": "17.7.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+49
View File
@@ -3,6 +3,55 @@
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)
### Bug Fixes
* **docutils:** fix typescript version ([6b8eb2c](https://github.com/appium/appium/commit/6b8eb2cbfec39185d8b81e2f4e2ce3f646ffd65f))
* **docutils:** update dependency typedoc to v0.23.25 ([a4c58cc](https://github.com/appium/appium/commit/a4c58cc5618a9c83de4c044bf8bc59f1ddb80122))
* **docutils:** update dependency yargs to v17.7.0 ([70d65b7](https://github.com/appium/appium/commit/70d65b7d6230dd415f26aaba6d097a3cbe876b47))
* **docutils:** update dependency yargs to v17.7.1 ([2602016](https://github.com/appium/appium/commit/2602016eb38c73d2d9a40f94e95277faf2444305))
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
## [0.2.1](https://github.com/appium/appium/compare/@appium/docutils@0.2.0...@appium/docutils@0.2.1) (2023-02-09)
### Bug Fixes
* **docutils:** fix bad offset in nav updates ([ab366f3](https://github.com/appium/appium/commit/ab366f3a1f9109f7bd75ae5ac32b862d5e6dd1bf))
* **docutils:** fix bad option name and ignore most falsy args ([5446e5c](https://github.com/appium/appium/commit/5446e5c7a755be081f46f1ed1ca8c13665d9a772))
# [0.2.0](https://github.com/appium/appium/compare/@appium/docutils@0.1.6...@appium/docutils@0.2.0) (2023-02-09)
+19 -7
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);
}
@@ -74,7 +75,7 @@ async function findDeployVersion(packageJsonPath?: string, cwd = process.cwd()):
* @param opts Options
*/
export async function deploy({
mkDocsYml: mkDocsYmlPath,
mkdocsYml: mkDocsYmlPath,
packageJson: packageJsonPath,
deployVersion: version,
cwd = process.cwd(),
@@ -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,
@@ -111,16 +116,23 @@ export async function deploy({
host,
};
if (serve) {
const mikeArgs = [...argify(_.pickBy(mikeOpts, Boolean)), version];
const mikeArgs = [
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value))),
version,
];
if (alias) {
mikeArgs.push(alias);
}
stop(); // discard
// unsure about how SIGHUP is handled here
await doServe(mikeArgs, serveOpts);
} else {
const mikeArgs = [
...argify(
_.omitBy(mikeOpts, (value, key) => _.includes(['port', 'host'], key) || value === false)
_.omitBy(
mikeOpts,
(value, key) => _.includes(['port', 'host'], key) || (!_.isNumber(value) && !value)
)
),
version,
];
@@ -140,7 +152,7 @@ export interface DeployOpts {
/**
* Path to `mike.yml`
*/
mkDocsYml?: string;
mkdocsYml?: string;
/**
* Current working directory
+5
View File
@@ -1,3 +1,8 @@
/**
* Exports APIs used by build-related commands
* @module
*/
export * from './deploy';
export * from './site';
export * from './reference';
+28 -23
View File
@@ -170,7 +170,6 @@ function getRootHeaderKeypath(headerItems: ParsedNavData[], header: string) {
* @param mkDocsDocsDir Configured `docs_dir` or via options
* @param navData Nav data parsed by {@linkcode parseNav}
* @param all If `true`, process all markdown files, not just commands
* @returns
*/
async function getNavItemsForDir(
dir: string,
@@ -180,45 +179,52 @@ async function getNavItemsForDir(
all = false
) {
let dataChanged = false;
const newHeaderItems: Omit<ParsedNavData, 'name'>[] = [];
const newNavHeaderItems: Omit<ParsedNavData, 'name'>[] = [];
const referenceOutputFilepaths = await findRelativeMarkdownFiles(dir, mkDocsDocsDir);
if (!referenceOutputFilepaths.length) {
log.warn('No markdown files found in %s; did TypeDoc run?', dir);
return {data: [], changed: false};
}
const newRefFilepaths = await findRelativeMarkdownFiles(dir, mkDocsDocsDir);
const header = all ? _.startCase(path.basename(dir)) : DEFAULT_NAV_HEADER;
const headerItems = filterHeaderItems(navData, header);
const navHeader = all ? _.startCase(path.basename(dir)) : DEFAULT_NAV_HEADER;
const navHeaderItems = filterHeaderItems(navData, navHeader);
// if we found items with this header already, we are going
// to replace them all wholesale
if (headerItems.length) {
if (navHeaderItems.length) {
log.debug('Found %d item(s) in header %s', navHeaderItems.length, navHeader);
// we append indices or names to this keypath
const rootHeaderKeypath = getRootHeaderKeypath(headerItems, header);
const rootHeaderKeypath = getRootHeaderKeypath(navHeaderItems, navHeader);
for (const [offset, fileOrUrl] of newRefFilepaths.entries()) {
const data = headerItems.find((item) => item.fileOrUrl === fileOrUrl);
const keypath = getKeypathForHeaderItem(rootHeaderKeypath, offset, data);
newHeaderItems.push({keypath, fileOrUrl});
for (const fileOrUrl of referenceOutputFilepaths) {
const offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
const newOffset = offset >= 0 ? offset : navHeaderItems.length;
const data = navHeaderItems[offset];
log.warn(rootHeaderKeypath, newOffset, data);
const keypath = getKeypathForHeaderItem(rootHeaderKeypath, newOffset, data);
newNavHeaderItems.push({keypath, fileOrUrl});
}
// look for any differences between what we have and what's in the file
if (navDataDidChange(newHeaderItems, headerItems)) {
log.debug('Will write new nav data for header %s', header);
if (navDataDidChange(newNavHeaderItems, navHeaderItems)) {
log.debug('Will write new nav data for header %s: %O', navHeader, newNavHeaderItems);
dataChanged = true;
} else {
log.debug('No changes for header %s', header);
log.debug('No changes for header %s', navHeader);
}
} else {
let navOffset = nav.length;
for (const [idx, newRefFilepath] of newRefFilepaths.entries()) {
newHeaderItems.push({
keypath: `${navOffset}.${header}.${idx}`,
log.debug('No items found in header %s', navHeader);
const navOffset = nav.length;
for (const [idx, newRefFilepath] of referenceOutputFilepaths.entries()) {
newNavHeaderItems.push({
keypath: `${navOffset}.${navHeader}.${idx}`,
fileOrUrl: newRefFilepath,
});
}
log.debug('Will create nav data for header %s', header);
log.debug('Will create nav data for header %s', navHeader);
dataChanged = true;
}
return {data: newHeaderItems, changed: dataChanged};
return {data: newNavHeaderItems, changed: dataChanged};
}
/**
@@ -248,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 -46
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 = {
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 = {
'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 = {
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 = {
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 = {
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 = {
'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 = {
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 = {
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,32 +190,35 @@ const opts = {
host: {
alias: 'h',
describe: 'Development server host',
group: NAME_GROUP_SERVE,
group: BuildCommandGroup.Serve,
type: 'string',
nargs: 1,
requiresArg: true,
implies: 'serve',
defaultDescription: 'localhost',
},
} as const;
} as const satisfies Record<string, Options>;
opts as Record<string, Options>;
type BuildOptions = InferredOptionTypes<typeof opts>;
const buildCommand: CommandModule<{}, BuildOptions> = {
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);
@@ -224,6 +241,4 @@ const buildCommand: CommandModule<{}, BuildOptions> = {
}
log.success('Done! (total: %dms)', stop());
},
};
export default buildCommand;
} 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 -32
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:',
}
/**
* 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 = {
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 = {
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 = {
},
'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 = {
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 = {
'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 = {
'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 = {
'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 = {
'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 = {
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,23 +158,31 @@ const opts = {
typescript: {
default: true,
description: 'Create tsconfig.json if needed',
group: NAME_GROUP_INIT_BEHAVIOR,
group: InitCommandGroup.Behavior,
type: 'boolean',
},
} as const;
opts as Record<string, Options>; // type check
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>;
const initCommand: CommandModule<{}, InitOptions> = {
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());
},
};
export default initCommand;
} as CommandModule<object, InitOptions>;
+35 -28
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 = {
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 = {
'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 = {
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 = {
'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 = {
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,24 +84,25 @@ const opts = {
typescript: {
default: true,
description: 'Validate TypeScript environment',
group: NAME_GROUP_VALIDATE,
group: ValidateCommandGroup.Behavior,
type: 'boolean',
},
} as const;
opts as Record<string, Options>;
} as const satisfies Record<string, Options>;
type ValidateOptions = InferredOptionTypes<typeof opts>;
const validateCommand: CommandModule<{}, ValidateOptions> = {
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[]) => {
@@ -113,10 +122,8 @@ const validateCommand: CommandModule<{}, ValidateOptions> = {
if (errorCount) {
throw new DocutilsError(
`Validation failed with ${errorCount} ${pluralize('error', errorCount)}`
`Validation failed with ${errorCount} ${util.pluralize('error', errorCount)}`
);
}
},
};
export default validateCommand;
} 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;
}
>;
+6 -11
View File
@@ -32,34 +32,34 @@ class DocutilsReporter extends FancyReporter {
/**
* Mapping of log types (the name of the logging method called) to chalk fg colors
*/
static TYPE_COLOR_MAP = {
static readonly TYPE_COLOR_MAP = {
info: 'cyan',
success: 'green',
error: 'red',
warn: 'yellow',
} as const;
} satisfies {[k in LogType]?: typeof ForegroundColor};
/**
* Mapping of log levels to chalk fg colors
*/
static LEVEL_COLORS = {
static readonly LEVEL_COLORS = {
0: 'red',
1: 'yellow',
2: 'white',
3: 'green',
} as const;
} satisfies {[k in LogLevel]?: typeof ForegroundColor};
/**
* Mapping of log types to icons/symbols
*/
static TYPE_ICONS = {
static readonly TYPE_ICONS = {
info: logSymbols.info,
success: logSymbols.success,
error: logSymbols.error,
warn: logSymbols.warning,
debug: figures('›'),
trace: figures('›'),
} as const;
} satisfies {[k in LogType]?: string};
/**
* Default color to use if we can't find a color for the log type or level
@@ -191,8 +191,3 @@ function createLogProxy(logger: Consola): Consola {
* @see {createLogProxy}
*/
export default rootLogger;
// these are just type-sanity checks
<{[k in LogType]?: typeof ForegroundColor}>DocutilsReporter.TYPE_COLOR_MAP;
<{[k in LogLevel]?: typeof ForegroundColor}>DocutilsReporter.LEVEL_COLORS;
<{[k in LogType]?: string}>DocutilsReporter.TYPE_ICONS;
+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';
+3 -9
View File
@@ -9,16 +9,10 @@ import type {TypeDocOptions} from 'typedoc';
/**
* A `tsconfig.json` file w/ `$schema` prop
*
* Due to some `unknown` types in {@linkcode type-fest.TsConfigJson}, we cannot use that type
* directly and need to use `Jsonify`.
*
*/
export type TsConfigJson = Jsonify<
TsConfigJsonBase & {
$schema?: string;
}
>;
export type TsConfigJson = TsConfigJsonBase & {
$schema?: string;
};
/**
* A `typedoc.json` file w/ `$schema` and `extends` props
+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 {
+9 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/docutils",
"version": "0.2.0",
"version": "0.3.0",
"description": "Documentation generation utilities for Appium and related projects",
"keywords": [
"automation",
@@ -46,36 +46,34 @@
"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.5",
"@appium/support": "^3.1.7",
"@appium/tsconfig": "^0.2.4",
"@appium/typedoc-plugin-appium": "^0.4.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.5.6",
"typedoc": "0.23.24",
"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.7.4",
"typescript": "4.9.5",
"yaml": "2.2.1",
"yargs": "17.6.2",
"yargs": "17.7.1",
"yargs-parser": "21.1.1"
},
"engines": {
+1 -1
View File
@@ -1,4 +1,4 @@
mkdocs==1.4.2
mkdocs-material==9.0.11
mkdocs-material==9.1.1
mkdocs-material-extensions==1.1.1
mike==1.1.2
+1 -1
View File
@@ -1,4 +1,4 @@
import {Mike} from '../..';
import {Mike} from '../../lib/mike';
import {expect} from 'chai';
describe('Mike', function () {
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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)
### Bug Fixes
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
### Features
* **driver-test-support:** add startStoppableAppium() ([893bd88](https://github.com/appium/appium/commit/893bd888cf4e7b43e07c83e8ce71d71a30a8428e))
## [0.3.7](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.6...@appium/driver-test-support@0.3.7) (2023-02-09)
+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
@@ -2,6 +2,9 @@ export {createSessionHelpers, driverE2ETestSuite} from './e2e-suite';
export * from './unit-suite';
export * from './helpers';
// eslint-disable-next-line import/no-unresolved
export * from './stoppable';
/**
* @typedef {import('@appium/types').DriverClass} DriverClass
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
@@ -0,0 +1,57 @@
import type {AppiumServer} from '@appium/types';
import {main as startAppium} from 'appium';
import type {Args, CliCommandServer} from 'appium/types';
import B from 'bluebird';
import type {Server} from 'node:http';
import stoppable from 'stoppable';
import type {Asyncify} from 'type-fest';
/**
* Options for {@linkcode startStoppableAppium}
*/
export type AppiumServerOpts = Args<CliCommandServer>;
/**
* An {@linkcode AppiumServer} with a method `stop() => Promise<void>`, which closes all sockets and fully stops the server.
*
* Returned by {@linkcode startStoppableAppium}
*/
export type TestAppiumServer = Omit<NormativeAppiumServer, 'close'> & {
stop: Asyncify<stoppable.WithStop['stop']>;
close: (callback: (err?: Error) => void) => Promise<void>;
};
/**
* The {@linkcode AppiumServer} type, but with the `close` method normalized to a callback-style function.
*/
export type NormativeAppiumServer = Omit<AppiumServer, 'close'> & {
close: Server['close'];
};
/**
* Coerces {@linkcode AppiumServer} into a {@linkcode TestAppiumServer}.
* @param opts Options for {@linkcode startAppium}
* @todo This should be moved into `@appium/driver-test-support` or something
* @returns A stoppable Appium server
*/
export async function startStoppableAppium(opts: AppiumServerOpts): Promise<TestAppiumServer> {
const appiumServer = (await startAppium(opts)) as AppiumServer;
const stoppableServer = stoppable(appiumServer as unknown as NormativeAppiumServer, 0);
const originalAsyncClose = appiumServer.close;
(stoppableServer as unknown as TestAppiumServer).close = async function (
callback?: (err?: Error) => void
) {
if (callback) {
try {
await originalAsyncClose.call(this);
callback();
} catch (err) {
callback(err);
}
} else {
await originalAsyncClose.call(this);
}
};
stoppableServer.stop = B.promisify(stoppableServer.stop, {context: stoppableServer});
return stoppableServer as unknown as TestAppiumServer;
}
+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
*/
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/driver-test-support",
"version": "0.3.7",
"version": "0.4.1",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
@@ -41,16 +41,18 @@
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^0.9.1",
"@appium/types": "^0.10.1",
"@types/lodash": "4.14.191",
"axios": "1.2.3",
"@types/stoppable": "1.1.1",
"axios": "1.3.4",
"bluebird": "3.7.2",
"chai": "4.3.7",
"get-port": "5.1.1",
"lodash": "4.17.21",
"sinon": "15.0.1",
"source-map-support": "0.5.21",
"type-fest": "3.5.6"
"stoppable": "1.1.0",
"type-fest": "3.6.1"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
@@ -0,0 +1,37 @@
import axios from 'axios';
import B from 'bluebird';
import {Agent} from 'node:http';
import {startStoppableAppium, TestAppiumServer} from '../../lib';
import getPort from 'get-port';
const {expect} = chai;
describe('startStoppableAppium()', function () {
it('should start an Appium server', async function () {
let server: TestAppiumServer | undefined;
try {
server = await startStoppableAppium({port: await getPort()});
expect(server, 'to be an object');
} finally {
if (server) {
await expect(server.stop()).to.be.fulfilled;
}
}
});
describe('when the server has connections', function () {
it('should stop the server and resolve with a boolean', async function () {
const port = await getPort();
const server = await startStoppableAppium({port});
const getConnections = B.promisify(server.getConnections, {context: server});
await axios.get(`http://127.0.0.1:${port}/status`, {
httpAgent: new Agent({keepAlive: true}),
});
try {
await expect(getConnections()).to.eventually.be.greaterThan(0);
} finally {
await expect(server.stop()).to.eventually.be.a('boolean');
}
});
});
});
+1 -1
View File
@@ -10,6 +10,6 @@
},
"types": ["mocha", "chai", "chai-as-promised"]
},
"include": ["./lib"],
"include": ["lib", "test"],
"references": [{"path": "../types"}, {"path": "../base-driver"}]
}
@@ -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,28 @@
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)
### Bug Fixes
* **execute-driver-plugin:** update dependency webdriverio to v7.30.1 ([547b8a4](https://github.com/appium/appium/commit/547b8a45c4b5629720b0f4bdb0485a861aecbebf))
## [3.0.6](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.5...@appium/execute-driver-plugin@3.0.6) (2023-02-09)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/execute-driver-plugin",
"version": "3.0.6",
"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.0"
"webdriverio": "7.30.2"
},
"peerDependencies": {
"appium": "^2.0.0-beta.35"

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