mirror of
https://github.com/appium/appium.git
synced 2026-11-10 20:05:42 +08:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59dd30ba20 | ||
|
|
6480679bfb | ||
|
|
c2753107c6 | ||
|
|
a794ade7b0 | ||
|
|
5e24fbac5c | ||
|
|
8caf18f542 | ||
|
|
93e05a8269 | ||
|
|
5cc22325f2 | ||
|
|
a2b07f6150 | ||
|
|
823014321a | ||
|
|
d89203f96c | ||
|
|
773d9d6c0d | ||
|
|
a686053f03 | ||
|
|
5a3a0f7c14 | ||
|
|
5482c921bc | ||
|
|
88a6655253 | ||
|
|
38b4fa2bad | ||
|
|
67dacd87cf | ||
|
|
f1b36cc7c5 | ||
|
|
ec1b95b96c | ||
|
|
bbce027f34 | ||
|
|
2e7c1bb46e | ||
|
|
9df8c08b09 | ||
|
|
a67ba79dba | ||
|
|
de5a96fb08 | ||
|
|
90918200e2 | ||
|
|
a725d052f8 | ||
|
|
e49ab6d343 | ||
|
|
fdee8701a6 | ||
|
|
2e8e7a5d65 | ||
|
|
39aab21aef | ||
|
|
b957123401 | ||
|
|
a2b2ded1e4 | ||
|
|
b34b75a80e | ||
|
|
4d91f76e4c | ||
|
|
2e57fd4900 | ||
|
|
1201230b6a | ||
|
|
70f9b38dd5 | ||
|
|
c5787f7cf0 | ||
|
|
5bdc476c62 | ||
|
|
9bfe482b97 | ||
|
|
00f38df09a | ||
|
|
19c4cd246b | ||
|
|
0d7f851410 |
@@ -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
|
||||
@@ -21,7 +21,9 @@ jobs:
|
||||
- name: Install latest npm
|
||||
run: npm install -g npm
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
npm run install-mjpeg-consumer
|
||||
- name: Run all tests
|
||||
run: npm run test:ci
|
||||
|
||||
|
||||
+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',
|
||||
};
|
||||
};
|
||||
|
||||
+14
-10
@@ -41,6 +41,7 @@
|
||||
"generate-docs": "lerna run --scope=appium generate-docs",
|
||||
"postinstall": "npm run build:loose",
|
||||
"install-fake-driver": "npm start -- driver install --source=local ./packages/fake-driver",
|
||||
"install-mjpeg-consumer": "npm install mjpeg-consumer --no-save",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"precommit-lint": "lint-staged",
|
||||
@@ -75,6 +76,7 @@
|
||||
"@appium/base-driver": "file:packages/base-driver",
|
||||
"@appium/base-plugin": "file:packages/base-plugin",
|
||||
"@appium/doctor": "file:packages/doctor",
|
||||
"@appium/docutils": "file:packages/docutils",
|
||||
"@appium/eslint-config-appium": "file:packages/eslint-config-appium",
|
||||
"@appium/fake-driver": "file:packages/fake-driver",
|
||||
"@appium/fake-plugin": "file:packages/fake-plugin",
|
||||
@@ -92,11 +94,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-plugin": "1.3.1",
|
||||
"@babel/cli": "7.17.6",
|
||||
"@babel/core": "7.17.9",
|
||||
"@babel/cli": "7.17.10",
|
||||
"@babel/core": "7.17.10",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"@babel/plugin-transform-runtime": "7.17.0",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/plugin-transform-runtime": "7.17.10",
|
||||
"@babel/preset-env": "7.17.10",
|
||||
"@babel/register": "7.17.7",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@colors/colors": "1.5.0",
|
||||
@@ -116,10 +118,10 @@
|
||||
"@types/klaw": "3.0.3",
|
||||
"@types/lockfile": "1.0.2",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/mocha": "9.1.0",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/mv": "2.1.2",
|
||||
"@types/ncp": "2.0.5",
|
||||
"@types/node": "16.11.27",
|
||||
"@types/node": "16.11.33",
|
||||
"@types/npmlog": "4.1.4",
|
||||
"@types/pluralize": "0.0.29",
|
||||
"@types/pngjs": "6.0.1",
|
||||
@@ -138,7 +140,7 @@
|
||||
"babel-plugin-source-map-support": "2.1.3",
|
||||
"chai": "4.3.6",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.6.0",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
@@ -151,10 +153,10 @@
|
||||
"handlebars": "4.7.7",
|
||||
"json-schema-to-typescript": "10.1.5",
|
||||
"lerna": "4.0.0",
|
||||
"lint-staged": "12.3.8",
|
||||
"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",
|
||||
"rewiremock": "3.14.3",
|
||||
@@ -162,12 +164,14 @@
|
||||
"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",
|
||||
"typescript": "4.6.3",
|
||||
"typescript": "4.6.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.19.5",
|
||||
"ws": "8.6.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -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.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.0.0-beta.33](https://github.com/appium/appium/compare/appium@2.0.0-beta.32...appium@2.0.0-beta.33) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package appium
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Execute Script Overloads
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: The Settings API
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -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!
|
||||
|
||||
@@ -2,4 +2,201 @@
|
||||
title: Intro to Appium
|
||||
---
|
||||
|
||||
TODO
|
||||
As mentioned on the main page, Appium is an open-source project and ecosystem of related software,
|
||||
designed to facilitate UI automation of many app platforms. With the release of Appium 2.0, Appium
|
||||
has the following primary goals:[^1]
|
||||
|
||||
- Make platform-specific automation capabilities available under a cross-platform, standard API
|
||||
- Allow easy access to this API from any programming language
|
||||
- Provide tools to enable convenient community development of Appium extensions
|
||||
|
||||
[^1]:
|
||||
To meet these primary goals, we also work with a set of secondary goals or methodology
|
||||
principles, which we also encourage for Appium extension developers:
|
||||
|
||||
- As far as possible, rely on (and contribute to) open source technology
|
||||
- As far as possible, rely on vendor-provided tools for a given platform
|
||||
- As far as possible, rely on automation tools that allow automation of unmodified apps (prefer
|
||||
not to require the user to build in additional SDKs or software that introduce discrepancies
|
||||
between the test version of the app and the production version)
|
||||
- As far as possible, rely on existing standards instead of creating new ones
|
||||
|
||||
So, take any app platform you know about, like iOS or Android. Appium wants for there to be a way
|
||||
for developers and testers to write UI automation code for that platform, according to a single,
|
||||
unified API. Based on Appium's goals, we have a lot of questions to answer to make it all work:
|
||||
|
||||
- Which API should that "single, unified" API be?
|
||||
- How do we map that API to automation behaviour for a specific platform?
|
||||
- How do we make that API accessible via multiple popular programming languages?
|
||||
|
||||
There's another, larger, question lurking in the background here too, given that there are more app
|
||||
platforms out there than just iOS and Android:
|
||||
|
||||
- How do we enable automation for *all* the platforms?
|
||||
|
||||
Exploring Appium's answers to these questions may not be the quickest way to learn what Appium is,
|
||||
but it is certainly a good one! So let's dive in.
|
||||
|
||||
## Appium's choice of API
|
||||
|
||||
Appium is very fortunate to have been preceded by a technology which has been a long-standing
|
||||
pioneer in the field of UI automation, namely [Selenium](https://seleniumhq.org). The goal of the
|
||||
Selenium project has been to support UI automation of web browsers, and in this way we can think of
|
||||
it as occupying a subset of Appium's goals. Along the way, Selenium (and, after they merged,
|
||||
another project called WebDriver) developed a relatively stable API for browser automation.
|
||||
|
||||
Over the years, Selenium worked with various web browser vendors and the [W3C](https://w3c.org)
|
||||
standards group to turn its API into an official web browser standard, called the [WebDriver
|
||||
specification](https://w3c.github.io/webdriver/webdriver-spec.html). All the main browsers now
|
||||
implement automation capabilities inline with the WebDriver spec, without the Selenium team having
|
||||
to maintain any software that performs actual automation; standards for the win!
|
||||
|
||||
Appium's initial goals were to develop an automation standard for mobile apps (iOS and Android). We
|
||||
could have made up something new, but in the spirit of joining forces and keeping standards, well,
|
||||
standard, we decided to adopt the WebDriver spec as Appium's API.[^2] While user interaction on
|
||||
websites and in mobile native apps are not entirely identical (with even greater differences once
|
||||
we start to consider, for example, TV platforms controlled by simple remotes), the fact is that
|
||||
most software UIs are pretty much the same. This means that the WebDriver spec provides automation
|
||||
API primitives (finding elements, interacting with elements, loading pages or screens, etc...) that
|
||||
more or less map to any platform.
|
||||
|
||||
[^2]: Technically, when Appium was first written, we were dealing with something older than the
|
||||
WebDriver spec, called the JSON Wire Protocol. Since then, Appium continued to evolve along with
|
||||
the W3C spec and is fully W3C-compliant.
|
||||
|
||||
Of course, Appium wants to support the cases where user interaction *does* differ from web to
|
||||
mobile or web to TV, and so Appium also makes use of the built-in *extensibility* of the WebDriver
|
||||
spec. The result is that, no matter what platform you want to automate, when you use Appium, you
|
||||
will do so using the standard WebDriver spec, with two caveats:
|
||||
|
||||
- We might not have any way to support a particular WebDriver API command on a given platform, and
|
||||
so some commands might be unsupported (for example, getting or setting cookies is not possible in
|
||||
the world of native mobile app automation).
|
||||
- We might support automation behaviours that go *beyond* what's available in the WebDriver API
|
||||
command list, though any such commands will be valid and spec-compliant extensions to the
|
||||
WebDriver API.
|
||||
|
||||
How do you actually *use* the WebDriver API, particularly in the context of Appium? We'll cover
|
||||
that in the [section below](#universal-programming-language-access) on how Appium provides
|
||||
universal programming language access. All you need to know for now is that the way Appium
|
||||
introduces a universal UI automation interface is by implementing the WebDriver protocol.
|
||||
|
||||
## Platform automation behaviour
|
||||
|
||||
The next question is, how does Appium map this protocol to automation behaviour on a wide range of
|
||||
platforms? The trick is that, strictly speaking, Appium doesn't! It leaves this responsibility up
|
||||
to a kind of software module called an Appium *driver*. There's a whole [Driver
|
||||
Introduction](./drivers.md) which you can read next, so we won't go into huge detail on how they
|
||||
work for now.
|
||||
|
||||
What's important to understand at the moment is that a driver is kind of like a pluggable module
|
||||
for Appium that gives Appium the power to automate a particular platform (or set of platforms,
|
||||
depending on the goal of the driver). At the end of the day, a driver's responsibility is to simply
|
||||
implement an Appium-internal interface representing the WebDriver protocol. How it implements this
|
||||
interface is totally up to the driver, based on its strategy for making automation happen on
|
||||
a specific platform. Typically, and with a lot more complexity and difficulty in the details,
|
||||
a driver does this by relying on platform-specific automation technologies. For example, Apple
|
||||
maintains an iOS automation technology called
|
||||
[XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests). The Appium
|
||||
driver that supports iOS app automation is called the [XCUITest
|
||||
Driver](https://github.com/appium/appium-xcuitest-driver) because ultimately what it does is
|
||||
convert the WebDriver protocol to XCUITest library calls.
|
||||
|
||||
One of the reasons that drivers are independent, pluggable modules is that they work completely
|
||||
differently from one another. The tools and requirements for building and using drivers for
|
||||
different platforms are completely different. And so Appium lets you use just the drivers that you
|
||||
need for your automation tasks. Choosing drivers and installing them so that you can use them with
|
||||
your Appium instance is so important that Appium has its very own [CLI for managing
|
||||
drivers](../cli/extensions.md).
|
||||
|
||||
So, to answer our original question, the way that Appium provides access to automation capabilities
|
||||
for a given platform is that the Appium team (or anyone else[^3]) writes a *driver* for that
|
||||
platform, implementing as much or little of the WebDriver protocol as desired. The driver can then
|
||||
be installed by anyone using Appium.
|
||||
|
||||
[^3]: You can build and share your own drivers! Check out [Building
|
||||
Drivers](../ecosystem/build-drivers.md) to learn more about how to develop drivers in Node.js
|
||||
that can be used with Appium.
|
||||
|
||||
## Universal programming language access
|
||||
|
||||
But what does it mean, or look like, to *use* Appium, anyway? Since Appium is ultimately a Node.js
|
||||
program, it *could* have looked like importing Appium and its drivers as libraries into your own
|
||||
Node.js programs. But that wouldn't meet Appium's goal of providing automation capabilities to
|
||||
people using any popular programming language.
|
||||
|
||||
Luckily, the fact that Appium rode in on Selenium's coattails meant that we had a solution to this
|
||||
problem from day one. You see, the WebDriver specification is actually an HTTP-based protocol,
|
||||
meaning it is designed to be used over a network rather than within the memory of a single program.
|
||||
|
||||
One of the main benefits of this "client-server" architecture is that it allows the automation
|
||||
implementer (the thing doing the automation, in this case the 'server') to be completely distinct
|
||||
from the automation runner (the thing defining what automation should be done, in what steps,
|
||||
etc..., in this case the 'client'). Basically, all the "hard stuff" (actually figuring out how to
|
||||
make automation happen on a given platform) can be handled in one place by the server, and "thin"
|
||||
client libraries can be written in any programming language which simply encode HTTP requests to
|
||||
the server in language-appropriate way. It's possible, in other words, to bring basic Appium
|
||||
/ WebDriver capabilities to a new programming language relatively easily, assuming high-level HTTP
|
||||
libraries exist, simply by coding up a basic HTTP client in that language.
|
||||
|
||||
There are a couple important takeaways here for you, the Appium user:
|
||||
|
||||
- Appium is an *HTTP server*. It must run as a process on some computer for as long as you want to
|
||||
be able to use it for automation. It must be accessible on the network to whichever computer you
|
||||
want to use to run the automation from (whether that is the same machine or one across the
|
||||
world).
|
||||
- Unless you want to write raw HTTP calls or use cURL, using Appium for automation involves the use
|
||||
of an [Appium Client](clients.md) in the language of your choice. The goal of each of these
|
||||
clients is to encapsulate the WebDriver protocol so that rather than worrying about the protocol
|
||||
itself, you can work with objects and methods that feel idiomatic for your language.
|
||||
- The Appium server and the Appium client do *not* need to be running on the same computer. You
|
||||
simply need to be able to send HTTP requests from the client to the server over some network.
|
||||
This greatly facilitates the use of cloud providers for Appium, since they can host the Appium
|
||||
server and any related drivers and devices, and all you need to do is point your client script to
|
||||
their secure endpoints.
|
||||
|
||||
And of course, none of this is about "testing" per se, purely about the use of Appium and its
|
||||
client libraries for automation purposes. If you want to do automation for the purpose of
|
||||
"testing", you'll likely want to enlist the help of test runners, test frameworks, and the like,
|
||||
none of which need be related to Appium; one of the benefits of Appium's "universal accessibility"
|
||||
is that it plays well with whatever set of tools you find most beneficial for your situation.
|
||||
|
||||
## Appium's huge scope
|
||||
|
||||
Appium's vision (automation of everything under a single API) is huge! Certainly, much bigger than
|
||||
the team of core maintainers for the open source project. So how does Appium hope to achieve this
|
||||
goal? Basically, by empowering the community to develop functionality on top of Appium as
|
||||
a *platform*. This is what we call the Appium "ecosystem".
|
||||
|
||||
The Appium team does officially maintain a few drivers itself (for example, the XCUITest driver
|
||||
that we spoke about earlier). But it cannot hope to have the platform-specific expertise or the
|
||||
capacity to maintain drivers for many different platforms. But what we have done, particularly
|
||||
beginning with Appium 2.0, is to provide tools to empower the community to join in our vision:
|
||||
|
||||
- Anyone can create a driver simply by creating a Node.js module that conforms to the appropriate
|
||||
conventions and implements any (sub|super)set of the WebDriver protocol. Creating a driver often
|
||||
involves a minimal amount of code because the WebDriver protocol details are abstracted away, and
|
||||
many helper libraries are available---the same libraries that power the Appium team's own
|
||||
drivers.
|
||||
- Sharing drivers with others is easy using the Appium driver CLI. There is no central authority.
|
||||
Anyone can share drivers publicly or privately, for free or for sale. Drivers can be open or
|
||||
closed source (though obviously we appreciate open source!).
|
||||
|
||||
Appium's vision of being a platform for development extends beyond the support of automation for
|
||||
all app platforms. As a popular automation tool, there are many opportunities for integrating
|
||||
Appium with all kinds of other tools and services. In addition, there are many feature ideas for
|
||||
Appium, either as a core server or in its incarnation across various drivers, which the core team
|
||||
will never have time to build. And so, with Appium 2.0, Appium has released a plugin system that
|
||||
enables anyone to build and share modules that change how Appium works!
|
||||
|
||||
In the same way that drivers are easily shareable and consumable via the Appium driver CLI, plugins
|
||||
can be published and consumed via a parallel [Plugin CLI](../cli/extensions.md). Plugins can do all
|
||||
sorts of things, for example adding the ability for Appium to find and interact with screen regions
|
||||
based on a template image (as in the [`images`
|
||||
plugin](https://github.com/appium/appium/tree/2.0/packages/images-plugin)). There are very few
|
||||
limitations on what you can do with plugins, so you might also be interested in learning how to
|
||||
[Build Plugins](../ecosystem/build-plugins.md) in Node.js that can be used with Appium.
|
||||
|
||||
So that's Appium: an extensible, universal interface for the UI automation of potentially
|
||||
everything! Read on into some of the specific intro docs for more details, or check out the various
|
||||
guides to dive into some more general concepts and features of Appium.
|
||||
|
||||
@@ -2,4 +2,50 @@
|
||||
title: Write a Test (Python)
|
||||
---
|
||||
|
||||
TODO
|
||||
The [Appium Python Client](https://github.com/appium/python-client) is
|
||||
an official Appium client in Python, which is available via pypi under the [Appium-Python-Client](https://pypi.org/project/Appium-Python-Client/) package name.
|
||||
It inherits from the [Selenium Python Binding](https://pypi.org/project/selenium/),
|
||||
so installing the Appium Python Client includes the selenium binding.
|
||||
|
||||
```bash
|
||||
pip install Appium-Python-Client
|
||||
```
|
||||
|
||||
This example uses Python's built-in `unittest` module, though you can use any Python test framework you want.
|
||||
The Appium Python client adds the `appium:` vendor prefix automatically.
|
||||
You usually do not need to worry about the prefix.
|
||||
|
||||
```python title="test.py"
|
||||
--8<-- "./sample-code/quickstarts/py/test.py"
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
It's not within the scope of this guide to give a complete run-down on the Python client
|
||||
library or everything that's happening here, so we'll leave the code itself unexplained in detail for now.
|
||||
- You may want to read up particularly on Appium [Capabilities](../guides/caps.md).
|
||||
- [functional test code](https://github.com/appium/python-client/tree/master/test/functional) in Python Client GitHub repository should help to find more working example.
|
||||
- [Documentation](https://appium.github.io/python-client-sphinx/) also helps to find methods
|
||||
defined in the Appium Python Client.
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
1. Defining a set of "Capabilities" (parameters) to send to the Appium server so Appium knows what
|
||||
kind of thing you want to automate.
|
||||
1. Starting an Appium session on the built-in Android settings app.
|
||||
1. Finding the "Battery" list item and clicking it.
|
||||
1. Pausing for a moment purely for visual effect.
|
||||
1. Ending the Appium session.
|
||||
|
||||
That's it! Let's give it a try. Before you run the test, make sure that you have an Appium server
|
||||
running in another terminal session, otherwise you'll get an error about not being able to connect
|
||||
to one. Then, you can execute the script:
|
||||
|
||||
```bash
|
||||
python test.js
|
||||
```
|
||||
|
||||
If all goes well, you'll see the Settings app open up and navigate to the "Battery" view before the
|
||||
app closes again.
|
||||
|
||||
Congratulations, you've started your Appium journey! Read on for some next steps to explore.
|
||||
|
||||
@@ -12,18 +12,20 @@ theme:
|
||||
nav:
|
||||
- Welcome: index.md
|
||||
- Introduction:
|
||||
- intro/index.md
|
||||
- intro/drivers.md
|
||||
- intro/clients.md
|
||||
- Intro to Appium:
|
||||
- Overview of Appium: intro/index.md
|
||||
- intro/drivers.md
|
||||
- intro/clients.md
|
||||
- intro/requirements.md
|
||||
- intro/history.md
|
||||
- Quickstart:
|
||||
- quickstart/index.md
|
||||
- quickstart/install.md
|
||||
- quickstart/uiauto2-driver.md
|
||||
- quickstart/test-js.md
|
||||
- quickstart/test-py.md
|
||||
- quickstart/test-java.md
|
||||
- Write a Test:
|
||||
- quickstart/test-js.md
|
||||
- quickstart/test-py.md
|
||||
- quickstart/test-java.md
|
||||
- quickstart/next-steps.md
|
||||
- CLI Reference:
|
||||
- cli/index.md
|
||||
@@ -36,14 +38,17 @@ nav:
|
||||
- guides/security.md
|
||||
- guides/caps.md
|
||||
- guides/context.md
|
||||
- guides/settings.md
|
||||
- guides/execute-script-overload.md
|
||||
- guides/event-timing.md
|
||||
- guides/log-filters.md
|
||||
- guides/grid.md
|
||||
- Ecosystem:
|
||||
- ecosystem/index.md
|
||||
- ecosystem/build-drivers.md
|
||||
- ecosystem/build-plugins.md
|
||||
- ecosystem/build-docs.md
|
||||
- Developing Appium Extensions:
|
||||
- ecosystem/build-drivers.md
|
||||
- ecosystem/build-plugins.md
|
||||
- ecosystem/build-docs.md
|
||||
- Contributing: contributing/index.md
|
||||
- Resources: resources.md
|
||||
- More Appium Resources: resources.md
|
||||
- Issues: https://github.com/appium/appium/issues
|
||||
|
||||
@@ -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.33",
|
||||
"version": "2.0.0-beta.35",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -53,11 +53,11 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.5.2",
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/docutils": "^0.0.3",
|
||||
"@appium/schema": "^0.0.5",
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/base-driver": "file:../base-driver",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"@appium/docutils": "file:../docutils",
|
||||
"@appium/schema": "file:../schema",
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@sidvind/better-ajv-errors": "1.1.1",
|
||||
"ajv": "8.11.0",
|
||||
@@ -65,13 +65,13 @@
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.3.1",
|
||||
"asyncbox": "2.9.2",
|
||||
"axios": "0.26.1",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"find-up": "5.0.0",
|
||||
"lilconfig": "2.0.5",
|
||||
"lodash": "4.17.21",
|
||||
"longjohn": "0.2.12",
|
||||
"npmlog": "6.0.1",
|
||||
"npmlog": "6.0.2",
|
||||
"ora": "5.4.1",
|
||||
"package-changed": "1.9.0",
|
||||
"resolve-from": "5.0.0",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import unittest
|
||||
from appium import webdriver
|
||||
from appium.webdriver.common.appiumby import AppiumBy
|
||||
|
||||
capabilities = dict(
|
||||
platformName='Android',
|
||||
automationName='uiautomator2',
|
||||
deviceName='Android',
|
||||
appPackage='com.android.settings',
|
||||
appActivity='.Settings',
|
||||
language='en',
|
||||
locale='US'
|
||||
)
|
||||
|
||||
appium_server_url = 'http://localhost:4723'
|
||||
|
||||
class TestAppium(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.driver = webdriver.Remote(appium_server_url, capabilities)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if self.driver:
|
||||
self.driver.quit()
|
||||
|
||||
def test_find_battery(self) -> None:
|
||||
el = self.driver.find_element(by=AppiumBy.XPATH, value='//*[@text="Battery"]')
|
||||
el.click()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -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.
|
||||
|
||||
## [8.5.3](https://github.com/appium/appium/compare/@appium/base-driver@8.5.2...@appium/base-driver@8.5.3) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.2](https://github.com/appium/appium/compare/@appium/base-driver@8.5.1...@appium/base-driver@8.5.2) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "8.5.2",
|
||||
"version": "8.5.3",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -48,16 +48,16 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@colors/colors": "1.5.0",
|
||||
"async-lock": "1.3.1",
|
||||
"asyncbox": "2.9.2",
|
||||
"axios": "0.26.1",
|
||||
"axios": "0.27.2",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.0",
|
||||
"es6-error": "4.1.1",
|
||||
"express": "4.17.3",
|
||||
"express": "4.18.1",
|
||||
"http-status-codes": "2.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.8.1",
|
||||
@@ -67,9 +67,6 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ws": "8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
|
||||
@@ -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.8.5](https://github.com/appium/appium/compare/@appium/base-plugin@1.8.4...@appium/base-plugin@1.8.5) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.8.4](https://github.com/appium/appium/compare/@appium/base-plugin@1.8.3...@appium/base-plugin@1.8.4) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.5",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
@@ -31,7 +31,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.57.4"
|
||||
"@appium/support": "file:../support"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [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.16.17](https://github.com/appium/appium/compare/@appium/doctor@1.16.16...@appium/doctor@1.16.17) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "1.16.17",
|
||||
"version": "1.16.19",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,12 +45,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.57.4",
|
||||
"@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.2",
|
||||
"inquirer": "8.2.4",
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.4](https://github.com/appium/appium/compare/@appium/docutils@0.0.3...@appium/docutils@0.0.4) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.3](https://github.com/appium/appium/compare/@appium/docutils@0.0.2...@appium/docutils@0.0.3) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/docutils
|
||||
|
||||
@@ -22,13 +22,7 @@ theme:
|
||||
name: material
|
||||
palette:
|
||||
- scheme: default
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
primary: deep purple
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
admonition:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"appium",
|
||||
@@ -38,11 +38,11 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"docdash": "1.2.0",
|
||||
"jsdoc": "3.6.10",
|
||||
"jsdoc-plugin-typescript": "2.0.6",
|
||||
"jsdoc-plugin-typescript": "2.0.7",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "1.16.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Mike } from '../..';
|
||||
import { Mike } from '../../lib';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('Mike', function () {
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **eslint-config-appium:** disable space-before-function-paren ([88a6655](https://github.com/appium/appium/commit/88a6655253a4879041478d64254471efebe4cbfe))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **eslint-config-appium:** adds a warning if code attempts to assign to an object prototype ([5bdc476](https://github.com/appium/appium/commit/5bdc476c626caa301c7cb4ffc01c296f437deb06)), closes [#16829](https://github.com/appium/appium/issues/16829)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.6](https://github.com/appium/appium/compare/@appium/eslint-config-appium@5.0.5...@appium/eslint-config-appium@5.0.6) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/eslint-config-appium
|
||||
|
||||
@@ -98,6 +98,20 @@ module.exports = {
|
||||
'require-atomic-updates': 0,
|
||||
'no-prototype-builtins': 1,
|
||||
'no-redeclare': 1,
|
||||
'no-restricted-syntax': [
|
||||
'warn',
|
||||
{
|
||||
selector: 'AssignmentExpression[left.object.property.name="prototype"]',
|
||||
message:
|
||||
'Avoid assignment to prototype; use class fields, methods or mixins instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'CallExpression[callee.object.name="Object"][callee.property.name="assign"][arguments.0.property.name="prototype"]',
|
||||
message:
|
||||
'Avoid assignment to prototype; use class fields, methods or mixins instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/eslint-config-appium",
|
||||
"version": "5.0.6",
|
||||
"version": "5.1.1",
|
||||
"description": "Shared config for Appium's eslint style",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
@@ -30,7 +30,7 @@
|
||||
"lint": "eslint index.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "7.17.9",
|
||||
"@babel/core": "7.17.10",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.5](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@1.0.4...@appium/execute-driver-plugin@1.0.5) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.4](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@1.0.3...@appium/execute-driver-plugin@1.0.4) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/execute-driver-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,8 +28,8 @@
|
||||
"index.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"@appium/support": "file:../support",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
"vm2": "3.9.9",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [3.2.13](https://github.com/appium/appium/compare/@appium/fake-driver@3.2.12...@appium/fake-driver@3.2.13) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.2.12](https://github.com/appium/appium/compare/@appium/fake-driver@3.2.11...@appium/fake-driver@3.2.12) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "3.2.12",
|
||||
"version": "3.2.13",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,8 +45,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.5.2",
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/base-driver": "file:../base-driver",
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"asyncbox": "2.9.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.
|
||||
|
||||
## [1.3.4](https://github.com/appium/appium/compare/@appium/fake-plugin@1.3.3...@appium/fake-plugin@1.3.4) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.3](https://github.com/appium/appium/compare/@appium/fake-plugin@1.2.2...@appium/fake-plugin@1.3.3) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"author": "Appium <maintainers@appium.io>",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -36,8 +36,8 @@
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"@appium/support": "file:../support",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.8](https://github.com/appium/appium/compare/@appium/gulp-plugins@6.0.7...@appium/gulp-plugins@6.0.8) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/gulp-plugins
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/gulp-plugins",
|
||||
"version": "6.0.8",
|
||||
"version": "6.0.10",
|
||||
"description": "Custom gulp plugins to be used across all appium modules",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,17 +41,17 @@
|
||||
"test:e2e": "gulp e2e-test:run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/eslint-config-appium": "^5.0.6",
|
||||
"@babel/core": "7.17.9",
|
||||
"@appium/eslint-config-appium": "file:../eslint-config-appium",
|
||||
"@babel/core": "7.17.10",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"@babel/plugin-transform-runtime": "7.17.0",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/plugin-transform-runtime": "7.17.10",
|
||||
"@babel/preset-env": "7.17.10",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@octokit/rest": "18.12.0",
|
||||
"@types/mocha": "9.1.0",
|
||||
"@types/node": "16.11.27",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "16.11.33",
|
||||
"ansi-red": "0.1.1",
|
||||
"axios": "0.26.1",
|
||||
"axios": "0.27.2",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
"babel-plugin-source-map-support": "2.1.3",
|
||||
@@ -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",
|
||||
|
||||
@@ -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.1.10](https://github.com/appium/appium/compare/@appium/images-plugin@1.1.9...@appium/images-plugin@1.1.10) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.9](https://github.com/appium/appium/compare/@appium/images-plugin@1.1.8...@appium/images-plugin@1.1.9) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/images-plugin",
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.10",
|
||||
"description": "Plugin for working with images and image elements in Appium",
|
||||
"keywords": [
|
||||
"appium",
|
||||
@@ -35,10 +35,10 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.5.2",
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/opencv": "^1.0.7",
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/base-driver": "file:../base-driver",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"@appium/opencv": "file:../opencv",
|
||||
"@appium/support": "file:../support",
|
||||
"lru-cache": "6.0.0"
|
||||
},
|
||||
"appium": {
|
||||
|
||||
@@ -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.0.0-beta.6](https://github.com/appium/appium/compare/@appium/relaxed-caps-plugin@1.0.0-beta.5...@appium/relaxed-caps-plugin@1.0.0-beta.6) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/relaxed-caps-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0-beta.5](https://github.com/appium/appium/compare/@appium/relaxed-caps-plugin@1.0.0-beta.4...@appium/relaxed-caps-plugin@1.0.0-beta.5) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/relaxed-caps-plugin
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"version": "1.0.0-beta.5",
|
||||
"version": "1.0.0-beta.6",
|
||||
"author": "Appium <maintainers@appium.io>",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -32,7 +32,7 @@
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.58.0](https://github.com/appium/appium/compare/@appium/support@2.57.4...@appium/support@2.58.0) (2022-05-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **support:** Add a helper to deep freeze objects ([#16849](https://github.com/appium/appium/issues/16849)) ([a2b2ded](https://github.com/appium/appium/commit/a2b2ded1e4cab84b6ff9a6ecdc5bb3e544118ba0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.57.4](https://github.com/appium/appium/compare/@appium/support@2.57.3...@appium/support@2.57.4) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/support
|
||||
|
||||
@@ -166,4 +166,33 @@ function getObjectId (object) {
|
||||
return OBJECTS_MAPPING.get(object);
|
||||
}
|
||||
|
||||
export { requirePackage, getObjectSize, getObjectId };
|
||||
/**
|
||||
* Perform deep freeze of the given object (e. g.
|
||||
* all nested objects also become immutable).
|
||||
* If the passed object is of a plain type
|
||||
* then no change is done and the same object
|
||||
* is returned.
|
||||
* ! This function changes the given object,
|
||||
* so it becomes immutable.
|
||||
*
|
||||
* @param {*} object Any valid ECMA object
|
||||
* @returns {*} The same object that was passed to the
|
||||
* function after it was made immutable.
|
||||
*/
|
||||
function deepFreeze (object) {
|
||||
let propNames;
|
||||
try {
|
||||
propNames = Object.getOwnPropertyNames(object);
|
||||
} catch (ign) {
|
||||
return object;
|
||||
}
|
||||
for (const name of propNames) {
|
||||
const value = object[name];
|
||||
if (value && typeof value === 'object') {
|
||||
deepFreeze(value);
|
||||
}
|
||||
}
|
||||
return Object.freeze(object);
|
||||
}
|
||||
|
||||
export { requirePackage, getObjectSize, getObjectId, deepFreeze };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/support",
|
||||
"version": "2.57.4",
|
||||
"version": "2.58.0",
|
||||
"description": "Support libs used across appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,7 +43,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.17.9",
|
||||
"archiver": "5.3.1",
|
||||
"axios": "0.26.1",
|
||||
"axios": "0.27.2",
|
||||
"base64-stream": "1.0.0",
|
||||
"bluebird": "3.7.2",
|
||||
"bplist-creator": "0.1.0",
|
||||
@@ -59,7 +59,7 @@
|
||||
"moment": "2.29.3",
|
||||
"mv": "2.1.1",
|
||||
"ncp": "2.0.0",
|
||||
"npmlog": "6.0.1",
|
||||
"npmlog": "6.0.2",
|
||||
"opencv-bindings": "4.5.5",
|
||||
"pkg-dir": "5.0.0",
|
||||
"plist": "3.0.5",
|
||||
@@ -85,11 +85,5 @@
|
||||
"access": "public"
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"devDependencies": {
|
||||
"type-fest": "2.12.2"
|
||||
},
|
||||
"overrides": {
|
||||
"type-fest": "$type-fest"
|
||||
},
|
||||
"gitHead": "722ae145fb40bf17b0168ea8b025763f120ad574"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { should } from 'chai';
|
||||
import { node } from '../../lib';
|
||||
|
||||
describe('node utilities', function () {
|
||||
@@ -30,4 +31,17 @@ describe('node utilities', function () {
|
||||
node.getObjectId(obj2).should.eql(node.getObjectId(obj2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('deepFreeze', function () {
|
||||
it('should be able to deep freeze objects', function () {
|
||||
const obj1 = {};
|
||||
node.deepFreeze(obj1).should.eql(obj1);
|
||||
const obj2 = node.deepFreeze({a: {b: 'c'}});
|
||||
should().throw(() => obj2.a.b = 'd');
|
||||
node.deepFreeze(1).should.eql(1);
|
||||
should().equal(node.deepFreeze(null), null);
|
||||
const obj3 = [1, {}, 3];
|
||||
should().equal(node.deepFreeze(obj3), obj3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.3.19](https://github.com/appium/appium/compare/@appium/test-support@1.3.18...@appium/test-support@1.3.19) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.18](https://github.com/appium/appium/compare/@appium/test-support@1.3.17...@appium/test-support@1.3.18) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/test-support
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/test-support",
|
||||
"version": "1.3.18",
|
||||
"version": "1.3.19",
|
||||
"description": "A collection of test utilities used across Appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,7 +45,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.57.4",
|
||||
"@appium/support": "file:../support",
|
||||
"@babel/runtime": "7.17.9",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [0.2.3](https://github.com/appium/appium/compare/@appium/types@0.2.2...@appium/types@0.2.3) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/types
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/appium/appium/compare/@appium/types@0.2.1...@appium/types@0.2.2) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/types
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/types",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"description": "Various type declarations used across Appium",
|
||||
"keywords": [
|
||||
"appium",
|
||||
@@ -38,6 +38,6 @@
|
||||
},
|
||||
"gitHead": "722ae145fb40bf17b0168ea8b025763f120ad574",
|
||||
"dependencies": {
|
||||
"@appium/schema": "^0.0.5"
|
||||
"@appium/schema": "file:../schema"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.0.0-beta.8](https://github.com/appium/appium-plugins/compare/@appium/universal-xml-plugin@1.0.0-beta.7...@appium/universal-xml-plugin@1.0.0-beta.8) (2022-05-02)
|
||||
|
||||
**Note:** Version bump only for package @appium/universal-xml-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0-beta.7](https://github.com/appium/appium-plugins/compare/@appium/universal-xml-plugin@1.0.0-beta.6...@appium/universal-xml-plugin@1.0.0-beta.7) (2022-04-20)
|
||||
|
||||
**Note:** Version bump only for package @appium/universal-xml-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/universal-xml-plugin",
|
||||
"version": "1.0.0-beta.7",
|
||||
"version": "1.0.0-beta.8",
|
||||
"description": "Appium plugin for making XML source and XPath queries the same across iOS and Android",
|
||||
"keywords": [
|
||||
"appium",
|
||||
@@ -32,8 +32,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^8.5.2",
|
||||
"@appium/base-plugin": "^1.8.4",
|
||||
"@appium/base-driver": "file:../base-driver",
|
||||
"@appium/base-plugin": "file:../base-plugin",
|
||||
"fast-xml-parser": "3.21.1",
|
||||
"xmldom": "0.6.0",
|
||||
"xpath": "0.0.32"
|
||||
|
||||
Reference in New Issue
Block a user