UNPKG

@percy/cypress

Version:

Cypress client library for visual testing with Percy

133 lines (113 loc) 3.64 kB
const utils = require('@percy/sdk-utils'); const { createRegion } = require('./createRegion'); // Collect client and environment information const sdkPkg = require('./package.json'); const CLIENT_INFO = `${sdkPkg.name}/${sdkPkg.version}`; const ENV_INFO = `cypress/${Cypress.version}`; // asset discovery should timeout before this // 1.5 times the 30 second nav timeout const CY_TIMEOUT = 30 * 1000 * 1.5; // Maybe set the CLI API address from the environment utils.percy.address = Cypress.env('PERCY_SERVER_ADDRESS'); // Use Cypress's http:request backend task utils.request.fetch = async function fetch(url, options) { options = { url, retryOnNetworkFailure: false, ...options }; return Cypress.backend('http:request', options); }; // Create Cypress log messages function cylog(message, meta) { Cypress.log({ name: 'percySnapshot', displayName: 'percy', consoleProps: () => meta, message }); } // Take a DOM snapshot and post it to the snapshot endpoint Cypress.Commands.add('percySnapshot', (name, options = {}) => { let log = utils.logger('cypress'); // if name is not passed if (typeof name === 'object') { options = name; name = undefined; } // Default name to test title name = name || cy.state('runnable').fullTitle(); const meta = { snapshot: { name: name, testCase: options.testCase } }; const withLog = async (func, context, _throw = true) => { try { return await func(); } catch (error) { log.error(`Got error while ${context}`, meta); log.error(error, meta); log.error(error.stack, meta); if (_throw) throw error; return error; } }; const withRetry = async (func) => { let num = 1; const maxNum = 3; const sleepTime = 1000; let error; while (num <= maxNum) { try { return await func(); } catch (e) { error = e; log.error(`Retrying... (${num}/${maxNum})`); await new Promise((res) => setTimeout(res, sleepTime)); } num += 1; } throw error; }; return cy.then({ timeout: CY_TIMEOUT }, async () => { if (Cypress.config('isInteractive') && !Cypress.config('enablePercyInteractiveMode')) { return cylog('Disabled in interactive mode', { details: 'use "cypress run" instead of "cypress open"', name }); } // Check if Percy is enabled if (!await utils.isPercyEnabled()) { return cylog('Not running', { name }); } await withLog(async () => { // Inject @percy/dom if (!window.PercyDOM) { // eslint-disable-next-line no-eval eval(await utils.fetchPercyDOM()); } }, 'injecting @percy/dom'); // Serialize and capture the DOM return cy.document({ log: false }).then({ timeout: CY_TIMEOUT }, async (dom) => { let domSnapshot = await withLog(() => { return window.PercyDOM.serialize({ ...options, dom }); }, 'taking dom snapshot'); const throwConfig = Cypress.config('percyThrowErrorOnFailure'); const _throw = throwConfig === undefined ? false : throwConfig; // Post the DOM snapshot to Percy let response = await withRetry(async () => await withLog(async () => { return await utils.postSnapshot({ ...options, environmentInfo: ENV_INFO, clientInfo: CLIENT_INFO, domSnapshot, url: dom.URL, name }); }, 'posting dom snapshot', _throw)); // Log the snapshot name on success cylog(name, meta); return response; }); }); }); module.exports = { createRegion };