Skip to content

Commit

Permalink
feat(testing): e2e-ci should use serve-static or vite preview for pla…
Browse files Browse the repository at this point in the history
…ywright and cypress (nrwl#27240)

- fix(vite): preview should dependOn build
- fix(react): playwright should use vite preview
- fix(vue): playwright should use vite preview
- fix(web): playwright should use vite preview
- chore(testing): add e2e test

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
Currently, `playwright` uses the `vite serve` command when setting up
the web server to run the e2e tests against.

The `vite preview` command/target should also depend on `vite build`.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`playwright` should use the `vite preview` command when setting up the
web server

`vite preview` targets add a `dependsOn["build"]`

Ensure `serve-static` has a dependsOn: ['build']

Cypress should use the `ciBaseUrl` if it exists when running the
`e2e-ci` targets

Migrations for Playwright and Cypress to use serve-static and preview
correctly

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
Coly010 authored and ZackDeRose committed Aug 8, 2024
1 parent f1bef75 commit e6a0cc5
Show file tree
Hide file tree
Showing 38 changed files with 2,010 additions and 63 deletions.
2 changes: 1 addition & 1 deletion e2e/react/src/playwright.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('React Playwright e2e tests', () => {
packages: ['@nx/react'],
});
runCLI(
`generate @nx/react:app ${appName} --e2eTestRunner=playwright --projectNameAndRootFormat=as-provided --no-interactive`
`generate @nx/react:app ${appName} --e2eTestRunner=playwright --bundler=vite --projectNameAndRootFormat=as-provided --no-interactive`
);
});

