mirror of
https://github.com/appium/appium.git
synced 2026-11-01 23:00:54 +08:00
Compare commits
295
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4138c6ec9 | ||
|
|
24a039b432 | ||
|
|
5ca62f8bfa | ||
|
|
d5369f8b08 | ||
|
|
33be2ee45d | ||
|
|
e3dcd66714 | ||
|
|
e80b6826ef | ||
|
|
275790ec01 | ||
|
|
82406f1775 | ||
|
|
a015f835dd | ||
|
|
27ff9a00ef | ||
|
|
a876f112b5 | ||
|
|
56f72c0af7 | ||
|
|
8c97ac5800 | ||
|
|
70650921d8 | ||
|
|
465495ef2a | ||
|
|
440708c876 | ||
|
|
c805155863 | ||
|
|
95be7dd477 | ||
|
|
2b2fb185a6 | ||
|
|
f21e09fe72 | ||
|
|
11510cb1a5 | ||
|
|
f764c38c29 | ||
|
|
5e51f6c5f2 | ||
|
|
4b20eca627 | ||
|
|
b34ce3cff0 | ||
|
|
6f602d3e50 | ||
|
|
2ad91655c2 | ||
|
|
1b4f36cebe | ||
|
|
a8d36b0a98 | ||
|
|
0598a81d09 | ||
|
|
e972d668fe | ||
|
|
3b43be1732 | ||
|
|
deb9548369 | ||
|
|
2cbf3af759 | ||
|
|
233dbd22ff | ||
|
|
807183925c | ||
|
|
38a506bbcd | ||
|
|
5e67a15299 | ||
|
|
a722f48f14 | ||
|
|
db9fd98e7d | ||
|
|
22f40f094e | ||
|
|
3dd72b2d0c | ||
|
|
2acf262482 | ||
|
|
e961a9b9c5 | ||
|
|
b5fd5b6d2c | ||
|
|
fd63e9bdbd | ||
|
|
eb6e0f82b3 | ||
|
|
f6f51c4b9e | ||
|
|
690bebe4bc | ||
|
|
0492fe49dc | ||
|
|
8526cc0333 | ||
|
|
1bde482ac8 | ||
|
|
4d634642c8 | ||
|
|
06fc841c6a | ||
|
|
a52ac2139e | ||
|
|
3d438eb6a8 | ||
|
|
21ba471eb7 | ||
|
|
395dc225e4 | ||
|
|
448e94ee50 | ||
|
|
f8d1fb7df7 | ||
|
|
fe16faaacb | ||
|
|
0211174300 | ||
|
|
bfa8cbf7ea | ||
|
|
36f9cd54ba | ||
|
|
4e8c91c8f6 | ||
|
|
05aedb20eb | ||
|
|
f645b50d9d | ||
|
|
799b743d72 | ||
|
|
3423fd9206 | ||
|
|
cf3910fdab | ||
|
|
6344d14665 | ||
|
|
2c2141c8c3 | ||
|
|
9d8d43c89a | ||
|
|
9ad20ff1c2 | ||
|
|
de7e10187c | ||
|
|
77d2ef74bb | ||
|
|
2350c4cd2a | ||
|
|
77a27ab16c | ||
|
|
8cf3efdbed | ||
|
|
80542b1046 | ||
|
|
270fd42a20 | ||
|
|
2205d1d41e | ||
|
|
6738e3d9ee | ||
|
|
732cc586c2 | ||
|
|
407706cf5e | ||
|
|
72f32848b6 | ||
|
|
572037a0e2 | ||
|
|
d7cf013401 | ||
|
|
eb5ec8940a | ||
|
|
9f655f6f85 | ||
|
|
38ef95fe09 | ||
|
|
4d48010a30 | ||
|
|
b4b20e05fd | ||
|
|
5be7f5d319 | ||
|
|
d44e66977c | ||
|
|
1f368828e6 | ||
|
|
b8368bb666 | ||
|
|
d55f13b2ed | ||
|
|
5899c2db93 | ||
|
|
1be08eca81 | ||
|
|
d5af68ecdd | ||
|
|
59ef09d955 | ||
|
|
ee8425a001 | ||
|
|
3936cb43dc | ||
|
|
f88bd3cb88 | ||
|
|
665219b7e0 | ||
|
|
b8f2dd8508 | ||
|
|
b139a2d313 | ||
|
|
26ab0ef759 | ||
|
|
85d06086dd | ||
|
|
a6d49c1ac8 | ||
|
|
dde504d501 | ||
|
|
9a0c059c3a | ||
|
|
c928a75f4a | ||
|
|
b4369473ee | ||
|
|
66626d26e5 | ||
|
|
7cb1621e80 | ||
|
|
03f8dc9b18 | ||
|
|
63d7550d02 | ||
|
|
a74132cc96 | ||
|
|
5f9aa955f1 | ||
|
|
de057fa8b0 | ||
|
|
cf792279b1 | ||
|
|
2687fa47a0 | ||
|
|
984e54a7ae | ||
|
|
cf17ec7a80 | ||
|
|
17e0dc03f4 | ||
|
|
caa4e586b1 | ||
|
|
7fc7446612 | ||
|
|
111b05c922 | ||
|
|
bd6ab81c94 | ||
|
|
3ce4f96e0b | ||
|
|
471e95504c | ||
|
|
47765747b6 | ||
|
|
2448fa0145 | ||
|
|
e7df8823e8 | ||
|
|
b126df6234 | ||
|
|
0e6c3eede2 | ||
|
|
3da17b6ecd | ||
|
|
636d6b3802 | ||
|
|
97c8ab5384 | ||
|
|
79378e4c20 | ||
|
|
df07665509 | ||
|
|
68456b8aea | ||
|
|
8529e360be | ||
|
|
91bb539420 | ||
|
|
3f6c61a084 | ||
|
|
82b024c77e | ||
|
|
7f7939dc62 | ||
|
|
8eb4e888f6 | ||
|
|
7516d4135a | ||
|
|
c4ef218006 | ||
|
|
917084ccca | ||
|
|
c905be032f | ||
|
|
e1ccdd2d48 | ||
|
|
dcf58168d6 | ||
|
|
7bee9ccc89 | ||
|
|
e4ae2772ac | ||
|
|
ef673786f4 | ||
|
|
98017937f7 | ||
|
|
26ded7a4cc | ||
|
|
d1705e2818 | ||
|
|
32677efe87 | ||
|
|
1fd29c7012 | ||
|
|
0d5a0a5727 | ||
|
|
f59e84e24c | ||
|
|
39cb9069a6 | ||
|
|
e808f14bf6 | ||
|
|
f837a6a9ea | ||
|
|
1c6b3216ab | ||
|
|
d49289b0ae | ||
|
|
7a23f11aab | ||
|
|
f4055bced2 | ||
|
|
ed8a91e721 | ||
|
|
f890d15de9 | ||
|
|
e04e4e902a | ||
|
|
b25e0fd576 | ||
|
|
6c034aa84a | ||
|
|
e21785a5e4 | ||
|
|
bd7b42007d | ||
|
|
28f0926bf8 | ||
|
|
d722ecc292 | ||
|
|
670a77b021 | ||
|
|
0755246be7 | ||
|
|
41cb869453 | ||
|
|
ea8c74b372 | ||
|
|
50d6181187 | ||
|
|
a059b12cb4 | ||
|
|
d25a0fca9b | ||
|
|
0db8dc7790 | ||
|
|
f92640a824 | ||
|
|
42c0bd370e | ||
|
|
383fee4e11 | ||
|
|
5951027ce7 | ||
|
|
889a533eef | ||
|
|
d83d436d01 | ||
|
|
bd8a92f588 | ||
|
|
db5a6323e6 | ||
|
|
2f56b05619 | ||
|
|
1e5201afad | ||
|
|
9ef18310da | ||
|
|
db0eec05c5 | ||
|
|
fc9a93fee0 | ||
|
|
7b40a3225e | ||
|
|
9cc598a92c | ||
|
|
91320ddd02 | ||
|
|
23dc3d56ce | ||
|
|
35742f0962 | ||
|
|
52a6f96e08 | ||
|
|
d064dd1e8c | ||
|
|
7bf4d694d4 | ||
|
|
cdc7c09421 | ||
|
|
17c7a7cb29 | ||
|
|
3032b0009c | ||
|
|
6dacb5e70c | ||
|
|
5a8af1302f | ||
|
|
569db839f1 | ||
|
|
32e9191431 | ||
|
|
21b808d94f | ||
|
|
83cc668596 | ||
|
|
b92ab24a25 | ||
|
|
4c7661dd13 | ||
|
|
b14c03f38a | ||
|
|
0e1eb95006 | ||
|
|
edb490f359 | ||
|
|
202da83449 | ||
|
|
d10b4ba2c4 | ||
|
|
6cd2c547de | ||
|
|
95e3fd2612 | ||
|
|
795092a97f | ||
|
|
5936d53d78 | ||
|
|
5f75d8c8ce | ||
|
|
214e922c3e | ||
|
|
30425f9038 | ||
|
|
6313704ee5 | ||
|
|
fb0f8f776c | ||
|
|
faff8dc199 | ||
|
|
501395c948 | ||
|
|
759e06a940 | ||
|
|
966d305e5e | ||
|
|
f3b212ce05 | ||
|
|
6f0101eb63 | ||
|
|
9e4cff1595 | ||
|
|
63ef7eb2fb | ||
|
|
3dcbd6afe7 | ||
|
|
45abac485b | ||
|
|
1fdf117c63 | ||
|
|
d9711b4191 | ||
|
|
9b16f0d9ad | ||
|
|
da84efc42d | ||
|
|
7733b0de3b | ||
|
|
e8e0444bd2 | ||
|
|
c3bea4cf14 | ||
|
|
b85a6b939f | ||
|
|
0e94a33240 | ||
|
|
4efd0c5cad | ||
|
|
fcb42f0809 | ||
|
|
882bd06d1c | ||
|
|
a6d6480074 | ||
|
|
45f5660b94 | ||
|
|
7aabbc3115 | ||
|
|
2ef9cc5983 | ||
|
|
6ce45cf4f8 | ||
|
|
9a21dbacf2 | ||
|
|
9617974481 | ||
|
|
ca54b4f79a | ||
|
|
35f1dbabf6 | ||
|
|
c3dfade284 | ||
|
|
cbc1f500bc | ||
|
|
293c8031d9 | ||
|
|
43690a4c77 | ||
|
|
40873fb655 | ||
|
|
7d9065cf31 | ||
|
|
06218c0c6a | ||
|
|
348b297578 | ||
|
|
314e1b468a | ||
|
|
16a7899154 | ||
|
|
cc24a26c6a | ||
|
|
0f1c12d859 | ||
|
|
f4011f17d4 | ||
|
|
f7e47316c0 | ||
|
|
d2666f9eb5 | ||
|
|
ac73522351 | ||
|
|
5aaca9b11c | ||
|
|
9733c7715e | ||
|
|
5cd9ca5c21 | ||
|
|
7084d7cfdc | ||
|
|
b5460fc131 | ||
|
|
fc3ea5caa2 | ||
|
|
332cc48a09 | ||
|
|
83f24acd39 | ||
|
|
b08210a6f8 | ||
|
|
474f3ce092 | ||
|
|
9ef2533256 |
@@ -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:
|
||||
|
||||
@@ -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,7 +20,7 @@ _Put an `x` in the boxes that apply. You can also fill these out after creating
|
||||
- [ ] I have signed the CLA
|
||||
- [ ] Lint and unit tests pass locally with my changes
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have added necessary documentation (if appropriate)
|
||||
- [ ] I have added the necessary documentation (if appropriate)
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
## Further comments
|
||||
|
||||
@@ -13,4 +13,4 @@ jobs:
|
||||
name: Auto-Label PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fuxingloh/multi-labeler@v2
|
||||
- uses: fuxingloh/multi-labeler@v4
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for "On Hold" label
|
||||
uses: egmacke/action-check-label@v2
|
||||
uses: egmacke/action-check-label@v3
|
||||
with:
|
||||
label: On Hold
|
||||
state: absent
|
||||
|
||||
@@ -25,6 +25,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Validate Renovate Config
|
||||
uses: rinchsan/renovate-config-validator@v0.0.12
|
||||
uses: rinchsan/renovate-config-validator@v0.2.0
|
||||
with:
|
||||
pattern: '{.renovaterc.json,renovate/default.json}'
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint:commit -- --edit ${1}
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint:staged
|
||||
-78
@@ -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',
|
||||
};
|
||||
};
|
||||
+126
-48
@@ -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.
|
||||
- **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 for TC Members, Committers, Contributors, and upstream projects
|
||||
|
||||
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)
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# 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!
|
||||
|
||||
* *Coming Soon!*
|
||||
|
||||
## 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
|
||||
|
||||
* [Become a Gold Sponsor](https://opencollective.com/appium/contribute/gold-sponsor-72877/checkout?interval=month&amount=500&contributeAs=me)
|
||||
|
||||
## Silver Sponsors
|
||||
|
||||
* [Become a Silver Sponsor](https://opencollective.com/appium/contribute/silver-sponsor-72876/checkout?interval=month&amount=250&contributeAs=me)
|
||||
|
||||
## Bronze Sponsors
|
||||
|
||||
- [MagicPod](https://magicpod.com/en/)
|
||||
* [Become a Bronze Sponsor](https://opencollective.com/appium/contribute/sponsors-70690/checkout?interval=month&amount=100&contributeAs=me)
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'body-leading-blank': [0],
|
||||
'body-max-line-length': [0],
|
||||
'footer-max-line-length': [0],
|
||||
'header-max-length': [0],
|
||||
'subject-case': [0],
|
||||
'subject-full-stop': [0],
|
||||
},
|
||||
};
|
||||
Generated
+2883
-4373
File diff suppressed because it is too large
Load Diff
+28
-35
@@ -44,13 +44,10 @@
|
||||
"docs:preview": "npm run -w packages/appium build:docs:preview",
|
||||
"doctor": "node ./packages/doctor",
|
||||
"generate-docs": "npm run --workspace=packages/appium generate-docs",
|
||||
"husky-install": "husky install",
|
||||
"lint": "eslint .",
|
||||
"lint:ci": "eslint --quiet .",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:fix": "run-s \"lint -- --fix\"",
|
||||
"lint:staged": "lint-staged",
|
||||
"prepare": "run-p husky-install rebuild",
|
||||
"prepare": "run-p rebuild",
|
||||
"prepublishOnly": "run-s rebuild",
|
||||
"preversion": "run-s rebuild",
|
||||
"version": "run-s sync-pkgs stage-synced-pkgs",
|
||||
@@ -84,64 +81,60 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.6.0",
|
||||
"@commitlint/cli": "18.4.3",
|
||||
"@commitlint/config-conventional": "18.4.3",
|
||||
"@tsconfig/node14": "14.1.0",
|
||||
"@types/chai": "4.3.11",
|
||||
"@tsconfig/node14": "14.1.2",
|
||||
"@types/chai": "4.3.16",
|
||||
"@types/chai-as-promised": "7.1.8",
|
||||
"@types/diff": "5.0.9",
|
||||
"@types/diff": "5.2.1",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/node": "20.10.6",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/sinon": "17.0.2",
|
||||
"@types/node": "20.12.12",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/sinon": "17.0.3",
|
||||
"@types/sinon-chai": "3.2.12",
|
||||
"@types/source-map-support": "0.5.10",
|
||||
"@types/teen_process": "2.0.4",
|
||||
"@types/ws": "8.5.10",
|
||||
"@types/yargs": "17.0.32",
|
||||
"@typescript-eslint/eslint-plugin": "6.17.0",
|
||||
"@typescript-eslint/parser": "6.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||
"@typescript-eslint/parser": "7.10.0",
|
||||
"asyncbox": "3.0.0",
|
||||
"chai": "4.3.10",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.8.0",
|
||||
"chai": "4.4.1",
|
||||
"chai-as-promised": "7.1.2",
|
||||
"chai-webdriverio-async": "3.0.0",
|
||||
"conventional-changelog-conventionalcommits": "7.0.2",
|
||||
"cpy-cli": "5.0.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-mocha": "10.2.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-mocha": "10.4.3",
|
||||
"eslint-plugin-promise": "6.2.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "13.1.1",
|
||||
"lerna": "8.0.1",
|
||||
"lint-staged": "15.2.0",
|
||||
"json-schema-to-typescript": "14.0.4",
|
||||
"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.2.0",
|
||||
"mocha": "10.4.0",
|
||||
"mocha-chai-jest-snapshot": "1.1.4",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "3.1.1",
|
||||
"npm-run-all2": "6.2.0",
|
||||
"prettier": "3.2.5",
|
||||
"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",
|
||||
"ts-node": "10.9.2",
|
||||
"tsd": "0.30.2",
|
||||
"typescript": "5.2.2",
|
||||
"tsd": "0.31.0",
|
||||
"typescript": "5.4.5",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "8.27.0",
|
||||
"ws": "8.16.0",
|
||||
"webdriverio": "8.38.0",
|
||||
"ws": "8.17.0",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -153,10 +146,10 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@sidvind/better-ajv-errors": {
|
||||
"ajv": "8.12.0"
|
||||
"ajv": "8.14.0"
|
||||
},
|
||||
"ajv-formats": {
|
||||
"ajv": "8.12.0"
|
||||
"ajv": "8.14.0"
|
||||
},
|
||||
"http-cache-semantics": "4.1.1"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,131 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.36.1 ([9ad20ff](https://github.com/appium/appium/commit/9ad20ff1c266923f3efe50bfe2ae4b1ab8ecb20a))
|
||||
|
||||
|
||||
|
||||
## [2.5.3](https://github.com/appium/appium/compare/appium@2.5.2...appium@2.5.3) (2024-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.36.0 ([#19998](https://github.com/appium/appium/issues/19998)) ([72f3284](https://github.com/appium/appium/commit/72f32848b68e6ef8c047f700ffd0be7d68c49aba))
|
||||
|
||||
|
||||
|
||||
## [2.5.2](https://github.com/appium/appium/compare/appium@2.5.1...appium@2.5.2) (2024-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** make --log-filters work ([#19825](https://github.com/appium/appium/issues/19825)) ([0d5a0a5](https://github.com/appium/appium/commit/0d5a0a572787d913412bbfd70835e5ba8f94334c))
|
||||
* **appium:** update definitelytyped ([2687fa4](https://github.com/appium/appium/commit/2687fa47a0c2f11983e5ccb2ee17eaae9279469d))
|
||||
* **appium:** update definitelytyped ([68456b8](https://github.com/appium/appium/commit/68456b8aea3c6ae7225814748fb36d9d4117cc9f))
|
||||
* **appium:** update dependency ajv-formats to v3 ([#19957](https://github.com/appium/appium/issues/19957)) ([59ef09d](https://github.com/appium/appium/commit/59ef09d9558f1a6a9f4ee3d81d4cb0b7218706b4))
|
||||
* **appium:** update dependency winston to v3.12.0 ([8eb4e88](https://github.com/appium/appium/commit/8eb4e888f624acfc1d42977eeda2d4c1ae139efc))
|
||||
* **appium:** update dependency winston to v3.13.0 ([03f8dc9](https://github.com/appium/appium/commit/03f8dc9b18f226975f025ceda7e1d907de8b45d5))
|
||||
* **docutils:** update dependency lilconfig to v3.1.0 ([f92640a](https://github.com/appium/appium/commit/f92640a824644545b680e23c7f3ec7f51de9df98))
|
||||
* **docutils:** update dependency lilconfig to v3.1.1 ([e04e4e9](https://github.com/appium/appium/commit/e04e4e902a212cf17ca79b3edc0b7545a1af507b))
|
||||
* **docutils:** update dependency typescript to v5.4.2 ([#19876](https://github.com/appium/appium/issues/19876)) ([2448fa0](https://github.com/appium/appium/commit/2448fa0145620657ccc72b5637f1b7737fe52580))
|
||||
* **docutils:** update dependency yaml to v2.4.0 ([26ded7a](https://github.com/appium/appium/commit/26ded7a4cce1fa9bca28521e4b60cad0edacbe96))
|
||||
* **docutils:** update dependency yaml to v2.4.1 ([636d6b3](https://github.com/appium/appium/commit/636d6b3802268ec6532f9f3a958f304c6760d172))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.32.0 ([d25a0fc](https://github.com/appium/appium/commit/d25a0fca9b3a94880525fd956068f59427a40b26))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.32.1 ([ea8c74b](https://github.com/appium/appium/commit/ea8c74b372e73e88cf0303aa17db943014416e64))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.32.2 ([#19807](https://github.com/appium/appium/issues/19807)) ([6c034aa](https://github.com/appium/appium/commit/6c034aa84af573d36fa934c71d3d4ff328772553))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.32.3 ([f837a6a](https://github.com/appium/appium/commit/f837a6a9eaf34914ffdbfddcc3cb2f7e7427bc63))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.32.4 ([7516d41](https://github.com/appium/appium/commit/7516d4135a1eec37ba298f407254d825961795af))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.33.1 ([#19877](https://github.com/appium/appium/issues/19877)) ([0e6c3ee](https://github.com/appium/appium/commit/0e6c3eede250193fad390387ec494918367176d9))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.34.1 ([7fc7446](https://github.com/appium/appium/commit/7fc7446612f3257fa421dfec006d02730df10f59))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.35.1 ([63d7550](https://github.com/appium/appium/commit/63d7550d02f049a5c3bea5d27e7e8385d7698a4f))
|
||||
* **support:** update dependency @types/semver to v7.5.8 ([d1705e2](https://github.com/appium/appium/commit/d1705e2818d435b4c37cb70753af1de5427378e2))
|
||||
* **support:** update dependency axios to v1.6.8 ([bd6ab81](https://github.com/appium/appium/commit/bd6ab81c9408ab0f90fc25fc112f9257ec2973ad))
|
||||
|
||||
|
||||
|
||||
## [2.5.1](https://github.com/appium/appium/compare/appium@2.5.0...appium@2.5.1) (2024-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.31.1 ([db0eec0](https://github.com/appium/appium/commit/db0eec05c58fe970351a309a01507cad824bdad2))
|
||||
* **support:** update dependency @types/semver to v7.5.7 ([bd8a92f](https://github.com/appium/appium/commit/bd8a92f588d40c97fbe51f8bd3b7835c96e9fe8f))
|
||||
* **support:** update dependency semver to v7.6.0 ([7b40a32](https://github.com/appium/appium/commit/7b40a3225ebe0f5976a26d05db2f22ba7b13a130))
|
||||
|
||||
|
||||
|
||||
## [2.5.0](https://github.com/appium/appium/compare/appium@2.4.1...appium@2.5.0) (2024-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Set a proper exit code if any of required doctor checks fails ([#19617](https://github.com/appium/appium/issues/19617)) ([f4011f1](https://github.com/appium/appium/commit/f4011f17d4d03bc854c43462848a4c38427acb85))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** Tune warning messages about installed extensions ([#19612](https://github.com/appium/appium/issues/19612)) ([5cd9ca5](https://github.com/appium/appium/commit/5cd9ca5c21393c70d259f7525d2c9d35b7eeb2a2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.27.2 ([35f1dba](https://github.com/appium/appium/commit/35f1dbabf6e852c4ae84ec128879bc7e61321a16))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.0 ([e8e0444](https://github.com/appium/appium/commit/e8e0444bd28d94963090065a813a4b10be665caa))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.6 ([da84efc](https://github.com/appium/appium/commit/da84efc42dd62b2be07a52dd4379791e2cef6eb2))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.28.8 ([1fdf117](https://github.com/appium/appium/commit/1fdf117c63f6e9871f4de3c0bd93ecbdb13a9235))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.0 ([6f0101e](https://github.com/appium/appium/commit/6f0101eb6343eb8b372e00a6b71d2b2ee909404c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.1 ([fb0f8f7](https://github.com/appium/appium/commit/fb0f8f776c6a03174f4569efa60d817a930809cf))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.3 ([6dacb5e](https://github.com/appium/appium/commit/6dacb5e70ca3529a080f629f5245c2b676746e2c))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.29.7 ([52a6f96](https://github.com/appium/appium/commit/52a6f96e085ff13acdf8d5013673861b7e0da818))
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v8.30.0 ([91320dd](https://github.com/appium/appium/commit/91320ddd02dca89a8cec258d0ab44a49d344d71c))
|
||||
* **support:** update dependency axios to v1.6.4 ([332cc48](https://github.com/appium/appium/commit/332cc48a09b5532a8d51f85f3a24785e2c754e00))
|
||||
* **support:** update dependency axios to v1.6.5 ([#19616](https://github.com/appium/appium/issues/19616)) ([ac73522](https://github.com/appium/appium/commit/ac73522351b31bd6c11972c61daa8b6b8d18fb91))
|
||||
* **support:** update dependency axios to v1.6.6 ([6313704](https://github.com/appium/appium/commit/6313704ee5a8ee3aee726eb512ef259b6fa1041c))
|
||||
* **support:** update dependency axios to v1.6.7 ([795092a](https://github.com/appium/appium/commit/795092a97f6d7569cccc4b5c166f52fef821514b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [2.4.1](https://github.com/appium/appium/compare/appium@2.4.0...appium@2.4.1) (2024-01-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** Properly handle version check for aliases ([474f3ce](https://github.com/appium/appium/commit/474f3ce092c68981655d9deba546a6202acea141))
|
||||
|
||||
|
||||
|
||||
## [2.4.0](https://github.com/appium/appium/compare/appium@2.3.0...appium@2.4.0) (2024-01-03)
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ theme:
|
||||
favicon: assets/images/appium-logo.png
|
||||
|
||||
plugins:
|
||||
blog:
|
||||
enabled: true
|
||||
post_excerpt: required
|
||||
redirects:
|
||||
redirect_maps:
|
||||
'intro/requirements.md': 'quickstart/requirements.md'
|
||||
@@ -34,7 +37,7 @@ extra:
|
||||
link: https://twitter.com/AppiumDevs
|
||||
alternate:
|
||||
- name: English
|
||||
link: /docs/en/
|
||||
link: /docs/en/
|
||||
lang: en
|
||||
- name: 日本
|
||||
link: /docs/ja/
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
authors:
|
||||
jlipps:
|
||||
name: "Jonathan Lipps"
|
||||
description: "Appium project lead. Learn more about Jonathan at https://jlipps.com"
|
||||
avatar: "https://gravatar.com/avatar/fdaadd825d92709bcf7fe76dffa12c40681c6108c67bcddf19478b2d00e3794b.jpg"
|
||||
slug: "jlipps"
|
||||
url: "https://jlipps.com"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Appium Blog
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
authors:
|
||||
- jlipps
|
||||
date: 2024-03-07
|
||||
---
|
||||
|
||||
# Hello World!
|
||||
|
||||
This is the first post in the Appium blog. There's not much to see here yet. We're creating this
|
||||
space so we can make announcements or post other news, or information about events, that don't
|
||||
really fit inside the documentation itself.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Stay tuned for more!
|
||||
@@ -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`|
|
||||
|
||||
@@ -19,3 +19,5 @@ 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.|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -217,6 +217,10 @@ A possibly empty list of elements inside the shadow root matching the selector
|
||||
|
||||
Get the log for a given log type.
|
||||
|
||||
!!! warning "Deprecated"
|
||||
|
||||
Please use the `/session/:sessionId/se/log` endpoint instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
@@ -273,6 +277,10 @@ The event history for the session
|
||||
|
||||
Get available log types as a list of strings
|
||||
|
||||
!!! warning "Deprecated"
|
||||
|
||||
Please use the `/session/:sessionId/se/log/types` endpoint instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
@@ -319,9 +327,22 @@ Get data for all sessions running on an Appium server
|
||||
|
||||
#### Response
|
||||
|
||||
`MultiSessionData`<`C`\>[]
|
||||
A list of session data objects.
|
||||
Each session data object will be returned with `id` and the session's capabilities as `capabilities` keys like an example below:
|
||||
|
||||
A list of session data objects
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id":"ba30c6da-c266-4734-8ddb-c16f5bb53e16",
|
||||
"capabilities":{ "platformName":"ios","browserName":"safari","automationName":"xcuitest","platformVersion":"17.2","deviceName":"iPhone 15" }
|
||||
},
|
||||
{
|
||||
"id":"1441110c-1ece-4e45-abbf-ebf404f45f0a",
|
||||
"capabilities":{ "platformName":"ios","browserName":"safari","automationName":"xcuitest","platformVersion":"17.0","deviceName":"iPhone 14" }
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### `getSettings`
|
||||
|
||||
@@ -465,7 +486,9 @@ Set the various timeouts associated with a session
|
||||
|
||||
Set the implicit wait timeout
|
||||
|
||||
**`Deprecated`**
|
||||
!!! warning "Deprecated"
|
||||
|
||||
Please use the `/session/:sessionId/timeouts` endpoint instead
|
||||
|
||||
Use `timeouts` instead
|
||||
|
||||
@@ -506,7 +529,9 @@ Add a custom-named event to the Appium event log
|
||||
|
||||
Reset the current session (run the delete session and create session subroutines)
|
||||
|
||||
**`Deprecated`**
|
||||
!!! warning "Deprecated"
|
||||
|
||||
Please use each driver's launch, activate, terminate or cleanup method.
|
||||
|
||||
Use explicit session management commands instead
|
||||
|
||||
|
||||
@@ -58,20 +58,20 @@ work with certain versions of Appium). For Appium 2, for example, this would loo
|
||||
Your `package.json` must contain an `appium` field, like this (we call this the 'Appium extension
|
||||
metadata'):
|
||||
|
||||
```json
|
||||
{
|
||||
...,
|
||||
"appium": {
|
||||
"driverName": "fake",
|
||||
"automationName": "Fake",
|
||||
"platformNames": [
|
||||
"Fake"
|
||||
],
|
||||
"mainClass": "FakeDriver"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
...,
|
||||
"appium": {
|
||||
"driverName": "fake",
|
||||
"automationName": "Fake",
|
||||
"platformNames": [
|
||||
"Fake"
|
||||
],
|
||||
"mainClass": "FakeDriver"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The required subfields are:
|
||||
|
||||
@@ -130,12 +130,12 @@ like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
...,
|
||||
"appium": "^2.0.0",
|
||||
"your-driver": "file:.",
|
||||
...
|
||||
}
|
||||
"devDependencies": {
|
||||
...,
|
||||
"appium": "^2.0.0",
|
||||
"your-driver": "file:.",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -657,6 +657,13 @@ A couple notes about this system:
|
||||
1. The `executeMethod` helper will reject with an error if a script name doesn't match one of the
|
||||
script names defined as a command in `executeMethodMap`, or if there are missing parameters.
|
||||
|
||||
|
||||
### Build Appium Doctor checks
|
||||
|
||||
Your users can run `appium driver doctor <driverName>` to run installation and health checks. Visit
|
||||
the [Building Doctor Checks](./build-doctor-checks.md) guide for more information on this
|
||||
capability.
|
||||
|
||||
### Implement handling of Appium settings
|
||||
|
||||
Appium users can send parameters to your driver via CLI args as well as via capabilities. But these
|
||||
|
||||
@@ -412,6 +412,12 @@ Overriding Execute Methods from drivers works as you'd expect: if your plugin de
|
||||
Method with the same name as that of a driver, your command (in this case `plugMeIn`) will be
|
||||
called first. You can choose to run the driver's original behaviour via `next` if you want.
|
||||
|
||||
### Build Appium Doctor checks
|
||||
|
||||
Your users can run `appium plugin doctor <pluginName>` to run installation and health checks. Visit
|
||||
the [Building Doctor Checks](./build-doctor-checks.md) guide for more information on this
|
||||
capability.
|
||||
|
||||
### Update the Appium server object
|
||||
|
||||
You probably don't normally need to update the Appium server object (which is an
|
||||
|
||||
@@ -31,6 +31,7 @@ These clients are not maintained by the Appium team and can be used with other l
|
||||
|[WebdriverIO](https://webdriver.io/docs/appium)|Node.js|
|
||||
|[Nightwatch.js](https://nightwatchjs.org/guide/mobile-app-testing/introduction.html)|Node.js|
|
||||
|[RobotFramework](https://github.com/serhatbolsu/robotframework-appiumlibrary)|DSL|
|
||||
|[multicatch's appium-client](https://github.com/multicatch/appium-client)|Rust|
|
||||
|
||||
In general, any W3C WebDriver spec-compatible client will also integrate well with Appium, though
|
||||
some Appium-specific commands may not be implemented in other clients.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Local Validation Of Extension PRs
|
||||
---
|
||||
|
||||
Sometimes it might be necessary to validate if a remote driver or a plugin PR works for the particular local
|
||||
environment before it is merged or published. This tutorial describes how to achieve that.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Recent LTS version of NodeJS. Check [Node.js main page](https://nodejs.org) for the download link.
|
||||
- Recent version of the Appium server. Use the following commands to ensure you have
|
||||
the latest version installed: `npm uninstall appium` and `npm install -g appium`.
|
||||
- [Git](https://git-scm.com/) should be available locally.
|
||||
|
||||
## Installation
|
||||
|
||||
- [Check out]((https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally)) the PR locally.
|
||||
There is also an option (although it is less flexible, because you won't be able to easily fetch
|
||||
any changes to this PR later) to simply [download](https://docs.github.com/en/repositories/working-with-files/using-files/downloading-source-code-archives) and unzip the sources
|
||||
locally. If you choose the later option then no `git` tool would be necessary.
|
||||
- Navigate to the local driver or plugin folder and run `npm i` from that folder.
|
||||
- Make sure you don't have the given driver or plugin already installed. Use the `appium driver uninstall <driver_name>`
|
||||
or `appium plugin uninstall <plugin_name>` CLI in order to delete any leftovers. The value of
|
||||
`<driver_name>`/`<plugin_name>` depends on the actual driver or plugin name the PR has been prepared for.
|
||||
If you are not sure which name you need to use then check the content of the `package.json` manifest,
|
||||
which must be always located under the root folder of the fetched sources. You should be looking for
|
||||
the `"appium" -> "driverName"` entry value there.
|
||||
- Change the current folder to the one, which is NOT the driver/plugin folder root or its subfolder.
|
||||
Also, make sure your current working folder does not contain any extra `package.json` file. If
|
||||
it does then simply navigate to any other folder that does not.
|
||||
- Run the following command in order to link the driver/plugin sources to the Appium server:
|
||||
`appium driver install --source=local <full_path_to_driver_folder_with_fetched_sources>` or
|
||||
`appium plugin install --source=local <full_path_to_plugin_folder_with_fetched_sources>`.
|
||||
- Stop the Appium server if it is running and start it again (`appium server --use-drivers=<driver_name>`)
|
||||
to check the list of loaded drivers. If the linking succeeded then you must see the driver name and the path to its
|
||||
parent folder in the server logs. In case of a plugin it is required to explicitly request
|
||||
this plugin to be loaded upon server startup: `appium server --use-plugins=<plugin_name>`.
|
||||
|
||||
## Update
|
||||
|
||||
After you have tested the PR and there are issues it might be necessary to update the local branch with
|
||||
the recent changes from Git. Follow the next steps for that:
|
||||
|
||||
- Navigate to the parent folder of your local driver/plugin and run `git pull`.
|
||||
- Stop Appium server if it is running.
|
||||
- Run `npm i` in the parent folder of your local driver/plugin to rebuild it and update any dependencies if necessary.
|
||||
- Start Appium server again similarly to how this is done in the corresponding [Installation](#installation) step above.
|
||||
|
||||
## Switching Back To a Stable Release
|
||||
|
||||
After the PR is merged there is no need to use the local plugin/driver deployment anymore, and it makes sense
|
||||
to switch back to the package managed by NPM. Follow the next steps for that:
|
||||
|
||||
- Unlink the installed driver/plugin from the server by running `appium driver uninstall <driver_name>` or
|
||||
`appium plugin uninstall <driver_name>`.
|
||||
- Delete the local source folder (`rm -rf <full_path_to_plugin_or_driver_folder_with_fetched_sources>`).
|
||||
- Install the driver or the plugin from NPM. Check the component README in order to find a proper command for that.
|
||||
@@ -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,8 +55,13 @@ 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
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -32,7 +32,7 @@ driver install`.
|
||||
So, from a technical perspective, an Appium driver is just a bit of code that inherits from some
|
||||
other Appium code. That's it! Now, inheriting from `BaseDriver` actually gives us a lot, because
|
||||
`BaseDriver` is essentially an encapsulation of the entire WebDriver protocol. So all a driver
|
||||
needs to do to do something useful is to *implement* Node.js methods with names corresponding to
|
||||
needs to do something useful is to *implement* Node.js methods with names corresponding to
|
||||
their WebDriver protocol equivalents.
|
||||
|
||||
So let's say I wanted to do something with this empty driver; first I have to decide which
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,27 +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.0.0
|
||||
[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 ...)
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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,6 +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
|
||||
* [`npm`](https://npmjs.com) version `>=8` (`npm` is usually bundled with Node.js, but can be upgraded
|
||||
independently)
|
||||
|
||||
@@ -23,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).
|
||||
|
||||
@@ -47,6 +47,11 @@ contents:
|
||||
[WebdriverIO documentation](https://webdriver.io/docs/gettingstarted) for a fuller explanation
|
||||
of the various API commands you see and what their purpose is.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/js).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
1. Defining a set of "Capabilities" (parameters) to send to the Appium server so Appium knows what
|
||||
@@ -68,4 +73,3 @@ If all goes well, you'll see the Settings app open up and navigate to the "Batte
|
||||
app closes again.
|
||||
|
||||
Congratulations, you've started your Appium journey! Read on for some [next steps](./next-steps.md) to explore.
|
||||
|
||||
|
||||
@@ -26,11 +26,17 @@ You usually do not need to worry about the prefix.
|
||||
|
||||
It's not within the scope of this guide to give a complete run-down on the Python client
|
||||
library or everything that's happening here, so we'll leave the code itself unexplained in detail for now.
|
||||
|
||||
- You may want to read up particularly on Appium [Capabilities](../guides/caps.md).
|
||||
- [functional test code](https://github.com/appium/python-client/tree/master/test/functional) in Python Client GitHub repository should help to find more working example.
|
||||
- [Documentation](https://appium.github.io/python-client-sphinx/) also helps to find methods
|
||||
defined in the Appium Python Client.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/py).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
1. Defining a set of "Capabilities" (parameters) to send to the Appium server so Appium knows what
|
||||
|
||||
@@ -6,12 +6,32 @@ title: Write a Test (Ruby)
|
||||
---
|
||||
|
||||
|
||||
The [AppiumLib](https://github.com/appium/ruby_lib) and the [AppiumLibCore](https://github.com/appium/ruby_lib_core) (**recommended**) are official Appium client libraries in Ruby, which are available via gem under the [appium_lib](https://rubygems.org/gems/appium_lib) and the [appium_lib_core](https://rubygems.org/gems/appium_lib_core) package names. The appium_lib_core inherits from the Selenium Ruby Binding, and the appium_lib inherits from the appium_lib_core, so installing these libraries include the selenium binding. We recommend `appium_lib_core` if you need a less complex client-side solution. The `appium_lib` has some useful methods the core does not have, but for the cost of greater complexity.
|
||||
The [AppiumLib](https://github.com/appium/ruby_lib) and the [AppiumLibCore](https://github.com/appium/ruby_lib_core) (**recommended**) are official Appium client libraries in Ruby, which are available via gem under the [appium_lib](https://rubygems.org/gems/appium_lib) and the [appium_lib_core](https://rubygems.org/gems/appium_lib_core) package names. The appium_lib_core inherits from the Selenium Ruby Binding, and the appium_lib inherits from the appium_lib_core, so installing these libraries include the selenium binding. We recommend `appium_lib_core` if you need a less complex client-side solution. The `appium_lib` has some useful methods the core does not have, but for the cost of greater complexity and historical methods which may not work in the latest environment.
|
||||
|
||||
As the first step, let's initialize a Gemfile to manage the dependency:
|
||||
|
||||
```bash
|
||||
gem install appium_lib
|
||||
bundle init
|
||||
```
|
||||
|
||||
Then, you could add Appium Ruby Client dependency as below:
|
||||
|
||||
```bash
|
||||
bundle add appium_lib_core
|
||||
# or
|
||||
gem install appium_lib_core
|
||||
# bundle add appium_lib
|
||||
```
|
||||
|
||||
Test code example below uses `test-unit` module, thus please run:
|
||||
|
||||
```bash
|
||||
bundle add test-unit
|
||||
```
|
||||
|
||||
Once these steps has done, your `Gemfile` file should include:
|
||||
|
||||
```ruby title="Gemfile"
|
||||
--8<-- "./sample-code/quickstarts/rb/Gemfile"
|
||||
```
|
||||
|
||||
The `appium_lib_core` is the main part as an Appium client.
|
||||
@@ -21,7 +41,7 @@ The `appium_lib_core` is the main part as an Appium client.
|
||||
This example is by the `appium_lib_core` with `test-unit` gem module.
|
||||
Tes code in `appium_lib` should be similar.
|
||||
|
||||
```python title="test.py"
|
||||
```ruby title="test.rb"
|
||||
--8<-- "./sample-code/quickstarts/rb/test.rb"
|
||||
```
|
||||
|
||||
@@ -34,6 +54,10 @@ Tes code in `appium_lib` should be similar.
|
||||
- [functional test code](https://github.com/appium/ruby_lib_core/tree/master/test/functional) in the appium_lib_core GitHub repository should help to find more working example.
|
||||
- Documentation [appium_lib_core](https://www.rubydoc.info/github/appium/ruby_lib_core) and [appium_lib](https://www.rubydoc.info/github/appium/ruby_lib) also helps to find available methods.
|
||||
|
||||
!!! note
|
||||
|
||||
The sample code is available from [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/rb).
|
||||
|
||||
|
||||
Basically, this code is doing the following:
|
||||
|
||||
@@ -49,7 +73,7 @@ running in another terminal session, otherwise you'll get an error about not bei
|
||||
to one. Then, you can execute the script:
|
||||
|
||||
```bash
|
||||
bundle install
|
||||
# Please run "bundle install" first if your environment has not run the installation command yet.
|
||||
bundle exec ruby test.rb
|
||||
```
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Blog
|
||||
|
||||
@@ -8,6 +8,7 @@ theme:
|
||||
language: en
|
||||
nav:
|
||||
- Welcome: index.md
|
||||
- Blog: blog/index.md
|
||||
- Introduction:
|
||||
- intro/index.md
|
||||
- Background:
|
||||
@@ -38,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
|
||||
@@ -50,6 +52,7 @@ nav:
|
||||
- guides/migrating-1-to-2.md
|
||||
- Server/Driver Configuration:
|
||||
- guides/managing-exts.md
|
||||
- guides/branch-testing.md
|
||||
- guides/config.md
|
||||
- guides/security.md
|
||||
- guides/log-filters.md
|
||||
@@ -68,6 +71,7 @@ nav:
|
||||
- developing/build-drivers.md
|
||||
- developing/build-plugins.md
|
||||
- developing/build-docs.md
|
||||
- developing/build-doctor-checks.md
|
||||
- Contributing:
|
||||
- contributing/index.md
|
||||
- contributing/develop.md
|
||||
|
||||
@@ -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,2 @@
|
||||
# Blog
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
---
|
||||
title: Appium文档
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
|
||||
title: 欢迎
|
||||
---
|
||||
<style>
|
||||
.md-typeset h1 {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<div style="text-align: center">
|
||||
<img src="assets/images/appium-logo-horiz.png" style="max-width: 400px;" />
|
||||
</div>
|
||||
|
||||
欢迎访问 Appium 文档!Appium 是一个开源项目和相关软件的生态系统。
|
||||
软件,旨在促进许多应用程序平台的用户界面自动化,包括移动平台(iOS、
|
||||
Android、Tizen)、浏览器(Chrome、Firefox、Safari)、桌面(macOS、Windows)、电视(Roku、tvOS、
|
||||
Android TV、三星)等!
|
||||
|
||||
TODO
|
||||
## 探索文档
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- 查看 [__介绍__](./intro/index.md) 确保您理解关键概念
|
||||
- 通过 [__快速入门__](./quickstart/index.md) 来进行设置并运行基本的 Android 测试
|
||||
- 访问 [__生态系统__](./ecosystem/index.md) 页面,查看您可能需要使用的驱动程序、客户端和插件列表
|
||||
- 参考 [__CLI 参考__](./cli/index.md) 用于从命令行使用 Appium
|
||||
- 查看 [__命令说明__](./commands/index.md) Appium 和插件公开的命令列表
|
||||
- 阅读不同的 [__指南__](./guides/migrating-1-to-2.md) 来获取各种说明、技巧和窍门
|
||||
- 查阅各种第三方的 [__资源__](./resources.md) 来探索网上的Appium运用
|
||||
- 为了创建您自己的Appium拓展, 请查看 [__开发者__](./developing/index.md) 文档
|
||||
- 对 Appium 自身的贡献,请查阅 [__贡献__](./contributing/index.md) 页面
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +1,29 @@
|
||||
---
|
||||
title: 快速开始
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 快速入门介绍
|
||||
---
|
||||
|
||||
让我们开始使用 Appium!为了成功使用本快速入门介绍, 我们建议您
|
||||
首先阅读 [介绍](../intro/index.md), 以便您了解
|
||||
运行 Appium 和编写 Appium 脚本所涉及的概念。
|
||||
|
||||
TODO
|
||||
本快速入门课程的基本计划如下:
|
||||
|
||||
1. 安装 Appium
|
||||
1. 安装 Appium 驱动及其依赖项 (我们将使用 [UiAutomator2
|
||||
driver](https://github.com/appium/appium-uiautomator2-driver) 对于这些例子)
|
||||
1. 用你选择的语言安装 Appium 客户端库(本指南包含以下选项
|
||||
JavaScript、Python、Java、Ruby 和 .NET 的选项)。
|
||||
1. 使用示例应用程序编写并运行简单的 Appium 自动化脚本
|
||||
|
||||
### 要求
|
||||
|
||||
开始之前,请确保您的系统满足运行 Appium 服务器的
|
||||
[要求](../quickstart/requirements.md).其他要求
|
||||
将在安装 UiAutomator2 驱动程序时一并讨论。 本指南还假设
|
||||
您已熟练掌握您的操作系统平台上的基本命令行功能,例如可以运行命令行命令, 设置
|
||||
永久的系统变量, 等等...
|
||||
|
||||
现在你已经准备好开始了!请前往 [安装 Appium](./install.md)吧.
|
||||
|
||||
@@ -1,6 +1,41 @@
|
||||
---
|
||||
title: Install Appium
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 安装 Appium
|
||||
---
|
||||
|
||||
!!! 信息
|
||||
|
||||
TODO
|
||||
安装前,请务必检查[系统要求](./requirements.md).
|
||||
|
||||
您可以使用 `npm` 在全局范围内安装 Appium:
|
||||
|
||||
```bash
|
||||
npm i -g appium
|
||||
```
|
||||
|
||||
!!! 注意
|
||||
|
||||
目前不支持其他软件包管理器。
|
||||
|
||||
安装完成后,您应该可以从命令行运行 Appium:
|
||||
|
||||
```
|
||||
appium
|
||||
```
|
||||
|
||||
你应该会看到一些输出结果,开头一行是这样的:
|
||||
|
||||
```
|
||||
[Appium] 欢迎来到 Appium v2.4.1
|
||||
```
|
||||
|
||||
为了更新Appiums使用 `npm`:
|
||||
|
||||
```bash
|
||||
npm update -g appium
|
||||
```
|
||||
|
||||
就是这样!如果你看到这个,说明 Appium 服务器已经启动并运行。按 (Ctrl-C)
|
||||
继续退出并跳转到到 [下一步](./uiauto2-driver.md), 在这里我们将安装一个用于自动运行 Android 应用程序的驱动程序.
|
||||
@@ -1,6 +1,23 @@
|
||||
---
|
||||
title: Next Steps
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 下一步
|
||||
---
|
||||
|
||||
既然您已经成功配置了您的系统进行Android自动化并运行了一个简单的测试,
|
||||
您可能希望继续探索这份文档。特别是对于初学者,
|
||||
以下是一些很好的指南和参考资料:
|
||||
|
||||
TODO
|
||||
- 在 [生态系统](../ecosystem/index.md) 页面: 浏览可用的驱动程序, 客户端, 插件, 和工具
|
||||
- [管理Appium驱动程序与插件](../guides/managing-exts.md)
|
||||
- [能力](../guides/caps.md)
|
||||
- [设置](../guides/settings.md)
|
||||
|
||||
您还会发现[Appium 监测者](https://github.com/appium/appium-inspector) 编写 Appium 测试不可或缺的工具。
|
||||
是编写 Appium 测试不可或缺的工具,因为它可以对应用程序进行可视化检查,并
|
||||
帮助您发现可用于测试脚本的元素定位器。
|
||||
|
||||
您还可以利用为您提供的众多在线 Appium 课程之一。
|
||||
|
||||
祝你好运,玩得开心!
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
---
|
||||
title: Write a Test (Java)
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 编写一个测试(Java)
|
||||
---
|
||||
|
||||
TODO
|
||||
Appium 团队为 Java 编程语言维护一个官方[客户端](https://github.com/appium/java-client).
|
||||
它是建立在 [Selenium](https://github.com/SeleniumHQ/selenium)上的.
|
||||
您也可以在 Kotlin 项目中使用该客户端.
|
||||
|
||||
遵循[在测试框架中添加 Appium Java 客户端](https://github.com/appium/java-client#add-appium-java-client-to-your-test-framework)
|
||||
教程,以便将该库连接到测试框架源。
|
||||
|
||||
Appium Java 客户端有专门的类来支持大多数官方 Appium 驱动程序。对于其他驱动程序
|
||||
您只需使用 [AppiumDriver](https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/AppiumDriver.java)类
|
||||
或在此基础上创建您的自定义衍生工具. 请查看 [驱动支持](https://github.com/appium/java-client#drivers-support)
|
||||
文章,了解有关当前驱动程序类实现的更多信息.
|
||||
|
||||
请参阅 [使用实例](https://github.com/appium/java-client#usage-examples) 一文,以了解
|
||||
如何从测试框架调用 Java 客户端功能。
|
||||
|
||||
成功运行测试后,您可以继续阅读以下内容 [下一步骤](./next-steps.md) 继续探索
|
||||
|
||||
@@ -1,6 +1,73 @@
|
||||
---
|
||||
title: Write a Test (JS)
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 编写一个测试 (JS)
|
||||
---
|
||||
|
||||
要在 JavaScript(Node.js)中编写 Appium 测试,我们需要选择一个与 Appium 兼容的客户端
|
||||
库。维护最好的库和 Appium 团队推荐使用的库是
|
||||
[WebdriverIO](https://webdriver.io), 所有就让我们使用它吧。既然我们已经安装了 Appium,我们
|
||||
已经满足了 Node 和 NPM 的要求。因此,只需在计算机上创建一个新的项目目录
|
||||
然后在其中初始化一个新的 Node.js 项目:
|
||||
|
||||
TODO
|
||||
```bash
|
||||
npm init
|
||||
```
|
||||
|
||||
您在提示中输入什么内容并不重要,只要您最终得到一个有效的
|
||||
`package.json`.
|
||||
|
||||
|
||||
现在,通过 NPM 安装 `webdriverio` 软件包:
|
||||
|
||||
```bash
|
||||
npm i --save-dev webdriverio
|
||||
```
|
||||
|
||||
完成上述操作后,您的 `package.json` 文件应包含类似以下内容的部分:
|
||||
|
||||
```json title="package.json"
|
||||
--8<-- "./sample-code/quickstarts/js/package.json"
|
||||
```
|
||||
|
||||
现在是编写测试本身的时候了。创建一个名为 `test.js` 的新文件,内容如下:
|
||||
|
||||
```js title="test.js"
|
||||
--8<-- "./sample-code/quickstarts/js/test.js"
|
||||
```
|
||||
|
||||
!!! 注意
|
||||
|
||||
本指南的范围不包括完整介绍 WebdriverIO 客户端
|
||||
库或这里发生的一切,因此我们暂且不对代码本身进行详细解释。
|
||||
所以目前我们暂时不对代码本身进行详细解释。您可能需要特别阅读 Appium
|
||||
[能力](../guides/caps.md), 除了熟悉优秀的
|
||||
[WebdriverIO 文档](https://webdriver.io/docs/gettingstarted) 来获得更全面的解释,
|
||||
你还可以看到的各种 API 命令以及用途。
|
||||
|
||||
!!! 注意
|
||||
|
||||
示例代码可从 [GitHub Appium repository](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/js).
|
||||
|
||||
|
||||
基本上,这段代码正在执行以下操作:
|
||||
|
||||
1. 定义一组 "Capabilities" 能力值(参数),以便 Appium 知道您想自动执行哪种任务。
|
||||
要自动执行的任务。
|
||||
1. 在内置的 Android 设置应用程序上启动 Appium 会话。
|
||||
1. 找到 "Battery"列表项并点击它。
|
||||
1. 停顿片刻,纯粹是为了观察自动化视觉效果。
|
||||
1. 结束 Appium 会话。
|
||||
|
||||
就是这样!让我们试一试吧。运行测试前,请确保您的 Appium 服务器
|
||||
在另一个终端会话中运行,否则会出现一个有关无法连接到 Appium 服务器的错误。
|
||||
然后,你就可以执行脚本了:
|
||||
|
||||
```bash
|
||||
node test.js
|
||||
```
|
||||
|
||||
如果一切顺利,在应用再次关闭之前,你会看到 "设置 "应用打开并导航到 "Battery "视图
|
||||
|
||||
恭喜您,您已经开始了 Appium 之旅!请继续阅读一些 [下一步骤](./next-steps.md) 继续探索.
|
||||
|
||||
@@ -1,6 +1,50 @@
|
||||
---
|
||||
title: Write a Test (Python)
|
||||
hide:
|
||||
- toc
|
||||
|
||||
title: 编写一个测试 (Python)
|
||||
---
|
||||
[Appium Python Client](https://github.com/appium/python-client) 是官方的Appium Python客户端,可通过pypi里包名为[Appium-Python-Client](https://pypi.org/project/Appium-Python-Client/)进行安装。
|
||||
它继承自 [Selenium Python Binding](https://pypi.org/project/selenium/),
|
||||
因此安装Appium Python客户端时会包含selenium绑定。
|
||||
|
||||
```bash
|
||||
pip install Appium-Python-Client
|
||||
```
|
||||
|
||||
TODO
|
||||
这个示例使用Python内置的`unittest`模块,尽管你可以使用任何你想要的Python测试框架。
|
||||
Appium Python客户端自动添加`appium:`供应商前缀。
|
||||
通常你不需要担心前缀。
|
||||
|
||||
```python title="test.py"
|
||||
--8<-- "./sample-code/quickstarts/py/test.py"
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
在本指南中,我们不会详细解释Python客户端库或这里发生的所有事情,因此现在不对代码本身进行详细说明。
|
||||
|
||||
- 你可能想要特别了解一下Appium [Capabilities](../guides/caps.md)。
|
||||
- Python客户端GitHub仓库中的[功能测试代码](https://github.com/appium/python-client/tree/master/test/functional)应该有助于找到更多的工作示例。
|
||||
- [文档](https://appium.github.io/python-client-sphinx/)也有助于找到在Appium Python客户端中定义的方法。
|
||||
|
||||
!!! note
|
||||
|
||||
示例代码可从 [GitHub Appium仓库](https://github.com/appium/appium/tree/master/packages/appium/sample-code/quickstarts/py) 获取。
|
||||
|
||||
基本上,这段代码正在做以下事情:
|
||||
|
||||
1. 定义一组“Capabilities”(参数),发送到Appium服务器,以便Appium知道你想要自动化的事物类型。
|
||||
2. 在内置的 Android 设置应用程序上启动 Appium 会话。
|
||||
3. 查找“Battery”列表项并点击它。
|
||||
4. 停顿片刻,纯粹是为了观察自动化视觉效果。。
|
||||
5. 结束Appium会话。
|
||||
|
||||
就这些!让我们试一试。在运行测试之前,请确保你有一个Appium服务器在另一个终端会话中运行,否则你将收到无法连接到服务器的错误。然后,你可以执行脚本:
|
||||
|
||||
```bash
|
||||
python test.py
|
||||
```
|
||||
|
||||
如果一切顺利,在应用再次关闭之前,你会看到 "设置 "应用打开并导航到 "Battery "视图。
|
||||
恭喜您,您已经开始了您的Appium之旅了!继续阅读一些[下一步骤](./next-steps.md)来探索它吧。
|
||||
|
||||
@@ -1,6 +1,86 @@
|
||||
---
|
||||
title: Install the UiAutomator2 Driver
|
||||
---
|
||||
|
||||
title: 安装 UiAutomator2 驱动
|
||||
|
||||
TODO
|
||||
---
|
||||
|
||||
如果没有[驱动](../intro/drivers.md),你几乎无法使用 Appium,驱动是允许 Appium 自动化特定平台的接口。
|
||||
|
||||
!!! info
|
||||
|
||||
在本快速入门指南中,我们将自动化一个 Android 平台的应用,因为通过 Appium 进行 Android 自动化的系统要求与 Appium 本身相同(而 iOS 驱动例如,需要你使用 macOS)。
|
||||
|
||||
我们将使用的驱动称为 [UiAutomator2 驱动](https://github.com/appium/appium-uiautomator2-driver)。值得访问该驱动的文档并将其加入书签,因为这将是今后不可或缺的参考资料。
|
||||
|
||||
## 设置 Android 自动化要求
|
||||
|
||||
根据驱动的要求,除了一个运行中的 Appium 服务器,我们还需要设置以下内容:
|
||||
|
||||
### Android SDK
|
||||
|
||||
- 设置 Android SDK 要求的最简单方法是下载 [Android Studio](https://developer.android.com/studio)。
|
||||
我们需要使用其 SDK 管理器 (_设置 -> 外观和行为 -> 系统设置 -> Android SDK_) 来下载以下项目:
|
||||
- Android SDK 平台(选择我们想要自动化的任何 Android 平台,例如,API 级别 30)
|
||||
- Android SDK 平台工具
|
||||
- 如果愿意,你也可以不通过 Android Studio 下载这些项目:
|
||||
- 可以使用 [Android 命令行工具](https://developer.android.com/studio#command-line-tools-only) 中包含的 `sdkmanager` 下载 Android SDK 平台
|
||||
- [Android SDK 平台工具](https://developer.android.com/tools/releases/platform-tools)
|
||||
- 设置 `ANDROID_HOME` 环境变量,指向安装 Android SDK 的目录。通常可以在 Android Studio 的 SDK 管理器中找到这个目录的路径。它将包含 `platform-tools` 和其他目录。
|
||||
|
||||
### Java JDK
|
||||
|
||||
- 安装 Java JDK(对于最新的 Android API 级别,需要 JDK 9,否则需要 JDK 8)。你可以从 [Oracle](https://jdk.java.net/) 或 [Adoptium](https://adoptium.net/en-GB/temurin/releases/) 下载。
|
||||
确保下载的是 JDK 而不是 JRE。
|
||||
- 设置 `JAVA_HOME` 环境变量,指向 JDK 的安装目录。它将包含 `bin`、`include` 和其他目录。
|
||||
|
||||
### 准备设备
|
||||
|
||||
- 如果使用模拟器,使用 Android Studio 创建并启动一个 Android 虚拟设备 (AVD)。
|
||||
你可能需要下载你想要创建的模拟器的 API 级别的系统镜像。使用 Android Studio 中的 AVD 创建向导通常是完成所有这些操作的最简单方式。
|
||||
- 如果使用真实设备,应该[为开发设置并启用 USB 调试](https://developer.android.com/studio/debug/dev-options)。
|
||||
- 连接模拟器或设备后,你可以运行 `adb devices`(通过位于 `$ANDROID_HOME/platform-tools/adb` 的二进制文件)来验证你的设备是否显示为已连接。
|
||||
|
||||
一旦你的设备在 `adb` 中显示为已连接,并且你已验证环境变量设置正确,你就可以开始了!如果在这些步骤中遇到任何问题,请参考驱动文档,或必要时查看各种 Android 或 Java 文档网站。
|
||||
|
||||
此外,恭喜你:不管你是否打算,你现在已经在你的系统上设置了 Android 开发者工具链,所以你可以开始制作 Android 应用了!
|
||||
|
||||
## 安装驱动本身
|
||||
|
||||
由于 UiAutomator2 驱动是由核心 Appium 团队维护的,它有一个官方的驱动名称,你可以通过 [Appium 扩展 CLI](../cli/extensions.md) 轻松安装:
|
||||
|
||||
```bash
|
||||
appium driver install uiautomator2
|
||||
```
|
||||
|
||||
它应该产生类似下面的输出:
|
||||
|
||||
```
|
||||
Attempting to find and install driver 'uiautomator2'
|
||||
✔ Installing 'uiautomator2' using NPM install spec 'appium-uiautomator2-driver'
|
||||
Driver uiautomator2@2.0.5 successfully installed
|
||||
- automationName: UiAutomator2
|
||||
- platformNames: ["Android"]
|
||||
```
|
||||
|
||||
运行此命令将定位并安装 UiAutomator2 驱动的最新版本,使其可用于自动化。请注意,当安装时,它会告诉你哪些平台它适用(在这种情况下是 `Android`),以及在 Appium 会话中使用此驱动时必须使用的自动化名称(在这种情况下是 `UiAutomator2` 的 `appium:automationName` [capability](../guides/caps.md))。
|
||||
|
||||
!!! note
|
||||
|
||||
在这个快速入门中,我们使用了 [扩展 CLI](../cli/extensions.md) 来安装 UiAutomator2 驱动,但如果你将 Appium 集成到一个 Node.js 项目中,你可能更喜欢使用 `npm` 来管理 Appium 及其相关驱动。要了解更多关于这种技术的信息,请访问关于[管理 Appium 扩展](../guides/managing-exts.md)的指南。
|
||||
|
||||
现在,再次启动 Appium 服务器(运行 `appium`),你应该看到新安装的驱动被列为可用:
|
||||
|
||||
```
|
||||
[Appium] Available drivers:
|
||||
[Appium] - uiautomator2@2.0.5 (automationName 'UiAutomator2')
|
||||
```
|
||||
|
||||
Android 设置完成并且 UiAutomator2 驱动已安装后,你就可以编写你的第一个测试了!现在选择你喜欢的语言并试一试:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-language-javascript: [__JavaScript__](./test-js.md)
|
||||
- :material-language-java: [__Java__](./test-java.md)
|
||||
- :material-language-python: [__Python__](./test-py.md)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
---
|
||||
title: 更多 Appium 资源
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
|
||||
title: 其他资源
|
||||
---
|
||||
|
||||
在这里,您可以找到网络上其他 Appium 资源的链接:
|
||||
|
||||
TODO
|
||||
## 网站
|
||||
|
||||
- [Appium Pro](https://appiumpro.com) - 是Appium其中一名维护者乔纳森-利普斯(Jonathan Lipps)撰写的博客和时事通讯。
|
||||
撰写的博客和时事通讯,其中包含大量实用指南
|
||||
|
||||
## 在线课程
|
||||
|
||||
- [Appium 和 Selenium 基础](https://ui.headspin.io/university/learn/appium-selenium-fundamentals-2020) - Jonathan Lipps 编写的学习 Python、Selenium 和 Appium 的综合视频课程
|
||||
- [使用 Appium 实现移动测试自动化](https://testautomationu.applitools.com/appium-java-tutorial/) - 由 Moataz Nabil 制作的视频课程
|
||||
- [高级Appoium](https://www.linkedin.com/learning/advanced-appium) - 乔纳森-利普斯(Jonathan Lipps)的视频课程
|
||||
|
||||
@@ -1085,7 +1085,7 @@ class AppiumDriver extends DriverCore {
|
||||
// is to do the proxy and retrieve the result internally so it can be passed to the plugin
|
||||
// in case it calls 'await next()'. This requires that the driver have defined
|
||||
// 'proxyCommand' and not just 'proxyReqRes'.
|
||||
if (!dstSession.proxyCommand) {
|
||||
if (!dstSession?.proxyCommand) {
|
||||
throw new NoDriverProxyCommandError();
|
||||
}
|
||||
return await dstSession.proxyCommand(
|
||||
@@ -1106,7 +1106,7 @@ class AppiumDriver extends DriverCore {
|
||||
}
|
||||
|
||||
// here we know that we are executing a session command, and have a valid session driver
|
||||
return await 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
|
||||
|
||||
@@ -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'],
|
||||
{
|
||||
|
||||
@@ -67,10 +67,11 @@ export default class DriverCliCommand extends ExtensionCliCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs doctor checks for the given driver
|
||||
* Runs doctor checks for the given driver.
|
||||
*
|
||||
* @param {DriverDoctorOptions} opts
|
||||
* @returns {Promise<import('@appium/types').IDoctorCheck[]>}
|
||||
* @returns {Promise<number>} The amount of executed doctor checks.
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async doctor({driver}) {
|
||||
return await super._doctor({
|
||||
|
||||
@@ -16,7 +16,7 @@ import {packageDidChange} from '../extension/package-changed';
|
||||
import {spawn} from 'child_process';
|
||||
import {inspect} from 'node:util';
|
||||
import {pathToFileURL} from 'url';
|
||||
import {Doctor} from '../doctor/doctor';
|
||||
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
|
||||
import {npmPackage} from '../utils';
|
||||
import semver from 'semver';
|
||||
|
||||
@@ -36,13 +36,17 @@ function receiptToManifest(receipt) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the remote extension version requirement
|
||||
* Fetches the remote extension version requirements
|
||||
*
|
||||
* @param {string} installSpec Extension name (could also contain the version suffix)
|
||||
* @param {string} pkgName Extension name
|
||||
* @param {string} [pkgVer] Extension version (if not provided then the latest is assumed)
|
||||
* @returns {Promise<[string, string|null]>}
|
||||
*/
|
||||
async function getRemoteExtensionVersionReq(installSpec) {
|
||||
const allDeps = await npm.getPackageInfo(installSpec, ['peerDependencies', 'dependencies']);
|
||||
async function getRemoteExtensionVersionReq(pkgName, pkgVer) {
|
||||
const allDeps = await npm.getPackageInfo(
|
||||
`${pkgName}${pkgVer ? `@${pkgVer}` : ``}`,
|
||||
['peerDependencies', 'dependencies']
|
||||
);
|
||||
const requiredVersionPair = _.flatMap(_.values(allDeps).map(_.toPairs))
|
||||
.find(([name]) => name === 'appium');
|
||||
return [npmPackage.version, requiredVersionPair ? requiredVersionPair[1] : null];
|
||||
@@ -246,13 +250,13 @@ class ExtensionCliCommand {
|
||||
* @param {InstallViaNpmArgs} installViaNpmOpts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async _checkInstallCompatibility({installSpec, pkgName, installType}) {
|
||||
async _checkInstallCompatibility({installSpec, pkgName, pkgVer, installType}) {
|
||||
if (INSTALL_TYPE_NPM !== installType) {
|
||||
return;
|
||||
}
|
||||
|
||||
await spinWith(this.isJsonOutput, `Checking if '${pkgName}' is compatible`, async () => {
|
||||
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(installSpec);
|
||||
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(pkgName, pkgVer);
|
||||
if (serverVersion && extVersionRequirement && !semver.satisfies(serverVersion, extVersionRequirement)) {
|
||||
throw this._createFatalError(
|
||||
`'${installSpec}' cannot be installed because the server version it requires (${extVersionRequirement}) ` +
|
||||
@@ -738,10 +742,12 @@ class ExtensionCliCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs doctor checks for the given extension
|
||||
* Runs doctor checks for the given extension.
|
||||
*
|
||||
* @param {DoctorOptions} opts
|
||||
* @returns {Promise<import('@appium/types').IDoctorCheck[]>}
|
||||
* @returns {Promise<number>} The amount of Doctor checks that were
|
||||
* successfully loaded and executed for the given extension
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async _doctor({installSpec}) {
|
||||
if (!this.config.isInstalled(installSpec)) {
|
||||
@@ -765,7 +771,7 @@ class ExtensionCliCommand {
|
||||
}
|
||||
if (!doctorSpec) {
|
||||
this.log.info(`The ${this.type} "${installSpec}" does not export any doctor checks`);
|
||||
return [];
|
||||
return 0;
|
||||
}
|
||||
if (!_.isPlainObject(doctorSpec) || !_.isArray(doctorSpec.checks)) {
|
||||
throw this._createFatalError(
|
||||
@@ -806,14 +812,17 @@ class ExtensionCliCommand {
|
||||
.filter(isDoctorCheck);
|
||||
if (_.isEmpty(checks)) {
|
||||
this.log.info(`The ${this.type} "${installSpec}" exports no valid doctor checks`);
|
||||
return [];
|
||||
return 0;
|
||||
}
|
||||
this.log.debug(
|
||||
`Running ${util.pluralize('doctor check', checks.length, true)} ` +
|
||||
`for the "${installSpec}" ${this.type}`
|
||||
);
|
||||
await new Doctor(checks).run();
|
||||
return checks;
|
||||
const exitCode = await new Doctor(checks).run();
|
||||
if (exitCode !== DOCTOR_EXIT_CODE.SUCCESS) {
|
||||
throw this._createFatalError('Treatment required');
|
||||
}
|
||||
return checks.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,8 @@ export default class PluginCliCommand extends ExtensionCliCommand {
|
||||
* Runs doctor checks for the given plugin
|
||||
*
|
||||
* @param {PluginDoctorOptions} opts
|
||||
* @returns {Promise<import('@appium/types').IDoctorCheck[]>}
|
||||
* @returns {Promise<number>} The amount of executed doctor checks.
|
||||
* @throws {Error} If any of the mandatory Doctor checks fails.
|
||||
*/
|
||||
async doctor({plugin}) {
|
||||
return await super._doctor({
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +2,11 @@ import '@colors/colors';
|
||||
import _ from 'lodash';
|
||||
import { util, doctor, logger } from '@appium/support';
|
||||
|
||||
export const EXIT_CODE = /** @type {const} */ Object.freeze({
|
||||
SUCCESS: 0,
|
||||
HAS_MAJOR_ISSUES: 127,
|
||||
});
|
||||
|
||||
export class Doctor {
|
||||
/**
|
||||
* @param {DoctorCheck[]} [checks=[]]
|
||||
@@ -68,7 +73,12 @@ export class Doctor {
|
||||
/** @type {string[]} */
|
||||
const fixMessages = [];
|
||||
for (const issue of issues) {
|
||||
const message = await issue.check.fix();
|
||||
let message;
|
||||
try {
|
||||
message = await issue.check.fix();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
if (message) {
|
||||
fixMessages.push(message);
|
||||
}
|
||||
@@ -84,7 +94,7 @@ export class Doctor {
|
||||
'The configuration cannot be automatically fixed, please do the following first:',
|
||||
], manualIssues);
|
||||
await handleIssues([
|
||||
'### Optional Manual Fixes ###',
|
||||
'### Optional Manual Fixes ###',
|
||||
'To fix these optional issues, please do the following manually:',
|
||||
], manualIssuesOptional);
|
||||
|
||||
@@ -127,6 +137,9 @@ export class Doctor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async runAutoFixes() {
|
||||
const autoFixes = _.filter(this.foundIssues, (f) => f.check.hasAutofix());
|
||||
for (const f of autoFixes) {
|
||||
@@ -136,22 +149,30 @@ export class Doctor {
|
||||
if (_.find(autoFixes, (f) => !f.fixed)) {
|
||||
// a few issues remain.
|
||||
this.log.info('Bye! A few issues remain, fix manually and/or rerun doctor!');
|
||||
} else {
|
||||
// nothing left to fix.
|
||||
this.log.info('Bye! All issues have been fixed!');
|
||||
this.log.info('');
|
||||
return false;
|
||||
}
|
||||
// nothing left to fix.
|
||||
this.log.info('Bye! All issues have been fixed!');
|
||||
this.log.info('');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<EXIT_CODE[keyof EXIT_CODE]>}
|
||||
*/
|
||||
async run() {
|
||||
await this.diagnose();
|
||||
if (this.reportSuccess()) {
|
||||
return;
|
||||
return EXIT_CODE.SUCCESS;
|
||||
}
|
||||
if (await this.reportManualIssues()) {
|
||||
return;
|
||||
return EXIT_CODE.HAS_MAJOR_ISSUES;
|
||||
}
|
||||
await this.runAutoFixes();
|
||||
if (!await this.runAutoFixes()) {
|
||||
return EXIT_CODE.HAS_MAJOR_ISSUES;
|
||||
}
|
||||
return EXIT_CODE.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -311,7 +311,9 @@ export class ExtensionConfig {
|
||||
const invalidFieldsText = invalidFields.map((field) => `"${field}"`).join(', ');
|
||||
|
||||
warnings.push(
|
||||
`${extTypeText} "${extName}" (package \`${pkgName}\`) has ${invalidFieldsEnumerationText} (${invalidFieldsText}) in \`extensions.yaml\`; this may cause upgrades done via the \`appium\` CLI tool to fail. Please reinstall with \`appium ${this.extensionType} uninstall ${extName}\` and \`appium ${this.extensionType} install ${extName}\` to attempt a fix.`
|
||||
`${extTypeText} "${extName}" (package \`${pkgName}\`) has ${invalidFieldsEnumerationText} (${invalidFieldsText}) in \`extensions.yaml\`; ` +
|
||||
`this may cause upgrades done via the \`appium\` CLI tool to fail. Please reinstall with \`appium ${this.extensionType} uninstall ` +
|
||||
`${extName}\` and \`appium ${this.extensionType} install ${extName}\` to attempt a fix.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -331,16 +333,16 @@ export class ExtensionConfig {
|
||||
);
|
||||
if (extListData?.installed) {
|
||||
const {updateVersion, upToDate} = extListData;
|
||||
if (!upToDate) {
|
||||
if (!upToDate && updateVersion) {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`its peer dependency on older Appium v${appiumVersion}. Please upgrade \`${pkgName}\` to v${updateVersion} or newer.`
|
||||
`its peer dependency on Appium ${appiumVersion}. Try to upgrade \`${pkgName}\` to v${updateVersion} or newer.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`its peer dependency on older Appium v${appiumVersion}. Please ask the developer of \`${pkgName}\` to update the peer dependency on Appium to v${APPIUM_VER}.`
|
||||
`its peer dependency on Appium ${appiumVersion}. Please install a compatible version of the ${_.toLower(extTypeText)}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -354,13 +356,15 @@ export class ExtensionConfig {
|
||||
if (!extListData?.upToDate && extListData?.updateVersion) {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`an invalid or missing peer dependency on Appium. A newer version of \`${pkgName}\` is available; please attempt to upgrade "${extName}" to v${extListData.updateVersion} or newer.`
|
||||
`an invalid or missing peer dependency on Appium. A newer version of \`${pkgName}\` is available; ` +
|
||||
`please attempt to upgrade "${extName}" to v${extListData.updateVersion} or newer.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
warnings.push(
|
||||
createPeerWarning(
|
||||
`an invalid or missing peer dependency on Appium. Please ask the developer of \`${pkgName}\` to add a peer dependency on \`^appium@${APPIUM_VER}\`.`
|
||||
`an invalid or missing peer dependency on Appium. ` +
|
||||
`Please ask the developer of \`${pkgName}\` to add a peer dependency on \`^appium@${APPIUM_VER}\`.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -201,11 +206,14 @@ 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}) => {
|
||||
const winstonLevel = npmToWinstonLevels[level] || 'info';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -248,10 +260,10 @@ async function init(args) {
|
||||
`Found no log filtering rules in '${serverArgs.logFilters}'. Is that expected?`,
|
||||
);
|
||||
} else {
|
||||
// Filtering aims to "hide" these values from the log,
|
||||
// so it would be nice to not show them in the log as well.
|
||||
logger.info(
|
||||
`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${
|
||||
serverArgs.logFilters
|
||||
}'`,
|
||||
`Loaded ${util.pluralize('filtering rule', rules.length, true)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ import {ArgumentTypeError} from 'argparse';
|
||||
import _ from 'lodash';
|
||||
import {formatErrors as formatErrors} from '../config-file';
|
||||
import {flattenSchema, validate} from './schema';
|
||||
import {transformers} from './cli-transformers';
|
||||
import {transformers, parseCsvLine} from './cli-transformers';
|
||||
|
||||
/**
|
||||
* This module concerns functions which convert schema definitions to
|
||||
@@ -136,13 +136,19 @@ function subSchemaToArgDef(subSchema, argSpec) {
|
||||
}
|
||||
|
||||
case TYPENAMES.OBJECT: {
|
||||
argTypeFunction = transformers.json;
|
||||
argTypeFunction = _.flow(transformers.json, (o) => {
|
||||
// Arrays and plain strings are also valid JSON
|
||||
if (!_.isPlainObject(o)) {
|
||||
throw new ArgumentTypeError(`'${_.truncate(o, {length: 100})}' must be a plain object`);;
|
||||
}
|
||||
return o;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// arrays are treated as CSVs, because `argparse` doesn't handle array data.
|
||||
case TYPENAMES.ARRAY: {
|
||||
argTypeFunction = transformers.csv;
|
||||
argTypeFunction = parseCsvLine;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -182,12 +188,13 @@ function subSchemaToArgDef(subSchema, argSpec) {
|
||||
argOpts.metavar = screamingSnakeCase(name);
|
||||
}
|
||||
|
||||
// the validity of "appiumCliTransformer" should already have been determined
|
||||
// by ajv during schema validation in `finalizeSchema()`. the `array` &
|
||||
// `object` types have already added a formatter (see above, so we don't do it
|
||||
// twice).
|
||||
if (type !== TYPENAMES.ARRAY && type !== TYPENAMES.OBJECT && appiumCliTransformer) {
|
||||
argTypeFunction = _.flow(argTypeFunction ?? _.identity, transformers[appiumCliTransformer]);
|
||||
if (appiumCliTransformer && transformers[appiumCliTransformer]) {
|
||||
if (type === TYPENAMES.ARRAY) {
|
||||
const csvTransformer = /** @type {(x: string) => string[]} */ (argTypeFunction);
|
||||
argTypeFunction = (val) => _.flatMap(csvTransformer(val).map(transformers[appiumCliTransformer]));
|
||||
} else {
|
||||
argTypeFunction = _.flow(argTypeFunction ?? _.identity, transformers[appiumCliTransformer]);
|
||||
}
|
||||
}
|
||||
|
||||
if (argTypeFunction) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {ArgumentTypeError} from 'argparse';
|
||||
import {readFileSync, existsSync} from 'fs';
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* This module provides custom keywords for Appium schemas, as well as
|
||||
@@ -16,7 +15,7 @@ import _ from 'lodash';
|
||||
* @param {string} value
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function parseCsvLine(value) {
|
||||
export function parseCsvLine(value) {
|
||||
return value
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
@@ -71,7 +70,6 @@ export const transformers = {
|
||||
}
|
||||
loadedFromFile = true;
|
||||
}
|
||||
|
||||
try {
|
||||
return loadedFromFile ? parseCsvFile(csv) : parseCsvLine(csv);
|
||||
} catch (err) {
|
||||
@@ -104,14 +102,10 @@ export const transformers = {
|
||||
loadedFromFile = true;
|
||||
}
|
||||
try {
|
||||
const result = JSON.parse(json);
|
||||
if (!_.isPlainObject(result)) {
|
||||
throw new Error(`'${_.truncate(result, {length: 100})}' is not an object`);
|
||||
}
|
||||
return result;
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
const msg = loadedFromFile
|
||||
? `The provided value of '${jsonOrPath}' must be a valid JSON`
|
||||
? `'${jsonOrPath}' must be a valid JSON`
|
||||
: `The provided value must be a valid JSON`;
|
||||
throw new TypeError(`${msg}. Original error: ${e.message}`);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.4.0",
|
||||
"version": "2.6.0",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -60,41 +60,41 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.5.0",
|
||||
"@appium/base-plugin": "^2.2.26",
|
||||
"@appium/docutils": "^1.0.2",
|
||||
"@appium/base-driver": "^9.7.0",
|
||||
"@appium/base-plugin": "^2.2.32",
|
||||
"@appium/docutils": "^1.0.8",
|
||||
"@appium/schema": "~0.5.0",
|
||||
"@appium/support": "^4.2.0",
|
||||
"@appium/types": "^0.16.0",
|
||||
"@appium/support": "^4.2.6",
|
||||
"@appium/types": "^0.18.0",
|
||||
"@sidvind/better-ajv-errors": "2.1.3",
|
||||
"@types/argparse": "2.0.14",
|
||||
"@types/argparse": "2.0.16",
|
||||
"@types/bluebird": "3.5.42",
|
||||
"@types/fancy-log": "2.0.2",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/teen_process": "2.0.4",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"ajv": "8.14.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.4.1",
|
||||
"asyncbox": "3.0.0",
|
||||
"axios": "1.6.3",
|
||||
"axios": "1.7.2",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"lilconfig": "3.0.0",
|
||||
"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.5.4",
|
||||
"semver": "7.6.2",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.1.1",
|
||||
"type-fest": "4.9.0",
|
||||
"winston": "3.11.0",
|
||||
"teen_process": "2.1.3",
|
||||
"type-fest": "4.18.3",
|
||||
"winston": "3.13.0",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"ws": "8.16.0",
|
||||
"yaml": "2.3.4"
|
||||
"ws": "8.17.0",
|
||||
"yaml": "2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "8.27.0"
|
||||
"webdriverio": "8.38.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,8 +423,8 @@ describe('Driver CLI', function () {
|
||||
|
||||
describe('when the driver defines doctor checks', function () {
|
||||
it('should load and run them', async function () {
|
||||
const checks = await runDoctor([driverName]);
|
||||
checks.length.should.eql(2);
|
||||
const checksLen = await runDoctor([driverName]);
|
||||
checksLen.should.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -592,8 +592,8 @@ describe('FakeDriver via HTTP', function () {
|
||||
collectedEvents.should.not.be.empty;
|
||||
|
||||
// finally unsubscribe and wait and assert that some events have been collected
|
||||
collectedEvents = [];
|
||||
await driver.sessionUnsubscribe({events: ['clock.currentTime']});
|
||||
collectedEvents = [];
|
||||
await B.delay(750);
|
||||
collectedEvents.should.be.empty;
|
||||
});
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -239,7 +239,7 @@ describe('ExtensionConfig', function () {
|
||||
await expect(
|
||||
config.getGenericConfigWarnings(extData, extData.pkgName)
|
||||
).to.eventually.eql([
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on older Appium v${extData.appiumVersion}. Please upgrade \`${extData.pkgName}\` to v${updateVersion} or newer.`,
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on Appium ${extData.appiumVersion}. Try to upgrade \`${extData.pkgName}\` to v${updateVersion} or newer.`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -254,7 +254,7 @@ describe('ExtensionConfig', function () {
|
||||
await expect(
|
||||
config.getGenericConfigWarnings(extData, extData.pkgName)
|
||||
).to.eventually.eql([
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on older Appium v${extData.appiumVersion}. Please ask the developer of \`${extData.pkgName}\` to update the peer dependency on Appium to v${APPIUM_VER}.`,
|
||||
`Driver "${extData.pkgName}" (package \`${extData.pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to its peer dependency on Appium ${extData.appiumVersion}. Please install a compatible version of the driver.`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import _ from 'lodash';
|
||||
import {PLUGIN_TYPE} from '../../../lib/constants';
|
||||
import {finalizeSchema, registerSchema, resetSchema} from '../../../lib/schema';
|
||||
import {toParserArgs} from '../../../lib/schema/cli-args';
|
||||
import {transformers} from '../../../lib/schema/cli-transformers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
@@ -63,7 +62,7 @@ describe('cli-args', function () {
|
||||
});
|
||||
|
||||
it('should use the `json` transformer', function () {
|
||||
expect(result['--plugin-blob-foo']).to.have.property('type', transformers.json);
|
||||
expect(result['--plugin-blob-foo']).to.have.property('type');
|
||||
});
|
||||
|
||||
it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () {
|
||||
@@ -78,7 +77,7 @@ describe('cli-args', function () {
|
||||
});
|
||||
|
||||
it('should use the `csv` transformer', function () {
|
||||
expect(result['--plugin-blob-foo']).to.have.property('type', transformers.csv);
|
||||
expect(result['--plugin-blob-foo']).to.have.property('type');
|
||||
});
|
||||
|
||||
it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () {
|
||||
@@ -211,11 +210,11 @@ describe('cli-args', function () {
|
||||
|
||||
it('should error if the value is not valid for the transformer', function () {
|
||||
const schema = {
|
||||
properties: {foo: {type: 'string', appiumCliTransformer: 'json'}},
|
||||
properties: {foo: {type: 'object'}},
|
||||
type: 'object',
|
||||
};
|
||||
result = getArgs({schema, extName, extType});
|
||||
expect(() => result['--plugin-blob-foo'].type('123')).to.throw(/must be a valid json/i);
|
||||
expect(() => result['--plugin-blob-foo'].type('123')).to.throw(/must be a plain object/i);
|
||||
});
|
||||
|
||||
// this is unlikely to happen, but I want to establish the behavior as defined.
|
||||
|
||||
@@ -82,12 +82,10 @@ describe('utils', function () {
|
||||
'appium:foo2': 'bar2',
|
||||
}
|
||||
);
|
||||
res.error.should.eql({
|
||||
jsonwpCode: 61,
|
||||
error: 'invalid argument',
|
||||
w3cStatus: 400,
|
||||
_stacktrace: null,
|
||||
});
|
||||
res.error.jsonwpCode.should.eql(61);
|
||||
res.error.error.should.eql('invalid argument');
|
||||
res.error.w3cStatus.should.eql(400);
|
||||
_.isNull(res.error._stacktrace).should.be.true;
|
||||
});
|
||||
it('should reject if W3C caps are not passing constraints', function () {
|
||||
const err = parseCapsForInnerDriver(undefined, W3C_CAPS, {
|
||||
@@ -101,14 +99,13 @@ describe('utils', function () {
|
||||
...W3C_CAPS,
|
||||
firstMatch: [{foo: 'bar'}, {'appium:hello': 'world'}],
|
||||
};
|
||||
parseCapsForInnerDriver(BASE_CAPS, w3cCaps, {
|
||||
const error = parseCapsForInnerDriver(BASE_CAPS, w3cCaps, {
|
||||
hello: {presence: true},
|
||||
}).error.should.eql({
|
||||
jsonwpCode: 61,
|
||||
error: 'invalid argument',
|
||||
w3cStatus: 400,
|
||||
_stacktrace: null,
|
||||
});
|
||||
}).error;
|
||||
error.jsonwpCode.should.eql(61);
|
||||
error.error.should.eql('invalid argument');
|
||||
error.w3cStatus.should.eql(400);
|
||||
_.isNull(error._stacktrace).should.be.true;
|
||||
});
|
||||
it('should add appium prefixes to W3C caps that are not standard in W3C', function () {
|
||||
parseCapsForInnerDriver(undefined, {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -3,6 +3,84 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **base-driver:** Add onDownload handler to the configureApp helper ([#20015](https://github.com/appium/appium/issues/20015)) ([8cf3efd](https://github.com/appium/appium/commit/8cf3efdbed6c0868f2d7838e249a3d2478f186d4))
|
||||
|
||||
|
||||
|
||||
## [9.5.4](https://github.com/appium/appium/compare/@appium/base-driver@9.5.3...@appium/base-driver@9.5.4) (2024-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not print deprecation errors for non-provided caps ([#19986](https://github.com/appium/appium/issues/19986)) ([9f655f6](https://github.com/appium/appium/commit/9f655f6f852bc10ca1f51529183c7c44eb4c79ea))
|
||||
|
||||
|
||||
|
||||
## [9.5.3](https://github.com/appium/appium/compare/@appium/base-driver@9.5.2...@appium/base-driver@9.5.3) (2024-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **base-driver:** update dependency express to v4.18.3 ([917084c](https://github.com/appium/appium/commit/917084ccca23c5483e7d83ce97721061dd2fe345))
|
||||
* **base-driver:** update dependency express to v4.19.1 ([a74132c](https://github.com/appium/appium/commit/a74132cc96d201bb5c7b7f11a7e3b1b79e7d2424))
|
||||
* **base-driver:** update dependency express to v4.19.2 ([7cb1621](https://github.com/appium/appium/commit/7cb1621e80bc4225c48833cc2bd02fe80dccd382))
|
||||
* **base-driver:** update dependency path-to-regexp to v6.2.2 ([#19979](https://github.com/appium/appium/issues/19979)) ([b8368bb](https://github.com/appium/appium/commit/b8368bb666fda44e3b0c45cc7307bff69fe40202))
|
||||
* **docutils:** update dependency typescript to v5.4.2 ([#19876](https://github.com/appium/appium/issues/19876)) ([2448fa0](https://github.com/appium/appium/commit/2448fa0145620657ccc72b5637f1b7737fe52580))
|
||||
* **driver-test-support:** update definitelytyped ([4776574](https://github.com/appium/appium/commit/47765747b66c5e0076f6ffe4619d6b98a42aee29))
|
||||
* **support:** update dependency axios to v1.6.8 ([bd6ab81](https://github.com/appium/appium/commit/bd6ab81c9408ab0f90fc25fc112f9257ec2973ad))
|
||||
|
||||
|
||||
|
||||
## [9.5.2](https://github.com/appium/appium/compare/@appium/base-driver@9.5.1...@appium/base-driver@9.5.2) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-driver
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.5.1](https://github.com/appium/appium/compare/@appium/base-driver@9.5.0...@appium/base-driver@9.5.1) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **images-plugin:** update dependency lru-cache to v10 ([#19723](https://github.com/appium/appium/issues/19723)) ([202da83](https://github.com/appium/appium/commit/202da83449073ddb3da8fb2cfbcfce482a5ecd40))
|
||||
* **support:** update dependency axios to v1.6.4 ([332cc48](https://github.com/appium/appium/commit/332cc48a09b5532a8d51f85f3a24785e2c754e00))
|
||||
* **support:** update dependency axios to v1.6.5 ([#19616](https://github.com/appium/appium/issues/19616)) ([ac73522](https://github.com/appium/appium/commit/ac73522351b31bd6c11972c61daa8b6b8d18fb91))
|
||||
* **support:** update dependency axios to v1.6.6 ([6313704](https://github.com/appium/appium/commit/6313704ee5a8ee3aee726eb512ef259b6fa1041c))
|
||||
* **support:** update dependency axios to v1.6.7 ([795092a](https://github.com/appium/appium/commit/795092a97f6d7569cccc4b5c166f52fef821514b))
|
||||
* **types:** update dependency type-fest to v4.10 ([#19694](https://github.com/appium/appium/issues/19694)) ([966d305](https://github.com/appium/appium/commit/966d305e5eade9369a3875243bcad951df88545c))
|
||||
* **types:** update dependency type-fest to v4.10.1 ([#19703](https://github.com/appium/appium/issues/19703)) ([501395c](https://github.com/appium/appium/commit/501395c9489320b84ab49ff78af4270f66070d62))
|
||||
|
||||
|
||||
|
||||
## [9.5.0](https://github.com/appium/appium/compare/@appium/base-driver@9.4.4...@appium/base-driver@9.5.0) (2024-01-03)
|
||||
|
||||
|
||||
|
||||
+98
-169
@@ -1,34 +1,54 @@
|
||||
// @ts-check
|
||||
|
||||
import type {
|
||||
Constraints,
|
||||
NSCapabilities,
|
||||
Capabilities,
|
||||
W3CCapabilities,
|
||||
StandardCapabilities,
|
||||
} from '@appium/types';
|
||||
import type {
|
||||
StringKeyOf,
|
||||
MergeExclusive,
|
||||
} from 'type-fest';
|
||||
import _ from 'lodash';
|
||||
import {validator} from './desired-caps';
|
||||
import {util} from '@appium/support';
|
||||
import log from './logger';
|
||||
import {errors} from '../protocol/errors';
|
||||
|
||||
const APPIUM_VENDOR_PREFIX = 'appium:';
|
||||
const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}options`;
|
||||
export const APPIUM_VENDOR_PREFIX = 'appium:';
|
||||
export const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}options`;
|
||||
|
||||
export type ParsedCaps<C extends Constraints> = {
|
||||
allFirstMatchCaps: NSCapabilities<C>[];
|
||||
validatedFirstMatchCaps: Capabilities<C>[];
|
||||
requiredCaps: NSCapabilities<C>;
|
||||
matchedCaps: Capabilities<C> | null;
|
||||
validationErrors: string[];
|
||||
};
|
||||
export type ValidateCapsOpts = {
|
||||
/** if true, skip the presence constraint */
|
||||
skipPresenceConstraint?: boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes primary caps object and merges it into a secondary caps object.
|
||||
* @template {Constraints} T
|
||||
* @template {Constraints} U
|
||||
* @template {Capabilities<T>} Primary
|
||||
* @template {Capabilities<U>} Secondary
|
||||
* @param {Primary} [primary]
|
||||
* @param {Secondary} [secondary]
|
||||
* @returns {MergeExclusive<Primary, Secondary>}
|
||||
*
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-merging-capabilities)
|
||||
*/
|
||||
function mergeCaps(
|
||||
primary = /** @type {Primary} */ ({}),
|
||||
secondary = /** @type {Secondary} */ ({})
|
||||
) {
|
||||
let result = /** @type {MergeExclusive<Primary, Secondary>} */ ({
|
||||
export function mergeCaps<
|
||||
T extends Constraints,
|
||||
U extends Constraints,
|
||||
Primary extends Capabilities<T>,
|
||||
Secondary extends Capabilities<U>
|
||||
>(
|
||||
primary: Primary | undefined = {} as Primary,
|
||||
secondary: Secondary | undefined = {} as Secondary
|
||||
): MergeExclusive<Primary, Secondary> {
|
||||
const result = ({
|
||||
...primary,
|
||||
});
|
||||
}) as MergeExclusive<Primary, Secondary>;
|
||||
|
||||
for (let [name, value] of Object.entries(secondary)) {
|
||||
for (const [name, value] of Object.entries(secondary)) {
|
||||
// Overwriting is not allowed. Primary and secondary must have different properties (w3c rule 4.4)
|
||||
if (!_.isUndefined(primary[name])) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
@@ -37,7 +57,7 @@ function mergeCaps(
|
||||
)}) and secondary (${JSON.stringify(secondary)}) object`
|
||||
);
|
||||
}
|
||||
result[/** @type {keyof typeof result} */ (name)] = value;
|
||||
result[name as keyof typeof result] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -45,21 +65,20 @@ function mergeCaps(
|
||||
|
||||
/**
|
||||
* Validates caps against a set of constraints
|
||||
* @template {Constraints} C
|
||||
* @param {Capabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {ValidateCapsOpts} [opts]
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) {
|
||||
let {skipPresenceConstraint} = opts;
|
||||
export function validateCaps<C extends Constraints>(
|
||||
caps: Capabilities<C>,
|
||||
constraints: C | undefined = {} as C,
|
||||
opts: ValidateCapsOpts | undefined = {}
|
||||
): Capabilities<C> {
|
||||
const {skipPresenceConstraint} = opts;
|
||||
|
||||
if (!_.isPlainObject(caps)) {
|
||||
throw new errors.InvalidArgumentError(`must be a JSON object`);
|
||||
}
|
||||
|
||||
// Remove the 'presence' constraint if we're not checking for it
|
||||
constraints = /** @type {C} */ (
|
||||
constraints = (
|
||||
_.mapValues(
|
||||
constraints,
|
||||
skipPresenceConstraint
|
||||
@@ -73,16 +92,16 @@ function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) {
|
||||
return constraint;
|
||||
}
|
||||
)
|
||||
);
|
||||
) as C;
|
||||
|
||||
const validationErrors = validator.validate(_.pickBy(caps, util.hasValue), constraints, {
|
||||
fullMessages: false,
|
||||
});
|
||||
|
||||
if (validationErrors) {
|
||||
let message = [];
|
||||
for (let [attribute, reasons] of _.toPairs(validationErrors)) {
|
||||
for (let reason of reasons) {
|
||||
const message: string[] = [];
|
||||
for (const [attribute, reasons] of _.toPairs(validationErrors)) {
|
||||
for (const reason of (reasons as string[])) {
|
||||
message.push(`'${attribute}' ${reason}`);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +118,7 @@ function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) {
|
||||
*/
|
||||
export const STANDARD_CAPS = Object.freeze(
|
||||
new Set(
|
||||
/** @type {StringKeyOf<import('@appium/types').StandardCapabilities>[]} */ ([
|
||||
([
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
@@ -110,29 +129,22 @@ export const STANDARD_CAPS = Object.freeze(
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
'webSocketUrl',
|
||||
])
|
||||
]) as StringKeyOf<StandardCapabilities>[]
|
||||
)
|
||||
);
|
||||
|
||||
const STANDARD_CAPS_LOWER = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
|
||||
|
||||
/**
|
||||
* @param {string} cap
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isStandardCap(cap) {
|
||||
export function isStandardCap(cap: string): boolean {
|
||||
return STANDARD_CAPS_LOWER.has(cap.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix
|
||||
* @template {Constraints} C
|
||||
* @param {NSCapabilities<C>} caps
|
||||
* @see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities
|
||||
* @internal
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function stripAppiumPrefixes(caps) {
|
||||
export function stripAppiumPrefixes<C extends Constraints>(caps: NSCapabilities<C>): Capabilities<C> {
|
||||
// split into prefixed and non-prefixed.
|
||||
// non-prefixed should be standard caps at this point
|
||||
const [prefixedCaps, nonPrefixedCaps] = _.partition(_.keys(caps), (cap) =>
|
||||
@@ -140,14 +152,12 @@ function stripAppiumPrefixes(caps) {
|
||||
);
|
||||
|
||||
// initialize this with the k/v pairs of the non-prefixed caps
|
||||
let strippedCaps = /** @type {Capabilities<C>} */ (_.pick(caps, nonPrefixedCaps));
|
||||
const badPrefixedCaps = [];
|
||||
const strippedCaps = (_.pick(caps, nonPrefixedCaps)) as Capabilities<C>;
|
||||
const badPrefixedCaps: string[] = [];
|
||||
|
||||
// Strip out the 'appium:' prefix
|
||||
for (let prefixedCap of prefixedCaps) {
|
||||
const strippedCapName = /** @type {StringKeyOf<Capabilities<C>>} */ (
|
||||
prefixedCap.substring(APPIUM_VENDOR_PREFIX.length)
|
||||
);
|
||||
for (const prefixedCap of prefixedCaps) {
|
||||
const strippedCapName = prefixedCap.substring(APPIUM_VENDOR_PREFIX.length) as StringKeyOf<Capabilities<C>>;
|
||||
|
||||
// If it's standard capability that was prefixed, add it to an array of incorrectly prefixed capabilities
|
||||
if (isStandardCap(strippedCapName)) {
|
||||
@@ -178,10 +188,13 @@ function stripAppiumPrefixes(caps) {
|
||||
|
||||
/**
|
||||
* Get an array of all the unprefixed caps that are being used in 'alwaysMatch' and all of the 'firstMatch' object
|
||||
* @template {Constraints} C
|
||||
* @param {W3CCapabilities<C>} caps A capabilities object
|
||||
*/
|
||||
function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
export function findNonPrefixedCaps<C extends Constraints>(
|
||||
{
|
||||
alwaysMatch = {},
|
||||
firstMatch = []
|
||||
}: W3CCapabilities<C>
|
||||
): string[] {
|
||||
return _.chain([alwaysMatch, ...firstMatch])
|
||||
.reduce(
|
||||
(unprefixedCaps, caps) => [
|
||||
@@ -194,27 +207,15 @@ function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) {
|
||||
.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by {@linkcode parseCaps}
|
||||
* @template {Constraints} C
|
||||
* @typedef ParsedCaps
|
||||
* @property {NSCapabilities<C>[]} allFirstMatchCaps
|
||||
* @property {Capabilities<C>[]} validatedFirstMatchCaps
|
||||
* @property {NSCapabilities<C>} requiredCaps
|
||||
* @property {Capabilities<C>|null} matchedCaps
|
||||
* @property {string[]} validationErrors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse capabilities
|
||||
* @template {Constraints} C
|
||||
* @param {W3CCapabilities<C>} caps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @see https://www.w3.org/TR/webdriver/#processing-capabilities
|
||||
* @returns {ParsedCaps<C>}
|
||||
*/
|
||||
function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps = true) {
|
||||
export function parseCaps<C extends Constraints>(
|
||||
caps: W3CCapabilities<C>,
|
||||
constraints: C | undefined = {} as C,
|
||||
shouldValidateCaps: boolean | undefined = true
|
||||
): ParsedCaps<C> {
|
||||
// If capabilities request is not an object, return error (#1.1)
|
||||
if (!_.isPlainObject(caps)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
@@ -224,9 +225,9 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
|
||||
// Let 'requiredCaps' be property named 'alwaysMatch' from capabilities request (#2)
|
||||
// and 'allFirstMatchCaps' be property named 'firstMatch' from capabilities request (#3)
|
||||
let {
|
||||
alwaysMatch: requiredCaps = /** @type {NSCapabilities<C>} */ ({}), // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1)
|
||||
firstMatch: allFirstMatchCaps = /** @type {NSCapabilities<C>[]} */ ([{}]), // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1)
|
||||
const {
|
||||
alwaysMatch: requiredCaps = {} as NSCapabilities<C>, // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1)
|
||||
firstMatch: allFirstMatchCaps = [{}] as NSCapabilities<C>[], // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1)
|
||||
} = caps;
|
||||
|
||||
// Reject 'firstMatch' argument if it's not an array (#3.2)
|
||||
@@ -247,7 +248,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
}
|
||||
|
||||
// Check for non-prefixed, non-standard capabilities and log warnings if they are found
|
||||
let nonPrefixedCaps = findNonPrefixedCaps(caps);
|
||||
const nonPrefixedCaps = findNonPrefixedCaps(caps);
|
||||
if (!_.isEmpty(nonPrefixedCaps)) {
|
||||
throw new errors.InvalidArgumentError(
|
||||
`All non-standard capabilities should have a vendor prefix. The following capabilities did not have one: ${nonPrefixedCaps}`
|
||||
@@ -256,8 +257,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
|
||||
// Strip out the 'appium:' prefix from all
|
||||
let strippedRequiredCaps = stripAppiumPrefixes(requiredCaps);
|
||||
/** @type {Capabilities<C>[]} */
|
||||
let strippedAllFirstMatchCaps = allFirstMatchCaps.map(stripAppiumPrefixes);
|
||||
const strippedAllFirstMatchCaps: Capabilities<C>[] = allFirstMatchCaps.map(stripAppiumPrefixes);
|
||||
|
||||
// Validate the requiredCaps. But don't validate 'presence' because if that constraint fails on 'alwaysMatch' it could still pass on one of the 'firstMatch' keys
|
||||
if (shouldValidateCaps) {
|
||||
@@ -267,14 +267,11 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
}
|
||||
// Remove the 'presence' constraint for any keys that are already present in 'requiredCaps'
|
||||
// since we know that this constraint has already passed
|
||||
const filteredConstraints = /** @type {C} */ (
|
||||
_.omitBy(constraints, (_, key) => key in strippedRequiredCaps)
|
||||
);
|
||||
const filteredConstraints = _.omitBy(constraints, (_, key) => key in strippedRequiredCaps) as C;
|
||||
|
||||
// Validate all of the first match capabilities and return an array with only the valid caps (see spec #5)
|
||||
/** @type {string[]} */
|
||||
let validationErrors = [];
|
||||
let validatedFirstMatchCaps = _.compact(
|
||||
const validationErrors: string[] = [];
|
||||
const validatedFirstMatchCaps = _.compact(
|
||||
strippedAllFirstMatchCaps.map((firstMatchCaps) => {
|
||||
try {
|
||||
// Validate firstMatch caps
|
||||
@@ -285,17 +282,16 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
validationErrors.push(e.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
) as Capabilities<C>[];
|
||||
|
||||
/**
|
||||
* Try to merge requiredCaps with first match capabilities, break once it finds its first match
|
||||
* (see spec #6)
|
||||
* @type {ParsedCaps<C>['matchedCaps']}
|
||||
*/
|
||||
let matchedCaps = null;
|
||||
for (let firstMatchCaps of validatedFirstMatchCaps) {
|
||||
let matchedCaps: ParsedCaps<C>['matchedCaps'] = null;
|
||||
for (const firstMatchCaps of validatedFirstMatchCaps) {
|
||||
try {
|
||||
matchedCaps = mergeCaps(strippedRequiredCaps, firstMatchCaps);
|
||||
matchedCaps = mergeCaps(strippedRequiredCaps, firstMatchCaps) as ParsedCaps<C>['matchedCaps'];
|
||||
if (matchedCaps) {
|
||||
break;
|
||||
}
|
||||
@@ -317,18 +313,15 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps
|
||||
|
||||
/**
|
||||
* Calls parseCaps and just returns the matchedCaps variable
|
||||
* @template {Constraints} C
|
||||
* @template {W3CCapabilities<C>} W3CCaps
|
||||
* @param {W3CCaps} w3cCaps
|
||||
* @param {C} [constraints]
|
||||
* @param {boolean} [shouldValidateCaps]
|
||||
* @returns {Capabilities<C>}
|
||||
*/
|
||||
function processCapabilities(
|
||||
w3cCaps,
|
||||
constraints = /** @type {C} */ ({}),
|
||||
shouldValidateCaps = true
|
||||
) {
|
||||
export function processCapabilities<
|
||||
C extends Constraints,
|
||||
W3CCaps extends W3CCapabilities<C>
|
||||
>(
|
||||
w3cCaps: W3CCaps,
|
||||
constraints: C | undefined = {} as C,
|
||||
shouldValidateCaps: boolean | undefined = true
|
||||
): Capabilities<C> {
|
||||
const {matchedCaps, validationErrors} = parseCaps(w3cCaps, constraints, shouldValidateCaps);
|
||||
|
||||
// If we found an error throw an exception
|
||||
@@ -346,18 +339,14 @@ function processCapabilities(
|
||||
}
|
||||
}
|
||||
|
||||
return /** @type {Capabilities<C>} */ (matchedCaps ?? {});
|
||||
return (matchedCaps ?? {}) as Capabilities<C>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of a "bare" (single-level, non-W3C) capabilities object which has taken everything
|
||||
* within the 'appium:options' capability and promoted it to the top level.
|
||||
*
|
||||
* @template {Constraints} C
|
||||
* @param {NSCapabilities<C>} obj
|
||||
* @return {NSCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
*/
|
||||
function promoteAppiumOptionsForObject(obj) {
|
||||
export function promoteAppiumOptionsForObject<C extends Constraints>(obj: NSCapabilities<C>): NSCapabilities<C> {
|
||||
const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP];
|
||||
if (!appiumOptions) {
|
||||
return obj;
|
||||
@@ -379,8 +368,8 @@ function promoteAppiumOptionsForObject(obj) {
|
||||
/**
|
||||
* @param {string} capName
|
||||
*/
|
||||
const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
|
||||
const verifyIfAcceptable = (/** @type {string} */ capName) => {
|
||||
const shouldAddVendorPrefix = (capName: string) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
|
||||
const verifyIfAcceptable = (capName: string) => {
|
||||
if (!_.isString(capName)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected`
|
||||
@@ -394,8 +383,8 @@ function promoteAppiumOptionsForObject(obj) {
|
||||
return capName;
|
||||
};
|
||||
const preprocessedOptions = _(appiumOptions)
|
||||
.mapKeys((value, /** @type {string} */ key) => verifyIfAcceptable(key))
|
||||
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
|
||||
.mapKeys((value, key: string) => verifyIfAcceptable(key))
|
||||
.mapKeys((value, key: string) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
|
||||
.value();
|
||||
// warn if we are going to overwrite any keys on the base caps object
|
||||
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
|
||||
@@ -406,7 +395,7 @@ function promoteAppiumOptionsForObject(obj) {
|
||||
);
|
||||
}
|
||||
return _.cloneDeep({
|
||||
.../** @type {NSCapabilities<C>} */ (_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)),
|
||||
..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP) as NSCapabilities<C>,
|
||||
...preprocessedOptions,
|
||||
});
|
||||
}
|
||||
@@ -414,13 +403,9 @@ function promoteAppiumOptionsForObject(obj) {
|
||||
/**
|
||||
* Return a copy of a capabilities object which has taken everything within the 'options'
|
||||
* capability and promoted it to the top level.
|
||||
*
|
||||
* @template {Constraints} C
|
||||
* @param {W3CCapabilities<C>} originalCaps
|
||||
* @return {W3CCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
*/
|
||||
function promoteAppiumOptions(originalCaps) {
|
||||
const result = {};
|
||||
export function promoteAppiumOptions<C extends Constraints>(originalCaps: W3CCapabilities<C>): W3CCapabilities<C> {
|
||||
const result = {} as W3CCapabilities<C>;
|
||||
const {alwaysMatch, firstMatch} = originalCaps;
|
||||
if (_.isPlainObject(alwaysMatch)) {
|
||||
result.alwaysMatch = promoteAppiumOptionsForObject(alwaysMatch);
|
||||
@@ -434,59 +419,3 @@ function promoteAppiumOptions(originalCaps) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export {
|
||||
parseCaps,
|
||||
processCapabilities,
|
||||
validateCaps,
|
||||
mergeCaps,
|
||||
APPIUM_VENDOR_PREFIX,
|
||||
findNonPrefixedCaps,
|
||||
isStandardCap,
|
||||
stripAppiumPrefixes,
|
||||
promoteAppiumOptions,
|
||||
promoteAppiumOptionsForObject,
|
||||
PREFIXED_APPIUM_OPTS_CAP,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').Constraints} Constraints
|
||||
* @typedef {import('@appium/types').Constraint} Constraint
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ValidateCapsOpts
|
||||
* @property {boolean} [skipPresenceConstraint] - if true, skip the presence constraint
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').NSCapabilities<C>} NSCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import('type-fest').StringKeyOf<T>} StringKeyOf
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T,U
|
||||
* @typedef {import('type-fest').MergeExclusive<T, U>} MergeExclusive<T,U>
|
||||
*/
|
||||
@@ -74,8 +74,12 @@ validator.validators.isArray = function isArray(value) {
|
||||
return 'must be of type array';
|
||||
};
|
||||
validator.validators.deprecated = function deprecated(value, options, key) {
|
||||
if (options) {
|
||||
log.warn(`${key} is a deprecated capability`);
|
||||
// do not print caps that hasn't been provided.
|
||||
if (typeof value !== 'undefined' && options) {
|
||||
log.warn(
|
||||
`The '${key}' capability has been deprecated and must not be used anymore. ` +
|
||||
`Please check the driver documentation for possible alternatives.`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -9,26 +9,26 @@ import axios from 'axios';
|
||||
import B from 'bluebird';
|
||||
|
||||
// for compat with running tests transpiled and in-place
|
||||
const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
|
||||
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})`,
|
||||
});
|
||||
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,
|
||||
// @ts-ignore The fullPath property exists
|
||||
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);
|
||||
@@ -46,16 +46,14 @@ process.on('exit', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const appPaths = [...APPLICATIONS_CACHE.values()]
|
||||
// @ts-ignore The fullPath property exists
|
||||
.map(({fullPath}) => fullPath);
|
||||
const appPaths = [...APPLICATIONS_CACHE.values()].map(({fullPath}) => fullPath);
|
||||
logger.debug(
|
||||
`Performing cleanup of ${appPaths.length} cached ` +
|
||||
util.pluralize('application', appPaths.length)
|
||||
);
|
||||
for (const appPath of appPaths) {
|
||||
try {
|
||||
// Asynchronous calls are not supported in onExit handler
|
||||
// @ts-ignore it's defined
|
||||
fs.rimrafSync(appPath);
|
||||
} catch (e) {
|
||||
logger.warn(e.message);
|
||||
@@ -63,6 +61,25 @@ process.on('exit', () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @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}
|
||||
*/
|
||||
function verifyAppExtension(app, supportedAppExtensions) {
|
||||
if (supportedAppExtensions.map(_.toLower).includes(_.toLower(path.extname(app)))) {
|
||||
return app;
|
||||
@@ -74,14 +91,27 @@ function verifyAppExtension(app, 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;
|
||||
@@ -104,7 +134,7 @@ async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
|
||||
* @param {string} app
|
||||
* @param {string|string[]|import('@appium/types').ConfigureAppOptions} options
|
||||
*/
|
||||
async function configureApp(
|
||||
export async function configureApp(
|
||||
app,
|
||||
options = /** @type {import('@appium/types').ConfigureAppOptions} */ ({})
|
||||
) {
|
||||
@@ -114,8 +144,8 @@ async function configureApp(
|
||||
}
|
||||
|
||||
let supportedAppExtensions;
|
||||
const onPostProcess =
|
||||
!_.isString(options) && !_.isArray(options) ? options.onPostProcess : undefined;
|
||||
const onPostProcess = !_.isString(options) && !_.isArray(options) ? options.onPostProcess : undefined;
|
||||
const onDownload = !_.isString(options) && !_.isArray(options) ? options.onDownload : undefined;
|
||||
|
||||
if (_.isString(options)) {
|
||||
supportedAppExtensions = [options];
|
||||
@@ -129,6 +159,7 @@ async function configureApp(
|
||||
}
|
||||
|
||||
let newApp = app;
|
||||
const originalAppLink = app;
|
||||
let shouldUnzipApp = false;
|
||||
let packageHash = null;
|
||||
/** @type {import('axios').AxiosResponse['headers']|undefined} */
|
||||
@@ -143,8 +174,6 @@ async function configureApp(
|
||||
const {protocol, pathname} = url.parse(newApp);
|
||||
const isUrl = protocol === null ? false : ['http:', 'https:'].includes(protocol);
|
||||
|
||||
/** @type {import('@appium/types').CachedAppInfo|undefined} */
|
||||
// @ts-ignore We know the returned type
|
||||
const cachedAppInfo = APPLICATIONS_CACHE.get(app);
|
||||
if (cachedAppInfo) {
|
||||
logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
|
||||
@@ -162,20 +191,22 @@ async function configureApp(
|
||||
} else if (cachedAppInfo?.lastModified) {
|
||||
reqHeaders['if-modified-since'] = cachedAppInfo.lastModified.toUTCString();
|
||||
}
|
||||
logger.debug(`Request headers: ${JSON.stringify(reqHeaders)}`);
|
||||
|
||||
let {headers, stream, status} = await queryAppLink(newApp, reqHeaders);
|
||||
logger.debug(`Response status: ${status}`);
|
||||
try {
|
||||
if (!_.isEmpty(headers)) {
|
||||
logger.debug(`Etag: ${headers.etag}`);
|
||||
if (headers.etag) {
|
||||
logger.debug(`Etag: ${headers.etag}`);
|
||||
remoteAppProps.etag = headers.etag;
|
||||
}
|
||||
logger.debug(`Last-Modified: ${headers['last-modified']}`);
|
||||
if (headers['last-modified']) {
|
||||
logger.debug(`Last-Modified: ${headers['last-modified']}`);
|
||||
remoteAppProps.lastModified = new Date(headers['last-modified']);
|
||||
}
|
||||
logger.debug(`Cache-Control: ${headers['cache-control']}`);
|
||||
if (headers['cache-control']) {
|
||||
logger.debug(`Cache-Control: ${headers['cache-control']}`);
|
||||
remoteAppProps.immutable = /\bimmutable\b/i.test(headers['cache-control']);
|
||||
const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(headers['cache-control']);
|
||||
if (maxAgeMatch) {
|
||||
@@ -184,9 +215,9 @@ async function configureApp(
|
||||
}
|
||||
}
|
||||
if (cachedAppInfo && status === HTTP_STATUS_NOT_MODIFIED) {
|
||||
if (await isAppIntegrityOk(cachedAppInfo.fullPath, cachedAppInfo.integrity)) {
|
||||
if (await isAppIntegrityOk(/** @type {string} */ (cachedAppInfo.fullPath), cachedAppInfo.integrity)) {
|
||||
logger.info(`Reusing previously downloaded application at '${cachedAppInfo.fullPath}'`);
|
||||
return verifyAppExtension(cachedAppInfo.fullPath, supportedAppExtensions);
|
||||
return verifyAppExtension(/** @type {string} */ (cachedAppInfo.fullPath), supportedAppExtensions);
|
||||
}
|
||||
logger.info(
|
||||
`The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
|
||||
@@ -251,11 +282,16 @@ async function configureApp(
|
||||
}
|
||||
fileName = `${resultingName}${resultingExt}`;
|
||||
}
|
||||
const targetPath = await tempDir.path({
|
||||
prefix: fileName,
|
||||
suffix: '',
|
||||
});
|
||||
newApp = await fetchApp(stream, targetPath);
|
||||
newApp = onDownload
|
||||
? await onDownload({
|
||||
url: originalAppLink,
|
||||
headers: /** @type {import('@appium/types').HTTPHeaders} */ (_.clone(headers)),
|
||||
stream,
|
||||
})
|
||||
: await fetchApp(stream, await tempDir.path({
|
||||
prefix: fileName,
|
||||
suffix: '',
|
||||
}));
|
||||
} finally {
|
||||
if (!stream.closed) {
|
||||
stream.destroy();
|
||||
@@ -285,12 +321,12 @@ async function configureApp(
|
||||
const archivePath = newApp;
|
||||
if (packageHash === cachedAppInfo?.packageHash) {
|
||||
const fullPath = cachedAppInfo?.fullPath;
|
||||
if (await isAppIntegrityOk(fullPath, cachedAppInfo?.integrity)) {
|
||||
if (await isAppIntegrityOk(/** @type {string} */ (fullPath), cachedAppInfo?.integrity)) {
|
||||
if (archivePath !== app) {
|
||||
await fs.rimraf(archivePath);
|
||||
}
|
||||
logger.info(`Will reuse previously cached application at '${fullPath}'`);
|
||||
return verifyAppExtension(fullPath, supportedAppExtensions);
|
||||
return verifyAppExtension(/** @type {string} */ (fullPath), supportedAppExtensions);
|
||||
}
|
||||
logger.info(
|
||||
`The application at '${fullPath}' does not exist anymore ` +
|
||||
@@ -342,6 +378,7 @@ async function configureApp(
|
||||
/** @type {import('@appium/types').PostProcessOptions<import('axios').AxiosResponseHeaders>} */ ({
|
||||
cachedAppInfo: _.clone(cachedAppInfo),
|
||||
isUrl,
|
||||
originalAppLink,
|
||||
headers: _.clone(headers),
|
||||
appPath: newApp,
|
||||
})
|
||||
@@ -518,7 +555,11 @@ async function unzipApp(zipPath, dstRoot, supportedAppExtensions) {
|
||||
}
|
||||
}
|
||||
|
||||
function isPackageOrBundle(app) {
|
||||
/**
|
||||
* @param {string} app
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPackageOrBundle(app) {
|
||||
return /^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/.test(app);
|
||||
}
|
||||
|
||||
@@ -532,7 +573,7 @@ function isPackageOrBundle(app) {
|
||||
* @param {String} firstKey The first key to duplicate
|
||||
* @param {String} secondKey The second key to duplicate
|
||||
*/
|
||||
function duplicateKeys(input, firstKey, secondKey) {
|
||||
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));
|
||||
@@ -563,7 +604,7 @@ function duplicateKeys(input, firstKey, secondKey) {
|
||||
*
|
||||
* @param {string|Array<String>} cap A desired capability
|
||||
*/
|
||||
function parseCapsArray(cap) {
|
||||
export function parseCapsArray(cap) {
|
||||
if (_.isArray(cap)) {
|
||||
return cap;
|
||||
}
|
||||
@@ -590,7 +631,7 @@ function parseCapsArray(cap) {
|
||||
* @param {string?} sessionId session identifier (if exists)
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateDriverLogPrefix(obj, sessionId = null) {
|
||||
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;
|
||||
}
|
||||
@@ -603,14 +644,6 @@ export default {
|
||||
parseCapsArray,
|
||||
generateDriverLogPrefix,
|
||||
};
|
||||
export {
|
||||
configureApp,
|
||||
isPackageOrBundle,
|
||||
duplicateKeys,
|
||||
parseCapsArray,
|
||||
generateDriverLogPrefix,
|
||||
BASEDRIVER_VER,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef RemoteAppProps
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ES6Error from 'es6-error';
|
||||
import _ from 'lodash';
|
||||
import {util, logger} from '@appium/support';
|
||||
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
|
||||
@@ -8,8 +7,36 @@ const w3cLog = logger.getLogger('W3C');
|
||||
|
||||
const W3C_UNKNOWN_ERROR = 'unknown error';
|
||||
|
||||
class BaseError extends Error {
|
||||
constructor(message = '') {
|
||||
super(message);
|
||||
/** @type {[string, () => any][]} */
|
||||
const propsMap = [
|
||||
['message', () => message],
|
||||
['name', () => this.constructor.name],
|
||||
['stack', () => (new Error(message)).stack],
|
||||
];
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
const shouldSkipStack = Error.hasOwnProperty('captureStackTrace');
|
||||
for (const [propName, valueGetter] of propsMap) {
|
||||
if (propName === 'stack' && shouldSkipStack) {
|
||||
continue;
|
||||
}
|
||||
Object.defineProperty(this, propName, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: valueGetter(),
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (shouldSkipStack) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// base error class for all of our errors
|
||||
export class ProtocolError extends ES6Error {
|
||||
export class ProtocolError extends BaseError {
|
||||
constructor(msg, jsonwpCode, w3cStatus, error) {
|
||||
super(msg);
|
||||
this.jsonwpCode = jsonwpCode;
|
||||
@@ -896,7 +923,7 @@ function generateBadParametersMessage(requiredParams, actualParams) {
|
||||
}
|
||||
|
||||
// Equivalent to W3C InvalidArgumentError
|
||||
export class BadParametersError extends ES6Error {
|
||||
export class BadParametersError extends BaseError {
|
||||
static error() {
|
||||
return 'invalid argument';
|
||||
}
|
||||
@@ -916,7 +943,7 @@ export class BadParametersError extends ES6Error {
|
||||
* In case of ProxyRequestError should fetch the actual error by calling `getActualError()`
|
||||
* for proxy failure to generate the client response.
|
||||
*/
|
||||
export class ProxyRequestError extends ES6Error {
|
||||
export class ProxyRequestError extends BaseError {
|
||||
constructor(err, responseError, httpStatus) {
|
||||
let responseErrorObj = util.safeJsonParse(responseError);
|
||||
if (!_.isPlainObject(responseErrorObj)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.5.0",
|
||||
"version": "9.7.0",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,31 +44,30 @@
|
||||
"test:types": "tsd"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.2.0",
|
||||
"@appium/types": "^0.16.0",
|
||||
"@appium/support": "^4.2.6",
|
||||
"@appium/types": "^0.18.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.14.202",
|
||||
"@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.3",
|
||||
"axios": "1.7.2",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.2",
|
||||
"es6-error": "4.1.1",
|
||||
"express": "4.18.2",
|
||||
"express": "4.19.2",
|
||||
"http-status-codes": "2.3.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "10.1.0",
|
||||
"lru-cache": "10.2.2",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"path-to-regexp": "6.2.2",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "4.9.0",
|
||||
"type-fest": "4.18.3",
|
||||
"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);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.30](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.29...@appium/base-plugin@2.2.30) (2024-04-16)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.29](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.28...@appium/base-plugin@2.2.29) (2024-04-08)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.28](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.27...@appium/base-plugin@2.2.28) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.27](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.26...@appium/base-plugin@2.2.27) (2024-02-06)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.26](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.25...@appium/base-plugin@2.2.26) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.26",
|
||||
"version": "2.2.32",
|
||||
"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.5.0",
|
||||
"@appium/support": "^4.2.0"
|
||||
"@appium/base-driver": "^9.7.0",
|
||||
"@appium/support": "^4.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,69 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.35](https://github.com/appium/appium/compare/@appium/doctor@2.0.34...@appium/doctor@2.0.35) (2024-04-16)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.34](https://github.com/appium/appium/compare/@appium/doctor@2.0.33...@appium/doctor@2.0.34) (2024-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v12.0.4 ([df07665](https://github.com/appium/appium/commit/df076655098db68c986079037a794ce3477314bd))
|
||||
* **doctor:** update dependency appium-adb to v12.0.5 ([a6d49c1](https://github.com/appium/appium/commit/a6d49c1ac84c98ebe085abf32161d2f39c172d5a))
|
||||
* **doctor:** update dependency appium-adb to v12.0.6 ([26ab0ef](https://github.com/appium/appium/commit/26ab0ef7595580722f9a63a1c1e5c3bbef3b18f1))
|
||||
* **doctor:** update dependency appium-adb to v12.0.7 ([3936cb4](https://github.com/appium/appium/commit/3936cb43dceb45e1386c9418453a0dd734e32215))
|
||||
* **doctor:** update dependency appium-adb to v12.0.8 ([1f36882](https://github.com/appium/appium/commit/1f368828e6b3093a7317853e94437bb79955c399))
|
||||
|
||||
|
||||
|
||||
## [2.0.33](https://github.com/appium/appium/compare/@appium/doctor@2.0.32...@appium/doctor@2.0.33) (2024-02-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.32](https://github.com/appium/appium/compare/@appium/doctor@2.0.31...@appium/doctor@2.0.32) (2024-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **doctor:** update dependency appium-adb to v11.1.0 ([cbc1f50](https://github.com/appium/appium/commit/cbc1f500bc0721edcdf7f47af949ae113c038a46))
|
||||
* **doctor:** update dependency appium-adb to v12 ([#19648](https://github.com/appium/appium/issues/19648)) ([9617974](https://github.com/appium/appium/commit/961797448116b6be7b500a68b4202b969805825c))
|
||||
* **doctor:** update dependency appium-adb to v12.0.1 ([b85a6b9](https://github.com/appium/appium/commit/b85a6b939f688703522ebb6ddab19aa807c64dbe))
|
||||
* **doctor:** update dependency appium-adb to v12.0.2 ([5936d53](https://github.com/appium/appium/commit/5936d53d7856e6a2aa31236eaa0ea9afee737c95))
|
||||
* **doctor:** update dependency appium-adb to v12.0.3 ([#19722](https://github.com/appium/appium/issues/19722)) ([6cd2c54](https://github.com/appium/appium/commit/6cd2c547def390eb5d739d6245fe0bab1f99931a))
|
||||
|
||||
|
||||
|
||||
## [2.0.31](https://github.com/appium/appium/compare/@appium/doctor@2.0.30...@appium/doctor@2.0.31) (2024-01-03)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user