Compare commits

..
Author SHA1 Message Date
Jonathan Lipps 59dd30ba20 Publish
- appium@2.0.0-beta.35
 - @appium/doctor@1.16.19
 - @appium/eslint-config-appium@5.1.1
 - @appium/gulp-plugins@6.0.10
2022-05-03 13:42:56 -07:00
Christopher Hiller 6480679bfb chore(appium): lint again 2022-05-03 13:34:10 -07:00
Christopher Hiller c2753107c6 test(appium): e2e helpers now log executed child process commands 2022-05-03 13:12:31 -07:00
Christopher Hiller a794ade7b0 chore(appium): lint 2022-05-03 13:09:17 -07:00
Christopher Hiller 5e24fbac5c chore(ci): add Node v18 to matrix; remove Node v17 2022-05-03 12:50:20 -07:00
Renovate Bot 8caf18f542 chore(gulp-plugins): update dependency mocha to v10 2022-05-03 12:21:06 -07:00
Christopher Hiller 93e05a8269 fix(eslint-config-appium): revert prettier-related change
This reverts commits b34b75a80e,  88a6655253 and d89203f96c which, as a whole, was breaking.

This will fix the publish of v5.1.0, which should have been a major release.

After v5.1.1 has been released, this PR should be reverted in prep for v6.
2022-05-03 12:19:13 -07:00
Christopher Hiller 5cc22325f2 chore(appium): refactor some variable/parameter names for clarity
renamed parameters named `ext` to `installSpec`, since these correspond to the values of `installSpec` in the manifest
2022-05-03 12:13:44 -07:00
Christopher Hiller a2b07f6150 test(appium): replace all occurrences of 'driver'/'plugin' literals w/ constants 2022-05-03 12:04:15 -07:00
Christopher Hiller 823014321a chore: do not use @babel/register w/ wallaby 2022-05-03 12:04:02 -07:00
Christopher Hiller d89203f96c fix(eslint-config-appium): remove custom indenting rules
these rules also conflict with prettier. prettier does something very similar, but not exactly.  w/o this, running `prettier` then `eslint` will cause an eslint error.
2022-05-03 11:42:30 -07:00
Jonathan Lipps 773d9d6c0d docs(appium): add drivers intro doc (resolves #16795) 2022-05-03 10:27:40 -07:00
Renovate Bot a686053f03 chore(doctor): update dependency appium-adb to v9.2.2 2022-05-03 11:19:47 +00:00
Christopher Hiller 5a3a0f7c14 chore(appium): remove unused appium-config.schema.json from vcs 2022-05-02 15:28:33 -07:00
Jonathan Lipps 5482c921bc Publish
- appium@2.0.0-beta.34
 - @appium/base-driver@8.5.3
 - @appium/base-plugin@1.8.5
 - @appium/doctor@1.16.18
 - @appium/docutils@0.0.4
 - @appium/eslint-config-appium@5.1.0
 - @appium/execute-driver-plugin@1.0.5
 - @appium/fake-driver@3.2.13
 - @appium/fake-plugin@1.3.4
 - @appium/gulp-plugins@6.0.9
 - @appium/images-plugin@1.1.10
 - @appium/relaxed-caps-plugin@1.0.0-beta.6
 - @appium/support@2.58.0
 - @appium/test-support@1.3.19
 - @appium/types@0.2.3
 - @appium/universal-xml-plugin@1.0.0-beta.8
2022-05-02 14:47:10 -07:00
Christopher Hiller 88a6655253 fix(eslint-config-appium): disable space-before-function-paren
BREAKING CHANGE

This explicitly-set rule conflicts with prettier and needs to be disabled.
2022-05-02 14:23:52 -07:00
Renovate Bot 38b4fa2bad chore(): update dependency ws to v8.6.0 2022-05-02 15:21:36 +00:00
Renovate Bot 67dacd87cf chore(gulp-plugins): update dependency @types/node to v16.11.33 2022-05-02 12:44:16 +00:00
Renovate Bot f1b36cc7c5 chore(support): update dependency axios to v0.27.2 2022-04-30 18:00:05 +00:00
Renovate Bot ec1b95b96c chore(): update dependency chai-webdriverio-async to v2.7.0 2022-04-30 15:20:46 +00:00
Renovate Bot bbce027f34 chore(): update babel monorepo to v7.17.10 2022-04-30 13:07:26 +00:00
Renovate Bot 2e7c1bb46e chore(base-driver): update dependency express to v4.18.1 2022-04-30 11:05:43 +00:00
Renovate Bot 9df8c08b09 chore(gulp-plugins): update dependency @types/node to v16.11.32 2022-04-29 15:35:36 +00:00
Renovate Bot a67ba79dba chore(): update dependency typescript to v4.6.4 2022-04-29 12:39:18 +00:00
Renovate Bot de5a96fb08 chore(gulp-plugins): update dependency @types/node to v16.11.31 2022-04-28 16:45:19 +00:00
Renovate Bot 90918200e2 chore(doctor): update dependency inquirer to v8.2.4 2022-04-28 13:47:57 +00:00
Renovate Bot a725d052f8 chore(doctor): update dependency inquirer to v8.2.3 2022-04-27 16:29:37 +00:00
Renovate Bot e49ab6d343 chore(): update dependency lint-staged to v12.4.1 2022-04-27 13:49:29 +00:00
Renovate Bot fdee8701a6 chore(base-driver): update dependency express to v4.18.0 2022-04-26 14:27:32 +00:00
Renovate Bot 2e8e7a5d65 chore(gulp-plugins): update dependency @types/node to v16.11.29 2022-04-26 12:08:33 +00:00
Christopher Hiller 39aab21aef chore: "lerna link convert"; replace siblings with relative "file:" links
This is a "fix" but only for the dev environment.

Further:

- add install of `mjpeg-consumer` to CI, since it may have disappeared from the environment
- remove `overrides` from `@appium/support`'s `package.json` since it no longer seems needed
2022-04-25 14:37:28 -07:00
Renovate Bot b957123401 chore(gulp-plugins): update dependency @types/node to v16.11.28 2022-04-25 12:01:35 +00:00
Mykola Mokhnach a2b2ded1e4 feat(support): Add a helper to deep freeze objects (#16849) 2022-04-24 07:08:50 +02:00
Christopher Hiller b34b75a80e chore: add prettier
As a pre-commit hook, `.js` files will be run thru `eslint --fix` and rewritten using `prettier`.
Added `eslint-config-prettier` to disable rules incompatible with `prettier`.
2022-04-22 15:56:35 -07:00
Kazuaki Matsuo 4d91f76e4c docs(appium): add python.py (#16840)
* add python.py

* update the explanation

* remove duplicated import

* tweak, add en lang to make the find element stable

* update review

* make list
2022-04-22 09:42:39 -07:00
Jonathan Lipps 2e57fd4900 docs(appium): add placeholders for settings and mobile method guides 2022-04-22 09:41:02 -07:00
Jonathan Lipps 1201230b6a docs(appium): adjust nav with greater structure and remove dark mode since it doesn't look very good 2022-04-22 09:41:02 -07:00
Jonathan Lipps 70f9b38dd5 docs(appium): add main intro doc
resolves #16794
2022-04-22 09:41:02 -07:00
Renovate Bot c5787f7cf0 chore(gulp-plugins): update dependency @types/mocha to v9.1.1 2022-04-22 10:19:17 +00:00
Christopher Hiller 5bdc476c62 feat(eslint-config-appium): adds a warning if code attempts to assign to an object prototype
See #16829

When using ES6 `class` syntax, direct assignment to `prototype` can be a source of bugs.  Class properties seem to exacerbate the issue.  While in many cases this code will work fine, there is not always a straightforward workaround, which is why the rule just emits a warning.

The new syntax rule considers assignment to the `prototype` of any object, as well as using `Object.assign()` to do the same.
2022-04-21 16:11:04 -07:00
Christopher Hiller 9bfe482b97 test(docutils): run mike test against sources
referencing the project root pulls the root `index.js`, which requires `build/lib`, which is not what we want.
2022-04-21 16:10:39 -07:00
Renovate Bot 00f38df09a chore(): update dependency lint-staged to v12.4.0 2022-04-21 16:50:38 +00:00
Renovate Bot 19c4cd246b chore(support): update dependency npmlog to v6.0.2 2022-04-21 13:21:08 +00:00
Renovate Bot 0d7f851410 chore(docutils): update dependency jsdoc-plugin-typescript to v2.0.7 2022-04-21 06:26:03 +00:00
61 changed files with 1187 additions and 624 deletions
+4 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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": {
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.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
+184 -1
View File
@@ -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!
+198 -1
View File
@@ -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.
+47 -1
View File
@@ -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.
+15 -10
View File
@@ -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"
}
+45 -20
View File
@@ -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
*/
+229 -123
View File
@@ -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
*/
+33 -18
View File
@@ -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
*/
+8 -8
View File
@@ -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()
+1 -1
View File
@@ -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();
});
+8 -5
View File
@@ -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]
+2 -1
View File
@@ -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: {
+3 -2
View File
@@ -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',
);
});
+35 -35
View File
@@ -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;
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+4 -7
View File
@@ -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"
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+2 -2
View File
@@ -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",
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+4 -4
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+1 -7
View File
@@ -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:
+3 -3
View File
@@ -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 -1
View File
@@ -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
+14
View File
@@ -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',
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+3 -3
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+3 -3
View File
@@ -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"
},
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+9 -9
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+5 -5
View File
@@ -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
+2 -2
View File
@@ -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": {
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+30 -1
View File
@@ -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 };
+3 -9
View File
@@ -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"
}
+14
View File
@@ -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);
});
});
});
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+2 -2
View File
@@ -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",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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"