appium 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +129 -158
  2. package/build/lib/appium.d.ts +72 -0
  3. package/build/lib/appium.d.ts.map +1 -1
  4. package/build/lib/appium.js +326 -0
  5. package/build/lib/appium.js.map +1 -1
  6. package/build/lib/cli/args.d.ts.map +1 -1
  7. package/build/lib/cli/args.js +24 -2
  8. package/build/lib/cli/args.js.map +1 -1
  9. package/build/lib/cli/driver-command.d.ts +16 -0
  10. package/build/lib/cli/driver-command.d.ts.map +1 -1
  11. package/build/lib/cli/driver-command.js +16 -0
  12. package/build/lib/cli/driver-command.js.map +1 -1
  13. package/build/lib/cli/extension-command.d.ts +26 -2
  14. package/build/lib/cli/extension-command.d.ts.map +1 -1
  15. package/build/lib/cli/extension-command.js +127 -1
  16. package/build/lib/cli/extension-command.js.map +1 -1
  17. package/build/lib/cli/parser.d.ts.map +1 -1
  18. package/build/lib/cli/parser.js +11 -7
  19. package/build/lib/cli/parser.js.map +1 -1
  20. package/build/lib/cli/plugin-command.d.ts +16 -0
  21. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  22. package/build/lib/cli/plugin-command.js +16 -0
  23. package/build/lib/cli/plugin-command.js.map +1 -1
  24. package/build/lib/cli/utils.d.ts.map +1 -1
  25. package/build/lib/cli/utils.js +3 -4
  26. package/build/lib/cli/utils.js.map +1 -1
  27. package/build/lib/config.d.ts +1 -1
  28. package/build/lib/config.d.ts.map +1 -1
  29. package/build/lib/config.js +25 -29
  30. package/build/lib/config.js.map +1 -1
  31. package/build/lib/constants.d.ts +10 -0
  32. package/build/lib/constants.d.ts.map +1 -1
  33. package/build/lib/constants.js +11 -1
  34. package/build/lib/constants.js.map +1 -1
  35. package/build/lib/doctor/doctor.d.ts +55 -0
  36. package/build/lib/doctor/doctor.d.ts.map +1 -0
  37. package/build/lib/doctor/doctor.js +201 -0
  38. package/build/lib/doctor/doctor.js.map +1 -0
  39. package/build/lib/extension/index.d.ts.map +1 -1
  40. package/build/lib/extension/index.js +10 -4
  41. package/build/lib/extension/index.js.map +1 -1
  42. package/build/lib/grid-register.d.ts.map +1 -1
  43. package/build/lib/grid-register.js +1 -2
  44. package/build/lib/grid-register.js.map +1 -1
  45. package/build/lib/main.d.ts.map +1 -1
  46. package/build/lib/main.js +6 -0
  47. package/build/lib/main.js.map +1 -1
  48. package/build/lib/utils.d.ts +1 -0
  49. package/build/lib/utils.d.ts.map +1 -1
  50. package/build/lib/utils.js +2 -1
  51. package/build/lib/utils.js.map +1 -1
  52. package/build/types/cli.d.ts +1 -1
  53. package/build/types/cli.d.ts.map +1 -1
  54. package/lib/appium.js +387 -1
  55. package/lib/cli/args.js +27 -2
  56. package/lib/cli/driver-command.js +18 -0
  57. package/lib/cli/extension-command.js +152 -2
  58. package/lib/cli/parser.js +22 -8
  59. package/lib/cli/plugin-command.js +18 -0
  60. package/lib/cli/utils.js +5 -7
  61. package/lib/config.js +18 -24
  62. package/lib/constants.js +12 -0
  63. package/lib/doctor/doctor.js +209 -0
  64. package/lib/extension/index.js +10 -4
  65. package/lib/grid-register.js +1 -2
  66. package/lib/main.js +6 -0
  67. package/lib/utils.js +2 -1
  68. package/package.json +13 -13
  69. package/types/cli.ts +1 -1
