2022-09-30 11:38:58 +02:00
|
|
|
|
import path from 'path';
|
2021-05-18 17:56:00 +02:00
|
|
|
|
import { ensureDir, pathExists, remove } from 'fs-extra';
|
2021-05-14 20:35:40 +02:00
|
|
|
|
import prompts from 'prompts';
|
2020-06-29 21:20:16 +02:00
|
|
|
|
import program from 'commander';
|
2022-07-22 09:30:35 +02:00
|
|
|
|
import { readConfig, writeConfig } from '../code/lib/csf-tools';
|
|
|
|
|
import { getInterpretedFile } from '../code/lib/core-common';
|
2020-05-09 11:34:50 +02:00
|
|
|
|
import { serve } from './utils/serve';
|
2022-09-06 22:40:59 -04:00
|
|
|
|
// @ts-expect-error (Converted from ts-ignore)
|
2020-09-19 19:17:43 +02:00
|
|
|
|
import { filterDataForCurrentCircleCINode } from './utils/concurrency';
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
2022-07-22 09:30:35 +02:00
|
|
|
|
import * as configs from '../code/lib/cli/src/repro-generators/configs';
|
|
|
|
|
import { Parameters } from '../code/lib/cli/src/repro-generators/configs';
|
|
|
|
|
import { exec } from '../code/lib/cli/src/repro-generators/scripts';
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
|
|
|
|
const logger = console;
|
|
|
|
|
|
2021-05-09 19:21:12 +02:00
|
|
|
|
export interface Options {
|
2021-04-27 23:25:29 +02:00
|
|
|
|
/** CLI repro template to use */
|
2020-05-09 11:34:50 +02:00
|
|
|
|
name: string;
|
2022-03-22 13:06:24 +01:00
|
|
|
|
mainOverrides?: Parameters['mainOverrides'];
|
2020-05-19 23:29:56 +02:00
|
|
|
|
/** Pre-build hook */
|
2020-05-09 11:34:50 +02:00
|
|
|
|
ensureDir?: boolean;
|
|
|
|
|
cwd?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const siblingDir = path.join(__dirname, '..', '..', 'storybook-e2e-testing');
|
|
|
|
|
|
2022-04-25 15:22:02 +02:00
|
|
|
|
program
|
|
|
|
|
.option('--clean', 'Clean up existing projects before running the tests', false)
|
|
|
|
|
.option('--pnp', 'Run tests using Yarn 2 PnP instead of Yarn 1 + npx', false)
|
|
|
|
|
.option(
|
|
|
|
|
'--use-local-sb-cli',
|
|
|
|
|
'Run tests using local @storybook/cli package (⚠️ Be sure @storybook/cli is properly built as it will not be rebuilt before running the tests)',
|
|
|
|
|
false
|
|
|
|
|
)
|
|
|
|
|
.option(
|
|
|
|
|
'--skip <value>',
|
|
|
|
|
'Skip a framework, can be used multiple times "--skip angular@latest --skip preact"',
|
|
|
|
|
(value, previous) => previous.concat([value]),
|
|
|
|
|
[]
|
|
|
|
|
)
|
|
|
|
|
.option('--docs-mode', 'Run Storybook test runner in docs mode', false)
|
|
|
|
|
.option('--all', `run e2e tests for every framework`, false);
|
|
|
|
|
program.parse(process.argv);
|
|
|
|
|
|
|
|
|
|
type ProgramOptions = {
|
|
|
|
|
all?: boolean;
|
|
|
|
|
pnp?: boolean;
|
|
|
|
|
useLocalSbCli?: boolean;
|
|
|
|
|
clean?: boolean;
|
|
|
|
|
args?: string[];
|
|
|
|
|
skip?: string[];
|
|
|
|
|
testRunner?: boolean;
|
|
|
|
|
docsMode?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
all: shouldRunAllFrameworks,
|
|
|
|
|
args: frameworkArgs,
|
|
|
|
|
skip: frameworksToSkip,
|
|
|
|
|
testRunner: shouldUseTestRunner,
|
|
|
|
|
docsMode: runTestsInDocsMode,
|
|
|
|
|
}: ProgramOptions = program;
|
|
|
|
|
|
|
|
|
|
let { useLocalSbCli }: ProgramOptions = program;
|
|
|
|
|
const { pnp, clean: startWithCleanSlate }: ProgramOptions = program;
|
|
|
|
|
|
|
|
|
|
const typedConfigs: { [key: string]: Parameters } = configs;
|
|
|
|
|
|
2021-05-10 22:47:47 +02:00
|
|
|
|
const prepareDirectory = async ({ cwd }: Options): Promise<boolean> => {
|
2022-02-01 15:16:56 +01:00
|
|
|
|
if (!(await pathExists(siblingDir))) {
|
2020-05-09 11:34:50 +02:00
|
|
|
|
await ensureDir(siblingDir);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
return pathExists(cwd);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-03-22 13:06:24 +01:00
|
|
|
|
const overrideMainConfig = async ({ cwd, mainOverrides }: Options) => {
|
|
|
|
|
logger.info(`📝 Overwriting main.js with the following configuration:`);
|
|
|
|
|
const configDir = path.join(cwd, '.storybook');
|
|
|
|
|
const mainConfigPath = getInterpretedFile(path.resolve(configDir, 'main'));
|
|
|
|
|
logger.debug(mainOverrides);
|
|
|
|
|
const mainConfig = await readConfig(mainConfigPath);
|
|
|
|
|
|
|
|
|
|
Object.keys(mainOverrides).forEach((field) => {
|
|
|
|
|
mainConfig.setFieldValue([field], mainOverrides[field]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await writeConfig(mainConfig);
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-09 19:21:12 +02:00
|
|
|
|
const buildStorybook = async ({ cwd }: Options) => {
|
2021-05-18 17:56:00 +02:00
|
|
|
|
await exec(
|
2022-06-07 09:28:14 +02:00
|
|
|
|
`yarn build-storybook --quiet`,
|
2022-02-01 15:16:56 +01:00
|
|
|
|
{ cwd },
|
2022-07-23 17:15:13 +02:00
|
|
|
|
{
|
|
|
|
|
startMessage: `👷 Building Storybook`,
|
|
|
|
|
errorMessage: `🚨 Storybook build failed`,
|
|
|
|
|
}
|
2021-05-18 17:56:00 +02:00
|
|
|
|
);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const serveStorybook = async ({ cwd }: Options, port: string) => {
|
|
|
|
|
const staticDirectory = path.join(cwd, 'storybook-static');
|
2021-05-18 17:56:00 +02:00
|
|
|
|
logger.info(`🌍 Serving ${staticDirectory} on http://localhost:${port}`);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
|
|
|
|
return serve(staticDirectory, port);
|
|
|
|
|
};
|
|
|
|
|
|
2022-03-22 13:06:24 +01:00
|
|
|
|
const runStorybookTestRunner = async (options: Options) => {
|
|
|
|
|
const viewMode = runTestsInDocsMode ? 'docs' : 'story';
|
|
|
|
|
await exec(
|
|
|
|
|
`VIEW_MODE=${viewMode} yarn test-storybook --url http://localhost:4000`,
|
|
|
|
|
{ cwd: options.cwd },
|
|
|
|
|
{
|
|
|
|
|
startMessage: `🤖 Running Storybook tests`,
|
|
|
|
|
errorMessage: `🚨 Storybook tests fails`,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-09 19:21:12 +02:00
|
|
|
|
const runTests = async ({ name, ...rest }: Parameters) => {
|
2020-05-09 11:34:50 +02:00
|
|
|
|
const options = {
|
|
|
|
|
name,
|
|
|
|
|
...rest,
|
2021-05-09 19:21:12 +02:00
|
|
|
|
cwd: path.join(siblingDir, `${name}`),
|
2020-05-09 11:34:50 +02:00
|
|
|
|
};
|
|
|
|
|
|
2020-09-16 11:09:08 +02:00
|
|
|
|
logger.log();
|
2021-05-18 17:56:00 +02:00
|
|
|
|
logger.info(`🏃️Starting for ${name}`);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
logger.log();
|
|
|
|
|
logger.debug(options);
|
|
|
|
|
logger.log();
|
|
|
|
|
|
|
|
|
|
if (!(await prepareDirectory(options))) {
|
2022-07-22 18:17:21 +02:00
|
|
|
|
let sbCLICommand = `node ${__dirname}/../code/lib/cli/bin/index.js repro`;
|
|
|
|
|
|
|
|
|
|
if (useLocalSbCli) {
|
2022-07-23 17:15:13 +02:00
|
|
|
|
sbCLICommand += ' --local';
|
2022-07-22 18:17:21 +02:00
|
|
|
|
}
|
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,
|
2022-05-31 15:17:42 +02:00
|
|
|
|
`--renderer ${options.renderer}`,
|
2021-05-10 15:08:37 +08:00
|
|
|
|
`--template ${options.name}`,
|
2022-08-22 17:40:30 +08:00
|
|
|
|
`--registry http://localhost:6001`,
|
2021-05-10 15:08:37 +08:00
|
|
|
|
'--e2e',
|
|
|
|
|
];
|
2021-05-09 17:22:22 +02:00
|
|
|
|
|
|
|
|
|
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(' ')}`;
|
2022-02-01 15:16:56 +01:00
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
await exec(
|
|
|
|
|
command,
|
2022-02-01 15:16:56 +01:00
|
|
|
|
{ cwd: siblingDir },
|
2021-05-18 17:56:00 +02:00
|
|
|
|
{
|
2022-05-31 15:17:42 +02:00
|
|
|
|
startMessage: `👷 Bootstrapping ${options.renderer} project`,
|
2021-05-18 17:56:00 +02:00
|
|
|
|
errorMessage: `🚨 Unable to bootstrap project`,
|
|
|
|
|
}
|
|
|
|
|
);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
2022-03-22 13:06:24 +01:00
|
|
|
|
if (options.mainOverrides) {
|
|
|
|
|
await overrideMainConfig(options);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 23:25:29 +02:00
|
|
|
|
await buildStorybook(options);
|
|
|
|
|
logger.log();
|
2020-05-09 11:34:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const server = await serveStorybook(options, '4000');
|
|
|
|
|
logger.log();
|
|
|
|
|
|
2020-05-22 09:02:08 +02:00
|
|
|
|
try {
|
2022-10-03 18:13:00 +02:00
|
|
|
|
await runStorybookTestRunner(options);
|
2022-03-22 13:06:24 +01:00
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
logger.info(`🎉 Storybook is working great with ${name}!`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.info(`🥺 Storybook has some issues with ${name}!`);
|
2021-05-24 17:48:29 +02:00
|
|
|
|
throw e;
|
2020-05-22 09:02:08 +02:00
|
|
|
|
} finally {
|
|
|
|
|
server.close();
|
|
|
|
|
}
|
2020-05-09 11:34:50 +02:00
|
|
|
|
};
|
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
async function postE2ECleanup(cwd: string, parameters: Parameters) {
|
|
|
|
|
if (!process.env.CI) {
|
|
|
|
|
const { cleanup } = await prompts({
|
|
|
|
|
type: 'toggle',
|
|
|
|
|
name: 'cleanup',
|
|
|
|
|
message: 'Should perform cleanup?',
|
|
|
|
|
initial: false,
|
|
|
|
|
active: 'yes',
|
|
|
|
|
inactive: 'no',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (cleanup) {
|
|
|
|
|
logger.log();
|
|
|
|
|
logger.info(`🗑 Cleaning ${cwd}`);
|
|
|
|
|
await cleanDirectory({ ...parameters, cwd });
|
|
|
|
|
} else {
|
|
|
|
|
logger.log();
|
|
|
|
|
logger.info(`🚯 No cleanup happened: ${cwd}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function preE2ECleanup(name: string, parameters: Parameters, cwd: string) {
|
2020-09-16 11:09:08 +02:00
|
|
|
|
if (startWithCleanSlate) {
|
|
|
|
|
logger.log();
|
2021-05-18 17:56:00 +02:00
|
|
|
|
logger.info(`♻️ Starting with a clean slate, removing existing ${name} folder`);
|
2020-09-16 11:09:08 +02:00
|
|
|
|
await cleanDirectory({ ...parameters, cwd });
|
|
|
|
|
}
|
2021-05-18 17:56:00 +02:00
|
|
|
|
}
|
2020-09-16 11:09:08 +02:00
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
/**
|
|
|
|
|
* Execute E2E for input parameters and return true is everything is ok, false
|
|
|
|
|
* otherwise.
|
|
|
|
|
* @param parameters
|
|
|
|
|
*/
|
|
|
|
|
const runE2E = async (parameters: Parameters): Promise<boolean> => {
|
|
|
|
|
const { name } = parameters;
|
|
|
|
|
const cwd = path.join(siblingDir, `${name}`);
|
|
|
|
|
return preE2ECleanup(name, parameters, cwd)
|
|
|
|
|
.then(() => runTests(parameters))
|
|
|
|
|
.then(() => postE2ECleanup(cwd, parameters))
|
|
|
|
|
.then(() => true)
|
2020-05-09 11:34:50 +02:00
|
|
|
|
.catch((e) => {
|
2021-05-18 17:56:00 +02:00
|
|
|
|
logger.error(`🛑 an error occurred:`);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
logger.error(e);
|
|
|
|
|
logger.log();
|
|
|
|
|
process.exitCode = 1;
|
2021-05-18 17:56:00 +02:00
|
|
|
|
return false;
|
2020-05-09 11:34:50 +02:00
|
|
|
|
});
|
2020-09-16 11:09:08 +02:00
|
|
|
|
};
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
2021-05-24 18:14:39 +02:00
|
|
|
|
const getConfig = async (): Promise<Parameters[]> => {
|
|
|
|
|
let e2eConfigsToRun = Object.values(typedConfigs);
|
2021-05-14 20:35:40 +02:00
|
|
|
|
|
2021-05-24 18:14:39 +02:00
|
|
|
|
if (shouldRunAllFrameworks) {
|
|
|
|
|
// Nothing to do here
|
|
|
|
|
} else if (frameworkArgs.length > 0) {
|
|
|
|
|
e2eConfigsToRun = e2eConfigsToRun.filter((config) => frameworkArgs.includes(config.name));
|
|
|
|
|
} else if (!process.env.CI) {
|
2021-05-14 20:35:40 +02:00
|
|
|
|
const selectedValues = await prompts([
|
|
|
|
|
{
|
|
|
|
|
type: 'toggle',
|
|
|
|
|
name: 'useLocalSbCli',
|
|
|
|
|
message: 'Use local Storybook CLI',
|
|
|
|
|
initial: false,
|
|
|
|
|
active: 'yes',
|
|
|
|
|
inactive: 'no',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'autocompleteMultiselect',
|
|
|
|
|
message: 'Select the frameworks to run',
|
|
|
|
|
name: 'frameworks',
|
2021-06-18 19:26:12 +02:00
|
|
|
|
min: 1,
|
2022-01-14 23:26:56 +08:00
|
|
|
|
hint: 'You can also run directly with package name like `test:e2e-framework react`, or `yarn test:e2e-framework --all` for all packages!',
|
2021-05-14 20:35:40 +02:00
|
|
|
|
choices: Object.keys(configs).map((key) => {
|
2022-09-06 22:40:59 -04:00
|
|
|
|
// @ts-expect-error (Converted from ts-ignore)
|
2021-05-14 20:35:40 +02:00
|
|
|
|
const { name, version } = configs[key];
|
|
|
|
|
return {
|
2022-09-06 22:40:59 -04:00
|
|
|
|
// @ts-expect-error (Converted from ts-ignore)
|
2021-05-14 20:35:40 +02:00
|
|
|
|
value: configs[key],
|
|
|
|
|
title: `${name}@${version}`,
|
|
|
|
|
selected: false,
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!selectedValues.frameworks) {
|
|
|
|
|
logger.info(`No framework was selected.`);
|
|
|
|
|
process.exit(process.exitCode || 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useLocalSbCli = selectedValues.useLocalSbCli;
|
2021-05-24 18:14:39 +02:00
|
|
|
|
e2eConfigsToRun = selectedValues.frameworks;
|
2021-05-14 20:35:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove frameworks listed with `--skip` arg
|
2021-05-24 18:14:39 +02:00
|
|
|
|
e2eConfigsToRun = e2eConfigsToRun.filter((config) => !frameworksToSkip.includes(config.name));
|
|
|
|
|
|
|
|
|
|
return e2eConfigsToRun;
|
2021-05-14 20:35:40 +02:00
|
|
|
|
};
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
const perform = async (): Promise<Record<string, boolean>> => {
|
2021-05-24 18:14:39 +02:00
|
|
|
|
const narrowedConfigs: Parameters[] = await getConfig();
|
2021-05-09 19:21:12 +02:00
|
|
|
|
|
2020-09-19 19:17:43 +02:00
|
|
|
|
const list = filterDataForCurrentCircleCINode(narrowedConfigs) as Parameters[];
|
|
|
|
|
|
2021-05-09 19:21:12 +02:00
|
|
|
|
logger.info(`📑 Will run E2E tests for:${list.map((c) => `${c.name}`).join(', ')}`);
|
2020-05-09 11:34:50 +02:00
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
const e2eResult: Record<string, boolean> = {};
|
|
|
|
|
|
|
|
|
|
// Run all e2e tests one after another and fill result map
|
|
|
|
|
await list.reduce(
|
|
|
|
|
(previousValue, config) =>
|
|
|
|
|
previousValue
|
|
|
|
|
.then(() => runE2E(config))
|
|
|
|
|
.then((result) => {
|
|
|
|
|
e2eResult[config.name] = result;
|
|
|
|
|
}),
|
|
|
|
|
Promise.resolve()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return e2eResult;
|
2020-05-09 11:34:50 +02:00
|
|
|
|
};
|
|
|
|
|
|
2021-05-18 17:56:00 +02:00
|
|
|
|
perform().then((e2eResult) => {
|
|
|
|
|
logger.info(`🧮 E2E Results`);
|
|
|
|
|
|
|
|
|
|
Object.entries(e2eResult).forEach(([configName, result]) => {
|
|
|
|
|
logger.info(`${configName}: ${result ? 'OK' : 'KO'}`);
|
|
|
|
|
});
|
|
|
|
|
|
2020-05-09 11:34:50 +02:00
|
|
|
|
process.exit(process.exitCode || 0);
|
|
|
|
|
});
|