@nx/cypress 17.2.0-beta.3 → 17.2.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/cypress",
3
- "version": "17.2.0-beta.3",
3
+ "version": "17.2.0-beta.5",
4
4
  "private": false,
5
5
  "description": "The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.",
6
6
  "repository": {
@@ -34,15 +34,15 @@
34
34
  "migrations": "./migrations.json"
35
35
  },
36
36
  "dependencies": {
37
- "@nx/devkit": "17.2.0-beta.3",
38
- "@nx/eslint": "17.2.0-beta.3",
39
- "@nx/js": "17.2.0-beta.3",
37
+ "@nx/devkit": "17.2.0-beta.5",
38
+ "@nx/eslint": "17.2.0-beta.5",
39
+ "@nx/js": "17.2.0-beta.5",
40
40
  "@phenomnomnominal/tsquery": "~5.0.1",
41
41
  "detect-port": "^1.5.1",
42
42
  "string-argv": "^0.3.2",
43
43
  "semver": "7.5.3",
44
44
  "tslib": "^2.3.0",
45
- "@nrwl/cypress": "17.2.0-beta.3"
45
+ "@nrwl/cypress": "17.2.0-beta.5"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "cypress": ">= 3 < 14"
@@ -32,6 +32,17 @@ export declare function nxBaseCypressPreset(pathToConfig: string, options?: {
32
32
  *
33
33
  */
34
34
  export declare function nxE2EPreset(pathToConfig: string, options?: NxCypressE2EPresetOptions): any;
35
+ export interface WebServerConfig {
36
+ /**
37
+ * Timeout to wait for the webserver to start listening
38
+ */
39
+ timeout?: number;
40
+ /**
41
+ * Reuse an existing web server if it exists
42
+ * If this is false, an error will be thrown if the server is already running
43
+ */
44
+ reuseExistingServer?: boolean;
45
+ }
35
46
  export type NxCypressE2EPresetOptions = {
36
47
  bundler?: string;
37
48
  /**
@@ -42,5 +53,6 @@ export type NxCypressE2EPresetOptions = {
42
53
  cypressDir?: string;
43
54
  webServerCommands?: Record<string, string>;
44
55
  ciWebServerCommand?: string;
56
+ webServerConfig?: WebServerConfig;
45
57
  };
46
58
  export {};
@@ -5,8 +5,9 @@ const devkit_1 = require("@nx/devkit");
5
5
  const path_1 = require("path");
6
6
  const fs_1 = require("fs");
7
7
  const preprocessor_vite_1 = require("../src/plugins/preprocessor-vite");
8
- const string_argv_1 = require("string-argv");
9
8
  const child_process_1 = require("child_process");
9
+ const http_1 = require("http");
10
+ const https_1 = require("https");
10
11
  function nxBaseCypressPreset(pathToConfig, options) {
11
12
  // used to set babel settings for react CT.
12
13
  process.env.NX_CYPRESS_COMPONENT_TEST =
@@ -27,6 +28,16 @@ function nxBaseCypressPreset(pathToConfig, options) {
27
28
  };
28
29
  }
29
30
  exports.nxBaseCypressPreset = nxBaseCypressPreset;
31
+ function startWebServer(webServerCommand) {
32
+ const serverProcess = (0, child_process_1.exec)(webServerCommand, {
33
+ cwd: devkit_1.workspaceRoot,
34
+ });
35
+ serverProcess.stdout.pipe(process.stdout);
36
+ serverProcess.stderr.pipe(process.stderr);
37
+ return () => {
38
+ serverProcess.kill();
39
+ };
40
+ }
30
41
  /**
31
42
  * nx E2E Preset for Cypress
32
43
  * @description
@@ -61,37 +72,68 @@ function nxE2EPreset(pathToConfig, options) {
61
72
  if (!config.env.webServerCommands) {
62
73
  return;
63
74
  }
64
- const webServerCommand = config.env.webServerCommand ?? config.env.webServerCommands.default;
75
+ const webServerCommand = config.env.webServerCommand;
65
76
  if (!webServerCommand) {
66
77
  return;
67
78
  }
68
- if (!config.baseUrl && webServerCommand) {
69
- return new Promise((resolve) => {
70
- const [command, ...args] = (0, string_argv_1.default)(webServerCommand);
71
- const serverProcess = (0, child_process_1.spawn)(command, args, {
72
- stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
73
- cwd: devkit_1.workspaceRoot,
74
- // Shell is required for windows but cannot be true on linux because it interferes with terminating the process
75
- shell: process.platform === 'win32',
76
- windowsHide: true,
77
- });
78
- serverProcess.on('message', (message) => {
79
- if (typeof message === 'object' &&
80
- 'type' in message &&
81
- 'baseUrl' in message &&
82
- message.type === 'server') {
83
- config.baseUrl = message.baseUrl;
84
- resolve(config);
85
- }
86
- });
87
- on('after:run', () => {
88
- serverProcess.kill('SIGTERM');
89
- });
79
+ if (config.baseUrl && webServerCommand) {
80
+ if (await isServerUp(config.baseUrl)) {
81
+ if (options.webServerConfig.reuseExistingServer === undefined
82
+ ? true
83
+ : options.webServerConfig.reuseExistingServer) {
84
+ console.log(`Reusing the server already running on ${config.baseUrl}`);
85
+ return;
86
+ }
87
+ else {
88
+ throw new Error(`Web server is already running at ${config.baseUrl}`);
89
+ }
90
+ }
91
+ const killWebServer = startWebServer(webServerCommand);
92
+ on('after:run', () => {
93
+ killWebServer();
90
94
  });
95
+ await waitForServer(config.baseUrl, options.webServerConfig);
91
96
  }
92
- return config;
93
97
  },
94
98
  };
95
99
  return baseConfig;
96
100
  }
97
101
  exports.nxE2EPreset = nxE2EPreset;
102
+ function waitForServer(url, webServerConfig) {
103
+ return new Promise((resolve, reject) => {
104
+ let pollTimeout;
105
+ const { protocol } = new URL(url);
106
+ const timeoutDuration = webServerConfig?.timeout ?? 5 * 1000;
107
+ const timeout = setTimeout(() => {
108
+ clearTimeout(pollTimeout);
109
+ reject(new Error(`Web server failed to start in ${timeoutDuration}ms. This can be configured in cypress.config.ts.`));
110
+ }, timeoutDuration);
111
+ const makeRequest = protocol === 'https:' ? https_1.request : http_1.request;
112
+ function pollForServer() {
113
+ const request = makeRequest(url, () => {
114
+ clearTimeout(timeout);
115
+ resolve();
116
+ });
117
+ request.on('error', () => {
118
+ pollTimeout = setTimeout(pollForServer, 100);
119
+ });
120
+ // Don't forget to end the request
121
+ request.end();
122
+ }
123
+ pollForServer();
124
+ });
125
+ }
126
+ function isServerUp(url) {
127
+ const { protocol } = new URL(url);
128
+ const makeRequest = protocol === 'https:' ? https_1.request : http_1.request;
129
+ return new Promise((resolve) => {
130
+ const request = makeRequest(url, () => {
131
+ resolve(true);
132
+ });
133
+ request.on('error', () => {
134
+ resolve(false);
135
+ });
136
+ // Don't forget to end the request
137
+ request.end();
138
+ });
139
+ }
@@ -97,7 +97,7 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
97
97
  const cyFile = (0, devkit_1.joinPathFragments)(projectConfig.root, 'cypress.config.ts');
98
98
  let webServerCommands;
99
99
  let ciDevServerTarget;
100
- if (hasPlugin && !options.baseUrl && options.devServerTarget) {
100
+ if (hasPlugin && options.devServerTarget) {
101
101
  webServerCommands = {};
102
102
  webServerCommands.default = 'nx run ' + options.devServerTarget;
103
103
  const parsedTarget = (0, devkit_1.parseTargetString)(options.devServerTarget, projectGraph);
@@ -36,7 +36,9 @@ exports.createNodes = [
36
36
  !siblingFiles.includes('project.json')) {
37
37
  return {};
38
38
  }
39
- const hash = (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, context);
39
+ const hash = (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, context, [
40
+ (0, js_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot)),
41
+ ]);
40
42
  const targets = targetsCache[hash]
41
43
  ? targetsCache[hash]
42
44
  : buildCypressTargets(configFilePath, projectRoot, options, context);
@@ -96,7 +98,7 @@ function buildCypressTargets(configFilePath, projectRoot, options, context) {
96
98
  ...cypressConfig.env,
97
99
  ...cypressConfig.e2e?.env,
98
100
  };
99
- const devServerTargets = cypressEnv?.devServerTargets;
101
+ const webServerCommands = cypressEnv?.webServerCommands;
100
102
  const relativeConfigPath = (0, path_1.relative)(projectRoot, configFilePath);
101
103
  const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
102
104
  const targets = {};
@@ -117,19 +119,21 @@ function buildCypressTargets(configFilePath, projectRoot, options, context) {
117
119
  if (e2eTargetDefaults?.outputs === undefined) {
118
120
  targets[options.targetName].outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
119
121
  }
120
- if (devServerTargets?.default) {
121
- delete devServerTargets.default;
122
+ if (webServerCommands?.default) {
123
+ cypressConfig.e2e ??= {};
124
+ cypressConfig.e2e.webServerCommand ??= webServerCommands?.default;
125
+ delete webServerCommands.default;
122
126
  }
123
- if (Object.keys(devServerTargets ?? {}).length > 0) {
127
+ if (Object.keys(webServerCommands ?? {}).length > 0) {
124
128
  targets[options.targetName].configurations ??= {};
125
- for (const [configuration, devServerTarget] of Object.entries(devServerTargets ?? {})) {
129
+ for (const [configuration, webServerCommand] of Object.entries(webServerCommands ?? {})) {
126
130
  targets[options.targetName].configurations[configuration] = {
127
- command: `cypress run --config-file ${relativeConfigPath} --e2e --env.webServerCommand "nx run ${devServerTarget}"`,
131
+ command: `cypress run --config-file ${relativeConfigPath} --e2e --env webServerCommand="${webServerCommand}"`,
128
132
  };
129
133
  }
130
134
  }
131
- const ciDevServerTarget = cypressEnv?.ciDevServerTarget;
132
- if (ciDevServerTarget) {
135
+ const ciWebServerCommand = cypressEnv?.ciWebServerCommand;
136
+ if (ciWebServerCommand) {
133
137
  const specPatterns = Array.isArray(cypressConfig.e2e.specPattern)
134
138
  ? cypressConfig.e2e.specPattern.map((p) => (0, path_1.join)(projectRoot, p))
135
139
  : [(0, path_1.join)(projectRoot, cypressConfig.e2e.specPattern)];
@@ -150,7 +154,10 @@ function buildCypressTargets(configFilePath, projectRoot, options, context) {
150
154
  outputs,
151
155
  inputs,
152
156
  cache: true,
153
- command: `cypress run --config-file ${relativeConfigPath} --e2e --env.webServerCommand "nx run ${ciDevServerTarget}" --spec ${relativeSpecFilePath}`,
157
+ command: `cypress run --config-file ${relativeConfigPath} --e2e --env webServerCommand="${ciWebServerCommand}" --spec ${relativeSpecFilePath}`,
158
+ options: {
159
+ cwd: projectRoot,
160
+ },
154
161
  };
155
162
  dependsOn.push({
156
163
  target: targetName,