@appium/base-driver 10.2.1 → 10.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.
@@ -1,317 +0,0 @@
1
- import _ from 'lodash';
2
- import {logger, util} from '@appium/support';
3
- import {duplicateKeys} from '../basedriver/helpers';
4
- import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
5
-
6
- export const COMMAND_URLS_CONFLICTS = [
7
- {
8
- commandNames: ['execute', 'executeAsync'],
9
- jsonwpConverter: (url) =>
10
- url.replace(/\/execute.*/, url.includes('async') ? '/execute_async' : '/execute'),
11
- w3cConverter: (url) =>
12
- url.replace(/\/execute.*/, url.includes('async') ? '/execute/async' : '/execute/sync'),
13
- },
14
- {
15
- commandNames: ['getElementScreenshot'],
16
- jsonwpConverter: (url) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
17
- w3cConverter: (url) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
18
- },
19
- {
20
- commandNames: ['getWindowHandles', 'getWindowHandle'],
21
- jsonwpConverter(url) {
22
- return url.endsWith('/window')
23
- ? url.replace(/\/window$/, '/window_handle')
24
- : url.replace(/\/window\/handle(s?)$/, '/window_handle$1');
25
- },
26
- w3cConverter(url) {
27
- return url.endsWith('/window_handle')
28
- ? url.replace(/\/window_handle$/, '/window')
29
- : url.replace(/\/window_handles$/, '/window/handles');
30
- },
31
- },
32
- {
33
- commandNames: ['getProperty'],
34
- jsonwpConverter: (w3cUrl) => {
35
- const w3cPropertyRegex = /\/element\/([^/]+)\/property\/([^/]+)/;
36
- const jsonwpUrl = w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2');
37
- return jsonwpUrl;
38
- },
39
- w3cConverter: (jsonwpUrl) => jsonwpUrl, // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property
40
- },
41
- ];
42
- const {MJSONWP, W3C} = PROTOCOLS;
43
- const DEFAULT_LOG = logger.getLogger('Protocol Converter');
44
-
45
- class ProtocolConverter {
46
- /**
47
- *
48
- * @param {ProxyFunction} proxyFunc
49
- * @param {import('@appium/types').AppiumLogger | null} [log=null]
50
- */
51
- constructor(proxyFunc, log = null) {
52
- this.proxyFunc = proxyFunc;
53
- this._downstreamProtocol = null;
54
- this._log = log;
55
- }
56
-
57
- get log() {
58
- return this._log ?? DEFAULT_LOG;
59
- }
60
-
61
- set downstreamProtocol(value) {
62
- this._downstreamProtocol = value;
63
- }
64
-
65
- get downstreamProtocol() {
66
- return this._downstreamProtocol;
67
- }
68
-
69
- /**
70
- * W3C /timeouts can take as many as 3 timeout types at once, MJSONWP /timeouts only takes one
71
- * at a time. So if we're using W3C and proxying to MJSONWP and there's more than one timeout type
72
- * provided in the request, we need to do 3 proxies and combine the result
73
- *
74
- * @param {Object} body Request body
75
- * @return {Object[]} Array of W3C + MJSONWP compatible timeout objects
76
- */
77
- getTimeoutRequestObjects(body) {
78
- if (this.downstreamProtocol === W3C && _.has(body, 'ms') && _.has(body, 'type')) {
79
- const typeToW3C = (x) => (x === 'page load' ? 'pageLoad' : x);
80
- return [
81
- {
82
- [typeToW3C(body.type)]: body.ms,
83
- },
84
- ];
85
- }
86
-
87
- if (this.downstreamProtocol === MJSONWP && (!_.has(body, 'ms') || !_.has(body, 'type'))) {
88
- const typeToJSONWP = (x) => (x === 'pageLoad' ? 'page load' : x);
89
- return (
90
- _.toPairs(body)
91
- // Only transform the entry if ms value is a valid positive float number
92
- .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
93
- .map(function (pair) {
94
- return {
95
- type: typeToJSONWP(pair[0]),
96
- ms: pair[1],
97
- };
98
- })
99
- );
100
- }
101
-
102
- return [body];
103
- }
104
-
105
- /**
106
- * Proxy an array of timeout objects and merge the result
107
- * @param {string} url Endpoint url
108
- * @param {string} method Endpoint method
109
- * @param {import('@appium/types').HTTPBody} body Request body
110
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
111
- */
112
- async proxySetTimeouts(url, method, body) {
113
- let response, resBody;
114
-
115
- const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
116
- this.log.debug(
117
- `Will send the following request bodies to /timeouts: ${JSON.stringify(
118
- timeoutRequestObjects
119
- )}`
120
- );
121
- for (const timeoutObj of timeoutRequestObjects) {
122
- [response, resBody] = await this.proxyFunc(url, method, timeoutObj);
123
-
124
- // If we got a non-MJSONWP response, return the result, nothing left to do
125
- if (this.downstreamProtocol !== MJSONWP) {
126
- return [response, resBody];
127
- }
128
-
129
- // If we got an error, return the error right away
130
- if (response.statusCode >= 400) {
131
- return [response, resBody];
132
- }
133
-
134
- // ...Otherwise, continue to the next timeouts call
135
- }
136
- return [/** @type {import('@appium/types').ProxyResponse} */(response), resBody];
137
- }
138
-
139
- /**
140
- *
141
- * @param {string} url
142
- * @param {string} method
143
- * @param {import('@appium/types').HTTPBody} body
144
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
145
- */
146
- async proxySetWindow(url, method, body) {
147
- const bodyObj = util.safeJsonParse(body);
148
- if (_.isPlainObject(bodyObj)) {
149
- if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
150
- this.log.debug(
151
- `Copied 'name' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).name}' to 'handle' as per W3C spec`
152
- );
153
- return await this.proxyFunc(url, method, {
154
- .../** @type {import('@appium/types').StringRecord} */ (bodyObj),
155
- handle: /** @type {import('@appium/types').StringRecord} */ (bodyObj).name,
156
- });
157
- }
158
- if (
159
- this.downstreamProtocol === MJSONWP &&
160
- _.has(bodyObj, 'handle') &&
161
- !_.has(bodyObj, 'name')
162
- ) {
163
- this.log.debug(
164
- `Copied 'handle' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).handle}' to 'name' as per JSONWP spec`
165
- );
166
- return await this.proxyFunc(url, method, {
167
- .../** @type {import('@appium/types').StringRecord} */ (bodyObj),
168
- name: /** @type {import('@appium/types').StringRecord} */ (bodyObj).handle,
169
- });
170
- }
171
- }
172
-
173
- return await this.proxyFunc(url, method, body);
174
- }
175
-
176
- /**
177
- *
178
- * @param {string} url
179
- * @param {string} method
180
- * @param {import('@appium/types').HTTPBody} body
181
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
182
- */
183
- async proxySetValue(url, method, body) {
184
- const bodyObj = util.safeJsonParse(body);
185
- if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj.text) || util.hasValue(bodyObj.value))) {
186
- let {text, value} = bodyObj;
187
- if (util.hasValue(text) && !util.hasValue(value)) {
188
- value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
189
- this.log.debug(`Added 'value' property to 'setValue' request body`);
190
- } else if (!util.hasValue(text) && util.hasValue(value)) {
191
- text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
192
- this.log.debug(`Added 'text' property to 'setValue' request body`);
193
- }
194
- return await this.proxyFunc(
195
- url,
196
- method,
197
- {
198
- ...bodyObj,
199
- text,
200
- value,
201
- }
202
- );
203
- }
204
-
205
- return await this.proxyFunc(url, method, body);
206
- }
207
-
208
- /**
209
- *
210
- * @param {string} url
211
- * @param {string} method
212
- * @param {import('@appium/types').HTTPBody} body
213
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
214
- */
215
- async proxySetFrame(url, method, body) {
216
- const bodyObj = util.safeJsonParse(body);
217
- return _.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id)
218
- ? await this.proxyFunc(url, method, {
219
- ...bodyObj,
220
- id: duplicateKeys(bodyObj.id, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
221
- })
222
- : await this.proxyFunc(url, method, body);
223
- }
224
-
225
- /**
226
- *
227
- * @param {string} url
228
- * @param {string} method
229
- * @param {import('@appium/types').HTTPBody} body
230
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
231
- */
232
- async proxyPerformActions(url, method, body) {
233
- const bodyObj = util.safeJsonParse(body);
234
- return _.isPlainObject(bodyObj)
235
- ? await this.proxyFunc(
236
- url,
237
- method,
238
- duplicateKeys(bodyObj, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
239
- )
240
- : await this.proxyFunc(url, method, body);
241
- }
242
-
243
- /**
244
- *
245
- * @param {string} url
246
- * @param {string} method
247
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
248
- */
249
- async proxyReleaseActions(url, method) {
250
- return await this.proxyFunc(url, method);
251
- }
252
-
253
- /**
254
- * Handle "crossing" endpoints for the case
255
- * when upstream and downstream drivers operate different protocols
256
- *
257
- * @param {string} commandName
258
- * @param {string} url
259
- * @param {string} method
260
- * @param {import('@appium/types').HTTPBody} [body]
261
- * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
262
- */
263
- async convertAndProxy(commandName, url, method, body) {
264
- if (!this.downstreamProtocol) {
265
- return await this.proxyFunc(url, method, body);
266
- }
267
-
268
- // Same url, but different arguments
269
- switch (commandName) {
270
- case 'timeouts':
271
- return await this.proxySetTimeouts(url, method, body);
272
- case 'setWindow':
273
- return await this.proxySetWindow(url, method, body);
274
- case 'setValue':
275
- return await this.proxySetValue(url, method, body);
276
- case 'performActions':
277
- return await this.proxyPerformActions(url, method, body);
278
- case 'releaseActions':
279
- return await this.proxyReleaseActions(url, method);
280
- case 'setFrame':
281
- return await this.proxySetFrame(url, method, body);
282
- default:
283
- break;
284
- }
285
-
286
- // Same arguments, but different URLs
287
- for (const {commandNames, jsonwpConverter, w3cConverter} of COMMAND_URLS_CONFLICTS) {
288
- if (!commandNames.includes(commandName)) {
289
- continue;
290
- }
291
-
292
- const rewrittenUrl =
293
- this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
294
- if (rewrittenUrl === url) {
295
- this.log.debug(
296
- `Did not know how to rewrite the original URL '${url}' ` +
297
- `for ${this.downstreamProtocol} protocol`
298
- );
299
- break;
300
- }
301
- this.log.info(
302
- `Rewrote the original URL '${url}' to '${rewrittenUrl}' ` +
303
- `for ${this.downstreamProtocol} protocol`
304
- );
305
- return await this.proxyFunc(rewrittenUrl, method, body);
306
- }
307
-
308
- // No matches found. Proceed normally
309
- return await this.proxyFunc(url, method, body);
310
- }
311
- }
312
-
313
- export default ProtocolConverter;
314
-
315
- /**
316
- * @typedef {(url: string, method: string, body?: import('@appium/types').HTTPBody) => Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} ProxyFunction
317
- */