UNPKG

@applitools/spec-driver-playwright

Version:
316 lines (315 loc) 12.8 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 fs = __importStar(require("fs")); const os = __importStar(require("os")); const path = __importStar(require("path")); const utils = __importStar(require("@applitools/utils")); async function handleToObject(handle) { var _a; let [, type] = (_a = handle.toString().match(/(?:.+@)?(\w*)(?:\(\d+\))?/i)) !== null && _a !== void 0 ? _a : []; type = type === null || type === void 0 ? void 0 : type.toLowerCase(); 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 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) { if (!selector) return false; return utils.types.isString(selector) || utils.types.instanceOf(selector, 'Locator'); } 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('Element is not attached to the DOM')) || // universal message ((_b = err === null || err === void 0 ? void 0 : err.message) === null || _b === void 0 ? void 0 : _b.includes('Protocol error (DOM.describeNode)')) || // chrome message ((_c = err === null || err === void 0 ? void 0 : err.message) === null || _c === void 0 ? void 0 : _c.includes('Protocol error (Page.adoptNode)')) || // firefox message ((_d = err === null || err === void 0 ? void 0 : err.message) === null || _d === void 0 ? void 0 : _d.includes('Unable to adopt element handle from a different document')) // webkit message ); } exports.isStaleElementError = isStaleElementError; function toSelector(selector) { if (utils.types.has(selector, 'selector')) { if (!utils.types.has(selector, 'type')) return selector.selector; else return `${selector.type}=${selector.selector}`; } return selector; } exports.toSelector = toSelector; function toSimpleCommonSelector(selector) { if (utils.types.instanceOf(selector, 'Locator')) { selector = selector._selector; } else if (utils.types.instanceOf(selector, 'FrameLocator')) { selector = selector._frameSelector; } return utils.types.isString(selector) ? { selector } : null; } 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) ? new Function(script) : script; const result = await frame.evaluateHandle(script, 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.addInitScript({ content: currentCommand.params.source, }); } break; default: native = false; const browser = page.context().browser(); if ((browser === null || browser === void 0 ? void 0 : browser.browserType().name()) === 'chromium') { const client = await page.context().newCDPSession(page); lastResponse = await client.send(currentCommand.command, currentCommand.params); } else { logger === null || logger === void 0 ? void 0 : logger.info('executeBrowserCommands not supported'); } } logger === null || logger === void 0 ? void 0 : logger.debug(`executeBrowserCommands native:${native} command:${currentCommand.command} ${JSON.stringify(currentCommand.params)} => ${JSON.stringify(lastResponse)}`); } return lastResponse; } exports.executeBrowserCommands = executeBrowserCommands; async function findElement(frame, selector, parent) { if (utils.types.instanceOf(selector, 'Locator')) { return selector.elementHandle(); } const root = parent !== null && parent !== void 0 ? parent : frame; return root.$(selector); } exports.findElement = findElement; async function findElements(frame, selector, parent) { if (utils.types.instanceOf(selector, 'Locator')) { return (await selector.elementHandles()); } const root = parent !== null && parent !== void 0 ? parent : frame; return root.$$(selector); } exports.findElements = findElements; async function setElementText(frame, element, text) { const resolvedElement = isSelector(element) ? await findElement(frame, element) : element; await (resolvedElement === null || resolvedElement === void 0 ? void 0 : resolvedElement.fill(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.viewportSize(); } exports.getViewportSize = getViewportSize; async function setViewportSize(page, size) { return page.setViewportSize(size); } exports.setViewportSize = setViewportSize; async function getCookies(page) { const cookies = await page.context().cookies(); return cookies.map(cookie => { const copy = { ...cookie, expiry: cookie.expires }; delete copy.expires; 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) { return page.screenshot(); } exports.takeScreenshot = takeScreenshot; const browserNames = { chrome: 'chromium', safari: 'webkit', firefox: '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) { let frameworkPath; try { frameworkPath = require.resolve('playwright', { paths: [`${process.cwd()}/node_modules`] }); } catch { frameworkPath = 'playwright'; } const playwright = require(frameworkPath); const parseEnv = require('@applitools/test-utils/src/parse-env'); const { browser, device, url, attach, proxy, args = [], headless, extension } = parseEnv(env, 'cdp'); const launcher = playwright[browserNames[browser] || browser]; if (!launcher) throw new Error(`Browser "${browser}" is not supported.`); if (attach) throw new Error(`Attaching to the existed browser doesn't supported by playwright`); const options = { args, headless: headless && !extension, ignoreDefaultArgs: ['--hide-scrollbars'], }; if ((browser === null || browser === void 0 ? void 0 : browser.toLowerCase()) === 'chrome') { const gpuOptions = [ '--use-angle=vulkan', '--enable-features=Vulkan', '--disable-vulkan-surface', '--enable-unsafe-webgpu', '--ignore-gpu-blocklist', '--use-gl=angle', ]; options.args = options.args.concat(gpuOptions); options.ignoreDefaultArgs.push('--use-gl'); if (process.env.CHROME_PATH) { options.executablePath = process.env.CHROME_PATH; } } // TODO remove this once Playwright provides formal support for headless: 'new' (https://github.com/microsoft/playwright/issues/21194) if (headless) { options.args.push('--headless=new'); delete options.headless; options.ignoreDefaultArgs.push('--headless'); } if (extension) { options.args.push(`--load-extension=${extension}`, `--disable-extensions-except=${extension}`); } if (proxy) { options.proxy = { server: proxy.https || proxy.http || proxy.server, bypass: proxy.bypass.join(','), }; } let driver, context; if (extension) { context = await launcher.launchPersistentContext(fs.mkdtempSync(path.join(os.tmpdir(), 'chrome-user-data-dir')), { ...options, viewport: null, ...(device ? playwright.devices[device] : {}), }); } else { if (url) { if (utils.types.isArray(options.ignoreDefaultArgs)) { url.searchParams.set('ignoreDefaultArgs', options.ignoreDefaultArgs.join(',')); } url.searchParams.set('headless', options.headless); options.args.forEach((arg) => url.searchParams.set(...arg.split('='))); driver = await launcher.connect({ wsEndpoint: url.href }); } else { driver = await launcher.launch(options); } context = await driver.newContext(device ? playwright.devices[device] : {}); } const page = await context.newPage(); return [page, () => (driver ? driver.close() : context.close()), () => driver]; } exports.build = build;