@applitools/spec-driver-playwright
Version:
316 lines (315 loc) • 12.8 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.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;