UNPKG

@applitools/spec-driver-puppeteer

Version:
346 lines (345 loc) 13.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.build = exports.takeScreenshot = exports.visit = exports.getUrl = exports.getTitle = exports.getCookies = exports.setViewportSize = exports.getViewportSize = exports.getDriverInfo = exports.childContext = exports.parentContext = exports.mainContext = exports.click = exports.hover = exports.setElementText = exports.findElements = exports.findElement = exports.executeBrowserCommands = exports.executeScript = exports.extractContext = exports.toSimpleCommonSelector = exports.toSelector = exports.isStaleElementError = exports.isSelector = exports.isElement = exports.isContext = exports.isDriver = void 0; const utils = __importStar(require("@applitools/utils")); async function handleToObject(handle) { const [, type] = handle.toString().split('@'); if (type === 'array') { const map = await handle.getProperties(); return Promise.all(Array.from(map.values(), handleToObject)); } else if (type === 'object') { const map = await handle.getProperties(); const chunks = await Promise.all(Array.from(map, async ([key, handle]) => ({ [key]: await handleToObject(handle) }))); return chunks.length > 0 ? Object.assign(...chunks) : {}; } else if (type === 'node') { return handle.asElement(); } else { return handle.jsonValue(); } } function transformArgument(arg) { const elements = []; const argWithElementMarkers = transform(arg); return [argWithElementMarkers, ...elements]; function transform(arg) { if (isElement(arg)) { elements.push(arg); return { isElement: true }; } else if (utils.types.isArray(arg)) { return arg.map(transform); } else if (utils.types.isObject(arg)) { return Object.entries(arg).reduce((object, [key, value]) => { return Object.assign(object, { [key]: transform(value) }); }, {}); } else { return arg; } } } // NOTE: // A few things to note: // - this function runs inside of the browser process // - evaluations in Puppeteer accept multiple arguments (not just one like in Playwright) // - an element reference (a.k.a. an ElementHandle) can only be sent as its // own argument. To account for this, we use a wrapper function to receive all // of the arguments in a serialized structure, deserialize them, and call the script, // and pass the arguments as originally intended function scriptRunner(script, arg, ...elements) { const func = new Function(script.startsWith('function') ? `return (${script}).apply(null, arguments)` : script); return func(transform(arg)); function transform(arg) { if (!arg) { return arg; } else if (arg.isElement) { return elements.shift(); } else if (Array.isArray(arg)) { return arg.map(transform); } else if (typeof arg === 'object') { return Object.entries(arg).reduce((object, [key, value]) => { return Object.assign(object, { [key]: transform(value) }); }, {}); } else { return arg; } } } const XPATH_SELECTOR_START = ['/', '(', '../', './', '*/']; function isXpathSelector(selector) { return XPATH_SELECTOR_START.some(start => selector.startsWith(start)); } function getFrameworkMajorVersion() { let frameworkManifestPath; try { frameworkManifestPath = require.resolve('puppeteer/package.json', { paths: [`${process.cwd()}/node_modules`] }); } catch { frameworkManifestPath = 'puppeteer/package.json'; } return Number.parseInt(require(frameworkManifestPath).version); } function getPuppeteer() { let frameworkPath; try { frameworkPath = require.resolve('puppeteer', { paths: [`${process.cwd()}/node_modules`] }); } catch { frameworkPath = 'puppeteer'; } return require(frameworkPath); } function isDriver(page) { if (!page) return false; return utils.types.instanceOf(page, 'Page'); } exports.isDriver = isDriver; function isContext(frame) { if (!frame) return false; return utils.types.instanceOf(frame, 'Frame'); } exports.isContext = isContext; function isElement(element) { if (!element) return false; return utils.types.instanceOf(element, 'ElementHandle'); } exports.isElement = isElement; function isSelector(selector) { return utils.types.isString(selector); } exports.isSelector = isSelector; function isStaleElementError(err) { var _a, _b, _c, _d; return (((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes('Execution context was destroyed')) || ((_b = err === null || err === void 0 ? void 0 : err.message) === null || _b === void 0 ? void 0 : _b.includes('Cannot find context with specified id')) || ((_c = err === null || err === void 0 ? void 0 : err.message) === null || _c === void 0 ? void 0 : _c.includes('Could not find object with given id')) || ((_d = err === null || err === void 0 ? void 0 : err.message) === null || _d === void 0 ? void 0 : _d.includes('JSHandles can be evaluated only in the context they were created'))); } exports.isStaleElementError = isStaleElementError; function toSelector(selector) { if (utils.types.has(selector, 'selector')) return selector.selector; return selector; } exports.toSelector = toSelector; function toSimpleCommonSelector(selector) { if (utils.types.isString(selector)) { return { type: isXpathSelector(selector) ? 'xpath' : 'css', selector }; } return selector; } exports.toSimpleCommonSelector = toSimpleCommonSelector; function extractContext(page) { return isDriver(page) ? page.mainFrame() : page; } exports.extractContext = extractContext; async function executeScript(frame, script, arg) { script = utils.types.isString(script) ? script : `function() {return (${script.toString()}).apply(null, arguments)}`; const result = await frame.evaluateHandle(scriptRunner, script, ...transformArgument(arg)); return handleToObject(result); } exports.executeScript = executeScript; async function executeBrowserCommands(page, commands, logger) { let lastResponse; let native = true; for (const currentCommand of commands) { switch (currentCommand.command) { case 'Page.addScriptToEvaluateOnNewDocument': if (currentCommand.params) { lastResponse = await page.evaluateOnNewDocument(currentCommand.params.source); } break; default: { native = false; const userAgent = await (await page.browser().userAgent()).toLowerCase(); if (userAgent.includes('chrome') || userAgent.includes('chromium')) { const cdpSession = await page.target().createCDPSession(); lastResponse = await cdpSession.send(currentCommand.command, currentCommand.params); } else { logger === null || logger === void 0 ? void 0 : logger.info('executeBrowserCommands not supported'); return; } } } logger === null || logger === void 0 ? void 0 : logger.debug(`executeBrowserCommands native:${native} command:${currentCommand.command} ${JSON.stringify(currentCommand.params)} => ${JSON.stringify(lastResponse)}`); } } exports.executeBrowserCommands = executeBrowserCommands; async function findElement(frame, selector, parent) { const root = parent !== null && parent !== void 0 ? parent : frame; if (isXpathSelector(selector)) { return getFrameworkMajorVersion() < 22 ? root.$x(selector).then(elements => elements[0]) : root.$(`xpath/${selector}`); } return root.$(selector); } exports.findElement = findElement; async function findElements(frame, selector, parent) { const root = parent !== null && parent !== void 0 ? parent : frame; if (isXpathSelector(selector)) { return getFrameworkMajorVersion() < 22 ? root.$x(selector) : root.$$(`xpath/${selector}`); } return root.$$(selector); } exports.findElements = findElements; async function setElementText(_frame, element, text) { await element.evaluate(element => (element.value = '')); await element.type(text); } exports.setElementText = setElementText; async function hover(_frame, element) { await element.hover(); } exports.hover = hover; async function click(_frame, element) { await element.click(); } exports.click = click; async function mainContext(frame) { let mainFrame = frame; while (mainFrame.parentFrame()) { mainFrame = mainFrame.parentFrame(); } return mainFrame; } exports.mainContext = mainContext; async function parentContext(frame) { var _a; return (_a = frame.parentFrame()) !== null && _a !== void 0 ? _a : frame; } exports.parentContext = parentContext; async function childContext(_frame, element) { const frame = (await element.contentFrame()); return frame; } exports.childContext = childContext; async function getDriverInfo(_page) { return { features: { allCookies: true } }; } exports.getDriverInfo = getDriverInfo; async function getViewportSize(page) { return page.viewport(); } exports.getViewportSize = getViewportSize; async function setViewportSize(page, size) { await page.setViewport(size); await new Promise(res => setTimeout(res, 100)); } exports.setViewportSize = setViewportSize; async function getCookies(page) { const cdpSession = await page.target().createCDPSession(); const { cookies } = await cdpSession.send('Network.getAllCookies'); return cookies.map(cookie => { const copy = { ...cookie, expiry: cookie.expires }; delete copy.expires; delete copy.size; delete copy.priority; delete copy.session; delete copy.sameParty; delete copy.sourceScheme; delete copy.sourcePort; return copy; }); } exports.getCookies = getCookies; async function getTitle(page) { return page.title(); } exports.getTitle = getTitle; async function getUrl(page) { return page.url(); } exports.getUrl = getUrl; async function visit(page, url) { await page.goto(url); } exports.visit = visit; async function takeScreenshot(page) { const result = await page.screenshot({ captureBeyondViewport: false }); return result; } exports.takeScreenshot = takeScreenshot; const browserNames = ['chrome', 'firefox']; /* * Spawn a browser with a given configuration (INTERNAL USE ONLY) * * NOTE: * This function is intended for internal use only. As a result it relies on some dev dependencies. * When wiring the spec-driver up to an SDK and calling this function, if you don't have the same dev deps * installed in the SDK, then this function will error. */ async function build(env) { const puppeteer = getPuppeteer(); const puppeteerVersion = getFrameworkMajorVersion(); const parseEnv = require('@applitools/test-utils/src/parse-env'); const { browser, attach, proxy, args = [], headless } = parseEnv(env, 'cdp'); if (!browserNames.includes(browser)) throw new Error(`Browser "${browser}" is not supported.`); let driver, page; if (attach) { driver = await puppeteer.connect({ browserURL: attach === true ? 'http://localhost:9222' : attach, product: browser, }); [page] = await driver.pages(); } else { const options = { headless, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', ...args], ignoreDefaultArgs: ['--hide-scrollbars'], product: browser, protocol: 'cdp', }; if (puppeteerVersion >= 22 && options.headless === true) options.headless = 'shell'; if (proxy) { options.proxy = { server: proxy.https || proxy.http || proxy.server, bypass: proxy.bypass.join(','), }; } driver = await puppeteer.launch(options); page = await driver.newPage(); } return [page, () => driver.close()]; } exports.build = build;