@toolstackhq/create-qa-patterns 1.0.14 → 1.0.15
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/README.md +5 -0
- package/index.js +252 -1076
- package/lib/args.js +139 -0
- package/lib/constants.js +115 -0
- package/lib/interactive.js +131 -0
- package/lib/local-env.js +65 -0
- package/lib/metadata.js +329 -0
- package/lib/output.js +326 -0
- package/lib/prereqs.js +72 -0
- package/lib/scaffold.js +120 -0
- package/lib/templates.js +40 -0
- package/package.json +5 -3
- package/templates/cypress-template/.env.example +2 -2
- package/templates/cypress-template/.github/workflows/cypress-tests.yml +2 -2
- package/templates/cypress-template/README.md +10 -6
- package/templates/cypress-template/allurerc.mjs +1 -1
- package/templates/cypress-template/config/environments.ts +13 -11
- package/templates/cypress-template/config/runtime-config.ts +17 -12
- package/templates/cypress-template/config/secret-manager.ts +1 -1
- package/templates/cypress-template/config/test-env.ts +3 -3
- package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +12 -10
- package/templates/cypress-template/cypress/support/app-config.ts +5 -5
- package/templates/cypress-template/cypress/support/commands.ts +7 -7
- package/templates/cypress-template/cypress/support/data/data-factory.ts +6 -4
- package/templates/cypress-template/cypress/support/data/id-generator.ts +1 -1
- package/templates/cypress-template/cypress/support/data/seeded-faker.ts +2 -2
- package/templates/cypress-template/cypress/support/e2e.ts +2 -2
- package/templates/cypress-template/cypress/support/pages/login-page.ts +4 -4
- package/templates/cypress-template/cypress/support/pages/people-page.ts +10 -10
- package/templates/cypress-template/cypress.config.ts +9 -9
- package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +1 -1
- package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +44 -41
- package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +31 -3
- package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +5 -5
- package/templates/cypress-template/eslint.config.mjs +53 -45
- package/templates/cypress-template/package.json +6 -5
- package/templates/cypress-template/scripts/ensure-local-env.mjs +36 -0
- package/templates/cypress-template/scripts/generate-allure-report.mjs +16 -10
- package/templates/cypress-template/scripts/run-cypress.mjs +33 -24
- package/templates/cypress-template/scripts/run-tests.sh +1 -0
- package/templates/cypress-template/tsconfig.json +7 -1
- package/templates/playwright-template/.env.example +6 -6
- package/templates/playwright-template/.github/workflows/playwright-tests.yml +14 -5
- package/templates/playwright-template/README.md +6 -5
- package/templates/playwright-template/allurerc.mjs +1 -1
- package/templates/playwright-template/components/flash-message.ts +2 -2
- package/templates/playwright-template/config/environments.ts +16 -14
- package/templates/playwright-template/config/runtime-config.ts +17 -12
- package/templates/playwright-template/config/secret-manager.ts +1 -1
- package/templates/playwright-template/config/test-env.ts +3 -3
- package/templates/playwright-template/data/factories/data-factory.ts +6 -4
- package/templates/playwright-template/data/generators/id-generator.ts +1 -1
- package/templates/playwright-template/data/generators/seeded-faker.ts +2 -2
- package/templates/playwright-template/demo-apps/api-demo-server/src/server.js +9 -9
- package/templates/playwright-template/demo-apps/api-demo-server/src/store.js +1 -1
- package/templates/playwright-template/demo-apps/ui-demo-app/public/styles.css +1 -1
- package/templates/playwright-template/demo-apps/ui-demo-app/src/server.js +44 -41
- package/templates/playwright-template/demo-apps/ui-demo-app/src/store.js +31 -3
- package/templates/playwright-template/demo-apps/ui-demo-app/src/templates.js +5 -5
- package/templates/playwright-template/eslint.config.mjs +40 -40
- package/templates/playwright-template/fixtures/test-fixtures.ts +27 -12
- package/templates/playwright-template/lint/architecture-plugin.cjs +36 -31
- package/templates/playwright-template/package.json +7 -6
- package/templates/playwright-template/pages/base-page.ts +4 -4
- package/templates/playwright-template/pages/login-page.ts +9 -9
- package/templates/playwright-template/pages/people-page.ts +21 -17
- package/templates/playwright-template/playwright.config.ts +22 -19
- package/templates/playwright-template/reporters/structured-reporter.ts +11 -8
- package/templates/playwright-template/scripts/ensure-local-env.mjs +37 -0
- package/templates/playwright-template/scripts/generate-allure-report.mjs +16 -10
- package/templates/playwright-template/scripts/run-tests.sh +1 -0
- package/templates/playwright-template/tests/api-people.spec.ts +8 -6
- package/templates/playwright-template/tests/ui-journey.spec.ts +13 -8
- package/templates/playwright-template/tsconfig.json +3 -11
- package/templates/playwright-template/utils/logger.ts +12 -8
- package/templates/playwright-template/utils/test-step.ts +5 -5
- package/templates/wdio-template/.env.example +14 -0
- package/templates/wdio-template/.github/workflows/wdio-tests.yml +46 -0
- package/templates/wdio-template/README.md +241 -0
- package/templates/wdio-template/allurerc.mjs +10 -0
- package/templates/wdio-template/components/README.md +5 -0
- package/templates/wdio-template/components/flash-message.ts +16 -0
- package/templates/wdio-template/config/README.md +5 -0
- package/templates/wdio-template/config/environments.ts +40 -0
- package/templates/wdio-template/config/runtime-config.ts +53 -0
- package/templates/wdio-template/config/secret-manager.ts +29 -0
- package/templates/wdio-template/config/test-env.ts +9 -0
- package/templates/wdio-template/data/README.md +9 -0
- package/templates/wdio-template/data/factories/README.md +6 -0
- package/templates/wdio-template/data/factories/data-factory.ts +36 -0
- package/templates/wdio-template/data/generators/README.md +5 -0
- package/templates/wdio-template/data/generators/id-generator.ts +18 -0
- package/templates/wdio-template/data/generators/seeded-faker.ts +14 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/public/styles.css +120 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/server.js +152 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/store.js +71 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/templates.js +121 -0
- package/templates/wdio-template/eslint.config.mjs +86 -0
- package/templates/wdio-template/lint/architecture-plugin.cjs +123 -0
- package/templates/wdio-template/package-lock.json +11058 -0
- package/templates/wdio-template/package.json +44 -0
- package/templates/wdio-template/pages/README.md +6 -0
- package/templates/wdio-template/pages/base-page.ts +15 -0
- package/templates/wdio-template/pages/login-page.ts +27 -0
- package/templates/wdio-template/pages/people-page.ts +54 -0
- package/templates/wdio-template/reporters/README.md +5 -0
- package/templates/wdio-template/reporters/structured-reporter.ts +78 -0
- package/templates/wdio-template/scripts/README.md +5 -0
- package/templates/wdio-template/scripts/ensure-local-env.mjs +36 -0
- package/templates/wdio-template/scripts/generate-allure-report.mjs +72 -0
- package/templates/wdio-template/scripts/run-tests.sh +7 -0
- package/templates/wdio-template/scripts/run-wdio.mjs +114 -0
- package/templates/wdio-template/tests/README.md +7 -0
- package/templates/wdio-template/tests/ui-journey.spec.ts +52 -0
- package/templates/wdio-template/tsconfig.json +22 -0
- package/templates/wdio-template/utils/README.md +5 -0
- package/templates/wdio-template/utils/logger.ts +60 -0
- package/templates/wdio-template/utils/test-step.ts +20 -0
- package/templates/wdio-template/wdio.conf.ts +58 -0
- package/tests/args.test.js +58 -0
- package/tests/local-env.test.js +70 -0
- package/tests/metadata.test.js +147 -0
- package/tests/templates.test.js +44 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
// Central Cypress configuration for specs, retries, artifacts, and runtime env values.
|
|
2
|
-
import * as os from
|
|
2
|
+
import * as os from 'node:os';
|
|
3
3
|
|
|
4
|
-
import { allureCypress } from
|
|
5
|
-
import { defineConfig } from
|
|
4
|
+
import { allureCypress } from 'allure-cypress/reporter';
|
|
5
|
+
import { defineConfig } from 'cypress';
|
|
6
6
|
|
|
7
|
-
import { loadRuntimeConfig } from
|
|
7
|
+
import { loadRuntimeConfig } from './config/runtime-config';
|
|
8
8
|
|
|
9
9
|
const runtimeConfig = loadRuntimeConfig();
|
|
10
10
|
|
|
11
11
|
export default defineConfig({
|
|
12
12
|
e2e: {
|
|
13
13
|
baseUrl: runtimeConfig.uiBaseUrl,
|
|
14
|
-
specPattern:
|
|
15
|
-
supportFile:
|
|
14
|
+
specPattern: 'cypress/e2e/**/*.cy.ts',
|
|
15
|
+
supportFile: 'cypress/support/e2e.ts',
|
|
16
16
|
setupNodeEvents(on, config) {
|
|
17
17
|
// Keep Cypress terminal output as the default path most users expect.
|
|
18
18
|
// Remove the Allure line below if you prefer to stay with Cypress output only.
|
|
19
19
|
allureCypress(on, config, {
|
|
20
|
-
resultsDir:
|
|
20
|
+
resultsDir: 'allure-results',
|
|
21
21
|
environmentInfo: {
|
|
22
22
|
os_platform: os.platform(),
|
|
23
23
|
os_release: os.release(),
|
|
@@ -43,6 +43,6 @@ export default defineConfig({
|
|
|
43
43
|
},
|
|
44
44
|
screenshotOnRunFailure: true,
|
|
45
45
|
video: true,
|
|
46
|
-
videosFolder:
|
|
47
|
-
screenshotsFolder:
|
|
46
|
+
videosFolder: 'reports/videos',
|
|
47
|
+
screenshotsFolder: 'reports/screenshots'
|
|
48
48
|
});
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const http = require(
|
|
3
|
-
const path = require(
|
|
4
|
-
const querystring = require(
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const http = require('node:http');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const querystring = require('node:querystring');
|
|
5
5
|
|
|
6
|
-
const { createPerson, getPeople, state } = require(
|
|
7
|
-
const { layout, loginPage, peoplePage } = require(
|
|
6
|
+
const { createPerson, getPeople, state } = require('./store');
|
|
7
|
+
const { layout, loginPage, peoplePage } = require('./templates');
|
|
8
8
|
|
|
9
|
-
const host = process.env.HOST ||
|
|
10
|
-
const port = Number(process.env.PORT ||
|
|
9
|
+
const host = process.env.HOST || '0.0.0.0';
|
|
10
|
+
const port = Number(process.env.PORT || '3000');
|
|
11
11
|
|
|
12
12
|
function readBody(request) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
let body =
|
|
15
|
-
request.on(
|
|
14
|
+
let body = '';
|
|
15
|
+
request.on('data', (chunk) => {
|
|
16
16
|
body += chunk.toString();
|
|
17
17
|
});
|
|
18
|
-
request.on(
|
|
19
|
-
request.on(
|
|
18
|
+
request.on('end', () => resolve(querystring.parse(body)));
|
|
19
|
+
request.on('error', reject);
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -26,8 +26,8 @@ function parseCookies(request) {
|
|
|
26
26
|
return {};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
return header.split(
|
|
30
|
-
const [key, value] = entry.trim().split(
|
|
29
|
+
return header.split(';').reduce((cookies, entry) => {
|
|
30
|
+
const [key, value] = entry.trim().split('=');
|
|
31
31
|
cookies[key] = value;
|
|
32
32
|
return cookies;
|
|
33
33
|
}, {});
|
|
@@ -39,22 +39,22 @@ function redirect(response, location) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function sendHtml(response, html) {
|
|
42
|
-
response.writeHead(200, {
|
|
42
|
+
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
43
43
|
response.end(html);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function sendJson(response, payload) {
|
|
47
|
-
response.writeHead(200, {
|
|
47
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
48
48
|
response.end(JSON.stringify(payload));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function isAuthenticated(request) {
|
|
52
|
-
return parseCookies(request).session ===
|
|
52
|
+
return parseCookies(request).session === 'authenticated';
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function protectedRoute(request, response) {
|
|
56
56
|
if (!isAuthenticated(request)) {
|
|
57
|
-
redirect(response,
|
|
57
|
+
redirect(response, '/login');
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -62,53 +62,53 @@ function protectedRoute(request, response) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function pageMessage(url) {
|
|
65
|
-
return new URL(url,
|
|
65
|
+
return new URL(url, 'http://127.0.0.1').searchParams.get('message') || '';
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const server = http.createServer(async (request, response) => {
|
|
69
|
-
const url = new URL(request.url,
|
|
69
|
+
const url = new URL(request.url, 'http://127.0.0.1');
|
|
70
70
|
|
|
71
|
-
if (request.method ===
|
|
72
|
-
const cssPath = path.join(__dirname,
|
|
73
|
-
response.writeHead(200, {
|
|
74
|
-
response.end(fs.readFileSync(cssPath,
|
|
71
|
+
if (request.method === 'GET' && url.pathname === '/styles.css') {
|
|
72
|
+
const cssPath = path.join(__dirname, '..', 'public', 'styles.css');
|
|
73
|
+
response.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8' });
|
|
74
|
+
response.end(fs.readFileSync(cssPath, 'utf8'));
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (request.method ===
|
|
79
|
-
sendJson(response, { status:
|
|
78
|
+
if (request.method === 'GET' && url.pathname === '/health') {
|
|
79
|
+
sendJson(response, { status: 'ok', people: state.people.length });
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
if (request.method ===
|
|
84
|
-
redirect(response,
|
|
83
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
84
|
+
redirect(response, '/login');
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
if (request.method ===
|
|
88
|
+
if (request.method === 'GET' && url.pathname === '/login') {
|
|
89
89
|
sendHtml(response, loginPage());
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if (request.method ===
|
|
93
|
+
if (request.method === 'POST' && url.pathname === '/login') {
|
|
94
94
|
const body = await readBody(request);
|
|
95
95
|
if (
|
|
96
96
|
body.username === state.credentials.username &&
|
|
97
97
|
body.password === state.credentials.password
|
|
98
98
|
) {
|
|
99
99
|
response.writeHead(302, {
|
|
100
|
-
Location:
|
|
101
|
-
|
|
100
|
+
Location: '/people',
|
|
101
|
+
'Set-Cookie': 'session=authenticated; HttpOnly; Path=/; SameSite=Lax'
|
|
102
102
|
});
|
|
103
103
|
response.end();
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
sendHtml(response, loginPage(
|
|
107
|
+
sendHtml(response, loginPage('Invalid credentials'));
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
if (request.method ===
|
|
111
|
+
if (request.method === 'GET' && url.pathname === '/people') {
|
|
112
112
|
if (!protectedRoute(request, response)) {
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
@@ -116,8 +116,8 @@ const server = http.createServer(async (request, response) => {
|
|
|
116
116
|
sendHtml(
|
|
117
117
|
response,
|
|
118
118
|
layout({
|
|
119
|
-
title:
|
|
120
|
-
body: peoplePage(getPeople(url.searchParams.get(
|
|
119
|
+
title: 'People',
|
|
120
|
+
body: peoplePage(getPeople(url.searchParams.get('search') || '')),
|
|
121
121
|
flashMessage: pageMessage(request.url),
|
|
122
122
|
username: state.credentials.username
|
|
123
123
|
})
|
|
@@ -125,7 +125,7 @@ const server = http.createServer(async (request, response) => {
|
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
if (request.method ===
|
|
128
|
+
if (request.method === 'POST' && url.pathname === '/people') {
|
|
129
129
|
if (!protectedRoute(request, response)) {
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
@@ -133,15 +133,18 @@ const server = http.createServer(async (request, response) => {
|
|
|
133
133
|
try {
|
|
134
134
|
const body = await readBody(request);
|
|
135
135
|
createPerson(body);
|
|
136
|
-
redirect(response,
|
|
136
|
+
redirect(response, '/people?message=Person%20added');
|
|
137
137
|
} catch (error) {
|
|
138
|
-
redirect(
|
|
138
|
+
redirect(
|
|
139
|
+
response,
|
|
140
|
+
`/people?message=${encodeURIComponent(error.message)}`
|
|
141
|
+
);
|
|
139
142
|
}
|
|
140
143
|
return;
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
response.writeHead(404, {
|
|
144
|
-
response.end(
|
|
146
|
+
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
147
|
+
response.end('Not found');
|
|
145
148
|
});
|
|
146
149
|
|
|
147
150
|
server.listen(port, host, () => {
|
|
@@ -1,7 +1,35 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const dotenv = require('dotenv');
|
|
5
|
+
|
|
6
|
+
function loadClosestEnv(startDirectory) {
|
|
7
|
+
let currentDirectory = startDirectory;
|
|
8
|
+
|
|
9
|
+
while (true) {
|
|
10
|
+
const candidate = path.join(currentDirectory, '.env');
|
|
11
|
+
if (fs.existsSync(candidate)) {
|
|
12
|
+
dotenv.config({ path: candidate });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
17
|
+
if (parentDirectory === currentDirectory) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
currentDirectory = parentDirectory;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
loadClosestEnv(process.cwd());
|
|
26
|
+
loadClosestEnv(__dirname);
|
|
27
|
+
|
|
1
28
|
const state = {
|
|
2
29
|
credentials: {
|
|
3
|
-
username:
|
|
4
|
-
|
|
30
|
+
username:
|
|
31
|
+
process.env.UI_DEMO_USERNAME || process.env.DEV_APP_USERNAME || '',
|
|
32
|
+
password: process.env.UI_DEMO_PASSWORD || process.env.DEV_APP_PASSWORD || ''
|
|
5
33
|
},
|
|
6
34
|
people: []
|
|
7
35
|
};
|
|
@@ -23,7 +51,7 @@ function createPerson(person) {
|
|
|
23
51
|
});
|
|
24
52
|
}
|
|
25
53
|
|
|
26
|
-
function getPeople(search =
|
|
54
|
+
function getPeople(search = '') {
|
|
27
55
|
const query = String(search).trim().toLowerCase();
|
|
28
56
|
if (!query) {
|
|
29
57
|
return state.people;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function layout({ title, body, flashMessage =
|
|
1
|
+
function layout({ title, body, flashMessage = '', username = 'Local user' }) {
|
|
2
2
|
return `<!DOCTYPE html>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
@@ -15,7 +15,7 @@ function layout({ title, body, flashMessage = "", username = "tester" }) {
|
|
|
15
15
|
<a href="/people">People</a>
|
|
16
16
|
</nav>
|
|
17
17
|
</header>
|
|
18
|
-
${flashMessage ? `<div class="flash-message" data-testid="flash-message" role="status">${flashMessage}</div>` :
|
|
18
|
+
${flashMessage ? `<div class="flash-message" data-testid="flash-message" role="status">${flashMessage}</div>` : ''}
|
|
19
19
|
<p data-testid="welcome-message">Signed in as ${username}</p>
|
|
20
20
|
${body}
|
|
21
21
|
</div>
|
|
@@ -23,7 +23,7 @@ function layout({ title, body, flashMessage = "", username = "tester" }) {
|
|
|
23
23
|
</html>`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function loginPage(errorMessage =
|
|
26
|
+
function loginPage(errorMessage = '') {
|
|
27
27
|
return `<!DOCTYPE html>
|
|
28
28
|
<html lang="en">
|
|
29
29
|
<head>
|
|
@@ -37,7 +37,7 @@ function loginPage(errorMessage = "") {
|
|
|
37
37
|
<section class="card login-card">
|
|
38
38
|
<h1>Login</h1>
|
|
39
39
|
<p>Keep the example intentionally small: sign in, add one person, assert the list.</p>
|
|
40
|
-
${errorMessage ? `<div class="flash-message" role="status">${errorMessage}</div>` :
|
|
40
|
+
${errorMessage ? `<div class="flash-message" role="status">${errorMessage}</div>` : ''}
|
|
41
41
|
<form action="/login" method="post">
|
|
42
42
|
<label for="username">
|
|
43
43
|
Username
|
|
@@ -64,7 +64,7 @@ function peoplePage(people) {
|
|
|
64
64
|
<td>${person.email}</td>
|
|
65
65
|
</tr>`;
|
|
66
66
|
})
|
|
67
|
-
.join(
|
|
67
|
+
.join('');
|
|
68
68
|
|
|
69
69
|
return `
|
|
70
70
|
<section class="panel-grid">
|
|
@@ -1,37 +1,43 @@
|
|
|
1
|
-
import js from
|
|
2
|
-
import tsParser from
|
|
3
|
-
import tsPlugin from
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import tsParser from '@typescript-eslint/parser';
|
|
3
|
+
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
|
4
4
|
|
|
5
5
|
const tsconfigRootDir = import.meta.dirname;
|
|
6
6
|
|
|
7
7
|
const cypressGlobals = {
|
|
8
|
-
Cypress:
|
|
9
|
-
cy:
|
|
10
|
-
describe:
|
|
11
|
-
it:
|
|
12
|
-
before:
|
|
13
|
-
beforeEach:
|
|
14
|
-
after:
|
|
15
|
-
afterEach:
|
|
8
|
+
Cypress: 'readonly',
|
|
9
|
+
cy: 'readonly',
|
|
10
|
+
describe: 'readonly',
|
|
11
|
+
it: 'readonly',
|
|
12
|
+
before: 'readonly',
|
|
13
|
+
beforeEach: 'readonly',
|
|
14
|
+
after: 'readonly',
|
|
15
|
+
afterEach: 'readonly'
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const nodeGlobals = {
|
|
19
|
-
process:
|
|
20
|
-
console:
|
|
19
|
+
process: 'readonly',
|
|
20
|
+
console: 'readonly'
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export default [
|
|
24
24
|
{
|
|
25
|
-
ignores: [
|
|
25
|
+
ignores: [
|
|
26
|
+
'demo-apps/**',
|
|
27
|
+
'node_modules/**',
|
|
28
|
+
'reports/**',
|
|
29
|
+
'allure-results/**',
|
|
30
|
+
'allure-report/**'
|
|
31
|
+
]
|
|
26
32
|
},
|
|
27
33
|
js.configs.recommended,
|
|
28
34
|
{
|
|
29
|
-
files: [
|
|
30
|
-
ignores: [
|
|
35
|
+
files: ['**/*.ts'],
|
|
36
|
+
ignores: ['**/*.d.ts'],
|
|
31
37
|
languageOptions: {
|
|
32
38
|
parser: tsParser,
|
|
33
39
|
parserOptions: {
|
|
34
|
-
project:
|
|
40
|
+
project: './tsconfig.json',
|
|
35
41
|
tsconfigRootDir
|
|
36
42
|
},
|
|
37
43
|
globals: {
|
|
@@ -40,22 +46,22 @@ export default [
|
|
|
40
46
|
}
|
|
41
47
|
},
|
|
42
48
|
plugins: {
|
|
43
|
-
|
|
49
|
+
'@typescript-eslint': tsPlugin
|
|
44
50
|
},
|
|
45
51
|
rules: {
|
|
46
52
|
...tsPlugin.configs.recommended.rules,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
'no-undef': 'off',
|
|
54
|
+
'@typescript-eslint/no-unused-vars': [
|
|
55
|
+
'error',
|
|
50
56
|
{
|
|
51
|
-
argsIgnorePattern:
|
|
52
|
-
varsIgnorePattern:
|
|
57
|
+
argsIgnorePattern: '^_',
|
|
58
|
+
varsIgnorePattern: '^_'
|
|
53
59
|
}
|
|
54
60
|
]
|
|
55
61
|
}
|
|
56
62
|
},
|
|
57
63
|
{
|
|
58
|
-
files: [
|
|
64
|
+
files: ['**/*.d.ts'],
|
|
59
65
|
languageOptions: {
|
|
60
66
|
parser: tsParser,
|
|
61
67
|
parserOptions: {
|
|
@@ -67,53 +73,55 @@ export default [
|
|
|
67
73
|
}
|
|
68
74
|
},
|
|
69
75
|
rules: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
'no-undef': 'off',
|
|
77
|
+
'no-unused-vars': 'off',
|
|
78
|
+
'@typescript-eslint/no-unused-vars': 'off'
|
|
73
79
|
}
|
|
74
80
|
},
|
|
75
81
|
{
|
|
76
|
-
files: [
|
|
82
|
+
files: ['scripts/**/*.mjs'],
|
|
77
83
|
languageOptions: {
|
|
78
84
|
globals: {
|
|
79
85
|
...nodeGlobals,
|
|
80
|
-
fetch:
|
|
81
|
-
setTimeout:
|
|
86
|
+
fetch: 'readonly',
|
|
87
|
+
setTimeout: 'readonly'
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
},
|
|
85
91
|
{
|
|
86
|
-
files: [
|
|
92
|
+
files: ['cypress/e2e/**/*.ts'],
|
|
87
93
|
rules: {
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
'no-restricted-properties': [
|
|
95
|
+
'error',
|
|
90
96
|
{
|
|
91
|
-
object:
|
|
92
|
-
property:
|
|
93
|
-
message:
|
|
97
|
+
object: 'cy',
|
|
98
|
+
property: 'get',
|
|
99
|
+
message:
|
|
100
|
+
'Keep selectors in support/page modules or custom commands, not in spec files.'
|
|
94
101
|
},
|
|
95
102
|
{
|
|
96
|
-
object:
|
|
97
|
-
property:
|
|
98
|
-
message:
|
|
103
|
+
object: 'cy',
|
|
104
|
+
property: 'contains',
|
|
105
|
+
message:
|
|
106
|
+
'Keep selectors in support/page modules or custom commands, not in spec files.'
|
|
99
107
|
}
|
|
100
108
|
]
|
|
101
109
|
}
|
|
102
110
|
},
|
|
103
111
|
{
|
|
104
|
-
files: [
|
|
112
|
+
files: ['cypress/support/commands.ts'],
|
|
105
113
|
rules: {
|
|
106
|
-
|
|
114
|
+
'@typescript-eslint/no-namespace': 'off'
|
|
107
115
|
}
|
|
108
116
|
},
|
|
109
117
|
{
|
|
110
|
-
files: [
|
|
118
|
+
files: ['cypress/support/pages/**/*.ts'],
|
|
111
119
|
rules: {
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
'no-restricted-syntax': [
|
|
121
|
+
'error',
|
|
114
122
|
{
|
|
115
123
|
selector: "CallExpression[callee.name='expect']",
|
|
116
|
-
message:
|
|
124
|
+
message: 'Keep assertions in Cypress spec files.'
|
|
117
125
|
}
|
|
118
126
|
]
|
|
119
127
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Production-ready Cypress automation framework template with a deterministic UI starter app.",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"cy:
|
|
11
|
-
"
|
|
7
|
+
"ensure:env": "node ./scripts/ensure-local-env.mjs",
|
|
8
|
+
"test": "node ./scripts/ensure-local-env.mjs && node ./scripts/run-cypress.mjs run",
|
|
9
|
+
"open": "node ./scripts/ensure-local-env.mjs && node ./scripts/run-cypress.mjs open",
|
|
10
|
+
"cy:run": "node ./scripts/ensure-local-env.mjs && cypress run",
|
|
11
|
+
"cy:open": "node ./scripts/ensure-local-env.mjs && cypress open",
|
|
12
|
+
"demo:ui": "node ./scripts/ensure-local-env.mjs && node ./demo-apps/ui-demo-app/src/server.js",
|
|
12
13
|
"lint": "eslint .",
|
|
13
14
|
"typecheck": "tsc --noEmit",
|
|
14
15
|
"report:allure": "node ./scripts/generate-allure-report.mjs",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const currentProcess = globalThis.process;
|
|
8
|
+
const envPath = path.resolve(currentProcess.cwd(), '.env');
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(envPath)) {
|
|
11
|
+
currentProcess.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectSlug = path
|
|
15
|
+
.basename(currentProcess.cwd())
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/^-+|-+$/g, '')
|
|
19
|
+
.slice(0, 12);
|
|
20
|
+
const username = `${projectSlug || 'local'}-${crypto.randomBytes(3).toString('hex')}`;
|
|
21
|
+
const password = `${crypto.randomBytes(9).toString('base64url')}A1!`;
|
|
22
|
+
|
|
23
|
+
const envContents = [
|
|
24
|
+
'TEST_ENV=dev',
|
|
25
|
+
'TEST_RUN_ID=local',
|
|
26
|
+
'DEV_UI_BASE_URL=http://127.0.0.1:3000',
|
|
27
|
+
`DEV_APP_USERNAME=${username}`,
|
|
28
|
+
`DEV_APP_PASSWORD=${password}`,
|
|
29
|
+
`UI_DEMO_USERNAME=${username}`,
|
|
30
|
+
`UI_DEMO_PASSWORD=${password}`
|
|
31
|
+
].join('\n');
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(envPath, `${envContents}\n`, 'utf8');
|
|
34
|
+
currentProcess.stdout.write(
|
|
35
|
+
`Generated local .env with demo credentials for ${username}\n`
|
|
36
|
+
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Builds a local Allure report from raw test results after a test run completes.
|
|
2
|
-
import { AllureReport, readConfig } from
|
|
3
|
-
import { readdir, rm, stat } from
|
|
4
|
-
import { join, resolve } from
|
|
5
|
-
import process from
|
|
2
|
+
import { AllureReport, readConfig } from '@allurereport/core';
|
|
3
|
+
import { readdir, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
6
|
|
|
7
7
|
const cwd = process.cwd();
|
|
8
|
-
const resultsDir = resolve(cwd,
|
|
9
|
-
const outputDir = resolve(cwd,
|
|
8
|
+
const resultsDir = resolve(cwd, 'allure-results');
|
|
9
|
+
const outputDir = resolve(cwd, 'reports/allure');
|
|
10
10
|
|
|
11
11
|
const collectResultFiles = async () => {
|
|
12
12
|
const entries = (await readdir(resultsDir)).sort();
|
|
@@ -28,20 +28,24 @@ const generateReport = async () => {
|
|
|
28
28
|
try {
|
|
29
29
|
await stat(resultsDir);
|
|
30
30
|
} catch {
|
|
31
|
-
process.stdout.write(
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
'Skipping Allure report generation because allure-results does not exist.\n'
|
|
33
|
+
);
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const files = await collectResultFiles();
|
|
36
38
|
|
|
37
39
|
if (files.length === 0) {
|
|
38
|
-
process.stdout.write(
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
'Skipping Allure report generation because no result files were found.\n'
|
|
42
|
+
);
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
await rm(outputDir, { force: true, recursive: true });
|
|
43
47
|
|
|
44
|
-
const config = await readConfig(cwd,
|
|
48
|
+
const config = await readConfig(cwd, 'allurerc.mjs', { output: outputDir });
|
|
45
49
|
const report = new AllureReport(config);
|
|
46
50
|
|
|
47
51
|
await report.start();
|
|
@@ -52,7 +56,9 @@ const generateReport = async () => {
|
|
|
52
56
|
|
|
53
57
|
await report.done();
|
|
54
58
|
|
|
55
|
-
process.stdout.write(
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
'Allure report generated at reports/allure/index.html\n'
|
|
61
|
+
);
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
generateReport().catch((error) => {
|