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: Combine tags correctly when transforming story files #485

Merged
merged 4 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 7 additions & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Preview } from '@storybook/react';
import { isTestRunner } from './is-test-runner';

const withSkippableTests = (StoryFn, { parameters }) => {
Expand All @@ -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;
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
14 changes: 11 additions & 3 deletions src/csf/transformCsf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -108,7 +108,8 @@ export const transformCsf = (
beforeEachPrefixer,
insertTestIfEmpty,
makeTitle,
}: TransformOptions
previewAnnotations = { tags: [] },
}: TransformOptions & { previewAnnotations?: Record<string, any> }
) => {
const { includeTags, excludeTags, skipTags } = getTagOptions();

Expand All @@ -126,7 +127,14 @@ 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 || []),
...previewAnnotations.tags
yannbf marked this conversation as resolved.
Show resolved Hide resolved
);

return acc;
},
{}
Expand Down
232 changes: 229 additions & 3 deletions src/playwright/transformPlaywright.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -324,22 +325,26 @@ 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`
export default { title: 'foo/bar', component: Button };
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
)
Expand Down Expand Up @@ -436,6 +441,227 @@ 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;
}
}
});
});
});
}
`);
});
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;
}
}
});
});
});
}
`);
Expand Down
2 changes: 2 additions & 0 deletions src/playwright/transformPlaywright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading