mirror of
https://github.com/appium/appium.git
synced 2026-11-15 15:17:58 +08:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59dd30ba20 | ||
|
|
6480679bfb | ||
|
|
c2753107c6 | ||
|
|
a794ade7b0 | ||
|
|
5e24fbac5c | ||
|
|
8caf18f542 | ||
|
|
93e05a8269 | ||
|
|
5cc22325f2 | ||
|
|
a2b07f6150 | ||
|
|
823014321a | ||
|
|
d89203f96c | ||
|
|
773d9d6c0d | ||
|
|
a686053f03 | ||
|
|
5a3a0f7c14 |
@@ -1 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 17.x]
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
+17
-24
@@ -19,8 +19,6 @@ module.exports = (wallaby) => {
|
||||
'!./packages/*/gulpfile.js',
|
||||
'!./packages/*/scripts/**',
|
||||
'./packages/*/test/**/fixtures/**/*',
|
||||
'./packages/*/test/**/mocks.js',
|
||||
'./packages/*/test/helpers.js',
|
||||
'./babel.config.json',
|
||||
// below this are fixtures
|
||||
{
|
||||
@@ -35,34 +33,29 @@ module.exports = (wallaby) => {
|
||||
instrument: false,
|
||||
pattern: './packages/base-driver/static/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/gulp-plugins/build/**/*',
|
||||
},
|
||||
// this setup file is special
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './test/setup.js'
|
||||
},
|
||||
'!**/local_appium_home/**'
|
||||
'!**/local_appium_home/**',
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: [
|
||||
'./packages/*/test/**/*-specs.js',
|
||||
'./packages/*/test/unit/**/*.spec.js',
|
||||
'!./packages/*/test/**/*-e2e-specs.js',
|
||||
'!./packages/*/test/e2e/**/*',
|
||||
'!./packages/*/node_modules/**',
|
||||
// this is more of an E2E test and it's tedious to run
|
||||
'!./packages/gulp-plugins/test/transpile-specs.js',
|
||||
'!**/local_appium_home/**'
|
||||
],
|
||||
tests: ['./packages/*/test/unit/**/*.spec.js'],
|
||||
workers: {
|
||||
restart: true,
|
||||
},
|
||||
setup() {
|
||||
require('./test/setup');
|
||||
// This copied out of `./test/setup.js`, which uses `@babel/register`.
|
||||
// Wallaby doesn't need `@babel/register` (and it probably makes Wallaby slow),
|
||||
// but we need the other stuff, so here it is.
|
||||
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const sinonChai = require('sinon-chai');
|
||||
|
||||
// The `chai` global is set if a test needs something special.
|
||||
// Most tests won't need this.
|
||||
global.chai = chai.use(chaiAsPromised).use(sinonChai);
|
||||
|
||||
// `should()` is only necessary when working with some `null` or `undefined` values.
|
||||
global.should = chai.should();
|
||||
},
|
||||
runMode: 'onsave'
|
||||
runMode: 'onsave',
|
||||
};
|
||||
};
|
||||
|
||||
+5
-13
@@ -29,11 +29,11 @@
|
||||
"build:loose": "run-s -c build:distfiles build:types || true",
|
||||
"build:types": "tsc -b",
|
||||
"clean": "run-s clean:artifacts clean:packages clean:monorepo",
|
||||
"clean:artifacts": "run-s clean:distfiles clean:types && npx rimraf \"packages/*/build\"",
|
||||
"clean:distfiles": "npx rimraf \"packages/*/build/**/*.js\"",
|
||||
"clean:monorepo": "npx rimraf node_modules package-lock.json",
|
||||
"clean:packages": "lerna clean -y && npx rimraf \"packages/*/package-lock.json\"",
|
||||
"clean:types": "tsc -b --clean",
|
||||
"clean:artifacts": "run-s clean:distfiles clean:types && npx rimraf \"packages/*/build\"",
|
||||
"dev": "run-p dev:*",
|
||||
"dev:distfiles": "lerna run --parallel --prefix --concurrency=8 dev",
|
||||
"dev:types": "tsc -b --watch",
|
||||
@@ -70,14 +70,7 @@
|
||||
"precommit-lint"
|
||||
],
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"bracketSpacing": false,
|
||||
"singleQuote": true
|
||||
"*.js": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "file:packages/base-driver",
|
||||
@@ -85,7 +78,6 @@
|
||||
"@appium/doctor": "file:packages/doctor",
|
||||
"@appium/docutils": "file:packages/docutils",
|
||||
"@appium/eslint-config-appium": "file:packages/eslint-config-appium",
|
||||
"@appium/execute-driver-plugin": "file:packages/execute-driver-plugin",
|
||||
"@appium/fake-driver": "file:packages/fake-driver",
|
||||
"@appium/fake-plugin": "file:packages/fake-plugin",
|
||||
"@appium/gulp-plugins": "file:packages/gulp-plugins",
|
||||
@@ -97,6 +89,7 @@
|
||||
"@appium/test-support": "file:packages/test-support",
|
||||
"@appium/types": "file:packages/types",
|
||||
"@appium/universal-xml-plugin": "file:packages/universal-xml-plugin",
|
||||
"@appium/execute-driver-plugin": "file:packages/execute-driver-plugin",
|
||||
"appium": "file:packages/appium"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -149,7 +142,6 @@
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "9.0.0",
|
||||
@@ -164,15 +156,15 @@
|
||||
"lint-staged": "12.4.1",
|
||||
"log-symbols": "4.1.0",
|
||||
"mjpeg-server": "0.3.1",
|
||||
"mocha": "9.2.2",
|
||||
"mocha": "10.0.0",
|
||||
"npm-run-all": "4.1.5",
|
||||
"pre-commit": "1.2.2",
|
||||
"prettier": "2.6.2",
|
||||
"rewiremock": "3.14.3",
|
||||
"rimraf": "3.0.2",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "13.0.2",
|
||||
"sinon-chai": "3.7.0",
|
||||
"strip-ansi": "6.0.1",
|
||||
"sync-monorepo-packages": "0.3.5",
|
||||
"through2": "4.0.2",
|
||||
"type-fest": "2.12.2",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.35](https://github.com/appium/appium/compare/appium@2.0.0-beta.34...appium@2.0.0-beta.35) (2022-05-03)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.34](https://github.com/appium/appium/compare/appium@2.0.0-beta.33...appium@2.0.0-beta.34) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
@@ -2,4 +2,187 @@
|
||||
title: Intro to Appium Drivers
|
||||
---
|
||||
|
||||
TODO
|
||||
As the [main Overview](index.md) makes clear, "drivers" are basically Appium's answer to the
|
||||
question, "how do we support automation of multiple, unrelated platforms?" In this doc we'll get
|
||||
into a little more detail about how drivers work. The specific details of how drivers work probably
|
||||
don't matter too much for you, unless you're planning on writing your own driver or contributing to
|
||||
an existing driver (things we hope you do!).
|
||||
|
||||
The main benefit in understanding a bit more of how drivers work is that being aware of the typical
|
||||
complexity or the typical driver architecture will inform your debugging process when you
|
||||
inevitably run into an issue in one of your tests.
|
||||
|
||||
## Interface Implementations
|
||||
|
||||
At the most basic level, drivers are simply Node.js classes that extend a special class included in
|
||||
Appium, called `BaseDriver`. You could have something very close to a "working" driver, with these
|
||||
very simple lines of code:
|
||||
|
||||
```js
|
||||
import BaseDriver from '@appium/base-driver'
|
||||
|
||||
class MyNewDriver extends BaseDriver {
|
||||
}
|
||||
```
|
||||
|
||||
This empty driver doesn't *do* anything, but you could wrap it up in a Node.js module, add a few
|
||||
Appium-related fields to the module's manifest (`package.json`), and then install it using `appium
|
||||
driver install`.
|
||||
|
||||
So, from a technical perspective, an Appium driver is just a bit of code that inherits from some
|
||||
other Appium code. That's it! Now, inheriting from `BaseDriver` actually gives us a lot, because
|
||||
`BaseDriver` is essentially an encapsulation of the entire WebDriver protocol. So all a driver
|
||||
needs to do to do something useful is to *implement* Node.js methods with names corresponding to
|
||||
their WebDriver protocol equivalents.
|
||||
|
||||
So let's say I wanted to do something with this empty driver; first I have to decide which
|
||||
WebDriver command I want to implement. For our example, let's take the [Navigate
|
||||
To](https://www.w3.org/TR/webdriver1/#navigate-to) WebDriver command. Leave aside for the moment
|
||||
what I want to have the driver *do* when this command is executed. To tell Appium the driver can
|
||||
handle the command, all we have to do is define a method like this in our driver class:[^1]
|
||||
|
||||
```js
|
||||
async setUrl(url) {
|
||||
// do whatever we want here
|
||||
}
|
||||
```
|
||||
|
||||
[^1]: You might notice that `setUrl` doesn't look anything like `Navigate To`, so how did we know
|
||||
to use it rather than some other random string? Well, Appium's WebDriver-protocol-to-method-name
|
||||
mapping is defined in a special file within the `@appium/base-driver` package called
|
||||
[routes.js](https://github.com/appium/appium/blob/2.0/packages/base-driver/lib/protocol/routes.js).
|
||||
So if you're writing a driver, this is where you would go to figure out what method names to use
|
||||
and what parameters to expect. Or you could look at the source for any of the main Appium
|
||||
drivers!
|
||||
|
||||
That's it! How we actually implement the command is totally up to us, and depends on the
|
||||
platform(s) we want to support. Here are some different example implementations of this command for
|
||||
different platforms:
|
||||
|
||||
- Browsers: execute some JavaScript to set `window.location.href`
|
||||
- iOS apps: launch an app using a deep link
|
||||
- Android apps: launch an app using a deep link
|
||||
- React apps: load a specific route
|
||||
- Unity: go to a named scene
|
||||
|
||||
So you can see there can be a lot of differences between how drivers implement the same WebDriver
|
||||
command across platforms.[^2] What is the *same*, though, is how they express that they can handle
|
||||
a protocol command.
|
||||
|
||||
[^2]: Of course, we want to keep the semantics as similar as possible, but in the world of iOS, for
|
||||
example, launching an app via a deep link (a URL with a special app-specific scheme) is about as
|
||||
close as we are going to get to navigating to a web URL.
|
||||
|
||||
We're going into this great amount of detail (which you don't need to remember, by the way),
|
||||
because it's important to stress the point that an Appium driver is not inherently anything in
|
||||
particular, other than a bit of JS code that can handle WebDriver protocol commands. Where you go
|
||||
from there is up to you, the driver author!
|
||||
|
||||
## Automation mapping
|
||||
|
||||
But *typically* what driver authors want to do is to provide automation behaviours for a given
|
||||
platform(s) that are semantically very similar to the the WebDriver spec implementations for
|
||||
browsers. When you want to find an element, you should get a reference to a UI element. When you
|
||||
want to click or tap that element, the resulting behaviour should be the same as if a person were
|
||||
to click or tap on the element. And so on.
|
||||
|
||||
So the real challenge for driver authors is not how to work with the WebDriver protocol (because
|
||||
`BaseDriver` encapsulates all that for you), but how to make the actual automation happen on the
|
||||
target platform. Every driver relies on its own set of underlying technologies here. As mentioned
|
||||
in the [Overview](index.md), the iOS driver uses an Apple technology called
|
||||
[XCUITest](https://developer.apple.com/documentation/xctest/xcuielement). These underlying
|
||||
automation technologies usually have proprietary or idiosyncratic APIs of their own. Writing
|
||||
a driver becomes the task of mapping the WebDriver protocol to this underlying API (or sometimes
|
||||
a set of different underlying APIs--for example, the UiAutomator2 driver relies not only on the
|
||||
[UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator)
|
||||
technology from Google, but also functions only available through
|
||||
[ADB](https://developer.android.com/studio/command-line/adb), as well as functions only available
|
||||
via the Android SDK inside a helper app). Tying it all together into a single, usable, WebDriver
|
||||
interface is the incredibly useful (but incredibly challenging) art of driver development!
|
||||
|
||||
## Multi-level architecture
|
||||
|
||||
In practice, this often results in a pretty complex architecture. Let's take iOS for example again.
|
||||
The XCUITest framework (the one used by the Appium driver) expects code that calls it to be written
|
||||
in Objective-C or Swift. Furthermore, XCUITest code can only be run in a special mode triggered by
|
||||
Xcode (and directly or indirectly, the Xcode command line tools). In other words, there's no
|
||||
straightforward way to go from a Node.js function implementation (like `setUrl()` above) to
|
||||
XCUITest API calls.
|
||||
|
||||
What the XCUITest driver authors have done is instead to split the driver into two parts: one part
|
||||
written in Node.js (the part which is incorporated into Appium and which initially handles the
|
||||
WebDriver commands), and the other part written in Objective-C (the part which actually gets run on
|
||||
an iOS device and makes XCUITest API calls). This makes interfacing with XCUITest possible, but
|
||||
introduces the new problem of coordination between the two parts.
|
||||
|
||||
The driver authors could have chosen any of a number of very different strategies to model the
|
||||
communication between the Node.js side and the Objective-C side, but at the end of the day decided
|
||||
to use ... the WebDriver protocol! That's right, the Objective-C side of the XCUITest driver is
|
||||
itself a WebDriver implementation, called
|
||||
[WebDriverAgent](https://github.com/appium/webdriveragent).[^3]
|
||||
|
||||
[^3]: You could in theory, therefore, point your WebDriver client straight to WebDriverAgent and
|
||||
bypass Appium entirely. This is usually not convenient, however, for a few reasons:
|
||||
|
||||
- The Appium XCUITest driver builds and manages WebDriverAgent for you, which can be a pain and
|
||||
involves the use of Xcode.
|
||||
- The XCUITest driver does lots more than what can be done by WebDriverAgent, for example working
|
||||
with simulators or devices, installing apps, and the like.
|
||||
|
||||
The moral of the story is that driver architectures can become quite complicated and multilayered,
|
||||
due to the nature of the problem we're trying to solve. It also means it can be difficult sometimes
|
||||
to tell where in this chain of technologies something has gone wrong, if you run into a problem
|
||||
with a particular test. With the XCUITest world again, we have something like the following set of
|
||||
technologies all in play at the same time:
|
||||
|
||||
- Your test code (in its programming language) - owned by you
|
||||
- The Appium client library - owned by Appium
|
||||
- The Selenium client library - owned by Selenium
|
||||
- The network (local or Internet)
|
||||
- The Appium server - owned by Appium
|
||||
- The Appium XCUITest driver - owned by Appium
|
||||
- WebDriverAgent - owned by Appium
|
||||
- Xcode - owned by Apple
|
||||
- XCUITest - owned by Apple
|
||||
- iOS itself - owned by Apple
|
||||
- macOS (where Xcode and iOS simulators run) - owned by Apple
|
||||
|
||||
It's a pretty deep stack!
|
||||
|
||||
## Proxy mode
|
||||
|
||||
There's one other important architectural aspect of drivers to understand. It can be exemplified
|
||||
again by the XCUITest driver. Recall that we just discussed how the two "halves" of the XCUITest
|
||||
driver both speak the WebDriver protocol---the Node.js half clicks right into Appium's WebDriver
|
||||
server, and the Objective-c half (WebDriverAgent) is its own WebDriver implementation.
|
||||
|
||||
This opens up the possibility of Appium taking a shortcut in certain cases. Let's imagine that the
|
||||
XCUITest driver needs to implement the `Click Element` command. The internal code of this
|
||||
implementation would look something like taking the appropriate parameters and constructing an HTTP
|
||||
request to the WebDriverAgent server. In this case, we're basically just reconstructing the
|
||||
client's original call to the Appium server![^4] So there's really no need to even write a function
|
||||
implementing the `Click Element` command. Instead, the XCUITest driver can just let Appium know
|
||||
that this command should be proxied directly to some other WebDriver server.
|
||||
|
||||
[^4]: It's not *exactly* the same call, because the Appium server and the WebDriverAgent server
|
||||
will generate different session IDs, but these differences will be handled transparently.
|
||||
|
||||
If you're not familiar with the concept of "proxying," in this case it just means that the XCUITest
|
||||
driver will not be involved at all in handling the command. Instead it will merely be repackaged
|
||||
and forwarded to WebDriverAgent at the protocol level, and WebDriverAgent's response will likewise
|
||||
be passed back directly to the client, without any XCUITest driver code seeing it or modifying it.
|
||||
|
||||
This architectural pattern provides a nice bonus for driver authors who choose to deal with the
|
||||
WebDriver protocol everywhere, rather than constructing bespoke protocols. It also means that
|
||||
Appium can create wrapper drivers for any other existing WebDriver implementation very easily. If
|
||||
you look at the [Appium Safari driver](https://github.com/appium/appium-safari-driver) code, for
|
||||
example, you'll see that it implements basically no standard commands, because all of these are
|
||||
proxied directly to an underlying SafariDriver process.
|
||||
|
||||
It's important to understand that this proxying business is sometimes happening under the hood,
|
||||
because if you're ever diving into some open source driver code trying to figure out where
|
||||
a command is implemented, you might be surprised to find no implementation at all in the Node.js
|
||||
driver code itself! In that case, you'll need to figure out where commands are being proxied to so
|
||||
you can look there for the appropriate implementation.
|
||||
|
||||
OK, that's enough for this very detailed introduction to drivers!
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"additionalProperties": false,
|
||||
"description": "A schema for Appium configuration files",
|
||||
"properties": {
|
||||
"server": {
|
||||
"additionalProperties": false,
|
||||
"description": "Configuration when running Appium as a server",
|
||||
"properties": {
|
||||
"address": {
|
||||
"$comment": "I think hostname covers both DNS and IPv4...could be wrong",
|
||||
"appiumCliAliases": [
|
||||
"a"
|
||||
],
|
||||
"default": "0.0.0.0",
|
||||
"description": "IP address to listen on",
|
||||
"format": "hostname",
|
||||
"title": "address config",
|
||||
"type": "string"
|
||||
},
|
||||
"allow-cors": {
|
||||
"description": "Whether the Appium server should allow web browser connections from any host",
|
||||
"title": "allow-cors config",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"allow-insecure": {
|
||||
"appiumCliTransformer": "csv",
|
||||
"default": [],
|
||||
"description": "Set which insecure features are allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Note that features defined via \"deny-insecure\" will be disabled, even if also listed here. If string, a path to a text file containing policy or a comma-delimited list.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "allow-insecure config",
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"base-path": {
|
||||
"appiumCliAliases": [
|
||||
"pa"
|
||||
],
|
||||
"default": "",
|
||||
"description": "Base path to use as the prefix for all webdriver routes running on the server",
|
||||
"title": "base-path config",
|
||||
"type": "string"
|
||||
},
|
||||
"callback-address": {
|
||||
"appiumCliAliases": [
|
||||
"ca"
|
||||
],
|
||||
"description": "Callback IP address (default: same as \"address\")",
|
||||
"title": "callback-address config",
|
||||
"type": "string"
|
||||
},
|
||||
"callback-port": {
|
||||
"appiumCliAliases": [
|
||||
"cp"
|
||||
],
|
||||
"default": 4723,
|
||||
"description": "Callback port (default: same as \"port\")",
|
||||
"maximum": 65535,
|
||||
"minimum": 1,
|
||||
"title": "callback-port config",
|
||||
"type": "integer"
|
||||
},
|
||||
"debug-log-spacing": {
|
||||
"default": false,
|
||||
"description": "Add exaggerated spacing in logs to help with visual inspection",
|
||||
"title": "debug-log-spacing config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"default-capabilities": {
|
||||
"$comment": "TODO",
|
||||
"appiumCliAliases": [
|
||||
"dc"
|
||||
],
|
||||
"description": "Set the default desired capabilities, which will be set on each session unless overridden by received capabilities. If a string, a path to a JSON file containing the capabilities, or raw JSON.",
|
||||
"title": "default-capabilities config",
|
||||
"type": "object"
|
||||
},
|
||||
"deny-insecure": {
|
||||
"$comment": "Allowed values are defined by drivers",
|
||||
"appiumCliTransformer": "csv",
|
||||
"default": [],
|
||||
"description": "Set which insecure features are not allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Features listed here will not be enabled even if also listed in \"allow-insecure\", and even if \"relaxed-security\" is enabled. If string, a path to a text file containing policy or a comma-delimited list.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "deny-insecure config",
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"driver": {
|
||||
"description": "Driver-specific configuration. Keys should correspond to driver package names",
|
||||
"properties": {},
|
||||
"title": "driver config",
|
||||
"type": "object"
|
||||
},
|
||||
"keep-alive-timeout": {
|
||||
"appiumCliAliases": [
|
||||
"ka"
|
||||
],
|
||||
"default": 600,
|
||||
"description": "Number of seconds the Appium server should apply as both the keep-alive timeout and the connection timeout for all requests. A value of 0 disables the timeout.",
|
||||
"minimum": 0,
|
||||
"title": "keep-alive-timeout config",
|
||||
"type": "integer"
|
||||
},
|
||||
"local-timezone": {
|
||||
"default": false,
|
||||
"description": "Use local timezone for timestamps",
|
||||
"title": "local-timezone config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"log": {
|
||||
"appiumCliAliases": [
|
||||
"g"
|
||||
],
|
||||
"appiumCliDest": "logFile",
|
||||
"description": "Also send log output to this file",
|
||||
"title": "log config",
|
||||
"type": "string"
|
||||
},
|
||||
"log-filters": {
|
||||
"$comment": "TODO",
|
||||
"description": "One or more log filtering rules",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "log-filters config",
|
||||
"type": "array"
|
||||
},
|
||||
"log-level": {
|
||||
"appiumCliDest": "loglevel",
|
||||
"default": "debug",
|
||||
"description": "Log level (console[:file])",
|
||||
"enum": [
|
||||
"info",
|
||||
"info:debug",
|
||||
"info:info",
|
||||
"info:warn",
|
||||
"info:error",
|
||||
"warn",
|
||||
"warn:debug",
|
||||
"warn:info",
|
||||
"warn:warn",
|
||||
"warn:error",
|
||||
"error",
|
||||
"error:debug",
|
||||
"error:info",
|
||||
"error:warn",
|
||||
"error:error",
|
||||
"debug",
|
||||
"debug:debug",
|
||||
"debug:info",
|
||||
"debug:warn",
|
||||
"debug:error"
|
||||
],
|
||||
"title": "log-level config",
|
||||
"type": "string"
|
||||
},
|
||||
"log-no-colors": {
|
||||
"default": false,
|
||||
"description": "Do not use color in console output",
|
||||
"title": "log-no-colors config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"log-timestamp": {
|
||||
"default": false,
|
||||
"description": "Show timestamps in console output",
|
||||
"title": "log-timestamp config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"long-stacktrace": {
|
||||
"default": false,
|
||||
"description": "Add long stack traces to log entries. Recommended for debugging only.",
|
||||
"title": "long-stacktrace config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"no-perms-check": {
|
||||
"default": false,
|
||||
"description": "Do not check that needed files are readable and/or writable",
|
||||
"title": "no-perms-check config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"nodeconfig": {
|
||||
"$comment": "Selenium Grid 3 is unmaintained and Selenium Grid 4 no longer supports this file.",
|
||||
"description": "Path to configuration JSON file to register Appium as a node with Selenium Grid 3; otherwise the configuration itself",
|
||||
"title": "nodeconfig config",
|
||||
"type": "object"
|
||||
},
|
||||
"plugin": {
|
||||
"description": "Plugin-specific configuration. Keys should correspond to plugin package names",
|
||||
"properties": {},
|
||||
"title": "plugin config",
|
||||
"type": "object"
|
||||
},
|
||||
"port": {
|
||||
"appiumCliAliases": [
|
||||
"p"
|
||||
],
|
||||
"default": 4723,
|
||||
"description": "Port to listen on",
|
||||
"maximum": 65535,
|
||||
"minimum": 1,
|
||||
"title": "port config",
|
||||
"type": "integer"
|
||||
},
|
||||
"relaxed-security": {
|
||||
"default": false,
|
||||
"description": "Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it's not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using \"deny-insecure\"",
|
||||
"title": "relaxed-security config",
|
||||
"type": "boolean",
|
||||
"appiumCliDest": "relaxedSecurityEnabled"
|
||||
},
|
||||
"session-override": {
|
||||
"default": false,
|
||||
"description": "Enables session override (clobbering)",
|
||||
"title": "session-override config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"strict-caps": {
|
||||
"default": false,
|
||||
"description": "Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device",
|
||||
"title": "strict-caps config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tmp": {
|
||||
"appiumCliDest": "tmpDir",
|
||||
"description": "Absolute path to directory Appium can use to manage temp files. Defaults to C:\\Windows\\Temp on Windows and /tmp otherwise.",
|
||||
"title": "tmp config",
|
||||
"type": "string"
|
||||
},
|
||||
"trace-dir": {
|
||||
"description": "Absolute path to directory Appium can use to save iOS instrument traces; defaults to <tmp>/appium-instruments",
|
||||
"title": "trace-dir config",
|
||||
"type": "string"
|
||||
},
|
||||
"use-drivers": {
|
||||
"appiumCliDescription": "A list of drivers to activate. Can be a comma-delimited string or path to CSV file. By default, all installed drivers will be activated.",
|
||||
"default": [],
|
||||
"description": "A list of drivers to activate. By default, all installed drivers will be activated.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "use-drivers config",
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"use-plugins": {
|
||||
"appiumCliDescription": "A list of plugins to activate. Can be a comma-delimited string, path to CSV file, or the string \"all\" to use all installed plugins.",
|
||||
"default": [],
|
||||
"description": "A list of plugins to activate. To activate all plugins, the value should be an array with a single item \"all\".",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "use-plugins config",
|
||||
"type": "array",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"webhook": {
|
||||
"$comment": "This should probably use a uri-template format to restrict the protocol to http/https",
|
||||
"appiumCliAliases": [
|
||||
"G"
|
||||
],
|
||||
"description": "Also send log output to this http listener",
|
||||
"format": "uri",
|
||||
"title": "webhook config",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "server config",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"title": "Appium Configuration",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
import _ from 'lodash';
|
||||
import ExtensionCommand from './extension-command';
|
||||
import { KNOWN_DRIVERS } from '../constants';
|
||||
import {KNOWN_DRIVERS} from '../constants';
|
||||
import '@colors/colors';
|
||||
|
||||
const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mainClass'];
|
||||
const REQ_DRIVER_FIELDS = [
|
||||
'driverName',
|
||||
'automationName',
|
||||
'platformNames',
|
||||
'mainClass',
|
||||
];
|
||||
|
||||
/**
|
||||
* @extends {ExtensionCommand<import('../extension/manifest').DriverType>}
|
||||
* @extends {ExtensionCommand<DriverType>}
|
||||
*/
|
||||
export default class DriverCommand extends ExtensionCommand {
|
||||
|
||||
/**
|
||||
* @param {DriverCommandOptions} opts
|
||||
*/
|
||||
@@ -18,43 +23,63 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
}
|
||||
|
||||
async install ({driver, installType, packageName}) {
|
||||
return await super._install({ext: driver, installType, packageName});
|
||||
return await super._install({
|
||||
installSpec: driver,
|
||||
installType,
|
||||
packageName,
|
||||
});
|
||||
}
|
||||
|
||||
async uninstall ({driver}) {
|
||||
return await super._uninstall({ext: driver});
|
||||
return await super._uninstall({installSpec: driver});
|
||||
}
|
||||
|
||||
async update ({driver, unsafe}) {
|
||||
return await super._update({ext: driver, unsafe});
|
||||
return await super._update({installSpec: driver, unsafe});
|
||||
}
|
||||
|
||||
async run ({driver, scriptName}) {
|
||||
return await super._run({ext: driver, scriptName});
|
||||
return await super._run({installSpec: driver, scriptName});
|
||||
}
|
||||
|
||||
getPostInstallText ({extName, extData}) {
|
||||
return `Driver ${extName}@${extData.version} successfully installed\n`.green +
|
||||
`- automationName: ${extData.automationName.green}\n` +
|
||||
`- platformNames: ${JSON.stringify(extData.platformNames).green}`;
|
||||
return (
|
||||
`Driver ${extName}@${extData.version} successfully installed\n`.green +
|
||||
`- automationName: ${extData.automationName.green}\n` +
|
||||
`- platformNames: ${JSON.stringify(extData.platformNames).green}`
|
||||
);
|
||||
}
|
||||
|
||||
validateExtensionFields (appiumPkgData) {
|
||||
const missingFields = REQ_DRIVER_FIELDS.reduce((acc, field) => (
|
||||
appiumPkgData[field] ? acc : [...acc, field]
|
||||
), []);
|
||||
/**
|
||||
* Validates fields in `appium` field of `driverMetadata`
|
||||
*
|
||||
* For any `package.json` fields which a driver requires, validate the type of
|
||||
* those fields on the `package.json` data, throwing an error if anything is
|
||||
* amiss.
|
||||
* @param {import('appium/types').ExtMetadata<DriverType>} driverMetadata
|
||||
* @param {string} installSpec
|
||||
*/
|
||||
validateExtensionFields (driverMetadata, installSpec) {
|
||||
const missingFields = REQ_DRIVER_FIELDS.reduce(
|
||||
(acc, field) => (driverMetadata[field] ? acc : [...acc, field]),
|
||||
[]
|
||||
);
|
||||
|
||||
if (!_.isEmpty(missingFields)) {
|
||||
throw new Error(`Installed driver did not expose correct fields for compability ` +
|
||||
`with Appium. Missing fields: ${JSON.stringify(missingFields)}`);
|
||||
throw new Error(
|
||||
`Driver "${installSpec}" did not expose correct fields for compability ` +
|
||||
`with Appium. Missing fields: ${JSON.stringify(missingFields)}`
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DriverCommandOptions
|
||||
* @property {import('../extension/extension-config').ExtensionConfig<import('../extension/manifest').DriverType>} config
|
||||
* @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
|
||||
* @property {boolean} json
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('appium/types').DriverType} DriverType
|
||||
*/
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import { npm, fs, util, env } from '@appium/support';
|
||||
import { log, 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 { packageDidChange } from '../extension/package-changed';
|
||||
import {npm, fs, util, env} from '@appium/support';
|
||||
import {log, 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 {packageDidChange} from '../extension/package-changed';
|
||||
|
||||
const UPDATE_ALL = 'installed';
|
||||
|
||||
@@ -80,24 +84,31 @@ class ExtensionCommand {
|
||||
* @return {Promise<ExtensionListData>} map of extension names to extension data
|
||||
*/
|
||||
async list ({showInstalled, showUpdates}) {
|
||||
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
|
||||
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${
|
||||
this.type
|
||||
}s`;
|
||||
const installedNames = Object.keys(this.config.installedExtensions);
|
||||
const knownNames = Object.keys(this.knownExtensions);
|
||||
const exts = [...installedNames, ...knownNames].reduce((acc, name) => {
|
||||
if (!acc[name]) {
|
||||
if (installedNames.includes(name)) {
|
||||
acc[name] = {...this.config.installedExtensions[name], installed: true};
|
||||
} else if (!showInstalled) {
|
||||
acc[name] = {pkgName: this.knownExtensions[name], installed: false};
|
||||
const exts = [...installedNames, ...knownNames].reduce(
|
||||
(acc, name) => {
|
||||
if (!acc[name]) {
|
||||
if (installedNames.includes(name)) {
|
||||
acc[name] = {
|
||||
...this.config.installedExtensions[name],
|
||||
installed: true,
|
||||
};
|
||||
} else if (!showInstalled) {
|
||||
acc[name] = {pkgName: this.knownExtensions[name], installed: false};
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
/**
|
||||
* This accumulator contains either {@linkcode UninstalledExtensionLIstData} _or_
|
||||
* {@linkcode InstalledExtensionListData} without upgrade information (which is added by the below code block)
|
||||
* @type {Record<string,Partial<InstalledExtensionListData>|UninstalledExtensionListData>}
|
||||
*/({}));
|
||||
return acc;
|
||||
},
|
||||
/**
|
||||
* This accumulator contains either {@linkcode UninstalledExtensionLIstData} _or_
|
||||
* {@linkcode InstalledExtensionListData} without upgrade information (which is added by the below code block)
|
||||
* @type {Record<string,Partial<InstalledExtensionListData>|UninstalledExtensionListData>}
|
||||
*/ ({})
|
||||
);
|
||||
|
||||
// if we want to show whether updates are available, put that behind a spinner
|
||||
await spinWith(this.isJsonOutput, lsMsg, async () => {
|
||||
@@ -113,11 +124,12 @@ class ExtensionCommand {
|
||||
const updates = await this.checkForExtensionUpdate(ext);
|
||||
data.updateVersion = updates.safeUpdate;
|
||||
data.unsafeUpdateVersion = updates.unsafeUpdate;
|
||||
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
|
||||
data.upToDate =
|
||||
updates.safeUpdate === null && updates.unsafeUpdate === null;
|
||||
}
|
||||
});
|
||||
|
||||
const listData = /** @type {ExtensionListData} */(exts);
|
||||
const listData = /** @type {ExtensionListData} */ (exts);
|
||||
|
||||
// if we're just getting the data, short circuit return here since we don't need to do any
|
||||
// formatting logic
|
||||
@@ -125,16 +137,20 @@ class ExtensionCommand {
|
||||
return listData;
|
||||
}
|
||||
|
||||
for (const [
|
||||
name,
|
||||
data
|
||||
] of _.toPairs(listData)) {
|
||||
for (const [name, data] of _.toPairs(listData)) {
|
||||
let installTxt = ' [not installed]'.grey;
|
||||
let updateTxt = '';
|
||||
let upToDateTxt = '';
|
||||
let unsafeUpdateTxt = '';
|
||||
if (data.installed) {
|
||||
const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} = data;
|
||||
const {
|
||||
installType,
|
||||
installSpec,
|
||||
updateVersion,
|
||||
unsafeUpdateVersion,
|
||||
version,
|
||||
upToDate,
|
||||
} = data;
|
||||
let typeTxt;
|
||||
switch (installType) {
|
||||
case INSTALL_TYPE_GIT:
|
||||
@@ -147,7 +163,9 @@ class ExtensionCommand {
|
||||
default:
|
||||
typeTxt = '(NPM)';
|
||||
}
|
||||
installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
|
||||
installTxt = `@${version.yellow} ${
|
||||
('[installed ' + typeTxt + ']').green
|
||||
}`;
|
||||
|
||||
if (showUpdates) {
|
||||
if (updateVersion) {
|
||||
@@ -157,12 +175,15 @@ class ExtensionCommand {
|
||||
upToDateTxt = ` [Up to date]`.green;
|
||||
}
|
||||
if (unsafeUpdateVersion) {
|
||||
unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
||||
unsafeUpdateTxt =
|
||||
` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
|
||||
console.log(
|
||||
`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`
|
||||
);
|
||||
}
|
||||
|
||||
return listData;
|
||||
@@ -174,33 +195,53 @@ class ExtensionCommand {
|
||||
* @param {InstallArgs} args
|
||||
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
||||
*/
|
||||
async _install ({ext, installType, packageName}) {
|
||||
async _install ({installSpec, installType, packageName}) {
|
||||
/** @type {ExtensionFields<typeof this.type>} */
|
||||
let extData;
|
||||
let installSpec = ext;
|
||||
|
||||
if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) {
|
||||
throw new Error(`When using --source=${installType}, cannot also use --package`);
|
||||
if (
|
||||
packageName &&
|
||||
[INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)
|
||||
) {
|
||||
throw new Error(
|
||||
`When using --source=${installType}, cannot also use --package`
|
||||
);
|
||||
}
|
||||
|
||||
if (!packageName && [INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)) {
|
||||
throw new Error(`When using --source=${installType}, must also use --package`);
|
||||
if (
|
||||
!packageName &&
|
||||
[INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)
|
||||
) {
|
||||
throw new Error(
|
||||
`When using --source=${installType}, must also use --package`
|
||||
);
|
||||
}
|
||||
|
||||
if (installType === INSTALL_TYPE_GITHUB) {
|
||||
if (installSpec.split('/').length !== 2) {
|
||||
throw new Error(`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
|
||||
'it should be of the form <org>/<repo>');
|
||||
throw new Error(
|
||||
`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
|
||||
'it should be of the form <org>/<repo>'
|
||||
);
|
||||
}
|
||||
extData = await this.installViaNpm({ext: installSpec, pkgName: /** @type {string} */(packageName)});
|
||||
extData = await this.installViaNpm({
|
||||
installSpec,
|
||||
pkgName: /** @type {string} */ (packageName),
|
||||
});
|
||||
} else if (installType === INSTALL_TYPE_GIT) {
|
||||
// git urls can have '.git' at the end, but this is not necessary and would complicate the
|
||||
// way we download and name directories, so we can just remove it
|
||||
installSpec = installSpec.replace(/\.git$/, '');
|
||||
extData = await this.installViaNpm({ext: installSpec, pkgName: /** @type {string} */(packageName)});
|
||||
extData = await this.installViaNpm({
|
||||
installSpec,
|
||||
pkgName: /** @type {string} */ (packageName),
|
||||
});
|
||||
} else {
|
||||
let pkgName, pkgVer;
|
||||
if (installType === INSTALL_TYPE_LOCAL) {
|
||||
pkgName = path.isAbsolute(installSpec) ? installSpec : path.resolve(installSpec);
|
||||
pkgName = path.isAbsolute(installSpec)
|
||||
? installSpec
|
||||
: path.resolve(installSpec);
|
||||
} else {
|
||||
// at this point we have either an npm package or an appium verified extension
|
||||
// name or a local path. both of which will be installed via npm.
|
||||
@@ -227,8 +268,9 @@ class ExtensionCommand {
|
||||
// check it exists and get the correct package
|
||||
const knownNames = Object.keys(this.knownExtensions);
|
||||
if (!_.includes(knownNames, name)) {
|
||||
const msg = `Could not resolve ${this.type}; are you sure it's in the list ` +
|
||||
`of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
|
||||
const msg =
|
||||
`Could not resolve ${this.type}; are you sure it's in the list ` +
|
||||
`of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
pkgName = this.knownExtensions[name];
|
||||
@@ -238,16 +280,18 @@ class ExtensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
extData = await this.installViaNpm({ext, pkgName, pkgVer});
|
||||
extData = await this.installViaNpm({installSpec, pkgName, pkgVer});
|
||||
}
|
||||
|
||||
const extName = extData[/** @type {string} */(`${this.type}Name`)];
|
||||
delete extData[/** @type {string} */(`${this.type}Name`)];
|
||||
const extName = extData[/** @type {string} */ (`${this.type}Name`)];
|
||||
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
||||
|
||||
if (this.config.isInstalled(extName)) {
|
||||
throw new Error(`A ${this.type} named '${extName}' is already installed. ` +
|
||||
`Did you mean to update? 'appium ${this.type} update'. See ` +
|
||||
`installed ${this.type}s with 'appium ${this.type} list --installed'.`);
|
||||
throw new Error(
|
||||
`A ${this.type} named '${extName}' is already installed. ` +
|
||||
`Did you mean to update? 'appium ${this.type} update'. See ` +
|
||||
`installed ${this.type}s with 'appium ${this.type} list --installed'.`
|
||||
);
|
||||
}
|
||||
|
||||
const extManifest = {...extData, installType, installSpec};
|
||||
@@ -269,19 +313,25 @@ class ExtensionCommand {
|
||||
*
|
||||
* @param {InstallViaNpmArgs} args
|
||||
*/
|
||||
async installViaNpm ({ext, pkgName, pkgVer}) {
|
||||
async installViaNpm ({installSpec, pkgName, pkgVer}) {
|
||||
const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`;
|
||||
const specMsg = npmSpec === ext ? '' : ` using NPM install spec '${npmSpec}'`;
|
||||
const msg = `Installing '${ext}'${specMsg}`;
|
||||
const specMsg =
|
||||
npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`;
|
||||
const msg = `Installing '${installSpec}'${specMsg}`;
|
||||
try {
|
||||
const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => (
|
||||
await npm.installPackage(this.config.appiumHome, pkgName, {
|
||||
pkgVer
|
||||
})
|
||||
));
|
||||
return this.getExtensionFields(pkgJsonData);
|
||||
const pkgJsonData = await spinWith(
|
||||
this.isJsonOutput,
|
||||
msg,
|
||||
async () =>
|
||||
await npm.installPackage(this.config.appiumHome, pkgName, {
|
||||
pkgVer,
|
||||
})
|
||||
);
|
||||
return this.getExtensionFields(pkgJsonData, installSpec);
|
||||
} catch (err) {
|
||||
throw new Error(`Encountered an error when installing package: ${err.message}`);
|
||||
throw new Error(
|
||||
`Encountered an error when installing package: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,31 +353,38 @@ class ExtensionCommand {
|
||||
* load as the main driver class, or to be able to detect incompatibilities between driver and
|
||||
* appium versions.
|
||||
*
|
||||
* @param {ExtPackageJson<ExtType>} pkgJsonData - the package.json data for a driver module, as if it had been
|
||||
* straightforwardly 'require'd
|
||||
* @param {ExtPackageJson<ExtType>} pkgJsonData - the package.json data for a driver module, as if it had been straightforwardly 'require'd
|
||||
* @param {string} installSpec
|
||||
* @returns {ExtensionFields<ExtType>}
|
||||
*/
|
||||
getExtensionFields (pkgJsonData) {
|
||||
getExtensionFields (pkgJsonData, installSpec) {
|
||||
if (!pkgJsonData.appium) {
|
||||
throw new Error(`Installed driver did not have an 'appium' section in its ` +
|
||||
`package.json file as expected`);
|
||||
throw new Error(
|
||||
`Installed driver did not have an 'appium' section in its ` +
|
||||
`package.json file as expected`
|
||||
);
|
||||
}
|
||||
const {appium, name, version} = pkgJsonData;
|
||||
this.validateExtensionFields(appium);
|
||||
this.validateExtensionFields(appium, installSpec);
|
||||
/** @type {unknown} */
|
||||
const result = {...appium, pkgName: name, version};
|
||||
return /** @type {ExtensionFields<ExtType>} */(result);
|
||||
return /** @type {ExtensionFields<ExtType>} */ (result);
|
||||
}
|
||||
|
||||
/**
|
||||
* For any package.json fields which a particular type of extension requires, validate the
|
||||
* presence and form of those fields on the package.json data, throwing an error if anything is
|
||||
|
||||
/**
|
||||
* For any `package.json` fields which a particular type of extension requires, validate the
|
||||
* presence and form of those fields on the `package.json` data, throwing an error if anything is
|
||||
* amiss.
|
||||
*
|
||||
* @param {ExtMetadata<ExtType>} appiumPkgData - the data in the "appium" field of package.json for an extension
|
||||
* @param {ExtMetadata<ExtType>} extMetadata - the data in the "appium" field of `package.json` for an extension
|
||||
* @param {string} installSpec - Extension name/spec
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
validateExtensionFields (appiumPkgData) {
|
||||
validateExtensionFields (extMetadata, installSpec) {
|
||||
throw new Error('Must be implemented in final class');
|
||||
}
|
||||
|
||||
@@ -337,17 +394,22 @@ class ExtensionCommand {
|
||||
* @param {UninstallOpts} opts
|
||||
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
||||
*/
|
||||
async _uninstall ({ext}) {
|
||||
if (!this.config.isInstalled(ext)) {
|
||||
throw new Error(`Can't uninstall ${this.type} '${ext}'; it is not installed`);
|
||||
async _uninstall ({installSpec}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
throw new Error(
|
||||
`Can't uninstall ${this.type} '${installSpec}'; it is not installed`
|
||||
);
|
||||
}
|
||||
const installPath = this.config.getInstallPath(ext);
|
||||
const installPath = this.config.getInstallPath(installSpec);
|
||||
try {
|
||||
await fs.rimraf(installPath);
|
||||
} finally {
|
||||
await this.config.removeExtension(ext);
|
||||
await this.config.removeExtension(installSpec);
|
||||
}
|
||||
log(this.isJsonOutput, `Successfully uninstalled ${this.type} '${ext}'`.green);
|
||||
log(
|
||||
this.isJsonOutput,
|
||||
`Successfully uninstalled ${this.type} '${installSpec}'`.green
|
||||
);
|
||||
return this.config.installedExtensions;
|
||||
}
|
||||
|
||||
@@ -357,13 +419,17 @@ class ExtensionCommand {
|
||||
* @param {ExtensionUpdateOpts} updateSpec
|
||||
* @return {Promise<ExtensionUpdateResult>}
|
||||
*/
|
||||
async _update ({ext, unsafe}) {
|
||||
const shouldUpdateAll = ext === UPDATE_ALL;
|
||||
async _update ({installSpec, unsafe}) {
|
||||
const shouldUpdateAll = installSpec === UPDATE_ALL;
|
||||
// if we're specifically requesting an update for an extension, make sure it's installed
|
||||
if (!shouldUpdateAll && !this.config.isInstalled(ext)) {
|
||||
throw new Error(`The ${this.type} '${ext}' was not installed, so can't be updated`);
|
||||
if (!shouldUpdateAll && !this.config.isInstalled(installSpec)) {
|
||||
throw new Error(
|
||||
`The ${this.type} '${installSpec}' was not installed, so can't be updated`
|
||||
);
|
||||
}
|
||||
const extsToUpdate = shouldUpdateAll ? Object.keys(this.config.installedExtensions) : [ext];
|
||||
const extsToUpdate = shouldUpdateAll
|
||||
? Object.keys(this.config.installedExtensions)
|
||||
: [installSpec];
|
||||
|
||||
// 'errors' will have ext names as keys and error objects as values
|
||||
/** @type {Record<string,Error>} */
|
||||
@@ -376,24 +442,40 @@ class ExtensionCommand {
|
||||
|
||||
for (const e of extsToUpdate) {
|
||||
try {
|
||||
await spinWith(this.isJsonOutput, `Checking if ${this.type} '${e}' is updatable`, () => {
|
||||
if (this.config.installedExtensions[e].installType !== INSTALL_TYPE_NPM) {
|
||||
throw new NotUpdatableError();
|
||||
await spinWith(
|
||||
this.isJsonOutput,
|
||||
`Checking if ${this.type} '${e}' is updatable`,
|
||||
() => {
|
||||
if (
|
||||
this.config.installedExtensions[e].installType !==
|
||||
INSTALL_TYPE_NPM
|
||||
) {
|
||||
throw new NotUpdatableError();
|
||||
}
|
||||
}
|
||||
});
|
||||
const update = await spinWith(this.isJsonOutput, `Checking if ${this.type} '${e}' needs an update`, async () => {
|
||||
const update = await this.checkForExtensionUpdate(e);
|
||||
if (!(update.safeUpdate || update.unsafeUpdate)) {
|
||||
throw new NoUpdatesAvailableError();
|
||||
);
|
||||
const update = await spinWith(
|
||||
this.isJsonOutput,
|
||||
`Checking if ${this.type} '${e}' needs an update`,
|
||||
async () => {
|
||||
const update = await this.checkForExtensionUpdate(e);
|
||||
if (!(update.safeUpdate || update.unsafeUpdate)) {
|
||||
throw new NoUpdatesAvailableError();
|
||||
}
|
||||
return update;
|
||||
}
|
||||
return update;
|
||||
});
|
||||
);
|
||||
if (!unsafe && !update.safeUpdate) {
|
||||
throw new Error(`The ${this.type} '${e}' has a major revision update ` +
|
||||
`(${update.current} => ${update.unsafeUpdate}), which could include ` +
|
||||
`breaking changes. If you want to apply this update, re-run with --unsafe`);
|
||||
throw new Error(
|
||||
`The ${this.type} '${e}' has a major revision update ` +
|
||||
`(${update.current} => ${update.unsafeUpdate}), which could include ` +
|
||||
`breaking changes. If you want to apply this update, re-run with --unsafe`
|
||||
);
|
||||
}
|
||||
const updateVer = unsafe && update.unsafeUpdate ? update.unsafeUpdate : update.safeUpdate;
|
||||
const updateVer =
|
||||
unsafe && update.unsafeUpdate
|
||||
? update.unsafeUpdate
|
||||
: update.safeUpdate;
|
||||
await spinWith(
|
||||
this.isJsonOutput,
|
||||
`Updating driver '${e}' from ${update.current} to ${updateVer}`,
|
||||
@@ -407,12 +489,18 @@ class ExtensionCommand {
|
||||
|
||||
log(this.isJsonOutput, 'Update report:');
|
||||
for (const [e, update] of _.toPairs(updates)) {
|
||||
log(this.isJsonOutput, `- ${this.type} ${e} updated: ${update.from} => ${update.to}`.green);
|
||||
log(
|
||||
this.isJsonOutput,
|
||||
`- ${this.type} ${e} updated: ${update.from} => ${update.to}`.green
|
||||
);
|
||||
}
|
||||
for (const [e, err] of _.toPairs(errors)) {
|
||||
if (err instanceof NotUpdatableError) {
|
||||
log(this.isJsonOutput, `- '${e}' was not installed via npm, so we could not check ` +
|
||||
`for updates`.yellow);
|
||||
log(
|
||||
this.isJsonOutput,
|
||||
`- '${e}' was not installed via npm, so we could not check ` +
|
||||
`for updates`.yellow
|
||||
);
|
||||
} else if (err instanceof NoUpdatesAvailableError) {
|
||||
log(this.isJsonOutput, `- '${e}' had no updates available`.yellow);
|
||||
} else {
|
||||
@@ -436,8 +524,15 @@ class ExtensionCommand {
|
||||
// this is a helper method, 'ext' is assumed to already be installed here, and of the npm
|
||||
// install type
|
||||
const {version, pkgName} = this.config.installedExtensions[ext];
|
||||
let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
|
||||
let safeUpdate = await npm.getLatestSafeUpgradeVersion(this.config.appiumHome, pkgName, version);
|
||||
let unsafeUpdate = await npm.getLatestVersion(
|
||||
this.config.appiumHome,
|
||||
pkgName
|
||||
);
|
||||
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
||||
this.config.appiumHome,
|
||||
pkgName,
|
||||
version
|
||||
);
|
||||
if (!util.compareVersions(unsafeUpdate, '>', version)) {
|
||||
// the latest version is not greater than the current version, so there's no possible update
|
||||
unsafeUpdate = null;
|
||||
@@ -458,16 +553,20 @@ class ExtensionCommand {
|
||||
* Actually update an extension installed by NPM, using the NPM cli. And update the installation
|
||||
* manifest.
|
||||
*
|
||||
* @param {string} ext - name of extension to update
|
||||
* @param {string} installSpec - name of extension to update
|
||||
* @param {string} version - version string identifier to update extension to
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateExtension (ext, version) {
|
||||
const {pkgName} = this.config.installedExtensions[ext];
|
||||
await fs.rimraf(this.config.getInstallPath(ext));
|
||||
const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
|
||||
delete extData[/** @type {string} */(`${this.type}Name`)];
|
||||
await this.config.updateExtension(ext, extData);
|
||||
async updateExtension (installSpec, version) {
|
||||
const {pkgName} = this.config.installedExtensions[installSpec];
|
||||
await fs.rimraf(this.config.getInstallPath(installSpec));
|
||||
const extData = await this.installViaNpm({
|
||||
installSpec,
|
||||
pkgName,
|
||||
pkgVer: version,
|
||||
});
|
||||
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
||||
await this.config.updateExtension(installSpec, extData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,33 +580,37 @@ class ExtensionCommand {
|
||||
* @param {RunOptions} opts
|
||||
* @return {Promise<RunOutput>}
|
||||
*/
|
||||
async _run ({ext, scriptName}) {
|
||||
if (!_.has(this.config.installedExtensions, ext)) {
|
||||
async _run ({installSpec, scriptName}) {
|
||||
if (!_.has(this.config.installedExtensions, installSpec)) {
|
||||
throw new Error(`please install the ${this.type} first`);
|
||||
}
|
||||
|
||||
const extConfig = this.config.installedExtensions[ext];
|
||||
const extConfig = this.config.installedExtensions[installSpec];
|
||||
|
||||
// note: TS cannot understand that _.has() is a type guard
|
||||
if (!extConfig.scripts) {
|
||||
throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
|
||||
`"scripts" field underneath the "appium" field in its package.json`);
|
||||
throw new Error(
|
||||
`The ${this.type} named '${installSpec}' does not contain the ` +
|
||||
`"scripts" field underneath the "appium" field in its package.json`
|
||||
);
|
||||
}
|
||||
|
||||
const extScripts = extConfig.scripts;
|
||||
|
||||
if (!_.isPlainObject(extScripts)) {
|
||||
throw new Error(`The ${this.type} named '${ext}' "scripts" field must be a plain object`);
|
||||
throw new Error(
|
||||
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object`
|
||||
);
|
||||
}
|
||||
|
||||
if (!_.has(extScripts, scriptName)) {
|
||||
throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
|
||||
throw new Error(
|
||||
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
||||
);
|
||||
}
|
||||
|
||||
const runner = new SubProcess(process.execPath, [
|
||||
extScripts[scriptName]
|
||||
], {
|
||||
cwd: this.config.getInstallPath(ext)
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
|
||||
cwd: this.config.getInstallPath(installSpec),
|
||||
});
|
||||
|
||||
const output = new RingBuffer(50);
|
||||
@@ -524,7 +627,10 @@ class ExtensionCommand {
|
||||
log(this.isJsonOutput, `${scriptName} successfully ran`.green);
|
||||
return {output: output.getBuff()};
|
||||
} catch (err) {
|
||||
log(this.isJsonOutput, `Encountered an error when running '${scriptName}': ${err.message}`.red);
|
||||
log(
|
||||
this.isJsonOutput,
|
||||
`Encountered an error when running '${scriptName}': ${err.message}`.red
|
||||
);
|
||||
return {error: err.message, output: output.getBuff()};
|
||||
}
|
||||
}
|
||||
@@ -602,7 +708,7 @@ export {ExtensionCommand};
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._run}.
|
||||
* @typedef RunOptions
|
||||
* @property {string} ext - name of the extension to run a script from
|
||||
* @property {string} installSpec - name of the extension to run a script from
|
||||
* @property {string} scriptName - name of the script to run
|
||||
*/
|
||||
|
||||
@@ -617,7 +723,7 @@ export {ExtensionCommand};
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._update}.
|
||||
* @typedef ExtensionUpdateOpts
|
||||
* @property {string} ext - the name of the extension to update
|
||||
* @property {string} installSpec - the name of the extension to update
|
||||
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
||||
*/
|
||||
|
||||
@@ -638,7 +744,7 @@ export {ExtensionCommand};
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._uninstall}.
|
||||
* @typedef UninstallOpts
|
||||
* @property {string} ext - the name or spec of an extension to uninstall
|
||||
* @property {string} installSpec - the name or spec of an extension to uninstall
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -651,7 +757,7 @@ export {ExtensionCommand};
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand.installViaNpm}
|
||||
* @typedef InstallViaNpmArgs
|
||||
* @property {string} ext - the name or spec of an extension to install
|
||||
* @property {string} installSpec - the name or spec of an extension to install
|
||||
* @property {string} pkgName - the NPM package name of the extension
|
||||
* @property {string} [pkgVer] - the specific version of the NPM package
|
||||
*/
|
||||
@@ -667,7 +773,7 @@ export {ExtensionCommand};
|
||||
/**
|
||||
* Options for {@linkcode ExtensionCommand._install}
|
||||
* @typedef InstallArgs
|
||||
* @property {string} ext - the name or spec of an extension to install
|
||||
* @property {string} installSpec - the name or spec of an extension to install
|
||||
* @property {import('../../types/appium-manifest').InstallType} installType - how to install this extension. One of the INSTALL_TYPES
|
||||
* @property {string} [packageName] - for git/github installs, the extension node package name
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import _ from 'lodash';
|
||||
import ExtensionCommand from './extension-command';
|
||||
import { KNOWN_PLUGINS } from '../constants';
|
||||
import {KNOWN_PLUGINS} from '../constants';
|
||||
|
||||
const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
|
||||
|
||||
/**
|
||||
* @extends {ExtensionCommand<PluginType>}
|
||||
*/
|
||||
export default class PluginCommand extends ExtensionCommand {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PluginCommandOptions} opts
|
||||
* @param {import('./extension-command').ExtensionCommandOptions<PluginType>} opts
|
||||
*/
|
||||
constructor ({config, json}) {
|
||||
super({config, json});
|
||||
@@ -16,41 +18,54 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
}
|
||||
|
||||
async install ({plugin, installType, packageName}) {
|
||||
return await super._install({ext: plugin, installType, packageName});
|
||||
return await super._install({
|
||||
installSpec: plugin,
|
||||
installType,
|
||||
packageName,
|
||||
});
|
||||
}
|
||||
|
||||
async uninstall ({plugin}) {
|
||||
return await super._uninstall({ext: plugin});
|
||||
return await super._uninstall({installSpec: plugin});
|
||||
}
|
||||
|
||||
async update ({plugin, unsafe}) {
|
||||
return await super._update({ext: plugin, unsafe});
|
||||
return await super._update({installSpec: plugin, unsafe});
|
||||
}
|
||||
|
||||
async run ({plugin, scriptName}) {
|
||||
return await super._run({ext: plugin, scriptName});
|
||||
return await super._run({installSpec: plugin, scriptName});
|
||||
}
|
||||
|
||||
getPostInstallText ({extName, extData}) {
|
||||
return `Plugin ${extName}@${extData.version} successfully installed`.green;
|
||||
}
|
||||
|
||||
validateExtensionFields (appiumPkgData) {
|
||||
const missingFields = REQ_PLUGIN_FIELDS.reduce((acc, field) => (
|
||||
appiumPkgData[field] ? acc : [...acc, field]
|
||||
), []);
|
||||
/**
|
||||
* Validates fields in `appium` field of `driverMetadata`
|
||||
*
|
||||
* For any `package.json` fields which a driver requires, validate the type of
|
||||
* those fields on the `package.json` data, throwing an error if anything is
|
||||
* amiss.
|
||||
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
|
||||
* @param {string} installSpec
|
||||
* @returns {void}
|
||||
*/
|
||||
validateExtensionFields (pluginMetadata, installSpec) {
|
||||
const missingFields = REQ_PLUGIN_FIELDS.reduce(
|
||||
(acc, field) => (pluginMetadata[field] ? acc : [...acc, field]),
|
||||
[]
|
||||
);
|
||||
|
||||
if (!_.isEmpty(missingFields)) {
|
||||
throw new Error(`Installed plugin did not expose correct fields for compability ` +
|
||||
`with Appium. Missing fields: ${JSON.stringify(missingFields)}`);
|
||||
throw new Error(
|
||||
`Installed plugin "${installSpec}" did not expose correct fields for compability ` +
|
||||
`with Appium. Missing fields: ${JSON.stringify(missingFields)}`
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef PluginCommandOptions
|
||||
* @property {import('../extension/extension-config').ExtensionConfig<import('../extension/manifest').PluginType>} config
|
||||
* @property {boolean} json
|
||||
* @typedef {import('appium/types').PluginType} PluginType
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.34",
|
||||
"version": "2.0.0-beta.35",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
|
||||
@@ -315,7 +315,7 @@ describe('CLI behavior', function () {
|
||||
'--source',
|
||||
'npm',
|
||||
]);
|
||||
await installLocalExtension(appiumHome, 'driver', testDriverPath);
|
||||
await installLocalExtension(appiumHome, DRIVER_TYPE, testDriverPath);
|
||||
const list = await runList(['--installed']);
|
||||
expect(list.fake).to.exist;
|
||||
expect(list.test).to.exist;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-check
|
||||
|
||||
import { DRIVER_TYPE } from '../../lib/constants';
|
||||
import { readConfigFile, normalizeConfig } from '../../lib/config-file';
|
||||
import { finalizeSchema, registerSchema, resetSchema } from '../../lib/schema/schema';
|
||||
import extSchema from '../fixtures/driver.schema.js';
|
||||
@@ -216,7 +217,7 @@ describe('config file behavior', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
resetSchema();
|
||||
registerSchema('driver', 'fake', extSchema);
|
||||
registerSchema(DRIVER_TYPE, 'fake', extSchema);
|
||||
finalizeSchema();
|
||||
});
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import { exec } from 'teen_process';
|
||||
import { PACKAGE_ROOT, resolveFixture } from '../helpers';
|
||||
import { fs } from '@appium/support';
|
||||
import {exec} from 'teen_process';
|
||||
import {PACKAGE_ROOT, resolveFixture} from '../helpers';
|
||||
import {fs, logger} from '@appium/support';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
/**
|
||||
* Path to the (compiled) main script of the `appium` executable.
|
||||
@@ -17,6 +18,8 @@ import { fs } from '@appium/support';
|
||||
*/
|
||||
export const EXECUTABLE = path.join(PACKAGE_ROOT, 'build', 'lib', 'main.js');
|
||||
|
||||
const log = logger.getLogger('appium-e2e-helpers');
|
||||
|
||||
/**
|
||||
* Runs the `appium` executable with the given args.
|
||||
*
|
||||
@@ -35,10 +38,11 @@ async function run (appiumHome, args, opts = {}) {
|
||||
};
|
||||
try {
|
||||
args = [...process.execArgv, '--', EXECUTABLE, ...args];
|
||||
log.debug(stripAnsi(`Running: ${process.execPath} ${args.join(' ')}`));
|
||||
return await exec(process.execPath, args, {
|
||||
cwd,
|
||||
env,
|
||||
...opts
|
||||
...opts,
|
||||
});
|
||||
} catch (err) {
|
||||
const {stdout, stderr} = /** @type {TeenProcessExecError} */ (err);
|
||||
@@ -215,7 +219,6 @@ export function formatAppiumArgErrorOutput (stderr) {
|
||||
* @returns {Promise<Result>}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @template [Result=unknown]
|
||||
* @template {import('../../types/cli').CliExtensionSubcommand} [ExtSubcommand=never]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
import { fs, tempDir } from '@appium/support';
|
||||
import path from 'path';
|
||||
import { DRIVER_TYPE } from '../../lib/constants';
|
||||
import { resolveFixture } from '../helpers';
|
||||
import { installLocalExtension, runAppium } from './e2e-helpers';
|
||||
|
||||
@@ -29,7 +30,7 @@ describe('CLI behavior controlled by schema', function () {
|
||||
before(async function () {
|
||||
await installLocalExtension(
|
||||
appiumHome,
|
||||
'driver',
|
||||
DRIVER_TYPE,
|
||||
path.dirname(resolveFixture('test-driver/package.json'))
|
||||
);
|
||||
help = await runAppium(appiumHome, ['server', '--help']);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-check
|
||||
|
||||
import { PLUGIN_TYPE } from '../../lib/constants';
|
||||
import B from 'bluebird';
|
||||
import { BaseDriver } from '@appium/base-driver';
|
||||
import { FakeDriver } from '@appium/fake-driver';
|
||||
@@ -183,7 +184,6 @@ describe('AppiumDriver', function () {
|
||||
...W3C_CAPS,
|
||||
alwaysMatch: {
|
||||
...W3C_CAPS.alwaysMatch,
|
||||
// @ts-expect-error
|
||||
'appium:someOtherParm': 'someOtherParm',
|
||||
},
|
||||
};
|
||||
@@ -358,7 +358,7 @@ describe('AppiumDriver', function () {
|
||||
// to establish defaults, we need to register a schema for the plugin.
|
||||
// note that the `noargs` plugin does not need a schema, because it
|
||||
// accepts no arguments.
|
||||
registerSchema('plugin', ArgsPlugin.pluginName, {
|
||||
registerSchema(PLUGIN_TYPE, ArgsPlugin.pluginName, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
randomArg: {
|
||||
@@ -367,7 +367,7 @@ describe('AppiumDriver', function () {
|
||||
}
|
||||
}
|
||||
});
|
||||
registerSchema('plugin', ArrayArgPlugin.pluginName, {
|
||||
registerSchema(PLUGIN_TYPE, ArrayArgPlugin.pluginName, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
arr: {
|
||||
|
||||
@@ -4,8 +4,9 @@ import _ from 'lodash';
|
||||
import { createSandbox } from 'sinon';
|
||||
import { getParser } from '../../lib/cli/parser';
|
||||
import { checkNodeOk, getBuildInfo, getNonDefaultServerArgs, showBuildInfo, showConfig, validateTmpDir, warnNodeDeprecations } from '../../lib/config';
|
||||
import { PLUGIN_TYPE } from '../../lib/constants';
|
||||
import logger from '../../lib/logger';
|
||||
import { getDefaultsForSchema, resetSchema, registerSchema, finalizeSchema } from '../../lib/schema/schema';
|
||||
import { finalizeSchema, getDefaultsForSchema, registerSchema, resetSchema } from '../../lib/schema/schema';
|
||||
|
||||
describe('Config', function () {
|
||||
/** @type {sinon.SinonSandbox} */
|
||||
@@ -187,7 +188,7 @@ describe('Config', function () {
|
||||
describe('with extension schemas', function () {
|
||||
beforeEach(function () {
|
||||
resetSchema();
|
||||
registerSchema('plugin', 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean', default: true}}});
|
||||
registerSchema(PLUGIN_TYPE, 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean', default: true}}});
|
||||
finalizeSchema();
|
||||
getParser(true);
|
||||
args = getDefaultsForSchema();
|
||||
|
||||
@@ -315,7 +315,7 @@ describe('Manifest', function () {
|
||||
/** @type {ExtData<DriverType>} */
|
||||
|
||||
beforeEach(function () {
|
||||
manifest.addExtension('driver', 'foo', extData);
|
||||
manifest.addExtension(DRIVER_TYPE, 'foo', extData);
|
||||
});
|
||||
|
||||
it('should rewrite', function () {
|
||||
@@ -412,7 +412,7 @@ describe('Manifest', function () {
|
||||
packageJson,
|
||||
'/some/path/to/package.json',
|
||||
);
|
||||
expect(manifest.getExtensionData('plugin')).to.deep.equal({
|
||||
expect(manifest.getExtensionData(PLUGIN_TYPE)).to.deep.equal({
|
||||
myPlugin: {
|
||||
mainClass: 'SomeClass',
|
||||
pkgName: 'derp',
|
||||
@@ -504,7 +504,7 @@ describe('Manifest', function () {
|
||||
|
||||
it('should add a found extension', async function () {
|
||||
await manifest.syncWithInstalledExtensions();
|
||||
expect(manifest.getExtensionData('driver')).to.have.property(
|
||||
expect(manifest.getExtensionData(DRIVER_TYPE)).to.have.property(
|
||||
'myDriver',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import { DRIVER_TYPE, PLUGIN_TYPE } from '../../lib/constants';
|
||||
import { getParser } from '../../lib/cli/parser';
|
||||
import { INSTALL_TYPES } from '../../lib/extension/extension-config';
|
||||
import * as schema from '../../lib/schema/schema';
|
||||
@@ -21,7 +21,7 @@ describe('parser', function () {
|
||||
it('should accept only server and driver subcommands', function () {
|
||||
p.parseArgs([]);
|
||||
p.parseArgs(['server']);
|
||||
p.parseArgs(['driver', 'list']);
|
||||
p.parseArgs([DRIVER_TYPE, 'list']);
|
||||
(() => p.parseArgs(['foo'])).should.throw();
|
||||
(() => p.parseArgs(['foo --bar'])).should.throw();
|
||||
});
|
||||
@@ -136,7 +136,7 @@ describe('parser', function () {
|
||||
// we have to require() here because babel will not compile stuff in node_modules
|
||||
// (even if it's in the monorepo; there may be a way around this)
|
||||
// anyway, if we do that, we need to use the `default` prop.
|
||||
schema.registerSchema('driver', 'fake', require('@appium/fake-driver/build/lib/fake-driver-schema').default);
|
||||
schema.registerSchema(DRIVER_TYPE, 'fake', require('@appium/fake-driver/build/lib/fake-driver-schema').default);
|
||||
schema.finalizeSchema();
|
||||
p = getParser(true);
|
||||
});
|
||||
@@ -160,12 +160,12 @@ describe('parser', function () {
|
||||
|
||||
it('should not yet apply defaults', function () {
|
||||
const args = p.parseArgs([]);
|
||||
args.should.not.have.property('driver');
|
||||
args.should.not.have.property(DRIVER_TYPE);
|
||||
});
|
||||
|
||||
it('should nicely handle extensions w/ dashes in them', function () {
|
||||
schema.resetSchema();
|
||||
schema.registerSchema('plugin', 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean'}}});
|
||||
schema.registerSchema(PLUGIN_TYPE, 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean'}}});
|
||||
schema.finalizeSchema();
|
||||
p = getParser(true);
|
||||
const args = p.parseArgs([
|
||||
@@ -198,123 +198,123 @@ describe('parser', function () {
|
||||
p = getParser(true);
|
||||
});
|
||||
it('should not allow random sub-subcommands', function () {
|
||||
(() => p.parseArgs(['driver', 'foo'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'foo'])).should.throw();
|
||||
});
|
||||
describe('list', function () {
|
||||
it('should allow an empty argument list', function () {
|
||||
const args = p.parseArgs(['driver', 'list']);
|
||||
args.subcommand.should.eql('driver');
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'list']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('list');
|
||||
args.showInstalled.should.eql(false);
|
||||
args.showUpdates.should.eql(false);
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format', function () {
|
||||
const args = p.parseArgs(['driver', 'list', '--json']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'list', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
it('should allow --installed', function () {
|
||||
const args = p.parseArgs(['driver', 'list', '--installed']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'list', '--installed']);
|
||||
args.showInstalled.should.eql(true);
|
||||
});
|
||||
it('should allow --updates', function () {
|
||||
const args = p.parseArgs(['driver', 'list', '--updates']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'list', '--updates']);
|
||||
args.showUpdates.should.eql(true);
|
||||
});
|
||||
});
|
||||
describe('install', function () {
|
||||
it('should not allow an empty argument list', function () {
|
||||
(() => p.parseArgs(['driver', 'install'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'install'])).should.throw();
|
||||
});
|
||||
it('should take a driver name to install', function () {
|
||||
const args = p.parseArgs(['driver', 'install', 'foobar']);
|
||||
args.subcommand.should.eql('driver');
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'install', 'foobar']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('install');
|
||||
args.driver.should.eql('foobar');
|
||||
should.not.exist(args.installType);
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format', function () {
|
||||
const args = p.parseArgs(['driver', 'install', 'foobar', '--json']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'install', 'foobar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
it('should allow --source', function () {
|
||||
for (const source of INSTALL_TYPES) {
|
||||
const args = p.parseArgs(['driver', 'install', 'foobar', '--source', source]);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'install', 'foobar', '--source', source]);
|
||||
args.installType.should.eql(source);
|
||||
}
|
||||
});
|
||||
it('should not allow unknown --source', function () {
|
||||
(() => p.parseArgs(['driver', 'install', 'fobar', '--source', 'blah'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'install', 'fobar', '--source', 'blah'])).should.throw();
|
||||
});
|
||||
});
|
||||
describe('uninstall', function () {
|
||||
it('should not allow an empty argument list', function () {
|
||||
(() => p.parseArgs(['driver', 'uninstall'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'uninstall'])).should.throw();
|
||||
});
|
||||
it('should take a driver name to uninstall', function () {
|
||||
const args = p.parseArgs(['driver', 'uninstall', 'foobar']);
|
||||
args.subcommand.should.eql('driver');
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'uninstall', 'foobar']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('uninstall');
|
||||
args.driver.should.eql('foobar');
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format', function () {
|
||||
const args = p.parseArgs(['driver', 'uninstall', 'foobar', '--json']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'uninstall', 'foobar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
});
|
||||
describe('update', function () {
|
||||
it('should not allow an empty argument list', function () {
|
||||
(() => p.parseArgs(['driver', 'update'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'update'])).should.throw();
|
||||
});
|
||||
it('should take a driver name to update', function () {
|
||||
const args = p.parseArgs(['driver', 'update', 'foobar']);
|
||||
args.subcommand.should.eql('driver');
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'update', 'foobar']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('update');
|
||||
args.driver.should.eql('foobar');
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format', function () {
|
||||
const args = p.parseArgs(['driver', 'update', 'foobar', '--json']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'update', 'foobar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
});
|
||||
describe('run', function () {
|
||||
it('should not allow an empty driver argument list', function () {
|
||||
(() => p.parseArgs(['driver', 'run'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no driver scriptName', function () {
|
||||
(() => p.parseArgs(['driver', 'run', 'foo'])).should.throw();
|
||||
(() => p.parseArgs([DRIVER_TYPE, 'run', 'foo'])).should.throw();
|
||||
});
|
||||
it('should take a driverName and scriptName to run', function () {
|
||||
const args = p.parseArgs(['driver', 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql('driver');
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql(DRIVER_TYPE);
|
||||
args.driverCommand.should.eql('run');
|
||||
args.driver.should.eql('foo');
|
||||
args.scriptName.should.eql('bar');
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format for driver', function () {
|
||||
const args = p.parseArgs(['driver', 'run', 'foo', 'bar', '--json']);
|
||||
const args = p.parseArgs([DRIVER_TYPE, 'run', 'foo', 'bar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
it('should not allow an empty plugin argument list', function () {
|
||||
(() => p.parseArgs(['plugin', 'run'])).should.throw();
|
||||
(() => p.parseArgs([PLUGIN_TYPE, 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no plugin scriptName', function () {
|
||||
(() => p.parseArgs(['plugin', 'run', 'foo'])).should.throw();
|
||||
(() => p.parseArgs([PLUGIN_TYPE, 'run', 'foo'])).should.throw();
|
||||
});
|
||||
it('should take a pluginName and scriptName to run', function () {
|
||||
const args = p.parseArgs(['plugin', 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql('plugin');
|
||||
const args = p.parseArgs([PLUGIN_TYPE, 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql(PLUGIN_TYPE);
|
||||
args.pluginCommand.should.eql('run');
|
||||
args.plugin.should.eql('foo');
|
||||
args.scriptName.should.eql('bar');
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format for plugin', function () {
|
||||
const args = p.parseArgs(['plugin', 'run', 'foo', 'bar', '--json']);
|
||||
const args = p.parseArgs([PLUGIN_TYPE, 'run', 'foo', 'bar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import {ArgSpec} from '../../../lib/schema/arg-spec';
|
||||
import { DRIVER_TYPE } from '../../../lib/constants';
|
||||
import { ArgSpec } from '../../../lib/schema/arg-spec';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -23,7 +24,7 @@ describe('ArgSpec', function () {
|
||||
|
||||
describe('when provided extension information', function () {
|
||||
it('should return a schema ID for a specific argument within an extension schema', function () {
|
||||
expect(ArgSpec.toSchemaRef('bar', 'driver', 'stuff')).to.equal(
|
||||
expect(ArgSpec.toSchemaRef('bar', DRIVER_TYPE, 'stuff')).to.equal(
|
||||
'driver-stuff.json#/properties/bar',
|
||||
);
|
||||
});
|
||||
@@ -41,7 +42,7 @@ describe('ArgSpec', function () {
|
||||
|
||||
describe('when provided extension information', function () {
|
||||
it('should return a schema ID for an extension', function () {
|
||||
expect(ArgSpec.toSchemaBaseRef('driver', 'stuff')).to.equal(
|
||||
expect(ArgSpec.toSchemaBaseRef(DRIVER_TYPE, 'stuff')).to.equal(
|
||||
'driver-stuff.json',
|
||||
);
|
||||
});
|
||||
@@ -57,7 +58,7 @@ describe('ArgSpec', function () {
|
||||
|
||||
describe('when provided extension information', function () {
|
||||
it('should return an extension-specific arg name', function () {
|
||||
expect(ArgSpec.toArg('no-oats', 'driver', 'bad-donkey')).to.equal(
|
||||
expect(ArgSpec.toArg('no-oats', DRIVER_TYPE, 'bad-donkey')).to.equal(
|
||||
'driver-bad-donkey-no-oats',
|
||||
);
|
||||
});
|
||||
@@ -75,7 +76,7 @@ describe('ArgSpec', function () {
|
||||
describe('when provided the schema ID of an extension schema', function () {
|
||||
expect(
|
||||
ArgSpec.extensionInfoFromRootSchemaId('driver-stuff.json'),
|
||||
).to.eql({extType: 'driver', normalizedExtName: 'stuff'});
|
||||
).to.eql({extType: DRIVER_TYPE, normalizedExtName: 'stuff'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// @ts-check
|
||||
|
||||
import _ from 'lodash';
|
||||
import {resetSchema, finalizeSchema, registerSchema} from '../../../lib/schema';
|
||||
import {toParserArgs} from '../../../lib/schema/cli-args';
|
||||
import {transformers} from '../../../lib/schema/cli-transformers';
|
||||
import { PLUGIN_TYPE } from '../../../lib/constants';
|
||||
import { finalizeSchema, registerSchema, resetSchema } from '../../../lib/schema';
|
||||
import { toParserArgs } from '../../../lib/schema/cli-args';
|
||||
import { transformers } from '../../../lib/schema/cli-transformers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -29,7 +30,7 @@ describe('cli-args', function () {
|
||||
|
||||
describe('schema contents', function () {
|
||||
const extName = 'blob';
|
||||
const extType = 'plugin';
|
||||
const extType = PLUGIN_TYPE;
|
||||
|
||||
describe('type', function () {
|
||||
let result;
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.16.19](https://github.com/appium/appium/compare/@appium/doctor@1.16.18...@appium/doctor@1.16.19) (2022-05-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.16.18](https://github.com/appium/appium/compare/@appium/doctor@1.16.17...@appium/doctor@1.16.18) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "1.16.18",
|
||||
"version": "1.16.19",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@colors/colors": "1.5.0",
|
||||
"appium-adb": "9.2.1",
|
||||
"appium-adb": "9.2.2",
|
||||
"bluebird": "3.7.2",
|
||||
"inquirer": "8.2.4",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.1](https://github.com/appium/appium/compare/@appium/eslint-config-appium@5.1.0...@appium/eslint-config-appium@5.1.1) (2022-05-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **eslint-config-appium:** remove custom indenting rules ([d89203f](https://github.com/appium/appium/commit/d89203f96c7d45e8cda5e447c808d1485449c284))
|
||||
* **eslint-config-appium:** revert prettier-related change ([93e05a8](https://github.com/appium/appium/commit/93e05a82696514be04d9792c90eb3fe7e3fa0143))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/appium/appium/compare/@appium/eslint-config-appium@5.0.6...@appium/eslint-config-appium@5.1.0) (2022-05-02)
|
||||
|
||||
|
||||
|
||||
@@ -6,18 +6,22 @@ module.exports = {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
experimentalObjectRestSpread: true
|
||||
}
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ['import', 'mocha', 'promise'],
|
||||
plugins: [
|
||||
'import',
|
||||
'mocha',
|
||||
'promise'
|
||||
],
|
||||
globals: {
|
||||
chai: true,
|
||||
should: true,
|
||||
should: true
|
||||
},
|
||||
rules: {
|
||||
'no-console': 2,
|
||||
@@ -25,31 +29,23 @@ module.exports = {
|
||||
radix: [2, 'always'],
|
||||
'dot-notation': 2,
|
||||
eqeqeq: [2, 'smart'],
|
||||
'brace-style': [
|
||||
2,
|
||||
'1tbs',
|
||||
{
|
||||
allowSingleLine: true,
|
||||
'brace-style': [2, '1tbs', {
|
||||
allowSingleLine: true,
|
||||
}],
|
||||
indent: [2, 2, {
|
||||
VariableDeclarator: {
|
||||
var: 2,
|
||||
let: 2,
|
||||
const: 3,
|
||||
},
|
||||
],
|
||||
indent: [
|
||||
2,
|
||||
2,
|
||||
{
|
||||
VariableDeclarator: {
|
||||
var: 2,
|
||||
let: 2,
|
||||
const: 3,
|
||||
},
|
||||
ImportDeclaration: 'first',
|
||||
CallExpression: {
|
||||
arguments: 'off',
|
||||
},
|
||||
MemberExpression: 'off',
|
||||
ObjectExpression: 'first',
|
||||
SwitchCase: 1,
|
||||
ImportDeclaration: 'first',
|
||||
CallExpression: {
|
||||
arguments: 'off',
|
||||
},
|
||||
],
|
||||
MemberExpression: 'off',
|
||||
ObjectExpression: 'first',
|
||||
SwitchCase: 1,
|
||||
}],
|
||||
'comma-dangle': 0,
|
||||
'no-empty': 0,
|
||||
'object-shorthand': 2,
|
||||
@@ -73,43 +69,31 @@ module.exports = {
|
||||
// enforce spacing
|
||||
'arrow-spacing': 2,
|
||||
'keyword-spacing': 2,
|
||||
'comma-spacing': [
|
||||
2,
|
||||
{
|
||||
before: false,
|
||||
after: true,
|
||||
},
|
||||
],
|
||||
'comma-spacing': [2, {
|
||||
before: false,
|
||||
after: true
|
||||
}],
|
||||
'array-bracket-spacing': 2,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-whitespace-before-property': 2,
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'space-unary-ops': [
|
||||
2,
|
||||
{
|
||||
words: true,
|
||||
nonwords: false,
|
||||
},
|
||||
],
|
||||
'space-before-function-paren': [2, 'always'],
|
||||
'space-unary-ops': [2, {
|
||||
words: true,
|
||||
nonwords: false,
|
||||
}],
|
||||
'space-infix-ops': 2,
|
||||
'key-spacing': [
|
||||
2,
|
||||
{
|
||||
mode: 'strict',
|
||||
beforeColon: false,
|
||||
afterColon: true,
|
||||
},
|
||||
],
|
||||
'key-spacing': [2, {
|
||||
mode: 'strict',
|
||||
beforeColon: false,
|
||||
afterColon: true,
|
||||
}],
|
||||
'no-multi-spaces': 2,
|
||||
quotes: [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true,
|
||||
},
|
||||
],
|
||||
quotes: [2, 'single', {
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true,
|
||||
}],
|
||||
'no-buffer-constructor': 1,
|
||||
'require-atomic-updates': 0,
|
||||
'no-prototype-builtins': 1,
|
||||
@@ -129,5 +113,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
extends: ['eslint:recommended', 'prettier'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.1",
|
||||
"description": "Shared config for Appium's eslint style",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
@@ -8,15 +8,14 @@
|
||||
"appium",
|
||||
"es2015"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"main": "index.js",
|
||||
@@ -34,17 +33,17 @@
|
||||
"@babel/core": "7.17.10",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-mocha": "9.0.0",
|
||||
"eslint-plugin-promise": "6.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://appium.io",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "722ae145fb40bf17b0168ea8b025763f120ad574"
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [6.0.10](https://github.com/appium/appium/compare/@appium/gulp-plugins@6.0.9...@appium/gulp-plugins@6.0.10) (2022-05-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/gulp-plugins
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.9](https://github.com/appium/appium/compare/@appium/gulp-plugins@6.0.8...@appium/gulp-plugins@6.0.9) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/gulp-plugins
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/gulp-plugins",
|
||||
"version": "6.0.9",
|
||||
"version": "6.0.10",
|
||||
"description": "Custom gulp plugins to be used across all appium modules",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -76,7 +76,7 @@
|
||||
"js-yaml": "4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"merge-stream": "2.0.0",
|
||||
"mocha": "9.2.2",
|
||||
"mocha": "10.0.0",
|
||||
"moment": "2.29.3",
|
||||
"node-notifier": "10.0.1",
|
||||
"nyc": "15.1.0",
|
||||
|
||||
Reference in New Issue
Block a user