UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

668 lines 32.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.takeScreenshot = takeScreenshot; exports.updateStorybookGlobals = updateStorybookGlobals; exports.loadStoriesFromBrowser = loadStoriesFromBrowser; exports.getBrowser = getBrowser; exports.closeBrowser = closeBrowser; exports.switchStory = switchStory; const core_events_1 = require("@storybook/core-events"); const chalk_1 = __importDefault(require("chalk")); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const loglevel_1 = __importDefault(require("loglevel")); const loglevel_plugin_prefix_1 = __importDefault(require("loglevel-plugin-prefix")); const os_1 = require("os"); const pngjs_1 = require("pngjs"); const selenium_webdriver_1 = require("selenium-webdriver"); // import { Options as IeOptions } from 'selenium-webdriver/ie'; // import { Options as EdgeOptions } from 'selenium-webdriver/edge'; // import { Options as ChromeOptions } from 'selenium-webdriver/chrome'; // import { Options as SafariOptions } from 'selenium-webdriver/safari'; // import { Options as FirefoxOptions } from 'selenium-webdriver/firefox'; const capabilities_js_1 = require("selenium-webdriver/lib/capabilities.js"); const types_js_1 = require("../../types.js"); const logger_js_1 = require("../logger.js"); const messages_js_1 = require("../messages.js"); const utils_js_1 = require("../utils.js"); // type UnPromise<P> = P extends Promise<infer T> ? T : never; const storybookRootID = 'storybook-root'; const DOCKER_INTERNAL = 'host.docker.internal'; let browserName = ''; let browser = null; // let context: UnPromise<ReturnType<typeof BrowsingContext>> | null = null; let creeveyServerHost = null; let creeveyServerPort = null; function getSessionData(grid, sessionId = '') { const gridUrl = new URL(grid); gridUrl.pathname = `/host/${sessionId}`; return new Promise((resolve, reject) => (gridUrl.protocol == 'https:' ? https_1.default : http_1.default).get(gridUrl.toString(), (res) => { if (res.statusCode !== 200) { reject(new Error(`Couldn't get session data for ${sessionId}. Status code: ${res.statusCode ?? 'Unknown'}`)); return; } let data = ''; res.setEncoding('utf-8'); res.on('data', (chunk) => (data += chunk)); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (error) { reject(new Error(`Couldn't get session data for ${sessionId}. ${error instanceof Error ? (error.stack ?? error.message) : error}`)); } }); })); } function getAddresses() { // TODO Check if docker is used return [DOCKER_INTERNAL].concat(...Object.values((0, os_1.networkInterfaces)()) .filter(types_js_1.isDefined) .map((network) => network.filter((info) => info.family == 'IPv4').map((info) => info.address))); } async function resolveStorybookUrl(storybookUrl, checkUrl) { (0, logger_js_1.logger)().debug('Resolving storybook url'); const addresses = getAddresses(); for (const ip of addresses) { const resolvedUrl = storybookUrl.replace(utils_js_1.LOCALHOST_REGEXP, ip); (0, logger_js_1.logger)().debug(`Checking storybook availability on ${chalk_1.default.magenta(resolvedUrl)}`); if (await checkUrl(resolvedUrl)) { (0, logger_js_1.logger)().debug(`Resolved storybook url ${chalk_1.default.magenta(resolvedUrl)}`); return resolvedUrl; } } throw new Error('Please specify `storybookUrl` with IP address that accessible from remote browser'); } async function openUrlAndWaitForPageSource(browser, url, predicate) { let source = ''; await browser.get(url); do { try { source = await browser.getPageSource(); } catch { // NOTE: Firefox can raise exception "curContainer.frame.document.documentElement is null" } } while (predicate(source)); return source; } function getUrlChecker(browser) { return async (url) => { try { // NOTE: Before trying a new url, reset the current one (0, logger_js_1.logger)().debug(`Opening ${chalk_1.default.magenta('about:blank')} page`); await openUrlAndWaitForPageSource(browser, 'about:blank', (source) => !source.includes('<body></body>')); (0, logger_js_1.logger)().debug(`Opening ${chalk_1.default.magenta(url)} and checking the page source`); const source = await openUrlAndWaitForPageSource(browser, url, // NOTE: IE11 can return only `head` without body (source) => source.length == 0 || !/<body([^>]*>).+<\/body>/s.test(source)); // NOTE: This is the most optimal way to check if we in storybook or not // We don't use any page load strategies except `NONE` // because other add significant delay and some of them don't work in earlier chrome versions // Browsers always load page successful even it's failed // So we just check `root` element (0, logger_js_1.logger)().debug(`Checking ${chalk_1.default.cyan(`#${storybookRootID}`)} existence on ${chalk_1.default.magenta(url)}`); return source.includes(`id="${storybookRootID}"`); } catch { return false; } }; } async function buildWebdriver(gridUrl, capabilities, prefs) { const maxRetries = 5; let maybeResult = null; let retries = 0; do { maybeResult = await Promise.race([ new Promise((resolve) => { setTimeout(() => { retries += 1; resolve(null); }, 120_000); }), (async () => { if (retries > 0) { (0, logger_js_1.logger)().debug(`Trying to initialize session to Selenium Grid: retried ${retries} of ${maxRetries}`); } const retry = retries; // const ie = new IeOptions(); // const edge = new EdgeOptions(); // const chrome = new ChromeOptions(); // const safari = new SafariOptions(); // const firefox = new FirefoxOptions(); // edge.enableBidi(); // chrome.enableBidi(); // firefox.enableBidi(); const browser = await new selenium_webdriver_1.Builder() // .setIeOptions(ie) // .setEdgeOptions(edge) // .setChromeOptions(chrome) // .setSafariOptions(safari) // .setFirefoxOptions(firefox) .usingServer(gridUrl) .withCapabilities(capabilities) .setLoggingPrefs(prefs) // NOTE: Should go last .build(); // const id = await browser.getWindowHandle(); // context = await BrowsingContext(browser, { browsingContextId: id }); const sessionId = (await browser.getSession()).getId(); if (retry != retries) { void browser.quit(); return null; } return [sessionId, browser]; })(), ]); if (maybeResult) break; } while (retries < maxRetries); if (!maybeResult) throw new Error('Failed to initialize session to Selenium Grid due to many retries'); return maybeResult; } async function waitForStorybook(browser) { (0, logger_js_1.logger)().debug('Waiting for `setStories` event to make sure that storybook is initiated'); const isTimeout = await Promise.race([ new Promise((resolve) => { setTimeout(() => { resolve(true); }, 60000); }), (async () => { let wait = true; do { try { wait = await browser.executeScript(function (SET_GLOBALS) { if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true; if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true; return false; }, core_events_1.SET_GLOBALS); } catch (e) { (0, logger_js_1.logger)().debug('An error has been caught during the script:', e); } } while (wait); return false; })(), ]); // TODO Change the message to describe a reason why it might happen if (isTimeout) throw new Error('Failed to wait `setStories` event'); } async function resetMousePosition(browser) { (0, logger_js_1.logger)().debug('Resetting mouse position to the top-left corner'); const browserName = (await browser.getCapabilities()).getBrowserName(); const [browserVersion] = (await browser.getCapabilities()).getBrowserVersion()?.split('.') ?? (await browser.getCapabilities()).get('version')?.split('.') ?? []; // NOTE Reset mouse position to support keweb selenium grid browser versions if (browserName == 'chrome' && browserVersion == '70') { const { top, left, width, height } = await browser.executeScript(function () { const bodyRect = document.body.getBoundingClientRect(); return { top: bodyRect.top, left: bodyRect.left, width: bodyRect.width, height: bodyRect.height, }; }); // NOTE Bridge mode doesn't support `Origin.VIEWPORT`, move mouse relative await browser .actions({ bridge: true }) .move({ origin: browser.findElement(selenium_webdriver_1.By.css('body')), x: Math.ceil((-1 * width) / 2) - left, y: Math.ceil((-1 * height) / 2) - top, }) .perform(); } else if (browserName == 'firefox') { // NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad: // NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all await browser.actions().move({ origin: selenium_webdriver_1.Origin.VIEWPORT, x: 0, y: 1 }).perform(); } else { // NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine await browser.actions().move({ origin: selenium_webdriver_1.Origin.VIEWPORT, x: 0, y: 0 }).perform(); } } async function resizeViewport(browser, viewport) { const windowRect = await browser.manage().window().getRect(); const { innerWidth, innerHeight } = await browser.executeScript(function () { return { innerWidth: window.innerWidth, innerHeight: window.innerHeight, }; }); (0, logger_js_1.logger)().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`); const dWidth = windowRect.width - innerWidth; const dHeight = windowRect.height - innerHeight; await browser .manage() .window() .setRect({ width: viewport.width + dWidth, height: viewport.height + dHeight, }); } const getScrollBarWidth = (() => { let scrollBarWidth = null; return async (browser) => { if (scrollBarWidth != null) return Promise.resolve(scrollBarWidth); scrollBarWidth = await browser.executeScript(function () { // eslint-disable-next-line no-var var div = document.createElement('div'); div.innerHTML = 'a'; // NOTE: In IE clientWidth is 0 if this div is empty. div.style.overflowY = 'scroll'; document.body.appendChild(div); // eslint-disable-next-line no-var var widthDiff = div.offsetWidth - div.clientWidth; document.body.removeChild(div); return widthDiff; }); return scrollBarWidth; }; })(); // NOTE Firefox and Safari take viewport screenshot without scrollbars async function hasScrollBar(browser) { const browserName = (await browser.getCapabilities()).getBrowserName(); const [browserVersion] = (await browser.getCapabilities()).getBrowserVersion()?.split('.') ?? []; return (browserName != 'Safari' && // NOTE This need to work with keweb selenium grid !(browserName == 'firefox' && browserVersion == '61')); } async function takeCompositeScreenshot(browser, windowRect, elementRect) { const screens = []; const isScreenshotWithoutScrollBar = !(await hasScrollBar(browser)); const scrollBarWidth = await getScrollBarWidth(browser); // NOTE Sometimes viewport has been scrolled somewhere const normalizedElementRect = { left: elementRect.left - windowRect.left, right: elementRect.left + elementRect.width - windowRect.left, top: elementRect.top - windowRect.top, bottom: elementRect.top + elementRect.height - windowRect.top, }; const isFitHorizontally = windowRect.width >= elementRect.width + normalizedElementRect.left; const isFitVertically = windowRect.height >= elementRect.height + normalizedElementRect.top; const viewportWidth = windowRect.width - (isFitVertically ? 0 : scrollBarWidth); const viewportHeight = windowRect.height - (isFitHorizontally ? 0 : scrollBarWidth); const cols = Math.ceil(elementRect.width / viewportWidth); const rows = Math.ceil(elementRect.height / viewportHeight); const xOffset = Math.round(isFitHorizontally ? normalizedElementRect.left : Math.max(0, cols * viewportWidth - elementRect.width)); const yOffset = Math.round(isFitVertically ? normalizedElementRect.top : Math.max(0, rows * viewportHeight - elementRect.height)); for (let row = 0; row < rows; row += 1) { for (let col = 0; col < cols; col += 1) { const dx = Math.min(viewportWidth * col + normalizedElementRect.left, Math.max(0, normalizedElementRect.right - viewportWidth)); const dy = Math.min(viewportHeight * row + normalizedElementRect.top, Math.max(0, normalizedElementRect.bottom - viewportHeight)); await browser.executeScript(function (x, y) { window.scrollTo(x, y); }, dx, dy); screens.push(await browser.takeScreenshot()); } } const images = screens.map((s) => Buffer.from(s, 'base64')).map((b) => pngjs_1.PNG.sync.read(b)); const compositeImage = new pngjs_1.PNG({ width: Math.round(elementRect.width), height: Math.round(elementRect.height) }); for (let y = 0; y < compositeImage.height; y += 1) { for (let x = 0; x < compositeImage.width; x += 1) { const col = Math.floor(x / viewportWidth); const row = Math.floor(y / viewportHeight); const isLastCol = cols - col == 1; const isLastRow = rows - row == 1; const scrollOffset = isFitVertically || isScreenshotWithoutScrollBar ? 0 : scrollBarWidth; const i = (y * compositeImage.width + x) * 4; const j = // NOTE compositeImage(x, y) => image(x, y) ((y % viewportHeight) * (viewportWidth + scrollOffset) + (x % viewportWidth)) * 4 + // NOTE Offset for last row/col image (isLastRow ? yOffset * (viewportWidth + scrollOffset) * 4 : 0) + (isLastCol ? xOffset * 4 : 0); const image = images[row * cols + col]; compositeImage.data[i + 0] = image.data[j + 0]; compositeImage.data[i + 1] = image.data[j + 1]; compositeImage.data[i + 2] = image.data[j + 2]; compositeImage.data[i + 3] = image.data[j + 3]; } } return pngjs_1.PNG.sync.write(compositeImage).toString('base64'); } async function takeScreenshot(browser, captureElement, ignoreElements) { let screenshot; const ignoreStyles = await insertIgnoreStyles(browser, ignoreElements); if ((0, logger_js_1.logger)().getLevel() <= loglevel_1.default.levels.DEBUG) { const { innerWidth, innerHeight } = await browser.executeScript(function () { return { innerWidth: window.innerWidth, innerHeight: window.innerHeight, }; }); (0, logger_js_1.logger)().debug(`Viewport size is: ${innerWidth}x${innerHeight}`); } try { if (!captureElement) { (0, logger_js_1.logger)().debug('Capturing viewport screenshot'); screenshot = await browser.takeScreenshot(); (0, logger_js_1.logger)().debug('Viewport screenshot is captured'); } else { (0, logger_js_1.logger)().debug(`Checking is element ${chalk_1.default.cyan(captureElement)} fit into viewport`); const rects = await browser.executeScript(function (selector) { window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition` // eslint-disable-next-line no-var var element = document.querySelector(selector); if (!element) return; // eslint-disable-next-line no-var var elementRect = element.getBoundingClientRect(); return { elementRect: { top: elementRect.top, left: elementRect.left, width: elementRect.width, height: elementRect.height, }, // NOTE page_Offset is used only for IE9-11 windowRect: { // eslint-disable-next-line @typescript-eslint/no-deprecated top: Math.round(window.scrollY || window.pageYOffset), // eslint-disable-next-line @typescript-eslint/no-deprecated left: Math.round(window.scrollX || window.pageXOffset), width: window.innerWidth, height: window.innerHeight, }, }; }, captureElement); const { elementRect, windowRect } = rects ?? {}; if (!elementRect || !windowRect) throw new Error(`Couldn't find element with selector: '${captureElement}'`); const isFitIntoViewport = elementRect.width + elementRect.left <= windowRect.width && elementRect.height + elementRect.top <= windowRect.height; if (isFitIntoViewport) { (0, logger_js_1.logger)().debug(`Capturing ${chalk_1.default.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`); } else (0, logger_js_1.logger)().debug(`Capturing composite screenshot image of ${chalk_1.default.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`); // const element = await browser.findElement(By.css(captureElement)); // screenshot = isFitIntoViewport // ? context // ? await context.captureElementScreenshot(await element.getId()) // : await browser.findElement(By.css(captureElement)).takeScreenshot() // : // TODO pointer-events: none, need to research // await takeCompositeScreenshot(browser, windowRect, elementRect); screenshot = isFitIntoViewport ? await browser.findElement(selenium_webdriver_1.By.css(captureElement)).takeScreenshot() : // TODO pointer-events: none, need to research await takeCompositeScreenshot(browser, windowRect, elementRect); (0, logger_js_1.logger)().debug(`${chalk_1.default.cyan(captureElement)} is captured`); } } finally { await removeIgnoreStyles(browser, ignoreStyles); } return screenshot; } async function selectStory(browser, storyId, waitForReady = false) { (0, logger_js_1.logger)().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk_1.default.magenta(storyId)}`); const result = await browser.executeAsyncScript(function (id, shouldWaitForReady, callback) { if (typeof window.__CREEVEY_SELECT_STORY__ == 'undefined') { callback([ "Creevey can't switch story. This may happened if forget to add `creevey` addon to your storybook config, or storybook not loaded in browser due syntax error.", ]); return; } void window.__CREEVEY_SELECT_STORY__(id, shouldWaitForReady, callback); }, storyId, waitForReady); const [errorMessage, isCaptureCalled = false] = result ?? []; if (errorMessage) throw new Error(errorMessage); return isCaptureCalled; } async function updateStorybookGlobals(browser, globals) { (0, logger_js_1.logger)().debug('Applying storybook globals'); await browser.executeScript(function (globals) { window.__CREEVEY_UPDATE_GLOBALS__(globals); }, globals); } function appendIframePath(url) { return `${url.replace(/\/$/, '')}/iframe.html`; } async function openStorybookPage(browser, storybookUrl, resolver) { if (!utils_js_1.LOCALHOST_REGEXP.test(storybookUrl)) { return browser.get(appendIframePath(storybookUrl)); } try { if (resolver) { (0, logger_js_1.logger)().debug('Resolving storybook url with custom resolver'); const resolvedUrl = await resolver(); (0, logger_js_1.logger)().debug(`Resolver storybook url ${resolvedUrl}`); await browser.get(appendIframePath(resolvedUrl)); } else { // NOTE: getUrlChecker already calls `browser.get` so we don't need another one await resolveStorybookUrl(appendIframePath(storybookUrl), getUrlChecker(browser)); } } catch (error) { (0, logger_js_1.logger)().error('Failed to resolve storybook URL', error instanceof Error ? error.message : ''); throw error; } } async function resolveCreeveyHost(browser, port, host) { const fetcher = function (hosts, port, callback) { void Promise.all(hosts.map(function (host) { return Promise.race([ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands fetch('http://' + host + ':' + port + '/ping').then(function (response) { return response.text(); }), new Promise((_resolve, reject) => { setTimeout(reject, 5000); }), ]) .then(function (pong) { return pong == 'pong' ? host : null; }) .catch(function () { return null; }); })).then(function (hosts) { callback(hosts.find(function (host) { return host != null; })); }); }; const addresses = host ? [host] : getAddresses(); creeveyServerPort = port; creeveyServerHost = await browser.executeAsyncScript(fetcher, addresses, port); if (creeveyServerHost == null) throw new Error("Can't reach creevey server from a browser"); } async function loadStoriesFromBrowser() { if (!browser) throw new Error("Can't get stories from browser if webdriver isn't connected"); const stories = await browser.executeAsyncScript(function (callback) { void window.__CREEVEY_GET_STORIES__().then(callback); }); if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available"); return stories; } async function getBrowser(config, options) { if (browser) return browser; browserName = options.browser; const browserConfig = config.browsers[browserName]; const { gridUrl = config.gridUrl, storybookUrl: address = config.storybookUrl, limit, viewport, _storybookGlobals, ...userCapabilities } = browserConfig; void limit; const realAddress = address; // TODO Define some capabilities explicitly and define typings const capabilities = new selenium_webdriver_1.Capabilities({ ...userCapabilities, pageLoadStrategy: capabilities_js_1.PageLoadStrategy.EAGER }); (0, messages_js_1.subscribeOn)('shutdown', () => { void browser?.quit().finally(() => process.exit()); browser = null; }); const url = new URL(gridUrl); url.username = url.username ? '********' : ''; url.password = url.password ? '********' : ''; (0, logger_js_1.logger)().debug(`(${browserName}) Connecting to Selenium ${chalk_1.default.magenta(url.toString())}`); const prefs = new selenium_webdriver_1.logging.Preferences(); if (options.trace) { for (const type of Object.values(selenium_webdriver_1.logging.Type)) { prefs.setLevel(type, selenium_webdriver_1.logging.Level.ALL); } } let sessionId; let browserHost = ''; [sessionId, browser] = await buildWebdriver(gridUrl, capabilities, prefs); try { const { Name } = await getSessionData(gridUrl, sessionId); if (typeof Name == 'string') browserHost = Name; } catch { /* noop */ } (0, logger_js_1.logger)().debug(`(${browserName}) Connected successful with ${[chalk_1.default.green(browserHost), chalk_1.default.magenta(sessionId)] .filter(Boolean) .join(':')}`); loglevel_plugin_prefix_1.default.apply((0, logger_js_1.logger)(), { format(level) { const levelColor = logger_js_1.colors[level.toUpperCase()]; return `[${browserName}:${chalk_1.default.gray(process.pid)}] ${levelColor(level)} => ${chalk_1.default.gray(sessionId)}`; }, }); try { await (0, utils_js_1.runSequence)([ () => browser?.manage().setTimeouts({ pageLoad: 60000, script: 60000 }), () => browser && openStorybookPage(browser, realAddress, config.resolveStorybookUrl), () => browser && waitForStorybook(browser), () => browser && resolveCreeveyHost(browser, options.port, config.host), () => browser && updateBrowserGlobalVariables(browser), () => _storybookGlobals && browser && updateStorybookGlobals(browser, _storybookGlobals), // NOTE: Selenium draws automation toolbar with some delay after webdriver initialization // NOTE: So if we resize window right after getting webdriver instance we might get situation // NOTE: When the toolbar appears after resize and final viewport size become smaller than we set () => viewport && browser && resizeViewport(browser, viewport), ], () => !utils_js_1.isShuttingDown.current); } catch (originalError) { if (utils_js_1.isShuttingDown.current) { browser.quit().catch(types_js_1.noop); browser = null; return null; } const currentUrl = await browser.getCurrentUrl(); const error = new Error(`Can't load storybook root page${currentUrl ? ` by URL ${currentUrl}` : ''}: ${originalError instanceof Error ? originalError.message : (originalError ?? 'Unknown error')}`); if (originalError instanceof Error) error.stack = originalError.stack; throw error; } return browser; } async function updateBrowserGlobalVariables(browser) { await browser.executeScript(function (workerId, creeveyHost, creeveyPort) { window.__CREEVEY_WORKER_ID__ = workerId; window.__CREEVEY_SERVER_HOST__ = creeveyHost; window.__CREEVEY_SERVER_PORT__ = creeveyPort; }, process.pid, creeveyServerHost, creeveyServerPort); } async function updateStoryArgs(browser, story, updatedArgs) { await browser.executeAsyncScript(function (storyId, updatedArgs, UPDATE_STORY_ARGS, STORY_RENDERED, callback) { window.__STORYBOOK_ADDONS_CHANNEL__.once(STORY_RENDERED, callback); window.__STORYBOOK_ADDONS_CHANNEL__.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs, }); }, story.id, updatedArgs, core_events_1.UPDATE_STORY_ARGS, core_events_1.STORY_RENDERED); } async function closeBrowser() { if (!browser) return; try { await browser.quit(); } finally { browser = null; } } async function switchStory() { let testOrSuite = this.currentTest; if (!testOrSuite) throw new Error("Can't switch story, because test context doesn't have 'currentTest' field"); this.testScope.length = 0; this.screenshots.length = 0; this.testScope.push(this.browserName); while (testOrSuite?.title) { this.testScope.push(testOrSuite.title); testOrSuite = testOrSuite.parent; } const story = this.currentTest?.ctx?.story; if (!story) throw new Error(`Current test '${this.testScope.join('/')}' context doesn't have 'story' field`); const { id, title, name, parameters } = story; const { captureElement = `#${storybookRootID}`, waitForReady, ignoreElements, } = (parameters.creevey ?? {}); (0, logger_js_1.logger)().debug(`Switching to story ${chalk_1.default.cyan(title)}/${chalk_1.default.cyan(name)} by id ${chalk_1.default.magenta(id)}`); if (captureElement) Object.defineProperty(this, 'captureElement', { enumerable: true, configurable: true, get: () => this.browser.findElement(selenium_webdriver_1.By.css(captureElement)), }); else Reflect.deleteProperty(this, 'captureElement'); this.takeScreenshot = () => takeScreenshot(this.browser, captureElement, ignoreElements); this.updateStoryArgs = (updatedArgs) => updateStoryArgs(this.browser, story, updatedArgs); this.testScope.reverse(); let storyPlayResolver; let waitForComplete = new Promise((resolve) => (storyPlayResolver = resolve)); const unsubscribe = (0, messages_js_1.subscribeOn)('stories', (message) => { if (message.type != 'capture') return; const { payload = {}, payload: { imageName } = {} } = message; void takeScreenshot(this.browser, payload.captureElement ?? captureElement, payload.ignoreElements ?? ignoreElements).then((screenshot) => { this.screenshots.push({ imageName, screenshot }); void this.browser .executeAsyncScript(function (callback) { window.__CREEVEY_HAS_PLAY_COMPLETED_YET__(callback); }) .then((isCompleted) => { storyPlayResolver(isCompleted); }); (0, messages_js_1.emitStoriesMessage)({ type: 'capture' }); }); }); await updateBrowserGlobalVariables(this.browser); await resetMousePosition(this.browser); const isCaptureCalled = await selectStory(this.browser, id, waitForReady); if (isCaptureCalled) { while (!(await waitForComplete)) { waitForComplete = new Promise((resolve) => (storyPlayResolver = resolve)); } } unsubscribe(); (0, logger_js_1.logger)().debug(`Story ${chalk_1.default.magenta(id)} ready for capturing`); } async function insertIgnoreStyles(browser, ignoreElements) { const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean); if (!ignoreSelectors.length) return null; (0, logger_js_1.logger)().debug('Hiding ignored elements before capturing'); return await browser.executeScript(function (ignoreSelectors) { return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors); }, ignoreSelectors); } async function removeIgnoreStyles(browser, ignoreStyles) { if (ignoreStyles) { (0, logger_js_1.logger)().debug('Revert hiding ignored elements'); await browser.executeScript(function (ignoreStyles) { window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles); }, ignoreStyles); } } //# sourceMappingURL=browser.js.map