Skip to content

Commit

Permalink
Core: Add Reporting API
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinpalkovic committed Nov 13, 2024
1 parent 53ffd11 commit 06f7d0f
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Passing: Story = {
// TODO: Remove after prototyping
beforeEach: async ({ reporting }) => {
reporting.addReport({
id: 'a11y',
status: 'passed',
version: 1,
result: {
violations: [{ id: 'a11y', impact: 'critical', description: 'A11y violation' }],
},
});
},
args: {
interactions: getInteractions(CallStates.DONE),
},
Expand Down
17 changes: 15 additions & 2 deletions code/addons/test/src/node/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
TestingModuleProgressReportPayload,
TestingModuleProgressReportProgress,
} from 'storybook/internal/core-events';
import type { Report } from 'storybook/internal/preview-api';

import type { API_StatusUpdate } from '@storybook/types';

Expand All @@ -26,13 +27,15 @@ export type TestResultResult =
storyId: string;
testRunId: string;
duration: number;
reports: Report[];
}
| {
status: 'failed';
storyId: string;
duration: number;
testRunId: string;
failureMessages: string[];
reports: Report[];
};

export type TestResult = {
Expand Down Expand Up @@ -113,16 +116,26 @@ export class StorybookReporter implements Reporter {

const status = StatusMap[t.result?.state || t.mode] || 'skipped';
const storyId = (t.meta as any).storyId as string;
const reports = (t.meta as any).reports as Report[];
const duration = t.result?.duration || 0;
const testRunId = this.start.toString();

switch (status) {
case 'passed':
case 'pending':
return [{ status, storyId, duration, testRunId } as TestResultResult];
return [{ status, storyId, duration, testRunId, reports } as TestResultResult];
case 'failed':
const failureMessages = t.result?.errors?.map((e) => e.stack || e.message) || [];
return [{ status, storyId, duration, failureMessages, testRunId } as TestResultResult];
return [
{
status,
storyId,
duration,
failureMessages,
testRunId,
reports,
} as TestResultResult,
];
default:
return [];
}
Expand Down
8 changes: 6 additions & 2 deletions code/addons/test/src/vitest-plugin/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* eslint-disable no-underscore-dangle */
import { type RunnerTask, type TaskContext, type TaskMeta, type TestContext } from 'vitest';

import { composeStory } from 'storybook/internal/preview-api';
import { type Report, composeStory } from 'storybook/internal/preview-api';
import type { ComponentAnnotations, ComposedStoryFn } from 'storybook/internal/types';

import { setViewport } from './viewports';
Expand All @@ -22,10 +22,14 @@ export const testStory = (

context.story = composedStory;

const _task = context.task as RunnerTask & { meta: TaskMeta & { storyId: string } };
const _task = context.task as RunnerTask & {
meta: TaskMeta & { storyId: string; reports: Report[] };
};
_task.meta.storyId = composedStory.id;

await setViewport(composedStory.parameters, composedStory.globals);
await composedStory.run();

_task.meta.reports = composedStory.reporting.reports;
};
};
2 changes: 2 additions & 0 deletions code/core/src/core-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum events {
STORY_CHANGED = 'storyChanged',
STORY_UNCHANGED = 'storyUnchanged',
STORY_RENDERED = 'storyRendered',
STORY_COMPLETED = 'storyCompleted',
STORY_MISSING = 'storyMissing',
STORY_ERRORED = 'storyErrored',
STORY_THREW_EXCEPTION = 'storyThrewException',
Expand Down Expand Up @@ -140,6 +141,7 @@ export const {
STORY_PREPARED,
STORY_RENDER_PHASE_CHANGED,
STORY_RENDERED,
STORY_COMPLETED,
STORY_SPECIFIED,
STORY_THREW_EXCEPTION,
STORY_UNCHANGED,
Expand Down
3 changes: 3 additions & 0 deletions code/core/src/manager/globals/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ export default {
'STORIES_EXPAND_ALL',
'STORY_ARGS_UPDATED',
'STORY_CHANGED',
'STORY_COMPLETED',
'STORY_ERRORED',
'STORY_INDEX_INVALIDATED',
'STORY_MISSING',
Expand Down Expand Up @@ -861,6 +862,7 @@ export default {
'STORIES_EXPAND_ALL',
'STORY_ARGS_UPDATED',
'STORY_CHANGED',
'STORY_COMPLETED',
'STORY_ERRORED',
'STORY_INDEX_INVALIDATED',
'STORY_MISSING',
Expand Down Expand Up @@ -925,6 +927,7 @@ export default {
'STORIES_EXPAND_ALL',
'STORY_ARGS_UPDATED',
'STORY_CHANGED',
'STORY_COMPLETED',
'STORY_ERRORED',
'STORY_INDEX_INVALIDATED',
'STORY_MISSING',
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/preview-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ export { createPlaywrightTest } from './modules/store/csf/portable-stories';
export type { PropDescriptor } from './store';

/** STORIES API */
export { StoryStore } from './store';
export { StoryStore, type Report, ReporterAPI } from './store';
export { Preview, PreviewWeb, PreviewWithSelection, UrlStore, WebView } from './preview-web';
export type { SelectionStore, View } from './preview-web';
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {

import {
PLAY_FUNCTION_THREW_EXCEPTION,
STORY_COMPLETED,
STORY_RENDERED,
STORY_RENDER_PHASE_CHANGED,
UNHANDLED_ERRORS_WHILE_PLAYING,
Expand All @@ -34,6 +35,7 @@ export type RenderPhase =
| 'playing'
| 'played'
| 'completed'
| 'finished'
| 'aborted'
| 'errored';

Expand Down Expand Up @@ -288,7 +290,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
const ignoreUnhandledErrors =
this.story.parameters?.test?.dangerouslyIgnoreUnhandledErrors === true;

const unhandledErrors: Set<unknown> = new Set();
const unhandledErrors: Set<unknown> = new Set<unknown>();
const onError = (event: ErrorEvent | PromiseRejectionEvent) =>
unhandledErrors.add('error' in event ? event.error : event.reason);

Expand Down Expand Up @@ -349,9 +351,30 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
await this.runPhase(abortSignal, 'completed', async () =>
this.channel.emit(STORY_RENDERED, id)
);

// The event name 'completed' is unfortunately already reserved by the STORY_RENDERED event
await this.runPhase(abortSignal, 'finished', async () =>
this.channel.emit(STORY_COMPLETED, {
storyId: id,
unhandledExceptions: !ignoreUnhandledErrors
? Array.from(unhandledErrors).map(serializeError)
: [],
status: !ignoreUnhandledErrors && unhandledErrors.size > 0 ? 'error' : 'success',
reporters: context.reporting.reports,
})
);
} catch (err) {
this.phase = 'errored';
this.callbacks.showException(err as Error);

Check failure on line 368 in code/core/src/preview-api/modules/preview-web/render/StoryRender.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/preview-api/modules/preview-web/render/StoryRender.test.ts > StoryRender > does run play function if passed autoplay=true

TypeError: this.callbacks.showException is not a function ❯ StoryRender.render src/preview-api/modules/preview-web/render/StoryRender.ts:368:22 ❯ src/preview-api/modules/preview-web/render/StoryRender.test.ts:75:5

Check failure on line 368 in code/core/src/preview-api/modules/preview-web/render/StoryRender.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/preview-api/modules/preview-web/render/StoryRender.test.ts > StoryRender > does not run play function if passed autoplay=false

TypeError: this.callbacks.showException is not a function ❯ StoryRender.render src/preview-api/modules/preview-web/render/StoryRender.ts:368:22 ❯ src/preview-api/modules/preview-web/render/StoryRender.test.ts:92:5

Check failure on line 368 in code/core/src/preview-api/modules/preview-web/render/StoryRender.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/preview-api/modules/preview-web/render/StoryRender.test.ts > StoryRender > calls mount if play function does not destructure mount

TypeError: this.callbacks.showException is not a function ❯ StoryRender.render src/preview-api/modules/preview-web/render/StoryRender.ts:368:22 ❯ src/preview-api/modules/preview-web/render/StoryRender.test.ts:155:5

Check failure on line 368 in code/core/src/preview-api/modules/preview-web/render/StoryRender.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/preview-api/modules/preview-web/render/StoryRender.test.ts > StoryRender > does not call mount twice if mount called in play function

TypeError: this.callbacks.showException is not a function ❯ StoryRender.render src/preview-api/modules/preview-web/render/StoryRender.ts:368:22 ❯ src/preview-api/modules/preview-web/render/StoryRender.test.ts:177:5

Check failure on line 368 in code/core/src/preview-api/modules/preview-web/render/StoryRender.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/preview-api/modules/preview-web/render/StoryRender.test.ts > StoryRender > enters rendering phase during play if play function calls mount

TypeError: this.callbacks.showException is not a function ❯ StoryRender.render src/preview-api/modules/preview-web/render/StoryRender.ts:368:22 ❯ src/preview-api/modules/preview-web/render/StoryRender.test.ts:254:5

await this.runPhase(abortSignal, 'finished', async () =>
this.channel.emit(STORY_COMPLETED, {
storyId: id,
unhandledExceptions: [serializeError(err)],
status: 'error',
reporters: [],
})
);
}

// If a rerender was enqueued during the render, clear the queue and render again
Expand Down
3 changes: 3 additions & 0 deletions code/core/src/preview-api/modules/store/StoryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
prepareStory,
processCSFFile,
} from './csf';
import { ReporterAPI } from './reporter-api';

export function picky<T extends Record<string, any>, K extends keyof T>(
obj: T,
Expand Down Expand Up @@ -253,12 +254,14 @@ export class StoryStore<TRenderer extends Renderer> {
getStoryContext(story: PreparedStory<TRenderer>, { forceInitialArgs = false } = {}) {
const userGlobals = this.userGlobals.get();
const { initialGlobals } = this.userGlobals;
const reporting = new ReporterAPI();
return prepareContext({
...story,
args: forceInitialArgs ? story.initialArgs : this.args.get(story.id),
initialGlobals,
globalTypes: this.projectAnnotations.globalTypes,
userGlobals,
reporting,
globals: {
...userGlobals,
...story.storyGlobals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { MountMustBeDestructuredError } from '@storybook/core/preview-errors';
import { dedent } from 'ts-dedent';

import { HooksContext } from '../../../addons';
import { ReporterAPI } from '../reporter-api';
import { composeConfigs } from './composeConfigs';
import { getValuesFromArgTypes } from './getValuesFromArgTypes';
import { normalizeComponentAnnotations } from './normalizeComponentAnnotations';
Expand Down Expand Up @@ -146,12 +147,15 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
...story.storyGlobals,
};

const reporting = new ReporterAPI();

const initializeContext = () => {
const context: StoryContext<TRenderer> = prepareContext({
hooks: new HooksContext(),
globals,
args: { ...story.initialArgs },
viewMode: 'story',
reporting,
loaded: {},
abortSignal: new AbortController().signal,
step: (label, play) => story.runStep(label, play, context),
Expand Down Expand Up @@ -258,6 +262,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
argTypes: story.argTypes as StrictArgTypes<TArgs>,
play: playFunction!,
run,
reporting,
tags: story.tags,
}
);
Expand Down
1 change: 1 addition & 0 deletions code/core/src/preview-api/modules/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './decorators';
export * from './args';
export * from './autoTitle';
export * from './sortStories';
export * from './reporter-api';
14 changes: 14 additions & 0 deletions code/core/src/preview-api/modules/store/reporter-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface Report {
id: string;
version: number;
result: unknown;
status: 'failed' | 'passed' | 'warning';
}

export class ReporterAPI {
reports: Report[] = [];

async addReport(report: Report) {
this.reports.push(report);
}
}
2 changes: 2 additions & 0 deletions code/core/src/types/modules/composedStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Tag,
} from '@storybook/csf';

import type { ReporterAPI } from '../../preview-api';
import type {
AnnotatedStoryFn,
Args,
Expand Down Expand Up @@ -49,6 +50,7 @@ export type ComposedStoryFn<
storyName: string;
parameters: Parameters;
argTypes: StrictArgTypes<TArgs>;
reporting: ReporterAPI;
tags: Tag[];
globals: Globals;
};
Expand Down

0 comments on commit 06f7d0f

Please sign in to comment.