appium 3.2.2 → 3.3.0

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 (120) hide show
  1. package/build/lib/cli/args.d.ts +16 -12
  2. package/build/lib/cli/args.d.ts.map +1 -1
  3. package/build/lib/cli/args.js +15 -35
  4. package/build/lib/cli/args.js.map +1 -1
  5. package/build/lib/cli/driver-command.d.ts +51 -93
  6. package/build/lib/cli/driver-command.d.ts.map +1 -1
  7. package/build/lib/cli/driver-command.js +11 -66
  8. package/build/lib/cli/driver-command.js.map +1 -1
  9. package/build/lib/cli/extension-command.d.ts +211 -415
  10. package/build/lib/cli/extension-command.d.ts.map +1 -1
  11. package/build/lib/cli/extension-command.js +384 -653
  12. package/build/lib/cli/extension-command.js.map +1 -1
  13. package/build/lib/cli/extension.d.ts +11 -16
  14. package/build/lib/cli/extension.d.ts.map +1 -1
  15. package/build/lib/cli/extension.js +10 -28
  16. package/build/lib/cli/extension.js.map +1 -1
  17. package/build/lib/cli/parser.d.ts +40 -69
  18. package/build/lib/cli/parser.d.ts.map +1 -1
  19. package/build/lib/cli/parser.js +24 -59
  20. package/build/lib/cli/parser.js.map +1 -1
  21. package/build/lib/cli/plugin-command.d.ts +50 -90
  22. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  23. package/build/lib/cli/plugin-command.js +11 -63
  24. package/build/lib/cli/plugin-command.js.map +1 -1
  25. package/build/lib/cli/setup-command.d.ts +21 -26
  26. package/build/lib/cli/setup-command.d.ts.map +1 -1
  27. package/build/lib/cli/setup-command.js +13 -55
  28. package/build/lib/cli/setup-command.js.map +1 -1
  29. package/build/lib/cli/utils.d.ts +27 -29
  30. package/build/lib/cli/utils.d.ts.map +1 -1
  31. package/build/lib/cli/utils.js +29 -31
  32. package/build/lib/cli/utils.js.map +1 -1
  33. package/build/lib/config-file.d.ts +24 -67
  34. package/build/lib/config-file.d.ts.map +1 -1
  35. package/build/lib/config-file.js +56 -115
  36. package/build/lib/config-file.js.map +1 -1
  37. package/build/lib/config.d.ts +42 -44
  38. package/build/lib/config.d.ts.map +1 -1
  39. package/build/lib/config.js +75 -107
  40. package/build/lib/config.js.map +1 -1
  41. package/build/lib/constants.d.ts +23 -23
  42. package/build/lib/constants.d.ts.map +1 -1
  43. package/build/lib/constants.js +10 -15
  44. package/build/lib/constants.js.map +1 -1
  45. package/build/lib/doctor/doctor.d.ts +40 -57
  46. package/build/lib/doctor/doctor.d.ts.map +1 -1
  47. package/build/lib/doctor/doctor.js +29 -60
  48. package/build/lib/doctor/doctor.js.map +1 -1
  49. package/build/lib/grid-register.d.ts +32 -7
  50. package/build/lib/grid-register.d.ts.map +1 -1
  51. package/build/lib/grid-register.js +84 -48
  52. package/build/lib/grid-register.js.map +1 -1
  53. package/build/lib/logsink.d.ts +13 -22
  54. package/build/lib/logsink.d.ts.map +1 -1
  55. package/build/lib/logsink.js +48 -103
  56. package/build/lib/logsink.js.map +1 -1
  57. package/build/lib/main.js +1 -1
  58. package/build/lib/main.js.map +1 -1
  59. package/build/lib/schema/arg-spec.d.ts +32 -107
  60. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  61. package/build/lib/schema/arg-spec.js +11 -107
  62. package/build/lib/schema/arg-spec.js.map +1 -1
  63. package/build/lib/schema/cli-args.d.ts +3 -15
  64. package/build/lib/schema/cli-args.d.ts.map +1 -1
  65. package/build/lib/schema/cli-args.js +15 -105
  66. package/build/lib/schema/cli-args.js.map +1 -1
  67. package/build/lib/schema/cli-transformers.d.ts +15 -12
  68. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  69. package/build/lib/schema/cli-transformers.js +15 -45
  70. package/build/lib/schema/cli-transformers.js.map +1 -1
  71. package/build/lib/schema/index.d.ts +2 -2
  72. package/build/lib/schema/index.d.ts.map +1 -1
  73. package/build/lib/schema/index.js.map +1 -1
  74. package/build/lib/schema/keywords.d.ts +12 -20
  75. package/build/lib/schema/keywords.d.ts.map +1 -1
  76. package/build/lib/schema/keywords.js +6 -51
  77. package/build/lib/schema/keywords.js.map +1 -1
  78. package/build/lib/schema/schema.d.ts +106 -231
  79. package/build/lib/schema/schema.d.ts.map +1 -1
  80. package/build/lib/schema/schema.js +75 -345
  81. package/build/lib/schema/schema.js.map +1 -1
  82. package/build/lib/utils.d.ts +59 -238
  83. package/build/lib/utils.d.ts.map +1 -1
  84. package/build/lib/utils.js +55 -207
  85. package/build/lib/utils.js.map +1 -1
  86. package/lib/cli/{args.js → args.ts} +40 -51
  87. package/lib/cli/driver-command.ts +122 -0
  88. package/lib/cli/{extension-command.js → extension-command.ts} +610 -689
  89. package/lib/cli/extension.ts +65 -0
  90. package/lib/cli/{parser.js → parser.ts} +48 -71
  91. package/lib/cli/plugin-command.ts +117 -0
  92. package/lib/cli/{setup-command.js → setup-command.ts} +57 -72
  93. package/lib/cli/utils.ts +97 -0
  94. package/lib/config-file.ts +212 -0
  95. package/lib/{config.js → config.ts} +129 -141
  96. package/lib/{constants.js → constants.ts} +30 -41
  97. package/lib/doctor/{doctor.js → doctor.ts} +81 -91
  98. package/lib/grid-register.ts +250 -0
  99. package/lib/{logsink.js → logsink.ts} +91 -137
  100. package/lib/main.js +1 -1
  101. package/lib/schema/arg-spec.ts +131 -0
  102. package/lib/schema/cli-args.ts +171 -0
  103. package/lib/schema/cli-transformers.ts +83 -0
  104. package/lib/schema/keywords.ts +96 -0
  105. package/lib/schema/schema.ts +449 -0
  106. package/lib/utils.ts +404 -0
  107. package/package.json +16 -16
  108. package/lib/cli/driver-command.js +0 -174
  109. package/lib/cli/extension.js +0 -74
  110. package/lib/cli/plugin-command.js +0 -164
  111. package/lib/cli/utils.js +0 -91
  112. package/lib/config-file.js +0 -228
  113. package/lib/grid-register.js +0 -146
  114. package/lib/schema/arg-spec.js +0 -229
  115. package/lib/schema/cli-args.js +0 -254
  116. package/lib/schema/cli-transformers.js +0 -113
  117. package/lib/schema/keywords.js +0 -136
  118. package/lib/schema/schema.js +0 -725
  119. package/lib/utils.js +0 -512
  120. /package/lib/schema/{index.js → index.ts} +0 -0
