Skip to content

Commit

Permalink
Merge pull request #59 from chromaui/steven/cypress-options
Browse files Browse the repository at this point in the history
Chromatic options for Cypress
  • Loading branch information
skitterm authored Jan 16, 2024
2 parents e240661 + 2372cdc commit cec4b20
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 39 deletions.
16 changes: 12 additions & 4 deletions packages/cypress/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ let watcher: Watcher = null;
let host = '';
let port = 0;

const setupNetworkListener = async (): Promise<null> => {
const setupNetworkListener = async ({
allowedDomains,
}: {
allowedDomains?: string[];
}): Promise<null> => {
try {
const { webSocketDebuggerUrl } = await Version({
host,
Expand All @@ -77,7 +81,7 @@ const setupNetworkListener = async (): Promise<null> => {
});

if (!watcher) {
watcher = new Watcher(cdp);
watcher = new Watcher(cdp, allowedDomains);
await watcher.watch();
}
} catch (err) {
Expand Down Expand Up @@ -110,7 +114,7 @@ interface TaskParams {
export const prepareArchives = async ({ action, payload }: TaskParams) => {
switch (action) {
case 'setup-network-listener':
return setupNetworkListener();
return setupNetworkListener(payload);
case 'save-archives':
return saveArchives(payload);
default:
Expand All @@ -133,13 +137,17 @@ export const onBeforeBrowserLaunch = (

if (portArg) {
[, portString] = portArg.split('=');
} else {
} else if (process.env.ELECTRON_EXTRA_LAUNCH_ARGS) {
// Electron doesn't pass along the address and port in the launch options, so we need to read the port from the
// environment variable that we'll require the user to use (this assumes the host will be 127.0.0.1).
const entry = process.env.ELECTRON_EXTRA_LAUNCH_ARGS.split(' ').find((item) =>
item.startsWith('--remote-debugging-port')
);
[, portString] = entry.split('=');
} else {
throw new Error(
'Please provide a port number \nExample: ELECTRON_EXTRA_LAUNCH_ARGS=--remote-debugging-port=<port-number> yarn cypress run'
);
}

port = parseInt(portString, 10);
Expand Down
22 changes: 20 additions & 2 deletions packages/cypress/src/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ beforeEach(() => {
// then cleaned up before the next test is run
// (see https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Aliases)
cy.wrap([]).as('manualSnapshots');
cy.task('prepareArchives', { action: 'setup-network-listener' });
cy.task('prepareArchives', {
action: 'setup-network-listener',
payload: { allowedDomains: Cypress.env('assetDomains') },
});
});

afterEach(() => {
if (Cypress.env('disableAutoSnapshot')) {
return;
}
// can we be sure this always fires after all the requests are back?
cy.document().then((doc) => {
const snap = snapshot(doc);
Expand All @@ -24,7 +30,19 @@ afterEach(() => {
testTitle: Cypress.currentTest.title,
domSnapshots: [...manualSnapshots, snap],
chromaticStorybookParams: {
diffThreshold: Cypress.env('diffThreshold'),
...(Cypress.env('diffThreshold') && { diffThreshold: Cypress.env('diffThreshold') }),
...(Cypress.env('delay') && { delay: Cypress.env('delay') }),
...(Cypress.env('diffIncludeAntiAliasing') && {
diffIncludeAntiAliasing: Cypress.env('diffIncludeAntiAliasing'),
}),
...(Cypress.env('diffThreshold') && { diffThreshold: Cypress.env('diffThreshold') }),
...(Cypress.env('forcedColors') && { forcedColors: Cypress.env('forcedColors') }),
...(Cypress.env('pauseAnimationAtEnd') && {
pauseAnimationAtEnd: Cypress.env('pauseAnimationAtEnd'),
}),
...(Cypress.env('prefersReducedMotion') && {
prefersReducedMotion: Cypress.env('prefersReducedMotion'),
}),
},
pageUrl: url,
},
Expand Down
6 changes: 2 additions & 4 deletions packages/cypress/tests/cypress/e2e/archiving-assets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ it('Assets / query params determine which asset is served', () => {
cy.visit('/asset-paths/query-params');
});

// TODO: Unskip when Cypress support achieves parity with Playwright
it.skip('Assets / asset doesnt prevent directory from being created', () => {
it('Assets / asset doesnt prevent directory from being created', () => {
cy.visit('/asset-paths/asset-at-directory-name');
});

Expand All @@ -27,8 +26,7 @@ it.skip('Assets / external asset is archived', () => {
cy.visit('/asset-paths/external-asset-archived');
});

// TODO: Unskip when Cypress support achieves parity with Playwright
it.skip('Assets / assets from css urls are archived', () => {
it('Assets / assets from css urls are archived', () => {
cy.visit('/asset-paths/css-urls');
});

Expand Down
19 changes: 19 additions & 0 deletions packages/cypress/tests/cypress/e2e/options.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
it('Options / delay', { env: { delay: 1200 } }, () => {
cy.visit('/options/delay');
});

it('Options / diff threshold', { env: { diffThreshold: 1 } }, () => {
cy.visit('/options/diff-threshold');
});

it('Options / pause animation at end', { env: { pauseAnimationAtEnd: true } }, () => {
cy.visit('/options/pause-animation-at-end');
});

it('Options / force high-contrast', { env: { forcedColors: 'active' } }, () => {
cy.visit('/options/forced-colors');
});

it('Options / prefers reduced motion', { env: { prefersReducedMotion: 'reduce' } }, () => {
cy.visit('/options/prefers-reduced-motion');
});
41 changes: 41 additions & 0 deletions packages/playwright/tests/options.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { test } from '../src';

test.describe(() => {
test.use({ delay: 1200 });

test('Options / delay', async ({ page }) => {
await page.goto('/options/delay');
});
});

test.describe(() => {
test.use({ diffThreshold: 1 });

test('Options / diff threshold', async ({ page }) => {
await page.goto('/options/diff-threshold');
});
});

test.describe(() => {
test.use({ pauseAnimationAtEnd: true });

test('Options / pause animation at end', async ({ page }) => {
await page.goto('/options/pause-animation-at-end');
});
});

test.describe(() => {
test.use({ forcedColors: 'active' });

test('Options / force high-contrast', async ({ page }) => {
await page.goto('/options/forced-colors');
});
});

test.describe(() => {
test.use({ prefersReducedMotion: 'reduce' });

test('Options / prefers reduced motion', async ({ page }) => {
await page.goto('/options/prefers-reduced-motion');
});
});
28 changes: 28 additions & 0 deletions test-server/fixtures/options/delay.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!doctype html>
<html>
<head>
<style>
/* don't show the image at the beginning */
img {
visibility: hidden;
animation-name: make-shown;
animation-duration: 2ms;
animation-delay: 1000ms;
animation-fill-mode: forwards;
}

@keyframes make-shown {
0% {
visibility: hidden;
}
100% {
visibility: visible;
}
}
</style>
</head>
<body>
<img src="/img?url=fixtures/blue.png"/>
<h4>The image should be snapshotted since we're waiting until the delayed-revealed image is visible.</h4>
</body>
</html>
22 changes: 22 additions & 0 deletions test-server/fixtures/options/diff-threshold.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<style>
h1 {
font-family: monospace;
}
</style>
</head>
<body>
<h1></h1>
<h4>Number above should have no diff, even though it switches between 6 and 8, since the diff threshold is so high</h4>

<script>
const randomInteger = Math.ceil(Math.random() * 10);
const isEven = randomInteger % 2 === 0;
// number is either 6 or 8... should always have no diff since diff threshold is so high
// and those 2 numbers are very similar in look
document.querySelector('h1').innerText = isEven ? 8 : 6;
</script>
</body>
</html>
13 changes: 13 additions & 0 deletions test-server/fixtures/options/forced-colors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<style>
h1 {
color: #eee;
}
</style>
</head>
<body>
<h1>This title is hard to read without high-contrast mode.</h1>
</body>
</html>
27 changes: 27 additions & 0 deletions test-server/fixtures/options/pause-animation-at-end.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<style>
img {
opacity: 0;
animation-name: make-shown;
/* image takes awhile to fade in */
animation-duration: 2s;
animation-fill-mode: forwards;
}

@keyframes make-shown {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<img src="/img?url=fixtures/blue.png"/>
<h4>The image should be snapshotted since we're forcing animations to the end (when the image is fully opaque).</h4>
</body>
</html>
19 changes: 19 additions & 0 deletions test-server/fixtures/options/prefers-reduced-motion.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<style>
h1 {
visibility: hidden;
}

@media (prefers-reduced-motion) {
h1 {
visibility: visible;
}
}
</style>
</head>
<body>
<h1>This message should appear when prefers-reduced-motion is enabled.</h1>
</body>
</html>
59 changes: 30 additions & 29 deletions test-server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,6 @@ const port = 3000;
const htmlIntro = `<!doctype html><html>`;
const htmlOutro = `</html>`;

// Pages
app.get('/', (req, res) => {
res.send(`${htmlIntro}<body>Testing</body>${htmlOutro}`);
});

app.get('/asset-paths', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/asset-paths.html'));
});

app.get('/ignore', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/dynamic-content.html'));
});

app.get('/forms', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/forms.html'));
});

// Send a redirect to the GET handler with the same path to ensure we're not caching POST responses and serving
// it instead of the real GET response.
app.post('/form-success', (req, res) => {
res.send(
`${htmlIntro}<head><meta http-equiv="refresh" content="0; URL=/form-success" /></head><body></body>${htmlOutro}`
);
});

app.get('/form-success', (req, res) => {
res.send(`${htmlIntro}<body><p>OK!</p></body>${htmlOutro}`);
});

// Assets

app.get('/css.urls.css', (req, res) => {
Expand Down Expand Up @@ -74,11 +45,41 @@ app.get('/background-img.png', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/purple.png'));
});

// Pages
app.get('/', (req, res) => {
res.send(`${htmlIntro}<body>Testing</body>${htmlOutro}`);
});

// Asset path pages
app.get('/asset-paths/:page', (req, res) => {
res.sendFile(path.join(__dirname, `fixtures/asset-paths/${req.params.page}.html`));
});

// Options pages
app.get('/options/:page', (req, res) => {
res.sendFile(path.join(__dirname, `fixtures/options/${req.params.page}.html`));
});

app.get('/ignore', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/dynamic-content.html'));
});

app.get('/forms', (req, res) => {
res.sendFile(path.join(__dirname, 'fixtures/forms.html'));
});

// Send a redirect to the GET handler with the same path to ensure we're not caching POST responses and serving
// it instead of the real GET response.
app.post('/form-success', (req, res) => {
res.send(
`${htmlIntro}<head><meta http-equiv="refresh" content="0; URL=/form-success" /></head><body></body>${htmlOutro}`
);
});

app.get('/form-success', (req, res) => {
res.send(`${htmlIntro}<body><p>OK!</p></body>${htmlOutro}`);
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});

0 comments on commit cec4b20

Please sign in to comment.