Compare commits

...
Author SHA1 Message Date
Jonathan Lipps 339acd345f chore: publish
- appium@2.7.0
 - @appium/base-driver@9.8.0
 - @appium/base-plugin@2.2.33
 - @appium/doctor@2.1.0
 - @appium/docutils@1.0.9
 - @appium/driver-test-support@0.4.25
 - @appium/execute-driver-plugin@3.0.30
 - @appium/fake-plugin@3.1.37
 - @appium/images-plugin@3.0.8
 - @appium/logger@1.1.0
 - @appium/plugin-test-support@0.3.32
 - @appium/support@4.3.0
 - @appium/test-support@3.0.37
 - @appium/types@0.19.0
2024-06-06 09:54:55 -07:00
Mykola Mokhnach 21316e94d0 feat(base-driver): Make addition of search query params to cache optional (#20195) 2024-06-06 12:04:36 +02:00
Jonathan LippsandEdgars Eglītis 43fcc17b31 docs(appium): recognize headspin as development partner (#20191)
* docs(appium): recognize headspin as development partner

* update partner announcement branch (#20200)

* docs: make sponsor placement prettier

* docs: add margin to sponsor footer

* docs: add margin around individual sponsor images in footer

* docs: hide sponsor footer on landing page

---------

Co-authored-by: Edgars Eglītis <edgars.eglitis@outlook.com>

* partners plural

---------

Co-authored-by: Edgars Eglītis <edgars.eglitis@outlook.com>
2024-06-05 12:47:07 -07:00
renovate[bot] b1a4045bbd chore(deps): update dependency prettier to v3.3.1 2024-06-05 15:09:29 +00:00
renovate[bot] d2a8edbb7a chore(deps): update dependency @types/node to v20.14.2 2024-06-05 14:54:14 +00:00
Edgars Eglītis 6d67177e2f chore(logger): tidy up logger integration (#20194) 2024-06-05 10:27:19 +03:00
renovate[bot] db62358dba fix(types): update dependency type-fest to v4.19.0 (#20193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-05 09:26:39 +03:00
Mykola Mokhnach 4fd6893146 fix(support): Make sure response stream is defined while stopping it (#20189) 2024-06-05 08:13:40 +02:00
Mykola Mokhnach 8915934270 feat(appium): Replace npmlog with the local fork (#20190) 2024-06-05 08:12:07 +02:00
renovate[bot] ae7f74a014 fix(appium): update dependency ajv to v8.16.0 2024-06-04 21:15:20 +00:00
renovate[bot] ea9cf8d910 chore(deps): update eslint-related packages to v7.12.0 2024-06-04 18:53:20 +00:00
Kazuaki Matsuo 70449cd077 feat(logger): add packages/logger package from npmlog (#20161)
* feat: move logger into appium/logger

* add logger in tsconfig.json

* add smoke with loading the index.js

* modify files to fit other modules

* refer to @appium/logger

* update with npm run sync-pkgs and a few

* cleanup changelog and package-lock.json

* update smoke

* pin deps

* adjust tsconfig.json with others

* Update README.md

* update license

* update license

* Update LICENSE

* fix review
2024-06-04 09:12:27 -07:00
Jonathan Lipps 65c47acf59 fix(appium): ensure ws close code is valid (#20178) 2024-06-04 07:46:16 -07:00
renovate[bot] 96a7a3f23b fix(support): update dependency teen_process to v2.1.4 2024-06-04 14:02:01 +00:00
renovate[bot] c8f997973c fix(execute-driver-plugin): update dependency webdriverio to v8.38.2 2024-06-04 11:27:38 +00:00
renovate[bot] bea47aacca fix(doctor): update dependency appium-adb to v12.4.2 2024-06-04 06:22:57 +00:00
renovate[bot] 10e5b37211 chore(deps): update dependency @types/node to v20.14.1 2024-06-04 06:05:50 +00:00
Kazuaki Matsuo 459f0a34b2 docs(appium): add icons by opencollevtive in sponsors (#20170)
* docs: add svg by opencollevtive to add icons

* Update SPONSORS.md
2024-06-03 14:11:00 -07:00
renovate[bot] 4c15dc6e5d chore(deps): update dependency @types/node to v20.14.0 2024-06-03 15:05:34 +00:00
renovate[bot] 848eb00afb fix(docutils): update dependency yaml to v2.4.3 2024-06-03 13:14:51 +00:00
renovate[bot] de8b1c97dc chore(deps): update dependency json-schema-to-typescript to v14.0.5 2024-06-03 11:00:57 +00:00
renovate[bot] 092ebb2a69 chore(deps): update dependency prettier to v3.3.0 2024-06-02 09:13:49 +00:00
renovate[bot] dddc4aa8c0 chore(deps): update dependency @types/node to v20.13.0 2024-06-01 07:26:37 +00:00
renovate[bot] 425d3eac1d chore(docutils): update dependency mkdocs-git-revision-date-localized-plugin to v1.2.6 2024-06-01 05:35:41 +00:00
renovate[bot] 57b0406a33 fix(support): update dependency @types/which to v3 (#20164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 13:28:10 +02:00
renovate[bot] e3e1757c56 fix(doctor): update dependency appium-adb to v12.4.1 2024-05-31 11:07:08 +00:00
renovate[bot] 17e271afff chore(deps): update dependency @types/node to v20.12.13 2024-05-30 07:53:34 +00:00
renovate[bot] 7718cd01d1 chore(deps): update eslint-related packages to v7.11.0 2024-05-28 12:21:51 +00:00
Jonathan Lipps a4138c6ec9 chore: publish
- appium@2.6.0
 - @appium/base-driver@9.7.0
 - @appium/base-plugin@2.2.32
 - @appium/doctor@2.0.37
 - @appium/docutils@1.0.8
 - @appium/driver-test-support@0.4.24
 - @appium/execute-driver-plugin@3.0.29
 - @appium/fake-driver@5.5.2
 - @appium/fake-plugin@3.1.36
 - @appium/images-plugin@3.0.7
 - @appium/opencv@3.0.4
 - @appium/plugin-test-support@0.3.31
 - @appium/support@4.2.6
 - @appium/test-support@3.0.36
 - @appium/types@0.18.0
 - @appium/universal-xml-plugin@1.0.19
2024-05-27 12:02:58 -07:00
renovate[bot] 24a039b432 chore(deps): update dependency eslint-plugin-promise to v6.2.0 2024-05-27 16:38:26 +00:00
renovate[bot] 5ca62f8bfa chore(docutils): update dependency mkdocs-material to v9.5.25 2024-05-27 13:49:38 +00:00
renovate[bot] d5369f8b08 fix(types): update dependency type-fest to v4.18.3 (#20149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-26 17:04:14 +03:00
renovate[bot] 33be2ee45d fix(doctor): update dependency appium-adb to v12.4.0 2024-05-26 14:03:54 +00:00
renovate[bot] e3dcd66714 fix(appium): update dependency ajv to v8.14.0 2024-05-26 10:54:27 +00:00
renovate[bot] e80b6826ef fix(execute-driver-plugin): update dependency webdriverio to v8.38.0 2024-05-25 07:42:04 +00:00
Mykola Mokhnach 275790ec01 fix(base-driver): Update the web socket upgrade behavior (#20142) 2024-05-24 19:57:54 +02:00
renovate[bot] 82406f1775 fix(support): update dependency glob to v10.4.1 (#20141)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 20:29:53 +03:00
Kazuaki Matsuo a015f835dd feat{appium}: add windows for desktop preset (#20135)
* feat: add windows for desktop preset

* move the position
2024-05-24 00:24:10 -07:00
renovate[bot] 27ff9a00ef fix(execute-driver-plugin): update dependency webdriverio to v8.37.0 2024-05-23 10:57:56 +00:00
renovate[bot] a876f112b5 fix(support): update dependency axios to v1.7.2 2024-05-22 13:22:13 +00:00
renovate[bot] 56f72c0af7 fix(support): update dependency glob to v10.3.16 (#20134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-22 10:22:29 +03:00
Edgars Eglītis 8c97ac5800 docs: update CLI and quickstart contents (#20131)
* docs: various adjustments for quickstart

* docs: add setup docs, more doctor references

* docs: add footnote for macOS-only items

* docs: update CLI args page

* docs: update CLI extension subcommands

* docs: fix indentation

* docs: fix grammar
2024-05-22 09:48:35 +03:00
renovate[bot] 70650921d8 chore(docutils): update dependency mkdocs-material to v9.5.24 2024-05-21 23:46:42 +00:00
renovate[bot] 465495ef2a fix(test-support): update dependency sinon to v18 (#20128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 15:43:15 +03:00
renovate[bot] 440708c876 chore(deps): update eslint-related packages to v7.10.0 (#20123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 11:18:00 +02:00
renovate[bot] c805155863 fix(universal-xml-plugin): update dependency fast-xml-parser to v4.4.0 (#20126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 11:17:18 +02:00
renovate[bot] 95be7dd477 fix(support): update dependency glob to v10 (#20127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 11:16:58 +02:00
Kazuaki Matsuo 2b2fb185a6 feat(appium): prepare setup subcommand as shortcut for drivers/plugins installation (#20102)
- add setup command
- add a couple of preset commands to install drivers/plugins
2024-05-20 23:05:02 -07:00
Kazuaki Matsuo f21e09fe72 docs(appium): update appium related tools section (#20116)
* docs(appium): update appium related  tools section
2024-05-20 18:43:44 -07:00
renovate[bot] 11510cb1a5 fix(support): update dependency axios to v1.7.1 2024-05-20 21:13:04 +00:00
renovate[bot] f764c38c29 fix(doctor): update dependency appium-adb to v12.3.2 2024-05-20 13:40:31 +00:00
renovate[bot] 5e51f6c5f2 chore(deps): update eslint-related packages to v7.9.0 2024-05-19 17:35:19 +00:00
renovate[bot] 4b20eca627 chore(deps): update dependency npm-run-all2 to v6.2.0 2024-05-19 12:28:25 +00:00
renovate[bot] b34ce3cff0 fix(support): update dependency teen_process to v2.1.3 2024-05-19 02:29:44 +00:00
Mykola Mokhnach 6f602d3e50 feat(appium): Add --show-debug-info CLI argument (#20108) 2024-05-18 23:12:56 +02:00
renovate[bot] 2ad91655c2 chore(support): update dependency sharp to v0.33.4 (#20109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-18 21:28:13 +03:00
renovate[bot] 1b4f36cebe fix(support): update dependency semver to v7.6.2 2024-05-18 16:35:05 +00:00
renovate[bot] a8d36b0a98 fix(driver-test-support): update definitelytyped 2024-05-18 09:10:31 +00:00
renovate[bot] 0598a81d09 chore(docutils): update dependency mkdocs-material to v9.5.23 2024-05-17 21:57:09 +00:00
renovate[bot] e972d668fe chore(deps): update dependency lerna to v8.1.3 2024-05-17 17:52:05 +00:00
renovate[bot] 3b43be1732 fix(types): update dependency type-fest to v4.18.2 (#20103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 10:23:37 +03:00
zhangxh075 deb9548369 docs: translate some documents into Chinese (#20100)
* Change the directory of the introduction module

* Translate the introduction index pages
2024-05-17 10:22:21 +03:00
Kazuaki Matsuo 2cbf3af759 docs: add example command for driver/plugin versions (#20098) 2024-05-16 13:17:43 -07:00
Jonathan Lipps 233dbd22ff docs(appium): write sponsorship release blog 2024-05-14 12:06:40 -07:00
Jonathan Lipps 807183925c docs(appium): rename platinum sponsors to strategic partners 2024-05-14 12:06:40 -07:00
renovate[bot] 38a506bbcd chore(deps): update dependency rimraf to v5.0.7 2024-05-13 02:46:27 +00:00
renovate[bot] 5e67a15299 chore(deps): update dependency chai-as-promised to v7.1.2 2024-05-12 15:00:34 +00:00
renovate[bot] a722f48f14 chore(deps): update definitelytyped (#20070) 2024-05-09 08:10:30 +02:00
Mykola Mokhnach db9fd98e7d fix: Revert "chore(deps): update dependency conventional-changelog-conventionalcom…" (#20084)
https://github.com/semantic-release/release-notes-generator/issues/633
2024-05-09 00:18:54 +02:00
Edgars Eglītis 22f40f094e chore(github): add link to Appium forum when creating an issue (#20083) 2024-05-08 21:12:49 +03:00
renovate[bot] 3dd72b2d0c chore(deps): update dependency conventional-changelog-conventionalcommits to v8 (#20072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-08 20:06:02 +02:00
renovate[bot] 2acf262482 fix(test-support): update dependency sinon to v17.0.2 (#20078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-08 20:05:43 +02:00
renovate[bot] e961a9b9c5 fix(doctor): update dependency appium-adb to v12.2.0 2024-05-08 15:23:54 +00:00
renovate[bot] b5fd5b6d2c fix(support): update dependency semver to v7.6.1 2024-05-08 09:44:19 +00:00
renovate[bot] fd63e9bdbd fix(doctor): update dependency appium-adb to v12.0.9 2024-05-04 17:26:16 +00:00
renovate[bot] eb6e0f82b3 chore(docutils): update dependency mkdocs-material to v9.5.21 2024-05-04 13:39:14 +00:00
renovate[bot] f6f51c4b9e chore(docutils): update dependency mike to v2.1.1 2024-05-04 09:35:05 +00:00
renovate[bot] 690bebe4bc chore(deps): update dependency @types/chai to v4.3.15 2024-05-04 00:15:22 +00:00
Mykola Mokhnach 0492fe49dc fix(appium): Do not crash the process if there was an exception in a winston transport (#20065) 2024-05-03 23:49:45 +02:00
renovate[bot] 8526cc0333 chore(deps): update dependency json-schema-to-typescript to v14.0.4 2024-05-02 11:16:24 +00:00
Edgars Eglītis 1bde482ac8 ci: add back Node 18 (#20059)
* ci: add back Node 18

* ci: use GH Action node-lts-versions

* ci: also run on versions that have not yet become LTS
2024-05-02 13:58:55 +03:00
Kazuaki Matsuo 4d634642c8 docs: remove unused files (#20057) 2024-05-02 00:20:22 -07:00
renovate[bot] 06fc841c6a chore(docutils): update dependency mike to v2.1.0 2024-05-02 06:42:50 +00:00
renovate[bot] a52ac2139e chore(deps): update dependency @types/node to v20.12.8 2024-05-02 06:40:58 +00:00
renovate[bot] 3d438eb6a8 chore(docutils): update dependency mkdocs-git-revision-date-localized-plugin to v1.2.5 2024-05-02 02:15:27 +00:00
Jonathan Lipps 21ba471eb7 docs(appium): add magicpod as a bronze sponsor (#20054) 2024-05-01 11:33:18 -07:00
renovate[bot] 395dc225e4 fix(appium): update dependency ws to v8.17.0 2024-04-30 21:20:45 +00:00
renovate[bot] 448e94ee50 fix(appium): update dependency ajv to v8.13.0 2024-04-30 19:27:48 +00:00
renovate[bot] f8d1fb7df7 fix(images-plugin): update dependency lru-cache to v10.2.2 (#20052)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 18:46:31 +03:00
renovate[bot] fe16faaacb chore(deps): update eslint-related packages to v7.8.0 2024-04-30 12:31:19 +00:00
renovate[bot] 0211174300 fix(docutils): update dependency yaml to v2.4.2 2024-04-30 09:24:13 +00:00
renovate[bot] bfa8cbf7ea chore(docutils): update dependency mkdocs-material to v9.5.20 2024-04-30 07:52:51 +00:00
Kazuaki Matsuo 36f9cd54ba docs: update commiteers (#20041)
* docs: update commiteers

* docs: update GOVERNANCE.md
2024-04-29 22:40:49 -07:00
Mykola Mokhnach 4e8c91c8f6 feat(base-driver): Add env variables to control applications cache (#20042) 2024-04-29 22:48:01 +02:00
Jonathan Lipps 05aedb20eb docs(appium): add sponsorship program information (#20037)
* docs(appium): add sponsorship program information

* add emeritus contribution section

* add forum contributors and fix emeritus section
2024-04-26 13:30:16 -07:00
renovate[bot] f645b50d9d fix(images-plugin): update dependency lru-cache to v10.2.1 (#20039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-26 15:01:44 +03:00
renovate[bot] 799b743d72 chore(docutils): update dependencies mkdocs-material to v9.5.19 & mkdocs to v1.6.0 (#20038) 2024-04-25 20:01:11 +03:00
Mykola Mokhnach 3423fd9206 feat(base-driver): Add the original app link argument to configureApp callbacks (#20035) 2024-04-24 18:01:14 +02:00
renovate[bot] cf3910fdab chore(deps): update eslint-related packages to v7.7.1 2024-04-23 06:11:50 +00:00
111 changed files with 4438 additions and 1933 deletions
+2 -2
View File
@@ -61,8 +61,8 @@ body:
value: |
- Operating system:
- Appium server version (output of `appium --version`):
- Appium driver(s) and their version(s):
- Appium plugin(s) and their version(s):
- Appium driver(s) and their version(s) (`appium driver list`):
- Appium plugin(s) and their version(s) (`appium plugin list`):
- Node.js version (output of `node --version`):
- `npm` version (output of `npm --version`):
- Last component(s) version which did _not_ exhibit the problem:
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: ❓ Question
url: https://discuss.appium.io/
about: Please use the Appium forum for questions
+20 -15
View File
@@ -48,16 +48,6 @@ labels:
matcher:
files: ['packages/base-driver/**']
- label: '@appium/eslint-config-appium'
sync: true
matcher:
files: ['packages/eslint-config-appium/**']
- label: '@appium/eslint-config-appium-ts'
sync: true
matcher:
files: ['packages/eslint-config-appium-ts/**']
- label: '@appium/base-plugin'
sync: true
matcher:
@@ -78,6 +68,16 @@ labels:
matcher:
files: ['packages/driver-test-support/**']
- label: '@appium/eslint-config-appium'
sync: true
matcher:
files: ['packages/eslint-config-appium/**']
- label: '@appium/eslint-config-appium-ts'
sync: true
matcher:
files: ['packages/eslint-config-appium-ts/**']
- label: '@appium/execute-driver-plugin'
sync: true
matcher:
@@ -98,6 +98,11 @@ labels:
matcher:
files: ['packages/images-plugin/**']
- label: '@appium/logger'
sync: true
matcher:
files: ['packages/logger/**']
- label: '@appium/opencv'
sync: true
matcher:
@@ -123,6 +128,11 @@ labels:
matcher:
files: ['packages/support/**']
- label: '@appium/strongbox'
sync: true
matcher:
files: ['packages/strongbox/**']
- label: '@appium/test-support'
sync: true
matcher:
@@ -142,8 +152,3 @@ labels:
sync: true
matcher:
files: ['packages/universal-xml-plugin/**']
- label: '@appium/strongbox'
sync: true
matcher:
files: ['packages/strongbox/**']
+3 -3
View File
@@ -53,11 +53,11 @@ jobs:
prepare_matrix:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.generate-matrix.outputs.versions }}
versions: ${{ steps.generate-matrix.outputs.active }}
steps:
- name: Select 2 most recent LTS versions of Node.js
- name: Select all active LTS versions of Node.js
id: generate-matrix
run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:2] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT"
uses: msimerson/node-lts-versions@v1
test:
needs:
+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
prepare_matrix:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.generate-matrix.outputs.versions }}
versions: ${{ steps.generate-matrix.outputs.active }}
steps:
- name: Select 2 most recent LTS versions of Node.js
- name: Select all active LTS versions of Node.js
id: generate-matrix
run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:2] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT"
uses: msimerson/node-lts-versions@v1
check:
needs:
-78
View File
@@ -1,78 +0,0 @@
'use strict';
module.exports = (wallaby) => {
return {
compilers: {
'**/*.js': wallaby.compilers.typeScript({
allowJs: true,
allowSyntheticDefaultImports: true,
resolveJsonModule: true,
isolatedModules: true,
}),
'**/*.ts?(x)': wallaby.compilers.typeScript(),
},
debug: true,
env: {
type: 'node',
},
files: [
'./packages/*/build/**/*',
'./packages/*/lib/**/*.js',
'./packages/*/lib/**/*.ts',
'./packages/*/test/**/*helper*.js',
'./packages/*/test/**/*helper*.ts',
'./packages/*/test/**/*mock*.js',
'./packages/*/test/**/*mock*.ts',
'./packages/*/package.json',
'./packages/*/tsconfig*.json',
'./packages/*/test/**/fixture?(s)/**/*',
{
binary: true,
pattern: './packages/support/test/unit/assets/sample_binary.plist',
},
{
instrument: false,
pattern: './packages/support/test/unit/assets/sample_text.plist',
},
{
instrument: false,
pattern: './packages/base-driver/static/**/*',
},
{
instrument: false,
pattern: './packages/*/test/**/__snapshots__/**/*',
},
'!./packages/*/test/**/*-specs.js',
'!./packages/*/test/**/*.e2e.spec.js',
'!./packages/*/test/**/*.e2e.spec.ts',
'!**/local_appium_home/**',
],
testFramework: 'mocha',
tests: [
'./packages/*/test/unit/**/*.spec.js',
'./packages/*/test/unit/**/*.spec.ts',
'!**/local_appium_home/**',
],
workers: {
// restart: true,
},
setup(wallaby) {
// copied out of `./test/setup.js`
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();
const mocha = wallaby.testFramework;
mocha.timeout(10000);
},
runMode: 'onsave',
};
};
+2 -2
View File
@@ -4,7 +4,7 @@ Appium contains many different modules and supports a variety of drivers, plugin
## Main Appium Module
* ### [Appium 2.0 Changelog](packages/appium/CHANGELOG.md)
* ### [Appium 2 Changelog](packages/appium/CHANGELOG.md)
* [Appium 1.x Changelog](https://github.com/appium/appium/blob/1.x/CHANGELOG.md)
## Appium Drivers / Clients / Plugins
@@ -23,6 +23,7 @@ Appium contains many different modules and supports a variety of drivers, plugin
* [fake-driver](packages/fake-driver/CHANGELOG.md)
* [fake-plugin](packages/fake-plugin/CHANGELOG.md)
* [images-plugin](packages/images-plugin/CHANGELOG.md)
* [logger](packages/logger/CHANGELOG.md)
* [opencv](packages/opencv/CHANGELOG.md)
* [plugin-test-support](packages/plugin-test-support/CHANGELOG.md)
* [relaxed-caps-plugin](packages/relaxed-caps-plugin/CHANGELOG.md)
@@ -31,6 +32,5 @@ Appium contains many different modules and supports a variety of drivers, plugin
* [support](packages/support/CHANGELOG.md)
* [test-support](packages/test-support/CHANGELOG.md)
* [tsconfig](packages/tsconfig/CHANGELOG.md)
* [typedoc-plugin-appium](packages/typedoc-plugin-appium/CHANGELOG.md)
* [types](packages/types/CHANGELOG.md)
* [universal-xml-plugin](packages/universal-xml-plugin/CHANGELOG.md)
+126 -48
View File
@@ -15,12 +15,8 @@ The project is officially led by a Technical Committee (TC), currently
consisting of:
* [@jlipps](https://github.com/jlipps)
* [@bootstraponline](https://github.com/bootstraponline)
* [@penguinho](https://github.com/penguinho)
* [@dandoveralba](https://github.com/dandoveralba)
* [@mykola-mokhnach](https://github.com/mykola-mokhnach)
* [@imurchie](https://github.com/imurchie)
* [@TikhomirovSergey](https://github.com/TikhomirovSergey)
* [@eglitise](https://github.com/eglitise)
* [@KazuCocoa](https://github.com/KazuCocoa)
They all represent different companies and different types of contribution to
@@ -31,7 +27,7 @@ changes to the project's governance processes.
* Membership in the TC is granted by unanimous vote of current TC members.
* TC members can step down at any time.
* No more than 1/3 of TC members may belong to the same company.
* No more than 1/3 (or 2, whichever is higher) of TC members may belong to the same company.
* Changes to project governance require unanimous vote of the TC.
### Project Committers
@@ -50,55 +46,105 @@ and so on.
* Any committer can decide to close a PR or issue if they determine the change doesn't suit the project.
* Appium has a large ecosystem of repositories. Some are 'core' in the sense of being central subpackages with a lot of traffic like `appium/appium` or `appium/appium-ios-driver`. Others are 'peripheral' in the sense of receiving few changes, being maintained by 1 person or no one, etc..., like `appium/ruby_lib` or `appium/appium_thor` For 'core' repos, committers should not merge their own code straightaway. Like all contributors they should open a PR and get a +1 from another committer. For 'peripheral' repos for which they are the sole maintainer or for which they have a good understanding, it is obtuse to insist on another contributor's +1 and this rule should be relaxed in that case. In sum: use good judgment and don't ram code through without a review when you can help it.
The current committers are (in addition to the TC members above):
The current committers are (along with their primary areas(s) of ownership or maintenance):
* [@hugs](https://github.com/hugs)
* [@filmaj](https://github.com/filmaj)
* [@aluedeke](https://github.com/aluedeke)
* [@sebv](https://github.com/sebv)
* [@vgrigoruk](https://github.com/vgrigoruk)
* [@DylanLacey](https://github.com/DylanLacey)
* [@maudineormsby](https://github.com/maudineormsby)
* [@titusfortner](https://github.com/titusfortner)
* [@bayandin](https://github.com/bayandin)
* [@jdeff](https://github.com/jdeff)
* [@moizjv](https://github.com/moizjv)
* [@gempesaw](https://github.com/gempesaw)
* [@Jonahss](https://github.com/Jonahss)
* [@JamieEdge](https://github.com/JamieEdge)
* [@saikrishna321](https://github.com/saikrishna321)
* [@sravanmedarapu](https://github.com/sravanmedarapu)
* [@peepa](https://github.com/peepa)
* [@scottdixon](https://github.com/scottdixon)
* [@drpy](https://github.com/drpy)
* [@Astro03](https://github.com/Astro03)
* [@sbonebrake](https://github.com/sbonebrake)
* [@SrinivasanTarget](https://github.com/SrinivasanTarget)
* [@triager](https://github.com/triager)
* [@ddkjin](https://github.com/ddkjin)
* [@stuartbrussell-intuit](https://github.com/stuartbrussell-intuit)
* [@vmaxim](https://github.com/vmaxim)
* [@Dan-Maor](https://github.com/Dan-Maor)
* [@laolubenson](https://github.com/laolubenson)
* [@Dor-bl](https://github.com/Dor-bl)
* [@boneskull](https://github.com/boneskull)
* [@mwakizaka](https://github.com/mwakizaka)
* [@rerorero](https://github.com/rerorero)
* [@tomriddly](https://github.com/tomriddly)
* [@eglitise](https://github.com/eglitise)
* [@dan-maor](https://github.com/Dan-Maor) - XCUITest driver and iOS internals
* [@dor-bl](https://github.com/Dor-bl) - DotNet client
* [@eglitise](https://github.com/eglitise) - Appium server, Inspector
* [@jlipps](https://github.com/jlipps) - Appium server, Inspector
* [@kazucocoa](https://github.com/KazuCocoa) - all Appium projects
* [@laolubenson](https://github.com/laolubenson) - DotNet client
* [@mwakizaka](https://github.com/mwakizaka) - XCUITest driver
* [@mykola-mokhnach](https://github.com/mykola-mokhnach) - all Appium projects
* [@rerorero](https://github.com/rerorero) - XCUITest/UIA2 driver
* [@saikrishna321](https://github.com/saikrishna321) - Java client
* [@srinivasantarget](https://github.com/SrinivasanTarget) - Java client
* [@tomriddly](https://github.com/tomriddly) - appium-ios-device
* [@valfirst](https://github.com/valfirst) - Java client
### Contributors
See [Emeritus Contributors](#emeritus-contributors) below for a list of people who formerly made
significant contributions to the project but are no longer actively committing.
### Other Contributors
Other, less formal, kinds of contribution are outlined in our
[CONTRIBUTING](/CONTRIBUTING.md) doc.
[CONTRIBUTING](/CONTRIBUTING.md) doc. We also very much appreciate those who spend their valuable
time assisting with the [Appium forums](https://discuss.appium.io). Current high-level forum
contributors and mods include:
* [aleksei](https://discuss.appium.io/u/aleksei/summary)
* [wreed](https://discuss.appium.io/u/wreed/summary)
### Sponsorship
The Appium project welcomes sponsorship of various organizations. Specific
details are not yet available but in general we will accept monetary donations,
contribution of code, contribution of dedicated committers, or donations of
other goods or service (internet hosting, etc...). To begin the conversation,
reach out to one of the TC members.
The Appium project welcomes the sponsorship of individuals and organizations. There are two types
of sponsorship: those related to contributions of Appium maintenance and leadership, and those
related to contributions of funds. Both are extremely important to the ongoing health of the
project. Financial sponsorship is mediated through our [OpenCollective page](https://opencollective.com/appium).
Sponsorship tiers and benefits are described below:
- **Development Partners**: The primary need of any open source project is for sustained maintenance and contributorship. Companies that officially sponsor such development with dedicated employee time are recognized as Development Partners, and receive the same benefits as the highest tier of financial sponsorship. To be considered a Development Partner, a company must devote at least 50% of a full-time employee's time to Appium maintenance. This means that the contributors designated by the company must have been appointed as "committers" in good standing of one or more core Appium repositories. (See above for a description of how to become a committer).
- **Strategic Partners**: Representing the highest tier of ongoing financial support for the project, Strategic Partners are what make our compensation scheme for contributors possible, and we thank them for their significant investment into the project! More than financial contribution, Strategic Partners help elevate Appium's position in the industry through marketing and evangelism efforts, and partner with the Appium team to help make Appium better. Along with the Development Partners, Strategic Partners have their logos displayed prominently on our website, and have the ability to link to their Appium-related products in a few relevant places in our documentation.
- **Gold, Silver, and Bronze Sponsors**: these sponsorship levels correspond to different amounts of recurring financial contribution that generously support our contributor compensation scheme. Sponsors at these levels also have the opportunity to display their logo on our website.
- **Backers**: individuals who benefit from the Appium project can also donate on a one-time or recurring basis, at any amount, and will be recognized as Backers for their contribution!
To become a sponsor at one of these levels, follow the process indicated at the [OpenCollective
page](https://opencollective.com/appium).
### Compensation Scheme
With funds received through sponsorship, the Appium project wants to incentivize contributions of
code, documentation efforts, maintenance, and project leadership. We have developed a scheme
(inspired by one developed at the [WebdriverIO](https://github.com/webdriverio/webdriverio)
project) for disbursing funds on a monthly basis to those who contribute to the project, as well as
some other "upstream" open source projects.
Each month, 15% of sponsorship funds received will be donated to upstream projects, based on the
discretion of the current TC. At this point, no attempt will be made to formalize quantitative
notions of relevance. If you manage a project upstream of Appium and would like to be considered as
part of this donation group, give us a shout!
Of the remaining 85% of sponsorship funds, 70% (roughly 60% of the original total) will go to
project Committers (i.e., those in a role of maintenance), and 30% (roughly 25% of the original
total) will go to Contributors (i.e., those making one-off or periodic contributions). The way that
Committers and Contributors are paid for their efforts differs.
For Committers, each Committer will keep a monthly time sheet of hours contributed to the project.
At the end of each month, the hours tracked from all Committers will be totalled, and then the
Committer-allocated funds will be disbursed (via OpenCollective) on a pro rata basis. Within this
scheme, Committers agree not to submit hours worked as part of their employment with a Development
Partner (since this is part of the "sponsorship" contribution of the Development Partner).
For Contributors, when they make contributions which are successfully merged into the project,
a Committer can discretionarily assign a "value tier" to the contribution. The "value tiers" and
corresponding payouts are as follows:
- XS: a trivial change (e.g. a typo change or applying of an automatically suggested security
patch). \$0.
- S: a small but useful change. \$25.
- M: a medium-small or slightly more complex change. \$50.
- L: a very valuable or sizable change. \$100.
- XL: a massive change or set of changes. \$500.
At the end of each month, Committers will total up the number of changes receiving potential
payout. If the total amount of funds allocated for Contributor payout exceeds the total amount to
be paid, then each Contributor will receive an OpenCollective link to claim their funds. If not
enough funds exist to cover all payouts, then the payout amounts will be scaled down in a pro rata
fashion.
The scope for Committer or Contributor payouts is work completed within any "core" Appium
repository (i.e., those officially maintained by the Appium team, residing within the Appium
organization on GitHub, under `https://github.com/appium`).
Some disclaimers: Payments will happen exclusively via OpenCollective. Recipients of funds are
responsible for maintaining their OpenCollective account in a fashion where funds can be disbursed,
and are responsible for any and all tax, financial, or legal consequences of receiving money in
this way. Receipt of funds from this scheme holds the Appium project and the OpenJS Foundation free
of any claim or obligation. While this document exists to clearly and publicly lay out the terms
under which payments are generally made, the TC retains the right to grant or withhold funding on
a discretionary of special basis. Basically, this scheme is run at the project's discretion, and is
not a public service. Making contributions to the project under this scheme does not obligate the
project to pay you for such work, or otherwise compensate you in any way.
### Raising Issues Related to Governance
@@ -106,3 +152,35 @@ This governance model necessarily leaves many situations unspecified. If
questions arise as to how a given situation should proceed according to the
overall goals of the project, the best thing to do is to open a GitHub issue
and ping the TC members.
### Emeritus Contributors
* [@aluedeke](https://github.com/aluedeke)
* [@astro03](https://github.com/Astro03)
* [@bayandin](https://github.com/bayandin)
* [@boneskull](https://github.com/boneskull)
* [@bootstraponline](https://github.com/bootstraponline)
* [@dandoveralba](https://github.com/dandoveralba)
* [@ddkjin](https://github.com/ddkjin)
* [@drpy](https://github.com/drpy)
* [@dylanLacey](https://github.com/DylanLacey)
* [@filmaj](https://github.com/filmaj)
* [@gempesaw](https://github.com/gempesaw)
* [@hugs](https://github.com/hugs)
* [@imurchie](https://github.com/imurchie)
* [@jamieEdge](https://github.com/JamieEdge)
* [@jdeff](https://github.com/jdeff)
* [@jonahss](https://github.com/Jonahss)
* [@maudineormsby](https://github.com/maudineormsby)
* [@moizjv](https://github.com/moizjv)
* [@peepa](https://github.com/peepa)
* [@penguinho](https://github.com/penguinho)
* [@sbonebrake](https://github.com/sbonebrake)
* [@scottdixon](https://github.com/scottdixon)
* [@sebv](https://github.com/sebv)
* [@sravanmedarapu](https://github.com/sravanmedarapu)
* [@stuartbrussell-intuit](https://github.com/stuartbrussell-intuit)
* [@tikhomirovSergey](https://github.com/TikhomirovSergey)
* [@titusfortner](https://github.com/titusfortner)
* [@vgrigoruk](https://github.com/vgrigoruk)
* [@vmaxim](https://github.com/vmaxim)
+23
View File
@@ -178,8 +178,31 @@ cross-platform native app automation, for mobile and beyond!
If you are looking for a more comprehensive description of what this is all about, please read our
documentation on [How Does Appium Work?](https://appium.io/docs/en/latest/intro/appium/).
## Sponsors
Appium has a [Sponsorship Program](GOVERNANCE.md#sponsorship)! If you or your company uses Appium
and wants to give back financially to the project, we use these funds to [encourage development and
contributions](GOVERNANCE.md#compensation-scheme), as well as support other open source projects we
rely on. [Become a sponsor](https://opencollective.com/appium) via our OpenCollective page.
### Development Partners
Appium is incredibly grateful to our Development Partners for their sustained contribution of
project development and leadership!
<p align="center">
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation"><img src="packages/appium/docs/assets/images/sponsor-logo-headspin.png"
width="300" alt="HeadSpin" /></a>
</p>
### Other Sponsors
A full list of sponsors is available at our [Sponsors page](SPONSORS.md).
## License
[Apache-2.0](./LICENSE)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
`@appium/logger` package is under [ISC](./packages/logger/LICENSE) License.
+41
View File
@@ -0,0 +1,41 @@
# Sponsors & Backers
Appium is an Apache-2 licensed open source project whose development is made possible entirely by
donations of time and money by individuals and companies who benefit from our software and our
mission. If you'd like to join this amazing group and help ensure Appium's continued development
and maintenance, you can contribute via [Appium's OpenCollective
Hub](https://opencollective.com/appium).
## Development Partners
We are very grateful for the support of our Development Partners, who donate Appium development and
maintenance as part of their employees' core job duties!
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation" target="_blank"><img
src="packages/appium/docs/assets/images/sponsor-logo-headspin.png" width="250"/></a>
## Strategic Partners
We are very grateful for the financial and marketing support of our exclusive Strategic Partners,
who contribute a significant ongoing investment of funds to help the project attract and reward
contributors!
* *Coming Soon!*
## Gold Sponsors
<a href="https://opencollective.com/appium" target="_blank"><img src="https://opencollective.com/appium/tiers/gold-sponsor.svg?avatarHeight=100&button=false"/></a>
[Become a Gold Sponsor](https://opencollective.com/appium/contribute/gold-sponsor-72877/checkout?interval=month&amount=500&contributeAs=me)
## Silver Sponsors
<a href="https://opencollective.com/appium" target="_blank"><img src="https://opencollective.com/appium/tiers/silver-sponsor.svg?avatarHeight=80&button=false"/></a>
[Become a Silver Sponsor](https://opencollective.com/appium/contribute/silver-sponsor-72876/checkout?interval=month&amount=250&contributeAs=me)
## Bronze Sponsors
<a href="https://opencollective.com/appium" target="_blank"><img src="https://opencollective.com/appium/tiers/sponsors.svg?avatarHeight=60&button=false"/></a>
[Become a Bronze Sponsor](https://opencollective.com/appium/contribute/sponsors-70690/checkout?interval=month&amount=100&contributeAs=me)
+1402 -1287
View File
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -82,11 +82,11 @@
"devDependencies": {
"@colors/colors": "1.6.0",
"@tsconfig/node14": "14.1.2",
"@types/chai": "4.3.14",
"@types/chai": "4.3.16",
"@types/chai-as-promised": "7.1.8",
"@types/diff": "5.2.0",
"@types/diff": "5.2.1",
"@types/mocha": "10.0.6",
"@types/node": "20.12.7",
"@types/node": "20.14.2",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/sinon-chai": "3.2.12",
@@ -94,11 +94,11 @@
"@types/teen_process": "2.0.4",
"@types/ws": "8.5.10",
"@types/yargs": "17.0.32",
"@typescript-eslint/eslint-plugin": "7.7.0",
"@typescript-eslint/parser": "7.7.0",
"@typescript-eslint/eslint-plugin": "7.12.0",
"@typescript-eslint/parser": "7.12.0",
"asyncbox": "3.0.0",
"chai": "4.4.1",
"chai-as-promised": "7.1.1",
"chai-as-promised": "7.1.2",
"chai-webdriverio-async": "3.0.0",
"conventional-changelog-conventionalcommits": "7.0.2",
"cpy-cli": "5.0.0",
@@ -108,24 +108,24 @@
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-mocha": "10.4.3",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-promise": "6.2.0",
"fancy-log": "2.0.0",
"finalhandler": "1.2.0",
"get-port": "5.1.1",
"json-schema-to-typescript": "14.0.0",
"lerna": "8.1.2",
"json-schema-to-typescript": "14.0.5",
"lerna": "8.1.3",
"log-symbols": "4.1.0",
"midnight-smoker": "8.0.0",
"mjpeg-consumer": "2.0.0",
"mjpeg-server": "0.3.1",
"mocha": "10.4.0",
"mocha-chai-jest-snapshot": "1.1.4",
"npm-run-all2": "6.1.2",
"prettier": "3.2.5",
"npm-run-all2": "6.2.0",
"prettier": "3.3.1",
"rewiremock": "3.14.5",
"rimraf": "5.0.5",
"rimraf": "5.0.7",
"serve-static": "1.15.0",
"sinon": "17.0.1",
"sinon": "18.0.0",
"sinon-chai": "3.7.0",
"sync-monorepo-packages": "1.0.2",
"through2": "4.0.2",
@@ -133,8 +133,8 @@
"tsd": "0.31.0",
"typescript": "5.4.5",
"validate.js": "0.13.1",
"webdriverio": "8.36.1",
"ws": "8.16.0",
"webdriverio": "8.38.2",
"ws": "8.17.0",
"yaml-js": "0.3.1"
},
"engines": {
@@ -146,10 +146,10 @@
},
"overrides": {
"@sidvind/better-ajv-errors": {
"ajv": "8.12.0"
"ajv": "8.16.0"
},
"ajv-formats": {
"ajv": "8.12.0"
"ajv": "8.16.0"
},
"http-cache-semantics": "4.1.1"
}
+49
View File
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.7.0](https://github.com/appium/appium/compare/appium@2.6.0...appium@2.7.0) (2024-06-06)
### Features
* **appium:** Replace npmlog with the local fork ([#20190](https://github.com/appium/appium/issues/20190)) ([8915934](https://github.com/appium/appium/commit/8915934270243bfb46c4d104a098ce1cc481b0ff))
* **base-driver:** Make addition of search query params to cache optional ([#20195](https://github.com/appium/appium/issues/20195)) ([21316e9](https://github.com/appium/appium/commit/21316e94d05b2f97aa0349a82da229713e92446c))
### Bug Fixes
* **appium:** ensure ws close code is valid ([#20178](https://github.com/appium/appium/issues/20178)) ([65c47ac](https://github.com/appium/appium/commit/65c47acf5954bbf1947e896ef79c81a9b3c712a5))
* **appium:** update dependency ajv to v8.16.0 ([ae7f74a](https://github.com/appium/appium/commit/ae7f74a0144d233653443622a84d49b1cb4e6222))
* **docutils:** update dependency yaml to v2.4.3 ([848eb00](https://github.com/appium/appium/commit/848eb00afbd1cab76a6a1c3d0f4a2a0b1acee0d0))
* **execute-driver-plugin:** update dependency webdriverio to v8.38.2 ([c8f9979](https://github.com/appium/appium/commit/c8f997973cc21673f95d02c92d43a2981b8713b8))
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
* **types:** update dependency type-fest to v4.19.0 ([#20193](https://github.com/appium/appium/issues/20193)) ([db62358](https://github.com/appium/appium/commit/db62358dbac25de2a75fdc7493338d98b1422c84))
## [2.6.0](https://github.com/appium/appium/compare/appium@2.5.4...appium@2.6.0) (2024-05-27)
### Features
* **appium:** Add --show-debug-info CLI argument ([#20108](https://github.com/appium/appium/issues/20108)) ([6f602d3](https://github.com/appium/appium/commit/6f602d3e501dbd9d44f6e7ba220f45ff1d4dfde8))
* **appium:** prepare setup subcommand as shortcut for drivers/plugins installation ([#20102](https://github.com/appium/appium/issues/20102)) ([2b2fb18](https://github.com/appium/appium/commit/2b2fb185a6bc882717e4aa0580b0136bf5ae2942))
* **base-driver:** Add env variables to control applications cache ([#20042](https://github.com/appium/appium/issues/20042)) ([4e8c91c](https://github.com/appium/appium/commit/4e8c91c8f647e545344d5b49282628413e1ccb19))
### Bug Fixes
* **appium:** Do not crash the process if there was an exception in a winston transport ([#20065](https://github.com/appium/appium/issues/20065)) ([0492fe4](https://github.com/appium/appium/commit/0492fe49dc34b3ef345a988bf56e49daf8dfcffe))
* **appium:** update dependency ajv to v8.13.0 ([448e94e](https://github.com/appium/appium/commit/448e94ee50fa127ae93e657a3958726aa13e7df8))
* **appium:** update dependency ajv to v8.14.0 ([e3dcd66](https://github.com/appium/appium/commit/e3dcd66714be603a8acbb3364f538c60772e76bd))
* **appium:** update dependency ws to v8.17.0 ([395dc22](https://github.com/appium/appium/commit/395dc225e4d7f47dc55134fba9fb9306cabf7e19))
* **docutils:** update dependency yaml to v2.4.2 ([0211174](https://github.com/appium/appium/commit/0211174300cf91c1cad2364d8ce3de7100139f5c))
* **execute-driver-plugin:** update dependency webdriverio to v8.37.0 ([27ff9a0](https://github.com/appium/appium/commit/27ff9a00ef65a66aea8da8e3be5ffebb6d8c65e1))
* **execute-driver-plugin:** update dependency webdriverio to v8.38.0 ([e80b682](https://github.com/appium/appium/commit/e80b6826efbe14ecfa6c0ff1f6aceb4db99b82d8))
* **support:** update dependency axios to v1.7.1 ([11510cb](https://github.com/appium/appium/commit/11510cb1a5d50a20ced884e5404d0be1e04ff142))
* **support:** update dependency axios to v1.7.2 ([a876f11](https://github.com/appium/appium/commit/a876f112b51dd25f70094b9e75330b9558050e42))
* **support:** update dependency semver to v7.6.1 ([b5fd5b6](https://github.com/appium/appium/commit/b5fd5b6d2c585be8391b80ac67207da610e8ea64))
* **support:** update dependency semver to v7.6.2 ([1b4f36c](https://github.com/appium/appium/commit/1b4f36cebe7aaf99976f24346b2d1325b79fc55c))
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
* **types:** update dependency type-fest to v4.18.2 ([#20103](https://github.com/appium/appium/issues/20103)) ([3b43be1](https://github.com/appium/appium/commit/3b43be17321f2ca16bac0abedabf9ef8cffa098a))
* **types:** update dependency type-fest to v4.18.3 ([#20149](https://github.com/appium/appium/issues/20149)) ([d5369f8](https://github.com/appium/appium/commit/d5369f8b08e7439282c5a211e684b154cc9f0051))
## [2.5.4](https://github.com/appium/appium/compare/appium@2.5.3...appium@2.5.4) (2024-04-21)
+23
View File
@@ -178,8 +178,31 @@ cross-platform native app automation, for mobile and beyond!
If you are looking for a more comprehensive description of what this is all about, please read our
documentation on [How Does Appium Work?](https://appium.io/docs/en/latest/intro/appium/).
## Sponsors
Appium has a [Sponsorship Program](GOVERNANCE.md#sponsorship)! If you or your company uses Appium
and wants to give back financially to the project, we use these funds to [encourage development and
contributions](GOVERNANCE.md#compensation-scheme), as well as support other open source projects we
rely on. [Become a sponsor](https://opencollective.com/appium) via our OpenCollective page.
### Development Partners
Appium is incredibly grateful to our Development Partners for their sustained contribution of
project development and leadership!
<p align="center">
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation"><img src="packages/appium/docs/assets/images/sponsor-logo-headspin.png"
width="300" alt="HeadSpin" /></a>
</p>
### Other Sponsors
A full list of sponsors is available at our [Sponsors page](SPONSORS.md).
## License
[Apache-2.0](./LICENSE)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
`@appium/logger` package is under [ISC](./packages/logger/LICENSE) License.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -4,3 +4,15 @@
.md-source__fact:nth-child(1n + 2):before {
margin-left: 0 !important;
}
.appium-sponsor-thanks {
width: 100%;
margin: auto 0.6rem;
display: flex;
align-items: center;
}
.appium-sponsor-thanks img {
margin: auto 6px;
height: 50px;
}
+1
View File
@@ -8,6 +8,7 @@ copyright: Copyright &copy; 2012 OpenJS Foundation - <a href="#__consent">Change
theme:
logo: assets/images/appium-logo-white.png
favicon: assets/images/appium-logo.png
custom_dir: overrides
plugins:
blog:
@@ -0,0 +1,47 @@
---
authors:
- jlipps
date: 2024-06-06
---
# Announcing HeadSpin as Appium's Development Partner
Recently, we announced a new [sponsorship and contributor compensation program](https://appium.io/docs/en/latest/blog/2024/05/14/announcing-appiums-sponsorship-program/) for the Appium
project. As part of this program, we felt it was important to have a place to recognize those
companies who make an extremely valuable kind of contribution to the project: dedicated maintainer
time. Appium only survives because of the tireless efforts of the core maintainers, and some of us
are only able to do this work because it is donated on behalf of our day jobs. We're therefore very
excited to announce our first Development Partner: <a
href="https://www.headspin.io/solutions/appium-mobile-test-automation">HeadSpin</a>!
<!-- more -->
<div style="text-align:center;width:100%">
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation"><img
src="/docs/en/assets/images/sponsor-logo-headspin.png" style="width: 300px"/></a>
</div>
HeadSpin is a real device performance and UX analysis cloud, with full support for Appium. When you
run your Appium tests on HeadSpin, you get access to comprehensive performance reports about your
app, including potential network, system resource, or user experience issues. HeadSpin supports
a variety of complex testing modes, including hooking up your own devices to the cloud, injecting
and receiving video and audio to/from devices, working with media (streaming) platforms, and more.
HeadSpin has been one of the Appium project's biggest supporters for a long time, and currently
employs two of Appium's core maintainers, donating a significant portion of their time towards the
development, maintenance, support, and leadership of open source Appium repos. HeadSpin has also
worked hard towards Appium's vision of a robust Appium ecosystem, contributing several
HeadSpin-maintained Appium drivers and plugins to the community:
- A [Roku TV driver](https://github.com/headspinio/appium-roku-driver)
- A [Tizen TV driver](https://github.com/headspinio/appium-tizen-tv-driver/)
- A [LG webOS TV driver](https://github.com/headspinio/appium-lg-webos-driver/)
- A [Unity game automation plugin](https://github.com/headspinio/appium-altunity-plugin)
These are all free and open source Appium components that HeadSpin has donated to the community
with the goal of fostering collaboration in these areas of media app automation.
Looking to the future, HeadSpin plans to continue supporting Appium in significant ways, both as
a Development Partner and a significant contributor to the Appium ecosystem. The Appium project is
deeply indebted to HeadSpin for all of its support, and is proud to take this moment to recognize
HeadSpin as our first official Development Partner!
@@ -0,0 +1,48 @@
---
authors:
- jlipps
date: 2024-05-14
---
# Announcing Appium's Sponsorship Program
Over the last several months, the Appium team has been working on a sponsorship program that is
designed to help give back to Appium's contributors as well as to recognize important partner
individuals or companies in the industry. This program is now officially launching! Let's dive into
some of the details.
<!-- more -->
First of all, the details and fine print will always be available at Appium's [Governance
document](https://github.com/appium/appium/blob/master/GOVERNANCE.md), which outlines how the
project is run, who makes project decisions, etc... This document will be kept up to date with the
latest details of the program, so that's the place to bookmark for reference!
As of now, Appium has opened up sponsorship opportunities via our [OpenCollective
page](https://opencollective.com/appium). OpenCollective is a service which allows people to donate
to open source groups. We've defined a number of giving tiers for donations, within a very wide
range, and welcome any financial amount with gratitude! Based on the donation tier, sponsorship
comes with a variety of benefits, including recognition at different places in the Appium repo,
docs, and website. In addition to financial sponsorship, the project has decided to recognize
significant donations of project leadership and maintenance efforts from companies who have
invested heavily into Appium with their employees' time (we call these "Development Partners").
What will the project do with the funds raised through this sponsorship program? We aim to give it
all back to the Appium community! With the details again available at our [Governance document](https://github.com/appium/appium/blob/master/GOVERNANCE.md),
we have implemented a scheme very much inspired by our friends at [WebdriverIO](https://webdriver.io/blog/2024/02/15/new-contributor-stipend-program) (thanks WDIO
friends!) There are three groups of contributors to Appium that this scheme supports: (1) project
maintainers (those who devote consistent time to leading the project), (2) project contributors
(anyone in the wider community that contributes code or documentation), and (3) upstream projects
(other open source projects Appium relies heavily on to work well). Each month, the funds raised
via sponsorship will be distributed in different amounts to each of these groups. So if you
make a valuable contribution to Appium moving forward, you'll be eligible to get some cash as
a thank-you! (You'll need to have your own OpenCollective account in good standing to receive
funds).
We're very excited about this new program, and hope that it helps existing and new contributors to
feel even better about donating their time to making Appium better. And of course, none of this
would be possible without the amazing Partners and Sponsors we hope to attract! On that note, stay
tuned over the next few weeks as we unveil some of our initial program launch Partners! And if you
or your company would like to participate by supporting Appium financially, you are most welcome.
Just head over to our OpenCollective page and choose the tier that reflects your desired
contribution level.
+15 -4
View File
@@ -27,10 +27,12 @@ below.
|`--base-path`|Base path to use as the prefix for all webdriver routes running on the server|string|`""`|`-pa`|
|`--callback-address`|Callback IP address (default: same as `--address`)|string||`-ca`|
|`--callback-port`|Callback port (default: same as `--port`) (Value must be between `1` and `65535`)|integer|`4723`|`-cp`|
|`--config`|Path to an [Appium configuration JSON file](../guides/config.md)|string|||
|`--debug-log-spacing`|Add exaggerated spacing in logs to help with visual inspection|boolean|`false`||
|`--default-capabilities`|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.|object||`-dc`|
|`--deny-insecure`|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.|array<string>|`[]`||
|`--driver`|Driver-specific configuration. Keys should correspond to driver package names|object|||
|`--drivers-import-chunk-size`|The maximum amount of drivers that could be imported in parallel on server startup|number|`3`||
|`--keep-alive-timeout`|Number of seconds the Appium server should apply as both the keep-alive timeout and the connection timeout for all requests. Setting this to `0` disables the timeout.|integer|`600`|`-ka`|
|`--local-timezone`|Use local timezone for timestamps|boolean|`false`||
|`--log`|Also send log output to this file|string||`-g`|
@@ -38,12 +40,11 @@ below.
|`--log-level`|Log level (console[:file]) (Value must be one of: `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`)|string|`debug`||
|`--log-no-colors`|Do not use color in console output|boolean|`false`||
|`--log-timestamp`|Show timestamps in console output|boolean|`false`||
|`--plugins-import-chunk-size`|The maximum amount of plugins that could be imported in parallel on server startup|number|`7`||
|`--drivers-import-chunk-size`|The maximum amount of drivers that could be imported in parallel on server startup|number|`3`||
|`--long-stacktrace`|Add long stack traces to log entries. Recommended for debugging only.|boolean|`false`||
|`--no-perms-check`|Skip various permission checks on the server startup if set to true|boolean|`false`||
|`--nodeconfig`|Path to configuration JSON file to register Appium as a node with Selenium Grid 3; otherwise the configuration itself|object|||
|`--no-perms-check`|Skip various permission checks on the server startup|boolean|`false`||
|`--nodeconfig`|Path to configuration JSON file to register Appium as a node with Selenium Grid 3; otherwise the configuration itself|string|||
|`--plugin`|Plugin-specific configuration. Keys should correspond to plugin package names|object|||
|`--plugins-import-chunk-size`|The maximum amount of plugins that could be imported in parallel on server startup|number|`7`||
|`--port`|Port to listen on (Value must be between `1` and `65535`)|integer|`4723`|`-p`|
|`--relaxed-security`|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`|boolean|`false`||
|`--session-override`|Enables session override (clobbering)|boolean|`false`||
@@ -55,3 +56,13 @@ below.
|`--use-drivers`|A list of drivers to activate. By default, all installed drivers will be activated.|array<string>|`[]`||
|`--use-plugins`|A list of plugins to activate. To activate all plugins, the value should be an array with a single item `"all"`.|array<string>|`[]`||
|`--webhook`|Also send log output to this http listener|string||`-G`|
The following arguments are used for information retrieval, after which the server will automatically exit. They are therefore meant for reference or debug purposes.
|<div style="width:10em">Argument</div>|Description|Alias|
|--|--|--|
|`--help`|Print instructions on using the Appium command-line. This argument can also be used for any Appium subcommands.|`-h`|
|`--show-build-info`|Print detailed information on the Appium server version||
|`--show-config`|Print the current Appium server configuration details||
|`--show-debug-info`|Print information on the current environment: details about the operating system, Node.js, and Appium itself||
|`--version`|Print the Appium server version|`-v`|
+5
View File
@@ -19,3 +19,8 @@ environment variables that the Appium server understands:
|`APPIUM_PORT`|Same as the `--port` CLI arg|
|`APPIUM_RELOAD_EXTENSIONS`|Set to `1` to cause Appium to re-require extensions when new sessions are created. This is mostly useful for [building extensions](../developing/build-drivers.md)|
|`APPIUM_OMIT_PEER_DEPS`|Adds `--omit=peer` to all the NPM commands run internally by Appium. Mostly an internal feature.|
|`APPIUM_APPS_CACHE_MAX_AGE`|Allows to set the maximum age (in minutes) for [cached applications](../guides/caching.md). The default value is `60 * 24` (24 hours). Do not set it to a lower number than the duration of a single session startup.|
|`APPIUM_APPS_CACHE_MAX_ITEMS`|Allows to set the maximum amount of [cached applications](../guides/caching.md). The default value is `1024`. Do not set it to a lower number than the amount of apps in all parallel sessions per process.|
|`APPIUM_APPS_CACHE_IGNORE_URL_QUERY`| If the above option is enabled then the 'search' part of the app URL will be cut off from cache keys. See the corresponding
[feature request](https://discuss.appium.io/t/regarding-app-caching-when-using-aws-s3-presigned-urls/42713)
for more details. Disabled by default.|
+52 -45
View File
@@ -30,28 +30,34 @@ All Extension CLI commands can take an optional `--json` argument, which will re
the command as a machine-readable JSON string rather than the standard output, which is colourized
and tuned for human consumption.
### `list`
### `doctor`
List installed and available extensions. "Available" extensions include those which are officially
recognized by the Appium team, but you are not limited to installing only the extensions displayed
in this list.
Run doctor checks for the given extension, which validate whether the extension has its prerequisites
configured correctly. Note that not all extensions include doctor checks. See the
[Building Doctor Checks](../developing/build-doctor-checks.md) tutorial for more details on
how to create them.
Usage:
```
appium <ext-type> list [--installed] [--updates] [--json]
appium <ext-type> doctor <ext-name>
```
Required arguments:
- `<ext-type>`: must be `driver` or `plugin`
- `<ext-name>`: the name of the extension whose doctor checks you want to run
Optional arguments:
- `--installed`: show only installed extensions, not installed plus available extensions
- `--updates`: for extensions installed via NPM, display a message if there are any updates
- `--json`: return the result in JSON format
Example (run doctor checks for the UiAutomator2 driver):
```
appium driver doctor uiautomator2
```
### `install`
Install an extension. If successful, respond with the short name of the extension which can be used
@@ -124,46 +130,26 @@ by `npm install`:
appium plugin install --source=local /path/to/my/plugin
```
### `update`
### `list`
Update one or more extensions that have been installed via NPM. By default, Appium will not
automatically update any extension that has a revision in its major version, so as to prevent
unintended breaking changes.
List installed and available extensions. "Available" extensions include those which are officially
recognized by the Appium team, but you are not limited to installing only the extensions displayed
in this list.
Usage:
```
appium <ext-type> update <ext-name> [--unsafe] [--json]
appium <ext-type> list [--installed] [--updates] [--json]
```
Required arguments:
- `<ext-type>`: must be `driver` or `plugin`
- `<ext-name>`: the name of the extension to update, or the string `installed` (which will update
all installed extensions)
Optional arguments:
- `--unsafe`: direct Appium to go ahead and update passed a major version boundary
- `--json`: return the result in JSON format
### `uninstall`
Remove an installed extension.
Usage:
```
appium <ext-type> uninstall <ext-name> [--json]
```
Required arguments:
- `<ext-type>`: must be `driver` or `plugin`
- `<ext-name>`: the name of the extension to uninstall
Optional arguments:
- `--installed`: show only installed extensions, not installed plus available extensions
- `--updates`: for extensions installed via NPM, display a message if there are any updates
- `--json`: return the result in JSON format
### `run`
@@ -187,7 +173,7 @@ Required arguments:
Optional arguments:
* `script-args`: any arguments that Appium does not interpret as belonging to its own set of
- `script-args`: any arguments that Appium does not interpret as belonging to its own set of
arguments will be passed along to the extension script
- `--json`: return the result in JSON format
@@ -197,23 +183,44 @@ Example (run the `reset` script included with the UiAutomator2 driver):
appium driver run uiautomator2 reset
```
### `doctor`
### `update`
Run doctor checks for the given extension if it has any. Extension authors can include
runnable scripts that assist with driver or plugin preconditions validations. These scripts
must be valid Node.js sources, must be listed in the package manifest, and must export
class instances that comply to the particular requirements.
See the [Building Doctor Checks](../developing/build-doctor-checks.md) tutorial for more
details on these requirements.
Update one or more extensions that have been installed via NPM. By default, Appium will not
automatically update any extension past a major version boundary, so as to prevent
unintended breaking changes.
Usage:
```
appium <ext-type> doctor <ext-name>
appium <ext-type> update <ext-name> [--unsafe] [--json]
```
Example (run doctor checks for the UiAutomator2 driver):
Required arguments:
- `<ext-type>`: must be `driver` or `plugin`
- `<ext-name>`: the name of the extension to update, or the string `installed` (which will update
all installed extensions)
Optional arguments:
- `--unsafe`: direct Appium to go ahead and update past a major version boundary
- `--json`: return the result in JSON format
### `uninstall`
Remove an installed extension.
Usage:
```
appium driver doctor uiautomator2
appium <ext-type> uninstall <ext-name> [--json]
```
Required arguments:
- `<ext-type>`: must be `driver` or `plugin`
- `<ext-name>`: the name of the extension to uninstall
Optional arguments:
- `--json`: return the result in JSON format
+5 -3
View File
@@ -11,14 +11,16 @@ the Appium server. This section of the Appium documentation describes how to use
To start off, you can run `appium -v` or `appium --version` to return the installed version,
or run `appium -h` or `appium --help` to return the help message.
The main `appium` executable provides 3 subcommands:
The main `appium` executable provides the following subcommands:
1. `appium server` (or simply `appium`) - launch an Appium server
- [See here for accepted arguments](./args.md)
- For advanced features, [see here for accepted environment variables](./env-vars.md)
2. `appium driver` - manage Appium drivers
- [See here for accepted arguments](./extensions.md)
- [See here for details](./extensions.md)
3. `appium plugin` - manage Appium plugins
- [See here for accepted arguments](./extensions.md)
- [See here for details](./extensions.md)
4. `appium setup` - batch install a preset of drivers and plugins
- [See here for details](./setup.md)
Like the main command, you can also run each subcommand with the `-h` or `--help` flag to learn more about it.
+28
View File
@@ -0,0 +1,28 @@
---
hide:
- toc
title: Setup Command-Line Usage
---
The `setup` command aims to simplify the initial procedure of setting up Appium. It allows to install
multiple extensions (drivers/plugins) in one go, without the need to run
`appium <ext-name> install <ext-name>` multiple times.
The command has several presets that can be used to install different sets of extensions.
The presets are as follows:
|Preset|Installation Command|Included Drivers|Included Plugins|
|--|--|--|--|
|Mobile|`appium setup mobile` or `appium setup`|`uiautomator2`, `xcuitest`[^1], `espresso`|`images`|
|Desktop application|`appium setup desktop`|`mac2`[^1], `windows`[^2]|`images`|
|Desktop browser|`appium setup browser`|`safari`[^1], `gecko`, `chromium`|`images`|
Attempting to install a preset while already having one or more of its included extensions installed
will only install the missing extensions.
Refer to the [Ecosystem documentation](../ecosystem/index.md) to learn more about the extensions
listed above.
[^1]: Only installed if the host machine is running macOS.
[^2]: Only installed if the host machine is running Windows.
+1 -1
View File
@@ -27,6 +27,7 @@ These drivers are currently maintained by the Appium team:
|[Mac2](https://github.com/appium/appium-mac2-driver)|`mac2`|macOS|Native|
|[Safari](https://github.com/appium/appium-safari-driver)|`safari`|macOS, iOS|Web|
|[UiAutomator2](https://github.com/appium/appium-uiautomator2-driver)|`uiautomator2`|Android|Native, Hybrid, Web|
|[Windows](https://github.com/appium/appium-windows-driver)|`windows`|Windows|Native|
|[XCUITest](https://github.com/appium/appium-xcuitest-driver)|`xcuitest`|iOS|Native, Hybrid, Web|
### Other Drivers
@@ -41,7 +42,6 @@ These drivers are not maintained by the Appium team and can be used to target ad
|[Roku](https://github.com/headspinio/appium-roku-driver)|`--source=npm @headspinio/appium-roku-driver`|Roku|Native|HeadSpin|
|[Tizen](https://github.com/Samsung/appium-tizen-driver)|`--source=npm appium-tizen-driver`|Android|Native|Community / Samsung|
|[TizenTV](https://github.com/headspinio/appium-tizen-tv-driver)|`--source=npm appium-tizen-tv-driver`|Samsung TV|Web|HeadSpin|
|[Windows](https://github.com/appium/appium-windows-driver)|`--source=npm appium-windows-driver`|Windows|Native|Community / Microsoft|
|[Youi](https://github.com/YOU-i-Labs/appium-youiengine-driver)|`--source=npm appium-youiengine-driver`|iOS, Android, macOS, Linux, tvOS|Native|Community / You.i|
!!! note
+12 -6
View File
@@ -8,7 +8,7 @@ title: Appium-Related Tools
There are several Appium tools that have been created to to assist with things not directly related
to testing, such as Appium installation, test development, and more.
### Appium Inspector
### [Appium Inspector](https://appium.github.io/appium-inspector/latest/)
Appium has a graphical client which can be used to inspect application screenshots, view the
application hierarchy, run Appium commands, record app interactions, and more. It is very useful
@@ -18,14 +18,20 @@ Find downloads and more information on its GitHub page: [Appium Inspector](https
### Appium Doctor
The `appium-doctor` CLI tool can be used to validate proper environment setup for Android and iOS
automation, and identify any common configuration issues. It can be installed with `npm`:
Appium Doctor is a command-line tool built into Appium drivers and plugins.
The command is expected to be used to validate whether a driver or plugin has all of its prerequisites and other environment details set up correctly
if the driver/plugin author implemented the `doctor` command.
```sh
npm install -g @appium/doctor
For example, `uiautomator2` driver provides the `doctor` command below.
```
appium driver doctor uiautomator2
```
Learn more on its GitHub page: [Appium Doctor](https://github.com/appium/appium/tree/master/packages/doctor)
It shows no result if the driver/plugin author did not implement them.
More information on this command can be found in the [Command-Line Usage documentation](../cli/extensions.md#doctor).
For driver/plugin developers, please read [Building Doctor Checks](../developing/build-doctor-checks.md).
### Other Tools
+25 -15
View File
@@ -16,7 +16,8 @@ bundle for each test.
Caching could be applied to application bundles generated by
[`configureApp`](https://github.com/appium/appium/blob/master/packages/base-driver/lib/basedriver/helpers.js#L107) helper call.
Inherited drivers can customize their caching logic by providing own `onPostProcess` property definition, but the general
Inherited drivers can customize their caching logic by providing own `onPostProcess`
(or both `inDownload` and `onPostProcess`) property definition, but the general
rule of thumb is that we need to cache locally all application bundles need to be downloaded and/or extracted
first before they could be actually installed on the device under test. On iOS, for example, these are `.ipa` or
`.zip` compressed application bundles, or .`aab` on Android.
@@ -26,18 +27,17 @@ first before they could be actually installed on the device under test. On iOS,
In order to validate whether an app bundle downloaded from the given URL could be (re)used from the cache the following
steps are applied:
1. The script is sending `HEAD` request to the given link in order to only fetch response headers. If this request fails/
times out then no caching is applied.
2. The following header values are being extracted: `Last-Modified`, `Cache-Control`. If no `Last-Modified` header is present
or the header value cannot be parsed as a valid datetime then no caching is applied.
3. The script checks if the given URL is already present in the cache. If the app is not cached yet then it gets downloaded
and its current header values along with hashsum are added to the cache.
4. If the URL was already in the cache then the script verifies that:
- the current `Last-Modified` datetime is not different from the previous one
- the difference between the current `Last-Modified` datetime and the cached one is not greater than max-age (if present)
- the hashsum of the cached file/folder is not changed (e.g. it was not corrupted while sitting in the cache)
5. If all validations above succeed then the cached build is returned, otherwise the currently cached entry gets
deleted and a new one is downloaded instead.
1. The script checks if the given URL is already present in the cache.
If yes then it tries to fetch previously remembered
[Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
or [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) header values for it.
2. If `ETag` value is present then it is put into
[If-None-Match request header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match).
Else if `Last-Modified` header value is present then it is put into
[If-Modified-Since request header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since).
Otherwise, no caching is applied.
3. If the response status is equal to `304` then the previously cached binary is used,
otherwise the cached entry is reset and refreshed.
## Caching of Local Application Bundles
@@ -55,9 +55,19 @@ The cache where the base driver keeps all application bundles is located in the
on per-process basis, so each test session initialized in scope of the same Appium server process takes advantages
of it. It is a [LRU Cache](https://www.npmjs.com/package/lru-cache) with the following limitations:
- Max items: 1024
- Max time to live (TTL) for each entry: 24 hours
- Max items: 1024. You may customize it by providing a new value to
the [APPIUM_APPS_CACHE_MAX_ITEMS](../cli/env-vars.md) environment variable.
Do not set it to a lower number than the amount of apps in all parallel sessions per process.
- Max time to live (TTL) for each entry: 24 hours.
You may customize it by providing a new value to the
[APPIUM_APPS_CACHE_MAX_AGE](../cli/env-vars.md) environment variable.
Do not set it to a lower number than the duration of a single session startup.
- TTL is refreshed for each entry upon access
- By default the full application URL is used as cache key. You may change this behavior
by enabling the [APPIUM_APPS_CACHE_IGNORE_URL_QUERY](../cli/env-vars.md) environment variable.
If the above option is enabled then the 'search' part of the app URL will be cut off from cache keys.
See the corresponding [feature request](https://discuss.appium.io/t/regarding-app-caching-when-using-aws-s3-presigned-urls/42713)
for more details.
!!! warning
+13 -1
View File
@@ -6,7 +6,8 @@ hide:
title: Welcome
---
<style>
.md-typeset h1 {
.md-typeset h1,
.appium-sponsor-thanks {
display: none;
}
</style>
@@ -19,6 +20,17 @@ software, designed to facilitate UI automation of many app platforms, including
Android, Tizen), browser (Chrome, Firefox, Safari), desktop (macOS, Windows), TV (Roku, tvOS,
Android TV, Samsung), and more!
<div style="text-align: center">
Appium is extremely grateful for the support of its key partners! (Learn more about our
sponsorship program and contributor compensation scheme
<a href="https://github.com/appium/appium/blob/master/GOVERNANCE.md#sponsorship">here</a>)
<br/>
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation">
<img src="assets/images/sponsor-logo-headspin.png" style="width: 200px;" />
</a>
</div>
## Explore the Documentation
<div class="grid cards" markdown>
+4 -4
View File
@@ -12,10 +12,10 @@ running Appium and writing Appium scripts.
The basic plan for this quickstart is as follows:
1. Install Appium
1. Install an Appium driver and its dependencies (we'll be using the [UiAutomator2
driver](https://github.com/appium/appium-uiautomator2-driver) for these examples)
1. Install an Appium client library in your language of choice (this guide contains options for
JavaScript, Python, Java, Ruby, and .NET).
1. Install an Appium driver and its dependencies
- This guide provides instructions for the [UiAutomator2 driver](https://github.com/appium/appium-uiautomator2-driver)
1. Install an Appium client library in your language of choice
- This guide contains options for JavaScript, Python, Java, Ruby, and .NET
1. Write and run a simple Appium automation script using a sample application
### Requirements
+21 -12
View File
@@ -9,33 +9,42 @@ title: Install Appium
Before installing, make sure to check the [System Requirements](./requirements.md).
You can install Appium globally using `npm`:
Appium can be installed globally using `npm`:
```bash
npm i -g appium
npm install -g appium
```
!!! note
Other package managers are not currently supported.
After installation, you should be able to run Appium from the command line:
## Starting Appium
Appium can be started [using the command line](../cli/index.md):
```
appium
```
You should see some output that starts with a line like this:
This launches the Appium server process, which loads all the installed Appium drivers, and
begins waiting for new session requests from client connections (such as test automation scripts).
Since the server process is independent from its clients, it must be explicitly launched before
attempting to start a new session.
When the server is launched, the console log will list all the valid URLs that clients can use to
connect to this server:
```
[Appium] Welcome to Appium v2.4.1
[Appium] You can provide the following URLs in your client code to connect to this server:
[Appium] http://127.0.0.1:4723/ (only accessible from the same host)
(... any other URLs ...)
```
In order to update Appium using `npm`:
Once a client requests a new session, the Appium server process will start logging all details about
this session until its termination. Keep this in mind - if you ever encounter issues with Appium
tests, you can always check the server log for more details.
```bash
npm update -g appium
```
That's it! If you see this, the Appium server is up and running. Go ahead and quit
it (Ctrl-C) and move on to the [next step](./uiauto2-driver.md), where we'll install a driver for automating Android apps.
So what's next? Even though Appium is installed and running, it does not come bundled with any
drivers, which means it cannot automate anything yet. We will therefore set up automation for
Android - continue to [Installing the UiAutomator2 Driver](./uiauto2-driver.md).
@@ -9,7 +9,7 @@ The basic requirements for the Appium server are:
* A macOS, Linux, or Windows operating system
* [Node.js](https://nodejs.org) version in the [SemVer](https://semver.org) range `^14.17.0 || ^16.13.0 || >=18.0.0`
* LTS is recommended
* LTS is recommended
* [`npm`](https://npmjs.com) version `>=8` (`npm` is usually bundled with Node.js, but can be upgraded
independently)
@@ -24,6 +24,6 @@ documentation of the [Appium driver(s)](../ecosystem/drivers.md) for that platfo
dependencies. It is almost universally the case that Appium drivers for a given platform will
require the developer toolchain and SDKs for that platform to be installed.
You may find it useful to use the `appium-doctor` tool for verifying if all driver requirements are
installed. It can check the prerequisites for Android and iOS drivers.
Learn more on its GitHub page: [Appium Doctor](https://github.com/appium/appium/tree/master/packages/doctor)
In order to assist with driver requirements, each (official) driver comes with the Appium Doctor tool,
which allows to verify if all requirements have been set up. Learn more about how to use this tool in
the [Command-Line Usage documentation](../cli/extensions.md#doctor).
@@ -60,8 +60,14 @@ set up on your system, so you can get busy making Android apps if you want!
## Install the driver itself
Since the UiAutomator2 driver is maintained by the core Appium team, it has an 'official' driver
name that you can use to install it easily via the [Appium Extension CLI](../cli/extensions.md):
### Standard Install
Like all Appium drivers, the UiAutomator2 driver is installed via the [Appium Extension CLI](../cli/extensions.md).
Since UiAutomator2 is maintained by the core Appium team, it has an 'official' driver name
(`uiautomator2`), which makes the installation simpler.
Before installing, make sure your Appium server is _not_ running (if it is, quit it with _Ctrl-C_).
Then run the following command:
```bash
appium driver install uiautomator2
@@ -77,11 +83,9 @@ Driver uiautomator2@2.0.5 successfully installed
- platformNames: ["Android"]
```
Running this command will locate and install the latest version of the UiAutomator2 driver, making
it available for automation. Note that when it is installed it tells you what platforms it is valid
for (in this case, `Android`), and what automation name (the `appium:automationName`
[capability](../guides/caps.md)) must be used to select this driver for use during an Appium
session (in this case, `UiAutomator2`).
Note how the installation process specifies what platforms is the driver valid for (in this case,
`Android`), and what automation name (the `appium:automationName` [capability](../guides/caps.md))
must be used to select this driver for use during an Appium session (in this case, `UiAutomator2`).
!!! note
@@ -90,6 +94,34 @@ session (in this case, `UiAutomator2`).
prefer to use `npm` to manage Appium and its connected drivers. To learn more about this
technique, visit the guide on [managing Appium extensions](../guides/managing-exts.md).
### Batch Install
You may want to use Appium with more than one driver. One way to accomplish this is to run
`appium driver install <driver-name>` for each individual driver, but you can also install multiple
drivers in one go:
```
appium setup
```
Running this will install Appium's mobile-specific drivers: UiAutomator2, [XCUITest](https://appium.github.io/appium-xcuitest-driver/)
(only if running macOS), and [Espresso](https://github.com/appium/appium-espresso-driver).
You can also use this command to batch install drivers for desktop applications or desktop browsers.
For more details on this, refer to the [Setup command documentation](../cli/setup.md).
### Validating the Install
The UiAutomator2 driver, like all official Appium drivers, comes with the Appium Doctor tool, which
allows validating whether all prerequisites have been set up correctly:
```
appium driver doctor uiautomator2
```
This guide has focused on essential requirements, so Appium Doctor may suggest one or more optional
fixes. But if you see `0 required fixes needed`, that means everything is set up!
Now, start the Appium server again (run `appium`), and you should see that the newly-installed
driver is listed as available:
+1
View File
@@ -39,6 +39,7 @@ nav:
- cli/args.md
- cli/env-vars.md
- cli/extensions.md
- cli/setup.md
- Command Reference:
- commands/index.md
- commands/base-driver.md
+4 -4
View File
@@ -9,12 +9,12 @@ theme:
nav:
- 首页: index.md
- 简介:
- 介绍 Appium:
- Appium 概述: intro/index.md
- intro/index.md
- 背景:
- intro/appium.md
- intro/drivers.md
- intro/clients.md
- intro/requirements.md
- 历史: intro/history.md
- intro/history.md
- 快速开始:
- quickstart/index.md
- quickstart/install.md
@@ -0,0 +1,79 @@
<!-- Footer -->
<footer class="md-footer">
<!-- Link to previous and/or next page -->
{% if "navigation.footer" in features %}
{% if page.previous_page or page.next_page %}
{% if page.meta and page.meta.hide %}
{% set hidden = "hidden" if "footer" in page.meta.hide %}
{% endif %}
<nav
class="md-footer__inner md-grid"
aria-label="{{ lang.t('footer') }}"
{{ hidden }}
>
<!-- Link to previous page -->
{% if page.previous_page %}
{% set direction = lang.t("footer.previous") %}
<a
href="{{ page.previous_page.url | url }}"
class="md-footer__link md-footer__link--prev"
aria-label="{{ direction }}: {{ page.previous_page.title | e }}"
>
<div class="md-footer__button md-icon">
{% set icon = config.theme.icon.previous or "material/arrow-left" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
{{ direction }}
</span>
<div class="md-ellipsis">
{{ page.previous_page.title }}
</div>
</div>
</a>
{% endif %}
<!-- Link to next page -->
{% if page.next_page %}
{% set direction = lang.t("footer.next") %}
<a
href="{{ page.next_page.url | url }}"
class="md-footer__link md-footer__link--next"
aria-label="{{ direction }}: {{ page.next_page.title | e }}"
>
<div class="md-footer__title">
<span class="md-footer__direction">
{{ direction }}
</span>
<div class="md-ellipsis">
{{ page.next_page.title }}
</div>
</div>
<div class="md-footer__button md-icon">
{% set icon = config.theme.icon.next or "material/arrow-right" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</div>
</a>
{% endif %}
</nav>
{% endif %}
{% endif %}
<!-- Further information -->
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<!-- Sponsor thanks -->
{% include "partials/sponsors.html" %}
{% include "partials/copyright.html" %}
<!-- Social links -->
{% if config.extra.social %}
{% include "partials/social.html" %}
{% endif %}
</div>
</div>
</footer>
@@ -0,0 +1,6 @@
<div class="appium-sponsor-thanks">
Supported by
<a href="https://www.headspin.io/solutions/appium-mobile-test-automation">
<img src="/docs/en/assets/images/sponsor-logo-headspin.png" />
</a>
</div>
+28 -2
View File
@@ -1,6 +1,32 @@
---
title: Appium 概述
hide:
- toc
title: Appium in a Nutshell
---
正如主页上提到的那样,Appium旨在支持许多不同平台(移动、网络、桌面等)的UI自动化。不仅如此,它还旨在支持用不同语言(JS、Java、Python等)编写的自动化代码。将所有这些功能结合到一个程序中是一项非常艰巨、甚至不可能的任务!
为了实现这一目标,Appium实际上被分为四个部分:
TODO
<div class="grid cards" markdown>
- :material-image-filter-center-focus-strong: __Appium Core__ - 定义核心API
- :material-car: __Drivers__ - 实现与特定平台的连接
- :octicons-code-16: __Clients__ - 实现与特定平台的连接
- :fontawesome-solid-plug: __Plugins__ - 更改或扩展Appium的核心功能
</div>
因此,为了开始使用Appium自动化某些内容,您需要:
- 安装Appium本身
- 为您的目标平台安装驱动程序
- 为您的目标编程语言安装客户端库
- (可选)安装一个或多个插件
这些都是基础!如果您准备好加入,请继续[快速入门](../quickstart/index.md)!
如果您想了解有关其运作方式的更多详细信息,请参阅以下页面了解背景材料:
- [Appium Core](./appium.md)
- [Appium Drivers](./drivers.md)
- [Appium Clients](./clients.md)
最后,要了解Appium的起源,请查看[Appium项目历史](./history.md)。
@@ -1,6 +0,0 @@
---
title: Appium Requirements
---
TODO
+15 -1
View File
@@ -21,6 +21,10 @@ import {getDefaultsForExtension} from './schema';
import {DRIVER_TYPE, BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
import WebSocket from 'ws';
const MIN_WS_CODE_VAL = 1000;
const MAX_WS_CODE_VAL = 1015;
const WS_FALLBACK_CODE = 1011; // server encountered an error while fulfilling request
const desiredCapabilityConstraints = /** @type {const} */ ({
automationName: {
presence: true,
@@ -418,6 +422,16 @@ class AppiumDriver extends DriverCore {
`Upstream bidi socket closed connection (code ${code}, reason: '${reason}'). ` +
`Closing proxy connection to client`,
);
if (!_.isNumber(code)) {
code = parseInt(code, 10);
}
if (_.isNaN(code) || code < MIN_WS_CODE_VAL || code > MAX_WS_CODE_VAL) {
this.log.warn(
`Received code ${code} from upstream socket, but this is not a valid ` +
`websocket code. Rewriting to ${WS_FALLBACK_CODE} for ws compatibility`,
);
code = WS_FALLBACK_CODE;
}
ws.close(code, reason);
});
@@ -1106,7 +1120,7 @@ class AppiumDriver extends DriverCore {
}
// here we know that we are executing a session command, and have a valid session driver
return await (/** @type {any} */ (dstSession)).executeCommand(cmd, ...args);
return await /** @type {any} */ (dstSession).executeCommand(cmd, ...args);
};
// now take our default behavior, wrap it with any number of plugin behaviors, and run it
+10
View File
@@ -290,6 +290,16 @@ const serverArgsDisallowedInConfig = new Map([
help: 'Show info about the Appium build and exit',
},
],
[
['--show-debug-info'],
{
dest: 'showDebugInfo',
action: 'store_const',
const: true,
required: false,
help: 'Show debug info about the current Appium deployment and exit',
},
],
[
['--show-config'],
{
+52 -2
View File
@@ -11,11 +11,19 @@ import {
EXT_SUBCOMMAND_UNINSTALL,
EXT_SUBCOMMAND_UPDATE,
PLUGIN_TYPE,
SERVER_SUBCOMMAND
SERVER_SUBCOMMAND,
SETUP_SUBCOMMAND
} from '../constants';
import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
import {rootDir} from '../config';
import {getExtensionArgs, getServerArgs} from './args';
import {
SUBCOMMAND_MOBILE,
SUBCOMMAND_DESKTOP,
SUBCOMMAND_BROWSER,
getPresetDrivers,
determinePlatformName
} from './setup-command';
export const EXTRA_ARGS = 'extraArgs';
@@ -24,7 +32,7 @@ export const EXTRA_ARGS = 'extraArgs';
* will automatially inject the `server` subcommand.
*/
const NON_SERVER_ARGS = Object.freeze(
new Set([DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version'])
new Set([SETUP_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version'])
);
const version = fs.readPackageJsonFrom(rootDir).version;
@@ -76,6 +84,9 @@ class ArgParser {
const subParsers = parser.add_subparsers({dest: 'subcommand'});
// add the 'setup' command
ArgParser._addSetupToParser(subParsers);
// add the 'server' subcommand, and store the raw arguments on the parser
// object as a way for other parts of the code to work with the arguments
// conceptually rather than just through argparse
@@ -280,6 +291,45 @@ class ArgParser {
}
}
}
/**
* Add subcommand and sub-sub commands for 'setup' subcommand.
* @param {import('argparse').SubParser} subParser
*/
static _addSetupToParser(subParser) {
const setupParser = subParser.add_parser('setup', {
add_help: true,
help: `Select a preset of official drivers/plugins to install ` +
`compatible with '${determinePlatformName()}' host platform. ` +
`Existing drivers/plugins will remain. The default preset is 'mobile'.`,
});
ArgParser._patchExit(setupParser);
const extSubParsers = setupParser.add_subparsers({
dest: `setupCommand`,
});
const parserSpecs = [
{
command: SUBCOMMAND_MOBILE,
help: `The preset for mobile devices: ${_.join(getPresetDrivers(SUBCOMMAND_MOBILE), ',')}`
},
{
command: SUBCOMMAND_BROWSER,
help: `The preset for desktop browser drivers: ${_.join(getPresetDrivers(SUBCOMMAND_BROWSER), ',')}`
},
{
command: SUBCOMMAND_DESKTOP,
help: `The preset for desktop application drivers: ${_.join(getPresetDrivers(SUBCOMMAND_DESKTOP), ',')}`
},
];
for (const {command, help} of parserSpecs) {
const parser = extSubParsers.add_parser(command, {help});
ArgParser._patchExit(parser);
}
}
}
/**
+185
View File
@@ -0,0 +1,185 @@
import _ from 'lodash';
import {
DESKTOP_BROWSERS,
DESKTOP_DRIVERS,
MOBILE_DRIVERS
} from '../constants';
import {runExtensionCommand} from './extension';
import { system } from '@appium/support';
import log from '../logger';
/**
* Subcommands of preset for setup
*/
export const SUBCOMMAND_MOBILE = 'mobile';
export const SUBCOMMAND_DESKTOP = 'desktop';
export const SUBCOMMAND_BROWSER = 'browser';
/**
* Pairs of preset subcommand and driver candidates.
* Driver names listed in KNOWN_DRIVERS to install by default
*/
const PRESET_PAIRS = Object.freeze(
/** @type {const} */ ({
mobile: _.keys(MOBILE_DRIVERS),
desktop: _.keys(DESKTOP_DRIVERS),
browser: _.keys(DESKTOP_BROWSERS)
}),
);
const DRIVERS_ONLY_MACOS = ['xcuitest', 'safari', 'mac2'];
const DRIVERS_ONLY_WINDOWS = ['windows'];
/**
* Plugin names listed in KNOWN_PLUGINS to install by default.
*/
export const DEFAULT_PLUGINS = ['images'];
/**
* Return a list of drivers available for current host platform.
* @param {import('appium/types').CliCommandSetupSubcommand} presetName
* @returns {Array<string>}
*/
export function getPresetDrivers(presetName) {
return _.filter(PRESET_PAIRS[presetName], (driver) => {
if (_.includes(DRIVERS_ONLY_MACOS, driver)) {
return system.isMac();
}
if (_.includes(DRIVERS_ONLY_WINDOWS, driver)) {
return system.isWindows();
}
return true;
});
}
/**
* Return desktop platform name for setup command description.
* @returns {string}
*/
export function determinePlatformName() {
if (system.isMac()) {
return 'macOS';
} else if (system.isWindows()) {
return 'Windows';
}
return 'Linux';
}
/**
* Run 'setup' command to install drivers/plugins into the given appium home.
* @template {import('appium/types').CliCommandSetup} SetupCmd
* @param {import('appium/types').Args<SetupCmd>} preConfigArgs
* @param {string} appiumHome
* @param {DriverConfig} driverConfig
* @param {PluginConfig} pluginConfig
* @returns {Promise<void>}
*/
export async function runSetupCommand(appiumHome, preConfigArgs, driverConfig, pluginConfig) {
switch (preConfigArgs.setupCommand) {
case SUBCOMMAND_DESKTOP:
await setupDesktopAppDrivers(driverConfig);
await setupDefaultPlugins(pluginConfig);
break;
case SUBCOMMAND_BROWSER:
await setupBrowserDrivers(driverConfig);
await setupDefaultPlugins(pluginConfig);
break;
default:
await setupMobileDrivers(driverConfig);
await setupDefaultPlugins(pluginConfig);
break;
}
};
/**
* Install drivers listed in DEFAULT_DRIVERS.
* @param {DriverConfig} driverConfig
* @returns {Promise<void>}
*/
async function setupMobileDrivers(driverConfig) {
await installDrivers(SUBCOMMAND_MOBILE, driverConfig);
}
/**
* Install all of known drivers listed in BROWSER_DRIVERS.
* @param {DriverConfig} driverConfig
* @returns {Promise<void>}
*/
async function setupBrowserDrivers(driverConfig) {
await installDrivers(SUBCOMMAND_BROWSER, driverConfig);
}
/**
* Install all of known drivers listed in DESKTOP_APP_DRIVERS.
* @param {DriverConfig} driverConfig
* @returns {Promise<void>}
*/
async function setupDesktopAppDrivers(driverConfig) {
await installDrivers(SUBCOMMAND_DESKTOP, driverConfig);
}
/**
* Install the given driver name. It skips the installation if the given driver name was already installed.
* @param {import('appium/types').CliCommandSetupSubcommand} subcommand
* @param {DriverConfig} driverConfig
* @returns {Promise<void>}
*/
async function installDrivers(subcommand, driverConfig) {
for (const driverName of getPresetDrivers(subcommand)) {
await installExtension(driverName, extensionCommandArgs('driver', driverName, 'install'), driverConfig);
}
}
/**
* Install plugins listed in DEFAULT_PLUGINS.
* @param {PluginConfig} pluginConfig
* @returns {Promise<void>}
*/
async function setupDefaultPlugins(pluginConfig) {
for (const pluginName of DEFAULT_PLUGINS) {
await installExtension(pluginName, extensionCommandArgs('plugin', pluginName, 'install'), pluginConfig);
}
}
/**
* Run the given extensionConfigArgs command after checking if the given extensionName was already installed.
* @param {string} extensionName
* @param {Args} extensionConfigArgs
* @param {DriverConfig|PluginConfig} extensionConfig
* @returns
*/
async function installExtension(extensionName, extensionConfigArgs, extensionConfig) {
if (_.keys(extensionConfig.installedExtensions).includes(extensionName)) {
log.info(`${extensionName} (${extensionConfig.installedExtensions[extensionName].version}) is already installed. ` +
`Skipping the installation.`);
return;
}
await runExtensionCommand(extensionConfigArgs, extensionConfig);
}
/**
* Return the command config for driver or plugin.
* @param {CliExtensionCommand} extensionCommand
* @param {string} extensionName
* @param {CliExtensionSubcommand} command
* @returns {Args}
*/
function extensionCommandArgs(extensionCommand, extensionName, command) {
return (extensionCommand === 'plugin')
? {'subcommand': 'plugin', 'pluginCommand': command, 'plugin': extensionName}
: {'subcommand': 'driver', 'driverCommand': command, 'driver': extensionName};
}
/**
* @typedef {import('appium/types').CliExtensionCommand} CliExtensionCommand
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
* @typedef {import('../extension/extension-config').ExtensionConfig<CliExtensionCommand>} PluginConfig
* @typedef {import('../extension/extension-config').ExtensionConfig<CliExtensionCommand>} DriverConfig
*/
/**
* @typedef {import('appium/types').Args<CliExtensionCommand, CliExtensionSubcommand>} Args
*/
+81 -20
View File
@@ -1,16 +1,18 @@
/* eslint-disable no-console */
import _ from 'lodash';
import {system, fs} from '@appium/support';
import {system, fs, npm} from '@appium/support';
import axios from 'axios';
import {exec} from 'teen_process';
import semver from 'semver';
import os from 'node:os';
import {npmPackage} from './utils';
import B from 'bluebird';
import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
export const APPIUM_VER = npmPackage.version;
const ENGINES = /** @type {Record<string,string>} */ (npmPackage.engines);
const MIN_NODE_VERSION = ENGINES.node;
export const rootDir = fs.findRoot(__dirname);
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
const GITHUB_API = 'https://api.github.com/repos/appium/appium';
@@ -28,8 +30,9 @@ function getNodeVersion() {
/**
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<void>}
*/
async function updateBuildInfo(useGithubApiFallback = false) {
export async function updateBuildInfo(useGithubApiFallback = false) {
const sha = await getGitRev(useGithubApiFallback);
if (!sha) {
return;
@@ -50,11 +53,61 @@ const getFullGitPath = _.memoize(async function getFullGitPath() {
}
});
/**
* Prints server debug into into the console.
*
* @param {{
* driverConfig: import('./extension/driver-config').DriverConfig,
* pluginConfig: import('./extension/plugin-config').PluginConfig,
* appiumHome: string
* }} info
* @returns {Promise<void>}
*/
export async function showDebugInfo({driverConfig, pluginConfig, appiumHome}) {
const getNpmVersion = async () => {
const {stdout} = await npm.exec('--version', [], {cwd: process.cwd()});
return _.trim(stdout);
};
const findNpmLocation = async () => await fs.which(system.isWindows() ? 'npm.cmd' : 'npm');
const [npmVersion, npmLocation] = await B.all([
...([getNpmVersion, findNpmLocation].map((f) => getSafeResult(f, 'unknown'))),
/** @type {any} */ (updateBuildInfo()),
]);
const debugInfo = {
os: {
platform: os.platform(),
release: os.release(),
arch: os.arch(),
homedir: os.homedir(),
username: os.userInfo().username,
},
node: {
version: process.version,
arch: process.arch,
cwd: process.cwd(),
argv: process.argv,
env: process.env,
npm: {
location: npmLocation,
version: npmVersion,
},
},
appium: {
location: rootDir,
homedir: appiumHome,
build: getBuildInfo(),
drivers: driverConfig.installedExtensions,
plugins: pluginConfig.installedExtensions,
},
};
console.log(JSON.stringify(debugInfo, null, 2));
}
/**
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<string?>}
*/
async function getGitRev(useGithubApiFallback = false) {
export async function getGitRev(useGithubApiFallback = false) {
const fullGitPath = await getFullGitPath();
if (fullGitPath) {
try {
@@ -122,11 +175,15 @@ async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
* and succeeds.
* @returns {import('appium/types').BuildInfo}
*/
function getBuildInfo() {
export function getBuildInfo() {
return BUILD_INFO;
}
function checkNodeOk() {
/**
* @returns {void}
* @throws {Error} If Node version is outside of the supported range
*/
export function checkNodeOk() {
const version = getNodeVersion();
if (!semver.satisfies(version, MIN_NODE_VERSION)) {
throw new Error(
@@ -135,7 +192,7 @@ function checkNodeOk() {
}
}
async function showBuildInfo() {
export async function showBuildInfo() {
await updateBuildInfo(true);
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
}
@@ -145,7 +202,7 @@ async function showBuildInfo() {
* @param {Args} parsedArgs
* @returns {Args}
*/
function getNonDefaultServerArgs(parsedArgs) {
export function getNonDefaultServerArgs(parsedArgs) {
/**
* Flattens parsed args into a single level object for comparison with
* flattened defaults across server args and extension args.
@@ -253,7 +310,7 @@ const compactConfig = _.partial(
* @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
* @param {ParsedArgs} parsedArgs - Entire parsed args object
*/
function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
export function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
console.log('Appium Configuration\n');
console.log('from defaults:\n');
console.dir(compactConfig(defaults));
@@ -308,18 +365,22 @@ export async function requireDir(root, requireWriteable = true, displayName = 'f
}
}
const rootDir = fs.findRoot(__dirname);
export {
getBuildInfo,
checkNodeOk,
showBuildInfo,
getNonDefaultServerArgs,
getGitRev,
updateBuildInfo,
showConfig,
rootDir,
};
/**
* Calculates the result of the given function and return its value
* or the default one if there was an exception.
*
* @template T
* @param {() => Promise<T>} f
* @param {T} defaultValue
* @returns {Promise<T>}
*/
async function getSafeResult(f, defaultValue) {
try {
return await f();
} catch {
return defaultValue;
}
}
/**
* @typedef {import('appium/types').ParsedArgs} ParsedArgs
+32 -7
View File
@@ -15,6 +15,12 @@ export const PLUGIN_TYPE = 'plugin';
*/
export const SERVER_SUBCOMMAND = 'server';
/**
* The `setup` command of the `appium` CLI
*/
export const SETUP_SUBCOMMAND = 'setup';
/**
* The value of `--use-plugins` if _all_ plugins should be loaded
*/
@@ -33,19 +39,38 @@ export const KNOWN_PLUGINS = Object.freeze(
}),
);
export const MOBILE_DRIVERS = Object.freeze(
/** @type {const} */ ({
uiautomator2: 'appium-uiautomator2-driver',
xcuitest: 'appium-xcuitest-driver',
espresso: 'appium-espresso-driver',
}),
);
export const DESKTOP_DRIVERS = Object.freeze(
/** @type {const} */ ({
mac2: 'appium-mac2-driver',
windows: 'appium-windows-driver',
}),
);
export const DESKTOP_BROWSERS = Object.freeze(
/** @type {const} */ ({
safari: 'appium-safari-driver',
gecko: 'appium-geckodriver',
chromium: 'appium-chromium-driver',
}),
);
// This is a map of driver names to npm packages representing those drivers.
// The drivers in this list will be available to the CLI so users can just
// type 'appium driver install 'name'', rather than having to specify the full
// npm package. I.e., these are the officially recognized drivers.
export const KNOWN_DRIVERS = Object.freeze(
/** @type {const} */ ({
uiautomator2: 'appium-uiautomator2-driver',
xcuitest: 'appium-xcuitest-driver',
mac2: 'appium-mac2-driver',
espresso: 'appium-espresso-driver',
safari: 'appium-safari-driver',
gecko: 'appium-geckodriver',
chromium: 'appium-chromium-driver',
...MOBILE_DRIVERS,
...DESKTOP_DRIVERS,
...DESKTOP_BROWSERS,
}),
);
+2 -2
View File
@@ -57,13 +57,13 @@ function postRequest(configHolder, addr, port, basePath) {
// Move Selenium 3 configuration properties to configuration object
if (!_.has(configHolder, 'configuration')) {
let configuration = {};
for (const property in configHolder) {
for (const property in /** @type {import('@appium/types').StringRecord} */ (configHolder)) {
if (_.has(configHolder, property) && property !== 'capabilities') {
configuration[property] = configHolder[property];
delete configHolder[property];
}
}
configHolder.configuration = configuration;
/** @type {import('@appium/types').StringRecord} */ (configHolder).configuration = configuration;
}
// if the node config does not have the appium/webdriver url, host, and port,
+31 -12
View File
@@ -1,15 +1,15 @@
import npmlog from 'npmlog';
import globalLog from '@appium/logger';
import {createLogger, format, transports} from 'winston';
import {fs, logger} from '@appium/support';
import { APPIUM_LOGGER_NAME } from './logger';
import _ from 'lodash';
// set up distributed logging before everything else
logger.patchLogger(npmlog);
global._global_npmlog = npmlog;
logger.patchLogger(globalLog);
global._global_npmlog = globalLog;
// npmlog is used only for emitting, we use winston for output
npmlog.level = 'info';
globalLog.level = 'info';
const levels = {
debug: 4,
info: 3,
@@ -131,8 +131,13 @@ function createHttpTransport(args, logLvl) {
});
}
/**
*
* @param {import('@appium/types').StringRecord} args
* @returns {Promise<import('winston-transport')[]>}
*/
async function createTransports(args) {
let transports = [];
const transports = [];
let consoleLogLevel = null;
let fileLogLevel = null;
@@ -193,7 +198,7 @@ function getColorizedPrefix(prefix) {
}
async function init(args) {
npmlog.level = 'silent';
globalLog.level = 'silent';
// set de facto param passed to timestamp function
useLocalTimeZone = args.localTimezone;
@@ -201,13 +206,16 @@ async function init(args) {
// clean up in case we have initiated before since npmlog is a global object
clear();
const transports = await createTransports(args);
const transportNames = new Set(transports.map((tr) => tr.constructor.name));
log = createLogger({
transports: await createTransports(args),
transports,
levels,
});
const reportedLoggerErrors = new Set();
// Capture logs emitted via npmlog and pass them through winston
npmlog.on('log', ({level, message, prefix}) => {
globalLog.on('log', ({level, message, prefix}) => {
const winstonLevel = npmToWinstonLevels[level] || 'info';
let msg = message;
if (prefix) {
@@ -217,9 +225,20 @@ async function init(args) {
: getColorizedPrefix(decoratedPrefix);
msg = `${args.logNoColors ? decoratedPrefix : toColorizedDecoratedPrefix()} ${msg}`;
}
log[winstonLevel](msg);
if (args.logHandler && _.isFunction(args.logHandler)) {
args.logHandler(level, msg);
try {
log[winstonLevel](msg);
if (_.isFunction(args.logHandler)) {
args.logHandler(level, msg);
}
} catch (e) {
if (!reportedLoggerErrors.has(e.message) && process.stderr.writable) {
// eslint-disable-next-line no-console
console.error(
`The log message '${_.truncate(msg, {length: 30})}' cannot be written into ` +
`one or more requested destinations: ${transportNames}. Original error: ${e.message}`
);
reportedLoggerErrors.add(e.message);
}
}
});
}
@@ -230,7 +249,7 @@ function clear() {
log.remove(transport);
}
}
npmlog.removeAllListeners('log');
globalLog.removeAllListeners('log');
}
export {init, clear};
+17 -1
View File
@@ -13,6 +13,7 @@ import {asyncify} from 'asyncbox';
import _ from 'lodash';
import {AppiumDriver} from './appium';
import {runExtensionCommand} from './cli/extension';
import { runSetupCommand } from './cli/setup-command';
import {getParser} from './cli/parser';
import {
APPIUM_VER,
@@ -21,6 +22,7 @@ import {
getNonDefaultServerArgs,
showConfig,
showBuildInfo,
showDebugInfo,
requireDir,
} from './config';
import {readConfigFile} from './config-file';
@@ -38,6 +40,7 @@ import {
fetchInterfaces,
V4_BROADCAST_IP,
V6_BROADCAST_IP,
isSetupCommandArgs,
} from './utils';
import net from 'node:net';
@@ -216,7 +219,7 @@ async function init(args) {
}
// merge config and apply defaults.
// the order of precendece is:
// the order of precedence is:
// 1. command line args
// 2. config file
// 3. defaults from config file.
@@ -231,6 +234,15 @@ async function init(args) {
return /** @type {InitResult<Cmd>} */ ({});
}
if (preConfigArgs.showDebugInfo) {
await showDebugInfo({
driverConfig,
pluginConfig,
appiumHome,
});
return /** @type {InitResult<Cmd>} */ ({});
}
await logsinkInit(serverArgs);
if (serverArgs.logFilters) {
@@ -276,6 +288,9 @@ async function init(args) {
pluginConfig,
appiumHome,
});
} else if (isSetupCommandArgs(preConfigArgs)) {
await runSetupCommand(appiumHome, preConfigArgs, driverConfig, pluginConfig);
return /** @type {InitResult<Cmd>} */ ({});
} else {
await requireDir(appiumHome, true, appiumHomeSourceName);
if (isExtensionCommandArgs(preConfigArgs)) {
@@ -465,6 +480,7 @@ export {main, init, resolveAppiumHome};
* @typedef {import('appium/types').CliCommandServer} ServerCommand
* @typedef {import('appium/types').CliCommandDriver} DriverCommand
* @typedef {import('appium/types').CliCommandPlugin} PluginCommand
* @typedef {import('appium/types').CliCommandSetup} SetupCommand
* @typedef {import('./extension').DriverNameMap} DriverNameMap
* @typedef {import('./extension').PluginNameMap} PluginNameMap
*/
+15 -3
View File
@@ -4,7 +4,7 @@ import {processCapabilities, PROTOCOLS, STANDARD_CAPS, errors} from '@appium/bas
import {inspect as dump} from 'util';
import {node, fs} from '@appium/support';
import path from 'path';
import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE} from './constants';
import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE, SETUP_SUBCOMMAND} from './constants';
import os from 'node:os';
const W3C_APPIUM_PREFIX = 'appium';
@@ -330,6 +330,16 @@ export function isServerCommandArgs(args) {
return args.subcommand === SERVER_SUBCOMMAND;
}
/**
* @template {CliCommand} Cmd
* @template {CliExtensionSubcommand|CliCommandSetupSubcommand|void} [SubCmd=void]
* @param {Args<Cmd, SubCmd>} args
* @returns {args is Args<SetupCommand>}
*/
export function isSetupCommandArgs(args) {
return args.subcommand === SETUP_SUBCOMMAND;
}
/**
* @template {CliCommand} [Cmd=ServerCommand]
* @template {CliExtensionSubcommand|void} [SubCmd=void]
@@ -451,19 +461,21 @@ export {
* @typedef {import('appium/types').CliCommand} CliCommand
* @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
* @typedef {import('appium/types').CliExtensionCommand} CliExtensionCommand
* @typedef {import('appium/types').CliCommandSetupSubcommand} CliCommandSetupSubcommand
* @typedef {import('appium/types').CliCommandServer} ServerCommand
* @typedef {import('appium/types').CliCommandDriver} DriverCommand
* @typedef {import('appium/types').CliCommandPlugin} PluginCommand
* @typedef {import('appium/types').CliCommandSetup} SetupCommand
*/
/**
* @template {CliCommand} [Cmd=ServerCommand]
* @template {CliExtensionSubcommand|void} [SubCmd=void]
* @template {CliExtensionSubcommand|CliCommandSetupSubcommand|void} [SubCmd=void]
* @typedef {import('appium/types').Args<Cmd, SubCmd>} Args
*/
/**
* @template {CliCommand} [Cmd=ServerCommand]
* @template {CliExtensionSubcommand|void} [SubCmd=void]
* @template {CliExtensionSubcommand|CliCommandSetupSubcommand|void} [SubCmd=void]
* @typedef {import('appium/types').ParsedArgs<Cmd, SubCmd>} ParsedArgs
*/
+13 -14
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.5.4",
"version": "2.7.0",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -60,12 +60,12 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.6.0",
"@appium/base-plugin": "^2.2.31",
"@appium/docutils": "^1.0.7",
"@appium/base-driver": "^9.8.0",
"@appium/base-plugin": "^2.2.33",
"@appium/docutils": "^1.0.9",
"@appium/schema": "~0.5.0",
"@appium/support": "^4.2.5",
"@appium/types": "^0.17.0",
"@appium/support": "^4.3.0",
"@appium/types": "^0.19.0",
"@sidvind/better-ajv-errors": "2.1.3",
"@types/argparse": "2.0.16",
"@types/bluebird": "3.5.42",
@@ -73,28 +73,27 @@
"@types/semver": "7.5.8",
"@types/teen_process": "2.0.4",
"@types/wrap-ansi": "3.0.0",
"ajv": "8.12.0",
"ajv": "8.16.0",
"ajv-formats": "3.0.1",
"argparse": "2.0.1",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.6.8",
"axios": "1.7.2",
"bluebird": "3.7.2",
"cross-env": "7.0.3",
"lilconfig": "3.1.1",
"lodash": "4.17.21",
"npmlog": "7.0.1",
"ora": "5.4.1",
"package-changed": "3.0.0",
"resolve-from": "5.0.0",
"semver": "7.6.0",
"semver": "7.6.2",
"source-map-support": "0.5.21",
"teen_process": "2.1.1",
"type-fest": "4.10.1",
"teen_process": "2.1.4",
"type-fest": "4.19.0",
"winston": "3.13.0",
"wrap-ansi": "7.0.0",
"ws": "8.16.0",
"yaml": "2.4.1"
"ws": "8.17.0",
"yaml": "2.4.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -1,5 +1,5 @@
{
"devDependencies": {
"webdriverio": "8.36.1"
"webdriverio": "8.38.2"
}
}
@@ -0,0 +1,43 @@
import {createSandbox} from 'sinon';
import {system} from '@appium/support';
import {getPresetDrivers} from '../../../lib/cli/setup-command';
const expect = chai.expect;
describe('SetupCommand', function () {
let sandbox;
beforeEach(function () {
sandbox = createSandbox();
});
afterEach(function () {
sandbox.restore();
});
describe('getPresetDrivers', function () {
it('for drivers on macOS environment', function () {
sandbox.stub(system, 'isMac').returns(true);
sandbox.stub(system, 'isWindows').returns(false);
expect(getPresetDrivers('mobile')).eql(['uiautomator2', 'xcuitest', 'espresso']);
expect(getPresetDrivers('browser')).eql(['safari', 'gecko', 'chromium']);
expect(getPresetDrivers('desktop')).eql(['mac2']);
});
it('for drivers on Windows environment', function () {
sandbox.stub(system, 'isMac').returns(false);
sandbox.stub(system, 'isWindows').returns(true);
expect(getPresetDrivers('mobile')).eql(['uiautomator2', 'espresso']);
expect(getPresetDrivers('browser')).eql(['gecko', 'chromium']);
expect(getPresetDrivers('desktop')).eql(['windows']);
});
it('for drivers on Linux environment', function () {
sandbox.stub(system, 'isMac').returns(false);
sandbox.stub(system, 'isWindows').returns(false);
expect(getPresetDrivers('mobile')).eql(['uiautomator2', 'espresso']);
expect(getPresetDrivers('browser')).eql(['gecko', 'chromium']);
expect(getPresetDrivers('desktop')).eql([]);
});
});
});
+19 -1
View File
@@ -1,5 +1,5 @@
import _ from 'lodash';
import {DRIVER_TYPE, PLUGIN_TYPE} from '../../lib/constants';
import {DRIVER_TYPE, PLUGIN_TYPE, SETUP_SUBCOMMAND} from '../../lib/constants';
import {getParser} from '../../lib/cli/parser';
import {INSTALL_TYPES} from '../../lib/extension/extension-config';
import * as schema from '../../lib/schema/schema';
@@ -395,4 +395,22 @@ describe('parser', function () {
});
});
});
describe('Setup Parser', function () {
let p;
beforeEach(function () {
p = getParser(true);
});
it('should not allow random sub-subcommands', function () {
(() => p.parseArgs([SETUP_SUBCOMMAND, 'foo'])).should.throw();
});
describe('all', function () {
it('should allow an empty argument mobile', function () {
const args = p.parseArgs([SETUP_SUBCOMMAND, 'mobile']);
args.subcommand.should.eql(SETUP_SUBCOMMAND);
args.setupCommand.should.eql('mobile');
});
});
});
});
+33 -8
View File
@@ -2,6 +2,7 @@ import {DriverType, PluginType, ServerArgs} from '@appium/types';
import {SetOptional} from 'type-fest';
import {InstallType} from './manifest';
export type CliCommandServer = 'server';
export type CliCommandSetup = 'setup';
export type CliCommandDriver = DriverType;
export type CliCommandPlugin = PluginType;
@@ -13,7 +14,13 @@ export type CliExtensionCommand = CliCommandDriver | CliCommandPlugin;
/**
* Possible commands for the `appium` CLI.
*/
export type CliCommand = CliCommandServer | CliExtensionCommand;
export type CliCommand = CliCommandServer | CliExtensionCommand | CliCommandSetup;
/**
* Possible subcommands of {@linkcode CliCommandSetup}.
* The command name will be preset name to get drivers/plugins to be installed.
*/
export type CliCommandSetupSubcommand = 'mobile' | 'browser' | 'desktop';
/**
* Possible subcommands of {@linkcode CliCommandDriver} or
@@ -55,6 +62,11 @@ export interface MoreArgs {
*/
showConfig?: boolean;
/**
* If true, show the server debug info and exit
*/
showDebugInfo?: boolean;
/**
* If true, open a REPL
*/
@@ -62,7 +74,7 @@ export interface MoreArgs {
}
/**
* These arguments are _not_ supported by the CLI, but only via programmatic usage / tests.
* These arguments are not necessarily supported by the CLI, but via programmatic usage / tests.
*/
export interface ProgrammaticArgs {
/**
@@ -93,6 +105,11 @@ export interface ProgrammaticArgs {
* If true, show config and exit
*/
showConfig?: boolean;
/**
* If true, show debug info and exit
*/
showDebugInfo?: boolean;
}
export interface DriverExtArgs {
@@ -105,6 +122,10 @@ export interface PluginExtArgs {
plugin?: string;
}
export interface SetupArgs {
setupCommand?: CliCommandSetupSubcommand;
}
/**
* These are args which the user will specify if using an extension command
*/
@@ -132,9 +153,11 @@ export interface BaseExtArgs<Cmd extends CliExtensionCommand> {
*/
export type ExtArgs<
Cmd extends CliExtensionCommand,
SubCmd extends CliExtensionSubcommand
SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand
> = BaseExtArgs<Cmd> &
(Cmd extends CliCommandDriver
(Cmd extends CliCommandSetup
? SetupArgs
: Cmd extends CliCommandDriver
? DriverExtArgs
: Cmd extends CliCommandPlugin
? PluginExtArgs
@@ -152,13 +175,15 @@ export type ExtArgs<
*/
export type CommonArgs<
Cmd extends CliCommand = CliCommandServer,
SubCmd extends CliExtensionSubcommand | void = void
SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void = void
> = MoreArgs &
ProgrammaticArgs &
(Cmd extends CliCommandServer
? ServerArgs
: Cmd extends CliCommandSetup
? SetupArgs
: Cmd extends CliExtensionCommand
? SubCmd extends CliExtensionSubcommand
? SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand
? ExtArgs<Cmd, SubCmd>
: never
: never);
@@ -168,7 +193,7 @@ export type CommonArgs<
*/
export type ParsedArgs<
Cmd extends CliCommand = CliCommandServer,
SubCmd extends CliExtensionSubcommand | void = void
SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void = void
> = CommonArgs<Cmd, SubCmd>;
/**
@@ -178,7 +203,7 @@ export type ParsedArgs<
*/
export type Args<
Cmd extends CliCommand = CliCommandServer,
SubCmd extends CliExtensionSubcommand | void = void
SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void = void
> = Cmd extends CliCommandServer
? SetOptional<CommonArgs<Cmd>, keyof ServerArgs>
: ParsedArgs<Cmd, SubCmd>;
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [9.8.0](https://github.com/appium/appium/compare/@appium/base-driver@9.7.0...@appium/base-driver@9.8.0) (2024-06-06)
### Features
* **base-driver:** Make addition of search query params to cache optional ([#20195](https://github.com/appium/appium/issues/20195)) ([21316e9](https://github.com/appium/appium/commit/21316e94d05b2f97aa0349a82da229713e92446c))
### Bug Fixes
* **types:** update dependency type-fest to v4.19.0 ([#20193](https://github.com/appium/appium/issues/20193)) ([db62358](https://github.com/appium/appium/commit/db62358dbac25de2a75fdc7493338d98b1422c84))
## [9.7.0](https://github.com/appium/appium/compare/@appium/base-driver@9.6.0...@appium/base-driver@9.7.0) (2024-05-27)
### Features
* **base-driver:** Add env variables to control applications cache ([#20042](https://github.com/appium/appium/issues/20042)) ([4e8c91c](https://github.com/appium/appium/commit/4e8c91c8f647e545344d5b49282628413e1ccb19))
* **base-driver:** Add the original app link argument to configureApp callbacks ([#20035](https://github.com/appium/appium/issues/20035)) ([3423fd9](https://github.com/appium/appium/commit/3423fd9206f71481bb34919e02a3dc8c5e3bec00))
### Bug Fixes
* **base-driver:** Update the web socket upgrade behavior ([#20142](https://github.com/appium/appium/issues/20142)) ([275790e](https://github.com/appium/appium/commit/275790ec012d0c773b1248c3c2a541301cf0167b))
* **driver-test-support:** update definitelytyped ([a8d36b0](https://github.com/appium/appium/commit/a8d36b0a981daf524bd4af422904193c087a8c2c))
* **images-plugin:** update dependency lru-cache to v10.2.1 ([#20039](https://github.com/appium/appium/issues/20039)) ([f645b50](https://github.com/appium/appium/commit/f645b50d9d6008d374bc131be78d8be5d84a7f53))
* **images-plugin:** update dependency lru-cache to v10.2.2 ([#20052](https://github.com/appium/appium/issues/20052)) ([f8d1fb7](https://github.com/appium/appium/commit/f8d1fb7df7b7d093d3664f58830cad5b5dffa244))
* **support:** update dependency axios to v1.7.1 ([11510cb](https://github.com/appium/appium/commit/11510cb1a5d50a20ced884e5404d0be1e04ff142))
* **support:** update dependency axios to v1.7.2 ([a876f11](https://github.com/appium/appium/commit/a876f112b51dd25f70094b9e75330b9558050e42))
* **types:** update dependency type-fest to v4.18.2 ([#20103](https://github.com/appium/appium/issues/20103)) ([3b43be1](https://github.com/appium/appium/commit/3b43be17321f2ca16bac0abedabf9ef8cffa098a))
* **types:** update dependency type-fest to v4.18.3 ([#20149](https://github.com/appium/appium/issues/20149)) ([d5369f8](https://github.com/appium/appium/commit/d5369f8b08e7439282c5a211e684b154cc9f0051))
## [9.6.0](https://github.com/appium/appium/compare/@appium/base-driver@9.5.4...@appium/base-driver@9.6.0) (2024-04-21)
+226 -136
View File
@@ -13,8 +13,8 @@ export const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
const IPA_EXT = '.ipa';
const ZIP_EXTS = new Set(['.zip', IPA_EXT]);
const ZIP_MIME_TYPES = ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip'];
const CACHED_APPS_MAX_AGE = 1000 * 60 * 60 * 24; // ms
const MAX_CACHED_APPS = 1024;
const CACHED_APPS_MAX_AGE_MS = 1000 * 60 * toNaturalNumber(60 * 24, 'APPIUM_APPS_CACHE_MAX_AGE');
const MAX_CACHED_APPS = toNaturalNumber(1024, 'APPIUM_APPS_CACHE_MAX_ITEMS');
const HTTP_STATUS_NOT_MODIFIED = 304;
const DEFAULT_REQ_HEADERS = Object.freeze({
'user-agent': `Appium (BaseDriver v${BASEDRIVER_VER})`,
@@ -23,12 +23,12 @@ const AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC = 2;
/** @type {LRUCache<string, import('@appium/types').CachedAppInfo>} */
const APPLICATIONS_CACHE = new LRUCache({
max: MAX_CACHED_APPS,
ttl: CACHED_APPS_MAX_AGE, // expire after 24 hours
ttl: CACHED_APPS_MAX_AGE_MS, // expire after 24 hours
updateAgeOnGet: true,
dispose: ({fullPath}, app) => {
logger.info(
`The application '${app}' cached at '${fullPath}' has ` +
`expired after ${CACHED_APPS_MAX_AGE}ms`
`expired after ${CACHED_APPS_MAX_AGE_MS}ms`
);
if (fullPath) {
fs.rimraf(fullPath);
@@ -61,60 +61,6 @@ process.on('exit', () => {
}
});
/**
* @param {string} app
* @param {string[]} supportedAppExtensions
* @returns {string}
*/
function verifyAppExtension(app, supportedAppExtensions) {
if (supportedAppExtensions.map(_.toLower).includes(_.toLower(path.extname(app)))) {
return app;
}
throw new Error(
`New app path '${app}' did not have ` +
`${util.pluralize('extension', supportedAppExtensions.length, false)}: ` +
supportedAppExtensions
);
}
/**
* @param {string} folderPath
* @returns {Promise<number>}
*/
async function calculateFolderIntegrity(folderPath) {
return (await fs.glob('**/*', {cwd: folderPath})).length;
}
/**
* @param {string} filePath
* @returns {Promise<string>}
*/
async function calculateFileIntegrity(filePath) {
return await fs.hash(filePath);
}
/**
* @param {string} currentPath
* @param {import('@appium/types').StringRecord} expectedIntegrity
* @returns {Promise<boolean>}
*/
async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
if (!(await fs.exists(currentPath))) {
return false;
}
// Folder integrity check is simple:
// Verify the previous amount of files is not greater than the current one.
// We don't want to use equality comparison because of an assumption that the OS might
// create some unwanted service files/cached inside of that folder or its subfolders.
// Ofc, validating the hash sum of each file (or at least of file path) would be much
// more precise, but we don't need to be very precise here and also don't want to
// overuse RAM and have a performance drop.
return (await fs.stat(currentPath)).isDirectory()
? (await calculateFolderIntegrity(currentPath)) >= expectedIntegrity?.folder
: (await calculateFileIntegrity(currentPath)) === expectedIntegrity?.file;
}
/**
*
* @param {string} app
@@ -145,6 +91,7 @@ export async function configureApp(
}
let newApp = app;
const originalAppLink = app;
let shouldUnzipApp = false;
let packageHash = null;
/** @type {import('axios').AxiosResponse['headers']|undefined} */
@@ -156,15 +103,16 @@ export async function configureApp(
maxAge: null,
etag: null,
};
const {protocol, pathname} = url.parse(newApp);
const isUrl = protocol === null ? false : ['http:', 'https:'].includes(protocol);
const {protocol, pathname} = parseAppLink(app);
const isUrl = isSupportedUrl(app);
const appCacheKey = toCacheKey(app);
const cachedAppInfo = APPLICATIONS_CACHE.get(app);
const cachedAppInfo = APPLICATIONS_CACHE.get(appCacheKey);
if (cachedAppInfo) {
logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
}
return await APPLICATIONS_CACHE_GUARD.acquire(app, async () => {
return await APPLICATIONS_CACHE_GUARD.acquire(appCacheKey, async () => {
if (isUrl) {
// Use the app from remote URL
logger.info(`Using downloadable app '${newApp}'`);
@@ -208,7 +156,7 @@ export async function configureApp(
`The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
`or its integrity has been damaged. Deleting it from the internal cache`
);
APPLICATIONS_CACHE.delete(app);
APPLICATIONS_CACHE.delete(appCacheKey);
if (!stream.closed) {
stream.destroy();
@@ -269,6 +217,7 @@ export async function configureApp(
}
newApp = onDownload
? await onDownload({
url: originalAppLink,
headers: /** @type {import('@appium/types').HTTPHeaders} */ (_.clone(headers)),
stream,
})
@@ -316,7 +265,7 @@ export async function configureApp(
`The application at '${fullPath}' does not exist anymore ` +
`or its integrity has been damaged. Deleting it from the cache`
);
APPLICATIONS_CACHE.delete(app);
APPLICATIONS_CACHE.delete(appCacheKey);
}
const tmpRoot = await tempDir.openDir();
try {
@@ -347,7 +296,7 @@ export async function configureApp(
} else {
integrity.file = await calculateFileIntegrity(appPathToCache);
}
APPLICATIONS_CACHE.set(app, {
APPLICATIONS_CACHE.set(appCacheKey, {
...remoteAppProps,
timestamp: Date.now(),
packageHash,
@@ -362,6 +311,7 @@ export async function configureApp(
/** @type {import('@appium/types').PostProcessOptions<import('axios').AxiosResponseHeaders>} */ ({
cachedAppInfo: _.clone(cachedAppInfo),
isUrl,
originalAppLink,
headers: _.clone(headers),
appPath: newApp,
})
@@ -372,12 +322,94 @@ export async function configureApp(
}
verifyAppExtension(newApp, supportedAppExtensions);
return app !== newApp && (packageHash || _.values(remoteAppProps).some(Boolean))
return appCacheKey !== toCacheKey(newApp) && (packageHash || _.values(remoteAppProps).some(Boolean))
? await storeAppInCache(newApp)
: newApp;
});
}
/**
* @param {string} app
* @returns {boolean}
*/
export function isPackageOrBundle(app) {
return /^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/.test(app);
}
/**
* Finds all instances 'firstKey' and create a duplicate with the key 'secondKey',
* Do the same thing in reverse. If we find 'secondKey', create a duplicate with the key 'firstKey'.
*
* This will cause keys to be overwritten if the object contains 'firstKey' and 'secondKey'.
* @param {*} input Any type of input
* @param {String} firstKey The first key to duplicate
* @param {String} secondKey The second key to duplicate
*/
export function duplicateKeys(input, firstKey, secondKey) {
// If array provided, recursively call on all elements
if (_.isArray(input)) {
return input.map((item) => duplicateKeys(item, firstKey, secondKey));
}
// If object, create duplicates for keys and then recursively call on values
if (_.isPlainObject(input)) {
const resultObj = {};
for (let [key, value] of _.toPairs(input)) {
const recursivelyCalledValue = duplicateKeys(value, firstKey, secondKey);
if (key === firstKey) {
resultObj[secondKey] = recursivelyCalledValue;
} else if (key === secondKey) {
resultObj[firstKey] = recursivelyCalledValue;
}
resultObj[key] = recursivelyCalledValue;
}
return resultObj;
}
// Base case. Return primitives without doing anything.
return input;
}
/**
* Takes a desired capability and tries to JSON.parse it as an array,
* and either returns the parsed array or a singleton array.
*
* @param {string|Array<String>} cap A desired capability
*/
export function parseCapsArray(cap) {
if (_.isArray(cap)) {
return cap;
}
let parsedCaps;
try {
parsedCaps = JSON.parse(cap);
if (_.isArray(parsedCaps)) {
return parsedCaps;
}
} catch (ign) {
logger.warn(`Failed to parse capability as JSON array`);
}
if (_.isString(cap)) {
return [cap];
}
throw new Error(`must provide a string or JSON Array; received ${cap}`);
}
/**
* Generate a string that uniquely describes driver instance
*
* @param {import('@appium/types').Core} obj driver instance
* @param {string?} sessionId session identifier (if exists)
* @returns {string}
*/
export function generateDriverLogPrefix(obj, sessionId = null) {
const instanceName = `${obj.constructor.name}@${node.getObjectId(obj).substring(0, 4)}`;
return sessionId ? `${instanceName} (${sessionId.substring(0, 8)})` : instanceName;
}
/**
* Sends a HTTP GET query to fetch the app with caching enabled.
* Follows https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
@@ -483,9 +515,7 @@ async function unzipApp(zipPath, dstRoot, supportedAppExtensions) {
try {
logger.debug(`Unzipping '${zipPath}'`);
const timer = new timing.Timer().start();
const useSystemUnzipEnv = process.env.APPIUM_PREFER_SYSTEM_UNZIP;
const useSystemUnzip =
_.isEmpty(useSystemUnzipEnv) || !['0', 'false'].includes(_.toLower(useSystemUnzipEnv));
const useSystemUnzip = isEnvOptionEnabled('APPIUM_PREFER_SYSTEM_UNZIP', true);
/**
* Attempt to use use the system `unzip` (e.g., `/usr/bin/unzip`) due
* to the significant performance improvement it provides over the native
@@ -539,84 +569,144 @@ async function unzipApp(zipPath, dstRoot, supportedAppExtensions) {
}
/**
* @param {string} app
* @returns {boolean}
*/
export function isPackageOrBundle(app) {
return /^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/.test(app);
}
/**
* Finds all instances 'firstKey' and create a duplicate with the key 'secondKey',
* Do the same thing in reverse. If we find 'secondKey', create a duplicate with the key 'firstKey'.
* Transforms the given app link to the cache key.
* This is necessary to properly cache apps
* having the same address, but different query strings,
* for example ones stored in S3 using presigned URLs.
*
* This will cause keys to be overwritten if the object contains 'firstKey' and 'secondKey'.
* @param {*} input Any type of input
* @param {String} firstKey The first key to duplicate
* @param {String} secondKey The second key to duplicate
* @param {string} app App link.
* @returns {string} Transformed app link or the original arg if
* no transfromation is needed.
*/
export function duplicateKeys(input, firstKey, secondKey) {
// If array provided, recursively call on all elements
if (_.isArray(input)) {
return input.map((item) => duplicateKeys(item, firstKey, secondKey));
function toCacheKey(app) {
if (!isEnvOptionEnabled('APPIUM_APPS_CACHE_IGNORE_URL_QUERY') || !isSupportedUrl(app)) {
return app;
}
// If object, create duplicates for keys and then recursively call on values
if (_.isPlainObject(input)) {
const resultObj = {};
for (let [key, value] of _.toPairs(input)) {
const recursivelyCalledValue = duplicateKeys(value, firstKey, secondKey);
if (key === firstKey) {
resultObj[secondKey] = recursivelyCalledValue;
} else if (key === secondKey) {
resultObj[firstKey] = recursivelyCalledValue;
}
resultObj[key] = recursivelyCalledValue;
}
return resultObj;
}
// Base case. Return primitives without doing anything.
return input;
}
/**
* Takes a desired capability and tries to JSON.parse it as an array,
* and either returns the parsed array or a singleton array.
*
* @param {string|Array<String>} cap A desired capability
*/
export function parseCapsArray(cap) {
if (_.isArray(cap)) {
return cap;
}
let parsedCaps;
try {
parsedCaps = JSON.parse(cap);
if (_.isArray(parsedCaps)) {
return parsedCaps;
const {href, search} = parseAppLink(app);
if (href && search) {
return href.replace(search, '');
}
} catch (ign) {
logger.warn(`Failed to parse capability as JSON array`);
}
if (_.isString(cap)) {
return [cap];
}
throw new Error(`must provide a string or JSON Array; received ${cap}`);
if (href) {
return href;
}
} catch {}
return app;
}
/**
* Generate a string that uniquely describes driver instance
* Safely parses the given app link to a URL object
*
* @param {import('@appium/types').Core} obj driver instance
* @param {string?} sessionId session identifier (if exists)
* @param {string} appLink
* @returns {URL|import('@appium/types').StringRecord} Parsed URL object
* or an empty object if the parsing has failed
*/
function parseAppLink(appLink) {
try {
return new URL(appLink);
} catch {
return {};
}
}
/**
* Checks whether we can threat the given app link
* as a URL,
*
* @param {string} app
* @returns {boolean} True if app is a supported URL
*/
function isSupportedUrl(app) {
try {
const {protocol} = parseAppLink(app);
return ['http:', 'https:'].includes(protocol);
} catch {
return false;
}
}
/**
* Check if the given environment option is enabled
*
* @param {string} optionName Option name
* @param {boolean|null} [defaultValue=null] The value to return if the given env value
* is not set explcitly
* @returns {boolean} True if the option is enabled
*/
function isEnvOptionEnabled(optionName, defaultValue = null) {
const value = process.env[optionName];
if (!_.isNull(defaultValue) && _.isEmpty(value)) {
return defaultValue;
}
return !_.isEmpty(value) && !['0', 'false', 'no'].includes(_.toLower(value));
}
/**
*
* @param {string} [envVarName]
* @param {number} defaultValue
* @returns {number}
*/
function toNaturalNumber(defaultValue, envVarName) {
if (!envVarName || _.isUndefined(process.env[envVarName])) {
return defaultValue;
}
const num = parseInt(`${process.env[envVarName]}`, 10);
return num > 0 ? num : defaultValue;
}
/**
* @param {string} app
* @param {string[]} supportedAppExtensions
* @returns {string}
*/
export function generateDriverLogPrefix(obj, sessionId = null) {
const instanceName = `${obj.constructor.name}@${node.getObjectId(obj).substring(0, 4)}`;
return sessionId ? `${instanceName} (${sessionId.substring(0, 8)})` : instanceName;
function verifyAppExtension(app, supportedAppExtensions) {
if (supportedAppExtensions.map(_.toLower).includes(_.toLower(path.extname(app)))) {
return app;
}
throw new Error(
`New app path '${app}' did not have ` +
`${util.pluralize('extension', supportedAppExtensions.length, false)}: ` +
supportedAppExtensions
);
}
/**
* @param {string} folderPath
* @returns {Promise<number>}
*/
async function calculateFolderIntegrity(folderPath) {
return (await fs.glob('**/*', {cwd: folderPath})).length;
}
/**
* @param {string} filePath
* @returns {Promise<string>}
*/
async function calculateFileIntegrity(filePath) {
return await fs.hash(filePath);
}
/**
* @param {string} currentPath
* @param {import('@appium/types').StringRecord} expectedIntegrity
* @returns {Promise<boolean>}
*/
async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
if (!(await fs.exists(currentPath))) {
return false;
}
// Folder integrity check is simple:
// Verify the previous amount of files is not greater than the current one.
// We don't want to use equality comparison because of an assumption that the OS might
// create some unwanted service files/cached inside of that folder or its subfolders.
// Ofc, validating the hash sum of each file (or at least of file path) would be much
// more precise, but we don't need to be very precise here and also don't want to
// overuse RAM and have a performance drop.
return (await fs.stat(currentPath)).isDirectory()
? (await calculateFolderIntegrity(currentPath)) >= expectedIntegrity?.folder
: (await calculateFileIntegrity(currentPath)) === expectedIntegrity?.file;
}
/** @type {import('@appium/types').DriverHelpers} */
+70 -16
View File
@@ -2,8 +2,16 @@ import _ from 'lodash';
import log from './logger';
import {errors} from '../protocol';
import {handleIdempotency} from './idempotency';
import {pathToRegexp} from 'path-to-regexp';
function allowCrossDomain(req, res, next) {
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
* @returns {any}
*/
export function allowCrossDomain(req, res, next) {
try {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
@@ -22,7 +30,11 @@ function allowCrossDomain(req, res, next) {
next();
}
function allowCrossDomainAsyncExecute(basePath) {
/**
* @param {string} basePath
* @returns {import('express').RequestHandler}
*/
export function allowCrossDomainAsyncExecute(basePath) {
return (req, res, next) => {
// there are two paths for async responses, so cover both
// https://regex101.com/r/txYiEz/1
@@ -36,12 +48,17 @@ function allowCrossDomainAsyncExecute(basePath) {
};
}
function fixPythonContentType(basePath) {
/**
*
* @param {string} basePath
* @returns {import('express').RequestHandler}
*/
export function fixPythonContentType(basePath) {
return (req, res, next) => {
// hack because python client library gives us wrong content-type
if (
new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) &&
/^Python/.test(req.headers['user-agent'])
/^Python/.test(req.headers['user-agent'] ?? '')
) {
if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
req.headers['content-type'] = 'application/json; charset=utf-8';
@@ -51,14 +68,55 @@ function fixPythonContentType(basePath) {
};
}
function defaultToJSONContentType(req, res, next) {
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
* @returns {any}
*/
export function defaultToJSONContentType(req, res, next) {
if (!req.headers['content-type']) {
req.headers['content-type'] = 'application/json; charset=utf-8';
}
next();
}
function catchAllHandler(err, req, res, next) {
/**
*
* @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping
* @returns {import('express').RequestHandler}
*/
export function handleUpgrade(webSocketsMapping) {
return (req, res, next) => {
if (!req.headers?.upgrade || _.toLower(req.headers.upgrade) !== 'websocket') {
return next();
}
let currentPathname;
try {
currentPathname = new URL(req.url ?? '').pathname;
} catch {
currentPathname = req.url ?? '';
}
for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) {
if (pathToRegexp(pathname).test(currentPathname)) {
return wsServer.handleUpgrade(req, req.socket, Buffer.from(''), (ws) => {
wsServer.emit('connection', ws, req);
});
}
}
log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`);
next();
};
}
/**
* @param {Error} err
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
export function catchAllHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
@@ -79,7 +137,11 @@ function catchAllHandler(err, req, res, next) {
log.error(err);
}
function catch404Handler(req, res) {
/**
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
export function catch404Handler(req, res) {
log.debug(`No route found for ${req.url}`);
const error = errors.UnknownCommandError;
res.status(error.w3cStatus()).json(
@@ -107,12 +169,4 @@ function patchWithSessionId(req, body) {
return body;
}
export {
allowCrossDomain,
fixPythonContentType,
defaultToJSONContentType,
catchAllHandler,
allowCrossDomainAsyncExecute,
handleIdempotency,
catch404Handler,
};
export { handleIdempotency };
+6 -1
View File
@@ -14,6 +14,7 @@ import {
catchAllHandler,
allowCrossDomainAsyncExecute,
handleIdempotency,
handleUpgrade,
catch404Handler,
} from './middleware';
import {guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR} from './static';
@@ -109,6 +110,7 @@ async function server(opts) {
allowCors,
basePath,
extraMethodMap,
webSocketsMapping: appiumServer.webSocketsMapping,
});
// allow extensions to update the app and http server objects
for (const updater of serverUpdaters) {
@@ -139,6 +141,7 @@ function configureServer({
allowCors = true,
basePath = DEFAULT_BASE_PATH,
extraMethodMap = {},
webSocketsMapping = {},
}) {
basePath = normalizeBasePath(basePath);
@@ -152,7 +155,7 @@ function configureServer({
app.use(`${basePath}/produce_error`, produceError);
app.use(`${basePath}/crash`, produceCrash);
// add middlewares
app.use(handleUpgrade(webSocketsMapping));
if (allowCors) {
app.use(allowCrossDomain);
} else {
@@ -195,6 +198,7 @@ function configureHttp({httpServer, reject, keepAliveTimeout}) {
* @type {AppiumServer}
*/
const appiumServer = /** @type {any} */ (httpServer);
appiumServer.webSocketsMapping = {};
appiumServer.addWebSocketHandler = addWebSocketHandler;
appiumServer.removeWebSocketHandler = removeWebSocketHandler;
appiumServer.removeAllWebSocketHandlers = removeAllWebSocketHandlers;
@@ -370,4 +374,5 @@ export {server, configureServer, normalizeBasePath};
* @property {boolean} [allowCors]
* @property {string} [basePath]
* @property {MethodMap} [extraMethodMap]
* @property {import('@appium/types').StringRecord} [webSocketsMapping={}]
*/
@@ -1,8 +1,6 @@
/* eslint-disable require-await */
import _ from 'lodash';
import {URL} from 'url';
import B from 'bluebird';
import { pathToRegexp } from 'path-to-regexp';
const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
@@ -11,27 +9,6 @@ const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
* @type {AppiumServer['addWebSocketHandler']}
*/
async function addWebSocketHandler(handlerPathname, handlerServer) {
if (_.isUndefined(this.webSocketsMapping)) {
this.webSocketsMapping = {};
// https://github.com/websockets/ws/pull/885
this.on('upgrade', (request, socket, head) => {
let currentPathname;
try {
currentPathname = new URL(request.url ?? '').pathname;
} catch {
currentPathname = request.url ?? '';
}
for (const [pathname, wsServer] of _.toPairs(this.webSocketsMapping)) {
if (pathToRegexp(pathname).test(currentPathname)) {
wsServer.handleUpgrade(request, socket, head, (ws) => {
wsServer.emit('connection', ws, request);
});
return;
}
}
socket.destroy();
});
}
this.webSocketsMapping[handlerPathname] = handlerServer;
}
@@ -40,10 +17,6 @@ async function addWebSocketHandler(handlerPathname, handlerServer) {
* @type {AppiumServer['getWebSocketHandlers']}
*/
async function getWebSocketHandlers(keysFilter = null) {
if (_.isEmpty(this.webSocketsMapping)) {
return {};
}
return _.toPairs(this.webSocketsMapping).reduce((acc, [pathname, wsServer]) => {
if (!_.isString(keysFilter) || pathname.includes(keysFilter)) {
acc[pathname] = wsServer;
@@ -134,10 +134,12 @@ class ProtocolConverter {
const bodyObj = util.safeJsonParse(body);
if (_.isPlainObject(bodyObj)) {
if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
this.log.debug(`Copied 'name' value '${bodyObj.name}' to 'handle' as per W3C spec`);
this.log.debug(
`Copied 'name' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).name}' to 'handle' as per W3C spec`
);
return await this.proxyFunc(url, method, {
...bodyObj,
handle: bodyObj.name,
.../** @type {import('@appium/types').StringRecord} */ (bodyObj),
handle: /** @type {import('@appium/types').StringRecord} */ (bodyObj).name,
});
}
if (
@@ -145,10 +147,12 @@ class ProtocolConverter {
_.has(bodyObj, 'handle') &&
!_.has(bodyObj, 'name')
) {
this.log.debug(`Copied 'handle' value '${bodyObj.handle}' to 'name' as per JSONWP spec`);
this.log.debug(
`Copied 'handle' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).handle}' to 'name' as per JSONWP spec`
);
return await this.proxyFunc(url, method, {
...bodyObj,
name: bodyObj.handle,
.../** @type {import('@appium/types').StringRecord} */ (bodyObj),
name: /** @type {import('@appium/types').StringRecord} */ (bodyObj).handle,
});
}
}
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "9.6.0",
"version": "9.8.0",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -44,30 +44,30 @@
"test:types": "tsd"
},
"dependencies": {
"@appium/support": "^4.2.5",
"@appium/types": "^0.17.0",
"@appium/support": "^4.3.0",
"@appium/types": "^0.19.0",
"@colors/colors": "1.6.0",
"@types/async-lock": "1.4.2",
"@types/bluebird": "3.5.42",
"@types/express": "4.17.21",
"@types/lodash": "4.17.0",
"@types/lodash": "4.17.4",
"@types/method-override": "0.0.35",
"@types/serve-favicon": "2.5.7",
"async-lock": "1.4.1",
"asyncbox": "3.0.0",
"axios": "1.6.8",
"axios": "1.7.2",
"bluebird": "3.7.2",
"body-parser": "1.20.2",
"express": "4.19.2",
"http-status-codes": "2.3.0",
"lodash": "4.17.21",
"lru-cache": "10.2.0",
"lru-cache": "10.2.2",
"method-override": "3.0.0",
"morgan": "1.10.0",
"path-to-regexp": "6.2.2",
"serve-favicon": "2.5.0",
"source-map-support": "0.5.21",
"type-fest": "4.10.1",
"type-fest": "4.19.0",
"validate.js": "0.13.1"
},
"optionalDependencies": {
@@ -35,11 +35,9 @@ describe('Websockets (e2e)', function () {
ws.send(WS_DATA);
}
});
const previousListenerCount = baseServer.listenerCount('upgrade');
const endpoint = `${DEFAULT_WS_PATHNAME_PREFIX}/hello`;
const timeout = 5000;
await baseServer.addWebSocketHandler(endpoint, wss);
baseServer.listenerCount('upgrade').should.be.above(previousListenerCount);
_.keys(await baseServer.getWebSocketHandlers()).length.should.eql(1);
await new B((resolve, reject) => {
const client = new WebSocket(`ws://${TEST_HOST}:${port}${endpoint}`);
@@ -78,7 +76,6 @@ describe('Websockets (e2e)', function () {
client.on('error', resolve);
setTimeout(resolve, timeout);
});
baseServer.listenerCount('upgrade').should.be.above(previousListenerCount);
});
});
});
@@ -59,7 +59,7 @@ describe('server configuration', function () {
const app = fakeApp();
const configureRoutes = () => {};
configureServer({app, addRoutes: configureRoutes});
app.use.callCount.should.equal(14);
app.use.callCount.should.equal(15);
app.all.callCount.should.equal(4);
});
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.33](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.32...@appium/base-plugin@2.2.33) (2024-06-06)
**Note:** Version bump only for package @appium/base-plugin
## [2.2.32](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.31...@appium/base-plugin@2.2.32) (2024-05-27)
**Note:** Version bump only for package @appium/base-plugin
## [2.2.31](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.30...@appium/base-plugin@2.2.31) (2024-04-21)
**Note:** Version bump only for package @appium/base-plugin
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-plugin",
"version": "2.2.31",
"version": "2.2.33",
"description": "The base plugin used to create Appium 2.0 plugins",
"keywords": [
"automation",
@@ -40,8 +40,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.6.0",
"@appium/support": "^4.2.5"
"@appium/base-driver": "^9.8.0",
"@appium/support": "^4.3.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+29
View File
@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.0](https://github.com/appium/appium/compare/@appium/doctor@2.0.37...@appium/doctor@2.1.0) (2024-06-06)
### Features
* **appium:** Replace npmlog with the local fork ([#20190](https://github.com/appium/appium/issues/20190)) ([8915934](https://github.com/appium/appium/commit/8915934270243bfb46c4d104a098ce1cc481b0ff))
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.4.1 ([e3e1757](https://github.com/appium/appium/commit/e3e1757c564cffd467f3e9931f9aaa5a973fa825))
* **doctor:** update dependency appium-adb to v12.4.2 ([bea47aa](https://github.com/appium/appium/commit/bea47aacca1b1a27cce48b027246513e674c2bb7))
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
## [2.0.37](https://github.com/appium/appium/compare/@appium/doctor@2.0.36...@appium/doctor@2.0.37) (2024-05-27)
### Bug Fixes
* **doctor:** update dependency appium-adb to v12.0.9 ([fd63e9b](https://github.com/appium/appium/commit/fd63e9bdbd0c941453963cfb9636972129a3b57a))
* **doctor:** update dependency appium-adb to v12.2.0 ([e961a9b](https://github.com/appium/appium/commit/e961a9b9c5347ed85317de9bfc5687ad74a711ff))
* **doctor:** update dependency appium-adb to v12.3.2 ([f764c38](https://github.com/appium/appium/commit/f764c38c29c657133dec1ea3f0e3c6c2663ee7df))
* **doctor:** update dependency appium-adb to v12.4.0 ([33be2ee](https://github.com/appium/appium/commit/33be2ee45d725e6a64fc24683dfbcbff29dfbe84))
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
## [2.0.36](https://github.com/appium/appium/compare/@appium/doctor@2.0.35...@appium/doctor@2.0.36) (2024-04-21)
**Note:** Version bump only for package @appium/doctor
+11 -13
View File
@@ -44,28 +44,26 @@ export async function prompt(question) {
return await inquirer.prompt(question);
}
let actualLog;
let onLogMessageHandler = null;
export function configureBinaryLog(opts) {
actualLog = log.unwrap().log;
log.unwrap().log = function (level, prefix, msg) {
let l = this.levels[level];
if (l < this.levels[this.level]) return; // eslint-disable-line curly
actualLog(level, prefix, msg);
if (isFunction(opts.onLogMessage)) {
opts.onLogMessage(level, prefix, msg);
}
const internalLogger = log.unwrap();
if (isFunction(opts.onLogMessage) && !onLogMessageHandler) {
onLogMessageHandler = (/** @type {import('@appium/logger').MessageObject} */ msg) => opts.onLogMessage(
msg.level, msg.prefix, msg.message
);
internalLogger.on('log', onLogMessageHandler);
};
log.level = opts.debug ? 'debug' : 'info';
internalLogger.level = opts.debug ? 'debug' : 'info';
}
/**
* If {@link configureBinaryLog} was called, this will restore the original `log` function.
*/
export function resetLog() {
if (actualLog) {
log.unwrap().log = actualLog;
if (onLogMessageHandler) {
log.unwrap().off('log', onLogMessageHandler);
onLogMessageHandler = null;
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/doctor",
"version": "2.0.36",
"version": "2.1.0",
"description": "Test environment for fitness to run Appium",
"keywords": [
"automation",
@@ -44,16 +44,16 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^4.2.5",
"@appium/support": "^4.3.0",
"@colors/colors": "1.6.0",
"@types/bluebird": "3.5.42",
"@types/teen_process": "2.0.4",
"appium-adb": "12.0.8",
"appium-adb": "12.4.2",
"bluebird": "3.7.2",
"inquirer": "9.2.12",
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"teen_process": "2.1.1",
"teen_process": "2.1.4",
"yargs": "17.7.2"
},
"engines": {
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.9](https://github.com/appium/appium/compare/@appium/docutils@1.0.8...@appium/docutils@1.0.9) (2024-06-06)
### Bug Fixes
* **docutils:** update dependency yaml to v2.4.3 ([848eb00](https://github.com/appium/appium/commit/848eb00afbd1cab76a6a1c3d0f4a2a0b1acee0d0))
* **support:** update dependency @types/which to v3 ([#20164](https://github.com/appium/appium/issues/20164)) ([57b0406](https://github.com/appium/appium/commit/57b0406a33bc687c731f9e7fedd56e1b07c3cc67))
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
* **types:** update dependency type-fest to v4.19.0 ([#20193](https://github.com/appium/appium/issues/20193)) ([db62358](https://github.com/appium/appium/commit/db62358dbac25de2a75fdc7493338d98b1422c84))
## [1.0.8](https://github.com/appium/appium/compare/@appium/docutils@1.0.7...@appium/docutils@1.0.8) (2024-05-27)
### Bug Fixes
* **docutils:** update dependency yaml to v2.4.2 ([0211174](https://github.com/appium/appium/commit/0211174300cf91c1cad2364d8ce3de7100139f5c))
* **support:** update dependency semver to v7.6.1 ([b5fd5b6](https://github.com/appium/appium/commit/b5fd5b6d2c585be8391b80ac67207da610e8ea64))
* **support:** update dependency semver to v7.6.2 ([1b4f36c](https://github.com/appium/appium/commit/1b4f36cebe7aaf99976f24346b2d1325b79fc55c))
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
* **types:** update dependency type-fest to v4.18.2 ([#20103](https://github.com/appium/appium/issues/20103)) ([3b43be1](https://github.com/appium/appium/commit/3b43be17321f2ca16bac0abedabf9ef8cffa098a))
* **types:** update dependency type-fest to v4.18.3 ([#20149](https://github.com/appium/appium/issues/20149)) ([d5369f8](https://github.com/appium/appium/commit/d5369f8b08e7439282c5a211e684b154cc9f0051))
## [1.0.7](https://github.com/appium/appium/compare/@appium/docutils@1.0.6...@appium/docutils@1.0.7) (2024-04-21)
**Note:** Version bump only for package @appium/docutils
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/docutils",
"version": "1.0.7",
"version": "1.0.9",
"description": "Documentation generation utilities for Appium and related projects",
"keywords": [
"automation",
@@ -48,10 +48,10 @@
"start": "node ./build/lib/cli/index.js"
},
"dependencies": {
"@appium/support": "^4.2.5",
"@appium/support": "^4.3.0",
"@appium/tsconfig": "^0.3.3",
"@sliphua/lilconfig-ts-loader": "3.2.2",
"@types/which": "3.0.3",
"@types/which": "3.0.4",
"chalk": "4.1.2",
"consola": "3.2.3",
"diff": "5.2.0",
@@ -60,12 +60,12 @@
"lodash": "4.17.21",
"pkg-dir": "5.0.0",
"read-pkg": "5.2.0",
"semver": "7.6.0",
"semver": "7.6.2",
"source-map-support": "0.5.21",
"teen_process": "2.1.1",
"type-fest": "4.10.1",
"teen_process": "2.1.4",
"type-fest": "4.19.0",
"typescript": "5.4.5",
"yaml": "2.4.1",
"yaml": "2.4.3",
"yargs": "17.7.2",
"yargs-parser": "21.1.1"
},
+4 -4
View File
@@ -1,5 +1,5 @@
mkdocs==1.5.3
mkdocs-git-revision-date-localized-plugin==1.2.4
mkdocs-material==9.5.18
mkdocs==1.6.0
mkdocs-git-revision-date-localized-plugin==1.2.6
mkdocs-material==9.5.25
mkdocs-redirects==1.2.1
mike==2.0.0
mike==2.1.1
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.4.25](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.24...@appium/driver-test-support@0.4.25) (2024-06-06)
### Bug Fixes
* **types:** update dependency type-fest to v4.19.0 ([#20193](https://github.com/appium/appium/issues/20193)) ([db62358](https://github.com/appium/appium/commit/db62358dbac25de2a75fdc7493338d98b1422c84))
## [0.4.24](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.23...@appium/driver-test-support@0.4.24) (2024-05-27)
### Bug Fixes
* **driver-test-support:** update definitelytyped ([a8d36b0](https://github.com/appium/appium/commit/a8d36b0a981daf524bd4af422904193c087a8c2c))
* **support:** update dependency axios to v1.7.1 ([11510cb](https://github.com/appium/appium/commit/11510cb1a5d50a20ced884e5404d0be1e04ff142))
* **support:** update dependency axios to v1.7.2 ([a876f11](https://github.com/appium/appium/commit/a876f112b51dd25f70094b9e75330b9558050e42))
* **test-support:** update dependency sinon to v17.0.2 ([#20078](https://github.com/appium/appium/issues/20078)) ([2acf262](https://github.com/appium/appium/commit/2acf262482750bb7b56f46dc3b9ad3168de8000a))
* **test-support:** update dependency sinon to v18 ([#20128](https://github.com/appium/appium/issues/20128)) ([465495e](https://github.com/appium/appium/commit/465495ef2ae436039f80a1ba397bb26a22ec01c6))
* **types:** update dependency type-fest to v4.18.2 ([#20103](https://github.com/appium/appium/issues/20103)) ([3b43be1](https://github.com/appium/appium/commit/3b43be17321f2ca16bac0abedabf9ef8cffa098a))
* **types:** update dependency type-fest to v4.18.3 ([#20149](https://github.com/appium/appium/issues/20149)) ([d5369f8](https://github.com/appium/appium/commit/d5369f8b08e7439282c5a211e684b154cc9f0051))
## [0.4.23](https://github.com/appium/appium/compare/@appium/driver-test-support@0.4.22...@appium/driver-test-support@0.4.23) (2024-04-21)
**Note:** Version bump only for package @appium/driver-test-support
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/driver-test-support",
"version": "0.4.23",
"version": "0.4.25",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
@@ -41,18 +41,18 @@
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^0.17.0",
"@types/lodash": "4.17.0",
"@appium/types": "^0.19.0",
"@types/lodash": "4.17.4",
"@types/stoppable": "1.1.3",
"axios": "1.6.8",
"axios": "1.7.2",
"bluebird": "3.7.2",
"chai": "4.4.1",
"get-port": "5.1.1",
"lodash": "4.17.21",
"sinon": "17.0.1",
"sinon": "18.0.0",
"source-map-support": "0.5.21",
"stoppable": "1.1.0",
"type-fest": "4.10.1"
"type-fest": "4.19.0"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.30](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.29...@appium/execute-driver-plugin@3.0.30) (2024-06-06)
### Bug Fixes
* **execute-driver-plugin:** update dependency webdriverio to v8.38.2 ([c8f9979](https://github.com/appium/appium/commit/c8f997973cc21673f95d02c92d43a2981b8713b8))
## [3.0.29](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.28...@appium/execute-driver-plugin@3.0.29) (2024-05-27)
### Bug Fixes
* **execute-driver-plugin:** update dependency webdriverio to v8.37.0 ([27ff9a0](https://github.com/appium/appium/commit/27ff9a00ef65a66aea8da8e3be5ffebb6d8c65e1))
* **execute-driver-plugin:** update dependency webdriverio to v8.38.0 ([e80b682](https://github.com/appium/appium/commit/e80b6826efbe14ecfa6c0ff1f6aceb4db99b82d8))
## [3.0.28](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.27...@appium/execute-driver-plugin@3.0.28) (2024-04-21)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/execute-driver-plugin",
"version": "3.0.28",
"version": "3.0.30",
"description": "Plugin for batching and executing driver commands with Appiums",
"keywords": [
"automation",
@@ -43,7 +43,7 @@
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"vm2": "3.9.19",
"webdriverio": "8.36.1"
"webdriverio": "8.38.2"
},
"peerDependencies": {
"appium": "^2.0.0-beta.35"
+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.
## [5.5.2](https://github.com/appium/appium/compare/@appium/fake-driver@5.5.1...@appium/fake-driver@5.5.2) (2024-05-27)
**Note:** Version bump only for package @appium/fake-driver
## [5.5.1](https://github.com/appium/appium/compare/@appium/fake-driver@5.5.0...@appium/fake-driver@5.5.1) (2024-02-13)
+1 -1
View File
@@ -122,7 +122,7 @@ const ElementsMixin: FakeDriverElementsMixin = {
async getCssProperty(this: FakeDriver, prop: string, elementId: string) {
this.assertWebviewContext();
const el = this.getElement(elementId);
return el.getCss(prop);
return el.getCss(prop) ?? '';
},
async getLocation(this: FakeDriver, elementId: string) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-driver",
"version": "5.5.1",
"version": "5.5.2",
"description": "Mock driver used internally by Appium for testing. Ignore",
"keywords": [
"automation",
@@ -102,9 +102,9 @@ function elementTests() {
let {elementId} = await driver.$('body');
(await driver.getElementCSSValue(elementId, 'background-color')).should.equal('#000');
});
it('should return null for an unspecified css property', async function () {
it('should return empty string for an unspecified css property', async function () {
let {elementId} = await driver.$('body');
should.equal(await driver.getElementCSSValue(elementId, 'font-size'), null);
should.equal(await driver.getElementCSSValue(elementId, 'font-size'), '');
});
});
}
+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.
## [3.1.37](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.36...@appium/fake-plugin@3.1.37) (2024-06-06)
**Note:** Version bump only for package @appium/fake-plugin
## [3.1.36](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.35...@appium/fake-plugin@3.1.36) (2024-05-27)
**Note:** Version bump only for package @appium/fake-plugin
## [3.1.35](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.34...@appium/fake-plugin@3.1.35) (2024-04-21)
**Note:** Version bump only for package @appium/fake-plugin
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-plugin",
"version": "3.1.35",
"version": "3.1.37",
"description": "A fake Appium 2.0 plugin",
"keywords": [
"automation",
@@ -39,8 +39,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-plugin": "^2.2.31",
"@appium/support": "^4.2.5",
"@appium/base-plugin": "^2.2.33",
"@appium/support": "^4.3.0",
"@types/bluebird": "3.5.42",
"bluebird": "3.7.2",
"lodash": "4.17.21",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.8](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.7...@appium/images-plugin@3.0.8) (2024-06-06)
**Note:** Version bump only for package @appium/images-plugin
## [3.0.7](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.6...@appium/images-plugin@3.0.7) (2024-05-27)
### Bug Fixes
* **images-plugin:** update dependency lru-cache to v10.2.1 ([#20039](https://github.com/appium/appium/issues/20039)) ([f645b50](https://github.com/appium/appium/commit/f645b50d9d6008d374bc131be78d8be5d84a7f53))
* **images-plugin:** update dependency lru-cache to v10.2.2 ([#20052](https://github.com/appium/appium/issues/20052)) ([f8d1fb7](https://github.com/appium/appium/commit/f8d1fb7df7b7d093d3664f58830cad5b5dffa244))
## [3.0.6](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.5...@appium/images-plugin@3.0.6) (2024-04-21)
**Note:** Version bump only for package @appium/images-plugin
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/images-plugin",
"version": "3.0.6",
"version": "3.0.8",
"description": "Plugin for working with images and image elements in Appium",
"keywords": [
"automation",
@@ -39,11 +39,11 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/opencv": "^3.0.3",
"@appium/support": "^4.2.5",
"@appium/opencv": "^3.0.4",
"@appium/support": "^4.3.0",
"lodash": "4.17.21",
"lru-cache": "10.2.0",
"sharp": "0.33.3",
"lru-cache": "10.2.2",
"sharp": "0.33.4",
"source-map-support": "0.5.21"
},
"peerDependencies": {
+12
View File
@@ -0,0 +1,12 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## 1.1.0 (2024-06-06)
### Features
* **appium:** Replace npmlog with the local fork ([#20190](https://github.com/appium/appium/issues/20190)) ([8915934](https://github.com/appium/appium/commit/8915934270243bfb46c4d104a098ce1cc481b0ff))
* **logger:** add packages/logger package from npmlog ([#20161](https://github.com/appium/appium/issues/20161)) ([70449cd](https://github.com/appium/appium/commit/70449cd077d7efc3dbc8aa498ee2072cc2dc0f22))
+18
View File
@@ -0,0 +1,18 @@
ISC License
Copyright npm, Inc.
Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this
permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND NPM DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
EVENT SHALL NPM BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
USE OR PERFORMANCE OF THIS SOFTWARE.
+31
View File
@@ -0,0 +1,31 @@
# @appium/logger
> Appium's logging functionality
## Installation
```console
npm install @appium/logger --save
```
## Basic Usage
```js
import log from '@appium/logger';
// additional stuff ---------------------------+
// message ----------+ |
// prefix ----+ | |
// level -+ | | |
// v v v v
log.info('fyi', 'I have a kitty cat: %j', myKittyCat);
```
## History
This module is forked from [npmlog](https://github.com/npm/npmlog) under ISC License because the original project has been archived.
Please check [the npmlog changelog](https://github.com/npm/npmlog/blob/main/CHANGELOG.md) to see the list of former module updates before it was forked.
## License
ISC License
+5
View File
@@ -0,0 +1,5 @@
import log from './lib/log';
export type * from './lib/types';
export {log};
export default log;
+313
View File
@@ -0,0 +1,313 @@
import {EventEmitter} from 'node:events';
// @ts-ignore This module does not provide type definitons
import setBlocking from 'set-blocking';
// @ts-ignore This module does not provide type definitons
import consoleControl from 'console-control-strings';
import * as util from 'node:util';
import type {MessageObject, StyleObject, Logger, LogLevel} from './types';
import type {Writable} from 'node:stream';
const DEFAULT_LOG_LEVELS: any[][] = [
['silly', -Infinity, {inverse: true}, 'sill'],
['verbose', 1000, {fg: 'cyan', bg: 'black'}, 'verb'],
['info', 2000, {fg: 'green'}],
['timing', 2500, {fg: 'green', bg: 'black'}],
['http', 3000, {fg: 'green', bg: 'black'}],
['notice', 3500, {fg: 'cyan', bg: 'black'}],
['warn', 4000, {fg: 'black', bg: 'yellow'}, 'WARN'],
['error', 5000, {fg: 'red', bg: 'black'}, 'ERR!'],
['silent', Infinity],
] as const;
setBlocking(true);
export class Log extends EventEmitter implements Logger {
level: LogLevel | string;
record: MessageObject[];
maxRecordSize: number;
prefixStyle: StyleObject;
headingStyle: StyleObject;
heading: string;
stream: Writable; // Defaults to process.stderr
_colorEnabled?: boolean;
_buffer: MessageObject[];
_style: Record<LogLevel | string, StyleObject | undefined>;
_levels: Record<LogLevel | string, number>;
_disp: Record<LogLevel | string, number | string>;
_id: number;
_paused: boolean;
constructor() {
super();
this.level = 'info';
this._buffer = [];
this.record = [];
this.maxRecordSize = 10000;
this.stream = process.stderr;
this.heading = '';
this.prefixStyle = {fg: 'magenta'};
this.headingStyle = {fg: 'white', bg: 'black'};
this._id = 0;
this._paused = false;
this._style = {};
this._levels = {};
this._disp = {};
this.initDefaultLevels();
// allow 'error' prefix
this.on('error', () => {});
}
private useColor(): boolean {
// by default, decide based on tty-ness.
return (
this._colorEnabled ?? Boolean(this.stream && 'isTTY' in this.stream && this.stream.isTTY)
);
}
enableColor(): void {
this._colorEnabled = true;
}
disableColor(): void {
this._colorEnabled = false;
}
// this functionality has been deliberately disabled
enableUnicode(): void {}
disableUnicode(): void {}
enableProgress(): void {}
disableProgress(): void {}
progressEnabled(): boolean {
return false;
}
/**
* Temporarily stop emitting, but don't drop
*/
pause(): void {
this._paused = true;
}
resume(): void {
if (!this._paused) {
return;
}
this._paused = false;
const b = this._buffer;
this._buffer = [];
for (const m of b) {
this.emitLog(m);
}
}
silly(prefix: string, message: any, ...args: any[]): void {
this.log('silly', prefix, message, ...args);
}
verbose(prefix: string, message: any, ...args: any[]): void {
this.log('verbose', prefix, message, ...args);
}
info(prefix: string, message: any, ...args: any[]): void {
this.log('info', prefix, message, ...args);
}
timing(prefix: string, message: any, ...args: any[]): void {
this.log('timing', prefix, message, ...args);
}
http(prefix: string, message: any, ...args: any[]): void {
this.log('http', prefix, message, ...args);
}
notice(prefix: string, message: any, ...args: any[]): void {
this.log('notice', prefix, message, ...args);
}
warn(prefix: string, message: any, ...args: any[]): void {
this.log('warn', prefix, message, ...args);
}
error(prefix: string, message: any, ...args: any[]): void {
this.log('error', prefix, message, ...args);
}
silent(prefix: string, message: any, ...args: any[]): void {
this.log('silent', prefix, message, ...args);
}
addLevel(level: string, n: number, style?: StyleObject, disp?: string): void {
this._levels[level] = n;
this._style[level] = style;
if (!(this as any)[level]) {
(this as any)[level] = (prefix: string, message: any, ...args: any[]) => {
this.log(level, prefix, message, ...args);
};
}
// If 'disp' is null or undefined, use the level as a default
this._disp[level] = disp ?? level;
}
/**
* Creates a log message
* @param level
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
log(level: LogLevel | string, prefix: string, message: any, ...args: any[]): void {
const l = this._levels[level];
if (l === undefined) {
this.emit('error', new Error(util.format('Undefined log level: %j', level)));
return;
}
const messageArguments: any[] = [];
let stack: string | null = null;
for (const formatArg of [message, ...args]) {
messageArguments.push(formatArg);
// resolve stack traces to a plain string.
if (typeof formatArg === 'object' && formatArg instanceof Error && formatArg.stack) {
Object.defineProperty(formatArg, 'stack', {
value: (stack = formatArg.stack + ''),
enumerable: true,
writable: true,
});
}
}
if (stack) {
messageArguments.unshift(`${stack}\n`);
}
const formattedMessage: string = util.format(...messageArguments);
const m: MessageObject = {
id: this._id++,
timestamp: Date.now(),
level,
prefix: String(prefix || ''),
message: formattedMessage,
};
this.emit('log', m);
this.emit('log.' + level, m);
if (m.prefix) {
this.emit(m.prefix, m);
}
this.record.push(m);
const mrs = this.maxRecordSize;
if (this.record.length > mrs) {
this.record.shift();
}
this.emitLog(m);
}
private emitLog(m: MessageObject): void {
if (this._paused) {
this._buffer.push(m);
return;
}
const l = this._levels[m.level];
if (l === undefined) {
return;
}
if (l < this._levels[this.level]) {
return;
}
if (l > 0 && !isFinite(l)) {
return;
}
// If 'disp' is null or undefined, use the lvl as a default
// Allows: '', 0 as valid disp
const disp = this._disp[m.level];
this.clearProgress();
for (const line of m.message.split(/\r?\n/)) {
const heading = this.heading;
if (heading) {
this.write(heading, this.headingStyle);
this.write(' ');
}
this.write(String(disp), this._style[m.level]);
const p = m.prefix || '';
if (p) {
this.write(' ');
}
this.write(p, this.prefixStyle);
this.write(` ${line}\n`);
}
this.showProgress();
}
private _format(msg: string, style: StyleObject = {}): string | undefined {
if (!this.stream) {
return;
}
let output = '';
if (this.useColor()) {
const settings: string[] = [];
if (style.fg) {
settings.push(style.fg);
}
if (style.bg) {
settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1));
}
if (style.bold) {
settings.push('bold');
}
if (style.underline) {
settings.push('underline');
}
if (style.inverse) {
settings.push('inverse');
}
if (settings.length) {
output += consoleControl.color(settings);
}
if (style.bell) {
output += consoleControl.beep();
}
}
output += msg;
if (this.useColor()) {
output += consoleControl.color('reset');
}
return output;
}
private write(msg: string, style: StyleObject = {}): void {
if (!this.stream) {
return;
}
const formatted = this._format(msg, style);
if (formatted !== undefined) {
this.stream.write(formatted);
}
}
private initDefaultLevels(): void {
for (const [level, index, style, disp] of DEFAULT_LOG_LEVELS) {
this._levels[level] = index;
this._style[level] = style;
this._disp[level] = disp ?? level;
}
}
// this functionality has been deliberately disabled
private clearProgress(): void {}
private showProgress(): void {}
}
export const GLOBAL_LOG = new Log();
export default GLOBAL_LOG;
+83
View File
@@ -0,0 +1,83 @@
import type {EventEmitter} from 'node:events';
export interface Logger extends EventEmitter {
level: string;
record: MessageObject[];
maxRecordSize: number;
prefixStyle: StyleObject;
headingStyle: StyleObject;
heading: string;
stream: any; // Defaults to process.stderr
/**
* Creates a log message
* @param level
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
log(level: LogLevel | string, prefix: string, message: any, ...args: any[]): void;
/**
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
silly(prefix: string, message: any, ...args: any[]): void;
verbose(prefix: string, message: any, ...args: any[]): void;
info(prefix: string, message: any, ...args: any[]): void;
timing(prefix: string, message: any, ...args: any[]): void;
http(prefix: string, message: any, ...args: any[]): void;
notice(prefix: string, message: any, ...args: any[]): void;
warn(prefix: string, message: any, ...args: any[]): void;
error(prefix: string, message: any, ...args: any[]): void;
silent(prefix: string, message: any, ...args: any[]): void;
enableColor(): void;
disableColor(): void;
enableProgress(): void;
disableProgress(): void;
progressEnabled(): boolean;
enableUnicode(): void;
disableUnicode(): void;
pause(): void;
resume(): void;
addLevel(level: string, n: number, style?: StyleObject, disp?: string): void;
// Allows for custom log levels
// log.addLevel("custom", level)
// log.custom(prefix, message)
[key: string]: any;
}
export type LogLevel =
| 'silly'
| 'verbose'
| 'info'
| 'timing'
| 'http'
| 'notice'
| 'warn'
| 'error'
| 'silent';
export interface StyleObject {
fg?: string | undefined;
bg?: string | undefined;
bold?: boolean | undefined;
inverse?: boolean | undefined;
underline?: boolean | undefined;
bell?: boolean | undefined;
}
export interface MessageObject {
id: number;
timestamp: number;
level: string;
prefix: string;
message: string;
}
+56
View File
@@ -0,0 +1,56 @@
{
"name": "@appium/logger",
"version": "1.1.0",
"author": "https://github.com/appium",
"description": "A Universal Logger For The Appium Ecosystem",
"repository": {
"type": "git",
"url": "https://github.com/appium/appium.git",
"directory": "packages/logger"
},
"main": "build/index.js",
"types": "index.ts",
"files": [
"index.ts",
"lib",
"build",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo",
"CHANGELOG.md"
],
"directories": {
"lib": "./lib"
},
"scripts": {
"test": "npm run test:unit",
"test:smoke": "node ./build/index.js",
"test:unit": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.js\""
},
"dependencies": {
"console-control-strings": "1.1.0",
"set-blocking": "2.0.0"
},
"license": "Apache-2.0",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
},
"bugs": {
"url": "https://github.com/appium/appium/issues"
},
"homepage": "https://appium.io",
"publishConfig": {
"access": "public"
},
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
]
}
+425
View File
@@ -0,0 +1,425 @@
/* eslint-disable no-console */
import { Log } from '../../lib/log';
import {Stream} from 'node:stream';
describe('basic', async function () {
const chai = await import('chai');
chai.should();
let log;
describe('logging', function () {
let s;
let result = [];
let logEvents = [];
let logInfoEvents = [];
let logPrefixEvents = [];
const resultExpect = [
// eslint-disable-next-line max-len
'\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[7msill\u001b[0m \u001b[0m\u001b[35msilly prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mverb\u001b[0m \u001b[0m\u001b[35mverbose prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32minfo\u001b[0m \u001b[0m\u001b[35minfo prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mtiming\u001b[0m \u001b[0m\u001b[35mtiming prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mhttp\u001b[0m \u001b[0m\u001b[35mhttp prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mnotice\u001b[0m \u001b[0m\u001b[35mnotice prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[30;43mWARN\u001b[0m \u001b[0m\u001b[35mwarn prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35merror prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32minfo\u001b[0m \u001b[0m\u001b[35minfo prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mtiming\u001b[0m \u001b[0m\u001b[35mtiming prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mhttp\u001b[0m \u001b[0m\u001b[35mhttp prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mnotice\u001b[0m \u001b[0m\u001b[35mnotice prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[30;43mWARN\u001b[0m \u001b[0m\u001b[35mwarn prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35merror prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m This is a longer\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m message, with some details\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m and maybe a stack.\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m \n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u0007noise\u001b[0m\u001b[35m\u001b[0m LOUD NOISES\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u0007noise\u001b[0m \u001b[0m\u001b[35merror\u001b[0m erroring\n',
'\u001b[0m',
];
const logPrefixEventsExpect = [
{ id: 2,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 11,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 20,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
];
// should be the same.
const logInfoEventsExpect = logPrefixEventsExpect;
const logEventsExpect = [
{ id: 0,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 1,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 2,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 3,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 4,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 5,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 6,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 7,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 8,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 9,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 10,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 11,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 12,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 13,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 14,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 15,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 16,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 17,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 18,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 19,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 20,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 21,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 22,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 23,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 24,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 25,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 26,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 27,
level: 'error',
prefix: '404',
message: 'This is a longer\nmessage, with some details\nand maybe a stack.\n',
},
{ id: 28,
level: 'noise',
prefix: '',
message: 'LOUD NOISES',
},
{ id: 29,
level: 'noise',
prefix: 'error',
message: 'erroring',
},
];
this.beforeEach(function () {
result = [];
logEvents = [];
logInfoEvents = [];
logPrefixEvents = [];
log = new Log();
s = new Stream();
s.write = (m) => result.push(m);
s.writable = true;
s.isTTY = true;
s.end = () => {};
log.stream = s;
log.heading = 'npm';
});
it('should work', function () {
log.stream.should.equal(s);
log.on('log', logEvents.push.bind(logEvents));
log.on('log.info', logInfoEvents.push.bind(logInfoEvents));
log.on('info prefix', logPrefixEvents.push.bind(logPrefixEvents));
console.error('log.level=silly');
log.level = 'silly';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
console.error('log.level=silent');
log.level = 'silent';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
console.error('log.level=info');
log.level = 'info';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('404', 'This is a longer\n' +
'message, with some details\n' +
'and maybe a stack.\n');
log.addLevel('noise', 10000, { bell: true });
log.noise(false, 'LOUD NOISES');
log.noise('error', 'erroring');
result.join('').trim().should.equal(resultExpect.join('').trim());
const withoutTimestamps = (x) => x.map((m) => {
Boolean(m.timestamp).should.be.true;
const copy = JSON.parse(JSON.stringify(m));
delete copy.timestamp;
return copy;
});
withoutTimestamps(log.record).should.eql(logEventsExpect);
withoutTimestamps(logEvents).should.eql(logEventsExpect);
withoutTimestamps(logInfoEvents).should.eql(logInfoEventsExpect);
withoutTimestamps(logPrefixEvents).should.eql(logPrefixEventsExpect);
});
});
describe('utils', function () {
it('enableColor', function () {
log.enableColor();
log.useColor().should.be.true;
});
it('disableColor', function () {
log.disableColor();
log.useColor().should.be.false;
});
it('_buffer while paused', function () {
log.pause();
log.log('verbose', 'test', 'test log');
log._buffer.length.should.equal(1);
log.resume();
log._buffer.length.should.equal(0);
});
});
describe('log.log', function () {
beforeEach(function () {
log = new Log();
});
it('emits error on bad loglevel', async function() {
await new Promise((resolve, reject) => {
log.once('error', (err) => {
/Undefined log level: "asdf"/.test(err).should.be.true;
resolve();
});
log.log('asdf', 'bad loglevel');
setTimeout(reject, 1000);
});
});
it('resolves stack traces to a plain string', async function() {
await new Promise((resolve, reject) => {
log.once('log', (m) => {
/Error: with a stack trace/.test(m.message).should.be.true;
/at Test/.test(m.message).should.be.true;
resolve();
});
const err = new Error('with a stack trace');
log.log('verbose', 'oops', err);
setTimeout(reject, 1000);
});
});
it('max record size', function() {
log.maxRecordSize = 3;
log.log('verbose', 'test', 'log 1');
log.log('verbose', 'test', 'log 2');
log.log('verbose', 'test', 'log 3');
log.log('verbose', 'test', 'log 4');
log.record.length.should.equal(3);
});
});
describe('stream', function () {
beforeEach(function () {
log = new Log();
});
it('write with no stream', function() {
log.stream = null;
log.write('message');
});
});
describe('emitLog', function () {
beforeEach(function () {
log = new Log();
});
it('to nonexistant level', function() {
log.emitLog({ prefix: 'test', level: 'asdf' });
});
});
describe('format', function () {
beforeEach(function () {
log = new Log();
});
it('with nonexistant stream', function() {
log.stream = null;
(log._format('message') === undefined).should.be.true;
});
it('fg', function () {
log.enableColor();
const o = log._format('test message', { bg: 'blue' });
o.includes('\u001b[44mtest message\u001b[0m').should.be.true;
});
it('bg', function () {
log.enableColor();
const o = log._format('test message', { bg: 'white' });
o.includes('\u001b[47mtest message\u001b[0m').should.be.true;
});
it('bold', function () {
log.enableColor();
const o = log._format('test message', { bold: true });
o.includes('\u001b[1mtest message\u001b[0m').should.be.true;
});
it('underline', function () {
log.enableColor();
const o = log._format('test message', { underline: true });
o.includes('\u001b[4mtest message\u001b[0m').should.be.true;
});
it('inverse', function () {
log.enableColor();
const o = log._format('test message', { inverse: true });
o.includes('\u001b[7mtest message\u001b[0m').should.be.true;
});
});
});
@@ -0,0 +1,34 @@
import { Log } from '../../lib/log';
import { waitForCondition } from 'asyncbox';
describe('display', function () {
let log;
describe('explicitly set new log level display to empty string', function () {
let actual;
beforeEach(function () {
actual = '';
log = new Log();
log.write = (msg) => {
actual += msg;
};
});
it('explicitly set new log level display to empty string', async function () {
log.addLevel('explicitNoLevelDisplayed', 20000, {}, '');
log.explicitNoLevelDisplayed('1', '2');
await waitForCondition(() => actual.trim() === '1 2', {waitMs: 1000, intervalMs: 50});
actual = '';
log.explicitNoLevelDisplayed('', '1');
await waitForCondition(() => actual.trim() === '1', {waitMs: 1000, intervalMs: 50});
});
it('explicitly set new log level display to 0', async function () {
log.addLevel('explicitNoLevelDisplayed', 20000, {}, 0);
log.explicitNoLevelDisplayed('', '1');
await waitForCondition(() => actual.trim() === '0 1', {waitMs: 1000, intervalMs: 50});
});
});
});
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"strict": false,
"outDir": "build",
"types": ["node"],
"checkJs": true
},
"include": [
"index.ts",
"lib"
]
}
+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.0.4](https://github.com/appium/appium/compare/@appium/opencv@3.0.3...@appium/opencv@3.0.4) (2024-05-27)
**Note:** Version bump only for package @appium/opencv
## [3.0.3](https://github.com/appium/appium/compare/@appium/opencv@3.0.2...@appium/opencv@3.0.3) (2024-04-08)
**Note:** Version bump only for package @appium/opencv
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/opencv",
"version": "3.0.3",
"version": "3.0.4",
"description": "OpenCV-related helper library",
"keywords": [
"automation",
@@ -45,7 +45,7 @@
"bluebird": "3.7.2",
"lodash": "4.17.21",
"opencv-bindings": "4.5.5",
"sharp": "0.33.3",
"sharp": "0.33.4",
"source-map-support": "0.5.21"
},
"engines": {
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.3.32](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.31...@appium/plugin-test-support@0.3.32) (2024-06-06)
### Bug Fixes
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
## [0.3.31](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.30...@appium/plugin-test-support@0.3.31) (2024-05-27)
### Bug Fixes
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
## [0.3.30](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.29...@appium/plugin-test-support@0.3.30) (2024-04-21)
**Note:** Version bump only for package @appium/plugin-test-support
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/plugin-test-support",
"version": "0.3.30",
"version": "0.3.32",
"description": "Testing utilities for Appium plugins",
"keywords": [
"automation",
@@ -41,11 +41,11 @@
"test:unit": "exit 0"
},
"dependencies": {
"@appium/types": "^0.17.0",
"@appium/types": "^0.19.0",
"get-port": "5.1.1",
"log-symbols": "4.1.0",
"source-map-support": "0.5.21",
"teen_process": "2.1.1"
"teen_process": "2.1.4"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.3.0](https://github.com/appium/appium/compare/@appium/support@4.2.6...@appium/support@4.3.0) (2024-06-06)
### Features
* **appium:** Replace npmlog with the local fork ([#20190](https://github.com/appium/appium/issues/20190)) ([8915934](https://github.com/appium/appium/commit/8915934270243bfb46c4d104a098ce1cc481b0ff))
### Bug Fixes
* **support:** Make sure response stream is defined while stopping it ([#20189](https://github.com/appium/appium/issues/20189)) ([4fd6893](https://github.com/appium/appium/commit/4fd68931465dd2ed3490bcebad7ccff3dacc9b3a))
* **support:** update dependency @types/which to v3 ([#20164](https://github.com/appium/appium/issues/20164)) ([57b0406](https://github.com/appium/appium/commit/57b0406a33bc687c731f9e7fedd56e1b07c3cc67))
* **support:** update dependency teen_process to v2.1.4 ([96a7a3f](https://github.com/appium/appium/commit/96a7a3f23bead7c1d18700550138cd2d70ce5e3c))
* **types:** update dependency type-fest to v4.19.0 ([#20193](https://github.com/appium/appium/issues/20193)) ([db62358](https://github.com/appium/appium/commit/db62358dbac25de2a75fdc7493338d98b1422c84))
## [4.2.6](https://github.com/appium/appium/compare/@appium/support@4.2.5...@appium/support@4.2.6) (2024-05-27)
### Bug Fixes
* **support:** update dependency axios to v1.7.1 ([11510cb](https://github.com/appium/appium/commit/11510cb1a5d50a20ced884e5404d0be1e04ff142))
* **support:** update dependency axios to v1.7.2 ([a876f11](https://github.com/appium/appium/commit/a876f112b51dd25f70094b9e75330b9558050e42))
* **support:** update dependency glob to v10 ([#20127](https://github.com/appium/appium/issues/20127)) ([95be7dd](https://github.com/appium/appium/commit/95be7dd47748e9d20bfb38e24c469fda81fe93e4))
* **support:** update dependency glob to v10.3.16 ([#20134](https://github.com/appium/appium/issues/20134)) ([56f72c0](https://github.com/appium/appium/commit/56f72c0af70d8a90a658035ab66e18e90b6f0b8c))
* **support:** update dependency glob to v10.4.1 ([#20141](https://github.com/appium/appium/issues/20141)) ([82406f1](https://github.com/appium/appium/commit/82406f17759026000cef70ba258631adaa252358))
* **support:** update dependency semver to v7.6.1 ([b5fd5b6](https://github.com/appium/appium/commit/b5fd5b6d2c585be8391b80ac67207da610e8ea64))
* **support:** update dependency semver to v7.6.2 ([1b4f36c](https://github.com/appium/appium/commit/1b4f36cebe7aaf99976f24346b2d1325b79fc55c))
* **support:** update dependency teen_process to v2.1.3 ([b34ce3c](https://github.com/appium/appium/commit/b34ce3cff0279be5bb5dcb9228f7e058d27a5e72))
* **types:** update dependency type-fest to v4.18.2 ([#20103](https://github.com/appium/appium/issues/20103)) ([3b43be1](https://github.com/appium/appium/commit/3b43be17321f2ca16bac0abedabf9ef8cffa098a))
* **types:** update dependency type-fest to v4.18.3 ([#20149](https://github.com/appium/appium/issues/20149)) ([d5369f8](https://github.com/appium/appium/commit/d5369f8b08e7439282c5a211e684b154cc9f0051))
## [4.2.5](https://github.com/appium/appium/compare/@appium/support@4.2.4...@appium/support@4.2.5) (2024-04-21)
+4 -4
View File
@@ -1,6 +1,6 @@
// @ts-check
import npmlog from 'npmlog';
import globalLog from '@appium/logger';
import _ from 'lodash';
import {unleakString} from './util';
import moment from 'moment';
@@ -20,7 +20,7 @@ for (let level of LEVELS) {
/**
*
* @param {import('npmlog').Logger} logger
* @param {import('@appium/logger').Logger} logger
*/
function patchLogger(logger) {
if (!logger.debug) {
@@ -30,7 +30,7 @@ function patchLogger(logger) {
/**
*
* @returns {[import('npmlog').Logger, boolean]}
* @returns {[import('@appium/logger').Logger, boolean]}
*/
function _getLogger() {
// check if the user set the `_TESTING` or `_FORCE_LOGS` flag
@@ -46,7 +46,7 @@ function _getLogger() {
logger = mockLog;
} else {
// otherwise, either use the global, or a new `npmlog` object
logger = global._global_npmlog || npmlog;
logger = global._global_npmlog || globalLog;
// The default value is 10000, which causes excessive memory usage
logger.maxRecordSize = MAX_LOG_RECORDS_COUNT;
}
+8 -6
View File
@@ -172,13 +172,15 @@ class MJpegStream extends Writable {
* the HTTP request itself. Then reset the state.
*/
stop() {
if (!this.consumer) {
return;
if (this.consumer) {
this.consumer.unpipe(this);
}
if (this.responseStream) {
if (this.consumer) {
this.responseStream.unpipe(this.consumer);
}
this.responseStream.destroy();
}
this.responseStream.unpipe(this.consumer);
this.consumer.unpipe(this);
this.responseStream.destroy();
this.clear();
}
+5 -2
View File
@@ -122,8 +122,11 @@ function multiResolve(roots, ...args) {
return roots.map((root) => path.resolve(root, ...args));
}
/*
/**
* Parses an object if possible. Otherwise returns the object without parsing.
*
* @param {any} obj
* @returns {any}
*/
function safeJsonParse(obj) {
try {
@@ -178,7 +181,7 @@ function jsonStringify(obj, replacer = null, space = 2) {
function unwrapElement(el) {
for (const propName of [W3C_WEB_ELEMENT_IDENTIFIER, 'ELEMENT']) {
if (_.has(el, propName)) {
return el[propName];
return /** @type {string} */ (el[propName]);
}
}
return /** @type {string} */(el);

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