storybook/scripts/run-e2e.ts

250 lines
6.6 KiB
TypeScript
Raw Normal View History

2020-05-09 11:34:50 +02:00
/* eslint-disable no-irregular-whitespace */
import path from 'path';
2021-05-08 21:51:32 +08:00
import { remove, ensureDir, pathExists } from 'fs-extra';
2020-05-09 11:34:50 +02:00
import { prompt } from 'enquirer';
import pLimit from 'p-limit';
import program from 'commander';
2020-05-09 11:34:50 +02:00
import { serve } from './utils/serve';
import { exec } from './utils/command';
2020-05-18 14:58:55 +02:00
// @ts-ignore
import { filterDataForCurrentCircleCINode } from './utils/concurrency';
2020-05-09 11:34:50 +02:00
import * as configs from '../lib/cli/src/repro-generators/configs';
import { Parameters } from '../lib/cli/src/repro-generators/configs';
2020-05-09 11:34:50 +02:00
const logger = console;
export interface Options {
/** CLI repro template to use */
2020-05-09 11:34:50 +02:00
name: string;
2020-05-19 23:29:56 +02:00
/** Pre-build hook */
2020-05-09 11:34:50 +02:00
ensureDir?: boolean;
cwd?: string;
}
const rootDir = path.join(__dirname, '..');
const siblingDir = path.join(__dirname, '..', '..', 'storybook-e2e-testing');
const prepareDirectory = async ({
cwd,
ensureDir: ensureDirOption = true,
}: Options): Promise<boolean> => {
const siblingExists = await pathExists(siblingDir);
if (!siblingExists) {
await ensureDir(siblingDir);
}
const cwdExists = await pathExists(cwd);
if (cwdExists) {
return true;
}
if (ensureDirOption) {
await ensureDir(cwd);
}
return false;
};
const cleanDirectory = async ({ cwd }: Options): Promise<void> => {
await remove(cwd);
2021-02-15 17:45:08 +01:00
};
2020-05-09 11:34:50 +02:00
const buildStorybook = async ({ cwd }: Options) => {
2020-05-09 11:34:50 +02:00
logger.info(`👷Building Storybook`);
try {
await exec(`yarn build-storybook --quiet`, { cwd });
} catch (e) {
logger.error(`🚨Storybook build failed`);
throw e;
}
};
const serveStorybook = async ({ cwd }: Options, port: string) => {
const staticDirectory = path.join(cwd, 'storybook-static');
logger.info(`🌍Serving ${staticDirectory} on http://localhost:${port}`);
return serve(staticDirectory, port);
};
const runCypress = async ({ name }: Options, location: string, open: boolean) => {
2020-05-09 11:34:50 +02:00
const cypressCommand = open ? 'open' : 'run';
logger.info(`🤖Running Cypress tests`);
try {
await exec(
2021-05-09 20:43:22 +02:00
`yarn cypress ${cypressCommand} --config pageLoadTimeout=4000,execTimeout=4000,taskTimeout=4000,responseTimeout=4000,integrationFolder="cypress/generated" --env location="${location}"`,
2020-05-09 11:34:50 +02:00
{ cwd: rootDir }
);
logger.info(`E2E tests success`);
logger.info(`🎉Storybook is working great with ${name}!`);
2020-05-09 11:34:50 +02:00
} catch (e) {
logger.error(`🚨E2E tests fails`);
logger.info(`🥺Storybook has some issues with ${name}!`);
2020-05-09 11:34:50 +02:00
throw e;
}
};
const runTests = async ({ name, ...rest }: Parameters) => {
2020-05-09 11:34:50 +02:00
const options = {
name,
...rest,
cwd: path.join(siblingDir, `${name}`),
2020-05-09 11:34:50 +02:00
};
logger.log();
logger.info(`🏃Starting for ${name}`);
2020-05-09 11:34:50 +02:00
logger.log();
logger.debug(options);
logger.log();
if (!(await prepareDirectory(options))) {
// Call repro cli
const sbCLICommand = useLocalSbCli
? 'node ../storybook/lib/cli/bin repro'
: // Need to use npx because at this time we don't have Yarn 2 installed
'npx -p @storybook/cli sb repro';
2020-05-19 23:29:56 +02:00
2021-05-10 15:08:37 +08:00
const targetFolder = path.join(siblingDir, `${name}`);
const commandArgs = [
targetFolder,
`--framework ${options.framework}`,
`--template ${options.name}`,
'--e2e',
];
if (pnp) {
commandArgs.push('--pnp');
}
2020-05-09 11:34:50 +02:00
2021-05-10 15:08:37 +08:00
const command = `${sbCLICommand} ${commandArgs.join(' ')}`;
logger.debug(command);
await exec(command, { cwd: siblingDir });
2020-05-09 11:34:50 +02:00
await buildStorybook(options);
logger.log();
2020-05-09 11:34:50 +02:00
}
const server = await serveStorybook(options, '4000');
logger.log();
let open = false;
if (!process.env.CI) {
({ open } = await prompt({
type: 'confirm',
name: 'open',
message: 'Should open cypress?',
}));
}
try {
await runCypress(options, 'http://localhost:4000', open);
logger.log();
} finally {
server.close();
}
2020-05-09 11:34:50 +02:00
};
// Run tests!
const runE2E = async (parameters: Parameters) => {
const { name } = parameters;
const cwd = path.join(siblingDir, `${name}`);
if (startWithCleanSlate) {
logger.log();
logger.info(`♻️  Starting with a clean slate, removing existing ${name} folder`);
await cleanDirectory({ ...parameters, cwd });
}
return runTests(parameters)
2020-05-09 11:34:50 +02:00
.then(async () => {
if (!process.env.CI) {
const { cleanup } = await prompt<{ cleanup: boolean }>({
2020-05-09 11:34:50 +02:00
type: 'confirm',
name: 'cleanup',
message: 'Should perform cleanup?',
});
if (cleanup) {
logger.log();
logger.info(`🗑Cleaning ${cwd}`);
await cleanDirectory({ ...parameters, cwd });
} else {
logger.log();
logger.info(`🚯No cleanup happened: ${cwd}`);
}
}
})
.catch((e) => {
logger.error(`🛑an error occurred:\n${e}`);
logger.log();
logger.error(e);
logger.log();
process.exitCode = 1;
});
};
2020-05-09 11:34:50 +02:00
program.option('--clean', 'Clean up existing projects before running the tests', false);
program.option('--pnp', 'Run tests using Yarn 2 PnP instead of Yarn 1 + npx', false);
program.option(
'--use-local-sb-cli',
'Run tests using local @storybook/cli package (⚠️ Be sure @storybook/cli is properly build as it will not be rebuild before running the tests)',
false
);
program.option(
'--skip <value>',
'Skip a framework, can be used multiple times "--skip angular@latest --skip preact"',
(value, previous) => previous.concat([value]),
[]
);
program.parse(process.argv);
const {
pnp,
useLocalSbCli,
clean: startWithCleanSlate,
args: frameworkArgs,
skip: frameworksToSkip,
}: {
pnp?: boolean;
useLocalSbCli?: boolean;
clean?: boolean;
args?: string[];
skip?: string[];
} = program;
2020-05-09 11:34:50 +02:00
const typedConfigs: { [key: string]: Parameters } = configs;
const e2eConfigs: { [key: string]: Parameters } = {};
2020-05-09 11:34:50 +02:00
// Compute the list of frameworks we will run E2E for
2020-05-09 11:34:50 +02:00
if (frameworkArgs.length > 0) {
frameworkArgs.forEach((framework) => {
e2eConfigs[framework] = Object.values(typedConfigs).find((c) => c.name === framework);
});
2020-05-09 11:34:50 +02:00
} else {
Object.values(typedConfigs).forEach((config) => {
e2eConfigs[config.name] = config;
});
}
// Remove frameworks listed with `--skip` arg
frameworksToSkip.forEach((framework) => {
delete e2eConfigs[framework];
});
2020-05-09 11:34:50 +02:00
const perform = () => {
const limit = pLimit(1);
const narrowedConfigs = Object.values(e2eConfigs);
const list = filterDataForCurrentCircleCINode(narrowedConfigs) as Parameters[];
logger.info(`📑 Will run E2E tests for:${list.map((c) => `${c.name}`).join(', ')}`);
2020-05-09 11:34:50 +02:00
return Promise.all(list.map((config) => limit(() => runE2E(config))));
};
perform().then(() => {
process.exit(process.exitCode || 0);
});