feat(testing): add playwright configuration generator (#18013)
This commit is contained in:
parent
d489de59e0
commit
e5e561d648
@ -2,18 +2,14 @@ import {
|
||||
cleanupProject,
|
||||
newProject,
|
||||
uniq,
|
||||
createFile,
|
||||
runCLI,
|
||||
packageInstall,
|
||||
runCommand,
|
||||
ensurePlaywrightBrowsersInstallation,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
const TEN_MINS_MS = 600_000;
|
||||
describe('Playwright E2E Test runner', () => {
|
||||
beforeAll(() => {
|
||||
newProject({ name: uniq('playwright') });
|
||||
packageInstall('@playwright/test', undefined, '^1.30.0', 'dev');
|
||||
runCommand('npx playwright install --with-deps');
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
@ -21,151 +17,30 @@ describe('Playwright E2E Test runner', () => {
|
||||
it(
|
||||
'should test example app',
|
||||
() => {
|
||||
//TODO: remove when generators are setup.Need to have basic workspace deps setup
|
||||
runCLI(`g @nx/js:init`);
|
||||
addSampleProject();
|
||||
runCLI(`g @nx/js:lib demo-e2e --unitTestRunner none --bundler none`);
|
||||
runCLI(`g @nx/playwright:configuration --project demo-e2e`);
|
||||
ensurePlaywrightBrowsersInstallation();
|
||||
|
||||
// NOTE: playwright throws errors if it detects running inside jest process. tmp remove and restore the env var for playwright to run
|
||||
const results = runCLI(`e2e demo-e2e`);
|
||||
expect(results).toContain('6 passed');
|
||||
expect(results).toContain('Successfully ran target e2e for project');
|
||||
},
|
||||
TEN_MINS_MS
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: remove this when there are project generators
|
||||
function addSampleProject() {
|
||||
createFile(
|
||||
'apps/demo-e2e/src/example.spec.ts',
|
||||
`
|
||||
import { test, expect } from '@playwright/test';
|
||||
it(
|
||||
'should test example app with js',
|
||||
() => {
|
||||
runCLI(
|
||||
`g @nx/js:lib demo-js-e2e --unitTestRunner none --bundler none --js`
|
||||
);
|
||||
runCLI(`g @nx/playwright:configuration --project demo-js-e2e --js`);
|
||||
ensurePlaywrightBrowsersInstallation();
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Playwright/);
|
||||
});
|
||||
|
||||
test('get started link', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Click the get started link.
|
||||
await page.getByRole('link', { name: 'Get started' }).click();
|
||||
|
||||
// Expects the URL to contain intro.
|
||||
await expect(page).toHaveURL(/.*intro/);
|
||||
});
|
||||
`
|
||||
);
|
||||
createFile(
|
||||
'apps/demo-e2e/playwright.config.ts',
|
||||
`
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './src',
|
||||
outputDir: '../../dist/playwright/apps/demo-e2e/output',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
[
|
||||
'html',
|
||||
{
|
||||
outputFolder:
|
||||
'../../dist/playwright/apps/demo-e2e/playwright-report',
|
||||
},
|
||||
],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like await page.goto('/'). */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
const results = runCLI(`e2e demo-js-e2e`);
|
||||
expect(results).toContain('6 passed');
|
||||
expect(results).toContain('Successfully ran target e2e for project');
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
TEN_MINS_MS
|
||||
);
|
||||
});
|
||||
`
|
||||
);
|
||||
createFile(
|
||||
'apps/demo-e2e/project.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
name: 'demo-e2e',
|
||||
root: 'apps/demo-e2e',
|
||||
sourceRoot: 'apps/demo-e2e/src',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@nx/playwright:playwright',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -111,6 +111,17 @@ export function ensureCypressInstallation() {
|
||||
}
|
||||
}
|
||||
|
||||
export function ensurePlaywrightBrowsersInstallation() {
|
||||
execSync('npx playwright install --with-deps --force', {
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
encoding: 'utf-8',
|
||||
cwd: tmpProjPath(),
|
||||
});
|
||||
e2eConsoleLogger(
|
||||
`Playwright browsers ${execSync('npx playwright --version')} installed.`
|
||||
);
|
||||
}
|
||||
|
||||
export function getStrippedEnvironmentVariables() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(process.env).filter(([key, value]) => {
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["./package.json", "./executors.json"],
|
||||
"files": ["./package.json", "./executors.json", "./generators.json"],
|
||||
"parser": "jsonc-eslint-parser",
|
||||
"rules": {
|
||||
"@nx/nx-plugin-checks": "error"
|
||||
|
||||
28
packages/playwright/generators.json
Normal file
28
packages/playwright/generators.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Nx Playwright",
|
||||
"version": "0.1",
|
||||
"schematics": {
|
||||
"configuration": {
|
||||
"factory": "./src/generators/configuration/configuration#configurationSchematic",
|
||||
"schema": "./src/generators/configuration/schema.json",
|
||||
"description": "Add Nx Playwright configuration to your project"
|
||||
},
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init#initSchematic",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initializes a Playwright project in the current workspace"
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
"configuration": {
|
||||
"factory": "./src/generators/configuration/configuration",
|
||||
"schema": "./src/generators/configuration/schema.json",
|
||||
"description": "Add Nx Playwright configuration to your project"
|
||||
},
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initializes a Playwright project in the current workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,12 +32,13 @@
|
||||
"@nx/devkit": "file:../devkit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": "^1.30.0"
|
||||
"@playwright/test": "^1.36.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@playwright/test": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"executors": "./executors.json"
|
||||
"executors": "./executors.json",
|
||||
"generators": "./generators.json"
|
||||
}
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
import {
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
offsetFromRoot,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
toJS,
|
||||
Tree,
|
||||
updateNxJson,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import * as path from 'path';
|
||||
import { ConfigurationGeneratorSchema } from './schema';
|
||||
import initGenerator from '../init/init';
|
||||
|
||||
export async function configurationGenerator(
|
||||
tree: Tree,
|
||||
options: ConfigurationGeneratorSchema
|
||||
) {
|
||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||
generateFiles(tree, path.join(__dirname, 'files'), projectConfig.root, {
|
||||
offsetFromRoot: offsetFromRoot(projectConfig.root),
|
||||
projectRoot: projectConfig.root,
|
||||
...options,
|
||||
});
|
||||
|
||||
addE2eTarget(tree, options);
|
||||
setupE2ETargetDefaults(tree);
|
||||
|
||||
if (options.js) {
|
||||
toJS(tree);
|
||||
}
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return initGenerator(tree, {
|
||||
skipFormat: true,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
});
|
||||
}
|
||||
|
||||
function setupE2ETargetDefaults(tree: Tree) {
|
||||
const nxJson = 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.inputs ??= [
|
||||
'default',
|
||||
productionFileSet ? '^production' : '^default',
|
||||
];
|
||||
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
|
||||
function addE2eTarget(tree: Tree, options: ConfigurationGeneratorSchema) {
|
||||
const projectConfig = 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.e2e = {
|
||||
executor: '@nx/playwright:playwright',
|
||||
options: {},
|
||||
};
|
||||
updateProjectConfiguration(tree, options.project, projectConfig);
|
||||
}
|
||||
|
||||
export default configurationGenerator;
|
||||
export const configurationSchematic = convertNxGenerator(
|
||||
configurationGenerator
|
||||
);
|
||||
@ -0,0 +1,18 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Playwright/);
|
||||
});
|
||||
|
||||
test('get started link', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Click the get started link.
|
||||
await page.getByRole('link', { name: 'Get started' }).click();
|
||||
|
||||
// Expects the URL to contain intro.
|
||||
await expect(page).toHaveURL(/.*intro/);
|
||||
});
|
||||
@ -0,0 +1,85 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './<%= directory %>',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
[
|
||||
'html',
|
||||
{
|
||||
outputFolder:
|
||||
'<%= offsetFromRoot %>/dist/playwright/<%= projectRoot %>/playwright-report',
|
||||
},
|
||||
],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
7
packages/playwright/src/generators/configuration/schema.d.ts
vendored
Normal file
7
packages/playwright/src/generators/configuration/schema.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export interface ConfigurationGeneratorSchema {
|
||||
project: string;
|
||||
directory: string;
|
||||
js: boolean; // default is false
|
||||
skipFormat: boolean;
|
||||
skipPackageJson: boolean;
|
||||
}
|
||||
42
packages/playwright/src/generators/configuration/schema.json
Normal file
42
packages/playwright/src/generators/configuration/schema.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxPlaywrightConfiguration",
|
||||
"description": "Add a Playwright configuration.",
|
||||
"title": "Add a Playwright configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to add a Playwright configuration to",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
},
|
||||
"x-priority": "important",
|
||||
"x-prompt": "What is the name of the project to set up Playwright for?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "A directory where the project is placed relative from the project root",
|
||||
"x-priority": "important",
|
||||
"default": "playwright"
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||
"default": false
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["project"]
|
||||
}
|
||||
33
packages/playwright/src/generators/init/init.ts
Normal file
33
packages/playwright/src/generators/init/init.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { InitGeneratorSchema } from './schema';
|
||||
import { nxVersion, playwrightVersion } from '../../utils/versions';
|
||||
|
||||
export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
if (!options.skipPackageJson) {
|
||||
tasks.push(
|
||||
addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'@nx/playwright': nxVersion,
|
||||
'@playwright/test': playwrightVersion,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default initGenerator;
|
||||
export const initSchematic = convertNxGenerator(initGenerator);
|
||||
4
packages/playwright/src/generators/init/schema.d.ts
vendored
Normal file
4
packages/playwright/src/generators/init/schema.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export interface InitGeneratorSchema {
|
||||
skipFormat: boolean;
|
||||
skipPackageJson: boolean;
|
||||
}
|
||||
22
packages/playwright/src/generators/init/schema.json
Normal file
22
packages/playwright/src/generators/init/schema.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxPlaywrightInit",
|
||||
"title": "Playwright Init Generator",
|
||||
"description": "Initializes a Playwright project in the current workspace.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
2
packages/playwright/src/utils/versions.ts
Normal file
2
packages/playwright/src/utils/versions.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const nxVersion = require('../../package.json').version;
|
||||
export const playwrightVersion = '^1.36.0';
|
||||
Loading…
x
Reference in New Issue
Block a user