Compare commits

...
Author SHA1 Message Date
Jonathan Lipps a7ce83e169 chore: publish
- appium@2.15.0
 - @appium/base-driver@9.15.0
 - @appium/base-plugin@2.3.0
 - @appium/docutils@1.0.29
 - @appium/driver-test-support@0.7.4
 - @appium/eslint-config-appium-ts@1.0.2
 - @appium/fake-driver@5.7.0
 - @appium/fake-plugin@3.2.0
 - @appium/images-plugin@3.0.27
 - @appium/plugin-test-support@0.3.49
 - @appium/support@6.0.3
 - @appium/test-support@3.1.3
 - @appium/types@0.24.0
2025-01-08 13:57:17 -08:00
Jonathan Lipps 8df1c217a1 feat(appium,base-driver,base-plugin): allow plugins to define custom bidi commands and emit bidi events (#20876)
* feat: allow drivers to define their own bidi commands

* test(appium): refactor plugin tests server startup

* feat: allow plugins to define their own bidi commands and events

* fix(fake-driver,fake-plugin): ensure custom bidi commands events have vendor prefixes

* feat(appium): link plugin and driver objects in log lines when possible

* fix(appium,base-driver): ensure new methods for plugins aren't called without existence checks
2025-01-08 13:45:36 -08:00
Edgars Eglītis aad5709913 fix(eslint-config-appium-ts): add Prettier config, update typescript-eslint imports (#20887) 2025-01-07 20:25:50 +02:00
Mykola Mokhnach 17ecc21747 chore(eslint-config-appium-ts): Use .gitingore from the current workdir rather than same shared config dir (#20888) 2025-01-07 16:08:17 +01:00
Jonathan Lipps 3467f32c8e chore: publish
- appium@2.14.1
 - @appium/base-driver@9.14.1
 - @appium/base-plugin@2.2.52
 - @appium/docutils@1.0.28
 - @appium/driver-test-support@0.7.3
 - @appium/execute-driver-plugin@4.0.1
 - @appium/fake-plugin@3.1.56
 - @appium/images-plugin@3.0.26
 - @appium/plugin-test-support@0.3.48
 - @appium/support@6.0.2
 - @appium/test-support@3.1.2
2025-01-06 14:50:32 -08:00
renovate[bot] 6f27d36cf5 fix(support): update dependency uuid to v11.0.4 (#20883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 12:24:23 +00:00
renovate[bot] 488e5dea82 fix(support): update dependency teen_process to v2.2.3 (#20882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 11:33:01 +00:00
Mykola Mokhnach 13d8b03cc1 fix(appium): Do not update BiDi commands for drivers which don't support it (#20879) 2025-01-05 15:04:26 +01:00
renovate[bot] fe209ed660 fix(driver-test-support): update dependency @types/lodash to v4.17.14 (#20877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-04 09:48:30 +00:00
renovate[bot] 93e4288253 fix(execute-driver-plugin): update dependency webdriverio to v9.5.1 (#20875)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 15:45:30 +00:00
renovate[bot] fb839be6ea chore(deps): update dependency @types/node to v22.10.5 (#20874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 12:40:58 +00:00
49 changed files with 988 additions and 396 deletions
+1
View File
@@ -12,6 +12,7 @@ export default [
],
},
{
name: 'Ignores',
ignores: [
'**/build-fixtures/**',
'packages/appium/docs/**/assets/**',
+140 -111
View File
@@ -19,7 +19,7 @@
"@types/chai-as-promised": "8.0.1",
"@types/diff": "6.0.0",
"@types/mocha": "10.0.10",
"@types/node": "22.10.3",
"@types/node": "22.10.5",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/sinon-chai": "4.0.0",
@@ -27,8 +27,6 @@
"@types/teen_process": "2.0.4",
"@types/ws": "8.5.13",
"@types/yargs": "17.0.33",
"@typescript-eslint/eslint-plugin": "8.19.0",
"@typescript-eslint/parser": "8.19.0",
"asyncbox": "3.0.0",
"chai": "5.1.2",
"chai-as-promised": "8.0.1",
@@ -59,8 +57,9 @@
"ts-node": "10.9.2",
"tsd": "0.31.2",
"typescript": "5.7.2",
"typescript-eslint": "8.19.0",
"validate.js": "0.13.1",
"webdriverio": "9.5.0",
"webdriverio": "9.5.1",
"ws": "8.18.0",
"yaml-js": "0.3.1"
},
@@ -2927,9 +2926,9 @@
"integrity": "sha512-Q8oFIHJHr+htLrTXN2FuZfg+WXVHQRwU/hC2GpUu+Q8e3FUM9EDkS2pE3R2AO1ZGu56f479ybdMCNF1DAu8cAQ=="
},
"node_modules/@types/lodash": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
"version": "4.17.14",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
"license": "MIT"
},
"node_modules/@types/method-override": {
@@ -2976,9 +2975,9 @@
}
},
"node_modules/@types/node": {
"version": "22.10.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz",
"integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==",
"version": "22.10.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@@ -16137,9 +16136,9 @@
}
},
"node_modules/teen_process": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.2.2.tgz",
"integrity": "sha512-Qs9JCzsKcbuBtbZxY9WVoYs3GG+WFfxoIDGpkj8AJ/BpWnh+pdLK+x1+E1bDlQuASs+CTQmykYPGAkzxFjqcaA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.2.3.tgz",
"integrity": "sha512-8L540OalWH83qc6LHV5VMr0DjdP7KWUHQwTOImtWaG2ElW8BvLTh6M9871oGmmpOMb2BpOJQn2+09ZHWYsdTUA==",
"license": "Apache-2.0",
"dependencies": {
"bluebird": "^3.7.2",
@@ -16723,6 +16722,28 @@
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz",
"integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.19.0",
"@typescript-eslint/parser": "8.19.0",
"@typescript-eslint/utils": "8.19.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/uglify-js": {
"version": "3.17.4",
"dev": true,
@@ -17059,9 +17080,9 @@
}
},
"node_modules/webdriver": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.5.0.tgz",
"integrity": "sha512-w57Cb+N9VjSoYEFosytqDrICF3W5GqP0ldxiUMrhwaGSe2CfqE666CyRhy533Y3oYg1gZvVMc7jq6CMbfSBoTg==",
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.5.1.tgz",
"integrity": "sha512-uVQJtk1fLoi8ITmIo5jVpzUb7KMILPptB7uMKBumQJpVxjehz3xi0RGcnscSBuKhi59wkPJ3I5BOvomxQUG8Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17156,9 +17177,9 @@
}
},
"node_modules/webdriverio": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.5.0.tgz",
"integrity": "sha512-74QK4lDH5zkD/oEO/zHs2GqjrTj/d012+4zuZKAIqAnO46E+um50gw4AMmNp7Bc5xcGdU9CoB5EGDMpt2Fwf9g==",
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.5.1.tgz",
"integrity": "sha512-G/YwalPM/wxRdjY28LoZ0jJzldDUuZ2Do1F90THNw3GwSZae9yARs1kHxdpy1aDxBFwrb4BNTMzzL9hpJ0lASw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17188,7 +17209,7 @@
"rgb2hex": "0.2.5",
"serialize-error": "^11.0.3",
"urlpattern-polyfill": "^10.0.0",
"webdriver": "9.5.0"
"webdriver": "9.5.1"
},
"engines": {
"node": ">=18.20.0"
@@ -17203,9 +17224,9 @@
}
},
"node_modules/webdriverio/node_modules/@types/node": {
"version": "20.17.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
"integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
"version": "20.17.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz",
"integrity": "sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17945,17 +17966,17 @@
}
},
"packages/appium": {
"version": "2.14.0",
"version": "2.15.0",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@appium/base-driver": "^9.14.0",
"@appium/base-plugin": "^2.2.51",
"@appium/docutils": "^1.0.27",
"@appium/base-driver": "^9.15.0",
"@appium/base-plugin": "^2.3.0",
"@appium/docutils": "^1.0.29",
"@appium/logger": "^1.6.1",
"@appium/schema": "^0.7.1",
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@sidvind/better-ajv-errors": "3.0.1",
"@types/argparse": "2.0.17",
"@types/bluebird": "3.5.42",
@@ -17978,7 +17999,7 @@
"resolve-from": "5.0.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"winston": "3.17.0",
"wrap-ansi": "7.0.0",
@@ -18054,16 +18075,16 @@
},
"packages/base-driver": {
"name": "@appium/base-driver",
"version": "9.14.0",
"version": "9.15.0",
"license": "Apache-2.0",
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/async-lock": "1.4.2",
"@types/bluebird": "3.5.42",
"@types/express": "5.0.0",
"@types/lodash": "4.17.13",
"@types/lodash": "4.17.14",
"@types/method-override": "3.0.0",
"@types/serve-favicon": "2.5.7",
"async-lock": "1.4.1",
@@ -18120,11 +18141,11 @@
},
"packages/base-plugin": {
"name": "@appium/base-plugin",
"version": "2.2.51",
"version": "2.3.0",
"license": "Apache-2.0",
"dependencies": {
"@appium/base-driver": "^9.14.0",
"@appium/support": "^6.0.1"
"@appium/base-driver": "^9.15.0",
"@appium/support": "^6.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -18133,10 +18154,10 @@
},
"packages/docutils": {
"name": "@appium/docutils",
"version": "1.0.27",
"version": "1.0.29",
"license": "Apache-2.0",
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@appium/tsconfig": "^0.3.3",
"@sliphua/lilconfig-ts-loader": "3.2.2",
"@types/which": "3.0.4",
@@ -18150,7 +18171,7 @@
"read-pkg": "5.2.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"typescript": "5.7.2",
"yaml": "2.7.0",
@@ -18277,11 +18298,11 @@
},
"packages/driver-test-support": {
"name": "@appium/driver-test-support",
"version": "0.7.2",
"version": "0.7.4",
"license": "Apache-2.0",
"dependencies": {
"@appium/types": "^0.23.0",
"@types/lodash": "4.17.13",
"@appium/types": "^0.24.0",
"@types/lodash": "4.17.14",
"axios": "1.7.9",
"bluebird": "3.7.2",
"chai": "5.1.2",
@@ -18341,7 +18362,7 @@
},
"packages/eslint-config-appium-ts": {
"name": "@appium/eslint-config-appium-ts",
"version": "1.0.1",
"version": "1.0.2",
"license": "Apache-2.0",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -18350,26 +18371,25 @@
"peerDependencies": {
"@eslint/compat": "^1.1.0",
"@eslint/js": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.5.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-mocha": "^10.4.0",
"eslint-plugin-promise": "^7.0.0"
"eslint-plugin-promise": "^7.0.0",
"typescript-eslint": "^8.0.0"
}
},
"packages/execute-driver-plugin": {
"name": "@appium/execute-driver-plugin",
"version": "4.0.0",
"version": "4.0.1",
"license": "Apache-2.0",
"dependencies": {
"@types/bluebird": "3.5.42",
"bluebird": "3.7.2",
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"webdriverio": "9.5.0"
"webdriverio": "9.5.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -18927,7 +18947,7 @@
},
"packages/fake-driver": {
"name": "@appium/fake-driver",
"version": "5.6.0",
"version": "5.7.0",
"license": "Apache-2.0",
"dependencies": {
"@types/bluebird": "3.5.42",
@@ -18948,11 +18968,11 @@
},
"packages/fake-plugin": {
"name": "@appium/fake-plugin",
"version": "3.1.55",
"version": "3.2.0",
"license": "Apache-2.0",
"dependencies": {
"@appium/base-plugin": "^2.2.51",
"@appium/support": "^6.0.1",
"@appium/base-plugin": "^2.3.0",
"@appium/support": "^6.0.3",
"@types/bluebird": "3.5.42",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -18968,11 +18988,11 @@
},
"packages/images-plugin": {
"name": "@appium/images-plugin",
"version": "3.0.25",
"version": "3.0.27",
"license": "Apache-2.0",
"dependencies": {
"@appium/opencv": "^3.0.7",
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"lodash": "4.17.21",
"lru-cache": "10.4.3",
"sharp": "0.33.5",
@@ -19166,14 +19186,14 @@
},
"packages/plugin-test-support": {
"name": "@appium/plugin-test-support",
"version": "0.3.47",
"version": "0.3.49",
"license": "Apache-2.0",
"dependencies": {
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"get-port": "5.1.1",
"log-symbols": "4.1.0",
"source-map-support": "0.5.21",
"teen_process": "2.2.2"
"teen_process": "2.2.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -19229,12 +19249,12 @@
},
"packages/support": {
"name": "@appium/support",
"version": "6.0.1",
"version": "6.0.3",
"license": "Apache-2.0",
"dependencies": {
"@appium/logger": "^1.6.1",
"@appium/tsconfig": "^0.3.3",
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/archiver": "6.0.3",
"@types/base64-stream": "1.0.5",
@@ -19279,9 +19299,9 @@
"shell-quote": "1.8.2",
"source-map-support": "0.5.21",
"supports-color": "8.1.1",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"uuid": "11.0.3",
"uuid": "11.0.4",
"which": "4.0.0",
"yauzl": "3.2.0"
},
@@ -19512,10 +19532,10 @@
},
"packages/test-support": {
"name": "@appium/test-support",
"version": "3.1.1",
"version": "3.1.3",
"license": "Apache-2.0",
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@colors/colors": "1.6.0",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -19546,7 +19566,7 @@
},
"packages/types": {
"name": "@appium/types",
"version": "0.23.0",
"version": "0.24.0",
"license": "Apache-2.0",
"dependencies": {
"@appium/logger": "^1.6.1",
@@ -19614,13 +19634,13 @@
"@appium/base-driver": {
"version": "file:packages/base-driver",
"requires": {
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/async-lock": "1.4.2",
"@types/bluebird": "3.5.42",
"@types/express": "5.0.0",
"@types/lodash": "4.17.13",
"@types/lodash": "4.17.14",
"@types/method-override": "3.0.0",
"@types/serve-favicon": "2.5.7",
"async-lock": "1.4.1",
@@ -19665,14 +19685,14 @@
"@appium/base-plugin": {
"version": "file:packages/base-plugin",
"requires": {
"@appium/base-driver": "^9.14.0",
"@appium/support": "^6.0.1"
"@appium/base-driver": "^9.15.0",
"@appium/support": "^6.0.3"
}
},
"@appium/docutils": {
"version": "file:packages/docutils",
"requires": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@appium/tsconfig": "^0.3.3",
"@sliphua/lilconfig-ts-loader": "3.2.2",
"@types/which": "3.0.4",
@@ -19686,7 +19706,7 @@
"read-pkg": "5.2.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"typescript": "5.7.2",
"yaml": "2.7.0",
@@ -19755,8 +19775,8 @@
"@appium/driver-test-support": {
"version": "file:packages/driver-test-support",
"requires": {
"@appium/types": "^0.23.0",
"@types/lodash": "4.17.13",
"@appium/types": "^0.24.0",
"@types/lodash": "4.17.14",
"axios": "1.7.9",
"bluebird": "3.7.2",
"chai": "5.1.2",
@@ -19794,7 +19814,7 @@
"bluebird": "3.7.2",
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"webdriverio": "9.5.0"
"webdriverio": "9.5.1"
},
"dependencies": {
"@puppeteer/browsers": {
@@ -20177,8 +20197,8 @@
"@appium/fake-plugin": {
"version": "file:packages/fake-plugin",
"requires": {
"@appium/base-plugin": "^2.2.51",
"@appium/support": "^6.0.1",
"@appium/base-plugin": "^2.3.0",
"@appium/support": "^6.0.3",
"@types/bluebird": "3.5.42",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -20189,7 +20209,7 @@
"version": "file:packages/images-plugin",
"requires": {
"@appium/opencv": "^3.0.7",
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"lodash": "4.17.21",
"lru-cache": "10.4.3",
"sharp": "0.33.5",
@@ -20335,11 +20355,11 @@
"@appium/plugin-test-support": {
"version": "file:packages/plugin-test-support",
"requires": {
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"get-port": "5.1.1",
"log-symbols": "4.1.0",
"source-map-support": "0.5.21",
"teen_process": "2.2.2"
"teen_process": "2.2.3"
}
},
"@appium/relaxed-caps-plugin": {
@@ -20369,7 +20389,7 @@
"requires": {
"@appium/logger": "^1.6.1",
"@appium/tsconfig": "^0.3.3",
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/archiver": "6.0.3",
"@types/base64-stream": "1.0.5",
@@ -20415,9 +20435,9 @@
"shell-quote": "1.8.2",
"source-map-support": "0.5.21",
"supports-color": "8.1.1",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"uuid": "11.0.3",
"uuid": "11.0.4",
"which": "4.0.0",
"yauzl": "3.2.0"
},
@@ -20543,8 +20563,7 @@
"integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ=="
},
"uuid": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
"version": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg=="
},
"which": {
@@ -20569,7 +20588,7 @@
"@appium/test-support": {
"version": "file:packages/test-support",
"requires": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@colors/colors": "1.6.0",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -22389,9 +22408,9 @@
"integrity": "sha512-Q8oFIHJHr+htLrTXN2FuZfg+WXVHQRwU/hC2GpUu+Q8e3FUM9EDkS2pE3R2AO1ZGu56f479ybdMCNF1DAu8cAQ=="
},
"@types/lodash": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg=="
"version": "4.17.14",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A=="
},
"@types/method-override": {
"version": "3.0.0",
@@ -22430,9 +22449,9 @@
}
},
"@types/node": {
"version": "22.10.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz",
"integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==",
"version": "22.10.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
"requires": {
"undici-types": "~6.20.0"
},
@@ -23125,13 +23144,13 @@
"appium": {
"version": "file:packages/appium",
"requires": {
"@appium/base-driver": "^9.14.0",
"@appium/base-plugin": "^2.2.51",
"@appium/docutils": "^1.0.27",
"@appium/base-driver": "^9.15.0",
"@appium/base-plugin": "^2.3.0",
"@appium/docutils": "^1.0.29",
"@appium/logger": "^1.6.1",
"@appium/schema": "^0.7.1",
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@sidvind/better-ajv-errors": "3.0.1",
"@types/argparse": "2.0.17",
"@types/bluebird": "3.5.42",
@@ -23154,7 +23173,7 @@
"resolve-from": "5.0.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"winston": "3.17.0",
"wrap-ansi": "7.0.0",
@@ -31354,9 +31373,9 @@
}
},
"teen_process": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.2.2.tgz",
"integrity": "sha512-Qs9JCzsKcbuBtbZxY9WVoYs3GG+WFfxoIDGpkj8AJ/BpWnh+pdLK+x1+E1bDlQuASs+CTQmykYPGAkzxFjqcaA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.2.3.tgz",
"integrity": "sha512-8L540OalWH83qc6LHV5VMr0DjdP7KWUHQwTOImtWaG2ElW8BvLTh6M9871oGmmpOMb2BpOJQn2+09ZHWYsdTUA==",
"requires": {
"bluebird": "^3.7.2",
"lodash": "^4.17.21",
@@ -31728,6 +31747,16 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="
},
"typescript-eslint": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz",
"integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==",
"requires": {
"@typescript-eslint/eslint-plugin": "8.19.0",
"@typescript-eslint/parser": "8.19.0",
"@typescript-eslint/utils": "8.19.0"
}
},
"uglify-js": {
"version": "3.17.4",
"dev": true,
@@ -31963,9 +31992,9 @@
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
},
"webdriver": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.5.0.tgz",
"integrity": "sha512-w57Cb+N9VjSoYEFosytqDrICF3W5GqP0ldxiUMrhwaGSe2CfqE666CyRhy533Y3oYg1gZvVMc7jq6CMbfSBoTg==",
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.5.1.tgz",
"integrity": "sha512-uVQJtk1fLoi8ITmIo5jVpzUb7KMILPptB7uMKBumQJpVxjehz3xi0RGcnscSBuKhi59wkPJ3I5BOvomxQUG8Vw==",
"dev": true,
"requires": {
"@types/node": "^20.1.0",
@@ -32031,9 +32060,9 @@
}
},
"webdriverio": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.5.0.tgz",
"integrity": "sha512-74QK4lDH5zkD/oEO/zHs2GqjrTj/d012+4zuZKAIqAnO46E+um50gw4AMmNp7Bc5xcGdU9CoB5EGDMpt2Fwf9g==",
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.5.1.tgz",
"integrity": "sha512-G/YwalPM/wxRdjY28LoZ0jJzldDUuZ2Do1F90THNw3GwSZae9yARs1kHxdpy1aDxBFwrb4BNTMzzL9hpJ0lASw==",
"dev": true,
"requires": {
"@types/node": "^20.11.30",
@@ -32062,13 +32091,13 @@
"rgb2hex": "0.2.5",
"serialize-error": "^11.0.3",
"urlpattern-polyfill": "^10.0.0",
"webdriver": "9.5.0"
"webdriver": "9.5.1"
},
"dependencies": {
"@types/node": {
"version": "20.17.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
"integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
"version": "20.17.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz",
"integrity": "sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==",
"dev": true,
"requires": {
"undici-types": "~6.19.2"
+3 -4
View File
@@ -91,7 +91,7 @@
"@types/chai-as-promised": "8.0.1",
"@types/diff": "6.0.0",
"@types/mocha": "10.0.10",
"@types/node": "22.10.3",
"@types/node": "22.10.5",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/sinon-chai": "4.0.0",
@@ -99,8 +99,6 @@
"@types/teen_process": "2.0.4",
"@types/ws": "8.5.13",
"@types/yargs": "17.0.33",
"@typescript-eslint/eslint-plugin": "8.19.0",
"@typescript-eslint/parser": "8.19.0",
"asyncbox": "3.0.0",
"chai": "5.1.2",
"chai-as-promised": "8.0.1",
@@ -131,8 +129,9 @@
"ts-node": "10.9.2",
"tsd": "0.31.2",
"typescript": "5.7.2",
"typescript-eslint": "8.19.0",
"validate.js": "0.13.1",
"webdriverio": "9.5.0",
"webdriverio": "9.5.1",
"ws": "8.18.0",
"yaml-js": "0.3.1"
},
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.15.0](https://github.com/appium/appium/compare/appium@2.14.1...appium@2.15.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [2.14.1](https://github.com/appium/appium/compare/appium@2.14.0...appium@2.14.1) (2025-01-06)
### Bug Fixes
* **appium:** Do not update BiDi commands for drivers which don't support it ([#20879](https://github.com/appium/appium/issues/20879)) ([13d8b03](https://github.com/appium/appium/commit/13d8b03cc185c5f4e0631002614981ee6f86fab4))
* **execute-driver-plugin:** update dependency webdriverio to v9.5.1 ([#20875](https://github.com/appium/appium/issues/20875)) ([93e4288](https://github.com/appium/appium/commit/93e4288253c07a844e6e10f0dd040b1a40eb92ff))
* **support:** update dependency teen_process to v2.2.3 ([#20882](https://github.com/appium/appium/issues/20882)) ([488e5de](https://github.com/appium/appium/commit/488e5dea82d3c525acf483952570e32f319f3722))
## [2.14.0](https://github.com/appium/appium/compare/appium@2.13.1...appium@2.14.0) (2025-01-02)
@@ -320,10 +320,6 @@ your driver of the appropriate name, and it will be called when the BiDi command
the client. To see which specific names you should use for BiDi commands, have a look at
[bidi-commands.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/bidi-commands.js)
Currently, you also need to define a `doesSupportBidi` field on your driver instances, and ensure
it is set to `true`. Appium will not turn on its Websocket servers for your driver and set up any
handlers unless your driver says that it supports BiDi in this way.
You are not limited to BiDi commands that are defined in the official BiDi specification. If you
wish to define new commands, you may do so; you just need to tell Appium about them! See
[below](#extend-the-existing-protocol-with-new-commands) for more information.
@@ -562,11 +558,9 @@ remote endpoint you're proxying to. Appium will take care of all the proxying fo
### Proxy BiDi commands to another BiDi implementation
All of the above about proxying WebDriver commands is conceptually also valid for proxying BiDi
commands specifically. In order to enable BiDi proxying, you need to:
1. Set the `doesSupportBidi` field on your driver instances to `true`.
1. Implement `get bidiProxyUrl` on your driver. This should return a Websocket URL which is the
address of the upstream socket you want BiDi commands to be proxied to.
commands specifically. In order to enable BiDi proxying, you need to implement `get bidiProxyUrl`
on your driver. This should return a Websocket URL which is the address of the upstream socket you
want BiDi commands to be proxied to.
The intended pattern here is for you to start a session on the upstream implementation, check
whether it has an active BiDi socket in the returned capabilities (e.g., the `webSocketUrl`
@@ -624,7 +618,7 @@ an example of a `newBidiCommands` as implemented on an imaginary driver:
```js
static newBidiCommands = {
video: {
'appium:video': {
startFramerateCapture: {
command: 'startFrameCap',
params: {
@@ -639,9 +633,15 @@ static newBidiCommands = {
};
```
In this imaginary example, we have defined two new BiDi commands: `video.startFramerateCapture` and
`video.stopFramerateCapture`. The first command takes a required and an optional parameter, and the
second does not. When combined with generic BiDi support in your driver (see [the section on BiDi](#implement-webdriver-bidi-commands) above), and given an implementation of the appropriate methods on your driver (e.g. `startFrameCap` and `stopFrameCap` in this example), clients would be able to send these BiDi commands using whatever mechanism normally exists for doing so in the client library.
In this imaginary example, we have defined two new BiDi commands:
`appium:video.startFramerateCapture` and `appium:video.stopFramerateCapture`. Note first of all
that, because we are defining a custom BiDi command, we should include a 'vendor prefix' (in this
case, `appium:`, though you should pick something that represents your project). The first command
takes a required and an optional parameter, and the second does not. When combined with generic
BiDi support in your driver (see [the section on BiDi](#implement-webdriver-bidi-commands) above),
and given an implementation of the appropriate methods on your driver (e.g. `startFrameCap` and
`stopFrameCap` in this example), clients would be able to send these BiDi commands using whatever
mechanism normally exists for doing so in the client library.
An alternative to these other ways of doing things is to overload a command which all WebDriver clients
have access to already: Execute Script. Appium provides some a convenient tool for making this
@@ -752,7 +752,7 @@ in `this.currentCpuLoad`):
```js
this.eventEmitter.emit('bidiEvent', {
method: 'system.cpu',
method: 'appium:system.cpu',
params: {load: this.currentCpuLoad},
})
```
@@ -325,9 +325,12 @@ If you want to offer functionality that doesn't map to any of the existing comma
drivers, you can create new commands in one of two ways, just as is possible for drivers:
1. Extending the WebDriver protocol and creating client-side plugins to access the extensions
1. Extending the WebDriver BiDi protocol with new modules and methods, accessed from a client via the BiDi interface
1. Overloading the Execute Script command by defining [Execute
Methods](../guides/execute-methods.md)
#### Extending the HTTP Protocol
If you want to follow the first path, you can direct Appium to recognize new methods and add them
to its set of allowed HTTP routes and command names. You do this by assigning the `newMethodMap`
static variable in your driver class to an object of the same form as Appium's `routes.js` object.
@@ -368,7 +371,60 @@ clients won't have nice client-side functions designed to target these endpoints
to create and release client-side plugins for each language you want to support (directions or
examples can be found at the relevant client docs).
An alternative to this way of doing things is to overload a command which all WebDriver clients
#### Extending the BiDi Protocol
You can also make new commands accessible via the WebDriver BiDi (WebSocket-based) protocol. BiDi
commands come in two parts: a "module", which is basically a container or namespace, and
a "command", which is the name of your new command.
As with the first method, you teach Appium to recognize your new BiDi commands by adding a static
field to your driver class, called `newBidiCommands`. It has a format similar to `newMethodMap`.
Basically it encapsulates information about the BiDi module, BiDi command name, reference to your
driver instance method that will handle the command, and required and optional parameters. Here's
an example of a `newBidiCommands` as implemented on an imaginary driver:
```js
static newBidiCommands = {
'appium:video': {
startFramerateCapture: {
command: 'startFrameCap',
params: {
required: ['videoSource'],
optional: ['showOnScreen'],
}
},
stopFramerateCapture: {
command: 'stopFrameCap',
},
}
};
```
In this imaginary example, we have defined two new BiDi commands: `appium:video.startFramerateCapture` and
`appium:video.stopFramerateCapture`. The first command takes a required and an optional parameter, and the
second does not. When you have implemented the `startFrameCap` and `stopFrameCap` methods on your
plugin class, they will be called whenever the BiDi commands are triggered by a client. The
signatures for these methods would look as follows:
```js
async startFrameCap(next: () => Promise<any>, driver: DriverClass, videoSource: string, showOnScreen: boolean): Promise<any>;
async stopFrameCap(next: () => Promise<any>, driver: DriverClass): Promise<any>;
```
As with adding or overriding existing HTTP protocol commands, these methods are injected with
a `next` parameter and a `driver` parameter. The `driver` object represents the driver currently
owning the session, and calling `await next()` will execute/return the behavior that would execute
were the plugin not active (i.e., the driver's own handling of that method, or the chain of
commands executed with the same name by other active plugins).
Note that, currently, if a driver has BiDi proxying turned on, plugins will not be able to override
BiDi methods handled by the proxy. Also note that since these are custom BiDi commands, our module
name should include a vendor prefix (we chose `appium:`, but you can/should choose something that
makes sense for your extension).
#### Overloading Execute Script
An alternative to these ways of doing things is to overload a command which all WebDriver clients
have access to already: Execute Script. Make sure to read the section on [adding new
commands](./build-drivers.md#extend-the-existing-protocol-with-new-commands) in
the Building Drivers guide to understand the way this works in general. The way it works with
@@ -405,6 +461,11 @@ 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.
### Emit BiDi Events
Your plugin can emit custom BiDi events in [exactly the same way as Appium
drivers](./build-drivers.md#emit-bidi-events).
### Build Appium Doctor checks
Your users can run `appium plugin doctor <pluginName>` to run installation and health checks. Visit
+28 -23
View File
@@ -10,6 +10,7 @@ import {
GET_STATUS_COMMAND,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
generateDriverLogPrefix,
} from '@appium/base-driver';
import AsyncLock from 'async-lock';
import {
@@ -18,7 +19,7 @@ import {
makeNonW3cCapsError,
validateFeatures,
} from './utils';
import {util, node, logger} from '@appium/support';
import {util} from '@appium/support';
import {getDefaultsForExtension} from './schema';
import {DRIVER_TYPE, BIDI_BASE_PATH} from './constants';
import * as bidiHelpers from './bidi';
@@ -140,17 +141,6 @@ class AppiumDriver extends DriverCore {
})();
}
/**
* Retrieves logger instance for the current umbrella driver instance
*/
get log() {
if (!this._log) {
const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`;
this._log = logger.getLogger(instanceName);
}
return this._log;
}
/**
* Cancel commands queueing for the umbrella Appium driver
*/
@@ -343,8 +333,11 @@ class AppiumDriver extends DriverCore {
}
// We also want to assign any new Bidi Commands that the driver has specified, including all
// the standard bidi commands
driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
// the standard bidi commands. But add a method existence guard since some old driver class
// instances might not have this method
if (_.isFunction(driverInstance.updateBidiCommands)) {
driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
}
if (!_.isEmpty(this.args.denyInsecure)) {
this.log.info('Explicitly preventing use of insecure features:');
@@ -427,11 +420,10 @@ class AppiumDriver extends DriverCore {
await driverInstance.updateSettings(jwpSettings);
}
// if the user has asked for bidi support, and the driver says that it supports bidi,
// send our bidi url back to the user. The inner driver will need to have already saved any
// internal bidi urls it might want to proxy to, cause we are going to overwrite that
// information here!
if (dCaps.webSocketUrl && driverInstance.doesSupportBidi) {
// if the user has asked for bidi support, send our bidi url back to the user. The inner
// driver will need to have already saved any internal bidi urls it might want to proxy to,
// cause we are going to overwrite that information here!
if (dCaps.webSocketUrl) {
const {address, port, basePath} = this.args;
const scheme = `ws${this.server.isSecure() ? 's' : ''}`;
const host = bidiHelpers.determineBiDiHost(address);
@@ -626,12 +618,13 @@ class AppiumDriver extends DriverCore {
* Get the appropriate plugins for a session (or sessionless plugins)
*
* @param {?string} sessionId - the sessionId (or null) to use to find plugins
* @returns {Array} - array of plugin instances
* @returns {Array<import('@appium/types').Plugin>} - array of plugin instances
*/
pluginsForSession(sessionId = null) {
if (sessionId) {
if (!this.sessionPlugins[sessionId]) {
this.sessionPlugins[sessionId] = this.createPluginInstances();
const driverId = generateDriverLogPrefix(this.sessions[sessionId]);
this.sessionPlugins[sessionId] = this.createPluginInstances(driverId || null);
}
return this.sessionPlugins[sessionId];
}
@@ -662,14 +655,19 @@ class AppiumDriver extends DriverCore {
/**
* Creates instances of all of the enabled Plugin classes
* @param {string|null} driverId - ID to use for linking a driver to a plugin in logs
* @returns {Plugin[]}
*/
createPluginInstances() {
createPluginInstances(driverId = null) {
/** @type {Plugin[]} */
const pluginInstances = [];
for (const [PluginClass, name] of this.pluginClasses.entries()) {
const cliArgs = this.getCliArgsForPlugin(name);
const plugin = new PluginClass(name, cliArgs);
const plugin = new PluginClass(name, cliArgs, driverId);
if (_.isFunction(/** @type {Plugin & ExtensionCore} */(plugin).updateBidiCommands)) {
// some old plugin classes don't have `updateBidiCommands`
/** @type {Plugin & ExtensionCore} */(plugin).updateBidiCommands(PluginClass.newBidiCommands ?? {});
}
pluginInstances.push(plugin);
}
return pluginInstances;
@@ -830,6 +828,12 @@ class AppiumDriver extends DriverCore {
`to session ID ${sessionId}`,
);
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
for (const p of /** @type {(Plugin & ExtensionCore)[]} */(this.sessionPlugins[sessionId])) {
if (_.isFunction(p.updateLogPrefix)) {
// some old plugin classes don't have `updateLogPrefix` yet
p.updateLogPrefix(`${generateDriverLogPrefix(p)} <${generateDriverLogPrefix(this.sessions[sessionId])}>`);
}
}
this.sessionlessPlugins = [];
}
@@ -977,6 +981,7 @@ export {AppiumDriver};
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
* @typedef {import('@appium/types').PluginClass} PluginClass
* @typedef {import('@appium/types').Plugin} Plugin
* @typedef {import('@appium/base-driver').ExtensionCore} ExtensionCore
* @typedef {import('@appium/types').DriverClass<import('@appium/types').Driver>} DriverClass
*/
+84 -50
View File
@@ -2,6 +2,7 @@ import _ from 'lodash';
import B from 'bluebird';
import {
errors,
ExtensionCore,
} from '@appium/base-driver';
import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
import WebSocket from 'ws';
@@ -17,14 +18,18 @@ import type {
ErrorBiDiCommandResponse,
SuccessBiDiCommandResponse,
ExternalDriver,
StringRecord
StringRecord,
Plugin,
BiDiResultData
} from '@appium/types';
type ExtensionPlugin = Plugin & ExtensionCore
type AnyDriver = ExternalDriver | AppiumDriver;
type SendData = (data: string | Buffer) => Promise<void>;
type LogSocketError = (err: Error) => void;
interface InitBiDiSocketResult {
bidiHandlerDriver: AnyDriver;
bidiHandlerPlugins: ExtensionPlugin[];
proxyClient: WebSocket | null;
send: SendData;
sendToProxy: SendData | null;
@@ -64,7 +69,7 @@ export function determineBiDiHost(address: string): string {
export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): void {
try {
const initBiDiSocketFunc: OmitThisParameter<typeof initBidiSocket> = initBidiSocket.bind(this);
const {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
const {bidiHandlerDriver, bidiHandlerPlugins, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
ws,
req,
);
@@ -77,6 +82,7 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
send,
sendToProxy,
bidiHandlerDriver,
bidiHandlerPlugins,
logSocketErr,
);
if (proxyClient) {
@@ -86,7 +92,7 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
}
const initBidiEventListenersFunc: OmitThisParameter<typeof initBidiEventListeners> = initBidiEventListeners
.bind(this);
initBidiEventListenersFunc(ws, bidiHandlerDriver, send);
initBidiEventListenersFunc(ws, bidiHandlerDriver, bidiHandlerPlugins, send);
} catch (err) {
this.log.error(err);
try {
@@ -95,14 +101,25 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
}
}
function wrapCommandWithPlugins(driver: ExtensionCore, plugins: ExtensionCore[], method: string, params: StringRecord): () => Promise<BiDiResultData> {
const [moduleName, methodName] = method.split('.');
let next = async () => await driver.executeBidiCommand(method, params);
for (const plugin of plugins.filter((p) => p.doesBidiCommandExist(moduleName, methodName))) {
next = ((_next) => async () => await plugin.executeBidiCommand(method, params, _next, driver))(next);
}
return next;
}
/**
* @param data
* @param driver
* @param plugins
*/
export async function onBidiMessage(
this: AppiumDriver,
data: Buffer,
driver: AnyDriver
driver: AnyDriver,
plugins: ExtensionPlugin[]
): Promise<SuccessBiDiCommandResponse | ErrorBiDiCommandResponse> {
let resMessage: SuccessBiDiCommandResponse | ErrorBiDiCommandResponse;
let id: number = 0;
@@ -129,7 +146,8 @@ export async function onBidiMessage(
`Missing params for BiDi operation in '${dataTruncated}`,
);
}
const result = await driver.executeBidiCommand(method, params);
const executeWrappedCommand = wrapCommandWithPlugins(driver as ExtensionCore, plugins, method, params);
const result = await executeWrappedCommand();
resMessage = {
id,
type: 'success',
@@ -186,6 +204,7 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
let bidiHandlerDriver: AnyDriver;
let proxyClient: WebSocket | null = null;
const bidiHandlerPlugins: ExtensionPlugin[] = [];
if (sessionMatch) {
// If we found a session id, see if it matches an active session
const sessionId = sessionMatch[1];
@@ -217,15 +236,19 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
`url ${bidiProxyUrl}, but this was not a valid url`,
);
}
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}`);
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}. ` +
`Plugins will not handle bidi commands`);
proxyClient = new WebSocket(bidiProxyUrl);
this.bidiProxyClients[sessionId] = proxyClient;
} else {
bidiHandlerPlugins.push(...this.pluginsForSession(sessionId) as ExtensionPlugin[]);
}
} else {
this.log.info('Bidi websocket connection made to main server');
// no need to store the socket connection if it's to the main server since it will just
// stay open as long as the server itself is and will close when the server closes.
bidiHandlerDriver = this; // eslint-disable-line @typescript-eslint/no-this-alias
bidiHandlerPlugins.push(...this.pluginsForSession() as ExtensionPlugin[]);
}
const driverLog = bidiHandlerDriver.log;
@@ -254,7 +277,7 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
// bidi socket server (e.g. on a browser)
const sendToProxy: SendData | null = proxyClient ? sendFactory(proxyClient) : null;
return {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr};
return {bidiHandlerDriver, bidiHandlerPlugins, proxyClient, send, sendToProxy, logSocketErr};
}
/**
@@ -315,6 +338,7 @@ function initBidiProxyHandlers(
* upstream socket
* @param bidiHandlerDriver - the driver
* handling the bidi commands
* @param bidiHandlerPlugins - plugins that might also handle bidi commands
* @param logSocketErr - a special prefixed logger
*/
function initBidiSocketHandlers(
@@ -324,6 +348,7 @@ function initBidiSocketHandlers(
send: SendData,
sendToProxy: SendData | null,
bidiHandlerDriver: AnyDriver,
bidiHandlerPlugins: ExtensionPlugin[],
logSocketErr: LogSocketError,
): void {
const driverLog = bidiHandlerDriver.log;
@@ -340,9 +365,11 @@ function initBidiSocketHandlers(
ws.on('message', async (data: Buffer) => {
if (proxyClient && sendToProxy) {
// if we're meant to proxy to an upstream bidi socket, just do that
// TODO trying to determine how this proxying behaviour would interface with plugins is too
// complex for now, so just ignore plugins in this case
await sendToProxy(data.toString('utf8'));
} else {
const res = await this.onBidiMessage(data, bidiHandlerDriver);
const res = await this.onBidiMessage(data, bidiHandlerDriver, bidiHandlerPlugins);
await send(JSON.stringify(res));
}
});
@@ -381,56 +408,63 @@ function initBidiEventListeners(
this: AppiumDriver,
ws: WebSocket,
bidiHandlerDriver: AnyDriver,
bidiHandlerPlugins: ExtensionPlugin[],
send: SendData,
): void {
// If the driver emits a bidi event that should maybe get sent to the client, check to make
// sure the client is subscribed and then pass it on
const driverLog = bidiHandlerDriver.log;
const driverEe = bidiHandlerDriver.eventEmitter;
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
const eventListener = async ({context, method, params = {}}) => {
// if the driver didn't specify a context, use the empty context
if (!context) {
context = '';
}
if (!method || !params) {
driverLog.warn(
`Driver emitted a bidi event that was malformed. Require method and params keys ` +
`(with optional context). But instead received: ${_.truncate(JSON.stringify({
context,
method,
params,
}), {length: MAX_LOGGED_DATA_LENGTH})}`,
);
return;
}
if (ws.readyState !== WebSocket.OPEN) {
// if the websocket is not still 'open', then we can ignore sending these events
if (ws.readyState > WebSocket.OPEN) {
// if the websocket is closed or closing, we can remove this listener as well to avoid
// leaks
driverEe.removeListener(BIDI_EVENT_NAME, eventListener);
const eventListenerFactory = (extType: 'driver'|'plugin', ext: ExtensionCore) => {
const eventListener = async ({context, method, params = {}}) => {
// if the driver didn't specify a context, use the empty context
if (!context) {
context = '';
}
return;
}
const eventSubs = bidiHandlerDriver.bidiEventSubs[method];
if (_.isArray(eventSubs) && eventSubs.includes(context)) {
if (method in eventLogCounts) {
++eventLogCounts[method];
} else {
driverLog.info(
`<-- BIDI EVENT ${method} (context: '${context}', ` +
`params: ${_.truncate(JSON.stringify(params), {length: MAX_LOGGED_DATA_LENGTH})}). ` +
`All further similar events won't be logged.`,
if (!method || !params) {
ext.log?.warn( // some old plugins might not have the `log` property
`${_.capitalize(extType)} emitted a bidi event that was malformed. Require method and params keys ` +
`(with optional context). But instead received: ${_.truncate(JSON.stringify({
context,
method,
params,
}), {length: MAX_LOGGED_DATA_LENGTH})}`,
);
eventLogCounts[method] = 1;
return;
}
// now we can send the event onto the socket
const ev = {type: 'event', context, method, params};
await send(JSON.stringify(ev));
}
if (ws.readyState !== WebSocket.OPEN) {
// if the websocket is not still 'open', then we can ignore sending these events
if (ws.readyState > WebSocket.OPEN) {
// if the websocket is closed or closing, we can remove this listener as well to avoid
// leaks. Some old plugin classes might not have the `eventEmitter` property, so use an
// existence guard for now.
ext.eventEmitter?.removeListener(BIDI_EVENT_NAME, eventListener);
}
return;
}
const eventSubs = bidiHandlerDriver.bidiEventSubs[method];
if (_.isArray(eventSubs) && eventSubs.includes(context)) {
if (method in eventLogCounts) {
++eventLogCounts[method];
} else {
ext.log?.info( // some old plugins might not have the `log` property
`<-- BIDI EVENT ${method} (context: '${context}', ` +
`params: ${_.truncate(JSON.stringify(params), {length: MAX_LOGGED_DATA_LENGTH})}). ` +
`All further similar events won't be logged.`,
);
eventLogCounts[method] = 1;
}
// now we can send the event onto the socket
const ev = {type: 'event', context, method, params};
await send(JSON.stringify(ev));
}
};
return eventListener;
};
driverEe.on(BIDI_EVENT_NAME, eventListener);
bidiHandlerDriver.eventEmitter.on(BIDI_EVENT_NAME, eventListenerFactory('driver', bidiHandlerDriver as ExtensionCore));
for (const plugin of bidiHandlerPlugins) {
// some old plugins might not have the eventEmitter property
plugin.eventEmitter?.on(BIDI_EVENT_NAME, eventListenerFactory('plugin', plugin));
}
}
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "appium",
"version": "2.14.0",
"version": "2.15.0",
"description": "Automation for Apps.",
"keywords": [
"automation",
@@ -60,13 +60,13 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-driver": "^9.14.0",
"@appium/base-plugin": "^2.2.51",
"@appium/docutils": "^1.0.27",
"@appium/base-driver": "^9.15.0",
"@appium/base-plugin": "^2.3.0",
"@appium/docutils": "^1.0.29",
"@appium/logger": "^1.6.1",
"@appium/schema": "^0.7.1",
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@sidvind/better-ajv-errors": "3.0.1",
"@types/argparse": "2.0.17",
"@types/bluebird": "3.5.42",
@@ -89,7 +89,7 @@
"resolve-from": "5.0.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"winston": "3.17.0",
"wrap-ansi": "7.0.0",
@@ -1,5 +1,5 @@
{
"devDependencies": {
"webdriverio": "9.5.0"
"webdriverio": "9.5.1"
}
}
+6 -6
View File
@@ -546,7 +546,7 @@ describe('FakeDriver via HTTP', function () {
it('should be able to subscribe and unsubscribe to bidi events', async function () {
let collectedEvents = [];
// asyncrhonously start our listener
driver.on('clock.currentTime', ({time}) => {
driver.on('appium:clock.currentTime', ({time}) => {
collectedEvents.push(time);
});
@@ -556,22 +556,22 @@ describe('FakeDriver via HTTP', function () {
collectedEvents.should.be.empty;
// now subscribe and wait and assert that some events have been collected
await driver.sessionSubscribe({events: ['clock.currentTime']});
await driver.sessionSubscribe({events: ['appium:clock.currentTime']});
await B.delay(750);
collectedEvents.should.not.be.empty;
// finally unsubscribe and wait and assert that some events have been collected
await driver.sessionUnsubscribe({events: ['clock.currentTime']});
await driver.sessionUnsubscribe({events: ['appium:clock.currentTime']});
collectedEvents = [];
await B.delay(750);
collectedEvents.should.be.empty;
});
it('should allow custom bidi commands', async function () {
let {result} = await driver.send({method: 'fake.getFakeThing', params: {}});
let {result} = await driver.send({method: 'appium:fake.getFakeThing', params: {}});
should.not.exist(result);
await driver.send({method: 'fake.setFakeThing', params: {thing: 'this is from bidi'}});
({result} = await driver.send({method: 'fake.getFakeThing', params: {}}));
await driver.send({method: 'appium:fake.setFakeThing', params: {thing: 'this is from bidi'}});
({result} = await driver.send({method: 'appium:fake.getFakeThing', params: {}}));
result.should.eql('this is from bidi');
});
});
+114 -26
View File
@@ -28,11 +28,32 @@ const wdOpts = {
capabilities: W3C_PREFIXED_CAPS,
};
/** @type {Partial<import('appium/types').ParsedArgs>} */
let baseServerArgs;
/**
* @param {Partial<import('appium/types').ParsedArgs>} args
*/
function serverSetup(args) {
/** @type {AppiumServer|null} */
let server = null;
before(async function () { // eslint-disable-line mocha/no-top-level-hooks
// then start server if we need to
server = /** @type {AppiumServer} */ (
await appiumServer({...baseServerArgs, ...args})
);
});
after(async function () { // eslint-disable-line mocha/no-top-level-hooks
if (server) {
await server.close();
}
});
}
describe('FakePlugin w/ FakeDriver via HTTP', function () {
/** @type {string} */
let appiumHome;
/** @type {Partial<import('appium/types').ParsedArgs>} */
let baseServerArgs;
/** @type {string} */
let testServerBaseUrl;
/** @type {number} */
@@ -61,7 +82,7 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
subcommand: DRIVER_TYPE,
suppressOutput: true,
},
driverConfig
driverConfig,
);
if (!('fake' in driverList)) {
await runExtensionCommand(
@@ -71,7 +92,7 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
installType: INSTALL_TYPE_LOCAL,
subcommand: DRIVER_TYPE,
},
driverConfig
driverConfig,
);
}
@@ -83,7 +104,7 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
json: true,
suppressOutput: true,
},
pluginConfig
pluginConfig,
);
if (!('fake' in pluginList)) {
await runExtensionCommand(
@@ -93,7 +114,7 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
plugin: FAKE_PLUGIN_DIR,
installType: INSTALL_TYPE_LOCAL,
},
pluginConfig
pluginConfig,
);
}
@@ -133,27 +154,9 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
for (const registrationType of ['explicit', 'all']) {
describe(`with plugin registered via type ${registrationType}`, function () {
/** @type {AppiumServer} */
let server;
/** @type {import('type-fest').LiteralUnion<'all', string>[]} */
let usePlugins;
before(async function () {
// then start server if we need to
usePlugins = registrationType === 'explicit' ? ['fake'] : ['all'];
const args = {
appiumHome,
port,
address: TEST_HOST,
usePlugins,
useDrivers: ['fake'],
};
server = /** @type {AppiumServer} */ (await appiumServer(args));
});
after(async function () {
if (server) {
await server.close();
}
});
const usePlugins = registrationType === 'explicit' ? ['fake'] : ['all'];
serverSetup({usePlugins});
it('should update the server', async function () {
const res = {fake: 'fakeResponse'};
(await axios.post(`http://${TEST_HOST}:${port}/fake`)).data.should.eql(res);
@@ -369,6 +372,91 @@ describe('FakePlugin w/ FakeDriver via HTTP', function () {
sum.should.eql(5);
});
});
describe('BiDi support', function () {
describe('with a single plugin', function() {
/** @type {import('webdriverio').Browser} */
let driver;
// this 'after' block needs to come before 'serverSetup' so that the delete session happens
// before the server shutdown
after(async function () {
if (driver) {
await driver.deleteSession();
}
});
serverSetup({});
before(async function () {
const caps = {...wdOpts.capabilities, webSocketUrl: true, 'appium:runClock': true};
driver = await wdio({...wdOpts, capabilities: caps});
});
it('should handle custom bidi commands if registered', async function () {
let {result} = await driver.send({method: 'appium:fake.getPluginThing', params: {}});
should.not.exist(result);
await driver.send({method: 'appium:fake.setPluginThing', params: {thing: 'plugin bidi'}});
({result} = await driver.send({method: 'appium:fake.getPluginThing', params: {}}));
result.should.eql('plugin bidi');
});
it('should subscribe and unsubscribe to/from custom bidi events', async function () {
let retrievals = 0;
// asyncrhonously start our listener
driver.on('appium:fake.pluginThingRetrieved', () => {
retrievals++;
});
await driver.send({method: 'appium:fake.getPluginThing', params: {}});
retrievals.should.eql(0);
await driver.sessionSubscribe({events: ['appium:fake.pluginThingRetrieved']});
await driver.send({method: 'appium:fake.getPluginThing', params: {}});
await driver.send({method: 'appium:fake.getPluginThing', params: {}});
retrievals.should.eql(2);
await driver.sessionUnsubscribe({events: ['appium:fake.pluginThingRetrieved']});
await driver.send({method: 'appium:fake.getPluginThing', params: {}});
await driver.send({method: 'appium:fake.getPluginThing', params: {}});
retrievals.should.eql(2);
});
it('should subscribe and unsubscribe to/from custom bidi events and merge with driver', async function () {
let collectedEvents = [];
// asyncrhonously start our listener
driver.on('appium:clock.currentTime', ({time}) => {
collectedEvents.push(time);
});
// wait for some time to be sure clock events have happened, and assert we don't receive
// any yet
await B.delay(750);
collectedEvents.should.be.empty;
// now subscribe and wait and assert that some events have been collected
await driver.sessionSubscribe({events: ['appium:clock.currentTime']});
await B.delay(800);
collectedEvents.length.should.eql(5);
// finally unsubscribe and wait and assert that some events have been collected
await driver.sessionUnsubscribe({events: ['appium:clock.currentTime']});
collectedEvents = [];
await B.delay(800);
collectedEvents.should.be.empty;
});
it('should call underlying driver bidi method if next is called', async function () {
const {result} = await driver.send({method: 'appium:fake.doSomeMath', params: {num1: 2, num2: 3}});
result.should.eql(11);
});
it('should override and not call underlying driver bidi method if next is not called', async function () {
const {result} = await driver.send({method: 'appium:fake.doSomeMath2', params: {num1: 2, num2: 3}});
result.should.eql(6);
});
});
});
});
/**
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [9.15.0](https://github.com/appium/appium/compare/@appium/base-driver@9.14.1...@appium/base-driver@9.15.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [9.14.1](https://github.com/appium/appium/compare/@appium/base-driver@9.14.0...@appium/base-driver@9.14.1) (2025-01-06)
### Bug Fixes
* **driver-test-support:** update dependency @types/lodash to v4.17.14 ([#20877](https://github.com/appium/appium/issues/20877)) ([fe209ed](https://github.com/appium/appium/commit/fe209ed660e0f790c98cd51938d0c75712cfd8e4))
## [9.14.0](https://github.com/appium/appium/compare/@appium/base-driver@9.13.1...@appium/base-driver@9.14.0) (2025-01-02)
+6 -96
View File
@@ -1,4 +1,3 @@
import {logger} from '@appium/support';
import type {
AppiumLogger,
Constraints,
@@ -11,22 +10,18 @@ import type {
Protocol,
RouteMatcher,
StringRecord,
BidiModuleMap,
BiDiResultData,
} from '@appium/types';
import AsyncLock from 'async-lock';
import _ from 'lodash';
import {EventEmitter} from 'node:events';
import os from 'node:os';
import {
DEFAULT_BASE_PATH,
PROTOCOLS,
MAX_LOG_BODY_LENGTH,
} from '../constants';
import {errors} from '../protocol';
import {DeviceSettings} from './device-settings';
import * as helpers from './helpers';
import {BIDI_COMMANDS} from '../protocol/bidi-commands';
import {ExtensionCore} from './extension-core';
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
@@ -36,7 +31,7 @@ const ALL_DRIVERS_MATCH = '*';
const FEATURE_NAME_SEPARATOR = ':';
export class DriverCore<const C extends Constraints, Settings extends StringRecord = StringRecord>
implements Core<C, Settings>
extends ExtensionCore implements Core<C, Settings>
{
/**
* Make the basedriver version available so for any driver which inherits from this package, we
@@ -82,11 +77,8 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
protected _eventHistory: EventHistory;
// used to handle driver events
eventEmitter: NodeJS.EventEmitter;
/**
* @privateRemarks XXX: unsure why this is wrapped in a getter when nothing else is
* TODO: remove this._log and use this.log instead
*/
protected _log: AppiumLogger;
@@ -106,14 +98,10 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
protocol?: Protocol;
bidiEventSubs: Record<string, string[]>;
doesSupportBidi: boolean;
bidiCommands: BidiModuleMap = BIDI_COMMANDS as BidiModuleMap;
constructor(opts: InitialOpts = <InitialOpts>{}, shouldValidateCaps = true) {
this._log = logger.getLogger(helpers.generateDriverLogPrefix(this as Core<C>));
super();
this._log = this.log; // TODO: remove references to this._log and use this.log instead
// setup state
this.opts = opts as DriverOpts<C>;
@@ -141,16 +129,9 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
this.managedDrivers = [];
this.noCommandTimer = null;
this._eventHistory = {commands: []};
this.eventEmitter = new EventEmitter();
this.shutdownUnexpectedly = false;
this.commandsQueueGuard = new AsyncLock();
this.settings = new DeviceSettings();
this.bidiEventSubs = {};
this.doesSupportBidi = false;
}
get log() {
return this._log;
}
/**
@@ -433,75 +414,4 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
}
}
updateBidiCommands(cmds: BidiModuleMap): void {
const overlappingKeys = _.intersection(Object.keys(cmds), Object.keys(this.bidiCommands));
if (overlappingKeys.length) {
this.log.warn(`Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`);
}
this.bidiCommands = {
...this.bidiCommands,
...cmds,
};
}
async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord): Promise<BiDiResultData> {
const [moduleName, methodName] = bidiCmd.split('.');
// if we don't get a valid format for bidi command name, reject
if (!moduleName || !methodName) {
throw new errors.UnknownCommandError(
`Did not receive a valid BiDi module and method name ` +
`of the form moduleName.methodName. Instead received ` +
`'${moduleName}.${methodName}'`,
);
}
// if the command module or method isn't part of our spec, reject
if (!this.bidiCommands[moduleName] || !this.bidiCommands[moduleName][methodName]) {
throw new errors.UnknownCommandError();
}
const {command, params} = this.bidiCommands[moduleName][methodName];
// if the command method isn't part of our spec, also reject
if (!command) {
throw new errors.UnknownCommandError();
}
// If the driver doesn't have this command, it must not be implemented
if (!this[command]) {
throw new errors.NotYetImplementedError();
}
// TODO improve param parsing and error messages along the lines of what we have in the http
// handlers
const args: any[] = [];
if (params?.required?.length) {
for (const requiredParam of params.required) {
if (_.isUndefined(bidiParams[requiredParam])) {
throw new errors.InvalidArgumentError(
`The ${requiredParam} parameter was required but you omitted it`,
);
}
args.push(bidiParams[requiredParam]);
}
}
if (params?.optional?.length) {
for (const optionalParam of params.optional) {
args.push(bidiParams[optionalParam]);
}
}
const logParams = _.truncate(JSON.stringify(bidiParams), {length: MAX_LOG_BODY_LENGTH});
this.log.debug(
`Executing bidi command '${bidiCmd}' with params ${logParams} by passing to driver ` +
`method '${command}'`,
);
const response = await this[command](...args);
const finalResponse = _.isUndefined(response) ? {} : response;
this.log.debug(
`Responding to bidi command '${bidiCmd}' with ` +
`${_.truncate(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`
);
return finalResponse;
}
}
@@ -0,0 +1,128 @@
import {logger} from '@appium/support';
import {EventEmitter} from 'node:events';
import type {
AppiumLogger,
BidiModuleMap,
BiDiResultData,
StringRecord,
} from '@appium/types';
import {
MAX_LOG_BODY_LENGTH,
} from '../constants';
import {errors} from '../protocol';
import {BIDI_COMMANDS} from '../protocol/bidi-commands';
import _ from 'lodash';
import {generateDriverLogPrefix} from './helpers';
export class ExtensionCore {
bidiEventSubs: Record<string, string[]>;
bidiCommands: BidiModuleMap = BIDI_COMMANDS as BidiModuleMap;
_logPrefix?: string;
protected _log: AppiumLogger;
// used to handle driver events
readonly eventEmitter: NodeJS.EventEmitter;
constructor(logPrefix?: string) {
this._logPrefix = logPrefix;
this.bidiEventSubs = {};
this.eventEmitter = new EventEmitter();
}
get log(): AppiumLogger {
if (!this._log) {
this.updateLogPrefix(this._logPrefix ?? generateDriverLogPrefix(this));
}
return this._log;
}
updateLogPrefix(logPrefix: string) {
this._log = logger.getLogger(logPrefix);
}
updateBidiCommands(cmds: BidiModuleMap): void {
const overlappingKeys = _.intersection(Object.keys(cmds), Object.keys(this.bidiCommands));
if (overlappingKeys.length) {
this.log.warn(`Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`);
}
this.bidiCommands = {
...this.bidiCommands,
...cmds,
};
}
doesBidiCommandExist(moduleName: string, methodName: string): boolean {
try {
this.ensureBidiCommandExists(moduleName, methodName);
} catch {
return false;
}
return true;
}
ensureBidiCommandExists(moduleName: string, methodName: string): void {
// if we don't get a valid format for bidi command name, reject
if (!moduleName || !methodName) {
throw new errors.UnknownCommandError(
`Did not receive a valid BiDi module and method name ` +
`of the form moduleName.methodName. Instead received ` +
`'${moduleName}.${methodName}'`,
);
}
// if the command module or method isn't part of our spec, reject
if (!this.bidiCommands[moduleName] || !this.bidiCommands[moduleName][methodName]) {
throw new errors.UnknownCommandError();
}
const {command} = this.bidiCommands[moduleName][methodName];
// if the command method isn't part of our spec, also reject
if (!command) {
throw new errors.UnknownCommandError();
}
// If the driver doesn't have this command, it must not be implemented
if (!this[command]) {
throw new errors.NotYetImplementedError();
}
}
async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord, next?: () => Promise<any>, driver?: ExtensionCore): Promise<BiDiResultData> {
const handlerType = (next && driver) ? 'plugin' : 'driver';
const [moduleName, methodName] = bidiCmd.split('.');
this.ensureBidiCommandExists(moduleName, methodName);
const {command, params} = this.bidiCommands[moduleName][methodName];
// TODO improve param parsing and error messages along the lines of what we have in the http
// handlers
const args: any[] = [];
if (params?.required?.length) {
for (const requiredParam of params.required) {
if (_.isUndefined(bidiParams[requiredParam])) {
throw new errors.InvalidArgumentError(
`The ${requiredParam} parameter was required but you omitted it`,
);
}
args.push(bidiParams[requiredParam]);
}
}
if (params?.optional?.length) {
for (const optionalParam of params.optional) {
args.push(bidiParams[optionalParam]);
}
}
const logParams = _.truncate(JSON.stringify(bidiParams), {length: MAX_LOG_BODY_LENGTH});
this.log.debug(
`Executing bidi command '${bidiCmd}' with params ${logParams} by passing to ${handlerType} ` +
`method '${command}'`,
);
// call the handler with the signature appropriate to extension type (plugin or driver)
const response = (next && driver) ? await this[command](next, driver, ...args) : await this[command](...args);
const finalResponse = _.isUndefined(response) ? {} : response;
this.log.debug(
`Responding to bidi command '${bidiCmd}' with ` +
`${_.truncate(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`
);
return finalResponse;
}
}
@@ -401,7 +401,7 @@ export function parseCapsArray(cap) {
/**
* Generate a string that uniquely describes driver instance
*
* @param {import('@appium/types').Core} obj driver instance
* @param {object} obj driver instance
* @param {string?} [sessionId=null] session identifier (if exists).
* This parameter is deprecated and is not used.
* @returns {string}
+3
View File
@@ -10,6 +10,7 @@ try {
}
// BaseDriver exports
export {ExtensionCore} from './basedriver/extension-core';
import {BaseDriver} from './basedriver/driver';
export {DriverCore} from './basedriver/core';
export {DeviceSettings} from './basedriver/device-settings';
@@ -48,6 +49,8 @@ export {
// Web socket helpers
export {DEFAULT_WS_PATHNAME_PREFIX} from './express/websocket';
export {generateDriverLogPrefix} from './basedriver/helpers';
/**
* @typedef {import('./express/server').ServerOpts} ServerOpts
*/
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-driver",
"version": "9.14.0",
"version": "9.15.0",
"description": "Base driver class for Appium drivers",
"keywords": [
"automation",
@@ -44,13 +44,13 @@
"test:types": "tsd"
},
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/types": "^0.23.0",
"@appium/support": "^6.0.3",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/async-lock": "1.4.2",
"@types/bluebird": "3.5.42",
"@types/express": "5.0.0",
"@types/lodash": "4.17.13",
"@types/lodash": "4.17.14",
"@types/method-override": "3.0.0",
"@types/serve-favicon": "2.5.7",
"async-lock": "1.4.1",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.3.0](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.52...@appium/base-plugin@2.3.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [2.2.52](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.51...@appium/base-plugin@2.2.52) (2025-01-06)
**Note:** Version bump only for package @appium/base-plugin
## [2.2.51](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.50...@appium/base-plugin@2.2.51) (2025-01-02)
+16 -5
View File
@@ -1,10 +1,16 @@
import {logger} from '@appium/support';
import {validateExecuteMethodParams} from '@appium/base-driver';
import {ExtensionCore, generateDriverLogPrefix, validateExecuteMethodParams} from '@appium/base-driver';
/**
* @implements {Plugin}
*/
class BasePlugin {
class BasePlugin extends ExtensionCore {
/**
* @deprecated Use this.log instead of this.logger
* @type {import('@appium/types').AppiumLogger}
*/
logger;
/**
* Subclasses should use type `import('@appium/types').MethodMap<SubclassName>`.
*
@@ -43,11 +49,16 @@ class BasePlugin {
/**
* @param {string} name
* @param {Record<string,unknown>} [cliArgs]
* @param {string|null} driverId
*/
constructor(name, cliArgs = {}) {
constructor(name, cliArgs = {}, driverId = null) {
super();
if (driverId) {
this.updateLogPrefix(`${generateDriverLogPrefix(this)} <${driverId}>`);
}
this.name = name;
this.cliArgs = cliArgs;
this.logger = logger.getLogger(`Plugin [${name}]`);
this.logger = this.log;
}
/**
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/base-plugin",
"version": "2.2.51",
"version": "2.3.0",
"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.14.0",
"@appium/support": "^6.0.1"
"@appium/base-driver": "^9.15.0",
"@appium/support": "^6.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.29](https://github.com/appium/appium/compare/@appium/docutils@1.0.28...@appium/docutils@1.0.29) (2025-01-08)
**Note:** Version bump only for package @appium/docutils
## [1.0.28](https://github.com/appium/appium/compare/@appium/docutils@1.0.27...@appium/docutils@1.0.28) (2025-01-06)
### Bug Fixes
* **support:** update dependency teen_process to v2.2.3 ([#20882](https://github.com/appium/appium/issues/20882)) ([488e5de](https://github.com/appium/appium/commit/488e5dea82d3c525acf483952570e32f319f3722))
## [1.0.27](https://github.com/appium/appium/compare/@appium/docutils@1.0.26...@appium/docutils@1.0.27) (2025-01-02)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/docutils",
"version": "1.0.27",
"version": "1.0.29",
"description": "Documentation generation utilities for Appium and related projects",
"keywords": [
"automation",
@@ -48,7 +48,7 @@
"start": "node ./build/lib/cli/index.js"
},
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@appium/tsconfig": "^0.3.3",
"@sliphua/lilconfig-ts-loader": "3.2.2",
"@types/which": "3.0.4",
@@ -62,7 +62,7 @@
"read-pkg": "5.2.0",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"typescript": "5.7.2",
"yaml": "2.7.0",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.7.4](https://github.com/appium/appium/compare/@appium/driver-test-support@0.7.3...@appium/driver-test-support@0.7.4) (2025-01-08)
**Note:** Version bump only for package @appium/driver-test-support
## [0.7.3](https://github.com/appium/appium/compare/@appium/driver-test-support@0.7.2...@appium/driver-test-support@0.7.3) (2025-01-06)
### Bug Fixes
* **driver-test-support:** update dependency @types/lodash to v4.17.14 ([#20877](https://github.com/appium/appium/issues/20877)) ([fe209ed](https://github.com/appium/appium/commit/fe209ed660e0f790c98cd51938d0c75712cfd8e4))
## [0.7.2](https://github.com/appium/appium/compare/@appium/driver-test-support@0.7.1...@appium/driver-test-support@0.7.2) (2025-01-02)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/driver-test-support",
"version": "0.7.2",
"version": "0.7.4",
"description": "Test utilities for Appium drivers",
"keywords": [
"automation",
@@ -41,8 +41,8 @@
},
"types": "./build/lib/index.d.ts",
"dependencies": {
"@appium/types": "^0.23.0",
"@types/lodash": "4.17.13",
"@appium/types": "^0.24.0",
"@types/lodash": "4.17.14",
"axios": "1.7.9",
"bluebird": "3.7.2",
"chai": "5.1.2",
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.2](https://github.com/appium/appium/compare/@appium/eslint-config-appium-ts@1.0.1...@appium/eslint-config-appium-ts@1.0.2) (2025-01-08)
### Bug Fixes
* **eslint-config-appium-ts:** add Prettier config, update typescript-eslint imports ([#20887](https://github.com/appium/appium/issues/20887)) ([aad5709](https://github.com/appium/appium/commit/aad57099135a02907b0cd03617c52d7baf248a26))
## [1.0.1](https://github.com/appium/appium/compare/@appium/eslint-config-appium-ts@0.3.3...@appium/eslint-config-appium-ts@1.0.1) (2025-01-02)
**Note:** Version bump only for package @appium/eslint-config-appium-ts
+6 -8
View File
@@ -1,24 +1,23 @@
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import fs from 'node:fs';
import {includeIgnoreFile} from '@eslint/compat';
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier';
import globals from 'globals';
import pluginPromise from 'eslint-plugin-promise';
import importPlugin from 'eslint-plugin-import';
import mochaPlugin from 'eslint-plugin-mocha';
import {includeIgnoreFile} from '@eslint/compat';
import {configs as tsConfigs, parser as tsParser, plugin as tsPlugin} from 'typescript-eslint';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, '.gitignore');
const gitignorePath = path.resolve(process.cwd(), '.gitignore');
export default [
js.configs.recommended,
eslintConfigPrettier,
pluginPromise.configs['flat/recommended'],
importPlugin.flatConfigs.recommended,
...tsConfigs.recommended,
{
name: 'Script Files',
@@ -50,7 +49,6 @@ export default [
},
},
rules: {
...tsPlugin.configs.recommended.rules,
/**
* This rule is configured to warn if a `@ts-ignore` or `@ts-expect-error` directive is used
* without explanation.
@@ -1,7 +1,7 @@
{
"name": "@appium/eslint-config-appium-ts",
"type": "module",
"version": "1.0.1",
"version": "1.0.2",
"description": "Shared ESLint config for Appium projects (TypeScript version)",
"keywords": [
"eslint",
@@ -31,14 +31,13 @@
"peerDependencies": {
"@eslint/compat": "^1.1.0",
"@eslint/js": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.5.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-mocha": "^10.4.0",
"eslint-plugin-promise": "^7.0.0"
"eslint-plugin-promise": "^7.0.0",
"typescript-eslint": "^8.0.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.0.1](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@4.0.0...@appium/execute-driver-plugin@4.0.1) (2025-01-06)
### Bug Fixes
* **execute-driver-plugin:** update dependency webdriverio to v9.5.1 ([#20875](https://github.com/appium/appium/issues/20875)) ([93e4288](https://github.com/appium/appium/commit/93e4288253c07a844e6e10f0dd040b1a40eb92ff))
## [4.0.0](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.36...@appium/execute-driver-plugin@4.0.0) (2025-01-02)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/execute-driver-plugin",
"version": "4.0.0",
"version": "4.0.1",
"description": "Plugin for batching and executing driver commands with Appiums",
"keywords": [
"automation",
@@ -42,7 +42,7 @@
"bluebird": "3.7.2",
"lodash": "4.17.21",
"source-map-support": "0.5.21",
"webdriverio": "9.5.0"
"webdriverio": "9.5.1"
},
"peerDependencies": {
"appium": "^2.0.0-beta.35"
+9
View File
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.7.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.6.0...@appium/fake-driver@5.7.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [5.6.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.5.7...@appium/fake-driver@5.6.0) (2025-01-02)
+32 -3
View File
@@ -76,7 +76,6 @@ export class FakeDriver extends BaseDriver {
this.shook = false;
this.appModel = new FakeApp();
this.desiredCapConstraints = FAKE_DRIVER_CONSTRAINTS;
this.doesSupportBidi = true;
this._bidiProxyUrl = null;
}
@@ -183,7 +182,7 @@ export class FakeDriver extends BaseDriver {
while (this._clockRunning) {
await B.delay(500);
this.eventEmitter.emit('bidiEvent', {
method: 'clock.currentTime',
method: 'appium:clock.currentTime',
params: {time: Date.now()},
});
}
@@ -235,8 +234,26 @@ export class FakeDriver extends BaseDriver {
await B.delay(1);
}
/**
* @param {number} num1
* @param {number} num2
*/
async doSomeMath(num1, num2) {
await B.delay(1);
return num1 + num2;
}
/**
* @param {number} num1
* @param {number} num2
*/
async doSomeMath2(num1, num2) {
await B.delay(1);
return num1 + num2;
}
static newBidiCommands = /** @type {const} */({
fake: {
'appium:fake': {
getFakeThing: {
command: 'getFakeThing',
},
@@ -246,6 +263,18 @@ export class FakeDriver extends BaseDriver {
required: ['thing'],
},
},
doSomeMath: {
command: 'doSomeMath',
params: {
required: ['num1', 'num2'],
},
},
doSomeMath2: {
command: 'doSomeMath2',
params: {
required: ['num1', 'num2'],
},
},
}
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-driver",
"version": "5.6.0",
"version": "5.7.0",
"description": "Mock driver used internally by Appium for testing. Ignore",
"keywords": [
"automation",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.2.0](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.56...@appium/fake-plugin@3.2.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [3.1.56](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.55...@appium/fake-plugin@3.1.56) (2025-01-06)
**Note:** Version bump only for package @appium/fake-plugin
## [3.1.55](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.54...@appium/fake-plugin@3.1.55) (2025-01-02)
**Note:** Version bump only for package @appium/fake-plugin
+78
View File
@@ -11,6 +11,13 @@ class FakePlugin extends BasePlugin {
*/
fakeThing;
/**
* @type {any}
*/
pluginThing = null;
_clockRunning = true;
/**
* @param {string} name
* @param {Record<string,unknown>} cliArgs
@@ -18,6 +25,7 @@ class FakePlugin extends BasePlugin {
constructor(name, cliArgs) {
super(name, cliArgs);
this.fakeThing = 'PLUGIN_FAKE_THING';
this.startClock();
}
static newMethodMap = /** @type {const} */ ({
@@ -34,6 +42,32 @@ class FakePlugin extends BasePlugin {
},
});
static newBidiCommands = /** @type {const} */({
'appium:fake': {
getPluginThing: {
command: 'getPluginThing',
},
setPluginThing: {
command: 'setPluginThing',
params: {
required: ['thing'],
},
},
doSomeMath: {
command: 'doSomeMath',
params: {
required: ['num1', 'num2'],
},
},
doSomeMath2: {
command: 'doSomeMath2',
params: {
required: ['num1', 'num2'],
},
},
}
});
/** @type {string?} */
static _unexpectedData = null;
@@ -68,11 +102,49 @@ class FakePlugin extends BasePlugin {
});
}
async startClock() {
while (this._clockRunning) {
await B.delay(250);
this.eventEmitter.emit('bidiEvent', {
method: 'appium:clock.currentTime',
params: {time: Date.now()},
});
}
}
async doSomeMath(next, driver, num1, num2) {
const sum = await next(); // call driver's 'doSomeMath' which sums num1 and num2
return (num1 * num2) + sum;
}
async doSomeMath2(next, driver, num1, num2) {
// ignore next & driver in this method
await B.delay(1);
return num1 * num2;
}
async getFakeThing() {
await B.delay(1);
return this.fakeThing;
}
async getPluginThing() {
this.eventEmitter.emit('bidiEvent', {
method: 'appium:fake.pluginThingRetrieved',
params: {},
});
return this.pluginThing;
}
/**
* @param {() => Promise<any>} next
* @param {import('@appium/types').Driver} driver
* @param {any} thing
*/
async setPluginThing(next, driver, thing) {
this.pluginThing = thing;
}
async plugMeIn(next, driver, /** @type {string} */ socket) {
await B.delay(1);
return `Plugged in to ${socket}`;
@@ -114,12 +186,18 @@ class FakePlugin extends BasePlugin {
async onUnexpectedShutdown(driver, cause) {
this._clockRunning = false;
FakePlugin._unexpectedData = `Session ended because ${cause}`;
}
async execute(next, driver, script, args) {
return await this.executeMethod(next, driver, script, args);
}
async deleteSession(next) {
this._clockRunning = false;
return await next();
}
}
export {FakePlugin};
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/fake-plugin",
"version": "3.1.55",
"version": "3.2.0",
"description": "A fake Appium 2.0 plugin",
"keywords": [
"automation",
@@ -39,8 +39,8 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/base-plugin": "^2.2.51",
"@appium/support": "^6.0.1",
"@appium/base-plugin": "^2.3.0",
"@appium/support": "^6.0.3",
"@types/bluebird": "3.5.42",
"bluebird": "3.7.2",
"lodash": "4.17.21",
@@ -1,6 +1,12 @@
import {FakePlugin} from '../../lib/plugin';
import {FakePlugin as _FakePlugin} from '../../lib/plugin';
import B from 'bluebird';
// Let's not use the actual FakePlugin because it runs a timer and we don't want to worry about
// needing to clean up timers so that unit test processes can exit!
class FakePlugin extends _FakePlugin {
_clockRunning = false;
}
class FakeExpress {
constructor() {
this.routes = {};
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.0.27](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.26...@appium/images-plugin@3.0.27) (2025-01-08)
**Note:** Version bump only for package @appium/images-plugin
## [3.0.26](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.25...@appium/images-plugin@3.0.26) (2025-01-06)
**Note:** Version bump only for package @appium/images-plugin
## [3.0.25](https://github.com/appium/appium/compare/@appium/images-plugin@3.0.24...@appium/images-plugin@3.0.25) (2025-01-02)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/images-plugin",
"version": "3.0.25",
"version": "3.0.27",
"description": "Plugin for working with images and image elements in Appium",
"keywords": [
"automation",
@@ -40,7 +40,7 @@
},
"dependencies": {
"@appium/opencv": "^3.0.7",
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"lodash": "4.17.21",
"lru-cache": "10.4.3",
"sharp": "0.33.5",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.3.49](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.48...@appium/plugin-test-support@0.3.49) (2025-01-08)
**Note:** Version bump only for package @appium/plugin-test-support
## [0.3.48](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.47...@appium/plugin-test-support@0.3.48) (2025-01-06)
### Bug Fixes
* **support:** update dependency teen_process to v2.2.3 ([#20882](https://github.com/appium/appium/issues/20882)) ([488e5de](https://github.com/appium/appium/commit/488e5dea82d3c525acf483952570e32f319f3722))
## [0.3.47](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.46...@appium/plugin-test-support@0.3.47) (2025-01-02)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/plugin-test-support",
"version": "0.3.47",
"version": "0.3.49",
"description": "Testing utilities for Appium plugins",
"keywords": [
"automation",
@@ -41,11 +41,11 @@
"test:unit": "exit 0"
},
"dependencies": {
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"get-port": "5.1.1",
"log-symbols": "4.1.0",
"source-map-support": "0.5.21",
"teen_process": "2.2.2"
"teen_process": "2.2.3"
},
"peerDependencies": {
"appium": "^2.0.0-beta.43",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.0.3](https://github.com/appium/appium/compare/@appium/support@6.0.2...@appium/support@6.0.3) (2025-01-08)
**Note:** Version bump only for package @appium/support
## [6.0.2](https://github.com/appium/appium/compare/@appium/support@6.0.1...@appium/support@6.0.2) (2025-01-06)
### Bug Fixes
* **support:** update dependency teen_process to v2.2.3 ([#20882](https://github.com/appium/appium/issues/20882)) ([488e5de](https://github.com/appium/appium/commit/488e5dea82d3c525acf483952570e32f319f3722))
* **support:** update dependency uuid to v11.0.4 ([#20883](https://github.com/appium/appium/issues/20883)) ([6f27d36](https://github.com/appium/appium/commit/6f27d36cf526002af1e40965b793141f5ef065b6))
## [6.0.1](https://github.com/appium/appium/compare/@appium/support@6.0.0...@appium/support@6.0.1) (2025-01-02)
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/support",
"version": "6.0.1",
"version": "6.0.3",
"description": "Support libs used across appium packages",
"keywords": [
"automation",
@@ -43,7 +43,7 @@
"dependencies": {
"@appium/logger": "^1.6.1",
"@appium/tsconfig": "^0.3.3",
"@appium/types": "^0.23.0",
"@appium/types": "^0.24.0",
"@colors/colors": "1.6.0",
"@types/archiver": "6.0.3",
"@types/base64-stream": "1.0.5",
@@ -88,9 +88,9 @@
"shell-quote": "1.8.2",
"source-map-support": "0.5.21",
"supports-color": "8.1.1",
"teen_process": "2.2.2",
"teen_process": "2.2.3",
"type-fest": "4.31.0",
"uuid": "11.0.3",
"uuid": "11.0.4",
"which": "4.0.0",
"yauzl": "3.2.0"
},
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.1.3](https://github.com/appium/appium/compare/@appium/test-support@3.1.2...@appium/test-support@3.1.3) (2025-01-08)
**Note:** Version bump only for package @appium/test-support
## [3.1.2](https://github.com/appium/appium/compare/@appium/test-support@3.1.1...@appium/test-support@3.1.2) (2025-01-06)
**Note:** Version bump only for package @appium/test-support
## [3.1.1](https://github.com/appium/appium/compare/@appium/test-support@3.1.0...@appium/test-support@3.1.1) (2025-01-02)
**Note:** Version bump only for package @appium/test-support
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/test-support",
"version": "3.1.1",
"version": "3.1.3",
"description": "A collection of test utilities used across Appium packages",
"keywords": [
"automation",
@@ -45,7 +45,7 @@
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"dependencies": {
"@appium/support": "^6.0.1",
"@appium/support": "^6.0.3",
"@colors/colors": "1.6.0",
"bluebird": "3.7.2",
"lodash": "4.17.21",
+9
View File
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.24.0](https://github.com/appium/appium/compare/@appium/types@0.23.0...@appium/types@0.24.0) (2025-01-08)
### Features
* **appium,base-driver,base-plugin:** allow plugins to define custom bidi commands and emit bidi events ([#20876](https://github.com/appium/appium/issues/20876)) ([8df1c21](https://github.com/appium/appium/commit/8df1c217a15d30300c04b9f59cdbdffa70325828))
## [0.23.0](https://github.com/appium/appium/compare/@appium/types@0.22.3...@appium/types@0.23.0) (2025-01-02)
+5 -2
View File
@@ -497,7 +497,7 @@ export interface DriverHelpers {
isPackageOrBundle: (app: string) => boolean;
duplicateKeys: <T>(input: T, firstKey: string, secondKey: string) => T;
parseCapsArray: (cap: string | string[]) => string[];
generateDriverLogPrefix: <C extends Constraints>(obj: Core<C>, sessionId?: string) => string;
generateDriverLogPrefix: (obj: object, sessionId?: string) => string;
}
export type SettingsUpdateListener<T extends Record<string, unknown> = Record<string, unknown>> = (
@@ -610,7 +610,10 @@ export interface Core<C extends Constraints, Settings extends StringRecord = Str
isCommandsQueueEnabled: boolean;
eventHistory: EventHistory;
bidiEventSubs: Record<string, string[]>;
doesSupportBidi: boolean;
/**
* @deprecated Drivers no longer need to opt into BiDi support explicitly
*/
doesSupportBidi?: boolean;
updateBidiCommands(cmds: BidiModuleMap): void;
onUnexpectedShutdown(handler: () => any): void;
/**
+3 -2
View File
@@ -1,5 +1,5 @@
import {AsyncReturnType} from 'type-fest';
import {ExecuteMethodMap, MethodMap} from './command';
import {BidiModuleMap, ExecuteMethodMap, MethodMap} from './command';
import {DriverCommand, ExternalDriver} from './driver';
import {AppiumLogger} from './logger';
import {UpdateServerCallback} from './server';
@@ -26,6 +26,7 @@ export interface PluginStatic<P extends Plugin> {
*/
newMethodMap?: MethodMap<P>;
executeMethodMap?: ExecuteMethodMap<P>;
newBidiCommands?: BidiModuleMap;
}
/**
@@ -122,5 +123,5 @@ export type PluginCommand<
export type PluginClass<P extends Plugin = Plugin> = Class<
P,
PluginStatic<P>,
[pluginName: string, cliArgs: StringRecord<unknown>]
[pluginName: string, cliArgs: StringRecord<unknown>, driverId: string|null]
>;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@appium/types",
"version": "0.23.0",
"version": "0.24.0",
"description": "Various type declarations used across Appium",
"keywords": [
"automation",