diff --git a/README.md b/README.md
index 9ec64030..0df996b5 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,23 @@ yarn test-storybook
> TARGET_URL=http://localhost:9009 yarn test-storybook
> ```
+## CLI Options
+
+```plaintext
+Usage: test-storybook [options]
+```
+
+| Options | Description |
+| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `--help` | Output usage information
`test-storybook --help` |
+| `-s`, `--stories-json` | Run in stories json mode (requires a compatible Storybook)
`test-storybook --stories-json` |
+| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from
`test-storybook -c .storybook` |
+| `--watch` | Run in watch mode
`test-storybook --watch` |
+| `--maxWorkers [amount]` | Specifies the maximum number of workers the worker-pool will spawn for running tests
`test-storybook --maxWorkers=2` |
+| `--no-cache` | Disable the cache
`test-storybook --no-cache` |
+| `--clearCache` | Deletes the Jest cache directory and then exits without running tests
`test-storybook --clearCache` |
+| `--verbose` | Display individual test results with the test suite hierarchy
`test-storybook --verbose` |
+
## Configuration
The test runner is based on [Jest](https://jestjs.io/) and will accept the [CLI options](https://jestjs.io/docs/cli) that Jest does, like `--watch`, `--maxWorkers`, etc.
diff --git a/bin/test-storybook.js b/bin/test-storybook.js
index e0e4b0ec..7ae8f9cc 100755
--- a/bin/test-storybook.js
+++ b/bin/test-storybook.js
@@ -6,6 +6,7 @@ const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');
const tempy = require('tempy');
+const { getCliOptions, getStorybookMain } = require('../dist/cjs/util/cli');
const { transformPlaywrightJson } = require('../dist/cjs/playwright/transformPlaywrightJson');
// Do this as the first thing so that any code reading it knows the right env.
@@ -103,15 +104,20 @@ async function fetchStoriesJson(url) {
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');
- if (args.length !== process.argv.length) {
+ const { jestOptions, runnerOptions } = getCliOptions()
+
+ if (runnerOptions.storiesJson) {
storiesJsonTmpDir = await fetchStoriesJson(targetURL);
process.env.TEST_ROOT = storiesJsonTmpDir;
process.env.TEST_MATCH = '**/*.test.js';
}
- await executeJestPlaywright(args);
+ // check if main.js exists, throw an error if not
+ getStorybookMain(runnerOptions.configDir);
+ process.env.STORYBOOK_CONFIG_DIR = runnerOptions.configDir;
+
+ await executeJestPlaywright(jestOptions);
};
main().catch((e) => console.log(`[test-storybook] ${e}`));
diff --git a/package.json b/package.json
index 738c9557..5ca20aa3 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
"buildTsc": "tsc --declaration --emitDeclarationOnly --outDir ./dist/ts",
"prebuild": "yarn clean",
"build": "concurrently \"yarn buildBabel\" \"yarn buildTsc\"",
- "build:watch": "concurrently \"yarn buildBabel:esm -- --watch\" \"yarn buildTsc -- --watch\"",
+ "build:watch": "concurrently \"yarn buildBabel:cjs -- --watch\" \"yarn buildTsc -- --watch\"",
"test": "jest",
"storybook": "start-storybook -p 6006",
"start": "concurrently \"yarn build:watch\" \"yarn storybook -- --no-manager-cache --quiet\"",
@@ -106,6 +106,7 @@
"dependencies": {
"@storybook/csf": "0.0.2--canary.87bc651.0",
"@storybook/csf-tools": "^6.4.14",
+ "commander": "^9.0.0",
"jest-playwright-preset": "^1.7.0",
"node-fetch": "^2",
"playwright": "^1.14.0",
diff --git a/src/playwright/transformPlaywright.test.ts b/src/playwright/transformPlaywright.test.ts
index 91d68e95..c93e791a 100644
--- a/src/playwright/transformPlaywright.test.ts
+++ b/src/playwright/transformPlaywright.test.ts
@@ -1,10 +1,12 @@
import dedent from 'ts-dedent';
import path from 'path';
import * as coreCommon from '@storybook/core-common';
+import * as cli from '../util/cli';
import { transformPlaywright } from './transformPlaywright';
jest.mock('@storybook/core-common');
+jest.mock('../util/cli');
expect.addSnapshotSerializer({
print: (val: any) => val.trim(),
@@ -15,7 +17,7 @@ describe('Playwright', () => {
beforeEach(() => {
const relativeSpy = jest.spyOn(path, 'relative');
relativeSpy.mockReturnValueOnce('stories/basic/Header.stories.js');
- jest.spyOn(coreCommon, 'serverRequire').mockImplementation(() => ({
+ jest.spyOn(cli, 'getStorybookMain').mockImplementation(() => ({
stories: [
{
directory: '../stories/basic',
diff --git a/src/playwright/transformPlaywright.ts b/src/playwright/transformPlaywright.ts
index 06e52362..fdf46c8d 100644
--- a/src/playwright/transformPlaywright.ts
+++ b/src/playwright/transformPlaywright.ts
@@ -1,8 +1,9 @@
-import { resolve, join, relative } from 'path';
+import { resolve, relative } from 'path';
import template from '@babel/template';
-import { serverRequire, normalizeStories } from '@storybook/core-common';
+import { normalizeStories } from '@storybook/core-common';
import { autoTitle } from '@storybook/store';
+import { getStorybookMain } from '../util/cli';
import { transformCsf } from '../csf/transformCsf';
export const testPrefixer = template(
@@ -24,16 +25,10 @@ export const testPrefixer = template(
);
const getDefaultTitle = (filename: string) => {
- // we'll need to figure this out for different cases
- // e.g. --config-dir
- const configDir = resolve('.storybook');
const workingDir = resolve();
+ const configDir = process.env.STORYBOOK_CONFIG_DIR;
- const main = serverRequire(join(configDir, 'main'));
-
- if (!main) {
- throw new Error(`Could not load main.js in ${configDir}`);
- }
+ const main = getStorybookMain(configDir);
const normalizedStoriesEntries = normalizeStories(main.stories, {
configDir,
diff --git a/src/util/cli.test.ts b/src/util/cli.test.ts
new file mode 100644
index 00000000..ab03ed60
--- /dev/null
+++ b/src/util/cli.test.ts
@@ -0,0 +1,44 @@
+import * as coreCommon from '@storybook/core-common';
+
+import * as cliHelper from './helpers';
+import { getCliOptions, getStorybookMain, defaultRunnerOptions } from './cli';
+
+jest.mock('@storybook/core-common');
+
+describe('CLI', () => {
+ describe('getCliOptions', () => {
+ it('returns default options if no extra option is passed', () => {
+ const opts = getCliOptions();
+ expect(opts.runnerOptions).toMatchObject(defaultRunnerOptions);
+ });
+
+ it('returns custom options if passed', () => {
+ const customConfig = { configDir: 'custom', storiesJson: true };
+ jest.spyOn(cliHelper, 'getParsedCliOptions').mockReturnValue(customConfig);
+ const opts = getCliOptions();
+ expect(opts.runnerOptions).toMatchObject(customConfig);
+ });
+ });
+
+ describe('getStorybookMain', () => {
+ it('should throw an error if no configuration is found', () => {
+ expect(() => getStorybookMain('.storybook')).toThrow();
+ });
+
+ it('should return mainjs', () => {
+ const mockedMain = {
+ stories: [
+ {
+ directory: '../stories/basic',
+ titlePrefix: 'Example',
+ },
+ ],
+ };
+
+ jest.spyOn(coreCommon, 'serverRequire').mockImplementation(() => mockedMain);
+
+ const res = getStorybookMain('.storybook');
+ expect(res).toMatchObject(mockedMain);
+ });
+ });
+});
diff --git a/src/util/cli.ts b/src/util/cli.ts
new file mode 100644
index 00000000..f8a97851
--- /dev/null
+++ b/src/util/cli.ts
@@ -0,0 +1,63 @@
+import { join, resolve } from 'path';
+import { serverRequire, StorybookConfig } from '@storybook/core-common';
+import { getParsedCliOptions } from './helpers';
+
+type CliOptions = {
+ runnerOptions: {
+ storiesJson: boolean;
+ configDir: string;
+ };
+ jestOptions: string[];
+};
+
+type StorybookRunnerCommand = keyof CliOptions['runnerOptions'];
+
+const STORYBOOK_RUNNER_COMMANDS: StorybookRunnerCommand[] = ['storiesJson', 'configDir'];
+
+export const defaultRunnerOptions: CliOptions['runnerOptions'] = {
+ configDir: '.storybook',
+ storiesJson: false,
+};
+
+let storybookMainConfig: StorybookConfig;
+
+export const getCliOptions = () => {
+ const allOptions = getParsedCliOptions();
+
+ const defaultOptions: CliOptions = {
+ runnerOptions: { ...defaultRunnerOptions },
+ jestOptions: process.argv.splice(0, 2),
+ };
+
+ return Object.keys(allOptions).reduce((acc, key: any) => {
+ if (STORYBOOK_RUNNER_COMMANDS.includes(key)) {
+ //@ts-ignore
+ acc.runnerOptions[key] = allOptions[key];
+ } else {
+ if (allOptions[key] === true) {
+ acc.jestOptions.push(`--${key}`);
+ } else if (allOptions[key] === false) {
+ acc.jestOptions.push(`--no-${key}`);
+ } else {
+ acc.jestOptions.push(`--${key}`, allOptions[key]);
+ }
+ }
+
+ return acc;
+ }, defaultOptions);
+};
+
+export const getStorybookMain = (configDir: string) => {
+ if (storybookMainConfig) {
+ return storybookMainConfig;
+ }
+
+ storybookMainConfig = serverRequire(join(resolve(configDir), 'main'));
+ if (!storybookMainConfig) {
+ throw new Error(
+ `Could not load main.js in ${configDir}. Is the config directory correct? You can change it by using --config-dir `
+ );
+ }
+
+ return storybookMainConfig;
+};
diff --git a/src/util/helpers.ts b/src/util/helpers.ts
new file mode 100644
index 00000000..5c2398bd
--- /dev/null
+++ b/src/util/helpers.ts
@@ -0,0 +1,41 @@
+export const getParsedCliOptions = () => {
+ const { program } = require('commander');
+
+ program
+ .option('-s, --stories-json', 'Run in stories json mode (requires a compatible Storybook)')
+ .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
+ .option('--watch', 'Run in watch mode')
+ .option(
+ '--maxWorkers ',
+ 'Specifies the maximum number of workers the worker-pool will spawn for running tests'
+ )
+ .option('--no-cache', 'Disable the cache')
+ .option('--clearCache', 'Deletes the Jest cache directory and then exits without running tests')
+ .option('--verbose', 'Display individual test results with the test suite hierarchy');
+
+ program.exitOverride();
+
+ try {
+ program.parse();
+ } catch (err) {
+ switch (err.code) {
+ case 'commander.unknownOption': {
+ program.outputHelp();
+ console.warn(
+ `\nIf you'd like this option to be supported, please open an issue at https://github.com/storybookjs/test-runner/issues/new\n`
+ );
+ process.exit(1);
+ }
+
+ case 'commander.helpDisplayed': {
+ process.exit(0);
+ }
+
+ default: {
+ throw err;
+ }
+ }
+ }
+
+ return program.opts();
+};
diff --git a/yarn.lock b/yarn.lock
index 3dd214f7..e1d74d8b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5124,6 +5124,11 @@ commander@^8.2.0:
resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+commander@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-9.0.0.tgz#86d58f24ee98126568936bd1d3574e0308a99a40"
+ integrity sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==
+
common-path-prefix@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"