From 19393238b7432f59d3304aaa9c36fc5473d915f7 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 21 Jun 2024 15:36:12 +0200 Subject: [PATCH 1/4] Fix: Combine tags correctly when transforming story files --- src/csf/transformCsf.ts | 10 ++++++++-- src/playwright/transformPlaywrightJson.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/csf/transformCsf.ts b/src/csf/transformCsf.ts index 27633bbc..ebac4a4b 100644 --- a/src/csf/transformCsf.ts +++ b/src/csf/transformCsf.ts @@ -2,7 +2,7 @@ import { loadCsf } from '@storybook/csf-tools'; import * as t from '@babel/types'; import generate from '@babel/generator'; -import { toId, storyNameFromExport } from '@storybook/csf'; +import { toId, storyNameFromExport, combineTags } from '@storybook/csf'; import dedent from 'ts-dedent'; import { getTagOptions } from '../util/getTagOptions'; @@ -126,7 +126,13 @@ export const transformCsf = ( acc[key].play = annotations.play; } - acc[key].tags = csf._stories[key].tags || csf.meta?.tags || []; + acc[key].tags = combineTags( + 'test', + 'dev', + ...(csf.meta?.tags || []), + ...(csf._stories[key].tags || []) + ); + return acc; }, {} diff --git a/src/playwright/transformPlaywrightJson.ts b/src/playwright/transformPlaywrightJson.ts index c22bea24..c3e4e5c6 100644 --- a/src/playwright/transformPlaywrightJson.ts +++ b/src/playwright/transformPlaywrightJson.ts @@ -120,7 +120,7 @@ export const transformPlaywrightJson = (index: V3StoriesIndex | V4Index | Unsupp Object.values((index as V3StoriesIndex).stories) ); titleIdToEntries = v3TitleMapToV4TitleMap(titleIdToStories); - // v4 and v5 are pretty much similar, so we process it in the same way + // v4 and v5 are pretty much similar, so we process it in the same way } else if (index.v === 4 || index.v === 5) { // TODO: Once Storybook 8.0 is released, we should only support v4 and higher titleIdToEntries = groupByTitleId(Object.values((index as V4Index).entries)); From 25598645b49c160008e984f2f5cfc4f0553940b7 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 21 Jun 2024 18:16:11 +0200 Subject: [PATCH 2/4] merge tags from preview annotations with meta and story tags --- .storybook/preview.ts | 8 +- src/csf/transformCsf.ts | 6 +- src/playwright/transformPlaywright.test.ts | 101 ++++++++++++++++++++- src/playwright/transformPlaywright.ts | 2 + src/test-storybook.ts | 18 +++- 5 files changed, 128 insertions(+), 7 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 9cfd9258..a1cc7482 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,3 +1,4 @@ +import type { Preview } from '@storybook/react'; import { isTestRunner } from './is-test-runner'; const withSkippableTests = (StoryFn, { parameters }) => { @@ -8,4 +9,9 @@ const withSkippableTests = (StoryFn, { parameters }) => { return StoryFn(); }; -export const decorators = [withSkippableTests]; +const preview: Preview = { + tags: ['global-tag'], + decorators: [withSkippableTests], +}; + +export default preview; diff --git a/src/csf/transformCsf.ts b/src/csf/transformCsf.ts index ebac4a4b..06ca70ac 100644 --- a/src/csf/transformCsf.ts +++ b/src/csf/transformCsf.ts @@ -108,7 +108,8 @@ export const transformCsf = ( beforeEachPrefixer, insertTestIfEmpty, makeTitle, - }: TransformOptions + previewAnnotations = { tags: [] }, + }: TransformOptions & { previewAnnotations?: Record } ) => { const { includeTags, excludeTags, skipTags } = getTagOptions(); @@ -130,7 +131,8 @@ export const transformCsf = ( 'test', 'dev', ...(csf.meta?.tags || []), - ...(csf._stories[key].tags || []) + ...(csf._stories[key].tags || []), + ...previewAnnotations.tags ); return acc; diff --git a/src/playwright/transformPlaywright.test.ts b/src/playwright/transformPlaywright.test.ts index 2e43f5f7..2e503687 100644 --- a/src/playwright/transformPlaywright.test.ts +++ b/src/playwright/transformPlaywright.test.ts @@ -42,6 +42,7 @@ describe('Playwright', () => { delete process.env.STORYBOOK_INCLUDE_TAGS; delete process.env.STORYBOOK_EXCLUDE_TAGS; delete process.env.STORYBOOK_SKIP_TAGS; + delete process.env.STORYBOOK_PREVIEW_TAGS; }); describe('tag filtering mechanism', () => { @@ -324,14 +325,17 @@ describe('Playwright', () => { `); }); it('should work in conjunction with includeTags, excludeTags and skipTags', () => { - process.env.STORYBOOK_INCLUDE_TAGS = 'play,design'; + process.env.STORYBOOK_INCLUDE_TAGS = 'play,design,global-tag'; process.env.STORYBOOK_SKIP_TAGS = 'skip'; process.env.STORYBOOK_EXCLUDE_TAGS = 'exclude'; + process.env.STORYBOOK_PREVIEW_TAGS = 'global-tag'; + // Should result in: // - A being excluded // - B being included, but skipped // - C being included - // - D being excluded + // - D being included + // - E being excluded expect( transformPlaywright( dedent` @@ -339,7 +343,8 @@ describe('Playwright', () => { export const A = { tags: ['play', 'exclude'] }; export const B = { tags: ['play', 'skip'] }; export const C = { tags: ['design'] }; - export const D = { }; + export const D = { tags: ['global-tag'] }; + export const E = { }; `, filename ) @@ -436,6 +441,96 @@ describe('Playwright', () => { } }); }); + describe("D", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-foo-bar--d", + title: "Example/foo/bar", + name: "D" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-foo-bar--d" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/foo/bar"}/\${"D"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); + describe("E", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-foo-bar--e", + title: "Example/foo/bar", + name: "E" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-foo-bar--e" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/foo/bar"}/\${"E"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); }); } `); diff --git a/src/playwright/transformPlaywright.ts b/src/playwright/transformPlaywright.ts index f74699ae..fce02b79 100644 --- a/src/playwright/transformPlaywright.ts +++ b/src/playwright/transformPlaywright.ts @@ -75,11 +75,13 @@ const makeTitleFactory = (filename: string) => { }; export const transformPlaywright = (src: string, filename: string) => { + const tags = process.env.STORYBOOK_PREVIEW_TAGS?.split(',') ?? []; const transformOptions = { testPrefixer, insertTestIfEmpty: true, clearBody: true, makeTitle: makeTitleFactory(filename), + previewAnnotations: { tags }, }; const result = transformCsf(src, transformOptions); diff --git a/src/test-storybook.ts b/src/test-storybook.ts index 714b04f5..1d78deea 100644 --- a/src/test-storybook.ts +++ b/src/test-storybook.ts @@ -5,7 +5,7 @@ import { execSync } from 'child_process'; import fetch from 'node-fetch'; import canBindToHost from 'can-bind-to-host'; import dedent from 'ts-dedent'; -import path from 'path'; +import path, { join, resolve } from 'path'; import tempy from 'tempy'; import { JestOptions, getCliOptions } from './util/getCliOptions'; @@ -15,6 +15,8 @@ import { transformPlaywrightJson } from './playwright/transformPlaywrightJson'; import { glob } from 'glob'; import { TestRunnerConfig } from './playwright/hooks'; +import { getInterpretedFile } from '@storybook/core-common'; +import { readConfig } from '@storybook/csf-tools'; // Do this as the first thing so that any code reading it knows the right env. process.env.BABEL_ENV = 'test'; @@ -260,6 +262,16 @@ function warnOnce(message: string) { }; } +const extractTagsFromPreview = async (configDir = '.storybook') => { + const previewConfigPath = getInterpretedFile(join(resolve(configDir), 'preview')); + + if (!previewConfigPath) return []; + const previewConfig = await readConfig(previewConfigPath); + const tags = previewConfig.getFieldValue(['tags']) ?? []; + + return tags.join(','); +}; + const main = async () => { const { jestOptions, runnerOptions } = getCliOptions(); @@ -368,6 +380,10 @@ const main = async () => { const { storiesPaths, lazyCompilation } = getStorybookMetadata(); process.env.STORYBOOK_STORIES_PATTERN = storiesPaths; + // 1 - We extract tags from preview file statically like it's done by the Storybook indexer. We only do this in non-index-json mode because it's not needed in that mode + // 2 - We pass it via env variable to avoid having to use async code in the babel plugin + process.env.STORYBOOK_PREVIEW_TAGS = await extractTagsFromPreview(runnerOptions.configDir); + if (lazyCompilation && isLocalStorybookIp) { log( `You're running Storybook with lazy compilation enabled, and will likely cause issues with the test runner locally. Consider disabling 'lazyCompilation' in ${runnerOptions.configDir}/main.js when running 'test-storybook' locally.` From 5da6582ac2610f78996fc53ceea79be6aeddaafb Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 21 Jun 2024 18:51:38 +0200 Subject: [PATCH 3/4] include "test" as default filter --- README.md | 2 +- src/playwright/transformPlaywright.test.ts | 131 ++++++++++++++++++ .../transformPlaywrightJson.test.ts | 74 +++++++++- src/playwright/transformPlaywrightJson.ts | 43 +++++- src/util/getTagOptions.ts | 2 +- 5 files changed, 242 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1e208862..8dcb96c8 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ module.exports = { ## Filtering tests (experimental) -You might want to skip certain stories in the test-runner, run tests only against a subset of stories, or exclude certain stories entirely from your tests. This is possible via the `tags` annotation. +You might want to skip certain stories in the test-runner, run tests only against a subset of stories, or exclude certain stories entirely from your tests. This is possible via the `tags` annotation. By default, the test-runner includes every story with the `"test"` tag. This tag is included by default in Storybook 8 for all stories, unless the user tells otherwise via [tag negation](https://storybook.js.org/docs/writing-stories/tags#removing-tags). This annotation can be part of a story, therefore only applying to it, or the component meta (the default export), which applies to all stories in the file: diff --git a/src/playwright/transformPlaywright.test.ts b/src/playwright/transformPlaywright.test.ts index 2e503687..0c5cdc91 100644 --- a/src/playwright/transformPlaywright.test.ts +++ b/src/playwright/transformPlaywright.test.ts @@ -535,6 +535,137 @@ describe('Playwright', () => { } `); }); + it('should work with tag negation', () => { + process.env.STORYBOOK_INCLUDE_TAGS = 'play'; + // Should result in: + // - A being included + // - B being excluded + expect( + transformPlaywright( + dedent` + export default { title: 'foo/bar', component: Button, tags: ['play'] }; + export const A = { }; + export const B = { tags: ['!play'] }; + `, + filename + ) + ).toMatchInlineSnapshot(` + if (!require.main) { + describe("Example/foo/bar", () => { + describe("A", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-foo-bar--a", + title: "Example/foo/bar", + name: "A" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-foo-bar--a" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/foo/bar"}/\${"A"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); + }); + } + `); + }); + it('should include "test" tag by default', () => { + // Should result in: + // - A being included + // - B being excluded + expect( + transformPlaywright( + dedent` + export default { title: 'foo/bar', component: Button }; + export const A = { }; + export const B = { tags: ['!test'] }; + `, + filename + ) + ).toMatchInlineSnapshot(` + if (!require.main) { + describe("Example/foo/bar", () => { + describe("A", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-foo-bar--a", + title: "Example/foo/bar", + name: "A" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-foo-bar--a" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/foo/bar"}/\${"A"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); + }); + } + `); + }); it('should no op when includeTags is passed but not matched', () => { process.env.STORYBOOK_INCLUDE_TAGS = 'play'; expect( diff --git a/src/playwright/transformPlaywrightJson.test.ts b/src/playwright/transformPlaywrightJson.test.ts index 2d16d735..e3712d82 100644 --- a/src/playwright/transformPlaywrightJson.test.ts +++ b/src/playwright/transformPlaywrightJson.test.ts @@ -25,17 +25,19 @@ describe('Playwright Json', () => { id: 'example-header--logged-in', title: 'Example/Header', name: 'Logged In', - tags: ['play-fn'], + tags: ['test', 'play-fn'], }, 'example-header--logged-out': { id: 'example-header--logged-out', title: 'Example/Header', name: 'Logged Out', + tags: ['test'], }, 'example-page--logged-in': { id: 'example-page--logged-in', title: 'Example/Page', name: 'Logged In', + tags: ['test'], }, }, } satisfies V4Index; @@ -656,6 +658,76 @@ describe('Playwright Json', () => { } `); }); + + it('should include "test" tag by default', () => { + process.env.STORYBOOK_INCLUDE_TAGS = 'test'; + const input = { + v: 3, + stories: { + 'example-page--logged-in': { + id: 'example-page--logged-in', + title: 'Example/Page', + name: 'Logged In', + parameters: { + __id: 'example-page--logged-in', + docsOnly: false, + fileName: './stories/basic/Page.stories.js', + }, + }, + }, + } satisfies V3StoriesIndex; + expect(transformPlaywrightJson(input)).toMatchInlineSnapshot(` + { + "example-page": "describe("Example/Page", () => { + describe("Logged In", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-page--logged-in", + title: "Example/Page", + name: "Logged In" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-page--logged-in" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/Page"}/\${"Logged In"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); + });", + } + `); + }); }); }); diff --git a/src/playwright/transformPlaywrightJson.ts b/src/playwright/transformPlaywrightJson.ts index c3e4e5c6..1777c85a 100644 --- a/src/playwright/transformPlaywrightJson.ts +++ b/src/playwright/transformPlaywrightJson.ts @@ -59,6 +59,11 @@ export const makeDescribe = (title: string, stmts: t.Statement[]) => { ); }; +type V3Story = Omit & { parameters?: StoryParameters }; +export type V3StoriesIndex = { + v: 3; + stories: Record; +}; type V4Entry = { type?: 'story' | 'docs'; id: StoryId; @@ -71,17 +76,18 @@ export type V4Index = { entries: Record; }; +type V5Entry = V4Entry & { tags: string[] }; +export type V5Index = { + v: 5; + entries: Record; +}; + type StoryParameters = { __id: StoryId; docsOnly?: boolean; fileName?: string; }; -type V3Story = Omit & { parameters?: StoryParameters }; -export type V3StoriesIndex = { - v: 3; - stories: Record; -}; export type UnsupportedVersion = { v: number }; const isV3DocsOnly = (stories: V3Story[]) => stories.length === 1 && stories[0].name === 'Page'; @@ -93,6 +99,7 @@ function v3TitleMapToV4TitleMap(titleIdToStories: Record) { ({ parameters, ...story }) => ({ type: isV3DocsOnly(stories) ? 'docs' : 'story', + tags: isV3DocsOnly(stories) ? [] : ['test', 'dev'], ...story, }) satisfies V4Entry ), @@ -100,6 +107,26 @@ function v3TitleMapToV4TitleMap(titleIdToStories: Record) { ); } +/** + * Storybook 8.0 and below did not automatically tag stories with 'dev'. + * Therefore Storybook 8.1 and above would not show composed 8.0 stories by default. + * This function adds the 'dev' tag to all stories in the index to workaround this issue. + */ +function v4TitleMapToV5TitleMap(titleIdToStories: Record) { + return Object.fromEntries( + Object.entries(titleIdToStories).map(([id, stories]) => [ + id, + stories.map( + (story) => + ({ + ...story, + tags: story.tags ? ['test', 'dev', ...story.tags] : ['test', 'dev'], + }) satisfies V4Entry + ), + ]) + ); +} + function groupByTitleId(entries: T[]) { return entries.reduce>((acc, entry) => { const titleId = toId(entry.title); @@ -120,10 +147,12 @@ export const transformPlaywrightJson = (index: V3StoriesIndex | V4Index | Unsupp Object.values((index as V3StoriesIndex).stories) ); titleIdToEntries = v3TitleMapToV4TitleMap(titleIdToStories); - // v4 and v5 are pretty much similar, so we process it in the same way - } else if (index.v === 4 || index.v === 5) { + } else if (index.v === 4) { // TODO: Once Storybook 8.0 is released, we should only support v4 and higher titleIdToEntries = groupByTitleId(Object.values((index as V4Index).entries)); + titleIdToEntries = v4TitleMapToV5TitleMap(titleIdToEntries); + } else if (index.v === 5) { + titleIdToEntries = groupByTitleId(Object.values((index as V4Index).entries)); } else { throw new Error(`Unsupported version ${index.v}`); } diff --git a/src/util/getTagOptions.ts b/src/util/getTagOptions.ts index 1bbdad8c..0a72d942 100644 --- a/src/util/getTagOptions.ts +++ b/src/util/getTagOptions.ts @@ -15,7 +15,7 @@ export function getTagOptions() { const config = getTestRunnerConfig(); let tagOptions = { - includeTags: config?.tags?.include || [], + includeTags: config?.tags?.include || ['test'], excludeTags: config?.tags?.exclude || [], skipTags: config?.tags?.skip || [], } as TagOptions; From a54e616b30dc45cd3dc73df6dc6923ab81ca51a9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 21 Jun 2024 19:19:43 +0200 Subject: [PATCH 4/4] fix tags precedence --- src/csf/transformCsf.ts | 4 +- src/playwright/transformPlaywright.test.ts | 52 +++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/csf/transformCsf.ts b/src/csf/transformCsf.ts index 06ca70ac..b8c19cdf 100644 --- a/src/csf/transformCsf.ts +++ b/src/csf/transformCsf.ts @@ -130,9 +130,9 @@ export const transformCsf = ( acc[key].tags = combineTags( 'test', 'dev', + ...previewAnnotations.tags, ...(csf.meta?.tags || []), - ...(csf._stories[key].tags || []), - ...previewAnnotations.tags + ...(csf._stories[key].tags || []) ); return acc; diff --git a/src/playwright/transformPlaywright.test.ts b/src/playwright/transformPlaywright.test.ts index 0c5cdc91..4c887fc4 100644 --- a/src/playwright/transformPlaywright.test.ts +++ b/src/playwright/transformPlaywright.test.ts @@ -536,16 +536,19 @@ describe('Playwright', () => { `); }); it('should work with tag negation', () => { - process.env.STORYBOOK_INCLUDE_TAGS = 'play'; + process.env.STORYBOOK_INCLUDE_TAGS = 'play,test'; + process.env.STORYBOOK_PREVIEW_TAGS = '!test'; // Should result in: // - A being included - // - B being excluded + // - B being excluded because it has no play nor test tag (removed by negation in preview tags) + // - C being included because it has test tag (overwritten via story tags) expect( transformPlaywright( dedent` export default { title: 'foo/bar', component: Button, tags: ['play'] }; export const A = { }; export const B = { tags: ['!play'] }; + export const C = { tags: ['!play', 'test'] }; `, filename ) @@ -597,6 +600,51 @@ describe('Playwright', () => { } }); }); + describe("C", () => { + it("smoke-test", async () => { + const testFn = async () => { + const context = { + id: "example-foo-bar--c", + title: "Example/foo/bar", + name: "C" + }; + if (globalThis.__sbPreVisit) { + await globalThis.__sbPreVisit(page, context); + } + const result = await page.evaluate(({ + id, + hasPlayFn + }) => __test(id, hasPlayFn), { + id: "example-foo-bar--c" + }); + if (globalThis.__sbPostVisit) { + await globalThis.__sbPostVisit(page, context); + } + if (globalThis.__sbCollectCoverage) { + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + if (!isCoverageSetupCorrectly) { + throw new Error(\`[Test runner] An error occurred when evaluating code coverage: + The code in this story is not instrumented, which means the coverage setup is likely not correct. + More info: https://github.com/storybookjs/test-runner#setting-up-code-coverage\`); + } + await jestPlaywright.saveCoverage(page); + } + return result; + }; + try { + await testFn(); + } catch (err) { + if (err.toString().includes('Execution context was destroyed')) { + console.log(\`An error occurred in the following story, most likely because of a navigation: "\${"Example/foo/bar"}/\${"C"}". Retrying...\`); + await jestPlaywright.resetPage(); + await globalThis.__sbSetupPage(globalThis.page, globalThis.context); + await testFn(); + } else { + throw err; + } + } + }); + }); }); } `);