@trackunit/iris-app-e2e 1.8.89 ā 1.8.91
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/CHANGELOG.md +2511 -0
- package/generators.json +12 -0
- package/package.json +19 -14
- package/src/commands/defaultCommands.js +135 -0
- package/src/commands/defaultCommands.js.map +1 -0
- package/src/generators/e2e-configuration/README.md +79 -0
- package/src/generators/e2e-configuration/files/root/cypress/e2e/app.cy.ts__tmpl__ +18 -0
- package/src/generators/e2e-configuration/files/root/cypress/fixtures/auth.json__tmpl__ +4 -0
- package/src/generators/e2e-configuration/files/root/cypress/support/commands.ts__tmpl__ +3 -0
- package/src/generators/e2e-configuration/files/root/cypress/support/e2e.ts__tmpl__ +18 -0
- package/src/generators/e2e-configuration/files/root/cypress/tsconfig.json__tmpl__ +17 -0
- package/src/generators/e2e-configuration/files/root/cypress.config.ts__tmpl__ +25 -0
- package/src/generators/e2e-configuration/generator.js +238 -0
- package/src/generators/e2e-configuration/generator.js.map +1 -0
- package/src/generators/e2e-configuration/schema.json +20 -0
- package/src/index.js +16 -0
- package/src/index.js.map +1 -0
- package/src/plugins/createLogFile.js +42 -0
- package/src/plugins/createLogFile.js.map +1 -0
- package/src/plugins/defaultPlugins.js +142 -0
- package/src/plugins/defaultPlugins.js.map +1 -0
- package/src/plugins/nxPreset.js +41 -0
- package/src/plugins/nxPreset.js.map +1 -0
- package/src/plugins/writeFileWithPrettier.js +35 -0
- package/src/plugins/writeFileWithPrettier.js.map +1 -0
- package/src/setup/defaultE2ESetup.js +45 -0
- package/src/setup/defaultE2ESetup.js.map +1 -0
- package/src/setup/setupHarRecording.js +24 -0
- package/src/setup/setupHarRecording.js.map +1 -0
- package/src/support.js +13 -0
- package/src/support.js.map +1 -0
- package/src/utils/Codeowner.js +136 -0
- package/src/utils/Codeowner.js.map +1 -0
- package/src/utils/fileNameBuilder.js +38 -0
- package/src/utils/fileNameBuilder.js.map +1 -0
- package/src/utils/fileUpdater.js +37 -0
- package/src/utils/fileUpdater.js.map +1 -0
- package/index.cjs.default.js +0 -1
- package/index.cjs.js +0 -422
- package/index.cjs.mjs +0 -2
- package/index.d.ts +0 -1
- package/index.esm.js +0 -398
package/generators.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"name": "iris-app-e2e",
|
|
4
|
+
"version": "1.0.3",
|
|
5
|
+
"generators": {
|
|
6
|
+
"e2e-configuration": {
|
|
7
|
+
"factory": "./src/generators/e2e-configuration/generator#e2eConfigurationGenerator",
|
|
8
|
+
"schema": "./src/generators/e2e-configuration/schema.json",
|
|
9
|
+
"description": "e2e configuration setup for Iris app projects"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/iris-app-e2e",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.91",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"generators": "./generators.json",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=24.x"
|
|
9
9
|
},
|
|
10
|
+
"main": "./src/index.js",
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./src/index.d.ts",
|
|
15
|
+
"require": "./src/index.js",
|
|
16
|
+
"import": "./src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./support": {
|
|
19
|
+
"types": "./src/support.d.ts",
|
|
20
|
+
"require": "./src/support.js",
|
|
21
|
+
"import": "./src/support.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
10
25
|
"dependencies": {
|
|
11
26
|
"@neuralegion/cypress-har-generator": "^5.17.0",
|
|
12
27
|
"@nx/cypress": "22.4.4",
|
|
@@ -14,18 +29,8 @@
|
|
|
14
29
|
"@nx/vite": "22.4.4",
|
|
15
30
|
"cypress-terminal-report": "7.0.3",
|
|
16
31
|
"node-xlsx": "^0.23.0",
|
|
17
|
-
"prettier": "^3.4.2"
|
|
18
|
-
|
|
19
|
-
"exports": {
|
|
20
|
-
"./package.json": "./package.json",
|
|
21
|
-
".": {
|
|
22
|
-
"module": "./index.esm.js",
|
|
23
|
-
"types": "./index.d.ts",
|
|
24
|
-
"import": "./index.cjs.mjs",
|
|
25
|
-
"default": "./index.cjs.js"
|
|
26
|
-
}
|
|
32
|
+
"prettier": "^3.4.2",
|
|
33
|
+
"tslib": "^2.6.2"
|
|
27
34
|
},
|
|
28
|
-
"
|
|
29
|
-
"main": "./index.cjs.js",
|
|
30
|
-
"types": "./index.d.ts"
|
|
35
|
+
"type": "commonjs"
|
|
31
36
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupDefaultCommands = setupDefaultCommands;
|
|
4
|
+
/**
|
|
5
|
+
* Sets up default Cypress commands for E2E testing.
|
|
6
|
+
* Adds custom commands like getByTestId, login, enterIrisApp, etc.
|
|
7
|
+
*/
|
|
8
|
+
function setupDefaultCommands() {
|
|
9
|
+
Cypress.Commands.add("getByTestId", {
|
|
10
|
+
prevSubject: ["optional"],
|
|
11
|
+
},
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
(subject, testId, options = {}) => {
|
|
14
|
+
const selector = `[data-testid="${testId}"]`;
|
|
15
|
+
const timeout = options.timeout ?? 15000;
|
|
16
|
+
if (subject) {
|
|
17
|
+
if (Cypress.dom.isElement(subject) || Cypress.dom.isJquery(subject)) {
|
|
18
|
+
return cy.wrap(subject, { timeout }).find(selector, { timeout });
|
|
19
|
+
}
|
|
20
|
+
else if (Cypress.dom.isWindow(subject)) {
|
|
21
|
+
return cy.get(selector, { timeout });
|
|
22
|
+
}
|
|
23
|
+
else if (Array.isArray(subject)) {
|
|
24
|
+
const element = subject.map(el => cy.wrap(el, { timeout }).find(selector, { timeout }));
|
|
25
|
+
if (element[0]) {
|
|
26
|
+
return element[0];
|
|
27
|
+
}
|
|
28
|
+
return cy.wrap(null);
|
|
29
|
+
}
|
|
30
|
+
return cy.get(selector, { timeout });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return cy.get(selector, { timeout });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Cypress.Commands.add("login", fixture => {
|
|
37
|
+
const envUrl = `${Cypress.config().baseUrl}/env`;
|
|
38
|
+
cy.log(`Getting env from: ${envUrl}`);
|
|
39
|
+
cy.request({
|
|
40
|
+
method: "GET",
|
|
41
|
+
url: envUrl,
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
}).then(envResponse => {
|
|
46
|
+
const env = envResponse.body;
|
|
47
|
+
const domain = env.auth?.url;
|
|
48
|
+
if (!(Boolean(domain))) {
|
|
49
|
+
throw new Error(`No domain found from servers /env found env: ${JSON.stringify(env)}`);
|
|
50
|
+
}
|
|
51
|
+
cy.log(`Using: ${domain}`);
|
|
52
|
+
cy.clearCookies();
|
|
53
|
+
cy.fixture(fixture ?? "auth").then(({ username, password }) => {
|
|
54
|
+
const options = {
|
|
55
|
+
warnBeforePasswordExpired: true,
|
|
56
|
+
multiOptionalFactorEnroll: false,
|
|
57
|
+
};
|
|
58
|
+
cy.request({
|
|
59
|
+
method: "POST",
|
|
60
|
+
url: `${domain}/api/v1/authn`,
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
},
|
|
64
|
+
body: {
|
|
65
|
+
username,
|
|
66
|
+
password,
|
|
67
|
+
options,
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
.then(response => {
|
|
71
|
+
if (response.isOkStatusCode) {
|
|
72
|
+
const sessionToken = response.body.sessionToken;
|
|
73
|
+
return cy.visit(`/auth/manager-classic#session_token=${sessionToken}&fleetHome=true`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw new Error(`Could not get a session token for user: ${username}, ${JSON.stringify(response)}`);
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.then(() => cy.url({ timeout: 20000 }).should("not.include", "manager-classic")) // Wait for redirect away from manager-classic
|
|
80
|
+
.url()
|
|
81
|
+
.should("contain", `${Cypress.config().baseUrl}`);
|
|
82
|
+
cy.get("#host-layout-content", { timeout: 15000 }).should("be.visible"); // Wait for host layout to be visible
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
Cypress.Commands.add("switchToLocalDevMode", () => {
|
|
87
|
+
cy.getByTestId("developerPortalNav").click();
|
|
88
|
+
cy.url({ timeout: 15000, log: true }).should("contain", "/iris-sdk-portal");
|
|
89
|
+
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
cy.getByTestId("localDevModeSwitch-input").then(($ele) => {
|
|
91
|
+
if ($ele && !$ele.is(":checked")) {
|
|
92
|
+
cy.getByTestId("localDevModeSwitch-thumb").click();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
Cypress.Commands.add("enterIrisApp", options => {
|
|
97
|
+
return cy
|
|
98
|
+
.get(`iframe[data-testid="${options?.testId ?? "app-iframe"}"]`, { timeout: 30000 })
|
|
99
|
+
.first()
|
|
100
|
+
.its("0.contentDocument.body", { timeout: 30000, log: true })
|
|
101
|
+
.should("not.be.empty")
|
|
102
|
+
.then($body => {
|
|
103
|
+
return () => cy.wrap($body);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
Cypress.Commands.add("enterStorybookPreview", options => {
|
|
107
|
+
return cy
|
|
108
|
+
.get(`iframe[id="${options?.testId ?? "storybook-preview-iframe"}"]`, { timeout: 30000 })
|
|
109
|
+
.first()
|
|
110
|
+
.its("0.contentDocument.body", { timeout: 30000, log: true })
|
|
111
|
+
.should("not.be.empty")
|
|
112
|
+
.then($body => {
|
|
113
|
+
return () => cy.wrap($body);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
Cypress.Commands.add("getValidateFeatureFlags", () => {
|
|
117
|
+
cy.intercept({ url: "**/ValidateFeatureFlags" }).as("ValidateFeatureFlags");
|
|
118
|
+
cy.intercept({ url: "**/UserPermissions" }).as("UserPermissions");
|
|
119
|
+
cy.intercept({ url: "**/ActiveSubscription" }).as("ActiveSubscription");
|
|
120
|
+
});
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
let ccData = null;
|
|
123
|
+
Cypress.Commands.add("configCat", value => {
|
|
124
|
+
if (!ccData) {
|
|
125
|
+
cy.wait("@ValidateFeatureFlags").then(intercept => {
|
|
126
|
+
ccData = intercept.response?.body?.data?.featureFlags;
|
|
127
|
+
return ccData?.find((ff) => ff.key === value).state;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return ccData?.find((ff) => ff.key === value).state;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=defaultCommands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultCommands.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-e2e/src/commands/defaultCommands.ts"],"names":[],"mappings":";;AAIA,oDAsIC;AA1ID;;;GAGG;AACH,SAAgB,oBAAoB;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAClB,aAAa,EACb;QACE,WAAW,EAAE,CAAC,UAAU,CAAC;KAC1B;IACD,8DAA8D;IAC9D,CAAC,OAAY,EAAE,MAAc,EAAE,UAAgC,EAAE,EAAE,EAAE;QACnE,MAAM,QAAQ,GAAG,iBAAiB,MAAM,IAAI,CAAC;QAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;QAEzC,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpE,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YACnE,CAAC;iBAAM,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzC,OAAO,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YACvC,CAAC;iBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;gBACxF,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBACf,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;gBACpB,CAAC;gBACD,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;YACD,OAAO,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE;QACtC,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,MAAM,CAAC;QACjD,EAAE,CAAC,GAAG,CAAC,qBAAqB,MAAM,EAAE,CAAC,CAAC;QACtC,EAAE,CAAC,OAAO,CAAC;YACT,MAAM,EAAE,KAAK;YACb,GAAG,EAAE,MAAM;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;YACpB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC;YAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;YAC7B,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,gDAAgD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzF,CAAC;YACD,EAAE,CAAC,GAAG,CAAC,UAAU,MAAM,EAAE,CAAC,CAAC;YAC3B,EAAE,CAAC,YAAY,EAAE,CAAC;YAClB,EAAE,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE;gBAC5D,MAAM,OAAO,GAAG;oBACd,yBAAyB,EAAE,IAAI;oBAC/B,yBAAyB,EAAE,KAAK;iBACjC,CAAC;gBAEF,EAAE,CAAC,OAAO,CAAC;oBACT,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,GAAG,MAAM,eAAe;oBAC7B,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;qBACnC;oBACD,IAAI,EAAE;wBACJ,QAAQ;wBACR,QAAQ;wBACR,OAAO;qBACR;iBACF,CAAC;qBACC,IAAI,CAAC,QAAQ,CAAC,EAAE;oBACf,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;wBAC5B,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;wBAEhD,OAAO,EAAE,CAAC,KAAK,CAAC,uCAAuC,YAAY,iBAAiB,CAAC,CAAC;oBACxF,CAAC;yBAAM,CAAC;wBACN,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;oBACtG,CAAC;gBACH,CAAC,CAAC;qBACD,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC,CAAC,8CAA8C;qBAC9H,GAAG,EAAE;qBACL,MAAM,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBACpD,EAAE,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,qCAAqC;YAChH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAChD,EAAE,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,KAAK,EAAE,CAAC;QAC7C,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;QAE5E,6DAA6D;QAC7D,EAAE,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC,CAAC,IAAS,EAAE,EAAE;YAC5D,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjC,EAAE,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;YACrD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE;QAC7C,OAAO,EAAE;aACN,GAAG,CAAC,uBAAuB,OAAO,EAAE,MAAM,IAAI,YAAY,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;aACnF,KAAK,EAAE;aACP,GAAG,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;aAC5D,MAAM,CAAC,cAAc,CAAC;aACtB,IAAI,CAAC,KAAK,CAAC,EAAE;YACZ,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,uBAAuB,EAAE,OAAO,CAAC,EAAE;QACtD,OAAO,EAAE;aACN,GAAG,CAAC,cAAc,OAAO,EAAE,MAAM,IAAI,0BAA0B,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;aACxF,KAAK,EAAE;aACP,GAAG,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;aAC5D,MAAM,CAAC,cAAc,CAAC;aACtB,IAAI,CAAC,KAAK,CAAC,EAAE;YACZ,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACnD,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,yBAAyB,EAAE,CAAC,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC;QAC5E,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,oBAAoB,EAAE,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;QAClE,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,uBAAuB,EAAE,CAAC,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,8DAA8D;IAC9D,IAAI,MAAM,GAAQ,IAAI,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE;QACxC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;gBAChD,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC;gBACtD,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,EAAmB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC;YACvE,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,EAAmB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC;QACvE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Sets up default Cypress commands for E2E testing.\n * Adds custom commands like getByTestId, login, enterIrisApp, etc.\n */\nexport function setupDefaultCommands() {\n Cypress.Commands.add(\n \"getByTestId\",\n {\n prevSubject: [\"optional\"],\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (subject: any, testId: string, options: { timeout?: number } = {}) => {\n const selector = `[data-testid=\"${testId}\"]`;\n const timeout = options.timeout ?? 15000;\n\n if (subject) {\n if (Cypress.dom.isElement(subject) || Cypress.dom.isJquery(subject)) {\n return cy.wrap(subject, { timeout }).find(selector, { timeout });\n } else if (Cypress.dom.isWindow(subject)) {\n return cy.get(selector, { timeout });\n } else if (Array.isArray(subject)) {\n const element = subject.map(el => cy.wrap(el, { timeout }).find(selector, { timeout }));\n if (element[0]) {\n return element[0];\n }\n return cy.wrap(null);\n }\n return cy.get(selector, { timeout });\n } else {\n return cy.get(selector, { timeout });\n }\n }\n );\n\n Cypress.Commands.add(\"login\", fixture => {\n const envUrl = `${Cypress.config().baseUrl}/env`;\n cy.log(`Getting env from: ${envUrl}`);\n cy.request({\n method: \"GET\",\n url: envUrl,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n }).then(envResponse => {\n const env = envResponse.body;\n const domain = env.auth?.url;\n if (!(Boolean(domain))) {\n throw new Error(`No domain found from servers /env found env: ${JSON.stringify(env)}`);\n }\n cy.log(`Using: ${domain}`);\n cy.clearCookies();\n cy.fixture(fixture ?? \"auth\").then(({ username, password }) => {\n const options = {\n warnBeforePasswordExpired: true,\n multiOptionalFactorEnroll: false,\n };\n\n cy.request({\n method: \"POST\",\n url: `${domain}/api/v1/authn`,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: {\n username,\n password,\n options,\n },\n })\n .then(response => {\n if (response.isOkStatusCode) {\n const sessionToken = response.body.sessionToken;\n\n return cy.visit(`/auth/manager-classic#session_token=${sessionToken}&fleetHome=true`);\n } else {\n throw new Error(`Could not get a session token for user: ${username}, ${JSON.stringify(response)}`);\n }\n })\n .then(() => cy.url({ timeout: 20000 }).should(\"not.include\", \"manager-classic\")) // Wait for redirect away from manager-classic\n .url()\n .should(\"contain\", `${Cypress.config().baseUrl}`);\n cy.get(\"#host-layout-content\", { timeout: 15000 }).should(\"be.visible\"); // Wait for host layout to be visible\n });\n });\n });\n\n Cypress.Commands.add(\"switchToLocalDevMode\", () => {\n cy.getByTestId(\"developerPortalNav\").click();\n cy.url({ timeout: 15000, log: true }).should(\"contain\", \"/iris-sdk-portal\");\n\n //eslint-disable-next-line @typescript-eslint/no-explicit-any\n cy.getByTestId(\"localDevModeSwitch-input\").then(($ele: any) => {\n if ($ele && !$ele.is(\":checked\")) {\n cy.getByTestId(\"localDevModeSwitch-thumb\").click();\n }\n });\n });\n\n Cypress.Commands.add(\"enterIrisApp\", options => {\n return cy\n .get(`iframe[data-testid=\"${options?.testId ?? \"app-iframe\"}\"]`, { timeout: 30000 })\n .first()\n .its(\"0.contentDocument.body\", { timeout: 30000, log: true })\n .should(\"not.be.empty\")\n .then($body => {\n return () => cy.wrap($body);\n });\n });\n\n Cypress.Commands.add(\"enterStorybookPreview\", options => {\n return cy\n .get(`iframe[id=\"${options?.testId ?? \"storybook-preview-iframe\"}\"]`, { timeout: 30000 })\n .first()\n .its(\"0.contentDocument.body\", { timeout: 30000, log: true })\n .should(\"not.be.empty\")\n .then($body => {\n return () => cy.wrap($body);\n });\n });\n\n Cypress.Commands.add(\"getValidateFeatureFlags\", () => {\n cy.intercept({ url: \"**/ValidateFeatureFlags\" }).as(\"ValidateFeatureFlags\");\n cy.intercept({ url: \"**/UserPermissions\" }).as(\"UserPermissions\");\n cy.intercept({ url: \"**/ActiveSubscription\" }).as(\"ActiveSubscription\");\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let ccData: any = null;\n Cypress.Commands.add(\"configCat\", value => {\n if (!ccData) {\n cy.wait(\"@ValidateFeatureFlags\").then(intercept => {\n ccData = intercept.response?.body?.data?.featureFlags;\n return ccData?.find((ff: { key: string }) => ff.key === value).state;\n });\n } else {\n return ccData?.find((ff: { key: string }) => ff.key === value).state;\n }\n });\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
# E2E Configuration Generator
|
|
3
|
+
|
|
4
|
+
This generator sets up Cypress E2E test configuration within an existing project
|
|
5
|
+
using the Iris App E2E library, rather than creating a separate E2E app.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
You can run the generator with or without explicitly specifying a project name.
|
|
10
|
+
|
|
11
|
+
### 1. With the project name
|
|
12
|
+
|
|
13
|
+
Use this option to directly target a specific project for the E2E setup.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
nx generate @trackunit/iris-app-e2e:e2e-configuration --project <PROJECT_NAME>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Without the project name
|
|
20
|
+
|
|
21
|
+
This option presents a list of available projects to select from.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
nx generate @trackunit/iris-app-e2e:e2e-configuration
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What it generates
|
|
28
|
+
|
|
29
|
+
- **cypress.config.ts** - Main Cypress configuration using Iris App E2E library
|
|
30
|
+
- **cypress/e2e/app.cy.ts** - Sample test file with basic structure
|
|
31
|
+
- **cypress/support/commands.ts** - Custom commands setup using public API
|
|
32
|
+
- **cypress/support/e2e.ts** - Support file configuration
|
|
33
|
+
- **cypress/fixtures/auth.json** - Authentication fixture template
|
|
34
|
+
- **Project targets** for e2e and e2e-ui execution with multiple environment configurations
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
- The target application needs to be deployed and accessible for tests to run successfully
|
|
39
|
+
- Authentication credentials must be configured in the `cypress/fixtures/auth.json` file
|
|
40
|
+
|
|
41
|
+
## Test User Considerations
|
|
42
|
+
|
|
43
|
+
When setting up E2E tests, consider the following for test user management:
|
|
44
|
+
|
|
45
|
+
### Authentication Setup
|
|
46
|
+
|
|
47
|
+
- Configure test user credentials in `cypress/fixtures/auth.json`
|
|
48
|
+
- Use dedicated test accounts separate from production users
|
|
49
|
+
- Ensure test users have appropriate permissions for the features being tested
|
|
50
|
+
|
|
51
|
+
### User Context Planning
|
|
52
|
+
|
|
53
|
+
- **Basic Access**: Use standard user accounts for general functionality testing
|
|
54
|
+
- **Administrative Access**: Use admin-level accounts when testing administrative features
|
|
55
|
+
- **Role-based Testing**: Consider different user roles if your application has role-based access control
|
|
56
|
+
- **Environment Considerations**: Test users and permissions may vary across different environments (dev, staging, production)
|
|
57
|
+
|
|
58
|
+
### Security Best Practices
|
|
59
|
+
|
|
60
|
+
- Regularly rotate test user passwords
|
|
61
|
+
- Ensure test users have minimal necessary permissions
|
|
62
|
+
|
|
63
|
+
## Running Tests
|
|
64
|
+
|
|
65
|
+
After generation, you can run your E2E tests using:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Run tests in headless mode
|
|
69
|
+
nx run <project-name>:e2e
|
|
70
|
+
|
|
71
|
+
# Run tests with UI for development
|
|
72
|
+
nx run <project-name>:e2e-ui
|
|
73
|
+
|
|
74
|
+
# Run against different environments
|
|
75
|
+
nx run <project-name>:e2e --configuration=local
|
|
76
|
+
nx run <project-name>:e2e --configuration=dev
|
|
77
|
+
nx run <project-name>:e2e --configuration=stage
|
|
78
|
+
nx run <project-name>:e2e --configuration=prod
|
|
79
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
describe("Sample E2E Test", () => {
|
|
2
|
+
beforeEach(() => {
|
|
3
|
+
// NEVER do anything in before or after as they are never timed out and retries will never happen
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
it("Can log in and navigate to app", () => {
|
|
7
|
+
cy.login("auth");
|
|
8
|
+
|
|
9
|
+
// Example: Test your application here
|
|
10
|
+
// cy.getByTestId("your-app-element").should("be.visible");
|
|
11
|
+
|
|
12
|
+
// Example: Test Iris app integration
|
|
13
|
+
// cy.enterIrisApp().then(irisApp => {
|
|
14
|
+
// irisApp().find(".your-selector").should("contain.text", "Expected Text");
|
|
15
|
+
// irisApp().getByTestId("your-test-id").should("exist");
|
|
16
|
+
// });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// ***********************************************************
|
|
2
|
+
// This example support/e2e.ts is processed and
|
|
3
|
+
// loaded automatically before your test files.
|
|
4
|
+
//
|
|
5
|
+
// This is a great place to put global configuration and
|
|
6
|
+
// behavior that modifies Cypress.
|
|
7
|
+
//
|
|
8
|
+
// You can change the location of this file or turn off
|
|
9
|
+
// automatically serving support files with the
|
|
10
|
+
// 'supportFile' configuration option.
|
|
11
|
+
//
|
|
12
|
+
// You can read more here:
|
|
13
|
+
// https://on.cypress.io/configuration
|
|
14
|
+
// ***********************************************************
|
|
15
|
+
|
|
16
|
+
import { setupE2E } from "@trackunit/iris-app-e2e/support";
|
|
17
|
+
import "./commands";
|
|
18
|
+
setupE2E();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"types": ["cypress", "node", "@testing-library/cypress"],
|
|
5
|
+
"sourceMap": false
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"**/*.ts",
|
|
9
|
+
"**/*.js",
|
|
10
|
+
"../cypress.config.ts",
|
|
11
|
+
"./**/*.cy.ts",
|
|
12
|
+
"./**/*.cy.tsx",
|
|
13
|
+
"./**/*.cy.js",
|
|
14
|
+
"./**/*.cy.jsx",
|
|
15
|
+
"./**/*.d.ts"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { install as installHarGenerator } from "@neuralegion/cypress-har-generator";
|
|
2
|
+
import { createNxPreset, CypressPluginConfig, defaultCypressConfig, setupPlugins } from "@trackunit/iris-app-e2e";
|
|
3
|
+
import { defineConfig } from "cypress";
|
|
4
|
+
import { format, resolveConfig } from "prettier";
|
|
5
|
+
|
|
6
|
+
const nxPreset = createNxPreset(__filename);
|
|
7
|
+
|
|
8
|
+
const cypressJsonConfig: Partial<CypressPluginConfig> = {
|
|
9
|
+
...defaultCypressConfig(__dirname),
|
|
10
|
+
fixturesFolder: "./cypress/fixtures",
|
|
11
|
+
supportFile: "cypress/support/e2e.ts",
|
|
12
|
+
specPattern: "cypress/e2e/**/*.cy.ts",
|
|
13
|
+
};
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
chromeWebSecurity: false,
|
|
16
|
+
e2e: {
|
|
17
|
+
...nxPreset,
|
|
18
|
+
...cypressJsonConfig,
|
|
19
|
+
async setupNodeEvents(on, config: CypressPluginConfig) {
|
|
20
|
+
await nxPreset.setupNodeEvents?.(on, config);
|
|
21
|
+
const formatter = { format, resolveConfig };
|
|
22
|
+
return setupPlugins(on, config, formatter, installHarGenerator);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.e2eConfigurationGenerator = e2eConfigurationGenerator;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const configuration_1 = tslib_1.__importDefault(require("@nx/cypress/src/generators/configuration/configuration"));
|
|
6
|
+
const devkit_1 = require("@nx/devkit");
|
|
7
|
+
const fileUpdater_1 = require("../../utils/fileUpdater");
|
|
8
|
+
/**
|
|
9
|
+
* Generates E2E test configuration for an existing project using Iris App E2E library.
|
|
10
|
+
*
|
|
11
|
+
* This generator sets up Cypress end-to-end testing infrastructure within an existing project,
|
|
12
|
+
* including configuration files, test targets, and template files that use the public
|
|
13
|
+
* iris-app-e2e library for cross-platform compatibility.
|
|
14
|
+
*
|
|
15
|
+
* @param tree - The NX virtual file system tree
|
|
16
|
+
* @param options - Configuration options for the generator
|
|
17
|
+
* @param options.project - The name of the project to add E2E configuration to
|
|
18
|
+
* @returns {Function} A function that logs completion message with the path to the generated test file
|
|
19
|
+
* @example
|
|
20
|
+
* ```bash
|
|
21
|
+
* nx generate @trackunit/iris-app-e2e:e2e-configuration --project my-app
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Generated structure:
|
|
25
|
+
* - cypress.config.ts - Main Cypress configuration using Iris App E2E
|
|
26
|
+
* - cypress/e2e/app.cy.ts - Sample test file
|
|
27
|
+
* - cypress/support/commands.ts - Custom commands setup
|
|
28
|
+
* - cypress/support/e2e.ts - Support file configuration
|
|
29
|
+
* - cypress/fixtures/auth.json - Authentication fixture template
|
|
30
|
+
* - Project targets for e2e and e2e-ui execution with multiple environment configurations
|
|
31
|
+
*/
|
|
32
|
+
async function e2eConfigurationGenerator(tree, options) {
|
|
33
|
+
const libName = options.project.replace(new RegExp("/", "g"), "-");
|
|
34
|
+
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, libName);
|
|
35
|
+
const projectRoot = projectConfig.root; // Get the actual root of the library
|
|
36
|
+
const optionsWithDefaults = {
|
|
37
|
+
linter: "none",
|
|
38
|
+
project: options.project,
|
|
39
|
+
bundler: "vite",
|
|
40
|
+
directory: "cypress",
|
|
41
|
+
baseUrl: "https://dev.manager.trackunit.com",
|
|
42
|
+
};
|
|
43
|
+
// reading project.json content
|
|
44
|
+
const existingProjectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
|
|
45
|
+
// generate the cypress configuration files
|
|
46
|
+
await (0, configuration_1.default)(tree, optionsWithDefaults);
|
|
47
|
+
//adding cypress target to project.json
|
|
48
|
+
(0, devkit_1.updateProjectConfiguration)(tree, options.project, {
|
|
49
|
+
...existingProjectConfig,
|
|
50
|
+
targets: {
|
|
51
|
+
...existingProjectConfig.targets,
|
|
52
|
+
"e2e-ui": {
|
|
53
|
+
executor: "@nx/cypress:cypress",
|
|
54
|
+
options: {
|
|
55
|
+
cypressConfig: `${projectRoot}/cypress.config.ts`,
|
|
56
|
+
baseUrl: "https://dev.manager.trackunit.com",
|
|
57
|
+
headed: true,
|
|
58
|
+
watch: true,
|
|
59
|
+
testingType: "e2e",
|
|
60
|
+
},
|
|
61
|
+
configurations: {
|
|
62
|
+
local: {
|
|
63
|
+
baseUrl: "http://localhost:3000",
|
|
64
|
+
},
|
|
65
|
+
dev: {
|
|
66
|
+
baseUrl: "https://dev.manager.trackunit.com",
|
|
67
|
+
},
|
|
68
|
+
stage: {
|
|
69
|
+
baseUrl: "https://stage.manager.trackunit.com",
|
|
70
|
+
},
|
|
71
|
+
prod: {
|
|
72
|
+
baseUrl: "https://new.manager.trackunit.com",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
e2e: {
|
|
77
|
+
executor: "@nx/cypress:cypress",
|
|
78
|
+
options: {
|
|
79
|
+
cypressConfig: `${projectRoot}/cypress.config.ts`,
|
|
80
|
+
browser: "chrome",
|
|
81
|
+
testingType: "e2e",
|
|
82
|
+
},
|
|
83
|
+
configurations: {
|
|
84
|
+
local: {
|
|
85
|
+
baseUrl: "http://localhost:3000",
|
|
86
|
+
},
|
|
87
|
+
dev: {
|
|
88
|
+
baseUrl: "https://dev.manager.trackunit.com",
|
|
89
|
+
},
|
|
90
|
+
stage: {
|
|
91
|
+
baseUrl: "https://stage.manager.trackunit.com",
|
|
92
|
+
},
|
|
93
|
+
prod: {
|
|
94
|
+
baseUrl: "https://new.manager.trackunit.com",
|
|
95
|
+
},
|
|
96
|
+
"feature-branch": {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
// overwrite the generated files with the custom files
|
|
102
|
+
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, "files/root"), projectRoot, {
|
|
103
|
+
...options,
|
|
104
|
+
directory: "",
|
|
105
|
+
tmpl: "",
|
|
106
|
+
});
|
|
107
|
+
// Check if we're in the manager-side monorepo (has eslint-e2e.config.cjs at root)
|
|
108
|
+
const isInternalWorkspace = tree.exists("eslint-e2e.config.cjs");
|
|
109
|
+
const workspaceEslintConfigCjsPath = "eslint.config.cjs";
|
|
110
|
+
const workspaceEslintConfigMjsPath = "eslint.config.mjs";
|
|
111
|
+
const eslintConfigCjsPath = (0, devkit_1.joinPathFragments)(projectConfig.root, "eslint.config.cjs");
|
|
112
|
+
const eslintConfigMjsPath = (0, devkit_1.joinPathFragments)(projectConfig.root, "eslint.config.mjs");
|
|
113
|
+
if (isInternalWorkspace) {
|
|
114
|
+
// Manager-side monorepo: update existing eslint.config.cjs
|
|
115
|
+
(0, fileUpdater_1.updateFileInTree)(tree, eslintConfigCjsPath, _ => {
|
|
116
|
+
const hasPublishNpmTag = projectConfig.tags?.includes("publish:npm") ?? false;
|
|
117
|
+
const publicRulesImport = hasPublishNpmTag
|
|
118
|
+
? `\nconst publicConfig = require("${(0, devkit_1.offsetFromRoot)(projectRoot)}eslint-public.config.cjs");`
|
|
119
|
+
: "";
|
|
120
|
+
const publicRulesSpread = hasPublishNpmTag ? ", ...publicConfig" : "";
|
|
121
|
+
return `const clientConfig = require("${(0, devkit_1.offsetFromRoot)(projectRoot)}eslint-client.config.cjs");
|
|
122
|
+
const e2eConfig = require("${(0, devkit_1.offsetFromRoot)(projectRoot)}eslint-e2e.config.cjs");
|
|
123
|
+
const { defineConfig } = require("eslint/config");${publicRulesImport}
|
|
124
|
+
module.exports = defineConfig([...clientConfig, ...e2eConfig${publicRulesSpread}]);
|
|
125
|
+
`;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// External workspace: extend existing config or create minimal config with Cypress support
|
|
130
|
+
// Check for both .mjs and .cjs ESLint config files
|
|
131
|
+
const workspaceMjsContent = tree.exists(workspaceEslintConfigMjsPath)
|
|
132
|
+
? tree.read(workspaceEslintConfigMjsPath, "utf-8") || ""
|
|
133
|
+
: "";
|
|
134
|
+
const workspaceCjsContent = tree.exists(workspaceEslintConfigCjsPath)
|
|
135
|
+
? tree.read(workspaceEslintConfigCjsPath, "utf-8") || ""
|
|
136
|
+
: "";
|
|
137
|
+
const existingMjsContent = tree.exists(eslintConfigMjsPath) ? tree.read(eslintConfigMjsPath, "utf-8") || "" : "";
|
|
138
|
+
const existingCjsContent = tree.exists(eslintConfigCjsPath) ? tree.read(eslintConfigCjsPath, "utf-8") || "" : "";
|
|
139
|
+
const cypressConfigBlock = `{
|
|
140
|
+
files: ["cypress/**/*.ts", "cypress/**/*.js"],
|
|
141
|
+
languageOptions: {
|
|
142
|
+
globals: {
|
|
143
|
+
cy: "readonly",
|
|
144
|
+
Cypress: "readonly",
|
|
145
|
+
describe: "readonly",
|
|
146
|
+
it: "readonly",
|
|
147
|
+
beforeEach: "readonly",
|
|
148
|
+
afterEach: "readonly",
|
|
149
|
+
before: "readonly",
|
|
150
|
+
after: "readonly",
|
|
151
|
+
expect: "readonly",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
rules: {
|
|
155
|
+
"@typescript-eslint/no-namespace": "off",
|
|
156
|
+
},
|
|
157
|
+
}`;
|
|
158
|
+
if (existingMjsContent && !existingMjsContent.includes("cypress")) {
|
|
159
|
+
// Update existing .mjs config to add Cypress support
|
|
160
|
+
// Find the export default array and add cypress config to it
|
|
161
|
+
const updatedContent = existingMjsContent.replace(/export\s+default\s+(\[[\s\S]*?)(];?\s*$)/m, (_, arrayContent, closing) => {
|
|
162
|
+
const trimmedContent = arrayContent.trimEnd();
|
|
163
|
+
const needsComma = trimmedContent.endsWith("}") || trimmedContent.endsWith(")");
|
|
164
|
+
return `export default ${trimmedContent}${needsComma ? "," : ""}\n ${cypressConfigBlock}\n${closing}`;
|
|
165
|
+
});
|
|
166
|
+
tree.write(eslintConfigMjsPath, updatedContent);
|
|
167
|
+
}
|
|
168
|
+
else if (existingCjsContent && !existingCjsContent.includes("cypress")) {
|
|
169
|
+
// Update existing .cjs config to add Cypress support
|
|
170
|
+
const eslintContent = `const baseConfig = require("./eslint.config.base.cjs");
|
|
171
|
+
|
|
172
|
+
module.exports = [
|
|
173
|
+
...(Array.isArray(baseConfig) ? baseConfig : [baseConfig]),
|
|
174
|
+
${cypressConfigBlock},
|
|
175
|
+
];
|
|
176
|
+
`;
|
|
177
|
+
// Backup the original config
|
|
178
|
+
tree.write((0, devkit_1.joinPathFragments)(projectConfig.root, "eslint.config.base.cjs"), existingCjsContent);
|
|
179
|
+
tree.write(eslintConfigCjsPath, eslintContent);
|
|
180
|
+
}
|
|
181
|
+
else if (!existingMjsContent && !existingCjsContent) {
|
|
182
|
+
// No project-level config - compose with the workspace base config when available.
|
|
183
|
+
if (workspaceCjsContent) {
|
|
184
|
+
const eslintContent = `const { defineConfig } = require("eslint/config");
|
|
185
|
+
const baseConfig = require("${(0, devkit_1.offsetFromRoot)(projectRoot)}eslint.config.cjs");
|
|
186
|
+
|
|
187
|
+
module.exports = defineConfig([
|
|
188
|
+
...(Array.isArray(baseConfig) ? baseConfig : [baseConfig]),
|
|
189
|
+
${cypressConfigBlock},
|
|
190
|
+
]);
|
|
191
|
+
`;
|
|
192
|
+
tree.write(eslintConfigCjsPath, eslintContent);
|
|
193
|
+
}
|
|
194
|
+
else if (workspaceMjsContent) {
|
|
195
|
+
const eslintContent = `import baseConfig from "${(0, devkit_1.offsetFromRoot)(projectRoot)}eslint.config.mjs";
|
|
196
|
+
|
|
197
|
+
export default [
|
|
198
|
+
...(Array.isArray(baseConfig) ? baseConfig : [baseConfig]),
|
|
199
|
+
${cypressConfigBlock},
|
|
200
|
+
];
|
|
201
|
+
`;
|
|
202
|
+
tree.write(eslintConfigMjsPath, eslintContent);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Final fallback for workspaces without any ESLint setup: bootstrap the same Nx React flat
|
|
206
|
+
// config shape our app generators start from, then layer Cypress overrides on top.
|
|
207
|
+
const eslintContent = `const nx = require("@nx/eslint-plugin");
|
|
208
|
+
const { defineConfig } = require("eslint/config");
|
|
209
|
+
|
|
210
|
+
module.exports = defineConfig([
|
|
211
|
+
...nx.configs["flat/react"],
|
|
212
|
+
{
|
|
213
|
+
files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
|
214
|
+
languageOptions: {
|
|
215
|
+
parserOptions: { project: ["./tsconfig.*?.json"] },
|
|
216
|
+
},
|
|
217
|
+
rules: {},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
ignores: ["node_modules/**", "dist/**"],
|
|
221
|
+
},
|
|
222
|
+
${cypressConfigBlock},
|
|
223
|
+
]);
|
|
224
|
+
`;
|
|
225
|
+
tree.write(eslintConfigCjsPath, eslintContent);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// If eslintConfig already includes cypress, don't modify it
|
|
229
|
+
}
|
|
230
|
+
(0, fileUpdater_1.deleteFileInTree)(tree, (0, devkit_1.joinPathFragments)(projectConfig.root, "cypress/support/app.po.ts"));
|
|
231
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
232
|
+
return () => {
|
|
233
|
+
// eslint-disable-next-line no-console
|
|
234
|
+
console.log("\n\n\nā
š Please open this file to get started\n", (0, devkit_1.joinPathFragments)(projectConfig.root, "cypress/e2e/app.cy.ts"));
|
|
235
|
+
return tree;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=generator.js.map
|