@@ -0,0 +1,209 @@
1
+ import '@colors/colors';
2
+ import _ from 'lodash';
3
+ import { util, doctor, logger } from '@appium/support';
4
+
5
+ export class Doctor {
6
+ /**
7
+ * @param {DoctorCheck[]} [checks=[]]
8
+ */
9
+ constructor(checks = []) {
10
+ this.log = logger.getLogger('Doctor');
11
+ /** @type {DoctorCheck[]} */
12
+ this.checks = checks;
13
+ this.checks
14
+ .filter((c) => _.isNil(c.log))
15
+ .forEach((c) => { c.log = this.log; });
16
+ /** @type {DoctorIssue[]} */
17
+ this.foundIssues = [];
18
+ }
19
+
20
+ /**
21
+ * @returns {DoctorIssue[]}
22
+ */
23
+ get issuesRequiredToFix() {
24
+ return this.foundIssues.filter((f) => !f.check.isOptional());
25
+ }
26
+
27
+ /**
28
+ * @returns {DoctorIssue[]}
29
+ */
30
+ get issuesOptionalToFix() {
31
+ return this.foundIssues.filter((f) => f.check.isOptional());
32
+ }
33
+
34
+ /**
35
+ * The doctor shows the report
36
+ */
37
+ async diagnose() {
38
+ this.log.info(`### Starting doctor diagnostics ###`);
39
+ this.foundIssues = [];
40
+ for (const check of this.checks) {
41
+ const res = await check.diagnose();
42
+ const issue = this.toIssue(res, check);
43
+ if (issue) {
44
+ this.foundIssues.push(issue);
45
+ }
46
+ }
47
+ this.log.info(
48
+ `### Diagnostic completed, ${this.buildFixMessage()}. ###`
49
+ );
50
+ this.log.info('');
51
+ }
52
+
53
+ /**
54
+ * @returns {Promise<boolean>}
55
+ */
56
+ async reportManualIssues() {
57
+ const manualIssues = _.filter(this.issuesRequiredToFix, (f) => !f.check.hasAutofix());
58
+ const manualIssuesOptional = _.filter(this.issuesOptionalToFix, (f) => !f.check.hasAutofix());
59
+
60
+ const handleIssues = async (headerLogs, issues) => {
61
+ if (_.isEmpty(issues)) {
62
+ return;
63
+ }
64
+
65
+ for (const logMsg of headerLogs) {
66
+ this.log.info(logMsg);
67
+ }
68
+ /** @type {string[]} */
69
+ const fixMessages = [];
70
+ for (const issue of issues) {
71
+ const message = await issue.check.fix();
72
+ if (message) {
73
+ fixMessages.push(message);
74
+ }
75
+ }
76
+ for (const m of _.uniq(fixMessages)) {
77
+ this.log.warn(` \u279C ${m}`);
78
+ }
79
+ this.log.info('');
80
+ };
81
+
82
+ await handleIssues([
83
+ '### Manual Fixes Needed ###',
84
+ 'The configuration cannot be automatically fixed, please do the following first:',
85
+ ], manualIssues);
86
+ await handleIssues([
87
+ '### Optional Manual Fixes ###',
88
+ 'To fix these optional issues, please do the following manually:',
89
+ ], manualIssuesOptional);
90
+
91
+ if (manualIssues.length > 0) {
92
+ this.log.info('###');
93
+ this.log.info('');
94
+ this.log.info('Bye! Run doctor again when all manual fixes have been applied!');
95
+ this.log.info('');
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+
101
+ /**
102
+ * @param {DoctorIssue} f
103
+ */
104
+ async runAutoFix(f) {
105
+ this.log.info(`### Fixing: ${f.error} ###`);
106
+ try {
107
+ await f.check.fix();
108
+ } catch (err) {
109
+ if (err.constructor.name === doctor.FixSkippedError.name) {
110
+ this.log.info(`### Skipped fix ###`);
111
+ return;
112
+ } else {
113
+ this.log.warn(`${err}`.replace(/\n$/g, ''));
114
+ this.log.info(`### Fix did not succeed ###`);
115
+ return;
116
+ }
117
+ }
118
+ this.log.info('Checking if this was fixed:');
119
+ const res = await f.check.diagnose();
120
+ if (res.ok) {
121
+ f.fixed = true;
122
+ this.log.info(` ${'\u2714'.green} ${res.message}`);
123
+ this.log.info(`### Fix was successfully applied ###`);
124
+ } else {
125
+ this.log.info(` ${'\u2716'.red} ${res.message}`);
126
+ this.log.info(`### Fix was applied but issue remains ###`);
127
+ }
128
+ }
129
+
130
+ async runAutoFixes() {
131
+ const autoFixes = _.filter(this.foundIssues, (f) => f.check.hasAutofix());
132
+ for (const f of autoFixes) {
133
+ await this.runAutoFix(f);
134
+ this.log.info('');
135
+ }
136
+ if (_.find(autoFixes, (f) => !f.fixed)) {
137
+ // a few issues remain.
138
+ this.log.info('Bye! A few issues remain, fix manually and/or rerun doctor!');
139
+ } else {
140
+ // nothing left to fix.
141
+ this.log.info('Bye! All issues have been fixed!');
142
+ }
143
+ this.log.info('');
144
+ }
145
+
146
+ async run() {
147
+ await this.diagnose();
148
+ if (this.reportSuccess()) {
149
+ return;
150
+ }
151
+ if (await this.reportManualIssues()) {
152
+ return;
153
+ }
154
+ await this.runAutoFixes();
155
+ }
156
+
157
+ /**
158
+ * @param {DoctorCheckResult} result
159
+ * @param {DoctorCheck} check
160
+ * @returns {DoctorIssue?}
161
+ */
162
+ toIssue(result, check) {
163
+ if (result.ok) {
164
+ this.log.info(` ${'\u2714'.green} ${result.message}`);
165
+ return null;
166
+ }
167
+
168
+ const errorMessage = result.optional
169
+ ? ` ${'\u2716'.yellow} ${result.message}`
170
+ : ` ${'\u2716'.red} ${result.message}`;
171
+ this.log.warn(errorMessage);
172
+ return {
173
+ error: errorMessage,
174
+ check,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * @returns {string}
180
+ */
181
+ buildFixMessage() {
182
+ return `${util.pluralize('required fix', this.issuesRequiredToFix.length, true)} needed, ` +
183
+ `${util.pluralize('optional fix', this.issuesOptionalToFix.length, true)} possible`;
184
+ }
185
+
186
+ /**
187
+ * @returns {boolean}
188
+ */
189
+ reportSuccess() {
190
+ if (this.issuesRequiredToFix.length === 0 && this.issuesOptionalToFix.length === 0) {
191
+ this.log.info('Everything looks good, bye!');
192
+ this.log.info('');
193
+ return true;
194
+ }
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * @typedef DoctorIssue
201
+ * @property {DoctorCheck} check
202
+ * @property {string} error
203
+ * @property {boolean} [fixed]
204
+ */
205
+
206
+ /**
207
+ * @typedef {import('@appium/types').IDoctorCheck} DoctorCheck
208
+ * @typedef {import('@appium/types').DoctorCheckResult} DoctorCheckResult
209
+ */
@@ -99,9 +99,12 @@ export async function getActivePlugins(pluginConfig, maxParallelImports, usePlug
99
99
  `The reserved plugin name '${pluginName}' cannot be combined with other names.`
100
100
  );
101
101
  } else {
102
+ const suffix = _.isEmpty(pluginConfig.installedExtensions)
103
+ ? `You don't have any plugins installed yet.`
104
+ : `Only the following ${_.size(pluginConfig.installedExtensions) === 1 ? `plugin is` : `plugins are`} ` +
105
+ `available: ${_.keys(pluginConfig.installedExtensions)}`;
102
106
  throw new Error(
103
- `Could not load the plugin '${pluginName}' because it is not installed. ` +
104
- `Only the following plugins are available: ${_.keys(pluginConfig.installedExtensions)}`
107
+ `Could not load the plugin '${pluginName}' because it is not installed. ${suffix}`
105
108
  );
106
109
  }
107
110
  }
@@ -131,9 +134,12 @@ export async function getActiveDrivers(driverConfig, maxParallelImports, useDriv
131
134
  if (driverName in driverConfig.installedExtensions) {
132
135
  filteredDriverNames.push(driverName);
133
136
  } else {
137
+ const suffix = _.isEmpty(driverConfig.installedExtensions)
138
+ ? `You don't have any drivers installed yet.`
139
+ : `Only the following ${_.size(driverConfig.installedExtensions) === 1 ? `driver is` : `drivers are`} ` +
140
+ `available: ${_.keys(driverConfig.installedExtensions)}`;
134
141
  throw new Error(
135
- `Could not load the driver '${driverName}' because it is not installed. ` +
136
- `Only the following drivers are available: ${_.keys(driverConfig.installedExtensions)}`
142
+ `Could not load the driver '${driverName}' because it is not installed. ${suffix}`
137
143
  );
138
144
  }
139
145
  }
@@ -30,10 +30,9 @@ async function registerNode(data, addr, port, basePath) {
30
30
  try {
31
31
  data = JSON.parse(data);
32
32
  } catch (err) {
33
- logger.errorAndThrow(
33
+ throw logger.errorWithException(
34
34
  `Syntax error in node configuration file ${configFilePath}: ${err.message}`
35
35
  );
36
- return;
37
36
  }
38
37
  }
39
38
 
package/lib/main.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import {WebSocketServer} from 'ws';
3
4
  import {init as logsinkInit} from './logsink'; // this import needs to come first since it sets up global npmlog
4
5
  import logger from './logger'; // logger needs to remain second
5
6
  import {
@@ -373,8 +374,13 @@ async function main(args) {
373
374
  serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
374
375
  }
375
376
  let server;
377
+ const bidiServer = new WebSocketServer({noServer: true});
378
+ bidiServer.on('connection', appiumDriver.onBidiConnection.bind(appiumDriver));
379
+ bidiServer.on('error', appiumDriver.onBidiServerError.bind(appiumDriver));
376
380
  try {
377
381
  server = await baseServer(serverOpts);
382
+ server.addWebSocketHandler('/bidi', bidiServer);
383
+ server.addWebSocketHandler('/bidi/:sessionId', bidiServer);
378
384
  } catch (err) {
379
385
  logger.error(
380
386
  `Could not configure Appium server. It's possible that a driver or plugin tried ` +
package/lib/utils.js CHANGED
@@ -2,7 +2,7 @@ import _ from 'lodash';
2
2
  import logger from './logger';
3
3
  import {processCapabilities, PROTOCOLS, STANDARD_CAPS, errors} from '@appium/base-driver';
4
4
  import {inspect as dump} from 'util';
5
- import {node} from '@appium/support';
5
+ import {node, fs} from '@appium/support';
6
6
  import path from 'path';
7
7
  import {SERVER_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE} from './constants';
8
8
  import os from 'node:os';
@@ -11,6 +11,7 @@ const W3C_APPIUM_PREFIX = 'appium';
11
11
  const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
12
12
  export const V4_BROADCAST_IP = '0.0.0.0';
13
13
  export const V6_BROADCAST_IP = '::';
14
+ export const npmPackage = fs.readPackageJsonFrom(__dirname);
14
15
 
15
16
 
16
17
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Automation for Apps.",
5
5
  "keywords": [
6
6
  "automation",
@@ -60,12 +60,12 @@
60
60
  "test:unit": "mocha \"./test/unit/**/*.spec.js\""
61
61
  },
62
62
  "dependencies": {
63
- "@appium/base-driver": "^9.4.4",
64
- "@appium/base-plugin": "^2.2.25",
65
- "@appium/docutils": "^1.0.1",
66
- "@appium/schema": "^0.5.0",
67
- "@appium/support": "^4.1.11",
68
- "@appium/types": "^0.15.0",
63
+ "@appium/base-driver": "^9.5.0",
64
+ "@appium/base-plugin": "^2.2.26",
65
+ "@appium/docutils": "^1.0.2",
66
+ "@appium/schema": "~0.5.0",
67
+ "@appium/support": "^4.2.0",
68
+ "@appium/types": "^0.16.0",
69
69
  "@sidvind/better-ajv-errors": "2.1.3",
70
70
  "@types/argparse": "2.0.14",
71
71
  "@types/bluebird": "3.5.42",
@@ -76,13 +76,12 @@
76
76
  "ajv": "8.12.0",
77
77
  "ajv-formats": "2.1.1",
78
78
  "argparse": "2.0.1",
79
- "async-lock": "1.4.0",
79
+ "async-lock": "1.4.1",
80
80
  "asyncbox": "3.0.0",
81
- "axios": "1.6.2",
81
+ "axios": "1.6.3",
82
82
  "bluebird": "3.7.2",
83
83
  "cross-env": "7.0.3",
84
- "find-up": "5.0.0",
85
- "lilconfig": "2.1.0",
84
+ "lilconfig": "3.0.0",
86
85
  "lodash": "4.17.21",
87
86
  "npmlog": "7.0.1",
88
87
  "ora": "5.4.1",
@@ -91,9 +90,10 @@
91
90
  "semver": "7.5.4",
92
91
  "source-map-support": "0.5.21",
93
92
  "teen_process": "2.1.1",
94
- "type-fest": "4.8.3",
93
+ "type-fest": "4.9.0",
95
94
  "winston": "3.11.0",
96
95
  "wrap-ansi": "7.0.0",
96
+ "ws": "8.16.0",
97
97
  "yaml": "2.3.4"
98
98
  },
99
99
  "engines": {
@@ -103,5 +103,5 @@
103
103
  "publishConfig": {
104
104
  "access": "public"
105
105
  },
106
- "gitHead": "5c1d20517cfc8773cd5ffb0f8bb996668d71116b"
106
+ "gitHead": "b08210a6f80dcae55bd9e6c415327eba73a8aadd"
107
107
  }
package/types/cli.ts CHANGED
@@ -19,7 +19,7 @@ export type CliCommand = CliCommandServer | CliExtensionCommand;
19
19
  * Possible subcommands of {@linkcode CliCommandDriver} or
20
20
  * {@linkcode CliCommandPlugin}.
21
21
  */
22
- export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update';
22
+ export type CliExtensionSubcommand = 'install' | 'list' | 'run' | 'uninstall' | 'update'| 'doctor';
23
23
 
24
24
  export interface CliExtensionSubcommandListArgs {
25
25
  showInstalled?: boolean;