@percy/cypress
Version:
Cypress client library for visual testing with Percy
133 lines (113 loc) • 3.64 kB
JavaScript
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 };