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.
Files changed (43) hide show
  1. package/README.md +3 -3
  2. package/build/bidi/core.d.ts +6 -1
  3. package/build/bidi/core.d.ts.map +1 -1
  4. package/build/bidi/handler.d.ts +49 -0
  5. package/build/bidi/handler.d.ts.map +1 -1
  6. package/build/bidi/localTypes.d.ts +134 -47
  7. package/build/bidi/localTypes.d.ts.map +1 -1
  8. package/build/bidi/remoteTypes.d.ts +135 -28
  9. package/build/bidi/remoteTypes.d.ts.map +1 -1
  10. package/build/command.d.ts.map +1 -1
  11. package/build/constants.d.ts +2 -1
  12. package/build/constants.d.ts.map +1 -1
  13. package/build/index.cjs +233 -0
  14. package/build/index.d.cts +2 -0
  15. package/build/index.d.cts.map +1 -0
  16. package/build/index.d.ts +2 -2
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +1615 -141
  19. package/build/request/index.d.ts +1 -2
  20. package/build/request/index.d.ts.map +1 -1
  21. package/build/request/request.d.ts +0 -1
  22. package/build/request/request.d.ts.map +1 -1
  23. package/build/types.d.ts +9 -7
  24. package/build/types.d.ts.map +1 -1
  25. package/build/utils.d.ts +4 -6
  26. package/build/utils.d.ts.map +1 -1
  27. package/package.json +15 -18
  28. package/build/bidi/core.js +0 -73
  29. package/build/bidi/handler.js +0 -446
  30. package/build/bidi/localTypes.js +0 -15
  31. package/build/bidi/remoteTypes.js +0 -15
  32. package/build/bidi/socket.js +0 -49
  33. package/build/cjs/index.d.ts +0 -2
  34. package/build/cjs/index.d.ts.map +0 -1
  35. package/build/cjs/index.js +0 -40
  36. package/build/cjs/package.json +0 -5
  37. package/build/command.js +0 -140
  38. package/build/constants.js +0 -146
  39. package/build/request/index.js +0 -215
  40. package/build/request/request.js +0 -38
  41. package/build/types.js +0 -1
  42. package/build/utils.js +0 -384
  43. /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
- }
@@ -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';
@@ -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
- }
@@ -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 {};