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 --playwright & --cypress flags #882

Merged
merged 11 commits into from
Jan 24, 2024
4 changes: 4 additions & 0 deletions action-src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ async function run() {
const branchName = getInput('branchName');
const buildScriptName = getInput('buildScriptName');
const configFile = getInput('configFile');
const cypress = getInput('cypress');
const debug = getInput('debug');
const diagnosticsFile = getInput('diagnosticsFile') || getInput('diagnostics');
const dryRun = getInput('dryRun');
Expand All @@ -107,6 +108,7 @@ async function run() {
const onlyChanged = getInput('onlyChanged');
const onlyStoryFiles = getInput('onlyStoryFiles');
const onlyStoryNames = getInput('onlyStoryNames');
const playwright = getInput('playwright');
const preserveMissing = getInput('preserveMissing');
const projectToken = getInput('projectToken') || getInput('appCode'); // backwards compatibility
const repositorySlug = getInput('repositorySlug');
Expand Down Expand Up @@ -139,6 +141,7 @@ async function run() {
branchName: maybe(branchName),
buildScriptName: maybe(buildScriptName),
configFile: maybe(configFile),
cypress: maybe(cypress),
debug: maybe(debug),
diagnosticsFile: maybe(diagnosticsFile),
dryRun: maybe(dryRun),
Expand All @@ -153,6 +156,7 @@ async function run() {
onlyChanged: maybe(onlyChanged),
onlyStoryFiles: maybe(onlyStoryFiles),
onlyStoryNames: maybe(onlyStoryNames),
playwright: maybe(playwright),
preserveMissing: maybe(preserveMissing),
projectToken,
repositorySlug: maybe(repositorySlug),
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ inputs:
required: false
configFile:
description: 'Path from where to load the Chromatic config JSON file.'
cypress:
description: 'Run build against `chromatic-cypress` test archives'
required: false
debug:
description: 'Output verbose debugging information'
Expand Down Expand Up @@ -66,6 +68,9 @@ inputs:
onlyStoryFiles:
description: 'Only run a single story or a subset of stories by their filename(s)'
required: false
playwright:
description: 'Run build against `chromatic-playwright` test archives'
required: false
preserveMissing:
description: 'Deprecated, use onlyChanged, onlyStoryNames or onlyStoryFiles instead'
required: false
Expand Down
40 changes: 35 additions & 5 deletions node-src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dns from 'dns';
import { execa as execaDefault } from 'execa';
import { execaCommand as execaDefault } from 'execa';
import jsonfile from 'jsonfile';
import { confirm } from 'node-ask';
import fetchDefault from 'node-fetch';
Expand Down Expand Up @@ -38,7 +38,11 @@ beforeEach(() => {
vi.mock('dns');
vi.mock('execa');

const execa = vi.mocked(execaDefault);
vi.mock('./lib/getE2eBinPath', () => ({
getE2eBinPath: () => 'path/to/bin',
}));

const execaCommand = vi.mocked(execaDefault);
const fetch = vi.mocked(fetchDefault);
const upload = vi.mocked(uploadFiles);

Expand Down Expand Up @@ -334,8 +338,8 @@ beforeEach(() => {
CHROMATIC_APP_CODE: undefined,
CHROMATIC_PROJECT_TOKEN: undefined,
};
execa.mockReset();
execa.mockResolvedValue({ stdout: '1.2.3' } as any);
execaCommand.mockReset();
execaCommand.mockResolvedValue({ stdout: '1.2.3' } as any);
getCommit.mockResolvedValue({
commit: 'commit',
committedAt: 1234,
Expand Down Expand Up @@ -466,6 +470,12 @@ it('should exit with code 6 and stop the build when abortSignal is aborted', asy
it('calls out to npm build script passed and uploads files', async () => {
const ctx = getContext(['--project-token=asdf1234', '--build-script-name=build-storybook']);
await runAll(ctx);

expect(execaCommand).toHaveBeenCalledWith(
expect.stringMatching(/build-storybook/),
expect.objectContaining({})
);

expect(ctx.exitCode).toBe(1);
expect(uploadFiles).toHaveBeenCalledWith(
expect.any(Object),
Expand Down Expand Up @@ -501,7 +511,7 @@ it('skips building and uploads directly with storybook-build-dir', async () => {
const ctx = getContext(['--project-token=asdf1234', '--storybook-build-dir=dirname']);
await runAll(ctx);
expect(ctx.exitCode).toBe(1);
expect(execa).not.toHaveBeenCalled();
expect(execaCommand).not.toHaveBeenCalled();
expect(uploadFiles).toHaveBeenCalledWith(
expect.any(Object),
[
Expand Down Expand Up @@ -532,6 +542,26 @@ it('skips building and uploads directly with storybook-build-dir', async () => {
);
});

it('builds with playwright with --playwright', async () => {
const ctx = getContext(['--project-token=asdf1234', '--playwright']);
await runAll(ctx);
expect(execaCommand).toHaveBeenCalledWith(
expect.stringMatching(/path\/to\/bin/),
expect.objectContaining({})
);
expect(ctx.exitCode).toBe(1);
});

it('builds with cypress with --cypress', async () => {
const ctx = getContext(['--project-token=asdf1234', '--cypress']);
await runAll(ctx);
expect(execaCommand).toHaveBeenCalledWith(
expect.stringMatching(/path\/to\/bin/),
expect.objectContaining({})
);
expect(ctx.exitCode).toBe(1);
});

it('passes autoAcceptChanges to the index', async () => {
const ctx = getContext(['--project-token=asdf1234', '--auto-accept-changes']);
await runAll(ctx);
Expand Down
7 changes: 1 addition & 6 deletions node-src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import buildCanceled from './ui/messages/errors/buildCanceled';
import { default as fatalError } from './ui/messages/errors/fatalError';
import fetchError from './ui/messages/errors/fetchError';
import graphqlError from './ui/messages/errors/graphqlError';
import invalidPackageJson from './ui/messages/errors/invalidPackageJson';
import missingStories from './ui/messages/errors/missingStories';
import noPackageJson from './ui/messages/errors/noPackageJson';
import runtimeError from './ui/messages/errors/runtimeError';
Expand Down Expand Up @@ -97,11 +96,6 @@ export async function run({
}

const { path: packagePath, packageJson } = pkgInfo;
if (typeof packageJson !== 'object' || typeof packageJson.scripts !== 'object') {
log.error(invalidPackageJson(packagePath));
process.exit(252);
}

const ctx: InitialContext = {
...parseArgs(argv),
...(flags && { flags }),
Expand Down Expand Up @@ -164,6 +158,7 @@ export async function runAll(ctx: InitialContext) {
const options = getOptions(ctx);
(ctx as Context).options = options;
ctx.log.setLogFile(options.logFile);

setExitCode(ctx, exitCodes.OK);
} catch (e) {
return onError(e);
Expand Down
2 changes: 2 additions & 0 deletions node-src/lib/getConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const configurationSchema = z
ignoreLastBuildOnBranch: z.string(),

buildScriptName: z.string(),
playwright: z.boolean(),
cypress: z.boolean(),
outputDir: z.string(),
skip: z.union([z.string(), z.boolean()]),

Expand Down
18 changes: 18 additions & 0 deletions node-src/lib/getE2eBinPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Context } from '../types';
import missingDependency from '../ui/messages/errors/missingDependency';
import { exitCodes, setExitCode } from './setExitCode';
import { failed } from '../ui/tasks/build';

export function getE2eBinPath(ctx: Context, flag: 'playwright' | 'cypress') {
const dependencyName = `chromatic-${flag}`;
try {
return require.resolve(`${dependencyName}/bin/build-archive-storybook`);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
ctx.log.error(missingDependency({ dependencyName, flag }));
setExitCode(ctx, exitCodes.MISSING_DEPENDENCY, true);
throw new Error(failed(ctx).output);
}
throw err;
}
}
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 17 additions & 0 deletions node-src/lib/getOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import invalidSingularOptions from '../ui/messages/errors/invalidSingularOptions
import missingBuildScriptName from '../ui/messages/errors/missingBuildScriptName';
import missingProjectToken from '../ui/messages/errors/missingProjectToken';
import deprecatedOption from '../ui/messages/warnings/deprecatedOption';
import invalidPackageJson from '../ui/messages/errors/invalidPackageJson';

const takeLast = (input: string | string[]) =>
Array.isArray(input) ? input[input.length - 1] : input;
Expand All @@ -39,6 +40,7 @@ export default function getOptions({
configuration,
log,
packageJson,
packagePath,
}: InitialContext): Options {
const defaultOptions = {
projectToken: env.CHROMATIC_PROJECT_TOKEN,
Expand Down Expand Up @@ -71,6 +73,8 @@ export default function getOptions({
preserveMissingSpecs: undefined,

buildScriptName: undefined,
playwright: undefined,
cypress: undefined,
outputDir: undefined,
allowConsoleErrors: undefined,
storybookBuildDir: undefined,
Expand Down Expand Up @@ -128,6 +132,8 @@ export default function getOptions({
flags.preserveMissing || typeof flags.only === 'string' ? true : undefined,

buildScriptName: flags.buildScriptName,
playwright: trueIfSet(flags.playwright),
cypress: trueIfSet(flags.cypress),
outputDir: takeLast(flags.outputDir),
allowConsoleErrors: flags.allowConsoleErrors,
storybookBuildDir: takeLast(flags.storybookBuildDir),
Expand Down Expand Up @@ -202,6 +208,8 @@ export default function getOptions({
const singularOpts = {
buildScriptName: '--build-script-name',
storybookBuildDir: '--storybook-build-dir',
playwright: '--playwright',
cypress: '--cypress',
};
const foundSingularOpts = Object.keys(singularOpts).filter((name) => !!options[name]);

Expand Down Expand Up @@ -254,6 +262,15 @@ export default function getOptions({
return options;
}

if (options.playwright || options.cypress) {
return options;
}

if (typeof packageJson !== 'object' || typeof packageJson.scripts !== 'object') {
log.error(invalidPackageJson(packagePath));
process.exit(252);
}

const { scripts } = packageJson;
if (typeof buildScriptName !== 'string') {
buildScriptName = 'build-storybook';
Expand Down
1 change: 1 addition & 0 deletions node-src/lib/setExitCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const exitCodes = {
// I/O errors
FETCH_ERROR: 201,
GRAPHQL_ERROR: 202,
MISSING_DEPENDENCY: 210,
INVALID_OPTIONS: 254,
};

Expand Down
27 changes: 18 additions & 9 deletions node-src/tasks/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { endActivity, startActivity } from '../ui/components/activity';
import buildFailed from '../ui/messages/errors/buildFailed';
import { failed, initial, pending, skipped, success } from '../ui/tasks/build';
import { getPackageManagerRunCommand } from '../lib/getPackageManager';
import { getE2eBinPath } from '../lib/getE2eBinPath';

export const setSourceDir = async (ctx: Context) => {
if (ctx.options.outputDir) {
Expand All @@ -34,15 +35,22 @@ export const setBuildCommand = async (ctx: Context) => {
ctx.log.warn('Storybook version 6.2.0 or later is required to use the --only-changed flag');
}

ctx.buildCommand = await getPackageManagerRunCommand(
[
const buildCommandOptions = [
'--output-dir',
ctx.sourceDir,
ctx.git.changedFiles && webpackStatsSupported && '--webpack-stats-json',
ctx.git.changedFiles && webpackStatsSupported && ctx.sourceDir,
].filter(Boolean);

if (ctx.options.playwright || ctx.options.cypress) {
const binPath = getE2eBinPath(ctx, ctx.options.playwright ? 'playwright' : 'cypress');
ctx.buildCommand = ['node', binPath, ...buildCommandOptions].join(' ');
} else {
ctx.buildCommand = await getPackageManagerRunCommand([
ctx.options.buildScriptName,
'--output-dir',
ctx.sourceDir,
ctx.git.changedFiles && webpackStatsSupported && '--webpack-stats-json',
ctx.git.changedFiles && webpackStatsSupported && ctx.sourceDir,
].filter(Boolean)
);
...buildCommandOptions,
]);
}
};

const timeoutAfter = (ms) =>
Expand All @@ -64,10 +72,11 @@ export const buildStorybook = async (ctx: Context) => {
ctx.log.debug('Running build command:', ctx.buildCommand);
ctx.log.debug('Runtime metadata:', JSON.stringify(ctx.runtimeMetadata, null, 2));

console.log(ctx.buildCommand);
const subprocess = execaCommand(ctx.buildCommand, {
stdio: [null, logFile, logFile],
signal,
env: { NODE_ENV: ctx.env.STORYBOOK_NODE_ENV || 'production' },
env: { NODE_ENV: ctx.env.STORYBOOK_NODE_ENV || 'production' },
});
await Promise.race([subprocess, timeoutAfter(ctx.env.STORYBOOK_BUILD_TIMEOUT)]);
} catch (e) {
Expand Down
6 changes: 6 additions & 0 deletions node-src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface Flags {
outputDir?: string[];
storybookBuildDir?: string[];

// E2E options
playwright?: boolean;
cypress?: boolean;

// Chromatic options
autoAcceptChanges?: string;
branchName?: string;
Expand Down Expand Up @@ -90,6 +94,8 @@ export interface Options extends Configuration {
originalArgv: string[];

buildScriptName: Flags['buildScriptName'];
playwright: Flags['playwright'];
cypress: Flags['cypress'];
outputDir: string;
allowConsoleErrors: Flags['allowConsoleErrors'];
url?: string;
Expand Down
8 changes: 8 additions & 0 deletions node-src/ui/messages/errors/missingDependency.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import missingDependency from './missingDependency';

export default {
title: 'CLI/Messages/Errors',
};

export const MissingDependency = () =>
missingDependency({ dependencyName: 'chromatic-playwright', flag: 'playwright' });
11 changes: 11 additions & 0 deletions node-src/ui/messages/errors/missingDependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';
import { error, info } from '../../components/icons';

export default ({ dependencyName, flag }: { dependencyName: string; flag: string }) => {
return dedent(chalk`
${error} Failed to import \`${dependencyName}\`, is it installed in \`package.json\`?

${info} To run \`chromatic --${flag}\` you must have \`${dependencyName}\` installed.
`);
};
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@
"zen-observable": "^0.8.15",
"zod": "^3.22.2"
},
"peerDependencies": {
"chromatic-playwright": "^0.4.0 || ^1.0.0",
"chromatic-cypress": "^0.4.0 || ^1.0.0"
},
"peerDependenciesMeta": {
"chromatic-playwright": {
"optional": true
},
"chromatic-cypress": {
"optional": true
}
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
Expand Down
Loading