UNPKG

@nx/playwright

Version:

The Nx Plugin for Playwright contains executors and generators allowing your workspace to use the powerful Playwright integration testing capabilities.

288 lines (287 loc) • 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.configurationGenerator = configurationGenerator; exports.configurationGeneratorInternal = configurationGeneratorInternal; const devkit_1 = require("@nx/devkit"); const project_name_and_root_utils_1 = require("@nx/devkit/src/generators/project-name-and-root-utils"); const prompt_1 = require("@nx/devkit/src/generators/prompt"); const js_1 = require("@nx/js"); const generator_prompts_1 = require("@nx/js/src/utils/generator-prompts"); const package_manager_workspaces_1 = require("@nx/js/src/utils/package-manager-workspaces"); const ensure_typescript_1 = require("@nx/js/src/utils/typescript/ensure-typescript"); const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup"); const child_process_1 = require("child_process"); const path = require("path"); const add_linter_1 = require("../../utils/add-linter"); const versions_1 = require("../../utils/versions"); const init_1 = require("../init/init"); const eslint_file_1 = require("@nx/eslint/src/generators/utils/eslint-file"); function configurationGenerator(tree, options) { return configurationGeneratorInternal(tree, { addPlugin: false, ...options }); } async function configurationGeneratorInternal(tree, rawOptions) { const options = await normalizeOptions(tree, rawOptions); const tasks = []; tasks.push(await (0, init_1.initGenerator)(tree, { skipFormat: true, skipPackageJson: options.skipPackageJson, addPlugin: options.addPlugin, })); const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project); const offsetFromProjectRoot = (0, devkit_1.offsetFromRoot)(projectConfig.root); (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files'), projectConfig.root, { offsetFromRoot: offsetFromProjectRoot, projectRoot: projectConfig.root, webServerCommand: options.webServerCommand ?? null, webServerAddress: options.webServerAddress ?? null, ...options, }); const isTsSolutionSetup = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(tree); const tsconfigPath = (0, devkit_1.joinPathFragments)(projectConfig.root, 'tsconfig.json'); if (tree.exists(tsconfigPath)) { if (isTsSolutionSetup) { const tsconfig = { extends: (0, js_1.getRelativePathToRootTsConfig)(tree, projectConfig.root), compilerOptions: { allowJs: true, outDir: 'out-tsc/playwright', sourceMap: false, }, include: [ (0, devkit_1.joinPathFragments)(options.directory, '**/*.ts'), (0, devkit_1.joinPathFragments)(options.directory, '**/*.js'), 'playwright.config.ts', ], exclude: ['out-tsc', 'test-output'], }; // skip eslint from typechecking since it extends from root file that is outside rootDir if (options.linter === 'eslint') { tsconfig.exclude.push('eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs'); } (0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(projectConfig.root, 'tsconfig.e2e.json'), tsconfig); (0, devkit_1.updateJson)(tree, tsconfigPath, (json) => { // add the project tsconfig to the workspace root tsconfig.json references json.references ??= []; json.references.push({ path: './tsconfig.e2e.json' }); return json; }); } } else { const tsconfig = { extends: (0, js_1.getRelativePathToRootTsConfig)(tree, projectConfig.root), compilerOptions: { allowJs: true, outDir: `${offsetFromProjectRoot}dist/out-tsc`, sourceMap: false, }, include: [ '**/*.ts', '**/*.js', 'playwright.config.ts', 'src/**/*.spec.ts', 'src/**/*.spec.js', 'src/**/*.test.ts', 'src/**/*.test.js', 'src/**/*.d.ts', ], }; if (isTsSolutionSetup) { tsconfig.exclude = ['out-tsc', 'test-output']; // skip eslint from typechecking since it extends from root file that is outside rootDir if (options.linter === 'eslint') { tsconfig.exclude.push('eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs'); } tsconfig.compilerOptions.outDir = 'out-tsc/playwright'; if (!options.rootProject) { (0, devkit_1.updateJson)(tree, 'tsconfig.json', (json) => { // add the project tsconfig to the workspace root tsconfig.json references json.references ??= []; json.references.push({ path: './' + projectConfig.root }); return json; }); } } else { tsconfig.compilerOptions.outDir = `${offsetFromProjectRoot}dist/out-tsc`; tsconfig.compilerOptions.module = 'commonjs'; } (0, devkit_1.writeJson)(tree, tsconfigPath, tsconfig); } if (isTsSolutionSetup) { const packageJsonPath = (0, devkit_1.joinPathFragments)(projectConfig.root, 'package.json'); if (!tree.exists(packageJsonPath)) { const importPath = (0, project_name_and_root_utils_1.resolveImportPath)(tree, projectConfig.name, projectConfig.root); const packageJson = { name: importPath, version: '0.0.1', private: true, }; if (options.project !== importPath) { packageJson.nx = { name: options.project }; } (0, devkit_1.writeJson)(tree, packageJsonPath, packageJson); } ignoreTestOutput(tree, options); } const hasPlugin = (0, devkit_1.readNxJson)(tree).plugins?.some((p) => typeof p === 'string' ? p === '@nx/playwright/plugin' : p.plugin === '@nx/playwright/plugin'); if (!hasPlugin) { addE2eTarget(tree, options); setupE2ETargetDefaults(tree); } tasks.push(await (0, add_linter_1.addLinterToPlaywrightProject)(tree, { project: options.project, linter: options.linter, skipPackageJson: options.skipPackageJson, js: options.js, directory: options.directory, setParserOptionsProject: options.setParserOptionsProject, rootProject: options.rootProject ?? projectConfig.root === '.', addPlugin: options.addPlugin, })); if (options.js) { const { ModuleKind } = (0, ensure_typescript_1.ensureTypescript)(); (0, devkit_1.toJS)(tree, { extension: '.cjs', module: ModuleKind.CommonJS }); } recommendVsCodeExtensions(tree); if (!options.skipPackageJson) { tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, {}, { // required since used in playwright config '@nx/devkit': versions_1.nxVersion, })); } if (!options.skipInstall) { tasks.push(getBrowsersInstallTask()); } if (!options.skipFormat) { await (0, devkit_1.formatFiles)(tree); } if (isTsSolutionSetup) { const projectPackageManagerWorkspaceState = (0, package_manager_workspaces_1.getProjectPackageManagerWorkspaceState)(tree, projectConfig.root); if (projectPackageManagerWorkspaceState !== 'included') { tasks.push((0, package_manager_workspaces_1.getProjectPackageManagerWorkspaceStateWarningTask)(projectPackageManagerWorkspaceState, tree.root)); } } return (0, devkit_1.runTasksInSerial)(...tasks); } async function normalizeOptions(tree, options) { const nxJson = (0, devkit_1.readNxJson)(tree); const addPlugin = options.addPlugin ?? (process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false); const linter = await (0, generator_prompts_1.normalizeLinterOption)(tree, options.linter); if (!options.webServerCommand || !options.webServerAddress) { const { webServerCommand, webServerAddress } = await promptForMissingServeData(options.project); options.webServerCommand = webServerCommand; options.webServerAddress = webServerAddress; } return { ...options, addPlugin, linter, directory: options.directory ?? 'e2e', }; } async function promptForMissingServeData(projectName) { const { command, port } = await (0, prompt_1.promptWhenInteractive)([ { type: 'input', name: 'command', message: 'What command should be run to serve the application locally?', initial: `npx nx serve ${projectName}`, }, { type: 'numeral', name: 'port', message: 'What port will the application be served on?', initial: 3000, }, ], { command: `npx nx serve ${projectName}`, port: 3000, }); return { webServerCommand: command, webServerAddress: `http://localhost:${port}`, }; } function getBrowsersInstallTask() { return () => { devkit_1.output.log({ title: 'Ensuring Playwright is installed.', bodyLines: ['use --skipInstall to skip installation.'], }); const pmc = (0, devkit_1.getPackageManagerCommand)(); (0, child_process_1.execSync)(`${pmc.exec} playwright install`, { cwd: devkit_1.workspaceRoot, windowsHide: false, }); }; } function recommendVsCodeExtensions(tree) { if (tree.exists('.vscode/extensions.json')) { (0, devkit_1.updateJson)(tree, '.vscode/extensions.json', (json) => { json.recommendations ??= []; const recs = new Set(json.recommendations); recs.add('ms-playwright.playwright'); json.recommendations = Array.from(recs); return json; }); } else { (0, devkit_1.writeJson)(tree, '.vscode/extensions.json', { recommendations: ['ms-playwright.playwright'], }); } } function setupE2ETargetDefaults(tree) { const nxJson = (0, devkit_1.readNxJson)(tree); if (!nxJson.namedInputs) { return; } // E2e targets depend on all their project's sources + production sources of dependencies nxJson.targetDefaults ??= {}; const productionFileSet = !!nxJson.namedInputs?.production; nxJson.targetDefaults.e2e ??= {}; nxJson.targetDefaults.e2e.cache ??= true; nxJson.targetDefaults.e2e.inputs ??= [ 'default', productionFileSet ? '^production' : '^default', ]; (0, devkit_1.updateNxJson)(tree, nxJson); } function addE2eTarget(tree, options) { const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project); if (projectConfig?.targets?.e2e) { throw new Error(`Project ${options.project} already has an e2e target. Rename or remove the existing e2e target.`); } projectConfig.targets ??= {}; projectConfig.targets.e2e = { executor: '@nx/playwright:playwright', outputs: [`{workspaceRoot}/dist/.playwright/${projectConfig.root}`], options: { config: `${projectConfig.root}/playwright.config.${options.js ? 'cjs' : 'ts'}`, }, }; (0, devkit_1.updateProjectConfiguration)(tree, options.project, projectConfig); } function ignoreTestOutput(tree, options) { // Make sure playwright outputs are not linted. if (options.linter === 'eslint') { (0, eslint_file_1.addIgnoresToLintConfig)(tree, '', ['**/test-output']); } // Handle gitignore if (!tree.exists('.gitignore')) { devkit_1.logger.warn(`Couldn't find a root .gitignore file to update.`); } let content = tree.read('.gitignore', 'utf-8'); if (/^test-output$/gm.test(content)) { return; } content = `${content}\ntest-output\n`; tree.write('.gitignore', content); } exports.default = configurationGenerator;