UNPKG

@applitools/driver

Version:

Applitools universal framework wrapper

394 lines (393 loc) 16.1 kB
"use strict"; // @ts-nocheck Object.defineProperty(exports, "__esModule", { value: true }); exports.MockDriver = void 0; const { inspect } = require('util'); const utils = require('@applitools/utils'); const snippets = require('@applitools/snippets'); const WELL_KNOWN_SCRIPTS = { 'dom-snapshot': script => /\/\* @applitools\/dom-snapshot@[\d.]+ \*\//.test(script), 'dom-capture': script => /\/\* @applitools\/dom-capture@[\d.]+ \*\//.test(script), }; const DEFAULT_DESKTOP_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'; const DEFAULT_MOBILE_UA = 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Mobile Safari/537.36'; const DEFAULT_STYLES = { 'border-left-width': '0px', 'border-top-width': '0px', overflow: null, }; const DEFAULT_PROPS = { clientWidth: 300, clientHeight: 400, overflow: null, }; class MockDriver { constructor(options = {}) { const { viewport = { width: 1000, height: 1000 }, device, platform, browser, ua, driverServerUrl, capabilities, sessionId, } = options; this._device = device; this._platform = platform; this._browser = browser; this._ua = ua; this._window = { title: 'Default Page Title', url: 'http://default.url', rect: { x: 0, y: 0, ...viewport }, }; this._driverServerUrl = driverServerUrl; this._capabilities = capabilities; this._sessionId = sessionId; this._methods = new Map(); this._scripts = new Map(); this._elements = new Map(); this._contexts = new Map(); this._contexts.set(null, { document: { id: Symbol('documentId') }, state: { get name() { return null; }, }, }); this._contextId = null; this.mockElement('html', { scrollPosition: { x: 0, y: 0 } }); this.mockScript(snippets.getContextInfo, async () => { const context = this._contexts.get(this._contextId); const isRoot = !this._contextId; const isCORS = !isRoot && context.isCORS; const contentDocument = await this.findElement('html'); const selector = !isCORS && !isRoot ? context.element.selector : null; return [contentDocument, selector, isRoot, isCORS]; }); this.mockScript(snippets.isEqualElements, async ([element1, element2]) => { return element1.id === element2.id; }); this.mockScript(snippets.getDocumentScrollingElement, async () => { return 'html'; }); this.mockScript(snippets.getChildFramesInfo, () => { return Array.from(this._contexts.values()) .filter(frame => frame.parentId === this._contextId) .map(frame => [frame.element, frame.isCORS]); }); this.mockScript(snippets.getElementRect, ([element]) => { return element.rect || { x: 0, y: 0, width: 100, height: 100 }; }); this.mockScript(snippets.getElementComputedStyleProperties, ([element, properties]) => { return properties.map(property => (element.styles || {})[property] || DEFAULT_STYLES[property]); }); this.mockScript(snippets.getElementProperties, ([element, properties]) => { return properties.map(property => (element.props || {})[property] || DEFAULT_PROPS[property]); }); this.mockScript(snippets.setElementStyleProperties, ([element, properties]) => { return Object.entries(properties).reduce((original, [name, value]) => { original[name] = { value: element.style[name], important: false }; element.style[name] = value; return original; }, {}); }); this.mockScript(snippets.setElementAttributes, ([element, attributes]) => { return Object.entries(attributes).reduce((original, [name, value]) => { original[name] = element.attrs[name]; element.attrs[name] = value; return original; }, {}); }); this.mockScript(snippets.scrollTo, ([element, offset]) => { let scrollingElement = element; if (!element) { scrollingElement = this.findElement('html'); } scrollingElement.scrollPosition = offset; return [scrollingElement.scrollPosition.x, scrollingElement.scrollPosition.y]; }); this.mockScript(snippets.getElementScrollOffset, ([element]) => { let scrollingElement = element; if (!element) { scrollingElement = this.findElement('html'); } if (!scrollingElement.scrollPosition) { scrollingElement.scrollPosition = { x: 0, y: 0 }; } return { x: scrollingElement.scrollPosition.x, y: scrollingElement.scrollPosition.y }; }); this.mockScript(snippets.getElementInnerOffset, ([element]) => { let scrollingElement = element; if (!element) { scrollingElement = this.findElement('html'); } if (!scrollingElement.scrollPosition) { scrollingElement.scrollPosition = { x: 0, y: 0 }; } return { x: scrollingElement.scrollPosition.x, y: scrollingElement.scrollPosition.y }; }); this.mockScript(snippets.getShadowRoot, ([element]) => { return element; }); this.mockScript(snippets.getUserAgent, () => { return JSON.stringify({ status: 'SUCCESS', value: this._ua !== undefined ? this._ua : this.environment.isMobile ? DEFAULT_MOBILE_UA : DEFAULT_DESKTOP_UA, }); }); this.mockScript(snippets.getViewport, () => { return { viewportSize: { width: this._window.rect.width, height: this._window.rect.height }, pixelRatio: 1 }; }); this.mockScript(snippets.getElementXpath, ([element]) => { if (element.xpath) return element.xpath; const elements = Array.from(this._elements.values()).reduce((elements, array) => elements.concat(array), []); const index = elements.findIndex(({ id }) => id === element.id); return index >= 0 ? `/HTML[1]/BODY[1]/DIV[${index + 1}]` : `//[data-fake-selector="${element.selector}"]`; }); this.mockScript(snippets.blurElement, () => { return null; }); this.mockScript(snippets.addElementIds, elementsWithIds => { return elementsWithIds.map(([elements, id]) => elements.map(element => { element.attributes = element.attributes || []; element.attributes.push({ name: 'data-applitools-selector', value: id }); return [`[data-applitools-selector~="${id}"]`]; // no shadow is mocked here })); }); this.mockScript(snippets.cleanupElementIds, ([elements]) => { for (const el of elements) { el.attributes.splice(el.attributes.findIndex(({ name }) => name === 'data-applitools-selector'), 1); } }); this.mockScript(snippets.getElementContentSize, ([element]) => { return element.rect || { x: 0, y: 0, width: 100, height: 100 }; }); this.mockScript(snippets.getDocumentSize, () => { // TODO get window for context: `this.contexts.get(this._contextId)` return { width: this._window.rect.width, height: this._window.rect.height }; }); this.mockScript('mobile:deviceScreenInfo', () => { return { scale: 1, statusBarSize: { height: 1 } }; }); } mockScript(scriptMatcher, resultGenerator) { this._scripts.set(String(scriptMatcher), resultGenerator); } mockElement(selector, state) { const element = { id: Symbol('elementId' + Math.floor(Math.random() * 100)), attrs: {}, style: {}, selector, parentId: null, parentContextId: null, parentRootId: null, ...state, }; if (element.shadow) { element.shadowRootId = Symbol('shadowId' + (element.name || Math.floor(Math.random() * 100))); } if (element.frame) { const contextId = Symbol('contextId' + (element.name || Math.floor(Math.random() * 100))); this._contexts.set(contextId, { id: contextId, parentId: state.parentContextId, isCORS: state.isCORS, element, document: { id: Symbol('documentId' + (element.name || Math.floor(Math.random() * 100))) }, state: { get name() { return element.name || element.selector; }, }, }); element.contextId = contextId; this.mockElement('html', { parentContextId: contextId, scrollPosition: { x: 0, y: 0 }, }); } this.mockSelector(selector, element); return element; } unmockElement(element) { this.unmockSelector(element.selector, element); } mockElements(nodes, { parentId = null, parentContextId = null, parentRootId = null } = {}) { for (const node of nodes) { const element = this.mockElement(node.selector, { ...node, parentId, parentContextId, parentRootId }); if (node.children) { this.mockElements(node.children, { parentId: element.frame ? null : element.id, parentContextId: element.frame ? element.contextId : parentContextId, parentRootId: element.shadow ? element.shadowRootId : parentRootId, }); } } } mockSelector(selector, element) { let elements = this._elements.get(selector); if (!elements) { elements = []; this._elements.set(selector, elements); } elements.push(element); } unmockSelector(selector, element) { const elements = this._elements.get(selector); if (!elements) return; const index = elements.indexOf(element); if (index < 0) return; elements.splice(index, 1); } wrapMethod(name, wrapper) { if (!this[name] || name.startsWith('_') || !utils.types.isFunction(this[name])) return; if (this._methods.has(name)) this.unwrapMethod(name); this._methods.set(name, this[name]); this[name] = new Proxy(this[name], { apply: (method, driver, args) => wrapper(method, driver, args), }); } unwrapMethod(name) { const method = this._methods.get(name); if (!method) return; this[name] = method; } get environment() { return { isMobile: this._device ? Boolean(this._device.isMobile) : false, isNative: this._device ? Boolean(this._device.isNative) : false, deviceName: this._device ? this._device.name : null, platformName: this._platform ? this._platform.name : null, platformVersion: this._platform ? this._platform.version : null, browserName: this._browser ? this._browser.name : null, browserVersion: this._browser ? this._browser.version : null, }; } get driverServerUrl() { return this._driverServerUrl; } get sessionId() { return this._sessionId; } get capabilities() { return this._capabilities; } async executeScript(script, args = []) { if (this.environment.isNative && !/^mobile\:/.test(script)) { throw new Error(`Native context doesn't support this method: ${script}`); } let result = this._scripts.get(String(script)); if (!result) { const name = Object.keys(WELL_KNOWN_SCRIPTS).find(name => WELL_KNOWN_SCRIPTS[name](script)); if (utils.types.isFunction(script)) { try { return JSON.stringify(await script(...args)); } catch (e) { if (script.throwing) { throw e; } else return null; } } if (!name) return null; result = this._scripts.get(name); } const { state } = this._contexts.get(this._contextId); return utils.types.isFunction(result) ? result.call(state, ...args) : result; } async findElement(selector, rootElement) { const elements = this._elements.get(selector); return elements ? elements.find(element => element.parentContextId === this._contextId && element.parentRootId === ((rootElement || {}).shadowRootId || null)) : null; } async findElements(selector, rootElement) { const elements = this._elements.get(typeof selector === 'string' ? selector : selector.id); return elements ? elements.filter(element => element.parentContextId === this._contextId && element.parentRootId === ((rootElement || {}).shadowRootId || null)) : []; } async switchToFrame(reference) { if (reference === null) { this._contextId = null; return this; } if (utils.types.isString(reference)) { reference = await this.findElement(reference); } const frame = this._contexts.get(reference.contextId); if (frame && this._contextId === frame.parentId) { this._contextId = frame.id; return this; } else { throw new Error('Frame not found'); } } async switchToParentFrame() { if (!this._contextId) return this; for (const frame of this._contexts.values()) { if (frame.id === this._contextId) { this._contextId = frame.parentId; return this; } } return this; } async getWindowRect() { return this._window.rect; } async setWindowRect(rect) { Object.assign(this._window.rect, rect); } async getUrl() { if (this.environment.isNative) throw new Error("Native context doesn't support this method"); return this._window.url; } async getTitle() { if (this.environment.isNative) throw new Error("Native context doesn't support this method"); return this._window.title; } async visit(url) { if (this.environment.isNative) throw new Error("Native context doesn't support this method"); this._window.url = url; } async takeScreenshot() { // const image = new png.Image({ // width: this._window.rect.width, // height: this._window.rect.height, // }) // const stream = image.pack() // return new Promise((resolve, reject) => { // let buffer = Buffer.from([]) // stream.on('data', chunk => { // buffer = Buffer.concat([buffer, chunk]) // }) // stream.on('end', () => resolve(buffer)) // stream.on('error', reject) // }) } toString() { return '<MockDriver>'; } toJSON() { return '<MockDriver>'; } [inspect.custom]() { return '<MockDriver>'; } async getCurrentWorld() { return this.environment.isNative ? 'NATIVE_APP' : 'WEBVIEW_1'; } } exports.MockDriver = MockDriver;