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.
- package/build/lib/cli/args.d.ts +16 -12
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +15 -35
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/driver-command.d.ts +51 -93
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +11 -66
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +211 -415
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +384 -653
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/extension.d.ts +11 -16
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js +10 -28
- package/build/lib/cli/extension.js.map +1 -1
- package/build/lib/cli/parser.d.ts +40 -69
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +24 -59
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/plugin-command.d.ts +50 -90
- package/build/lib/cli/plugin-command.d.ts.map +1 -1
- package/build/lib/cli/plugin-command.js +11 -63
- package/build/lib/cli/plugin-command.js.map +1 -1
- package/build/lib/cli/setup-command.d.ts +21 -26
- package/build/lib/cli/setup-command.d.ts.map +1 -1
- package/build/lib/cli/setup-command.js +13 -55
- package/build/lib/cli/setup-command.js.map +1 -1
- package/build/lib/cli/utils.d.ts +27 -29
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +29 -31
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/config-file.d.ts +24 -67
- package/build/lib/config-file.d.ts.map +1 -1
- package/build/lib/config-file.js +56 -115
- package/build/lib/config-file.js.map +1 -1
- package/build/lib/config.d.ts +42 -44
- package/build/lib/config.d.ts.map +1 -1
- package/build/lib/config.js +75 -107
- package/build/lib/config.js.map +1 -1
- package/build/lib/constants.d.ts +23 -23
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +10 -15
- package/build/lib/constants.js.map +1 -1
- package/build/lib/doctor/doctor.d.ts +40 -57
- package/build/lib/doctor/doctor.d.ts.map +1 -1
- package/build/lib/doctor/doctor.js +29 -60
- package/build/lib/doctor/doctor.js.map +1 -1
- package/build/lib/grid-register.d.ts +32 -7
- package/build/lib/grid-register.d.ts.map +1 -1
- package/build/lib/grid-register.js +84 -48
- package/build/lib/grid-register.js.map +1 -1
- package/build/lib/logsink.d.ts +13 -22
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +48 -103
- package/build/lib/logsink.js.map +1 -1
- package/build/lib/main.js +1 -1
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/arg-spec.d.ts +32 -107
- package/build/lib/schema/arg-spec.d.ts.map +1 -1
- package/build/lib/schema/arg-spec.js +11 -107
- package/build/lib/schema/arg-spec.js.map +1 -1
- package/build/lib/schema/cli-args.d.ts +3 -15
- package/build/lib/schema/cli-args.d.ts.map +1 -1
- package/build/lib/schema/cli-args.js +15 -105
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/cli-transformers.d.ts +15 -12
- package/build/lib/schema/cli-transformers.d.ts.map +1 -1
- package/build/lib/schema/cli-transformers.js +15 -45
- package/build/lib/schema/cli-transformers.js.map +1 -1
- package/build/lib/schema/index.d.ts +2 -2
- package/build/lib/schema/index.d.ts.map +1 -1
- package/build/lib/schema/index.js.map +1 -1
- package/build/lib/schema/keywords.d.ts +12 -20
- package/build/lib/schema/keywords.d.ts.map +1 -1
- package/build/lib/schema/keywords.js +6 -51
- package/build/lib/schema/keywords.js.map +1 -1
- package/build/lib/schema/schema.d.ts +106 -231
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +75 -345
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +59 -238
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +55 -207
- package/build/lib/utils.js.map +1 -1
- package/lib/cli/{args.js → args.ts} +40 -51
- package/lib/cli/driver-command.ts +122 -0
- package/lib/cli/{extension-command.js → extension-command.ts} +610 -689
- package/lib/cli/extension.ts +65 -0
- package/lib/cli/{parser.js → parser.ts} +48 -71
- package/lib/cli/plugin-command.ts +117 -0
- package/lib/cli/{setup-command.js → setup-command.ts} +57 -72
- package/lib/cli/utils.ts +97 -0
- package/lib/config-file.ts +212 -0
- package/lib/{config.js → config.ts} +129 -141
- package/lib/{constants.js → constants.ts} +30 -41
- package/lib/doctor/{doctor.js → doctor.ts} +81 -91
- package/lib/grid-register.ts +250 -0
- package/lib/{logsink.js → logsink.ts} +91 -137
- package/lib/main.js +1 -1
- package/lib/schema/arg-spec.ts +131 -0
- package/lib/schema/cli-args.ts +171 -0
- package/lib/schema/cli-transformers.ts +83 -0
- package/lib/schema/keywords.ts +96 -0
- package/lib/schema/schema.ts +449 -0
- package/lib/utils.ts +404 -0
- package/package.json +16 -16
- package/lib/cli/driver-command.js +0 -174
- package/lib/cli/extension.js +0 -74
- package/lib/cli/plugin-command.js +0 -164
- package/lib/cli/utils.js +0 -91
- package/lib/config-file.js +0 -228
- package/lib/grid-register.js +0 -146
- package/lib/schema/arg-spec.js +0 -229
- package/lib/schema/cli-args.js +0 -254
- package/lib/schema/cli-transformers.js +0 -113
- package/lib/schema/keywords.js +0 -136
- package/lib/schema/schema.js +0 -725
- package/lib/utils.js +0 -512
- /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 {
|
|
3
|
+
import {util, doctor, logger} from '@appium/support';
|
|
4
|
+
import type {AppiumLogger, DoctorCheckResult, IDoctorCheck} from '@appium/types';
|
|
4
5
|
|
|
5
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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) => {
|
|
21
|
-
|
|
39
|
+
.forEach((c) => {
|
|
40
|
+
c.log = this.log;
|
|
41
|
+
});
|
|
22
42
|
this.foundIssues = [];
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
/**
|
|
26
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|