From 1c8ee62613d8e201889621665fde55680a003f64 Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Thu, 20 Jul 2023 21:54:11 -0400 Subject: [PATCH] feat(testing): add lint target --- e2e/playwright/src/playwright.test.ts | 20 ++-- e2e/utils/get-env-info.ts | 4 +- .../generators/configuration/configuration.ts | 26 ++++- .../src/generators/configuration/schema.d.ts | 4 + .../src/generators/configuration/schema.json | 11 ++ packages/playwright/src/utils/add-linter.ts | 103 ++++++++++++++++++ packages/playwright/src/utils/preset.ts | 2 +- packages/playwright/src/utils/versions.ts | 1 + 8 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 packages/playwright/src/utils/add-linter.ts diff --git a/e2e/playwright/src/playwright.test.ts b/e2e/playwright/src/playwright.test.ts index f6d591b98e2df..7233b4f4c414c 100644 --- a/e2e/playwright/src/playwright.test.ts +++ b/e2e/playwright/src/playwright.test.ts @@ -15,15 +15,18 @@ describe('Playwright E2E Test runner', () => { afterAll(() => cleanupProject()); it( - 'should test example app', + 'should test and lint example app', () => { runCLI(`g @nx/js:lib demo-e2e --unitTestRunner none --bundler none`); runCLI(`g @nx/playwright:configuration --project demo-e2e`); ensurePlaywrightBrowsersInstallation(); - const results = runCLI(`e2e demo-e2e`); - expect(results).toContain('6 passed'); - expect(results).toContain('Successfully ran target e2e for project'); + const e2eResults = runCLI(`e2e demo-e2e`); + expect(e2eResults).toContain('6 passed'); + expect(e2eResults).toContain('Successfully ran target e2e for project'); + + const lintResults = runCLI(`lint demo-e2e`); + expect(lintResults).toContain('All files pass linting'); }, TEN_MINS_MS ); @@ -37,9 +40,12 @@ describe('Playwright E2E Test runner', () => { runCLI(`g @nx/playwright:configuration --project demo-js-e2e --js`); ensurePlaywrightBrowsersInstallation(); - const results = runCLI(`e2e demo-js-e2e`); - expect(results).toContain('6 passed'); - expect(results).toContain('Successfully ran target e2e for project'); + const e2eResults = runCLI(`e2e demo-js-e2e`); + expect(e2eResults).toContain('6 passed'); + expect(e2eResults).toContain('Successfully ran target e2e for project'); + + const lintResults = runCLI(`lint demo-e2e`); + expect(lintResults).toContain('All files pass linting'); }, TEN_MINS_MS ); diff --git a/e2e/utils/get-env-info.ts b/e2e/utils/get-env-info.ts index 81c97e83a6f8c..a46ece3043f34 100644 --- a/e2e/utils/get-env-info.ts +++ b/e2e/utils/get-env-info.ts @@ -118,7 +118,9 @@ export function ensurePlaywrightBrowsersInstallation() { cwd: tmpProjPath(), }); e2eConsoleLogger( - `Playwright browsers ${execSync('npx playwright --version')} installed.` + `Playwright browsers ${execSync('npx playwright --version') + .toString() + .trim()} installed.` ); } diff --git a/packages/playwright/src/generators/configuration/configuration.ts b/packages/playwright/src/generators/configuration/configuration.ts index 1dd0a11ed4b43..139b7656e7977 100644 --- a/packages/playwright/src/generators/configuration/configuration.ts +++ b/packages/playwright/src/generators/configuration/configuration.ts @@ -2,9 +2,11 @@ import { convertNxGenerator, formatFiles, generateFiles, + GeneratorCallback, offsetFromRoot, readNxJson, readProjectConfiguration, + runTasksInSerial, toJS, Tree, updateNxJson, @@ -13,11 +15,19 @@ import { import * as path from 'path'; import { ConfigurationGeneratorSchema } from './schema'; import initGenerator from '../init/init'; +import { addLinterToPlaywrightProject } from '../../utils/add-linter'; export async function configurationGenerator( tree: Tree, options: ConfigurationGeneratorSchema ) { + const tasks: GeneratorCallback[] = []; + tasks.push( + await initGenerator(tree, { + skipFormat: true, + skipPackageJson: options.skipPackageJson, + }) + ); const projectConfig = readProjectConfiguration(tree, options.project); generateFiles(tree, path.join(__dirname, 'files'), projectConfig.root, { offsetFromRoot: offsetFromRoot(projectConfig.root), @@ -29,6 +39,17 @@ export async function configurationGenerator( addE2eTarget(tree, options); setupE2ETargetDefaults(tree); + tasks.push( + await addLinterToPlaywrightProject(tree, { + project: options.project, + linter: options.linter, + skipPackageJson: options.skipPackageJson, + js: options.js, + directory: options.directory, + setParserOptionsProject: options.setParserOptionsProject, + rootProject: projectConfig.root === '.', + }) + ); if (options.js) { toJS(tree); @@ -37,10 +58,7 @@ export async function configurationGenerator( await formatFiles(tree); } - return initGenerator(tree, { - skipFormat: true, - skipPackageJson: options.skipPackageJson, - }); + return runTasksInSerial(...tasks); } function setupE2ETargetDefaults(tree: Tree) { diff --git a/packages/playwright/src/generators/configuration/schema.d.ts b/packages/playwright/src/generators/configuration/schema.d.ts index 73fb2258298f3..a9bf8c0aa1459 100644 --- a/packages/playwright/src/generators/configuration/schema.d.ts +++ b/packages/playwright/src/generators/configuration/schema.d.ts @@ -1,9 +1,13 @@ +import type { Linter } from '@nx/linter'; + export interface ConfigurationGeneratorSchema { project: string; directory: string; js: boolean; // default is false skipFormat: boolean; skipPackageJson: boolean; + linter: Linter; + setParserOptionsProject: boolean; // default is false /** * command to give playwright to run the web server * @example: "npx nx serve my-fe-app" diff --git a/packages/playwright/src/generators/configuration/schema.json b/packages/playwright/src/generators/configuration/schema.json index 6d3b2ad2e6632..87f51da01daee 100644 --- a/packages/playwright/src/generators/configuration/schema.json +++ b/packages/playwright/src/generators/configuration/schema.json @@ -33,6 +33,17 @@ "type": "string", "description": "The address of the web server." }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "eslint" + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", + "default": false + }, "skipFormat": { "description": "Skip formatting files.", "type": "boolean", diff --git a/packages/playwright/src/utils/add-linter.ts b/packages/playwright/src/utils/add-linter.ts new file mode 100644 index 0000000000000..30405da38940b --- /dev/null +++ b/packages/playwright/src/utils/add-linter.ts @@ -0,0 +1,103 @@ +import { + addDependenciesToPackageJson, + GeneratorCallback, + joinPathFragments, + readProjectConfiguration, + runTasksInSerial, + Tree, + updateJson, +} from '@nx/devkit'; +import { Linter, lintProjectGenerator } from '@nx/linter'; +import { globalJavaScriptOverrides } from '@nx/linter/src/generators/init/global-eslint-config'; +import { eslintPluginPlaywrightVersion } from './versions'; + +export interface PlaywrightLinterOptions { + project: string; + linter: Linter; + setParserOptionsProject: boolean; + skipPackageJson: boolean; + rootProject: boolean; + js?: boolean; + /** + * Directory from the project root, where the playwright files will be located. + **/ + directory: string; +} + +export async function addLinterToPlaywrightProject( + tree: Tree, + options: PlaywrightLinterOptions +): Promise { + if (options.linter === Linter.None) { + return () => {}; + } + + const tasks: GeneratorCallback[] = []; + const projectConfig = readProjectConfiguration(tree, options.project); + + if (!tree.exists(joinPathFragments(projectConfig.root, '.eslintrc.json'))) { + tasks.push( + await lintProjectGenerator(tree, { + project: options.project, + linter: options.linter, + skipFormat: true, + tsConfigPaths: [joinPathFragments(projectConfig.root, 'tsconfig.json')], + eslintFilePatterns: [ + `${projectConfig.root}/**/*.${options.js ? 'js' : '{js,ts}'}`, + ], + setParserOptionsProject: options.setParserOptionsProject, + skipPackageJson: options.skipPackageJson, + rootProject: options.rootProject, + }) + ); + } + + if (!options.linter || options.linter !== Linter.EsLint) { + return runTasksInSerial(...tasks); + } + + tasks.push( + !options.skipPackageJson + ? addDependenciesToPackageJson( + tree, + {}, + { 'eslint-plugin-playwright': eslintPluginPlaywrightVersion } + ) + : () => {} + ); + + updateJson( + tree, + joinPathFragments(projectConfig.root, '.eslintrc.json'), + (json) => { + if (options.rootProject) { + json.plugins = ['@nx']; + json.extends = ['plugin:playwright/recommended']; + } else { + json.extends = ['plugin:playwright/recommended', ...json.extends]; + } + json.overrides ??= []; + const globals = options.rootProject ? [globalJavaScriptOverrides] : []; + const override = { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + parserOptions: !options.setParserOptionsProject + ? undefined + : { + project: `${projectConfig.root}/tsconfig.*?.json`, + }, + rules: {}, + }; + const palywrightFiles = [ + { + ...override, + files: [`${options.directory}/**/*.{ts,js,tsx,jsx}`], + }, + ]; + json.overrides.push(...globals); + json.overrides.push(...palywrightFiles); + return json; + } + ); + + return runTasksInSerial(...tasks); +} diff --git a/packages/playwright/src/utils/preset.ts b/packages/playwright/src/utils/preset.ts index 881a1e2141554..1a7b080d16600 100644 --- a/packages/playwright/src/utils/preset.ts +++ b/packages/playwright/src/utils/preset.ts @@ -41,7 +41,7 @@ export interface NxPlaywrightOptions { * }) * * @param pathToConfig will be used to construct the output paths for reporters and test results - * @param options optional confiuration options + * @param options optional configuration options */ export function nxE2EPreset( pathToConfig: string, diff --git a/packages/playwright/src/utils/versions.ts b/packages/playwright/src/utils/versions.ts index cb9d1709cb7d1..875a771d9a6c3 100644 --- a/packages/playwright/src/utils/versions.ts +++ b/packages/playwright/src/utils/versions.ts @@ -1,2 +1,3 @@ export const nxVersion = require('../../package.json').version; export const playwrightVersion = '^1.36.0'; +export const eslintPluginPlaywrightVersion = '^0.15.3';