UNPKG

10.9 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.getBrowserObject = getBrowserObject;
7exports.transformToCharString = transformToCharString;
8exports.parseCSS = parseCSS;
9exports.checkUnicode = checkUnicode;
10exports.findElement = findElement;
11exports.findElements = findElements;
12exports.verifyArgsAndStripIfElement = verifyArgsAndStripIfElement;
13exports.getElementRect = getElementRect;
14exports.getAbsoluteFilepath = getAbsoluteFilepath;
15exports.assertDirectoryExists = assertDirectoryExists;
16exports.validateUrl = validateUrl;
17exports.getScrollPosition = getScrollPosition;
18exports.hasElementId = hasElementId;
19exports.addLocatorStrategyHandler = addLocatorStrategyHandler;
20exports.getAutomationProtocol = exports.isStub = exports.enhanceElementsArray = exports.getElementFromResponse = exports.getPrototype = void 0;
21
22var _fs = _interopRequireDefault(require("fs"));
23
24var _http = _interopRequireDefault(require("http"));
25
26var _path = _interopRequireDefault(require("path"));
27
28var _cssValue = _interopRequireDefault(require("css-value"));
29
30var _rgb2hex = _interopRequireDefault(require("rgb2hex"));
31
32var _graphemeSplitter = _interopRequireDefault(require("grapheme-splitter"));
33
34var _logger = _interopRequireDefault(require("@wdio/logger"));
35
36var _lodash = _interopRequireDefault(require("lodash.isobject"));
37
38var _url = require("url");
39
40var _devtools = require("devtools");
41
42var _lodash2 = _interopRequireDefault(require("lodash.isplainobject"));
43
44var _constants = require("./constants");
45
46var _findStrategy = require("./utils/findStrategy");
47
48function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
49
50const browserCommands = require('./commands/browser');
51
52const elementCommands = require('./commands/element');
53
54const log = (0, _logger.default)('webdriverio');
55const INVALID_SELECTOR_ERROR = 'selector needs to be typeof `string` or `function`';
56const scopes = {
57 browser: browserCommands,
58 element: elementCommands
59};
60
61const applyScopePrototype = (prototype, scope) => {
62 Object.entries(scopes[scope]).forEach(([commandName, command]) => {
63 prototype[commandName] = {
64 value: command
65 };
66 });
67};
68
69const getPrototype = scope => {
70 const prototype = {};
71 applyScopePrototype(prototype, scope);
72 prototype.strategies = {
73 value: new Map()
74 };
75 return prototype;
76};
77
78exports.getPrototype = getPrototype;
79
80const getElementFromResponse = res => {
81 if (!res) {
82 return null;
83 }
84
85 if (res.ELEMENT) {
86 return res.ELEMENT;
87 }
88
89 if (res[_constants.ELEMENT_KEY]) {
90 return res[_constants.ELEMENT_KEY];
91 }
92
93 return null;
94};
95
96exports.getElementFromResponse = getElementFromResponse;
97
98function getBrowserObject(elem) {
99 return elem.parent ? getBrowserObject(elem.parent) : elem;
100}
101
102function transformToCharString(value) {
103 const ret = [];
104
105 if (!Array.isArray(value)) {
106 value = [value];
107 }
108
109 for (const val of value) {
110 if (typeof val === 'string') {
111 ret.push(...checkUnicode(val));
112 } else if (typeof val === 'number') {
113 const entry = `${val}`.split('');
114 ret.push(...entry);
115 } else if (val && typeof val === 'object') {
116 try {
117 ret.push(...JSON.stringify(val).split(''));
118 } catch (e) {}
119 } else if (typeof val === 'boolean') {
120 const entry = val ? 'true'.split('') : 'false'.split('');
121 ret.push(...entry);
122 }
123 }
124
125 return ret;
126}
127
128function sanitizeCSS(value) {
129 if (!value) {
130 return value;
131 }
132
133 return value.trim().replace(/'/g, '').replace(/"/g, '').toLowerCase();
134}
135
136function parseCSS(cssPropertyValue, cssProperty) {
137 if (!cssPropertyValue) {
138 return null;
139 }
140
141 let parsedValue = {
142 property: cssProperty,
143 value: cssPropertyValue.toLowerCase().trim()
144 };
145
146 if (parsedValue.value.indexOf('rgb') === 0) {
147 parsedValue.value = parsedValue.value.replace(/\s/g, '');
148 let color = parsedValue.value;
149 parsedValue.parsed = (0, _rgb2hex.default)(parsedValue.value);
150 parsedValue.parsed.type = 'color';
151 parsedValue.parsed[/[rgba]+/g.exec(color)[0]] = color;
152 } else if (parsedValue.property === 'font-family') {
153 let font = (0, _cssValue.default)(cssPropertyValue);
154 let string = parsedValue.value;
155 let value = cssPropertyValue.split(/,/).map(sanitizeCSS);
156 parsedValue.value = sanitizeCSS(font[0].value || font[0].string);
157 parsedValue.parsed = {
158 value,
159 type: 'font',
160 string
161 };
162 } else {
163 try {
164 parsedValue.parsed = (0, _cssValue.default)(cssPropertyValue);
165
166 if (parsedValue.parsed.length === 1) {
167 parsedValue.parsed = parsedValue.parsed[0];
168 }
169
170 if (parsedValue.parsed.type && parsedValue.parsed.type === 'number' && parsedValue.parsed.unit === '') {
171 parsedValue.value = parsedValue.parsed.value;
172 }
173 } catch (e) {}
174 }
175
176 return parsedValue;
177}
178
179function checkUnicode(value, isDevTools) {
180 return Object.prototype.hasOwnProperty.call(_constants.UNICODE_CHARACTERS, value) ? isDevTools ? [value] : [_constants.UNICODE_CHARACTERS[value]] : new _graphemeSplitter.default().splitGraphemes(value);
181}
182
183function fetchElementByJSFunction(selector, scope) {
184 if (!scope.elementId) {
185 return scope.execute(selector);
186 }
187
188 const script = function (elem) {
189 return selector.call(elem);
190 }.toString().replace('selector', `(${selector.toString()})`);
191
192 return getBrowserObject(scope).execute(`return (${script}).apply(null, arguments)`, scope);
193}
194
195async function findElement(selector) {
196 if (typeof selector === 'string' || (0, _lodash2.default)(selector)) {
197 const {
198 using,
199 value
200 } = (0, _findStrategy.findStrategy)(selector, this.isW3C, this.isMobile);
201 return this.elementId ? this.findElementFromElement(this.elementId, using, value) : this.findElement(using, value);
202 }
203
204 if (typeof selector === 'function') {
205 const notFoundError = new Error(`Function selector "${selector.toString()}" did not return an HTMLElement`);
206 let elem = await fetchElementByJSFunction(selector, this);
207 elem = Array.isArray(elem) ? elem[0] : elem;
208 return getElementFromResponse(elem) ? elem : notFoundError;
209 }
210
211 throw new Error(INVALID_SELECTOR_ERROR);
212}
213
214async function findElements(selector) {
215 if (typeof selector === 'string' || (0, _lodash2.default)(selector)) {
216 const {
217 using,
218 value
219 } = (0, _findStrategy.findStrategy)(selector, this.isW3C, this.isMobile);
220 return this.elementId ? this.findElementsFromElement(this.elementId, using, value) : this.findElements(using, value);
221 }
222
223 if (typeof selector === 'function') {
224 let elems = await fetchElementByJSFunction(selector, this);
225 elems = Array.isArray(elems) ? elems : [elems];
226 return elems.filter(elem => elem && getElementFromResponse(elem));
227 }
228
229 throw new Error(INVALID_SELECTOR_ERROR);
230}
231
232function verifyArgsAndStripIfElement(args) {
233 function verify(arg) {
234 if ((0, _lodash.default)(arg) && arg.constructor.name === 'Element') {
235 if (!arg.elementId) {
236 throw new Error(`The element with selector "${arg.selector}" you trying to pass into the execute method wasn't found`);
237 }
238
239 return {
240 [_constants.ELEMENT_KEY]: arg.elementId,
241 ELEMENT: arg.elementId
242 };
243 }
244
245 return arg;
246 }
247
248 return !Array.isArray(args) ? verify(args) : args.map(verify);
249}
250
251async function getElementRect(scope) {
252 const rect = await scope.getElementRect(scope.elementId);
253 let defaults = {
254 x: 0,
255 y: 0,
256 width: 0,
257 height: 0
258 };
259
260 if (Object.keys(defaults).some(key => rect[key] == null)) {
261 const rectJs = await getBrowserObject(scope).execute(function (el) {
262 if (!el || !el.getBoundingClientRect) {
263 return;
264 }
265
266 const {
267 left,
268 top,
269 width,
270 height
271 } = el.getBoundingClientRect();
272 return {
273 x: left + this.scrollX,
274 y: top + this.scrollY,
275 width,
276 height
277 };
278 }, scope);
279 Object.keys(defaults).forEach(key => {
280 if (rect[key] != null) {
281 return;
282 }
283
284 if (typeof rectJs[key] === 'number') {
285 rect[key] = Math.floor(rectJs[key]);
286 } else {
287 log.error('getElementRect', {
288 rect,
289 rectJs,
290 key
291 });
292 throw new Error('Failed to receive element rects via execute command');
293 }
294 });
295 }
296
297 return rect;
298}
299
300function getAbsoluteFilepath(filepath) {
301 return filepath.startsWith('/') || filepath.startsWith('\\') || filepath.match(/^[a-zA-Z]:\\/) ? filepath : _path.default.join(process.cwd(), filepath);
302}
303
304function assertDirectoryExists(filepath) {
305 if (!_fs.default.existsSync(_path.default.dirname(filepath))) {
306 throw new Error(`directory (${_path.default.dirname(filepath)}) doesn't exist`);
307 }
308}
309
310function validateUrl(url, origError) {
311 try {
312 const urlObject = new _url.URL(url);
313 return urlObject.href;
314 } catch (e) {
315 if (origError) {
316 throw origError;
317 }
318
319 return validateUrl(`http://${url}`, e);
320 }
321}
322
323function getScrollPosition(scope) {
324 return getBrowserObject(scope).execute('return { scrollX: this.pageXOffset, scrollY: this.pageYOffset };');
325}
326
327async function hasElementId(element) {
328 if (!element.elementId) {
329 const method = element.isReactElement ? 'react$' : '$';
330 element.elementId = (await element.parent[method](element.selector)).elementId;
331 }
332
333 if (!element.elementId) {
334 return false;
335 }
336
337 return true;
338}
339
340function addLocatorStrategyHandler(scope) {
341 return (name, script) => {
342 if (scope.strategies.get(name)) {
343 throw new Error(`Strategy ${name} already exists`);
344 }
345
346 scope.strategies.set(name, script);
347 };
348}
349
350const enhanceElementsArray = (elements, parent, selector, foundWith = '$$', props = []) => {
351 elements.parent = parent;
352 elements.selector = selector;
353 elements.foundWith = foundWith;
354 elements.props = props;
355 return elements;
356};
357
358exports.enhanceElementsArray = enhanceElementsArray;
359
360const isStub = automationProtocol => automationProtocol === './protocol-stub';
361
362exports.isStub = isStub;
363
364const getAutomationProtocol = async config => {
365 if (config.automationProtocol) {
366 return config.automationProtocol;
367 }
368
369 if (config.hostname || config.port || config.path || config.user && config.key) {
370 return 'webdriver';
371 }
372
373 if (config.capabilities && typeof config.capabilities.browserName === 'string' && !_devtools.SUPPORTED_BROWSER.includes(config.capabilities.browserName.toLowerCase())) {
374 return 'webdriver';
375 }
376
377 const driverEndpointHeaders = await new Promise(resolve => {
378 const req = _http.default.request(_constants.DRIVER_DEFAULT_ENDPOINT, resolve);
379
380 req.on('error', error => resolve({
381 error
382 }));
383 req.end();
384 });
385
386 if (driverEndpointHeaders && parseInt(driverEndpointHeaders.statusCode, 10) === 200) {
387 return 'webdriver';
388 }
389
390 return 'devtools';
391};
392
393exports.getAutomationProtocol = getAutomationProtocol;
\No newline at end of file