appium-geckodriver 2.1.11 → 2.2.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/CHANGELOG.md +12 -0
- package/README.md +1 -0
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/constants.d.ts +9 -0
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +10 -1
- package/build/lib/constants.js.map +1 -1
- package/build/lib/desired-caps.d.ts +3 -0
- package/build/lib/desired-caps.d.ts.map +1 -1
- package/build/lib/desired-caps.js +17 -14
- package/build/lib/desired-caps.js.map +1 -1
- package/build/lib/doctor/optional-checks.d.ts.map +1 -1
- package/build/lib/doctor/optional-checks.js.map +1 -1
- package/build/lib/driver.d.ts +1 -0
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +14 -0
- package/build/lib/driver.js.map +1 -1
- package/build/lib/gecko.d.ts.map +1 -1
- package/build/lib/gecko.js +28 -14
- package/build/lib/gecko.js.map +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +3 -4
- package/build/lib/utils.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/commands/find.ts +8 -4
- package/lib/constants.ts +10 -0
- package/lib/desired-caps.ts +19 -17
- package/lib/doctor/optional-checks.ts +6 -3
- package/lib/doctor/required-checks.ts +0 -1
- package/lib/doctor/utils.ts +0 -1
- package/lib/driver.ts +32 -13
- package/lib/gecko.ts +86 -56
- package/lib/index.ts +2 -2
- package/lib/logger.ts +1 -2
- package/lib/utils.ts +26 -14
- package/npm-shrinkwrap.json +307 -1862
- package/package.json +4 -2
package/lib/commands/find.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import {util} from 'appium/support';
|
|
2
|
+
import type {GeckoDriver} from '../driver';
|
|
3
3
|
|
|
4
4
|
// This is needed to make lookup by image working
|
|
5
|
-
export async function findElOrEls
|
|
6
|
-
this: GeckoDriver,
|
|
5
|
+
export async function findElOrEls(
|
|
6
|
+
this: GeckoDriver,
|
|
7
|
+
strategy: string,
|
|
8
|
+
selector: string,
|
|
9
|
+
mult: boolean,
|
|
10
|
+
context?: string,
|
|
7
11
|
): Promise<any> {
|
|
8
12
|
const endpoint = `/element${context ? `/${util.unwrapElement(context)}/element` : ''}${mult ? 's' : ''}`;
|
|
9
13
|
return await this.gecko.proxy.command(endpoint, 'POST', {
|
package/lib/constants.ts
CHANGED
|
@@ -2,3 +2,13 @@ export const VERBOSITY = {
|
|
|
2
2
|
DEBUG: 'debug',
|
|
3
3
|
TRACE: 'trace',
|
|
4
4
|
} as const;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Insecure feature flag name which must be enabled on the Appium server
|
|
8
|
+
* in order to use the `geckodriverExecutable` capability.
|
|
9
|
+
*
|
|
10
|
+
* Note: when starting the Appium server, this feature must be referenced
|
|
11
|
+
* with the automation name prefix (for example
|
|
12
|
+
* `--allow-insecure gecko:custom_geckodriver_executable`).
|
|
13
|
+
*/
|
|
14
|
+
export const INSECURE_FEAT_CUSTOM_GECKODRIVER_EXECUTABLE = 'custom_geckodriver_executable' as const;
|
package/lib/desired-caps.ts
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type {Constraints} from '@appium/types';
|
|
2
|
+
import {VERBOSITY} from './constants';
|
|
3
3
|
|
|
4
4
|
const DESIRED_CAP_CONSTRAINTS = {
|
|
5
5
|
browserName: {
|
|
6
|
-
isString: true
|
|
6
|
+
isString: true,
|
|
7
7
|
},
|
|
8
8
|
browserVersion: {
|
|
9
|
-
isString: true
|
|
9
|
+
isString: true,
|
|
10
10
|
},
|
|
11
11
|
acceptInsecureCerts: {
|
|
12
|
-
isBoolean: true
|
|
12
|
+
isBoolean: true,
|
|
13
13
|
},
|
|
14
14
|
pageLoadStrategy: {
|
|
15
|
-
isString: true
|
|
15
|
+
isString: true,
|
|
16
16
|
},
|
|
17
17
|
proxy: {
|
|
18
|
-
isObject: true
|
|
18
|
+
isObject: true,
|
|
19
19
|
},
|
|
20
20
|
setWindowRect: {
|
|
21
|
-
isBoolean: true
|
|
21
|
+
isBoolean: true,
|
|
22
22
|
},
|
|
23
23
|
timeouts: {
|
|
24
|
-
isObject: true
|
|
24
|
+
isObject: true,
|
|
25
25
|
},
|
|
26
26
|
unhandledPromptBehavior: {
|
|
27
|
-
isString: true
|
|
27
|
+
isString: true,
|
|
28
28
|
},
|
|
29
29
|
systemPort: {
|
|
30
|
-
isNumber: true
|
|
30
|
+
isNumber: true,
|
|
31
31
|
},
|
|
32
32
|
marionettePort: {
|
|
33
|
-
isNumber: true
|
|
33
|
+
isNumber: true,
|
|
34
|
+
},
|
|
35
|
+
geckodriverExecutable: {
|
|
36
|
+
isString: true,
|
|
34
37
|
},
|
|
35
38
|
verbosity: {
|
|
36
39
|
isString: true,
|
|
37
|
-
inclusionCaseInsensitive: Object.values(VERBOSITY) as [string, ...string[]]
|
|
40
|
+
inclusionCaseInsensitive: Object.values(VERBOSITY) as [string, ...string[]],
|
|
38
41
|
},
|
|
39
42
|
androidStorage: {
|
|
40
43
|
isString: true,
|
|
41
|
-
inclusionCaseInsensitive: ['auto', 'app', 'internal', 'sdcard']
|
|
44
|
+
inclusionCaseInsensitive: ['auto', 'app', 'internal', 'sdcard'],
|
|
42
45
|
},
|
|
43
46
|
'moz:firefoxOptions': {
|
|
44
|
-
isObject: true
|
|
45
|
-
}
|
|
47
|
+
isObject: true,
|
|
48
|
+
},
|
|
46
49
|
} as const satisfies Constraints;
|
|
47
50
|
|
|
48
51
|
export const desiredCapConstraints = DESIRED_CAP_CONSTRAINTS;
|
|
49
|
-
|
|
@@ -78,7 +78,9 @@ export class AndroidSdkCheck implements IDoctorCheck {
|
|
|
78
78
|
const listOfTools = this.TOOL_NAMES.join(', ');
|
|
79
79
|
const sdkRoot = getSdkRootFromEnv();
|
|
80
80
|
if (!sdkRoot) {
|
|
81
|
-
return doctor.nokOptional(
|
|
81
|
+
return doctor.nokOptional(
|
|
82
|
+
`${listOfTools} could not be found because ANDROID_HOME is NOT set!`,
|
|
83
|
+
);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
this.log.info(` Checking ${listOfTools}`);
|
|
@@ -92,7 +94,9 @@ export class AndroidSdkCheck implements IDoctorCheck {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
if (missingBinaries.length > 0) {
|
|
95
|
-
return doctor.nokOptional(
|
|
97
|
+
return doctor.nokOptional(
|
|
98
|
+
`${missingBinaries.join(', ')} could NOT be found in '${sdkRoot}'!`,
|
|
99
|
+
);
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
return doctor.okOptional(`${listOfTools} exist in '${sdkRoot}'`);
|
|
@@ -115,4 +119,3 @@ export class AndroidSdkCheck implements IDoctorCheck {
|
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
export const androidSdkCheck = new AndroidSdkCheck();
|
|
118
|
-
|
package/lib/doctor/utils.ts
CHANGED
package/lib/driver.ts
CHANGED
|
@@ -7,11 +7,12 @@ import type {
|
|
|
7
7
|
ExternalDriver,
|
|
8
8
|
W3CDriverCaps,
|
|
9
9
|
} from '@appium/types';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import {BaseDriver, errors} from 'appium/driver';
|
|
11
|
+
import {GECKO_SERVER_HOST, GeckoDriverServer} from './gecko';
|
|
12
|
+
import {desiredCapConstraints} from './desired-caps';
|
|
13
|
+
import {INSECURE_FEAT_CUSTOM_GECKODRIVER_EXECUTABLE} from './constants';
|
|
13
14
|
import * as findCommands from './commands/find';
|
|
14
|
-
import {
|
|
15
|
+
import {formatCapsForServer} from './utils';
|
|
15
16
|
|
|
16
17
|
const NO_PROXY: RouteMatcher[] = [
|
|
17
18
|
['GET', new RegExp('^/session/[^/]+/appium')],
|
|
@@ -31,7 +32,7 @@ export class GeckoDriver
|
|
|
31
32
|
private _bidiProxyUrl: string | null = null;
|
|
32
33
|
public proxyReqRes: (...args: any) => any;
|
|
33
34
|
|
|
34
|
-
constructor
|
|
35
|
+
constructor(opts: InitialOpts = {} as InitialOpts) {
|
|
35
36
|
super(opts);
|
|
36
37
|
this.desiredCapConstraints = _.cloneDeep(desiredCapConstraints);
|
|
37
38
|
this.locatorStrategies = [
|
|
@@ -48,26 +49,45 @@ export class GeckoDriver
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
-
override proxyActive
|
|
52
|
+
override proxyActive(sessionId?: string): boolean {
|
|
52
53
|
return this.isProxyActive;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
override getProxyAvoidList
|
|
56
|
+
override getProxyAvoidList(): RouteMatcher[] {
|
|
56
57
|
return NO_PROXY;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
override canProxy
|
|
60
|
+
override canProxy(): boolean {
|
|
60
61
|
return true;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
get gecko
|
|
64
|
+
get gecko(): GeckoDriverServer {
|
|
64
65
|
if (!this._gecko) {
|
|
65
66
|
throw new Error('Gecko driver is not initialized');
|
|
66
67
|
}
|
|
67
68
|
return this._gecko;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
override
|
|
71
|
+
override validateDesiredCaps(caps: any): caps is any {
|
|
72
|
+
const isValid = super.validateDesiredCaps(caps);
|
|
73
|
+
if (!isValid) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
caps.geckodriverExecutable &&
|
|
79
|
+
!this.isFeatureEnabled(INSECURE_FEAT_CUSTOM_GECKODRIVER_EXECUTABLE)
|
|
80
|
+
) {
|
|
81
|
+
throw new errors.SessionNotCreatedError(
|
|
82
|
+
`The 'geckodriverExecutable' capability requires the ` +
|
|
83
|
+
`'${INSECURE_FEAT_CUSTOM_GECKODRIVER_EXECUTABLE}' insecure feature to be enabled ` +
|
|
84
|
+
`on the Appium server.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override async createSession(
|
|
71
91
|
w3cCaps1: W3CDriverCaps<GeckoConstraints>,
|
|
72
92
|
w3cCaps2?: W3CDriverCaps<GeckoConstraints>,
|
|
73
93
|
...args: any[]
|
|
@@ -96,7 +116,7 @@ export class GeckoDriver
|
|
|
96
116
|
return this._bidiProxyUrl;
|
|
97
117
|
}
|
|
98
118
|
|
|
99
|
-
override async deleteSession
|
|
119
|
+
override async deleteSession(): Promise<void> {
|
|
100
120
|
this.log.info('Ending Gecko Driver session');
|
|
101
121
|
await this._gecko?.stop();
|
|
102
122
|
this.resetState();
|
|
@@ -104,7 +124,7 @@ export class GeckoDriver
|
|
|
104
124
|
await super.deleteSession();
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
private resetState
|
|
127
|
+
private resetState(): void {
|
|
108
128
|
this._gecko = null;
|
|
109
129
|
this.proxyReqRes = null as any;
|
|
110
130
|
this.isProxyActive = false;
|
|
@@ -131,4 +151,3 @@ export class GeckoDriver
|
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
export default GeckoDriver;
|
|
134
|
-
|
package/lib/gecko.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
4
|
+
import {JWProxy, errors} from 'appium/driver';
|
|
5
|
+
import {fs, util, system} from 'appium/support';
|
|
6
|
+
import {SubProcess} from 'teen_process';
|
|
7
|
+
import {waitForCondition} from 'asyncbox';
|
|
8
|
+
import {findAPortNotInUse} from 'portscanner';
|
|
9
|
+
import {execSync} from 'node:child_process';
|
|
10
|
+
import type {AppiumLogger, StringRecord, HTTPMethod, HTTPBody} from '@appium/types';
|
|
11
|
+
import {VERBOSITY} from './constants';
|
|
12
12
|
|
|
13
13
|
const GD_BINARY = `geckodriver${system.isWindows() ? '.exe' : ''}`;
|
|
14
14
|
const STARTUP_TIMEOUT_MS = 10000; // 10 seconds
|
|
15
15
|
const GECKO_PORT_RANGE: [number, number] = [5200, 5300];
|
|
16
16
|
const GECKO_SERVER_GUARD = util.getLockFileGuard(
|
|
17
17
|
path.resolve(os.tmpdir(), 'gecko_server_guard.lock'),
|
|
18
|
-
{timeout: 5, tryRecovery: true}
|
|
18
|
+
{timeout: 5, tryRecovery: true},
|
|
19
19
|
);
|
|
20
20
|
const DEFAULT_MARIONETTE_PORT = 2828;
|
|
21
21
|
export const GECKO_SERVER_HOST = '127.0.0.1';
|
|
@@ -27,11 +27,12 @@ export interface SessionOptions {
|
|
|
27
27
|
export class GeckoProxy extends JWProxy {
|
|
28
28
|
didProcessExit?: boolean;
|
|
29
29
|
|
|
30
|
-
override async proxyCommand
|
|
30
|
+
override async proxyCommand(url: string, method: HTTPMethod, body: HTTPBody = null) {
|
|
31
31
|
if (this.didProcessExit) {
|
|
32
32
|
throw new errors.InvalidContextError(
|
|
33
33
|
`'${method} ${url}' cannot be proxied to Gecko Driver server because ` +
|
|
34
|
-
|
|
34
|
+
'its process is not running (probably crashed). Check the Appium log for more details',
|
|
35
|
+
);
|
|
35
36
|
}
|
|
36
37
|
return await super.proxyCommand(url, method, body);
|
|
37
38
|
}
|
|
@@ -42,32 +43,34 @@ class GeckoDriverProcess {
|
|
|
42
43
|
private readonly verbosity?: string;
|
|
43
44
|
private readonly androidStorage?: string;
|
|
44
45
|
private readonly marionettePort?: number;
|
|
46
|
+
private readonly geckodriverExecutable?: string;
|
|
45
47
|
private _port?: number;
|
|
46
48
|
private readonly log: AppiumLogger;
|
|
47
49
|
private _proc: SubProcess | null = null;
|
|
48
50
|
|
|
49
|
-
constructor
|
|
51
|
+
constructor(log: AppiumLogger, opts: StringRecord = {}) {
|
|
50
52
|
this.noReset = opts.noReset;
|
|
51
53
|
this.verbosity = opts.verbosity;
|
|
52
54
|
this.androidStorage = opts.androidStorage;
|
|
53
55
|
this.marionettePort = opts.marionettePort;
|
|
56
|
+
this.geckodriverExecutable = opts.geckodriverExecutable;
|
|
54
57
|
this._port = opts.systemPort;
|
|
55
58
|
this.log = log;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
get isRunning
|
|
59
|
-
return !!
|
|
61
|
+
get isRunning(): boolean {
|
|
62
|
+
return !!this._proc?.isRunning;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
get port
|
|
65
|
+
get port(): number | undefined {
|
|
63
66
|
return this._port;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
get proc
|
|
69
|
+
get proc(): SubProcess | null {
|
|
67
70
|
return this._proc;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
async init
|
|
73
|
+
async init(): Promise<void> {
|
|
71
74
|
if (this.isRunning) {
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
@@ -80,19 +83,14 @@ class GeckoDriverProcess {
|
|
|
80
83
|
} catch {
|
|
81
84
|
throw new Error(
|
|
82
85
|
`Cannot find any free port in range ${startPort}..${endPort}. ` +
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
`Double check the processes that are locking ports within this range and terminate ` +
|
|
87
|
+
`these which are not needed anymore or set any free port number to the 'systemPort' capability`,
|
|
88
|
+
);
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
driverBin = await fs.which(GD_BINARY);
|
|
92
|
-
} catch {
|
|
93
|
-
throw new Error(`${GD_BINARY} binary cannot be found in PATH. ` +
|
|
94
|
-
`Please make sure it is present on your system`);
|
|
95
|
-
}
|
|
93
|
+
const driverBin = await this.resolveGeckodriverBinary();
|
|
96
94
|
const args: string[] = [];
|
|
97
95
|
/* #region Options */
|
|
98
96
|
switch (_.toLower(this.verbosity)) {
|
|
@@ -107,8 +105,12 @@ class GeckoDriverProcess {
|
|
|
107
105
|
args.push('--connect-existing');
|
|
108
106
|
// https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html#code-connect-existing-code
|
|
109
107
|
if (_.isNil(this.marionettePort)) {
|
|
110
|
-
this.log.info(
|
|
111
|
-
|
|
108
|
+
this.log.info(
|
|
109
|
+
`'marionettePort' capability value is not provided while 'noReset' is enabled`,
|
|
110
|
+
);
|
|
111
|
+
this.log.info(
|
|
112
|
+
`Assigning 'marionettePort' to the default value (${DEFAULT_MARIONETTE_PORT})`,
|
|
113
|
+
);
|
|
112
114
|
}
|
|
113
115
|
args.push('--marionette-port', `${this.marionettePort ?? DEFAULT_MARIONETTE_PORT}`);
|
|
114
116
|
} else if (!_.isNil(this.marionettePort)) {
|
|
@@ -134,19 +136,40 @@ class GeckoDriverProcess {
|
|
|
134
136
|
await this._proc.start(0);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
async stop
|
|
139
|
+
async stop(): Promise<void> {
|
|
138
140
|
if (this.isRunning) {
|
|
139
141
|
await this._proc?.stop('SIGTERM');
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
async kill
|
|
145
|
+
async kill(): Promise<void> {
|
|
144
146
|
if (this.isRunning) {
|
|
145
147
|
try {
|
|
146
148
|
await this._proc?.stop('SIGKILL');
|
|
147
149
|
} catch {}
|
|
148
150
|
}
|
|
149
151
|
}
|
|
152
|
+
|
|
153
|
+
private async resolveGeckodriverBinary(): Promise<string> {
|
|
154
|
+
if (this.geckodriverExecutable) {
|
|
155
|
+
if (!(await fs.exists(this.geckodriverExecutable))) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`The custom geckodriver binary at '${this.geckodriverExecutable}' cannot be found. ` +
|
|
158
|
+
`Make sure the path is correct and accessible to the Appium server process`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return this.geckodriverExecutable;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
return await fs.which(GD_BINARY);
|
|
166
|
+
} catch {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`${GD_BINARY} binary cannot be found in PATH. ` +
|
|
169
|
+
`Please make sure it is present on your system`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
const RUNNING_PROCESS_IDS: (number | undefined)[] = [];
|
|
@@ -156,7 +179,10 @@ process.once('exit', () => {
|
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
const command = system.isWindows()
|
|
159
|
-
?
|
|
182
|
+
? 'taskkill.exe ' +
|
|
183
|
+
RUNNING_PROCESS_IDS.filter((pid): pid is number => pid !== undefined)
|
|
184
|
+
.map((pid) => `/PID ${pid}`)
|
|
185
|
+
.join(' ')
|
|
160
186
|
: `kill ${RUNNING_PROCESS_IDS.filter((pid): pid is number => pid !== undefined).join(' ')}`;
|
|
161
187
|
try {
|
|
162
188
|
execSync(command);
|
|
@@ -168,24 +194,24 @@ export class GeckoDriverServer {
|
|
|
168
194
|
private readonly _process: GeckoDriverProcess;
|
|
169
195
|
private readonly log: AppiumLogger;
|
|
170
196
|
|
|
171
|
-
constructor
|
|
197
|
+
constructor(log: AppiumLogger, caps: StringRecord) {
|
|
172
198
|
this._process = new GeckoDriverProcess(log, caps);
|
|
173
199
|
this.log = log;
|
|
174
200
|
this._proxy = null;
|
|
175
201
|
}
|
|
176
202
|
|
|
177
|
-
get proxy
|
|
203
|
+
get proxy(): GeckoProxy {
|
|
178
204
|
if (!this._proxy) {
|
|
179
205
|
throw new Error('Gecko proxy is not initialized');
|
|
180
206
|
}
|
|
181
207
|
return this._proxy;
|
|
182
208
|
}
|
|
183
209
|
|
|
184
|
-
get isRunning
|
|
185
|
-
return !!
|
|
210
|
+
get isRunning(): boolean {
|
|
211
|
+
return !!this._process?.isRunning;
|
|
186
212
|
}
|
|
187
213
|
|
|
188
|
-
async start
|
|
214
|
+
async start(geckoCaps: StringRecord, opts: SessionOptions = {}): Promise<StringRecord> {
|
|
189
215
|
await this._process.init();
|
|
190
216
|
|
|
191
217
|
const proxyOpts: any = {
|
|
@@ -207,28 +233,33 @@ export class GeckoDriverServer {
|
|
|
207
233
|
});
|
|
208
234
|
|
|
209
235
|
try {
|
|
210
|
-
await waitForCondition(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
236
|
+
await waitForCondition(
|
|
237
|
+
async () => {
|
|
238
|
+
try {
|
|
239
|
+
await this._proxy?.command('/status', 'GET');
|
|
240
|
+
return true;
|
|
241
|
+
} catch (err: any) {
|
|
242
|
+
if (this._proxy?.didProcessExit) {
|
|
243
|
+
throw new Error(err.message);
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
217
246
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
waitMs: STARTUP_TIMEOUT_MS,
|
|
250
|
+
intervalMs: 1000,
|
|
251
|
+
},
|
|
252
|
+
);
|
|
224
253
|
} catch (e: any) {
|
|
225
254
|
if (this._process.isRunning) {
|
|
226
255
|
// avoid "frozen" processes,
|
|
227
256
|
await this._process.kill();
|
|
228
257
|
}
|
|
229
258
|
if (/Condition unmet/.test(e.message)) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Gecko Driver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
|
|
261
|
+
`Make sure it could be started manually from a terminal`,
|
|
262
|
+
);
|
|
232
263
|
}
|
|
233
264
|
throw e;
|
|
234
265
|
}
|
|
@@ -238,15 +269,15 @@ export class GeckoDriverServer {
|
|
|
238
269
|
this._process.proc?.on('exit', () => void _.pull(RUNNING_PROCESS_IDS, pid));
|
|
239
270
|
}
|
|
240
271
|
|
|
241
|
-
return await this._proxy.command('/session', 'POST', {
|
|
272
|
+
return (await this._proxy.command('/session', 'POST', {
|
|
242
273
|
capabilities: {
|
|
243
274
|
firstMatch: [{}],
|
|
244
275
|
alwaysMatch: geckoCaps,
|
|
245
|
-
}
|
|
246
|
-
}) as StringRecord;
|
|
276
|
+
},
|
|
277
|
+
})) as StringRecord;
|
|
247
278
|
}
|
|
248
279
|
|
|
249
|
-
async stop
|
|
280
|
+
async stop(): Promise<void> {
|
|
250
281
|
if (!this.isRunning) {
|
|
251
282
|
this.log.info(`Gecko Driver session cannot be stopped, because the server is not running`);
|
|
252
283
|
return;
|
|
@@ -270,4 +301,3 @@ export class GeckoDriverServer {
|
|
|
270
301
|
}
|
|
271
302
|
|
|
272
303
|
export default GeckoDriverServer;
|
|
273
|
-
|
package/lib/index.ts
CHANGED
package/lib/logger.ts
CHANGED
package/lib/utils.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import {
|
|
2
|
+
import {fs, net, zip, tempDir} from 'appium/support';
|
|
3
3
|
import tar from 'tar-stream';
|
|
4
4
|
import zlib from 'node:zlib';
|
|
5
5
|
import B from 'bluebird';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
7
|
+
import type {StringRecord} from '@appium/types';
|
|
8
|
+
import {STANDARD_CAPS} from 'appium/driver';
|
|
9
9
|
|
|
10
10
|
const GECKO_CAP_PREFIXES = ['moz:'] as const;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Format capabilities for Gecko server
|
|
14
14
|
*/
|
|
15
|
-
export function formatCapsForServer
|
|
15
|
+
export function formatCapsForServer(caps: StringRecord): StringRecord {
|
|
16
16
|
const result: StringRecord = {};
|
|
17
17
|
for (const [name, value] of _.toPairs(caps)) {
|
|
18
|
-
if (
|
|
18
|
+
if (
|
|
19
|
+
GECKO_CAP_PREFIXES.some((prefix) => name.startsWith(prefix)) ||
|
|
20
|
+
STANDARD_CAPS.has(name as any)
|
|
21
|
+
) {
|
|
19
22
|
result[name] = value;
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -46,7 +49,11 @@ export async function mkdirp(p: string): Promise<void> {
|
|
|
46
49
|
/**
|
|
47
50
|
* Extract a specific file from a tar.gz archive
|
|
48
51
|
*/
|
|
49
|
-
export async function extractFileFromTarGz(
|
|
52
|
+
export async function extractFileFromTarGz(
|
|
53
|
+
srcAcrhive: string,
|
|
54
|
+
fileToExtract: string,
|
|
55
|
+
dstPath: string,
|
|
56
|
+
): Promise<void> {
|
|
50
57
|
const chunks: Buffer[] = [];
|
|
51
58
|
const extract = tar.extract();
|
|
52
59
|
const extractPromise = new B<void>((resolve, reject) => {
|
|
@@ -56,7 +63,7 @@ export async function extractFileFromTarGz(srcAcrhive: string, fileToExtract: st
|
|
|
56
63
|
chunks.push(chunk);
|
|
57
64
|
});
|
|
58
65
|
}
|
|
59
|
-
stream.on('end', function() {
|
|
66
|
+
stream.on('end', function () {
|
|
60
67
|
next();
|
|
61
68
|
});
|
|
62
69
|
stream.resume();
|
|
@@ -71,16 +78,16 @@ export async function extractFileFromTarGz(srcAcrhive: string, fileToExtract: st
|
|
|
71
78
|
}
|
|
72
79
|
} else {
|
|
73
80
|
return reject(
|
|
74
|
-
new Error(
|
|
81
|
+
new Error(
|
|
82
|
+
`The file '${fileToExtract}' could not be found in the '${srcAcrhive}' archive`,
|
|
83
|
+
),
|
|
75
84
|
);
|
|
76
85
|
}
|
|
77
86
|
resolve();
|
|
78
87
|
});
|
|
79
88
|
});
|
|
80
89
|
|
|
81
|
-
fs.createReadStream(srcAcrhive)
|
|
82
|
-
.pipe(zlib.createGunzip())
|
|
83
|
-
.pipe(extract);
|
|
90
|
+
fs.createReadStream(srcAcrhive).pipe(zlib.createGunzip()).pipe(extract);
|
|
84
91
|
|
|
85
92
|
await extractPromise;
|
|
86
93
|
}
|
|
@@ -88,7 +95,11 @@ export async function extractFileFromTarGz(srcAcrhive: string, fileToExtract: st
|
|
|
88
95
|
/**
|
|
89
96
|
* Extract a specific file from a zip archive
|
|
90
97
|
*/
|
|
91
|
-
export async function extractFileFromZip(
|
|
98
|
+
export async function extractFileFromZip(
|
|
99
|
+
srcAcrhive: string,
|
|
100
|
+
fileToExtract: string,
|
|
101
|
+
dstPath: string,
|
|
102
|
+
): Promise<void> {
|
|
92
103
|
let didFindEntry = false;
|
|
93
104
|
await zip.readEntries(srcAcrhive, async ({entry, extractEntryTo}) => {
|
|
94
105
|
if (didFindEntry || entry.fileName !== fileToExtract) {
|
|
@@ -105,7 +116,8 @@ export async function extractFileFromZip(srcAcrhive: string, fileToExtract: stri
|
|
|
105
116
|
}
|
|
106
117
|
});
|
|
107
118
|
if (!didFindEntry) {
|
|
108
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`The file '${fileToExtract}' could not be found in the '${srcAcrhive}' archive`,
|
|
121
|
+
);
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
|
-
|