Skip to content

Commit

Permalink
✨ Skip story on error and move to next stories (#819)
Browse files Browse the repository at this point in the history
* Now we skip story on error than crash

* Added a comment and fixed test
  • Loading branch information
ninadbstack committed Oct 13, 2023
1 parent 4ce82ec commit 7b02234
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 29 deletions.
82 changes: 53 additions & 29 deletions src/snapshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,38 +188,62 @@ export async function* takeStorybookSnapshots(percy, callback, { baseUrl, flags
// set storybook environment info
percy.client.addEnvironmentInfo(environmentInfo);

// use a single page to capture story snapshots without reloading
yield* withPage(percy, previewUrl, async function*(page) {
// determines when to retry page crashes
lastCount = snapshots.length;

while (snapshots.length) {
// separate story and snapshot options
let { id, args, globals, queryParams, ...options } = snapshots[0];

if (flags.dryRun || options.enableJavaScript || percy.config.snapshot.enableJavaScript) {
// when dry-running or when javascript is enabled, use the preview dom
options.domSnapshot = previewResource.content;
// We use an outer and inner loop on same snapshots.length
// - we create a new page and load one story on it at a time for snapshotting
// - if it throws exception then we want to catch it outside of `withPage` call as
// when `withPage` returns it closes the page
// - we want to make sure we close the page that had exception in story to make sure
// we dont reuse a page which is possibly in a weird state due to last exception
// - so post exception we come out of inner loop and skip the story, create new page
// using outer loop and continue next stories again on a new page
while (snapshots.length) {
try {
// use a single page to capture story snapshots without reloading
yield* withPage(percy, previewUrl, async function*(page) {
// determines when to retry page crashes
lastCount = snapshots.length;

while (snapshots.length) {
// separate story and snapshot options
let { id, args, globals, queryParams, ...options } = snapshots[0];

if (flags.dryRun || options.enableJavaScript || percy.config.snapshot.enableJavaScript) {
log.debug(`Loading story via previewResource: ${options.name}`);
// when dry-running or when javascript is enabled, use the preview dom
options.domSnapshot = previewResource.content;
} else {
log.debug(`Loading story: ${options.name}`);
// when not dry-running and javascript is not enabled, capture the story dom
yield page.eval(evalSetCurrentStory, { id, args, globals, queryParams });
/* istanbul ignore next: tested, but coverage is stripped */
let { dom, domSnapshot = dom } = yield page.snapshot(options);
options.domSnapshot = domSnapshot;
}

// validate without logging to prune all other options
PercyConfig.validate(options, '/snapshot/dom');
// snapshots are queued and do not need to be awaited on
percy.snapshot(options);
// discard this story snapshot when done
snapshots.shift();
}
}, () => {
log.debug(`Page crashed while loading story: ${snapshots[0].name}`);
// return true to retry as long as the length decreases
return lastCount > snapshots.length;
});
} catch (e) {
if (process.env.PERCY_SKIP_STORY_ON_ERROR === 'true') {
let { name } = snapshots[0];
log.error(`Failed to capture story: ${name}`);
log.error(e);
// ignore story
snapshots.shift();
} else {
// when not dry-running and javascript is not enabled, capture the story dom
yield page.eval(evalSetCurrentStory, { id, args, globals, queryParams });
/* istanbul ignore next: tested, but coverage is stripped */
let { dom, domSnapshot = dom } = yield page.snapshot(options);
options.domSnapshot = domSnapshot;
throw e;
}

// validate without logging to prune all other options
PercyConfig.validate(options, '/snapshot/dom');
// snapshots are queued and do not need to be awaited on
percy.snapshot(options);
// discard this story snapshot when done
snapshots.shift();
}
}, () => {
log.debug(`Page crashed while loading story: ${snapshots[0].name}`);
// return true to retry as long as the length decreases
return lastCount > snapshots.length;
});
}

// will stop once snapshots are done processing
yield* percy.yield.stop();
Expand Down
33 changes: 33 additions & 0 deletions test/storybook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,39 @@ describe('percy storybook', () => {
]);
});

describe('with PERCY_SKIP_STORY_ON_ERROR set to true', () => {
beforeAll(() => {
process.env.PERCY_SKIP_STORY_ON_ERROR = true;
});

afterAll(() => {
delete process.env.PERCY_SKIP_STORY_ON_ERROR;
});

it('skips the story and logs the error but does not break build', async () => {
server.reply('/iframe.html', () => [200, 'text/html', [
`<script>__STORYBOOK_PREVIEW__ = { async extract() {}, ${
'channel: { emit() {}, on: (a, c) => a === "storyErrored" && c(new Error("Story Error")) }'
} }</script>`,
`<script>__STORYBOOK_STORY_STORE__ = { raw: () => ${JSON.stringify([
{ id: '1', kind: 'foo', name: 'bar' }
])} }</script>`
].join('')]);

// does not reject
await storybook(['http://localhost:8000']);

// contains logs of story error
expect(logger.stderr).toEqual([
'[percy] Failed to capture story: foo: bar',
// error logs contain the client stack trace
jasmine.stringMatching(/^\[percy\] Error: Story Error\n.*\/iframe\.html.*$/s),
// does not create a build if all stories failed [ 1 in this case ]
'[percy] Build not created'
]);
});
});

it('uses the preview dom when javascript is enabled', async () => {
let previewDOM = '<p>This is the preview</p>';
let i = 0;
Expand Down

0 comments on commit 7b02234

Please sign in to comment.