Expand Down
8 changes: 8 additions & 0 deletions e2e/webpack/src/__snapshots__/webpack.legacy.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ exports[`Webpack Plugin (legacy) ConvertConfigToWebpackPlugin, should convert wi
"lint": {
"executor": "@nx/eslint:lint"
},
"serve-static": {
"executor": "@nx/web:file-server",
"dependsOn": ["build"],
"options": {
"buildTarget": "app3224373:build",
"spa": true
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
Expand Down
6 changes: 3 additions & 3 deletions e2e/webpack/src/webpack.legacy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ describe('Webpack Plugin (legacy)', () => {
updateFile(
`${appName}/src/main.ts`,
`
document.querySelector('proj-root').innerHTML = '<h1>Welcome</h1>';
document.querySelector('proj-root')!.innerHTML = '<h1>Welcome</h1>';
`
);
updateFile(
`${appName}/webpack.config.js`,
`
const { join } = require('path');
const {NxWebpackPlugin} = require('@nx/webpack');
const {NxAppWebpackPlugin} = require('@nx/webpack/app-plugin');
module.exports = {
output: {
path: join(__dirname, '../dist/app9524918'),
path: join(__dirname, '../dist/${appName}'),
},
plugins: [
new NxAppWebpackPlugin({
Expand Down
6 changes: 6 additions & 0 deletions packages/cypress/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"version": "18.1.0-beta.3",
"description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.",
"implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6"
},
"update-19-6-0-update-ci-webserver-for-vite": {
"cli": "nx",
"version": "19.6.0-beta.0",
"description": "Update ciWebServerCommand to use previewTargetName if Vite is detected for the application.",
"implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-vite"
}
},
"packageJsonUpdates": {
Expand Down
6 changes: 6 additions & 0 deletions packages/cypress/plugins/cypress-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export function nxE2EPreset(
webServerCommand: options?.webServerCommands?.default,
webServerCommands: options?.webServerCommands,
ciWebServerCommand: options?.ciWebServerCommand,
ciBaseUrl: options?.ciBaseUrl,
},

async setupNodeEvents(on, config) {
Expand Down Expand Up @@ -268,6 +269,11 @@ export type NxCypressE2EPresetOptions = {
*/
ciWebServerCommand?: string;

/**
* The url of the web server for ciWebServerCommand
*/
ciBaseUrl?: string;

/**
* Configures how the web server command is started and monitored.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface CypressE2EConfigSchema {

webServerCommands?: Record<string, string>;
ciWebServerCommand?: string;
ciBaseUrl?: string;
addPlugin?: boolean;
}

Expand Down Expand Up @@ -218,10 +219,12 @@ async function addFiles(
let webServerCommands: Record<string, string>;

let ciWebServerCommand: string;
let ciBaseUrl: string;

if (hasPlugin && options.webServerCommands && options.ciWebServerCommand) {
webServerCommands = options.webServerCommands;
ciWebServerCommand = options.ciWebServerCommand;
ciBaseUrl = options.ciBaseUrl;
} else if (hasPlugin && options.devServerTarget) {
webServerCommands = {};

Expand Down Expand Up @@ -253,6 +256,7 @@ async function addFiles(
bundler: options.bundler === 'vite' ? 'vite' : undefined,
webServerCommands,
ciWebServerCommand: ciWebServerCommand,
ciBaseUrl,
},
options.baseUrl
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import updateCiWebserverForVite from './update-ci-webserver-for-vite';
import {
type Tree,
type ProjectGraph,
readNxJson,
updateNxJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';

let projectGraph: ProjectGraph;
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
return projectGraph;
}),
}));

describe('updateCiWebserverForVite', () => {
let tree: Tree;
let tempFs: TempFs;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
tempFs = new TempFs('add-e2e-ci');
tree.root = tempFs.tempDir;
projectGraph = {
nodes: {},
dependencies: {},
externalNodes: {},
};
});

afterEach(() => {
tempFs.reset();
});

it('should do nothing if vite is not found for application', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
},
];
updateNxJson(tree, nxJson);

addProject(tree, tempFs, {
buildTargetName: 'build',
ciTargetName: 'e2e-ci',
appName: 'app',
noVite: true,
});

// ACT
await updateCiWebserverForVite(tree);

// ASSERT
expect(tree.read('app-e2e/cypress.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, {
cypressDir: 'src',
bundler: 'vite',
webServerCommands: {
default: 'nx run app:serve',
production: 'nx run app:preview',
},
ciWebServerCommand: 'nx run app:serve-static',
}),
baseUrl: 'http://localhost:4200',
},
});
"
`);
});

it('should update ciWebServerCommand to preview for vite app', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = [
{
plugin: '@nx/cypress/plugin',
options: {
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
},
{
plugin: '@nx/vite/plugin',
options: {
buildTargetName: 'build',
previewTargetName: 'preview',
},
},
];
updateNxJson(tree, nxJson);

addProject(tree, tempFs);

// ACT
await updateCiWebserverForVite(tree);

// ASSERT
expect(tree.read('app-e2e/cypress.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, {
cypressDir: 'src',
bundler: 'vite',
webServerCommands: {
default: 'nx run app:serve',
production: 'nx run app:preview',
},
ciWebServerCommand: 'nx run app:preview',
ciBaseUrl: 'http://localhost:4300',
}),
baseUrl: 'http://localhost:4200',
},
});
"
`);
});
});

function addProject(
tree: Tree,
tempFs: TempFs,
overrides: {
ciTargetName: string;
buildTargetName: string;
appName: string;
noCi?: boolean;
noVite?: boolean;
} = { ciTargetName: 'e2e-ci', buildTargetName: 'build', appName: 'app' }
) {
const appProjectConfig = {
name: overrides.appName,
root: overrides.appName,
sourceRoot: `${overrides.appName}/src`,
projectType: 'application',
};
const viteConfig = `/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/${overrides.appName}',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/${overrides.appName}',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});`;

const e2eProjectConfig = {
name: `${overrides.appName}-e2e`,
root: `${overrides.appName}-e2e`,
sourceRoot: `${overrides.appName}-e2e/src`,
projectType: 'application',
};

const cypressConfig = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, {
cypressDir: 'src',
bundler: 'vite',
webServerCommands: {
default: 'nx run ${overrides.appName}:serve',
production: 'nx run ${overrides.appName}:preview',
},
${
!overrides.noCi
? `ciWebServerCommand: 'nx run ${overrides.appName}:serve-static',`
: ''
}
}),
baseUrl: 'http://localhost:4200',
},
});
`;

if (!overrides.noVite) {
tree.write(`${overrides.appName}/vite.config.ts`, viteConfig);
}
tree.write(
`${overrides.appName}/project.json`,
JSON.stringify(appProjectConfig)
);
tree.write(`${overrides.appName}-e2e/cypress.config.ts`, cypressConfig);
tree.write(
`${overrides.appName}-e2e/project.json`,
JSON.stringify(e2eProjectConfig)
);
if (!overrides.noVite) {
tempFs.createFile(`${overrides.appName}/vite.config.ts`, viteConfig);
}
tempFs.createFilesSync({
[`${overrides.appName}/project.json`]: JSON.stringify(appProjectConfig),
[`${overrides.appName}-e2e/cypress.config.ts`]: cypressConfig,
[`${overrides.appName}-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
});

projectGraph.nodes[overrides.appName] = {
name: overrides.appName,
type: 'app',
data: {
projectType: 'application',
root: overrides.appName,
targets: {
[overrides.buildTargetName]: {},
'serve-static': {
options: {
buildTarget: overrides.buildTargetName,
},
},
},
},
};

projectGraph.nodes[`${overrides.appName}-e2e`] = {
name: `${overrides.appName}-e2e`,
type: 'app',
data: {
projectType: 'application',
root: `${overrides.appName}-e2e`,
targets: {
e2e: {},
[overrides.ciTargetName]: {},
},
},
};
}
Loading

0 comments on commit e6a0cc5

Please sign in to comment.