Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 ✨Fix eval setCurrentStory error + make logging better #837

Merged
merged 5 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/snapshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ export async function* takeStorybookSnapshots(percy, callback, { baseUrl, flags
while (snapshots.length) {
try {
// use a single page to capture story snapshots without reloading
yield* withPage(percy, previewUrl, async function*(page) {
// loading an existing story instead of just iframe.html as that triggers `storyMissing` event
itsjwala marked this conversation as resolved.
Show resolved Hide resolved
// This in turn leads to promise rejection and failure
yield* withPage(percy, `${previewUrl}?id=${snapshots[0].id}&viewMode=story`, async function*(page) {
itsjwala marked this conversation as resolved.
Show resolved Hide resolved
// determines when to retry page crashes
lastCount = snapshots.length;

Expand Down
8 changes: 4 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export async function* withPage(percy, url, callback, retry) {
return yield* yieldTo(callback(page));
} catch (error) {
// if the page crashed and retry returns truthy, try again
if (error?.message?.includes('crashed') && retry?.()) {
if (error.message?.includes('crashed') && retry?.()) {
return yield* withPage(...arguments);
}

Expand Down Expand Up @@ -247,9 +247,9 @@ export function evalSetCurrentStory({ waitFor }, story) {
// resolve when rendered, reject on any other renderer event
return new Promise((resolve, reject) => {
channel.on('storyRendered', resolve);
channel.on('storyMissing', reject);
channel.on('storyErrored', reject);
channel.on('storyThrewException', reject);
channel.on('storyMissing', () => reject(new Error('Story Missing')));
Copy link
Contributor

@ninadbstack ninadbstack Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we get original error passed to us, if yes we can wrap and rethrow it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even tried -

      channel.on('storyMissing', (err) => {
        reject(err);
      });

But this again resulted in -
[percy] TypeError: Cannot read properties of undefined (reading 'message')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, just learned something new about JS which explains this better.
Whenever a function name is passed as callback, it calls the function by whatever parameters are passed.
So, in this case

channel.on('storyMissing', reject)

This would call reject with whatever parameters are passed to eventListener, and since nothing is passed, hence we reject with undefined resulting in above error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically story missing isnt passing anything ...
Also its not a new thing, its a standard thing that works across almost all languages ( except where brackets are not required for a function call, Ruby 👀 ), it works because its no different, it expects a function and you are passing a function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically story missing isnt passing anything ...

yes

Also its not a new thing, its a standard thing that works across almost all languages ( except where brackets are not required for a function call, Ruby 👀 ), it works because its no different, it expects a function and you are passing a function

Wasn't aware of this, thanks!

channel.on('storyErrored', () => reject(new Error('Story Errored')));
channel.on('storyThrewException', () => reject(new Error('Story Threw Exception')));
});
});
}
6 changes: 3 additions & 3 deletions test/storybook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,12 @@ describe('percy storybook', () => {

await expectAsync(storybook(['http://localhost:8000']))
// message contains the client stack trace
.toBeRejectedWithError(/^Story Error\n.*\/iframe\.html.*$/s);
.toBeRejectedWithError(/^Story Errored\n.*\/iframe\.html.*$/s);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Word change ? Is this same for v6 and v7

Copy link
Contributor Author

@nilshah98 nilshah98 Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a custom error that we are throwing.
Earlier in the test itself, we were mocking the error to be thrown, since storyErrored event was caught, but we reject the promise and passed nothing to it, so it returned undefined and fell back to the mocked error.

Okay, now I understand this a little better.
Earlier we were calling c (callback) with new Error('Story Error')

channel.on('storyErrored'. reject)

This would result in reject being called with the new Error('Story Error'), but since we have seen circumstances where nothing is passed to the callback which results in rejection with undefined, and hence added a custom message of Story Errored, instead of relying on default arguments.

Edit: Updated the mock to execute callback with no arguments, instead of new Error('Story Error')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored error throwing logic, so as to not update tests.


expect(logger.stderr).toEqual([
'[percy] Build not created',
// message contains the client stack trace
jasmine.stringMatching(/^\[percy\] Error: Story Error\n.*\/iframe\.html.*$/s)
jasmine.stringMatching(/^\[percy\] Error: Story Errored\n.*\/iframe\.html.*$/s)
]);
});

Expand Down Expand Up @@ -191,7 +191,7 @@ describe('percy storybook', () => {
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),
jasmine.stringMatching(/^\[percy\] Error: Story Errored\n.*\/iframe\.html.*$/s),
// does not create a build if all stories failed [ 1 in this case ]
'[percy] Build not created'
]);
Expand Down