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

Add stories.json support #31

Merged
merged 12 commits into from
Jan 28, 2022
10 changes: 5 additions & 5 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
stories: [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: ["@storybook/addon-essentials"],
stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials'],
features: {
buildStoriesJson: true,
},
};
93 changes: 66 additions & 27 deletions bin/test-storybook.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
//@ts-check
'use strict';

const urlExists = require('url-exists');
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');
const tempy = require('tempy');
const { transformPlaywrightJson } = require('../dist/cjs/playwright/transformPlaywrightJson');

const STORIES_JSON_TEST_JS = 'stories-json.test.js';

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
Expand All @@ -12,19 +18,19 @@ process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
process.on('unhandledRejection', (err) => {
throw err;
});

function sanitizeURL(url) {
let finalURL = url
let finalURL = url;
// prepend URL protocol if not there
if (finalURL.indexOf("http://") === -1 && finalURL.indexOf("https://") === -1) {
if (finalURL.indexOf('http://') === -1 && finalURL.indexOf('https://') === -1) {
finalURL = 'http://' + finalURL;
}

// remove iframe.html if present
finalURL = finalURL.replace(/iframe.html\s*$/, "");
finalURL = finalURL.replace(/iframe.html\s*$/, '');

// add forward slash at the end if not there
if (finalURL.slice(-1) !== '/') {
Expand All @@ -34,32 +40,65 @@ function sanitizeURL(url) {
return finalURL;
}

const targetURL = sanitizeURL(process.env.TARGET_URL || `http://localhost:6006`);

urlExists(targetURL, function (err, exists) {
if (!exists) {
console.error(`[test-storybook] It seems that your Storybook instance is not running at: ${targetURL}. Are you sure it's running?`)
process.exit(1)
}

executeJestPlaywright()
});

function executeJestPlaywright() {
const fs = require('fs');
const path = require('path');

async function executeJestPlaywright(args) {
const jest = require('jest');
let argv = process.argv.slice(2);
let argv = args.slice(2);

const jestConfigPath = fs.existsSync('test-runner-jest.config.js')
? 'test-runner-jest.config.js'
: path.resolve(__dirname, '../playwright/test-runner-jest.config.js')
: path.resolve(__dirname, '../playwright/test-runner-jest.config.js');

argv.push(
'--config',
jestConfigPath
);
argv.push('--config', jestConfigPath);

jest.run(argv);
await jest.run(argv);
}

async function checkStorybook(url) {
try {
const res = await fetch(url, { method: 'HEAD' });
if (res.status !== 200) throw new Error(`Unxpected status: ${res.status}`);
} catch (e) {
console.error(
`[test-storybook] It seems that your Storybook instance is not running at: ${url}. Are you sure it's running?`
);
process.exit(1);
}
}

async function fetchStoriesJson(url) {
const storiesJsonUrl = new URL('/stories.json', url).toString();
shilman marked this conversation as resolved.
Show resolved Hide resolved
let tmpFile;
try {
const res = await fetch(storiesJsonUrl);
const tmpDir = tempy.directory();
tmpFile = path.join(tmpDir, 'stories-json.test.js');
const json = await res.text();
const js = transformPlaywrightJson(json);
fs.writeFileSync(tmpFile, js);
} catch (err) {
console.error(`[test-storybook] Failed to fetch stories.json from ${storiesJsonUrl}.`);
yannbf marked this conversation as resolved.
Show resolved Hide resolved
process.exit(1);
}
return tmpFile;
}

const main = async () => {
const targetURL = sanitizeURL(process.env.TARGET_URL || `http://localhost:6006`);
await checkStorybook(targetURL);
let args = process.argv.filter((arg) => arg !== '--stories-json');
shilman marked this conversation as resolved.
Show resolved Hide resolved
let testFile;
if (args.length !== process.argv.length) {
testFile = await fetchStoriesJson(targetURL);
process.env.TEST_ROOT = path.dirname(testFile);
process.env.TEST_MATCH = '**/*.test.js';
}

await executeJestPlaywright(args);

if (testFile) {
shilman marked this conversation as resolved.
Show resolved Hide resolved
fs.rmSync(testFile);
fs.rmdirSync(path.dirname(testFile));
}
};

main().catch((e) => console.log(`[test-storybook] ${e}`));
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"build-storybook": "build-storybook",
"release": "yarn build && auto shipit",
"test-storybook:jsdom": "jest --no-cache --config ./jsdom-jest.config.js -i",
"test-storybook:playwright": "jest --no-cache --config ./test-runner-jest.config.js"
"test-storybook:playwright-no-cache": "jest --no-cache --config ./test-runner-jest.config.js",
"test-storybook:playwright": "./bin/test-storybook.js",
"test-storybook:playwright-json": "./bin/test-storybook.js --stories-json"
shilman marked this conversation as resolved.
Show resolved Hide resolved
},
"bin": {
"test-storybook": "./bin/test-storybook.js"
Expand All @@ -58,9 +60,9 @@
"@babel/template": "^7.14.5",
"@babel/types": "^7.14.8",
"@jest/types": "^27.0.6",
"@storybook/addon-essentials": "^6.3.5",
"@storybook/react": "^6.3.5",
"@storybook/testing-react": "^0.0.21",
"@storybook/addon-essentials": "^6.4.14",
"@storybook/react": "^6.4.14",
"@storybook/testing-react": "^1.2.3",
"@testing-library/dom": "^8.1.0",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
Expand Down Expand Up @@ -95,10 +97,11 @@
},
"dependencies": {
"@storybook/csf": "0.0.2--canary.87bc651.0",
"@storybook/csf-tools": "^6.4.0",
"@storybook/csf-tools": "^6.4.14",
"jest-playwright-preset": "^1.7.0",
"node-fetch": "^2",
"playwright": "^1.14.0",
"url-exists": "^1.0.3"
"tempy": "^1.0.1"
},
"peerDependencies": {
"jest": ">=26.6.3"
Expand Down
7 changes: 6 additions & 1 deletion playwright/test-runner-jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
const { TEST_ROOT, TEST_MATCH } = process.env;

const roots = TEST_ROOT ? [TEST_ROOT] : undefined;

module.exports = {
cacheDirectory: 'node_modules/.cache/storybook/test-runner',
rootDir: process.cwd(),
testMatch: ['**/*.stories.[jt]s?(x)'],
roots,
testMatch: [TEST_MATCH || '**/*.stories.[jt]s?(x)'],
transform: {
'^.+\\.stories\\.[jt]sx?$': '@storybook/test-runner/playwright/transform',
'^.+\\.[jt]sx?$': 'babel-jest',
Expand Down
1 change: 0 additions & 1 deletion src/csf/transformCsf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const prefixFunction = (
input: t.Expression,
testPrefixer?: TestPrefixer
) => {
const clone = t.cloneDeepWithoutLoc(input);
const name = storyNameFromExport(key);
const context: TestContext = {
storyExport: t.identifier(key),
Expand Down
2 changes: 1 addition & 1 deletion src/playwright/transformPlaywright.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import template from '@babel/template';
import { transformCsf } from '../csf/transformCsf';

const testPrefixer = template(
export const testPrefixer = template(
`
console.log({ id: %%id%%, title: %%title%%, name: %%name%%, storyExport: %%storyExport%% });
async () => {
Expand Down
182 changes: 182 additions & 0 deletions src/playwright/transformPlaywrightJson.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import dedent from 'ts-dedent';
import { transformPlaywrightJson } from './transformPlaywrightJson';

expect.addSnapshotSerializer({
print: (val: any) => val.trim(),
test: (val: any) => true,
});

describe('Playwright Json', () => {
it('should generate a test for each story', () => {
const input = dedent`{
"v": 3,
"stories": {
"example-header--logged-in": {
"id": "example-header--logged-in",
"title": "Example/Header",
"name": "Logged In",
"importPath": "./stories/basic/Header.stories.js",
"kind": "Example/Header",
"story": "Logged In",
"parameters": {
"__id": "example-header--logged-in",
"docsOnly": false,
"fileName": "./stories/basic/Header.stories.js"
}
},
"example-header--logged-out": {
"id": "example-header--logged-out",
"title": "Example/Header",
"name": "Logged Out",
"importPath": "./stories/basic/Header.stories.js",
"kind": "Example/Header",
"story": "Logged Out",
"parameters": {
"__id": "example-header--logged-out",
"docsOnly": false,
"fileName": "./stories/basic/Header.stories.js"
}
},
"example-page--logged-in": {
"id": "example-page--logged-in",
"title": "Example/Page",
"name": "Logged In",
"importPath": "./stories/basic/Page.stories.js",
"kind": "Example/Page",
"story": "Logged In",
"parameters": {
"__id": "example-page--logged-in",
"docsOnly": false,
"fileName": "./stories/basic/Page.stories.js"
}
}
}
}`;
expect(transformPlaywrightJson(input)).toMatchInlineSnapshot(`
describe("Example/Header", () => {
describe("Logged In", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-header--logged-in",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-header--logged-in",
hasPlayFn: false
});
});
});
describe("Logged Out", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-header--logged-out",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-header--logged-out",
hasPlayFn: false
});
});
});
describe("Logged In", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-page--logged-in",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-page--logged-in",
hasPlayFn: false
});
});
});
});
describe("Example/Page", () => {
describe("Logged In", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-header--logged-in",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-header--logged-in",
hasPlayFn: false
});
});
});
describe("Logged Out", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-header--logged-out",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-header--logged-out",
hasPlayFn: false
});
});
});
describe("Logged In", () => {
it("test", async () => {
page.on('pageerror', err => {
page.evaluate(({
id,
err
}) => __throwError(id, err), {
id: "example-page--logged-in",
err: err.message
});
});
return page.evaluate(({
id,
hasPlayFn
}) => __test(id, hasPlayFn), {
id: "example-page--logged-in",
hasPlayFn: false
});
});
});
});
`);
});
});
Loading