@@ -1,45 +1,79 @@
1
1
  import '@colors/colors';
2
2
  import _ from 'lodash';
3
- import { util, doctor, logger } from '@appium/support';
3
+ import {util, doctor, logger} from '@appium/support';
4
+ import type {AppiumLogger, DoctorCheckResult, IDoctorCheck} from '@appium/types';
4
5
 
5
- export const EXIT_CODE = /** @type {const} */ Object.freeze({
6
+ /**
7
+ * Process exit codes returned by {@link Doctor.run}.
8
+ */
9
+ export const EXIT_CODE = Object.freeze({
6
10
  SUCCESS: 0,
7
11
  HAS_MAJOR_ISSUES: 127,
8
- });
12
+ } as const);
13
+
14
+ /** Exit code values produced by {@link Doctor.run}. */
15
+ export type DoctorExitCode = (typeof EXIT_CODE)[keyof typeof EXIT_CODE];
16
+
17
+ /**
18
+ * A failed check reported during {@link Doctor} diagnostics.
19
+ */
20
+ export interface DoctorIssue {
21
+ /** The check that produced this issue. */
22
+ check: IDoctorCheck;
23
+ /** Colored message string as logged during diagnosis. */
24
+ error: string;
25
+ /** Set after a successful automatic fix attempt. */
26
+ fixed?: boolean;
27
+ }
9
28
 
10
29
  export class Doctor {
11
- /**
12
- * @param {DoctorCheck[]} [checks=[]]
13
- */
14
- constructor(checks = []) {
30
+ private readonly log: AppiumLogger;
31
+ private readonly checks: IDoctorCheck[];
32
+ private foundIssues: DoctorIssue[];
33
+
34
+ constructor(checks: IDoctorCheck[] = []) {
15
35
  this.log = logger.getLogger('Doctor');
16
- /** @type {DoctorCheck[]} */
17
36
  this.checks = checks;
18
37
  this.checks
19
38
  .filter((c) => _.isNil(c.log))
20
- .forEach((c) => { c.log = this.log; });
21
- /** @type {DoctorIssue[]} */
39
+ .forEach((c) => {
40
+ c.log = this.log;
41
+ });
22
42
  this.foundIssues = [];
23
43
  }
24
44
 
25
45
  /**
26
- * @returns {DoctorIssue[]}
46
+ * Runs diagnostics, reports issues, attempts automatic fixes where supported, and returns an exit code.
47
+ *
48
+ * @returns {@link EXIT_CODE.SUCCESS} when there are no issues or all issues were resolved;
49
+ * {@link EXIT_CODE.HAS_MAJOR_ISSUES} when manual intervention is still required or fixes failed.
27
50
  */
28
- get issuesRequiredToFix() {
51
+ async run(): Promise<DoctorExitCode> {
52
+ await this.diagnose();
53
+ if (this.reportSuccess()) {
54
+ return EXIT_CODE.SUCCESS;
55
+ }
56
+ if (await this.reportManualIssues()) {
57
+ return EXIT_CODE.HAS_MAJOR_ISSUES;
58
+ }
59
+ if (!(await this.runAutoFixes())) {
60
+ return EXIT_CODE.HAS_MAJOR_ISSUES;
61
+ }
62
+ return EXIT_CODE.SUCCESS;
63
+ }
64
+
65
+ private get issuesRequiredToFix(): DoctorIssue[] {
29
66
  return this.foundIssues.filter((f) => !f.check.isOptional());
30
67
  }
31
68
 
32
- /**
33
- * @returns {DoctorIssue[]}
34
- */
35
- get issuesOptionalToFix() {
69
+ private get issuesOptionalToFix(): DoctorIssue[] {
36
70
  return this.foundIssues.filter((f) => f.check.isOptional());
37
71
  }
38
72
 
39
73
  /**
40
74
  * The doctor shows the report
41
75
  */
42
- async diagnose() {
76
+ private async diagnose(): Promise<void> {
43
77
  this.log.info(`### Starting doctor diagnostics ###`);
44
78
  this.foundIssues = [];
45
79
  for (const check of this.checks) {
@@ -49,20 +83,15 @@ export class Doctor {
49
83
  this.foundIssues.push(issue);
50
84
  }
51
85
  }
52
- this.log.info(
53
- `### Diagnostic completed, ${this.buildFixMessage()}. ###`
54
- );
86
+ this.log.info(`### Diagnostic completed, ${this.buildFixMessage()}. ###`);
55
87
  this.log.info('');
56
88
  }
57
89
 
58
- /**
59
- * @returns {Promise<boolean>}
60
- */
61
- async reportManualIssues() {
90
+ private async reportManualIssues(): Promise<boolean> {
62
91
  const manualIssues = _.filter(this.issuesRequiredToFix, (f) => !f.check.hasAutofix());
63
92
  const manualIssuesOptional = _.filter(this.issuesOptionalToFix, (f) => !f.check.hasAutofix());
64
93
 
65
- const handleIssues = async (headerLogs, issues) => {
94
+ const handleIssues = async (headerLogs: string[], issues: DoctorIssue[]): Promise<void> => {
66
95
  if (_.isEmpty(issues)) {
67
96
  return;
68
97
  }
@@ -70,14 +99,13 @@ export class Doctor {
70
99
  for (const logMsg of headerLogs) {
71
100
  this.log.info(logMsg);
72
101
  }
73
- /** @type {string[]} */
74
- const fixMessages = [];
102
+ const fixMessages: string[] = [];
75
103
  for (const issue of issues) {
76
- let message;
104
+ let message: string | null;
77
105
  try {
78
106
  message = await issue.check.fix();
79
107
  } catch (e) {
80
- message = e.message;
108
+ message = (e as Error).message;
81
109
  }
82
110
  if (message) {
83
111
  fixMessages.push(message);
@@ -89,14 +117,20 @@ export class Doctor {
89
117
  this.log.info('');
90
118
  };
91
119
 
92
- await handleIssues([
93
- '### Manual Fixes Needed ###',
94
- 'The configuration cannot be automatically fixed, please do the following first:',
95
- ], manualIssues);
96
- await handleIssues([
97
- '### Optional Manual Fixes ###',
98
- 'To fix these optional issues, please do the following manually:',
99
- ], manualIssuesOptional);
120
+ await handleIssues(
121
+ [
122
+ '### Manual Fixes Needed ###',
123
+ 'The configuration cannot be automatically fixed, please do the following first:',
124
+ ],
125
+ manualIssues
126
+ );
127
+ await handleIssues(
128
+ [
129
+ '### Optional Manual Fixes ###',
130
+ 'To fix these optional issues, please do the following manually:',
131
+ ],
132
+ manualIssuesOptional
133
+ );
100
134
 
101
135
  if (manualIssues.length > 0) {
102
136
  this.log.info('###');
@@ -108,15 +142,12 @@ export class Doctor {
108
142
  return false;
109
143
  }
110
144
 
111
- /**
112
- * @param {DoctorIssue} f
113
- */
114
- async runAutoFix(f) {
145
+ private async runAutoFix(f: DoctorIssue): Promise<void> {
115
146
  this.log.info(`### Fixing: ${f.error} ###`);
116
147
  try {
117
148
  await f.check.fix();
118
149
  } catch (err) {
119
- if (err.constructor.name === doctor.FixSkippedError.name) {
150
+ if (err instanceof doctor.FixSkippedError) {
120
151
  this.log.info(`### Skipped fix ###`);
121
152
  return;
122
153
  } else {
@@ -137,10 +168,7 @@ export class Doctor {
137
168
  }
138
169
  }
139
170
 
140
- /**
141
- * @returns {Promise<boolean>}
142
- */
143
- async runAutoFixes() {
171
+ private async runAutoFixes(): Promise<boolean> {
144
172
  const autoFixes = _.filter(this.foundIssues, (f) => f.check.hasAutofix());
145
173
  for (const f of autoFixes) {
146
174
  await this.runAutoFix(f);
@@ -158,29 +186,7 @@ export class Doctor {
158
186
  return true;
159
187
  }
160
188
 
161
- /**
162
- * @returns {Promise<EXIT_CODE[keyof EXIT_CODE]>}
163
- */
164
- async run() {
165
- await this.diagnose();
166
- if (this.reportSuccess()) {
167
- return EXIT_CODE.SUCCESS;
168
- }
169
- if (await this.reportManualIssues()) {
170
- return EXIT_CODE.HAS_MAJOR_ISSUES;
171
- }
172
- if (!await this.runAutoFixes()) {
173
- return EXIT_CODE.HAS_MAJOR_ISSUES;
174
- }
175
- return EXIT_CODE.SUCCESS;
176
- }
177
-
178
- /**
179
- * @param {DoctorCheckResult} result
180
- * @param {DoctorCheck} check
181
- * @returns {DoctorIssue?}
182
- */
183
- toIssue(result, check) {
189
+ private toIssue(result: DoctorCheckResult, check: IDoctorCheck): DoctorIssue | null {
184
190
  if (result.ok) {
185
191
  this.log.info(` ${'\u2714'.green} ${result.message}`);
186
192
  return null;
@@ -196,18 +202,14 @@ export class Doctor {
196
202
  };
197
203
  }
198
204
 
199
- /**
200
- * @returns {string}
201
- */
202
- buildFixMessage() {
203
- return `${util.pluralize('required fix', this.issuesRequiredToFix.length, true)} needed, ` +
204
- `${util.pluralize('optional fix', this.issuesOptionalToFix.length, true)} possible`;
205
+ private buildFixMessage(): string {
206
+ return (
207
+ `${util.pluralize('required fix', this.issuesRequiredToFix.length, true)} needed, ` +
208
+ `${util.pluralize('optional fix', this.issuesOptionalToFix.length, true)} possible`
209
+ );
205
210
  }
206
211
 
207
- /**
208
- * @returns {boolean}
209
- */
210
- reportSuccess() {
212
+ private reportSuccess(): boolean {
211
213
  if (this.issuesRequiredToFix.length === 0 && this.issuesOptionalToFix.length === 0) {
212
214
  this.log.info('Everything looks good, bye!');
213
215
  this.log.info('');
@@ -216,15 +218,3 @@ export class Doctor {
216
218
  return false;
217
219
  }
218
220
  }
219
-
220
- /**
221
- * @typedef DoctorIssue
222
- * @property {DoctorCheck} check
223
- * @property {string} error
224
- * @property {boolean} [fixed]
225
- */
226
-
227
- /**
228
- * @typedef {import('@appium/types').IDoctorCheck} DoctorCheck
229
- * @typedef {import('@appium/types').DoctorCheckResult} DoctorCheckResult
230
- */
@@ -0,0 +1,250 @@
1
+ import axios from 'axios';
2
+ import {fs} from '@appium/support';
3
+ import type {StringRecord} from '@appium/types';
4
+ import _ from 'lodash';
5
+ import logger from './logger';
6
+
7
+ /**
8
+ * Selenium **Grid 3** (legacy hub) node integration.
9
+ *
10
+ * Registers this Appium process as a node with a Selenium Grid 3 hub using the classic
11
+ * `/grid/register` endpoint and `/grid/api/proxy` for re-registration checks.
12
+ * This does **not** apply to Selenium Grid 4 (which uses a different topology and APIs).
13
+ *
14
+ * @see https://www.selenium.dev/documentation/legacy/grid_3/
15
+ */
16
+
17
+ /** REST paths exposed by a Selenium Grid 3 hub. */
18
+ const GRID_V3_REGISTER_PATH = '/grid/register';
19
+ const GRID_V3_PROXY_API_PATH = '/grid/api/proxy';
20
+
21
+ /**
22
+ * Registers this server as a node with a **Selenium Grid 3** hub.
23
+ *
24
+ * When `data` is a path to a JSON config file, `addr`, `port`, and `basePath` are required (they define
25
+ * how the hub reaches this Appium server). When `data` is an inline config object, those parameters are
26
+ * optional and only used to fill missing node URL fields in the config.
27
+ *
28
+ * @deprecated Selenium Grid 3 registration is legacy and slated for removal.
29
+ * Use Selenium Grid 4 integration (node relay) instead.
30
+ *
31
+ * @param data - Path to a JSON file or an inline parsed config object
32
+ * @param addr - Bind to this address
33
+ * @param port - Bind to this port
34
+ * @param basePath - Base path for the Appium server (used in the node URL sent to the hub; may be `''`)
35
+ */
36
+ export default async function registerNode(
37
+ data: string,
38
+ addr: string,
39
+ port: number,
40
+ basePath: string
41
+ ): Promise<void>;
42
+ export default async function registerNode(
43
+ data: Grid3NodeConfig,
44
+ addr?: string,
45
+ port?: number,
46
+ basePath?: string
47
+ ): Promise<void>;
48
+ export default async function registerNode(
49
+ data: string | Grid3NodeConfig,
50
+ addr?: string,
51
+ port?: number,
52
+ basePath?: string
53
+ ): Promise<void> {
54
+ if (typeof data === 'string') {
55
+ if (addr === undefined || port === undefined || basePath === undefined) {
56
+ throw logger.errorWithException(
57
+ 'When the first argument is a Selenium Grid 3 node config file path, address, port, and basePath ' +
58
+ 'are required (e.g. match your Appium `--address`, `--port`, and base path).'
59
+ );
60
+ }
61
+ if (typeof port !== 'number' || !Number.isFinite(port)) {
62
+ throw logger.errorWithException(
63
+ 'When registering from a node config file path, port must be a finite number.'
64
+ );
65
+ }
66
+ }
67
+
68
+ let configHolder: Grid3NodeConfig;
69
+ if (_.isString(data)) {
70
+ const configFilePath = data;
71
+ let fileContent: string;
72
+ try {
73
+ fileContent = await fs.readFile(data, 'utf-8');
74
+ } catch (err) {
75
+ logger.error(
76
+ `Unable to load Selenium Grid 3 node configuration file ${configFilePath} to ` +
77
+ `register with the hub: ${(err as Error).message}`
78
+ );
79
+ return;
80
+ }
81
+ try {
82
+ configHolder = JSON.parse(fileContent) as Grid3NodeConfig;
83
+ } catch (err) {
84
+ throw logger.errorWithException(
85
+ `Syntax error in Selenium Grid 3 node configuration file ${configFilePath}: ` +
86
+ (err as Error).message
87
+ );
88
+ }
89
+ } else {
90
+ configHolder = data;
91
+ }
92
+
93
+ postRequest(configHolder, addr, port, basePath);
94
+ }
95
+
96
+ /** Base URL for the Selenium Grid 3 hub (protocol + host + port). */
97
+ function hubUri(config: Grid3HubConfiguration): string {
98
+ const protocol = config.hubProtocol || 'http';
99
+ return `${protocol}://${config.hubHost}:${config.hubPort}`;
100
+ }
101
+
102
+ /** POST registration payload to the Selenium Grid 3 hub. */
103
+ async function registerToGrid(
104
+ postOptions: {url: string; method: string; data: Grid3NodeConfig},
105
+ configHolder: Grid3NodeConfig
106
+ ): Promise<void> {
107
+ const hubCfg = configHolder.configuration;
108
+ if (!hubCfg) {
109
+ return;
110
+ }
111
+ try {
112
+ const {status} = await axios(postOptions);
113
+ if (status !== 200) {
114
+ throw new Error(`Request failed with code ${status}`);
115
+ }
116
+ logger.debug(
117
+ `Appium successfully registered with the Selenium Grid 3 hub at ` + hubUri(hubCfg)
118
+ );
119
+ } catch (err) {
120
+ logger.error(
121
+ `An attempt to register with the Selenium Grid 3 hub was unsuccessful: ` +
122
+ (err as Error).message
123
+ );
124
+ }
125
+ }
126
+
127
+ function postRequest(
128
+ configHolder: Grid3NodeConfig,
129
+ addr?: string,
130
+ port?: number,
131
+ basePath?: string
132
+ ): void {
133
+ // Move Selenium Grid 3 (flat) configuration properties into `configuration`
134
+ if (!_.has(configHolder, 'configuration')) {
135
+ const configuration: StringRecord = {};
136
+ const holder = configHolder as StringRecord;
137
+ for (const property in holder) {
138
+ if (_.has(holder, property) && property !== 'capabilities') {
139
+ configuration[property] = holder[property];
140
+ delete holder[property];
141
+ }
142
+ }
143
+ holder.configuration = configuration as Grid3HubConfiguration;
144
+ }
145
+
146
+ const cfg = configHolder.configuration;
147
+ if (!cfg) {
148
+ return;
149
+ }
150
+
151
+ // if the node config does not have the appium/webdriver url, host, and port,
152
+ // automatically add it based on how appium was initialized
153
+ // otherwise, we will take whatever the user setup
154
+ // because we will always set localhost/127.0.0.1. this won't work if your
155
+ // node and hub aren't in the same place
156
+ if (!cfg.url || !cfg.host || !cfg.port) {
157
+ cfg.url = `http://${addr}:${port}${basePath}`;
158
+ cfg.host = addr;
159
+ cfg.port = port;
160
+ }
161
+ // if the node config does not have id automatically add it
162
+ if (!cfg.id) {
163
+ cfg.id = `http://${cfg.host}:${cfg.port}`;
164
+ }
165
+
166
+ // the post options
167
+ const regRequest = {
168
+ url: `${hubUri(cfg)}${GRID_V3_REGISTER_PATH}`,
169
+ method: 'POST',
170
+ data: configHolder,
171
+ };
172
+
173
+ if (cfg.register !== true) {
174
+ logger.debug(`No Selenium Grid 3 hub registration sent (${cfg.register} = false)`);
175
+ return;
176
+ }
177
+
178
+ const registerCycleInterval = cfg.registerCycle;
179
+ if (registerCycleInterval === undefined || isNaN(registerCycleInterval) || registerCycleInterval <= 0) {
180
+ logger.warn(
181
+ `'registerCycle' is not a valid positive number. ` +
182
+ `No registration request will be sent to the Selenium Grid 3 hub.`
183
+ );
184
+ return;
185
+ }
186
+ // initiate a new Thread
187
+ let first = true;
188
+ logger.debug(
189
+ `Starting auto-register thread for Selenium Grid 3. ` +
190
+ `Will try to register every ${registerCycleInterval} ms.`
191
+ );
192
+ setInterval(async function registerRetry() {
193
+ if (first) {
194
+ first = false;
195
+ await registerToGrid(regRequest, configHolder);
196
+ } else if (!(await isAlreadyRegistered(configHolder))) {
197
+ // make the http POST to the Selenium Grid 3 hub for registration
198
+ await registerToGrid(regRequest, configHolder);
199
+ }
200
+ }, registerCycleInterval);
201
+ }
202
+
203
+ /** Query the Selenium Grid 3 hub to see if this node id is already registered. */
204
+ async function isAlreadyRegistered(configHolder: Grid3NodeConfig): Promise<boolean | undefined> {
205
+ //check if node is already registered
206
+ const hubCfg = configHolder.configuration;
207
+ if (!hubCfg?.id) {
208
+ return;
209
+ }
210
+ const id = hubCfg.id;
211
+ try {
212
+ const {data, status} = await axios<Grid3ProxyApiResponse>({
213
+ url: `${hubUri(hubCfg)}${GRID_V3_PROXY_API_PATH}`,
214
+ params: {id},
215
+ timeout: 10000,
216
+ });
217
+ if (status !== 200) {
218
+ throw new Error(`Request failed with code ${status}`);
219
+ }
220
+ if (!data.success) {
221
+ // if register fail, print the debug msg
222
+ logger.debug(`Selenium Grid 3 hub registration check: ${data.msg}`);
223
+ }
224
+ return data.success;
225
+ } catch (err) {
226
+ logger.debug(`Selenium Grid 3 hub down or not responding: ${(err as Error).message}`);
227
+ }
228
+ }
229
+
230
+ interface Grid3HubConfiguration {
231
+ hubProtocol?: string;
232
+ hubHost?: string;
233
+ hubPort?: number;
234
+ url?: string;
235
+ host?: string;
236
+ port?: number;
237
+ id?: string;
238
+ register?: boolean;
239
+ registerCycle?: number;
240
+ }
241
+
242
+ interface Grid3NodeConfig extends StringRecord {
243
+ configuration?: Grid3HubConfiguration;
244
+ capabilities?: unknown;
245
+ }
246
+
247
+ interface Grid3ProxyApiResponse {
248
+ success: boolean;
249
+ msg?: string;
250
+ }