Skip to content

Commit

Permalink
feat(testing): add playwright configuration generator (#18013)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi authored Jul 14, 2023
1 parent d489de5 commit e5e561d
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 145 deletions.
159 changes: 17 additions & 142 deletions e2e/playwright/src/playwright.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,170 +2,45 @@ 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());

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';
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'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
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();

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
const results = runCLI(`e2e demo-js-e2e`);
expect(results).toContain('6 passed');
expect(results).toContain('Successfully ran target e2e for project');
},
/* 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,
// },
});
`
);
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
)
TEN_MINS_MS
);
}
});
11 changes: 11 additions & 0 deletions e2e/utils/get-env-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
28 changes: 28 additions & 0 deletions packages/playwright/generators.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
5 changes: 3 additions & 2 deletions packages/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
80 changes: 80 additions & 0 deletions packages/playwright/src/generators/configuration/configuration.ts
Original file line number Diff line number Diff line change
@@ -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
);
Original file line number Diff line number Diff line change
@@ -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/);
});
Loading

1 comment on commit e5e561d

@vercel
Copy link

@vercel vercel bot commented on e5e561d Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx.dev
nx-five.vercel.app

Please sign in to comment.