webdriver 9.0.0-alpha.78 → 9.0.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/README.md +3 -3
- package/build/bidi/core.d.ts +6 -1
- package/build/bidi/core.d.ts.map +1 -1
- package/build/bidi/handler.d.ts +49 -0
- package/build/bidi/handler.d.ts.map +1 -1
- package/build/bidi/localTypes.d.ts +134 -47
- package/build/bidi/localTypes.d.ts.map +1 -1
- package/build/bidi/remoteTypes.d.ts +135 -28
- package/build/bidi/remoteTypes.d.ts.map +1 -1
- package/build/command.d.ts.map +1 -1
- package/build/constants.d.ts +2 -1
- package/build/constants.d.ts.map +1 -1
- package/build/index.cjs +233 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.cts.map +1 -0
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1615 -141
- package/build/request/index.d.ts +1 -2
- package/build/request/index.d.ts.map +1 -1
- package/build/request/request.d.ts +0 -1
- package/build/request/request.d.ts.map +1 -1
- package/build/types.d.ts +9 -7
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +4 -6
- package/build/utils.d.ts.map +1 -1
- package/package.json +15 -18
- package/build/bidi/core.js +0 -73
- package/build/bidi/handler.js +0 -446
- package/build/bidi/localTypes.js +0 -15
- package/build/bidi/remoteTypes.js +0 -15
- package/build/bidi/socket.js +0 -49
- package/build/cjs/index.d.ts +0 -2
- package/build/cjs/index.d.ts.map +0 -1
- package/build/cjs/index.js +0 -40
- package/build/cjs/package.json +0 -5
- package/build/command.js +0 -140
- package/build/constants.js +0 -146
- package/build/request/index.js +0 -215
- package/build/request/request.js +0 -38
- package/build/types.js +0 -1
- package/build/utils.js +0 -384
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/command.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import logger from '@wdio/logger';
|
|
2
|
-
import { commandCallStructure, isValidParameter, getArgumentType } from '@wdio/utils';
|
|
3
|
-
import { WebDriverBidiProtocol, } from '@wdio/protocols';
|
|
4
|
-
import Request from './request/request.js';
|
|
5
|
-
const log = logger('webdriver');
|
|
6
|
-
const BIDI_COMMANDS = Object.values(WebDriverBidiProtocol).map((def) => def.socket.command);
|
|
7
|
-
export default function (method, endpointUri, commandInfo, doubleEncodeVariables = false) {
|
|
8
|
-
const { command, deprecated, ref, parameters, variables = [], isHubCommand = false } = commandInfo;
|
|
9
|
-
return async function protocolCommand(...args) {
|
|
10
|
-
const isBidiCommand = BIDI_COMMANDS.includes(command);
|
|
11
|
-
let endpoint = endpointUri; // clone endpointUri in case we change it
|
|
12
|
-
const commandParams = [...variables.map((v) => Object.assign(v, {
|
|
13
|
-
/**
|
|
14
|
-
* url variables are:
|
|
15
|
-
*/
|
|
16
|
-
required: true, // always required as they are part of the endpoint
|
|
17
|
-
type: 'string' // have to be always type of string
|
|
18
|
-
})), ...parameters];
|
|
19
|
-
const commandUsage = `${command}(${commandParams.map((p) => p.name).join(', ')})`;
|
|
20
|
-
const moreInfo = `\n\nFor more info see ${ref}\n`;
|
|
21
|
-
const body = {};
|
|
22
|
-
/**
|
|
23
|
-
* log deprecation warning if command is deprecated
|
|
24
|
-
*/
|
|
25
|
-
if (typeof deprecated === 'string') {
|
|
26
|
-
log.warn(deprecated.replace('This command', `The "${command}" command`));
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Throw this error message for all WebDriver Bidi commands.
|
|
30
|
-
* In case a successful connection to the browser bidi interface was established,
|
|
31
|
-
* we attach a custom Bidi prototype to the browser instance.
|
|
32
|
-
*/
|
|
33
|
-
if (isBidiCommand) {
|
|
34
|
-
throw new Error(`Failed to execute WebDriver Bidi command "${command}" as no Bidi session ` +
|
|
35
|
-
'was established. Make sure you enable it by setting "webSocketUrl: true" ' +
|
|
36
|
-
'in your capabilities and verify that your environment and browser supports it.');
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* parameter check
|
|
40
|
-
*/
|
|
41
|
-
const minAllowedParams = commandParams.filter((param) => param.required).length;
|
|
42
|
-
if (args.length < minAllowedParams || args.length > commandParams.length) {
|
|
43
|
-
const parameterDescription = commandParams.length
|
|
44
|
-
? `\n\nProperty Description:\n${commandParams.map((p) => ` "${p.name}" (${p.type}): ${p.description}`).join('\n')}`
|
|
45
|
-
: '';
|
|
46
|
-
throw new Error(`Wrong parameters applied for ${command}\n` +
|
|
47
|
-
`Usage: ${commandUsage}` +
|
|
48
|
-
parameterDescription +
|
|
49
|
-
moreInfo);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* parameter type check
|
|
53
|
-
*/
|
|
54
|
-
for (const [it, arg] of Object.entries(args)) {
|
|
55
|
-
if (isBidiCommand) {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
const i = parseInt(it, 10);
|
|
59
|
-
const commandParam = commandParams[i];
|
|
60
|
-
if (!isValidParameter(arg, commandParam.type)) {
|
|
61
|
-
/**
|
|
62
|
-
* ignore if argument is not required
|
|
63
|
-
*/
|
|
64
|
-
if (typeof arg === 'undefined' && !commandParam.required) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const actual = commandParam.type.endsWith('[]')
|
|
68
|
-
? `(${(Array.isArray(arg) ? arg : [arg]).map((a) => getArgumentType(a))})[]`
|
|
69
|
-
: getArgumentType(arg);
|
|
70
|
-
throw new Error(`Malformed type for "${commandParam.name}" parameter of command ${command}\n` +
|
|
71
|
-
`Expected: ${commandParam.type}\n` +
|
|
72
|
-
`Actual: ${actual}` +
|
|
73
|
-
moreInfo);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* inject url variables
|
|
77
|
-
*/
|
|
78
|
-
if (i < variables.length) {
|
|
79
|
-
const encodedArg = doubleEncodeVariables ? encodeURIComponent(encodeURIComponent(arg)) : encodeURIComponent(arg);
|
|
80
|
-
endpoint = endpoint.replace(`:${commandParams[i].name}`, encodedArg);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* rest of args are part of body payload
|
|
85
|
-
*/
|
|
86
|
-
body[commandParams[i].name] = arg;
|
|
87
|
-
}
|
|
88
|
-
const request = new Request(method, endpoint, body, isHubCommand);
|
|
89
|
-
request.on('performance', (...args) => this.emit('request.performance', ...args));
|
|
90
|
-
this.emit('command', { method, endpoint, body });
|
|
91
|
-
log.info('COMMAND', commandCallStructure(command, args));
|
|
92
|
-
/**
|
|
93
|
-
* use then here so we can better unit test what happens before and after the request
|
|
94
|
-
*/
|
|
95
|
-
return request.makeRequest(this.options, this.sessionId).then((result) => {
|
|
96
|
-
if (typeof result.value !== 'undefined') {
|
|
97
|
-
let resultLog = result.value;
|
|
98
|
-
if (/screenshot|recording/i.test(command) && typeof result.value === 'string' && result.value.length > 64) {
|
|
99
|
-
resultLog = `${result.value.slice(0, 61)}...`;
|
|
100
|
-
}
|
|
101
|
-
else if (command === 'executeScript' && body.script && body.script.includes('(() => window.__wdioEvents__)')) {
|
|
102
|
-
resultLog = `[${result.value.length} framework events captured]`;
|
|
103
|
-
}
|
|
104
|
-
log.info('RESULT', resultLog);
|
|
105
|
-
}
|
|
106
|
-
this.emit('result', { method, endpoint, body, result });
|
|
107
|
-
if (command === 'deleteSession') {
|
|
108
|
-
const shutdownDriver = body.deleteSessionOpts?.shutdownDriver !== false;
|
|
109
|
-
/**
|
|
110
|
-
* kill driver process if there is one
|
|
111
|
-
*/
|
|
112
|
-
if (shutdownDriver && 'wdio:driverPID' in this.capabilities && this.capabilities['wdio:driverPID']) {
|
|
113
|
-
log.info(`Kill driver process with PID ${this.capabilities['wdio:driverPID']}`);
|
|
114
|
-
const killedSuccessfully = process.kill(this.capabilities['wdio:driverPID'], 'SIGKILL');
|
|
115
|
-
if (!killedSuccessfully) {
|
|
116
|
-
log.warn('Failed to kill driver process, manually clean-up might be required');
|
|
117
|
-
}
|
|
118
|
-
setTimeout(() => {
|
|
119
|
-
/**
|
|
120
|
-
* clear up potential leaked TLS Socket handles
|
|
121
|
-
* see https://github.com/puppeteer/puppeteer/pull/10667
|
|
122
|
-
*/
|
|
123
|
-
for (const handle of process._getActiveHandles()) {
|
|
124
|
-
if (handle.servername && handle.servername.includes('edgedl.me')) {
|
|
125
|
-
handle.destroy();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}, 10);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* clear logger stream if session has been terminated
|
|
132
|
-
*/
|
|
133
|
-
if (!process.env.WDIO_WORKER_ID) {
|
|
134
|
-
logger.clearLogger();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return result.value;
|
|
138
|
-
});
|
|
139
|
-
};
|
|
140
|
-
}
|
package/build/constants.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
export const DEFAULTS = {
|
|
3
|
-
/**
|
|
4
|
-
* protocol of automation driver
|
|
5
|
-
*/
|
|
6
|
-
protocol: {
|
|
7
|
-
type: 'string',
|
|
8
|
-
default: 'http',
|
|
9
|
-
match: /(http|https)/
|
|
10
|
-
},
|
|
11
|
-
/**
|
|
12
|
-
* hostname of automation driver
|
|
13
|
-
*/
|
|
14
|
-
hostname: {
|
|
15
|
-
type: 'string',
|
|
16
|
-
default: 'localhost'
|
|
17
|
-
},
|
|
18
|
-
/**
|
|
19
|
-
* port of automation driver
|
|
20
|
-
*/
|
|
21
|
-
port: {
|
|
22
|
-
type: 'number'
|
|
23
|
-
},
|
|
24
|
-
/**
|
|
25
|
-
* path to WebDriver endpoints
|
|
26
|
-
*/
|
|
27
|
-
path: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
validate: (path) => {
|
|
30
|
-
if (!path.startsWith('/')) {
|
|
31
|
-
throw new TypeError('The option "path" needs to start with a "/"');
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
34
|
-
},
|
|
35
|
-
default: '/'
|
|
36
|
-
},
|
|
37
|
-
/**
|
|
38
|
-
* A key-value store of query parameters to be added to every selenium request
|
|
39
|
-
*/
|
|
40
|
-
queryParams: {
|
|
41
|
-
type: 'object'
|
|
42
|
-
},
|
|
43
|
-
/**
|
|
44
|
-
* cloud user if applicable
|
|
45
|
-
*/
|
|
46
|
-
user: {
|
|
47
|
-
type: 'string'
|
|
48
|
-
},
|
|
49
|
-
/**
|
|
50
|
-
* access key to user
|
|
51
|
-
*/
|
|
52
|
-
key: {
|
|
53
|
-
type: 'string'
|
|
54
|
-
},
|
|
55
|
-
/**
|
|
56
|
-
* capability of WebDriver session
|
|
57
|
-
*/
|
|
58
|
-
capabilities: {
|
|
59
|
-
type: 'object',
|
|
60
|
-
required: true
|
|
61
|
-
},
|
|
62
|
-
/**
|
|
63
|
-
* Level of logging verbosity
|
|
64
|
-
*/
|
|
65
|
-
logLevel: {
|
|
66
|
-
type: 'string',
|
|
67
|
-
default: 'info',
|
|
68
|
-
match: /(trace|debug|info|warn|error|silent)/
|
|
69
|
-
},
|
|
70
|
-
/**
|
|
71
|
-
* directory for log files
|
|
72
|
-
*/
|
|
73
|
-
outputDir: {
|
|
74
|
-
type: 'string'
|
|
75
|
-
},
|
|
76
|
-
/**
|
|
77
|
-
* Timeout for any WebDriver request to a driver or grid
|
|
78
|
-
*/
|
|
79
|
-
connectionRetryTimeout: {
|
|
80
|
-
type: 'number',
|
|
81
|
-
default: 120000
|
|
82
|
-
},
|
|
83
|
-
/**
|
|
84
|
-
* Count of request retries to the Selenium server
|
|
85
|
-
*/
|
|
86
|
-
connectionRetryCount: {
|
|
87
|
-
type: 'number',
|
|
88
|
-
default: 3
|
|
89
|
-
},
|
|
90
|
-
/**
|
|
91
|
-
* Override default agent
|
|
92
|
-
*/
|
|
93
|
-
logLevels: {
|
|
94
|
-
type: 'object'
|
|
95
|
-
},
|
|
96
|
-
/**
|
|
97
|
-
* Pass custom headers
|
|
98
|
-
*/
|
|
99
|
-
headers: {
|
|
100
|
-
type: 'object'
|
|
101
|
-
},
|
|
102
|
-
/**
|
|
103
|
-
* Function transforming the request options before the request is made
|
|
104
|
-
*/
|
|
105
|
-
transformRequest: {
|
|
106
|
-
type: 'function',
|
|
107
|
-
default: (requestOptions) => requestOptions
|
|
108
|
-
},
|
|
109
|
-
/**
|
|
110
|
-
* Function transforming the response object after it is received
|
|
111
|
-
*/
|
|
112
|
-
transformResponse: {
|
|
113
|
-
type: 'function',
|
|
114
|
-
default: (response) => response
|
|
115
|
-
},
|
|
116
|
-
/**
|
|
117
|
-
* Appium direct connect options server (https://appiumpro.com/editions/86-connecting-directly-to-appium-hosts-in-distributed-environments)
|
|
118
|
-
* Whether to allow direct connect caps to adjust endpoint details (Appium only)
|
|
119
|
-
*/
|
|
120
|
-
enableDirectConnect: {
|
|
121
|
-
type: 'boolean',
|
|
122
|
-
default: true
|
|
123
|
-
},
|
|
124
|
-
/**
|
|
125
|
-
* Whether it requires SSL certificates to be valid in HTTP/s requests
|
|
126
|
-
* for an environment which cannot get process environment well.
|
|
127
|
-
*/
|
|
128
|
-
strictSSL: {
|
|
129
|
-
type: 'boolean',
|
|
130
|
-
default: true
|
|
131
|
-
},
|
|
132
|
-
/**
|
|
133
|
-
* The path to the root of the cache directory. This directory is used to store all drivers that are downloaded
|
|
134
|
-
* when attempting to start a session.
|
|
135
|
-
*/
|
|
136
|
-
cacheDir: {
|
|
137
|
-
type: 'string',
|
|
138
|
-
default: process.env.WEBDRIVER_CACHE_DIR || os.tmpdir()
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
export const REG_EXPS = {
|
|
142
|
-
commandName: /.*\/session\/[0-9a-f-]+\/(.*)/,
|
|
143
|
-
execFn: /return \(([\s\S]*)\)\.apply\(null, arguments\)/
|
|
144
|
-
};
|
|
145
|
-
export const ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf';
|
|
146
|
-
export const SHADOW_ELEMENT_KEY = 'shadow-6066-11e4-a52e-4f735466cecf';
|
package/build/request/index.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { WebDriverProtocol } from '@wdio/protocols';
|
|
5
|
-
import { URL } from 'node:url';
|
|
6
|
-
import logger from '@wdio/logger';
|
|
7
|
-
import { transformCommandLogResult } from '@wdio/utils';
|
|
8
|
-
import { isSuccessfulResponse, getErrorFromResponseBody, getTimeoutError } from '../utils.js';
|
|
9
|
-
let pkg = { version: '' };
|
|
10
|
-
if ('process' in globalThis && globalThis.process.versions?.node) {
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
pkg = require('../../package.json');
|
|
13
|
-
}
|
|
14
|
-
const ERRORS_TO_EXCLUDE_FROM_RETRY = [
|
|
15
|
-
'detached shadow root',
|
|
16
|
-
'move target out of bounds'
|
|
17
|
-
];
|
|
18
|
-
export class RequestLibError extends Error {
|
|
19
|
-
statusCode;
|
|
20
|
-
body;
|
|
21
|
-
code;
|
|
22
|
-
}
|
|
23
|
-
export const COMMANDS_WITHOUT_RETRY = [
|
|
24
|
-
findCommandPathByName('performActions'),
|
|
25
|
-
];
|
|
26
|
-
const DEFAULT_HEADERS = {
|
|
27
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
28
|
-
'Connection': 'keep-alive',
|
|
29
|
-
'Accept': 'application/json',
|
|
30
|
-
'User-Agent': 'webdriver/' + pkg.version
|
|
31
|
-
};
|
|
32
|
-
const log = logger('webdriver');
|
|
33
|
-
export default class WebDriverRequest extends EventEmitter {
|
|
34
|
-
body;
|
|
35
|
-
method;
|
|
36
|
-
endpoint;
|
|
37
|
-
isHubCommand;
|
|
38
|
-
requiresSessionId;
|
|
39
|
-
constructor(method, endpoint, body, isHubCommand = false) {
|
|
40
|
-
super();
|
|
41
|
-
this.body = body;
|
|
42
|
-
this.method = method;
|
|
43
|
-
this.endpoint = endpoint;
|
|
44
|
-
this.isHubCommand = isHubCommand;
|
|
45
|
-
this.requiresSessionId = Boolean(this.endpoint.match(/:sessionId/));
|
|
46
|
-
}
|
|
47
|
-
async makeRequest(options, sessionId) {
|
|
48
|
-
const { url, requestOptions } = await this._createOptions(options, sessionId);
|
|
49
|
-
let fullRequestOptions = Object.assign({ method: this.method }, requestOptions);
|
|
50
|
-
if (typeof options.transformRequest === 'function') {
|
|
51
|
-
fullRequestOptions = options.transformRequest(fullRequestOptions);
|
|
52
|
-
}
|
|
53
|
-
this.emit('request', fullRequestOptions);
|
|
54
|
-
return this._request(url, fullRequestOptions, options.transformResponse, options.connectionRetryCount, 0);
|
|
55
|
-
}
|
|
56
|
-
async _createOptions(options, sessionId, isBrowser = false) {
|
|
57
|
-
const controller = new AbortController();
|
|
58
|
-
setTimeout(() => controller.abort(), options.connectionRetryTimeout || 120000);
|
|
59
|
-
const requestOptions = {
|
|
60
|
-
signal: controller.signal
|
|
61
|
-
};
|
|
62
|
-
const requestHeaders = new Headers({
|
|
63
|
-
...DEFAULT_HEADERS,
|
|
64
|
-
...(typeof options.headers === 'object' ? options.headers : {})
|
|
65
|
-
});
|
|
66
|
-
const searchParams = isBrowser ? undefined : (typeof options.queryParams === 'object' ? options.queryParams : undefined);
|
|
67
|
-
/**
|
|
68
|
-
* only apply body property if existing
|
|
69
|
-
*/
|
|
70
|
-
if (this.body && (Object.keys(this.body).length || this.method === 'POST')) {
|
|
71
|
-
const contentLength = Buffer.byteLength(JSON.stringify(this.body), 'utf8');
|
|
72
|
-
requestOptions.body = this.body;
|
|
73
|
-
requestHeaders.set('Content-Length', `${contentLength}`);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* if we don't have a session id we set it here, unless we call commands that don't require session ids, for
|
|
77
|
-
* example /sessions. The call to /sessions is not connected to a session itself and it therefore doesn't
|
|
78
|
-
* require it
|
|
79
|
-
*/
|
|
80
|
-
let endpoint = this.endpoint;
|
|
81
|
-
if (this.requiresSessionId) {
|
|
82
|
-
if (!sessionId) {
|
|
83
|
-
throw new Error('A sessionId is required for this command');
|
|
84
|
-
}
|
|
85
|
-
endpoint = endpoint.replace(':sessionId', sessionId);
|
|
86
|
-
}
|
|
87
|
-
const url = new URL(`${options.protocol}://${options.hostname}:${options.port}${this.isHubCommand ? this.endpoint : path.join(options.path || '', endpoint)}`);
|
|
88
|
-
if (searchParams) {
|
|
89
|
-
url.search = new URLSearchParams(searchParams).toString();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* send authentication credentials only when creating new session
|
|
93
|
-
*/
|
|
94
|
-
if (this.endpoint === '/session' && options.user && options.key) {
|
|
95
|
-
requestHeaders.set('Authorization', 'Basic ' + btoa(options.user + ':' + options.key));
|
|
96
|
-
}
|
|
97
|
-
requestOptions.headers = requestHeaders;
|
|
98
|
-
return { url, requestOptions };
|
|
99
|
-
}
|
|
100
|
-
async _libRequest(url, options) {
|
|
101
|
-
throw new Error('This function must be implemented');
|
|
102
|
-
}
|
|
103
|
-
_libPerformanceNow() {
|
|
104
|
-
throw new Error('This function must be implemented');
|
|
105
|
-
}
|
|
106
|
-
async _request(url, fullRequestOptions, transformResponse, totalRetryCount = 0, retryCount = 0) {
|
|
107
|
-
log.info(`[${fullRequestOptions.method}] ${url.href}`);
|
|
108
|
-
if (fullRequestOptions.body && Object.keys(fullRequestOptions.body).length) {
|
|
109
|
-
log.info('DATA', transformCommandLogResult(fullRequestOptions.body));
|
|
110
|
-
}
|
|
111
|
-
const { ...requestLibOptions } = fullRequestOptions;
|
|
112
|
-
const startTime = this._libPerformanceNow();
|
|
113
|
-
let response = await this._libRequest(url, requestLibOptions)
|
|
114
|
-
.catch((err) => err);
|
|
115
|
-
const durationMillisecond = this._libPerformanceNow() - startTime;
|
|
116
|
-
/**
|
|
117
|
-
* handle retries for requests
|
|
118
|
-
* @param {Error} error error object that causes the retry
|
|
119
|
-
* @param {string} msg message that is being shown as warning to user
|
|
120
|
-
*/
|
|
121
|
-
const retry = (error, msg) => {
|
|
122
|
-
/**
|
|
123
|
-
* stop retrying if totalRetryCount was exceeded or there is no reason to
|
|
124
|
-
* retry, e.g. if sessionId is invalid
|
|
125
|
-
*/
|
|
126
|
-
if (retryCount >= totalRetryCount || error.message.includes('invalid session id')) {
|
|
127
|
-
log.error(`Request failed with status ${response.statusCode} due to ${error}`);
|
|
128
|
-
this.emit('response', { error });
|
|
129
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
++retryCount;
|
|
133
|
-
this.emit('retry', { error, retryCount });
|
|
134
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
|
|
135
|
-
log.warn(msg);
|
|
136
|
-
log.info(`Retrying ${retryCount}/${totalRetryCount}`);
|
|
137
|
-
return this._request(url, fullRequestOptions, transformResponse, totalRetryCount, retryCount);
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* handle request errors
|
|
141
|
-
*/
|
|
142
|
-
if (response instanceof Error) {
|
|
143
|
-
/**
|
|
144
|
-
* handle timeouts
|
|
145
|
-
*/
|
|
146
|
-
if (response.code === 'ETIMEDOUT') {
|
|
147
|
-
const error = getTimeoutError(response, fullRequestOptions, url);
|
|
148
|
-
return retry(error, 'Request timed out! Consider increasing the "connectionRetryTimeout" option.');
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* throw if request error is unknown
|
|
152
|
-
*/
|
|
153
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error: response, retryCount });
|
|
154
|
-
throw response;
|
|
155
|
-
}
|
|
156
|
-
if (typeof transformResponse === 'function') {
|
|
157
|
-
response = transformResponse(response, fullRequestOptions);
|
|
158
|
-
}
|
|
159
|
-
const error = getErrorFromResponseBody(response.body, fullRequestOptions.body);
|
|
160
|
-
/**
|
|
161
|
-
* retry connection refused errors
|
|
162
|
-
*/
|
|
163
|
-
if (error.message === 'java.net.ConnectException: Connection refused: connect') {
|
|
164
|
-
return retry(error, 'Connection to Selenium Standalone server was refused.');
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* hub commands don't follow standard response formats
|
|
168
|
-
* and can have empty bodies
|
|
169
|
-
*/
|
|
170
|
-
if (this.isHubCommand) {
|
|
171
|
-
/**
|
|
172
|
-
* if body contains HTML the command was called on a node
|
|
173
|
-
* directly without using a hub, therefore throw
|
|
174
|
-
*/
|
|
175
|
-
if (typeof response.body === 'string' && response.body.startsWith('<!DOCTYPE html>')) {
|
|
176
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
|
|
177
|
-
return Promise.reject(new Error('Command can only be called to a Selenium Hub'));
|
|
178
|
-
}
|
|
179
|
-
return { value: response.body || null };
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Resolve only if successful response
|
|
183
|
-
*/
|
|
184
|
-
if (isSuccessfulResponse(response.statusCode, response.body)) {
|
|
185
|
-
this.emit('response', { result: response.body });
|
|
186
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: true, retryCount });
|
|
187
|
-
return response.body;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* stop retrying as this will never be successful.
|
|
191
|
-
* we will handle this at the elementErrorHandler
|
|
192
|
-
*/
|
|
193
|
-
if (error.name === 'stale element reference') {
|
|
194
|
-
log.warn('Request encountered a stale element - terminating request');
|
|
195
|
-
this.emit('response', { error });
|
|
196
|
-
this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* some errors can be excluded from the request retry mechanism as
|
|
201
|
-
* it likely does not changes anything and the error is handled within the command.
|
|
202
|
-
*/
|
|
203
|
-
if (ERRORS_TO_EXCLUDE_FROM_RETRY.includes(error.name)) {
|
|
204
|
-
throw error;
|
|
205
|
-
}
|
|
206
|
-
return retry(error, `Request failed with status ${response.statusCode} due to ${error.message}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function findCommandPathByName(commandName) {
|
|
210
|
-
const command = Object.entries(WebDriverProtocol).find(([, command]) => Object.values(command).find((cmd) => cmd.command === commandName));
|
|
211
|
-
if (!command) {
|
|
212
|
-
throw new Error(`Couldn't find command "${commandName}"`);
|
|
213
|
-
}
|
|
214
|
-
return command[0];
|
|
215
|
-
}
|
package/build/request/request.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { performance } from 'node:perf_hooks';
|
|
2
|
-
import dns from 'node:dns';
|
|
3
|
-
import WebDriverRequest, { RequestLibError } from './index.js';
|
|
4
|
-
// As per this https://github.com/node-fetch/node-fetch/issues/1624#issuecomment-1407717012 we are setting ipv4first as default IP resolver.
|
|
5
|
-
// This can be removed when we drop Node18 support.
|
|
6
|
-
if ('process' in globalThis && globalThis.process.versions?.node) {
|
|
7
|
-
dns.setDefaultResultOrder('ipv4first');
|
|
8
|
-
}
|
|
9
|
-
export default class FetchRequest extends WebDriverRequest {
|
|
10
|
-
constructor(method, endpoint, body, isHubCommand = false) {
|
|
11
|
-
super(method, endpoint, body, isHubCommand);
|
|
12
|
-
}
|
|
13
|
-
async _libRequest(url, opts) {
|
|
14
|
-
try {
|
|
15
|
-
const response = await fetch(url, {
|
|
16
|
-
method: opts.method,
|
|
17
|
-
body: JSON.stringify(opts.body),
|
|
18
|
-
headers: opts.headers,
|
|
19
|
-
signal: opts.signal,
|
|
20
|
-
});
|
|
21
|
-
// Cloning the response to prevent body unusable error
|
|
22
|
-
const resp = response.clone();
|
|
23
|
-
return {
|
|
24
|
-
statusCode: resp.status,
|
|
25
|
-
body: await resp.json() ?? {},
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
if (!(err instanceof Error)) {
|
|
30
|
-
throw new RequestLibError(err.message || err);
|
|
31
|
-
}
|
|
32
|
-
throw err;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
_libPerformanceNow() {
|
|
36
|
-
return performance.now();
|
|
37
|
-
}
|
|
38
|
-
}
|
package/build/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|