@nx/cypress 23.1.0-beta.5 → 23.1.0-beta.6
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/dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.d.ts +2 -0
- package/dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.js +140 -0
- package/dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.md +38 -0
- package/dist/src/utils/config.d.ts +7 -1
- package/dist/src/utils/config.js +28 -4
- package/migrations.json +21 -0
- package/package.json +5 -5
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = disableWebpackCtJustInTimeCompile;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const internal_1 = require("@nx/js/internal");
|
|
6
|
+
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
7
|
+
const config_1 = require("../../utils/config");
|
|
8
|
+
const migrations_1 = require("../../utils/migrations");
|
|
9
|
+
// @nx/remix component testing uses the vite dev server, so justInTimeCompile
|
|
10
|
+
// (webpack only) does not apply even though the preset takes no bundler option.
|
|
11
|
+
const NX_VITE_CT_PRESET = '@nx/remix/plugins/component-testing';
|
|
12
|
+
let ts;
|
|
13
|
+
async function disableWebpackCtJustInTimeCompile(tree) {
|
|
14
|
+
let wereProjectsMigrated = false;
|
|
15
|
+
for await (const { cypressConfigPath } of (0, migrations_1.cypressProjectConfigs)(tree)) {
|
|
16
|
+
if (!tree.exists(cypressConfigPath)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const contents = tree.read(cypressConfigPath, 'utf-8');
|
|
20
|
+
const config = (0, config_1.resolveCypressConfigObject)(contents);
|
|
21
|
+
if (!config) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const component = (0, migrations_1.getObjectProperty)(config, 'component');
|
|
25
|
+
if (!component ||
|
|
26
|
+
!isWebpackComponentTesting(component) ||
|
|
27
|
+
setsJustInTimeCompile(component)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
tree.write(cypressConfigPath, addJustInTimeCompile(contents, component));
|
|
31
|
+
wereProjectsMigrated = true;
|
|
32
|
+
}
|
|
33
|
+
if (wereProjectsMigrated) {
|
|
34
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isWebpackComponentTesting(component) {
|
|
38
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
39
|
+
// A vite bundler (inline devServer or preset options) opts out.
|
|
40
|
+
if (getComponentProperty(component, 'bundler').some((property) => ts.isStringLiteral(property.initializer) &&
|
|
41
|
+
property.initializer.text === 'vite')) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Preset-based config: trust it only when `component` actually calls
|
|
45
|
+
// nxComponentTestingPreset, so a stale/unused preset import doesn't count.
|
|
46
|
+
if (usesNxComponentTestingPreset(component)) {
|
|
47
|
+
// @nx/remix is the only vite-based Nx CT preset; resolve the import bound
|
|
48
|
+
// to the call so an unrelated remix reference elsewhere can't misclassify.
|
|
49
|
+
return getComponentTestingPresetImport(component) !== NX_VITE_CT_PRESET;
|
|
50
|
+
}
|
|
51
|
+
// Hand-written config: migrate only when it has an inline devServer framework.
|
|
52
|
+
return hasInlineDevServerFramework(component);
|
|
53
|
+
}
|
|
54
|
+
function usesNxComponentTestingPreset(component) {
|
|
55
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
56
|
+
return (0, tsquery_1.query)(component, 'CallExpression').some((call) => ts.isIdentifier(call.expression) &&
|
|
57
|
+
call.expression.text === 'nxComponentTestingPreset');
|
|
58
|
+
}
|
|
59
|
+
function hasInlineDevServerFramework(component) {
|
|
60
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
61
|
+
if (!ts.isObjectLiteralExpression(component.initializer)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const devServer = (0, migrations_1.getObjectProperty)(component.initializer, 'devServer');
|
|
65
|
+
if (!devServer || !ts.isObjectLiteralExpression(devServer.initializer)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return !!(0, migrations_1.getObjectProperty)(devServer.initializer, 'framework');
|
|
69
|
+
}
|
|
70
|
+
function getComponentProperty(component, name) {
|
|
71
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
72
|
+
return (0, tsquery_1.query)(component, 'PropertyAssignment').filter((property) => ts.isIdentifier(property.name) && property.name.text === name);
|
|
73
|
+
}
|
|
74
|
+
// Resolves the module specifier that binds the `nxComponentTestingPreset`
|
|
75
|
+
// identifier used inside `component`, covering both ESM `import` and CJS
|
|
76
|
+
// `require` cypress configs. Returns null when no such binding is found.
|
|
77
|
+
function getComponentTestingPresetImport(component) {
|
|
78
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
79
|
+
for (const statement of component.getSourceFile().statements) {
|
|
80
|
+
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
81
|
+
const namedBindings = statement.importClause.namedBindings;
|
|
82
|
+
if (namedBindings &&
|
|
83
|
+
ts.isNamedImports(namedBindings) &&
|
|
84
|
+
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
85
|
+
namedBindings.elements.some((element) => element.name.text === 'nxComponentTestingPreset')) {
|
|
86
|
+
return statement.moduleSpecifier.text;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (ts.isVariableStatement(statement)) {
|
|
90
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
91
|
+
const initializer = declaration.initializer;
|
|
92
|
+
if (!initializer ||
|
|
93
|
+
!ts.isCallExpression(initializer) ||
|
|
94
|
+
!ts.isIdentifier(initializer.expression) ||
|
|
95
|
+
initializer.expression.text !== 'require' ||
|
|
96
|
+
!ts.isObjectBindingPattern(declaration.name)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const moduleSpecifier = initializer.arguments[0];
|
|
100
|
+
if (moduleSpecifier &&
|
|
101
|
+
ts.isStringLiteral(moduleSpecifier) &&
|
|
102
|
+
declaration.name.elements.some((element) => ts.isIdentifier(element.name) &&
|
|
103
|
+
element.name.text === 'nxComponentTestingPreset')) {
|
|
104
|
+
return moduleSpecifier.text;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function setsJustInTimeCompile(component) {
|
|
112
|
+
return getComponentProperty(component, 'justInTimeCompile').length > 0;
|
|
113
|
+
}
|
|
114
|
+
function addJustInTimeCompile(contents, component) {
|
|
115
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
116
|
+
const indent = ' ';
|
|
117
|
+
const comment = config_1.JIT_COMPILE_DISABLE_COMMENT.map((line) => `${indent}${line}`).join('\n');
|
|
118
|
+
const newProperty = `${comment}\n${indent}justInTimeCompile: false,`;
|
|
119
|
+
const initializer = component.initializer;
|
|
120
|
+
// Object literal: insert the property after the last existing one, leaving
|
|
121
|
+
// the surrounding source (comments, formatting) untouched. `component` is
|
|
122
|
+
// gated to be non-empty before we reach here.
|
|
123
|
+
if (ts.isObjectLiteralExpression(initializer)) {
|
|
124
|
+
const properties = initializer.properties;
|
|
125
|
+
const lastProperty = properties[properties.length - 1];
|
|
126
|
+
let insertAt = lastProperty.getEnd();
|
|
127
|
+
if (properties.hasTrailingComma) {
|
|
128
|
+
// Insert after the existing trailing comma, not before it.
|
|
129
|
+
const commaIndex = contents.indexOf(',', insertAt);
|
|
130
|
+
if (commaIndex !== -1) {
|
|
131
|
+
insertAt = commaIndex + 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const separator = properties.hasTrailingComma ? '' : ',';
|
|
135
|
+
return `${contents.slice(0, insertAt)}${separator}\n${newProperty}${contents.slice(insertAt)}`;
|
|
136
|
+
}
|
|
137
|
+
// Preset call (or any other expression): wrap it in an object and spread.
|
|
138
|
+
const componentValue = `{\n${indent}...${initializer.getText()},\n${newProperty}\n }`;
|
|
139
|
+
return `${contents.slice(0, component.getStart())}component: ${componentValue}${contents.slice(component.getEnd())}`;
|
|
140
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#### Disable `justInTimeCompile` for webpack component testing
|
|
2
|
+
|
|
3
|
+
Cypress 14+ defaults `justInTimeCompile` to `true` for the webpack dev server, compiling each spec on demand. In run mode the runner can load a component test before its spec finishes compiling, so the spec executes 0 tests while the run still exits green - a false pass that hides broken component tests in CI.
|
|
4
|
+
|
|
5
|
+
This migration sets an explicit `justInTimeCompile: false` in the Cypress configs of webpack component testing projects, keeping the choice visible and reversible. Remove the line to opt back into just-in-time compilation.
|
|
6
|
+
|
|
7
|
+
#### Sample Code Changes
|
|
8
|
+
|
|
9
|
+
##### Before
|
|
10
|
+
|
|
11
|
+
```ts title="apps/my-app/cypress.config.ts"
|
|
12
|
+
import { defineConfig } from 'cypress';
|
|
13
|
+
import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
component: nxComponentTestingPreset(__filename),
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
##### After
|
|
21
|
+
|
|
22
|
+
```ts title="apps/my-app/cypress.config.ts"
|
|
23
|
+
import { defineConfig } from 'cypress';
|
|
24
|
+
import { nxComponentTestingPreset } from '@nx/react/plugins/component-testing';
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
component: {
|
|
28
|
+
...nxComponentTestingPreset(__filename),
|
|
29
|
+
// Cypress 14+ defaults justInTimeCompile to true (webpack only), which can
|
|
30
|
+
// intermittently run 0 tests in CI. Remove this line to opt back in.
|
|
31
|
+
justInTimeCompile: false,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### What is not changed
|
|
37
|
+
|
|
38
|
+
`justInTimeCompile` only applies to the webpack dev server, so vite-based component testing is left untouched. This migration skips vite configs (including `@nx/remix`, which uses the vite dev server), configs that already set `justInTimeCompile`, and e2e-only configs.
|
|
@@ -2,6 +2,7 @@ import { type Tree } from '@nx/devkit';
|
|
|
2
2
|
import type { ObjectLiteralExpression } from 'typescript';
|
|
3
3
|
import type { NxComponentTestingOptions, NxCypressE2EPresetOptions } from '../../plugins/cypress-preset';
|
|
4
4
|
export declare const CYPRESS_CONFIG_FILE_NAME_PATTERN = "cypress.config.{js,ts,mjs,cjs}";
|
|
5
|
+
export declare const JIT_COMPILE_DISABLE_COMMENT: string[];
|
|
5
6
|
export declare function addDefaultE2EConfig(cyConfigContents: string, options: NxCypressE2EPresetOptions, baseUrl: string): Promise<string>;
|
|
6
7
|
/**
|
|
7
8
|
* Adds the nxComponentTestingPreset to the cypress config file.
|
|
@@ -13,8 +14,13 @@ export declare function addDefaultE2EConfig(cyConfigContents: string, options: N
|
|
|
13
14
|
* doing so unconditionally produces mixed-syntax files in CJS workspaces
|
|
14
15
|
* (an ESM `import` followed by a CJS `module.exports`), so prefer passing
|
|
15
16
|
* `presetImportPath`.
|
|
17
|
+
*
|
|
18
|
+
* Pass `cypressMajorVersion` to opt webpack setups out of `justInTimeCompile`
|
|
19
|
+
* on Cypress 14+, where it defaults to `true` and can intermittently run 0
|
|
20
|
+
* tests in CI. The opt-out is emitted as an explicit `justInTimeCompile: false`
|
|
21
|
+
* so it is visible and reversible.
|
|
16
22
|
**/
|
|
17
|
-
export declare function addDefaultCTConfig(cyConfigContents: string, options?: NxComponentTestingOptions, presetImportPath?: string): Promise<string>;
|
|
23
|
+
export declare function addDefaultCTConfig(cyConfigContents: string, options?: NxComponentTestingOptions, presetImportPath?: string, cypressMajorVersion?: number | null): Promise<string>;
|
|
18
24
|
/**
|
|
19
25
|
* Adds the mount command for Cypress
|
|
20
26
|
* Make sure after calling this the correct import statement is added
|
package/dist/src/utils/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CYPRESS_CONFIG_FILE_NAME_PATTERN = void 0;
|
|
3
|
+
exports.JIT_COMPILE_DISABLE_COMMENT = exports.CYPRESS_CONFIG_FILE_NAME_PATTERN = void 0;
|
|
4
4
|
exports.addDefaultE2EConfig = addDefaultE2EConfig;
|
|
5
5
|
exports.addDefaultCTConfig = addDefaultCTConfig;
|
|
6
6
|
exports.addMountDefinition = addMountDefinition;
|
|
@@ -11,6 +11,12 @@ const internal_1 = require("@nx/js/internal");
|
|
|
11
11
|
exports.CYPRESS_CONFIG_FILE_NAME_PATTERN = 'cypress.config.{js,ts,mjs,cjs}';
|
|
12
12
|
const TS_QUERY_COMMON_JS_EXPORT_SELECTOR = 'BinaryExpression:has(Identifier[name="module"]):has(Identifier[name="exports"])';
|
|
13
13
|
const TS_QUERY_EXPORT_CONFIG_PREFIX = `:matches(ExportAssignment, ${TS_QUERY_COMMON_JS_EXPORT_SELECTOR}) `;
|
|
14
|
+
// Shared so the CT generator (addDefaultCTConfig) and the
|
|
15
|
+
// disable-webpack-ct-just-in-time-compile migration emit the identical note.
|
|
16
|
+
exports.JIT_COMPILE_DISABLE_COMMENT = [
|
|
17
|
+
'// Cypress 14+ defaults justInTimeCompile to true (webpack only), which can',
|
|
18
|
+
'// intermittently run 0 tests in CI. Remove this line to opt back in.',
|
|
19
|
+
];
|
|
14
20
|
async function addDefaultE2EConfig(cyConfigContents, options, baseUrl) {
|
|
15
21
|
if (!cyConfigContents) {
|
|
16
22
|
throw new Error('The passed in cypress config file is empty!');
|
|
@@ -71,8 +77,13 @@ ${updatedConfigContents}`;
|
|
|
71
77
|
* doing so unconditionally produces mixed-syntax files in CJS workspaces
|
|
72
78
|
* (an ESM `import` followed by a CJS `module.exports`), so prefer passing
|
|
73
79
|
* `presetImportPath`.
|
|
80
|
+
*
|
|
81
|
+
* Pass `cypressMajorVersion` to opt webpack setups out of `justInTimeCompile`
|
|
82
|
+
* on Cypress 14+, where it defaults to `true` and can intermittently run 0
|
|
83
|
+
* tests in CI. The opt-out is emitted as an explicit `justInTimeCompile: false`
|
|
84
|
+
* so it is visible and reversible.
|
|
74
85
|
**/
|
|
75
|
-
async function addDefaultCTConfig(cyConfigContents, options = {}, presetImportPath) {
|
|
86
|
+
async function addDefaultCTConfig(cyConfigContents, options = {}, presetImportPath, cypressMajorVersion) {
|
|
76
87
|
if (!cyConfigContents) {
|
|
77
88
|
throw new Error('The passed in cypress config file is empty!');
|
|
78
89
|
}
|
|
@@ -85,6 +96,11 @@ async function addDefaultCTConfig(cyConfigContents, options = {}, presetImportPa
|
|
|
85
96
|
// See addDefaultE2EConfig for the rationale on __filename vs
|
|
86
97
|
// import.meta.url.
|
|
87
98
|
const pathToConfig = isCommonJS ? '__filename' : 'import.meta.url';
|
|
99
|
+
// justInTimeCompile only applies to the webpack dev server and only exists
|
|
100
|
+
// on Cypress 14+, where it defaults to true.
|
|
101
|
+
const disableJustInTimeCompile = options.bundler !== 'vite' &&
|
|
102
|
+
cypressMajorVersion != null &&
|
|
103
|
+
cypressMajorVersion >= 14;
|
|
88
104
|
let configValue = `nxComponentTestingPreset(${pathToConfig})`;
|
|
89
105
|
if (options) {
|
|
90
106
|
if (options.bundler !== 'vite') {
|
|
@@ -95,15 +111,23 @@ async function addDefaultCTConfig(cyConfigContents, options = {}, presetImportPa
|
|
|
95
111
|
configValue = `nxComponentTestingPreset(${pathToConfig}, ${JSON.stringify(options)})`;
|
|
96
112
|
}
|
|
97
113
|
}
|
|
114
|
+
const jitComment = exports.JIT_COMPILE_DISABLE_COMMENT.map((line) => ` ${line}`).join('\n');
|
|
115
|
+
const componentValue = disableJustInTimeCompile
|
|
116
|
+
? `{
|
|
117
|
+
...${configValue},
|
|
118
|
+
${jitComment}
|
|
119
|
+
justInTimeCompile: false,
|
|
120
|
+
}`
|
|
121
|
+
: configValue;
|
|
98
122
|
updatedConfigContents = tsquery.replace(cyConfigContents, `${TS_QUERY_EXPORT_CONFIG_PREFIX} ObjectLiteralExpression:first-child`, (node) => {
|
|
99
123
|
if (node.properties.length > 0) {
|
|
100
124
|
return `{
|
|
101
125
|
${node.properties.map((p) => p.getText()).join(',\n')},
|
|
102
|
-
component: ${
|
|
126
|
+
component: ${componentValue}
|
|
103
127
|
}`;
|
|
104
128
|
}
|
|
105
129
|
return `{
|
|
106
|
-
component: ${
|
|
130
|
+
component: ${componentValue}
|
|
107
131
|
}`;
|
|
108
132
|
});
|
|
109
133
|
}
|
package/migrations.json
CHANGED
|
@@ -52,6 +52,15 @@
|
|
|
52
52
|
"description": "Rename imports of `createNodesV2` from `@nx/cypress/plugin` to the canonical `createNodes` export.",
|
|
53
53
|
"implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes",
|
|
54
54
|
"documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md"
|
|
55
|
+
},
|
|
56
|
+
"disable-webpack-ct-just-in-time-compile": {
|
|
57
|
+
"version": "23.1.0-beta.6",
|
|
58
|
+
"requires": {
|
|
59
|
+
"cypress": ">=14.0.0"
|
|
60
|
+
},
|
|
61
|
+
"description": "Set `justInTimeCompile: false` in webpack component testing Cypress configs on Cypress 14+, where it defaults to true and can intermittently run 0 tests in CI.",
|
|
62
|
+
"implementation": "./dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile",
|
|
63
|
+
"documentation": "./dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.md"
|
|
55
64
|
}
|
|
56
65
|
},
|
|
57
66
|
"packageJsonUpdates": {
|
|
@@ -127,6 +136,18 @@
|
|
|
127
136
|
"alwaysAddToPackageJson": false
|
|
128
137
|
}
|
|
129
138
|
}
|
|
139
|
+
},
|
|
140
|
+
"23.1.0-eslint-plugin-cypress": {
|
|
141
|
+
"version": "23.1.0-beta.6",
|
|
142
|
+
"requires": {
|
|
143
|
+
"eslint-plugin-cypress": ">=2.0.0 <3.5.0"
|
|
144
|
+
},
|
|
145
|
+
"packages": {
|
|
146
|
+
"eslint-plugin-cypress": {
|
|
147
|
+
"version": "^3.5.0",
|
|
148
|
+
"alwaysAddToPackageJson": false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/cypress",
|
|
3
|
-
"version": "23.1.0-beta.
|
|
3
|
+
"version": "23.1.0-beta.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"files": [
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"semver": "^7.6.3",
|
|
90
90
|
"tree-kill": "^1.2.2",
|
|
91
91
|
"tslib": "^2.3.0",
|
|
92
|
-
"@nx/devkit": "23.1.0-beta.
|
|
93
|
-
"@nx/
|
|
94
|
-
"@nx/
|
|
92
|
+
"@nx/devkit": "23.1.0-beta.6",
|
|
93
|
+
"@nx/js": "23.1.0-beta.6",
|
|
94
|
+
"@nx/eslint": "23.1.0-beta.6"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"nx": "23.1.0-beta.
|
|
97
|
+
"nx": "23.1.0-beta.6"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"cypress": ">= 13 < 16"
|