feat(testing): support continuous tasks (#30632)
## Current Behavior The Cypress and Playwright graph plugins do not infer tasks configured to take advantage of continuous tasks (do not add the task they run to start the app/server as a dependency of the e2e task). ## Expected Behavior The Cypress and Playwright graph plugins should infer tasks configured to take advantage of continuous tasks. ## Related Issue(s) Fixes #
This commit is contained in:
parent
46888b294c
commit
9a60dec0de
@ -444,6 +444,16 @@
|
||||
}
|
||||
},
|
||||
"migrations": {
|
||||
"/nx-api/angular/migrations/set-continuous-option": {
|
||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
||||
"hidden": false,
|
||||
"name": "set-continuous-option",
|
||||
"version": "21.0.0-beta.3",
|
||||
"originalFilePath": "/packages/angular",
|
||||
"path": "/nx-api/angular/migrations/set-continuous-option",
|
||||
"type": "migration"
|
||||
},
|
||||
"/nx-api/angular/migrations/20.5.0-angular-eslint-package-updates": {
|
||||
"description": "",
|
||||
"file": "generated/packages/angular/migrations/20.5.0-angular-eslint-package-updates.json",
|
||||
|
||||
@ -439,6 +439,16 @@
|
||||
}
|
||||
],
|
||||
"migrations": [
|
||||
{
|
||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
||||
"hidden": false,
|
||||
"name": "set-continuous-option",
|
||||
"version": "21.0.0-beta.3",
|
||||
"originalFilePath": "/packages/angular",
|
||||
"path": "angular/migrations/set-continuous-option",
|
||||
"type": "migration"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "generated/packages/angular/migrations/20.5.0-angular-eslint-package-updates.json",
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "set-continuous-option",
|
||||
"cli": "nx",
|
||||
"version": "21.0.0-beta.3",
|
||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||
"factory": "./src/migrations/update-21-0-0/set-continuous-option",
|
||||
"implementation": "/packages/angular/src/migrations/update-21-0-0/set-continuous-option.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/angular",
|
||||
"schema": null,
|
||||
"type": "migration",
|
||||
"examplesFile": "#### Set `continuous` Option for Continuous Tasks\n\nThis migration sets the `continuous` option to `true` for tasks that are known to run continuously, and only if the option is not already explicitly set.\n\nSpecifically, it updates Angular targets using the following executors:\n\n- `@angular-devkit/build-angular:dev-server`\n- `@angular-devkit/build-angular:ssr-dev-server`\n- `@nx/angular:dev-server`\n- `@nx/angular:module-federation-dev-server`\n- `@nx/angular:module-federation-dev-ssr`\n\n#### Examples\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/project.json\" %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"executor\": \"@angular-devkit/build-angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": true,\n \"executor\": \"@angular-devkit/build-angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nWhen a target is already explicitly configured with a `continuous` option, the migration will not modify it:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": false,\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": false,\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n"
|
||||
}
|
||||
@ -356,6 +356,12 @@
|
||||
},
|
||||
"description": "Update the @angular/cli package version to ~19.2.0.",
|
||||
"factory": "./src/migrations/update-20-5-0/update-angular-cli"
|
||||
},
|
||||
"set-continuous-option": {
|
||||
"cli": "nx",
|
||||
"version": "21.0.0-beta.3",
|
||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||
"factory": "./src/migrations/update-21-0-0/set-continuous-option"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -737,10 +737,12 @@ exports[`app nested should create project configs 1`] = `
|
||||
"buildTarget": "my-app:build:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
},
|
||||
"serve-static": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
@ -853,10 +855,12 @@ exports[`app not nested should create project configs 1`] = `
|
||||
"buildTarget": "my-app:build:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
},
|
||||
"serve-static": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
|
||||
@ -30,6 +30,7 @@ function addFileServerTarget(
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, options.name);
|
||||
projectConfig.targets[targetName] = {
|
||||
continuous: true,
|
||||
executor: '@nx/web:file-server',
|
||||
options: {
|
||||
buildTarget: `${options.name}:build`,
|
||||
|
||||
@ -93,6 +93,7 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
defaultConfiguration: 'production',
|
||||
},
|
||||
serve: {
|
||||
continuous: true,
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: options.port
|
||||
? {
|
||||
|
||||
@ -251,6 +251,7 @@ exports[`Host App Generator --ssr should generate the correct files 10`] = `
|
||||
"serverTarget": "test:server:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||
}
|
||||
@ -475,6 +476,7 @@ exports[`Host App Generator --ssr should generate the correct files for standalo
|
||||
"serverTarget": "test:server:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||
}
|
||||
@ -700,6 +702,7 @@ exports[`Host App Generator --ssr should generate the correct files for standalo
|
||||
"serverTarget": "test:server:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||
}
|
||||
@ -910,6 +913,7 @@ exports[`Host App Generator --ssr should generate the correct files when --types
|
||||
"serverTarget": "test:server:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"defaultConfiguration": "development",
|
||||
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ export function setupServeTarget(host: Tree, options: Schema) {
|
||||
|
||||
if (options.mfType === 'remote') {
|
||||
appConfig.targets['serve-static'] = {
|
||||
continuous: true,
|
||||
executor: '@nx/web:file-server',
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
|
||||
@ -118,6 +118,7 @@ export function updateProjectConfigForBrowserBuilder(
|
||||
};
|
||||
|
||||
projectConfig.targets['serve-ssr'] = {
|
||||
continuous: true,
|
||||
executor: '@angular-devkit/build-angular:ssr-dev-server',
|
||||
configurations: {
|
||||
development: {
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
#### Set `continuous` Option for Continuous Tasks
|
||||
|
||||
This migration sets the `continuous` option to `true` for tasks that are known to run continuously, and only if the option is not already explicitly set.
|
||||
|
||||
Specifically, it updates Angular targets using the following executors:
|
||||
|
||||
- `@angular-devkit/build-angular:dev-server`
|
||||
- `@angular-devkit/build-angular:ssr-dev-server`
|
||||
- `@nx/angular:dev-server`
|
||||
- `@nx/angular:module-federation-dev-server`
|
||||
- `@nx/angular:module-federation-dev-ssr`
|
||||
|
||||
#### Examples
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Before" %}
|
||||
|
||||
```json {% fileName="apps/app1/project.json" %}
|
||||
{
|
||||
// ...
|
||||
"targets": {
|
||||
// ...
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
"port": 4200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="After" %}
|
||||
|
||||
```json {% fileName="apps/app1/project.json" highlightLines=[6] %}
|
||||
{
|
||||
// ...
|
||||
"targets": {
|
||||
// ...
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
"port": 4200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
When a target is already explicitly configured with a `continuous` option, the migration will not modify it:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Before" %}
|
||||
|
||||
```json {% fileName="apps/app1/project.json" highlightLines=[6] %}
|
||||
{
|
||||
// ...
|
||||
"targets": {
|
||||
// ...
|
||||
"serve": {
|
||||
"continuous": false,
|
||||
"executor": "@nx/angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
"port": 4200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="After" %}
|
||||
|
||||
```json {% fileName="apps/app1/project.json" highlightLines=[6] %}
|
||||
{
|
||||
// ...
|
||||
"targets": {
|
||||
// ...
|
||||
"serve": {
|
||||
"continuous": false,
|
||||
"executor": "@nx/angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build",
|
||||
"port": 4200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
@ -0,0 +1,78 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import migration, { continuousExecutors } from './set-continuous-option';
|
||||
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
formatFiles: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('set-continuous-option migration', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it.each([...continuousExecutors])(
|
||||
'should set continuous option to true for targets using "%s" executor',
|
||||
async (executor) => {
|
||||
addProjectConfiguration(tree, 'app1', {
|
||||
root: 'apps/app1',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
serve: {
|
||||
executor,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migration(tree);
|
||||
|
||||
const project = readProjectConfiguration(tree, 'app1');
|
||||
expect(project.targets.serve.continuous).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it('should not change continuous option when it is already set', async () => {
|
||||
addProjectConfiguration(tree, 'app1', {
|
||||
root: 'apps/app1',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
serve: {
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
continuous: false,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migration(tree);
|
||||
|
||||
const project = readProjectConfiguration(tree, 'app1');
|
||||
expect(project.targets.serve.continuous).toBe(false);
|
||||
});
|
||||
|
||||
it('should not modify targets using other executors', async () => {
|
||||
addProjectConfiguration(tree, 'app1', {
|
||||
root: 'apps/app1',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migration(tree);
|
||||
|
||||
const project = readProjectConfiguration(tree, 'app1');
|
||||
expect(project.targets.build.continuous).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import {
|
||||
formatFiles,
|
||||
getProjects,
|
||||
type Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
|
||||
export const continuousExecutors = new Set([
|
||||
'@angular-devkit/build-angular:dev-server',
|
||||
'@angular-devkit/build-angular:ssr-dev-server',
|
||||
'@nx/angular:dev-server',
|
||||
'@nx/angular:module-federation-dev-server',
|
||||
'@nx/angular:module-federation-dev-ssr',
|
||||
]);
|
||||
|
||||
export default async function (tree: Tree) {
|
||||
const projects = getProjects(tree);
|
||||
|
||||
for (const [projectName, projectConfig] of projects) {
|
||||
let updated = false;
|
||||
|
||||
for (const targetConfig of Object.values(projectConfig.targets ?? {})) {
|
||||
if (
|
||||
continuousExecutors.has(targetConfig.executor) &&
|
||||
targetConfig.continuous === undefined
|
||||
) {
|
||||
targetConfig.continuous = true;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
updateProjectConfiguration(tree, projectName, projectConfig);
|
||||
}
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -137,6 +137,7 @@ describe('@nx/angular/plugin', () => {
|
||||
"command": "ng run my-app:serve:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"metadata": {
|
||||
"description": "Run the "serve" target for "my-app".",
|
||||
"help": {
|
||||
@ -435,6 +436,7 @@ describe('@nx/angular/plugin', () => {
|
||||
"command": "ng run org1-app1:serve:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"metadata": {
|
||||
"description": "Run the "serve" target for "org1-app1".",
|
||||
"help": {
|
||||
@ -619,6 +621,7 @@ describe('@nx/angular/plugin', () => {
|
||||
"command": "ng run org2-app1:serve:production",
|
||||
},
|
||||
},
|
||||
"continuous": true,
|
||||
"metadata": {
|
||||
"description": "Run the "serve" target for "org2-app1".",
|
||||
"help": {
|
||||
|
||||
@ -208,6 +208,7 @@ async function buildAngularProjects(
|
||||
namedInputs
|
||||
);
|
||||
} else if (knownExecutors.devServer.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].continuous = true;
|
||||
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
|
||||
} else if (knownExecutors.extractI18n.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].metadata.help.example.options = {
|
||||
@ -233,6 +234,7 @@ async function buildAngularProjects(
|
||||
namedInputs
|
||||
);
|
||||
} else if (knownExecutors.serveSsr.has(angularTarget.builder)) {
|
||||
targets[nxTargetName].continuous = true;
|
||||
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
|
||||
} else if (knownExecutors.prerender.has(angularTarget.builder)) {
|
||||
prerenderTargets.push({ target: nxTargetName, project: projectName });
|
||||
|
||||
@ -139,6 +139,7 @@ export function nxE2EPreset(
|
||||
webServerCommands: options?.webServerCommands,
|
||||
ciWebServerCommand: options?.ciWebServerCommand,
|
||||
ciBaseUrl: options?.ciBaseUrl,
|
||||
reuseExistingServer: options?.webServerConfig?.reuseExistingServer,
|
||||
},
|
||||
|
||||
async setupNodeEvents(on, config) {
|
||||
|
||||
@ -11,6 +11,8 @@ describe('@nx/cypress/plugin', () => {
|
||||
let createNodesFunction = createNodesV2[1];
|
||||
let context: CreateNodesContext;
|
||||
let tempFs: TempFs;
|
||||
let cwd = process.cwd();
|
||||
let originalCacheProjectGraph: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempFs = new TempFs('cypress-plugin');
|
||||
@ -37,12 +39,18 @@ describe('@nx/cypress/plugin', () => {
|
||||
workspaceRoot: tempFs.tempDir,
|
||||
configFiles: [],
|
||||
};
|
||||
|
||||
process.chdir(tempFs.tempDir);
|
||||
originalCacheProjectGraph = process.env.NX_CACHE_PROJECT_GRAPH;
|
||||
process.env.NX_CACHE_PROJECT_GRAPH = 'false';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
tempFs.cleanup();
|
||||
tempFs = null;
|
||||
process.chdir(cwd);
|
||||
process.env.NX_CACHE_PROJECT_GRAPH = originalCacheProjectGraph;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@ -90,6 +98,14 @@ describe('@nx/cypress/plugin', () => {
|
||||
"command": "cypress run --env webServerCommand="nx run my-app:serve:production"",
|
||||
},
|
||||
},
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"my-app",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
@ -124,7 +140,6 @@ describe('@nx/cypress/plugin', () => {
|
||||
"{projectRoot}/dist/videos",
|
||||
"{projectRoot}/dist/screenshots",
|
||||
],
|
||||
"parallelism": false,
|
||||
},
|
||||
"open-cypress": {
|
||||
"command": "cypress open",
|
||||
@ -451,6 +466,399 @@ describe('@nx/cypress/plugin', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should infer dependsOn using the task run in the webServerCommands.default and ciWebServerCommand for the e2e and atomized e2e-ci targets respectively and not set parallelism to false', async () => {
|
||||
mockCypressConfig(
|
||||
defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(join(tempFs.tempDir, 'cypress.config.js'), {
|
||||
webServerCommands: {
|
||||
default: 'npx nx run my-app:serve',
|
||||
production: 'npx nx run my-app:serve:production',
|
||||
},
|
||||
ciWebServerCommand: 'npx nx run my-app:serve-static',
|
||||
}),
|
||||
specPattern: '**/*.cy.ts',
|
||||
videosFolder: './dist/videos',
|
||||
screenshotsFolder: './dist/screenshots',
|
||||
},
|
||||
})
|
||||
);
|
||||
const nodes = await createNodesFunction(
|
||||
['cypress.config.js'],
|
||||
{ targetName: 'e2e' },
|
||||
context
|
||||
);
|
||||
|
||||
expect(nodes).toMatchInlineSnapshot(`
|
||||
[
|
||||
[
|
||||
"cypress.config.js",
|
||||
{
|
||||
"projects": {
|
||||
".": {
|
||||
"metadata": {
|
||||
"targetGroups": {
|
||||
"E2E (CI)": [
|
||||
"e2e-ci--src/test.cy.ts",
|
||||
"e2e-ci",
|
||||
],
|
||||
},
|
||||
},
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"cache": true,
|
||||
"command": "cypress run",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"command": "cypress run --env webServerCommand="npx nx run my-app:serve:production"",
|
||||
},
|
||||
},
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"my-app",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos",
|
||||
"{projectRoot}/dist/screenshots",
|
||||
],
|
||||
},
|
||||
"e2e-ci": {
|
||||
"cache": true,
|
||||
"dependsOn": [
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--src/test.cy.ts",
|
||||
},
|
||||
],
|
||||
"executor": "nx:noop",
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests in CI",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"nonAtomizedTarget": "e2e",
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos",
|
||||
"{projectRoot}/dist/screenshots",
|
||||
],
|
||||
"parallelism": false,
|
||||
},
|
||||
"e2e-ci--src/test.cy.ts": {
|
||||
"cache": true,
|
||||
"command": "cypress run --env webServerCommand="npx nx run my-app:serve-static" --spec src/test.cy.ts --config="{\\"e2e\\":{\\"videosFolder\\":\\"dist/videos/src-test-cy-ts\\",\\"screenshotsFolder\\":\\"dist/screenshots/src-test-cy-ts\\"}}"",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"my-app",
|
||||
],
|
||||
"target": "serve-static",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests in src/test.cy.ts in CI",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos/src-test-cy-ts",
|
||||
"{projectRoot}/dist/screenshots/src-test-cy-ts",
|
||||
],
|
||||
},
|
||||
"open-cypress": {
|
||||
"command": "cypress open",
|
||||
"metadata": {
|
||||
"description": "Opens Cypress",
|
||||
"help": {
|
||||
"command": "npx cypress open --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--e2e",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should set parallelism to false and not infer commands in dependsOn if reuseExistingServer is false', async () => {
|
||||
mockCypressConfig(
|
||||
defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(join(tempFs.tempDir, 'cypress.config.js'), {
|
||||
webServerCommands: {
|
||||
default: 'npx nx run my-app:serve',
|
||||
production: 'npx nx run my-app:serve:production',
|
||||
},
|
||||
ciWebServerCommand: 'npx nx run my-app:serve-static',
|
||||
webServerConfig: {
|
||||
reuseExistingServer: false,
|
||||
},
|
||||
}),
|
||||
specPattern: '**/*.cy.ts',
|
||||
videosFolder: './dist/videos',
|
||||
screenshotsFolder: './dist/screenshots',
|
||||
},
|
||||
})
|
||||
);
|
||||
const nodes = await createNodesFunction(
|
||||
['cypress.config.js'],
|
||||
{ targetName: 'e2e' },
|
||||
context
|
||||
);
|
||||
|
||||
expect(nodes).toMatchInlineSnapshot(`
|
||||
[
|
||||
[
|
||||
"cypress.config.js",
|
||||
{
|
||||
"projects": {
|
||||
".": {
|
||||
"metadata": {
|
||||
"targetGroups": {
|
||||
"E2E (CI)": [
|
||||
"e2e-ci--src/test.cy.ts",
|
||||
"e2e-ci",
|
||||
],
|
||||
},
|
||||
},
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"cache": true,
|
||||
"command": "cypress run",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"command": "cypress run --env webServerCommand="npx nx run my-app:serve:production"",
|
||||
},
|
||||
},
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos",
|
||||
"{projectRoot}/dist/screenshots",
|
||||
],
|
||||
"parallelism": false,
|
||||
},
|
||||
"e2e-ci": {
|
||||
"cache": true,
|
||||
"dependsOn": [
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--src/test.cy.ts",
|
||||
},
|
||||
],
|
||||
"executor": "nx:noop",
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests in CI",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"nonAtomizedTarget": "e2e",
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos",
|
||||
"{projectRoot}/dist/screenshots",
|
||||
],
|
||||
"parallelism": false,
|
||||
},
|
||||
"e2e-ci--src/test.cy.ts": {
|
||||
"cache": true,
|
||||
"command": "cypress run --env webServerCommand="npx nx run my-app:serve-static" --spec src/test.cy.ts --config="{\\"e2e\\":{\\"videosFolder\\":\\"dist/videos/src-test-cy-ts\\",\\"screenshotsFolder\\":\\"dist/screenshots/src-test-cy-ts\\"}}"",
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Cypress Tests in src/test.cy.ts in CI",
|
||||
"help": {
|
||||
"command": "npx cypress run --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--headed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist/videos/src-test-cy-ts",
|
||||
"{projectRoot}/dist/screenshots/src-test-cy-ts",
|
||||
],
|
||||
"parallelism": false,
|
||||
},
|
||||
"open-cypress": {
|
||||
"command": "cypress open",
|
||||
"metadata": {
|
||||
"description": "Opens Cypress",
|
||||
"help": {
|
||||
"command": "npx cypress open --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--dev",
|
||||
"--e2e",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"cypress",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
function mockCypressConfig(cypressConfig: Cypress.ConfigOptions) {
|
||||
// This isn't JS, but all that really matters here
|
||||
// is that the hash is different after updating the
|
||||
|
||||
@ -36,7 +36,13 @@ export interface CypressPluginOptions {
|
||||
}
|
||||
|
||||
function readTargetsCache(cachePath: string): Record<string, CypressTargets> {
|
||||
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
|
||||
try {
|
||||
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false'
|
||||
? readJsonFile(cachePath)
|
||||
: {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function writeTargetsToCache(cachePath: string, results: CypressTargets) {
|
||||
@ -257,6 +263,8 @@ async function buildCypressTargets(
|
||||
|
||||
const webServerCommands: Record<string, string> =
|
||||
pluginPresetOptions?.webServerCommands;
|
||||
const shouldReuseExistingServer =
|
||||
pluginPresetOptions?.reuseExistingServer ?? true;
|
||||
|
||||
const namedInputs = getNamedInputs(projectRoot, context);
|
||||
|
||||
@ -274,7 +282,6 @@ async function buildCypressTargets(
|
||||
cache: true,
|
||||
inputs: getInputs(namedInputs),
|
||||
outputs: getOutputs(projectRoot, cypressConfig, 'e2e'),
|
||||
parallelism: false,
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: 'Runs Cypress Tests',
|
||||
@ -288,7 +295,23 @@ async function buildCypressTargets(
|
||||
};
|
||||
|
||||
if (webServerCommands?.default) {
|
||||
const webServerCommandTask = shouldReuseExistingServer
|
||||
? parseTaskFromCommand(webServerCommands.default)
|
||||
: null;
|
||||
if (webServerCommandTask) {
|
||||
targets[options.targetName].dependsOn = [
|
||||
{
|
||||
projects: [webServerCommandTask.project],
|
||||
target: webServerCommandTask.target,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
targets[options.targetName].parallelism = false;
|
||||
}
|
||||
|
||||
delete webServerCommands.default;
|
||||
} else {
|
||||
targets[options.targetName].parallelism = false;
|
||||
}
|
||||
|
||||
if (Object.keys(webServerCommands ?? {}).length > 0) {
|
||||
@ -329,6 +352,9 @@ async function buildCypressTargets(
|
||||
const groupName = 'E2E (CI)';
|
||||
metadata = { targetGroups: { [groupName]: [] } };
|
||||
const ciTargetGroup = metadata.targetGroups[groupName];
|
||||
const ciWebServerCommandTask = shouldReuseExistingServer
|
||||
? parseTaskFromCommand(ciWebServerCommand)
|
||||
: null;
|
||||
|
||||
for (const file of specFiles) {
|
||||
const relativeSpecFilePath = normalizePath(relative(projectRoot, file));
|
||||
@ -351,7 +377,6 @@ async function buildCypressTargets(
|
||||
cwd: projectRoot,
|
||||
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
|
||||
},
|
||||
parallelism: false,
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: `Runs Cypress Tests in ${relativeSpecFilePath} in CI`,
|
||||
@ -368,6 +393,17 @@ async function buildCypressTargets(
|
||||
projects: 'self',
|
||||
params: 'forward',
|
||||
});
|
||||
|
||||
if (ciWebServerCommandTask) {
|
||||
targets[targetName].dependsOn = [
|
||||
{
|
||||
target: ciWebServerCommandTask.target,
|
||||
projects: [ciWebServerCommandTask.project],
|
||||
},
|
||||
];
|
||||
} else {
|
||||
targets[targetName].parallelism = false;
|
||||
}
|
||||
}
|
||||
|
||||
targets[options.ciTargetName] = {
|
||||
@ -457,3 +493,26 @@ function getInputs(
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function parseTaskFromCommand(command: string): {
|
||||
project: string;
|
||||
target: string;
|
||||
} | null {
|
||||
const nxRunRegex =
|
||||
/^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx run (\S+:\S+)$/;
|
||||
const infixRegex = /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx (\S+ \S+)$/;
|
||||
|
||||
const nxRunMatch = command.match(nxRunRegex);
|
||||
if (nxRunMatch) {
|
||||
const [project, target] = nxRunMatch[1].split(':');
|
||||
return { project, target };
|
||||
}
|
||||
|
||||
const infixMatch = command.match(infixRegex);
|
||||
if (infixMatch) {
|
||||
const [target, project] = infixMatch[1].split(' ');
|
||||
return { project, target };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -40,12 +40,14 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = `
|
||||
},
|
||||
"my-serve": {
|
||||
"command": "next dev",
|
||||
"continuous": true,
|
||||
"options": {
|
||||
"cwd": "my-app",
|
||||
},
|
||||
},
|
||||
"my-serve-static": {
|
||||
"command": "next start",
|
||||
"continuous": true,
|
||||
"dependsOn": [
|
||||
"my-build",
|
||||
],
|
||||
@ -55,6 +57,7 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = `
|
||||
},
|
||||
"my-start": {
|
||||
"command": "next start",
|
||||
"continuous": true,
|
||||
"dependsOn": [
|
||||
"my-build",
|
||||
],
|
||||
@ -117,12 +120,14 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = `
|
||||
},
|
||||
"dev": {
|
||||
"command": "next dev",
|
||||
"continuous": true,
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
},
|
||||
"serve-static": {
|
||||
"command": "next start",
|
||||
"continuous": true,
|
||||
"dependsOn": [
|
||||
"build",
|
||||
],
|
||||
@ -132,6 +137,7 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = `
|
||||
},
|
||||
"start": {
|
||||
"command": "next start",
|
||||
"continuous": true,
|
||||
"dependsOn": [
|
||||
"build",
|
||||
],
|
||||
|
||||
@ -219,6 +219,7 @@ async function getBuildTargetConfig(
|
||||
|
||||
function getDevTargetConfig(projectRoot: string) {
|
||||
const targetConfig: TargetConfiguration = {
|
||||
continuous: true,
|
||||
command: `next dev`,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
@ -230,6 +231,7 @@ function getDevTargetConfig(projectRoot: string) {
|
||||
|
||||
function getStartTargetConfig(options: NextPluginOptions, projectRoot: string) {
|
||||
const targetConfig: TargetConfiguration = {
|
||||
continuous: true,
|
||||
command: `next start`,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
|
||||
@ -26,13 +26,13 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: '<%= webServerCommand %>',
|
||||
url: '<%= webServerAddress %>',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot
|
||||
},<% } else {%>
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// reuseExistingServer: true,
|
||||
// cwd: workspaceRoot,
|
||||
// },<% } %>
|
||||
projects: [
|
||||
|
||||
@ -137,7 +137,7 @@ function createTestProject(
|
||||
webServer: {
|
||||
command: 'npx nx serve myapp',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
|
||||
@ -8,6 +8,8 @@ describe('@nx/playwright/plugin', () => {
|
||||
let createNodesFunction = createNodesV2[1];
|
||||
let context: CreateNodesContext;
|
||||
let tempFs: TempFs;
|
||||
let cwd = process.cwd();
|
||||
let originalCacheProjectGraph: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempFs = new TempFs('playwright-plugin');
|
||||
@ -26,11 +28,16 @@ describe('@nx/playwright/plugin', () => {
|
||||
workspaceRoot: tempFs.tempDir,
|
||||
configFiles: [],
|
||||
};
|
||||
|
||||
process.chdir(tempFs.tempDir);
|
||||
originalCacheProjectGraph = process.env.NX_CACHE_PROJECT_GRAPH;
|
||||
process.env.NX_CACHE_PROJECT_GRAPH = 'false';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// tempFs.cleanup();
|
||||
jest.resetModules();
|
||||
process.chdir(cwd);
|
||||
process.env.NX_CACHE_PROJECT_GRAPH = originalCacheProjectGraph;
|
||||
});
|
||||
|
||||
it('should create nodes with default playwright configuration', async () => {
|
||||
@ -431,6 +438,451 @@ describe('@nx/playwright/plugin', () => {
|
||||
expect(targets['e2e-ci--tests/ignored/run-me.spec.ts']).not.toBeDefined();
|
||||
expect(targets['e2e-ci--not-tests/run-me.spec.ts']).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should infer dependsOn using the task run in the webServer.command and not set parallelism to false', async () => {
|
||||
await mockPlaywrightConfig(tempFs, {
|
||||
testDir: 'tests',
|
||||
webServer: {
|
||||
command: 'npx nx run app1:serve',
|
||||
reuseExistingServer: true,
|
||||
},
|
||||
});
|
||||
await tempFs.createFiles({
|
||||
'tests/run-me.spec.ts': '',
|
||||
'tests/run-me-2.spec.ts': '',
|
||||
});
|
||||
|
||||
const results = await createNodesFunction(
|
||||
['playwright.config.js'],
|
||||
{
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
context
|
||||
);
|
||||
const project = results[0][1].projects['.'];
|
||||
const { targets } = project;
|
||||
expect(targets['e2e']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"dependsOn": [
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--tests/run-me-2.spec.ts",
|
||||
},
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--tests/run-me.spec.ts",
|
||||
},
|
||||
],
|
||||
"executor": "nx:noop",
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"nonAtomizedTarget": "e2e",
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results",
|
||||
],
|
||||
"parallelism": false,
|
||||
}
|
||||
`);
|
||||
expect(project.metadata.targetGroups).toMatchInlineSnapshot(`
|
||||
{
|
||||
"E2E (CI)": [
|
||||
"e2e-ci--tests/run-me-2.spec.ts",
|
||||
"e2e-ci--tests/run-me.spec.ts",
|
||||
"e2e-ci",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci--tests/run-me.spec.ts']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test tests/run-me.spec.ts --output=test-results/tests-run-me-spec-ts",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in tests/run-me.spec.ts in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"env": {},
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results/tests-run-me-spec-ts",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci--tests/run-me-2.spec.ts']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test tests/run-me-2.spec.ts --output=test-results/tests-run-me-2-spec-ts",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in tests/run-me-2.spec.ts in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"env": {},
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results/tests-run-me-2-spec-ts",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not set parallelism to false and should infer dependsOn using the tasks run in the different webServer.command that have reuseExistingServer set to true', async () => {
|
||||
await mockPlaywrightConfig(tempFs, {
|
||||
testDir: 'tests',
|
||||
webServer: [
|
||||
{ command: 'npx nx run app1:serve', reuseExistingServer: true },
|
||||
{ command: 'npx nx run api1:serve', reuseExistingServer: true },
|
||||
{ command: 'npx nx run api2:dev', reuseExistingServer: true },
|
||||
{ command: 'npx nx run api3:serve', reuseExistingServer: false }, // this one should not be included in dependsOn
|
||||
],
|
||||
});
|
||||
await tempFs.createFiles({
|
||||
'tests/run-me.spec.ts': '',
|
||||
'tests/run-me-2.spec.ts': '',
|
||||
});
|
||||
|
||||
const results = await createNodesFunction(
|
||||
['playwright.config.js'],
|
||||
{
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
context
|
||||
);
|
||||
const project = results[0][1].projects['.'];
|
||||
const { targets } = project;
|
||||
expect(targets['e2e']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
"api1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
{
|
||||
"projects": [
|
||||
"api2",
|
||||
],
|
||||
"target": "dev",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"dependsOn": [
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--tests/run-me-2.spec.ts",
|
||||
},
|
||||
{
|
||||
"params": "forward",
|
||||
"projects": "self",
|
||||
"target": "e2e-ci--tests/run-me.spec.ts",
|
||||
},
|
||||
],
|
||||
"executor": "nx:noop",
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"nonAtomizedTarget": "e2e",
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results",
|
||||
],
|
||||
"parallelism": false,
|
||||
}
|
||||
`);
|
||||
expect(project.metadata.targetGroups).toMatchInlineSnapshot(`
|
||||
{
|
||||
"E2E (CI)": [
|
||||
"e2e-ci--tests/run-me-2.spec.ts",
|
||||
"e2e-ci--tests/run-me.spec.ts",
|
||||
"e2e-ci",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci--tests/run-me.spec.ts']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test tests/run-me.spec.ts --output=test-results/tests-run-me-spec-ts",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
"api1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
{
|
||||
"projects": [
|
||||
"api2",
|
||||
],
|
||||
"target": "dev",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in tests/run-me.spec.ts in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"env": {},
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results/tests-run-me-spec-ts",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(targets['e2e-ci--tests/run-me-2.spec.ts']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"cache": true,
|
||||
"command": "playwright test tests/run-me-2.spec.ts --output=test-results/tests-run-me-2-spec-ts",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"app1",
|
||||
"api1",
|
||||
],
|
||||
"target": "serve",
|
||||
},
|
||||
{
|
||||
"projects": [
|
||||
"api2",
|
||||
],
|
||||
"target": "dev",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"@playwright/test",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs Playwright Tests in tests/run-me-2.spec.ts in CI",
|
||||
"help": {
|
||||
"command": "npx playwright test --help",
|
||||
"example": {
|
||||
"options": {
|
||||
"workers": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"playwright",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"env": {},
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/test-results/tests-run-me-2-spec-ts",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
async function mockPlaywrightConfig(
|
||||
|
||||
@ -44,7 +44,13 @@ type PlaywrightTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
|
||||
function readTargetsCache(
|
||||
cachePath: string
|
||||
): Record<string, PlaywrightTargets> {
|
||||
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
|
||||
try {
|
||||
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false'
|
||||
? readJsonFile(cachePath)
|
||||
: {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function writeTargetsToCache(
|
||||
@ -159,12 +165,12 @@ async function buildPlaywrightTargets(
|
||||
|
||||
const testOutput = getTestOutput(playwrightConfig);
|
||||
const reporterOutputs = getReporterOutputs(playwrightConfig);
|
||||
const webserverCommandTasks = getWebserverCommandTasks(playwrightConfig);
|
||||
const baseTargetConfig: TargetConfiguration = {
|
||||
command: 'playwright test',
|
||||
options: {
|
||||
cwd: '{projectRoot}',
|
||||
},
|
||||
parallelism: false,
|
||||
metadata: {
|
||||
technologies: ['playwright'],
|
||||
description: 'Runs Playwright Tests',
|
||||
@ -179,6 +185,12 @@ async function buildPlaywrightTargets(
|
||||
},
|
||||
};
|
||||
|
||||
if (webserverCommandTasks.length) {
|
||||
baseTargetConfig.dependsOn = getDependsOn(webserverCommandTasks);
|
||||
} else {
|
||||
baseTargetConfig.parallelism = false;
|
||||
}
|
||||
|
||||
targets[options.targetName] = {
|
||||
...baseTargetConfig,
|
||||
cache: true,
|
||||
@ -438,6 +450,74 @@ function addSubfolderToOutput(output: string, subfolder?: string): string {
|
||||
return join(output, subfolder);
|
||||
}
|
||||
|
||||
function getWebserverCommandTasks(
|
||||
playwrightConfig: PlaywrightTestConfig
|
||||
): Array<{ project: string; target: string }> {
|
||||
if (!playwrightConfig.webServer) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tasks: Array<{ project: string; target: string }> = [];
|
||||
|
||||
const webServer = Array.isArray(playwrightConfig.webServer)
|
||||
? playwrightConfig.webServer
|
||||
: [playwrightConfig.webServer];
|
||||
|
||||
for (const server of webServer) {
|
||||
if (!server.reuseExistingServer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const task = parseTaskFromCommand(server.command);
|
||||
if (task) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
function parseTaskFromCommand(command: string): {
|
||||
project: string;
|
||||
target: string;
|
||||
} | null {
|
||||
const nxRunRegex =
|
||||
/^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx run (\S+:\S+)$/;
|
||||
const infixRegex = /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx (\S+ \S+)$/;
|
||||
|
||||
const nxRunMatch = command.match(nxRunRegex);
|
||||
if (nxRunMatch) {
|
||||
const [project, target] = nxRunMatch[1].split(':');
|
||||
return { project, target };
|
||||
}
|
||||
|
||||
const infixMatch = command.match(infixRegex);
|
||||
if (infixMatch) {
|
||||
const [target, project] = infixMatch[1].split(' ');
|
||||
return { project, target };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDependsOn(
|
||||
tasks: Array<{ project: string; target: string }>
|
||||
): TargetConfiguration['dependsOn'] {
|
||||
const projectsPerTask = new Map<string, string[]>();
|
||||
|
||||
for (const { project, target } of tasks) {
|
||||
if (!projectsPerTask.has(target)) {
|
||||
projectsPerTask.set(target, []);
|
||||
}
|
||||
projectsPerTask.get(target).push(project);
|
||||
}
|
||||
|
||||
return Array.from(projectsPerTask.entries()).map(([target, projects]) => ({
|
||||
projects,
|
||||
target,
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeOutput(
|
||||
path: string,
|
||||
workspaceRoot: string,
|
||||
|
||||
@ -180,7 +180,7 @@ describe('app', () => {
|
||||
webServer: {
|
||||
command: '${packageCmd} nx run my-app:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot
|
||||
},
|
||||
projects: [
|
||||
|
||||
@ -182,7 +182,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run test:serve-static',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
@ -712,7 +712,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run test:serve-static',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
|
||||
@ -364,7 +364,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run test:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
|
||||
@ -29,7 +29,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run my-app:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
@ -101,7 +101,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run cool-app:serve-static',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
@ -173,7 +173,7 @@ export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npx nx run my-app:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user