@applitools/spec-driver-puppeteer
Version:
364 lines (363 loc) • 14.3 kB
JavaScript
;
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.bringToFront = 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));
// Use plain loops instead of Array/Object prototype methods — pages may monkey-patch
// Array.prototype.map, Object.entries, Object.assign, etc. to throw.
function transform(arg) {
if (!arg) {
return arg;
}
else if (arg.isElement) {
return elements.shift();
}
else if (Array.isArray(arg)) {
const result = [];
for (let i = 0; i < arg.length; i++)
result[i] = transform(arg[i]);
return result;
}
else if (typeof arg === 'object') {
const result = {};
for (const key in arg) {
if (Object.prototype.hasOwnProperty.call(arg, key))
result[key] = transform(arg[key]);
}
return result;
}
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) {
const [argWithElementMarkers, ...elements] = transformArgument(arg);
script = utils.types.isString(script) ? script : `function() {return (${script.toString()}).apply(null, arguments)}`;
const result = await frame.evaluateHandle(scriptRunner, script, argWithElementMarkers, ...elements);
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;
case 'Page.setBypassCSP':
if (currentCommand.params) {
await page.setBypassCSP(currentCommand.params.enabled);
}
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;
async function bringToFront(page) {
page.bringToFront();
}
exports.bringToFront = bringToFront;