@toolstackhq/create-qa-patterns 1.0.5 → 1.0.7
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 +15 -2
- package/index.js +160 -54
- package/package.json +1 -1
- package/templates/cypress-template/.env.example +5 -0
- package/templates/cypress-template/.github/workflows/cypress-tests.yml +47 -0
- package/templates/cypress-template/README.md +195 -0
- package/templates/cypress-template/config/environments.ts +37 -0
- package/templates/cypress-template/config/runtime-config.ts +46 -0
- package/templates/cypress-template/config/secret-manager.ts +19 -0
- package/templates/cypress-template/config/test-env.ts +13 -0
- package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +20 -0
- package/templates/cypress-template/cypress/support/app-config.ts +23 -0
- package/templates/cypress-template/cypress/support/commands.d.ts +15 -0
- package/templates/cypress-template/cypress/support/commands.ts +17 -0
- package/templates/cypress-template/cypress/support/data/data-factory.ts +33 -0
- package/templates/cypress-template/cypress/support/data/id-generator.ts +13 -0
- package/templates/cypress-template/cypress/support/data/seeded-faker.ts +11 -0
- package/templates/cypress-template/cypress/support/e2e.ts +1 -0
- package/templates/cypress-template/cypress/support/pages/login-page.ts +23 -0
- package/templates/cypress-template/cypress/support/pages/people-page.ts +59 -0
- package/templates/cypress-template/cypress.config.ts +32 -0
- package/templates/cypress-template/demo-apps/ui-demo-app/package.json +10 -0
- package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +120 -0
- package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +149 -0
- package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +43 -0
- package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +121 -0
- package/templates/cypress-template/eslint.config.mjs +93 -0
- package/templates/cypress-template/package-lock.json +3758 -0
- package/templates/cypress-template/package.json +31 -0
- package/templates/cypress-template/scripts/run-cypress.mjs +105 -0
- package/templates/cypress-template/scripts/run-tests.sh +6 -0
- package/templates/cypress-template/tsconfig.json +15 -0
package/README.md
CHANGED
|
@@ -20,15 +20,24 @@ Generate into a new directory:
|
|
|
20
20
|
create-qa-patterns my-project
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
The generated project is initialized with `git init` automatically and includes a default `.gitignore` for common local artifacts.
|
|
24
|
+
|
|
23
25
|
Generate the Playwright template explicitly:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
create-qa-patterns playwright-template my-project
|
|
27
29
|
```
|
|
28
30
|
|
|
31
|
+
Generate the Cypress template explicitly:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
create-qa-patterns cypress-template my-project
|
|
35
|
+
```
|
|
36
|
+
|
|
29
37
|
## Supported templates
|
|
30
38
|
|
|
31
39
|
- `playwright-template`
|
|
40
|
+
- `cypress-template`
|
|
32
41
|
|
|
33
42
|
## Interactive flow
|
|
34
43
|
|
|
@@ -39,16 +48,20 @@ When run in a terminal, the CLI shows:
|
|
|
39
48
|
- scaffold progress while files are generated
|
|
40
49
|
- optional post-generate actions for:
|
|
41
50
|
- `npm install`
|
|
42
|
-
- `npx playwright install`
|
|
43
51
|
- `npm test`
|
|
44
52
|
|
|
53
|
+
For Playwright projects, the interactive flow also offers:
|
|
54
|
+
|
|
55
|
+
- `npx playwright install`
|
|
56
|
+
|
|
45
57
|
## Prerequisite checks
|
|
46
58
|
|
|
47
59
|
The CLI checks:
|
|
48
60
|
|
|
49
61
|
- required Node.js version
|
|
50
62
|
- `npm` availability for install and test actions
|
|
51
|
-
- `npx` availability for
|
|
63
|
+
- `npx` availability for template setup that depends on it
|
|
52
64
|
- `docker` availability and warns if it is missing
|
|
65
|
+
- `git` availability so the scaffold can start as a repository immediately
|
|
53
66
|
|
|
54
67
|
If `npx playwright install` fails because the host is missing browser dependencies, the CLI keeps the generated project and prints the recovery steps instead of treating scaffold generation as failed.
|
package/index.js
CHANGED
|
@@ -18,7 +18,24 @@ const DEFAULT_GITIGNORE = `node_modules/
|
|
|
18
18
|
.env.*
|
|
19
19
|
!.env.example
|
|
20
20
|
|
|
21
|
+
.DS_Store
|
|
22
|
+
*.log
|
|
23
|
+
*.tgz
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
.nyc_output/
|
|
27
|
+
coverage/
|
|
28
|
+
dist/
|
|
29
|
+
build/
|
|
30
|
+
tmp/
|
|
31
|
+
temp/
|
|
32
|
+
downloads/
|
|
33
|
+
cypress.env.json
|
|
21
34
|
reports/
|
|
35
|
+
cypress/screenshots/
|
|
36
|
+
cypress/videos/
|
|
37
|
+
reports/screenshots/
|
|
38
|
+
reports/videos/
|
|
22
39
|
allure-results/
|
|
23
40
|
allure-report/
|
|
24
41
|
test-results/
|
|
@@ -30,7 +47,29 @@ const TEMPLATES = [
|
|
|
30
47
|
id: DEFAULT_TEMPLATE,
|
|
31
48
|
aliases: ["playwright", "pw"],
|
|
32
49
|
label: "Playwright Template",
|
|
33
|
-
description: "TypeScript starter with page objects, fixtures, multi-environment config, reporting, linting, CI and Docker."
|
|
50
|
+
description: "TypeScript starter with page objects, fixtures, multi-environment config, reporting, linting, CI and Docker.",
|
|
51
|
+
defaultPackageName: "playwright-template",
|
|
52
|
+
demoAppsManagedByTemplate: true,
|
|
53
|
+
setup: {
|
|
54
|
+
availability: "npx",
|
|
55
|
+
prompt: "Run npx playwright install now?",
|
|
56
|
+
summaryLabel: "Playwright browser install",
|
|
57
|
+
nextStep: "npx playwright install",
|
|
58
|
+
run(targetDirectory) {
|
|
59
|
+
return runCommand("npx", ["playwright", "install"], targetDirectory);
|
|
60
|
+
},
|
|
61
|
+
recovery(targetDirectory) {
|
|
62
|
+
printPlaywrightInstallRecovery(targetDirectory);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "cypress-template",
|
|
68
|
+
aliases: ["cypress", "cy"],
|
|
69
|
+
label: "Cypress Template",
|
|
70
|
+
description: "TypeScript starter with Cypress e2e specs, custom commands, page modules, env-based config, CI, and a bundled demo app.",
|
|
71
|
+
defaultPackageName: "cypress-template",
|
|
72
|
+
demoAppsManagedByTemplate: true
|
|
34
73
|
}
|
|
35
74
|
];
|
|
36
75
|
|
|
@@ -71,6 +110,8 @@ const colors = {
|
|
|
71
110
|
};
|
|
72
111
|
|
|
73
112
|
function printHelp() {
|
|
113
|
+
const supportedTemplates = TEMPLATES.map((template) => ` ${template.id}${template.aliases.length > 0 ? ` (${template.aliases.join(", ")})` : ""}`).join("\n");
|
|
114
|
+
|
|
74
115
|
process.stdout.write(`${colors.bold("create-qa-patterns")}
|
|
75
116
|
|
|
76
117
|
Usage:
|
|
@@ -82,9 +123,7 @@ Interactive mode:
|
|
|
82
123
|
When run without an explicit template, the CLI shows an interactive template picker.
|
|
83
124
|
|
|
84
125
|
Supported templates:
|
|
85
|
-
|
|
86
|
-
playwright
|
|
87
|
-
pw
|
|
126
|
+
${supportedTemplates}
|
|
88
127
|
`);
|
|
89
128
|
}
|
|
90
129
|
|
|
@@ -145,7 +184,8 @@ function collectPrerequisites() {
|
|
|
145
184
|
return {
|
|
146
185
|
npm: commandExists("npm"),
|
|
147
186
|
npx: commandExists("npx"),
|
|
148
|
-
docker: commandExists("docker")
|
|
187
|
+
docker: commandExists("docker"),
|
|
188
|
+
git: commandExists("git")
|
|
149
189
|
};
|
|
150
190
|
}
|
|
151
191
|
|
|
@@ -155,14 +195,18 @@ function printPrerequisiteWarnings(prerequisites) {
|
|
|
155
195
|
}
|
|
156
196
|
|
|
157
197
|
if (!prerequisites.npx) {
|
|
158
|
-
process.stdout.write(`${colors.yellow("Warning:")} npx was not found.
|
|
198
|
+
process.stdout.write(`${colors.yellow("Warning:")} npx was not found. Template setup steps that depend on npx will be unavailable.\n`);
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
if (!prerequisites.docker) {
|
|
162
202
|
process.stdout.write(`${colors.yellow("Warning:")} docker was not found. Docker-based template flows will not run until Docker is installed.\n`);
|
|
163
203
|
}
|
|
164
204
|
|
|
165
|
-
if (!prerequisites.
|
|
205
|
+
if (!prerequisites.git) {
|
|
206
|
+
process.stdout.write(`${colors.yellow("Warning:")} git was not found. The generated project cannot be initialized as a repository automatically.\n`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!prerequisites.npm || !prerequisites.npx || !prerequisites.docker || !prerequisites.git) {
|
|
166
210
|
process.stdout.write("\n");
|
|
167
211
|
}
|
|
168
212
|
}
|
|
@@ -174,14 +218,15 @@ function createLineInterface() {
|
|
|
174
218
|
});
|
|
175
219
|
}
|
|
176
220
|
|
|
177
|
-
function createSummary(
|
|
221
|
+
function createSummary(template, targetDirectory, generatedInCurrentDirectory) {
|
|
178
222
|
return {
|
|
179
|
-
|
|
223
|
+
template,
|
|
180
224
|
targetDirectory,
|
|
181
225
|
generatedInCurrentDirectory,
|
|
182
|
-
demoAppsManagedByTemplate,
|
|
226
|
+
demoAppsManagedByTemplate: Boolean(template.demoAppsManagedByTemplate),
|
|
227
|
+
gitInit: "not-run",
|
|
183
228
|
npmInstall: "not-run",
|
|
184
|
-
|
|
229
|
+
extraSetup: template.setup ? "not-run" : null,
|
|
185
230
|
testRun: "not-run"
|
|
186
231
|
};
|
|
187
232
|
}
|
|
@@ -333,7 +378,9 @@ function resolveNonInteractiveArgs(args) {
|
|
|
333
378
|
const templateName = resolveTemplate(args[0]);
|
|
334
379
|
|
|
335
380
|
if (!templateName) {
|
|
336
|
-
throw new Error(
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Unsupported template "${args[0]}". Supported templates: ${TEMPLATES.map((template) => template.id).join(", ")}.`
|
|
383
|
+
);
|
|
337
384
|
}
|
|
338
385
|
|
|
339
386
|
return {
|
|
@@ -385,14 +432,14 @@ function ensureScaffoldTarget(targetDirectory) {
|
|
|
385
432
|
}
|
|
386
433
|
}
|
|
387
434
|
|
|
388
|
-
function toPackageName(targetDirectory) {
|
|
435
|
+
function toPackageName(targetDirectory, template) {
|
|
389
436
|
const baseName = path.basename(targetDirectory).toLowerCase();
|
|
390
437
|
const normalized = baseName
|
|
391
438
|
.replace(/[^a-z0-9-_]+/g, "-")
|
|
392
439
|
.replace(/^-+|-+$/g, "")
|
|
393
440
|
.replace(/-{2,}/g, "-");
|
|
394
441
|
|
|
395
|
-
return normalized || "
|
|
442
|
+
return normalized || template.defaultPackageName || "qa-patterns-template";
|
|
396
443
|
}
|
|
397
444
|
|
|
398
445
|
function updateJsonFile(filePath, update) {
|
|
@@ -401,8 +448,8 @@ function updateJsonFile(filePath, update) {
|
|
|
401
448
|
fs.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
402
449
|
}
|
|
403
450
|
|
|
404
|
-
function customizeProject(targetDirectory) {
|
|
405
|
-
const packageName = toPackageName(targetDirectory);
|
|
451
|
+
function customizeProject(targetDirectory, template) {
|
|
452
|
+
const packageName = toPackageName(targetDirectory, template);
|
|
406
453
|
const packageJsonPath = path.join(targetDirectory, "package.json");
|
|
407
454
|
const packageLockPath = path.join(targetDirectory, "package-lock.json");
|
|
408
455
|
const gitignorePath = path.join(targetDirectory, ".gitignore");
|
|
@@ -435,6 +482,21 @@ function customizeProject(targetDirectory) {
|
|
|
435
482
|
}
|
|
436
483
|
}
|
|
437
484
|
|
|
485
|
+
function initializeGitRepository(targetDirectory) {
|
|
486
|
+
if (fs.existsSync(path.join(targetDirectory, ".git"))) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const result = spawnSync(getCommandName("git"), ["init"], {
|
|
491
|
+
cwd: targetDirectory,
|
|
492
|
+
encoding: "utf8"
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
if (result.status !== 0) {
|
|
496
|
+
throw new Error(result.stderr || "git init failed.");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
438
500
|
function renderProgress(completed, total, label) {
|
|
439
501
|
const width = 24;
|
|
440
502
|
const filled = Math.round((completed / total) * width);
|
|
@@ -444,7 +506,8 @@ function renderProgress(completed, total, label) {
|
|
|
444
506
|
process.stdout.write(`\r[${bar}] ${percentage}% ${label}`);
|
|
445
507
|
}
|
|
446
508
|
|
|
447
|
-
async function scaffoldProject(
|
|
509
|
+
async function scaffoldProject(template, targetDirectory, prerequisites) {
|
|
510
|
+
const templateName = template.id;
|
|
448
511
|
const templateDirectory = path.resolve(__dirname, "templates", templateName);
|
|
449
512
|
|
|
450
513
|
if (!fs.existsSync(templateDirectory)) {
|
|
@@ -469,10 +532,14 @@ async function scaffoldProject(templateName, targetDirectory) {
|
|
|
469
532
|
renderProgress(2, steps.length, steps[1]);
|
|
470
533
|
await sleep(80);
|
|
471
534
|
|
|
472
|
-
customizeProject(targetDirectory);
|
|
535
|
+
customizeProject(targetDirectory, template);
|
|
473
536
|
renderProgress(3, steps.length, steps[2]);
|
|
474
537
|
await sleep(80);
|
|
475
538
|
|
|
539
|
+
if (prerequisites.git) {
|
|
540
|
+
initializeGitRepository(targetDirectory);
|
|
541
|
+
}
|
|
542
|
+
|
|
476
543
|
renderProgress(4, steps.length, steps[3]);
|
|
477
544
|
await sleep(60);
|
|
478
545
|
process.stdout.write("\n");
|
|
@@ -526,11 +593,9 @@ function runCommand(command, args, cwd) {
|
|
|
526
593
|
});
|
|
527
594
|
}
|
|
528
595
|
|
|
529
|
-
function printSuccess(
|
|
530
|
-
const template = getTemplate(templateName);
|
|
531
|
-
|
|
596
|
+
function printSuccess(template, targetDirectory, generatedInCurrentDirectory) {
|
|
532
597
|
process.stdout.write(`\n${colors.green(colors.bold("Success"))}
|
|
533
|
-
Generated ${template ? template.label :
|
|
598
|
+
Generated ${template ? template.label : template.id} in ${targetDirectory}
|
|
534
599
|
\n`);
|
|
535
600
|
|
|
536
601
|
if (!generatedInCurrentDirectory) {
|
|
@@ -538,16 +603,40 @@ Generated ${template ? template.label : templateName} in ${targetDirectory}
|
|
|
538
603
|
}
|
|
539
604
|
}
|
|
540
605
|
|
|
541
|
-
function printNextSteps(
|
|
542
|
-
|
|
606
|
+
function printNextSteps(summary) {
|
|
607
|
+
const steps = [];
|
|
543
608
|
|
|
544
|
-
if (!generatedInCurrentDirectory) {
|
|
545
|
-
|
|
609
|
+
if (!summary.generatedInCurrentDirectory) {
|
|
610
|
+
steps.push(`cd ${path.relative(process.cwd(), summary.targetDirectory) || "."}`);
|
|
546
611
|
}
|
|
547
612
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
613
|
+
if (summary.npmInstall !== "completed") {
|
|
614
|
+
steps.push("npm install");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (summary.template.setup && summary.extraSetup !== "completed") {
|
|
618
|
+
steps.push(summary.template.setup.nextStep);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (summary.testRun !== "completed") {
|
|
622
|
+
steps.push("npm test");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (steps.length > 0) {
|
|
626
|
+
process.stdout.write(`${colors.cyan("Next steps:")}\n`);
|
|
627
|
+
for (const step of steps) {
|
|
628
|
+
process.stdout.write(` ${step}\n`);
|
|
629
|
+
}
|
|
630
|
+
process.stdout.write("\n");
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (summary.demoAppsManagedByTemplate) {
|
|
634
|
+
process.stdout.write(
|
|
635
|
+
`${colors.yellow(colors.bold("Demo apps included:"))} sample tests run against bundled demo apps in local ${colors.bold("dev")}. Delete or replace ${colors.bold("demo-apps/")} if you do not want them.\n`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
process.stdout.write(`${colors.green(colors.bold("Happy testing."))}\n`);
|
|
551
640
|
}
|
|
552
641
|
|
|
553
642
|
function formatStatus(status) {
|
|
@@ -567,17 +656,20 @@ function formatStatus(status) {
|
|
|
567
656
|
|
|
568
657
|
function printSummary(summary) {
|
|
569
658
|
process.stdout.write(`\n${colors.bold("Summary")}\n`);
|
|
570
|
-
process.stdout.write(` Template: ${summary.
|
|
659
|
+
process.stdout.write(` Template: ${summary.template.id}\n`);
|
|
571
660
|
process.stdout.write(` Target: ${summary.targetDirectory}\n`);
|
|
661
|
+
process.stdout.write(` Git repository: ${formatStatus(summary.gitInit)}\n`);
|
|
572
662
|
process.stdout.write(
|
|
573
663
|
` Demo apps: ${summary.demoAppsManagedByTemplate ? "bundled and auto-started in dev when using default local URLs" : "external application required"}\n`
|
|
574
664
|
);
|
|
575
665
|
process.stdout.write(` npm install: ${formatStatus(summary.npmInstall)}\n`);
|
|
576
|
-
|
|
666
|
+
if (summary.template.setup) {
|
|
667
|
+
process.stdout.write(` ${summary.template.setup.summaryLabel}: ${formatStatus(summary.extraSetup)}\n`);
|
|
668
|
+
}
|
|
577
669
|
process.stdout.write(` npm test: ${formatStatus(summary.testRun)}\n`);
|
|
578
670
|
}
|
|
579
671
|
|
|
580
|
-
async function runPostGenerateActions(targetDirectory, summary) {
|
|
672
|
+
async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
581
673
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
582
674
|
return;
|
|
583
675
|
}
|
|
@@ -598,29 +690,35 @@ async function runPostGenerateActions(targetDirectory, summary) {
|
|
|
598
690
|
summary.npmInstall = "unavailable";
|
|
599
691
|
}
|
|
600
692
|
|
|
601
|
-
if (
|
|
602
|
-
|
|
693
|
+
if (template.setup) {
|
|
694
|
+
if (prerequisites[template.setup.availability]) {
|
|
695
|
+
const shouldRunExtraSetup = await askYesNo(template.setup.prompt, true);
|
|
603
696
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
697
|
+
if (shouldRunExtraSetup) {
|
|
698
|
+
try {
|
|
699
|
+
await template.setup.run(targetDirectory);
|
|
700
|
+
summary.extraSetup = "completed";
|
|
701
|
+
} catch (error) {
|
|
702
|
+
summary.extraSetup = "manual-recovery";
|
|
703
|
+
if (typeof template.setup.recovery === "function") {
|
|
704
|
+
template.setup.recovery(targetDirectory);
|
|
705
|
+
}
|
|
611
706
|
|
|
612
|
-
|
|
707
|
+
const shouldContinue = await askYesNo("Continue without completing setup?", true);
|
|
613
708
|
|
|
614
|
-
|
|
615
|
-
|
|
709
|
+
if (!shouldContinue) {
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
616
712
|
}
|
|
713
|
+
} else {
|
|
714
|
+
summary.extraSetup = "skipped";
|
|
617
715
|
}
|
|
618
716
|
} else {
|
|
619
|
-
|
|
717
|
+
process.stdout.write(
|
|
718
|
+
`${colors.yellow("Skipping")} ${template.setup.summaryLabel.toLowerCase()} prompt because ${template.setup.availability} is not available.\n`
|
|
719
|
+
);
|
|
720
|
+
summary.extraSetup = "unavailable";
|
|
620
721
|
}
|
|
621
|
-
} else {
|
|
622
|
-
process.stdout.write(`${colors.yellow("Skipping")} Playwright browser install prompt because npx is not available.\n`);
|
|
623
|
-
summary.playwrightInstall = "unavailable";
|
|
624
722
|
}
|
|
625
723
|
|
|
626
724
|
if (prerequisites.npm) {
|
|
@@ -649,13 +747,21 @@ async function main() {
|
|
|
649
747
|
}
|
|
650
748
|
|
|
651
749
|
const { templateName, targetDirectory, generatedInCurrentDirectory } = await resolveScaffoldArgs(args);
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
750
|
+
const template = getTemplate(templateName);
|
|
751
|
+
|
|
752
|
+
if (!template) {
|
|
753
|
+
throw new Error(`Unsupported template "${templateName}".`);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const prerequisites = collectPrerequisites();
|
|
757
|
+
const summary = createSummary(template, targetDirectory, generatedInCurrentDirectory);
|
|
758
|
+
printPrerequisiteWarnings(prerequisites);
|
|
759
|
+
await scaffoldProject(template, targetDirectory, prerequisites);
|
|
760
|
+
summary.gitInit = prerequisites.git ? "completed" : "unavailable";
|
|
761
|
+
printSuccess(template, targetDirectory, generatedInCurrentDirectory);
|
|
762
|
+
await runPostGenerateActions(template, targetDirectory, summary);
|
|
657
763
|
printSummary(summary);
|
|
658
|
-
printNextSteps(
|
|
764
|
+
printNextSteps(summary);
|
|
659
765
|
}
|
|
660
766
|
|
|
661
767
|
main().catch((error) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Cypress Template Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
- master
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
cypress-template:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: "20"
|
|
21
|
+
|
|
22
|
+
- name: Install template dependencies
|
|
23
|
+
working-directory: templates/cypress-template
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Run lint and typecheck
|
|
27
|
+
working-directory: templates/cypress-template
|
|
28
|
+
run: |
|
|
29
|
+
npm run lint
|
|
30
|
+
npm run typecheck
|
|
31
|
+
|
|
32
|
+
- name: Run Cypress tests
|
|
33
|
+
uses: cypress-io/github-action@v7
|
|
34
|
+
with:
|
|
35
|
+
working-directory: templates/cypress-template
|
|
36
|
+
install: false
|
|
37
|
+
start: npm run demo:ui
|
|
38
|
+
wait-on: "http://127.0.0.1:3000/health"
|
|
39
|
+
command: npm run cy:run
|
|
40
|
+
|
|
41
|
+
- name: Upload Cypress artifacts
|
|
42
|
+
if: always()
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: cypress-template-artifacts
|
|
46
|
+
path: |
|
|
47
|
+
templates/cypress-template/reports
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Cypress Template
|
|
2
|
+
|
|
3
|
+
This is a Cypress + TypeScript automation framework template for a small deterministic UI flow.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Feature set](#feature-set)
|
|
8
|
+
- [How it works](#how-it-works)
|
|
9
|
+
- [Project structure](#project-structure)
|
|
10
|
+
- [Quick start](#quick-start)
|
|
11
|
+
- [Environment and secrets](#environment-and-secrets)
|
|
12
|
+
- [Main commands](#main-commands)
|
|
13
|
+
- [Reports and artifacts](#reports-and-artifacts)
|
|
14
|
+
- [Add a new test](#add-a-new-test)
|
|
15
|
+
- [Extend the framework](#extend-the-framework)
|
|
16
|
+
- [CI](#ci)
|
|
17
|
+
|
|
18
|
+
## Feature set
|
|
19
|
+
|
|
20
|
+
- Cypress + TypeScript setup
|
|
21
|
+
- Cypress-native custom commands for common user actions
|
|
22
|
+
- page modules that own selectors and keep spec files focused on behavior
|
|
23
|
+
- generic `DataFactory` helpers for repeatable UI data
|
|
24
|
+
- multi-environment runtime config with `dev`, `staging`, and `prod`
|
|
25
|
+
- env-based secret resolution with a replaceable `SecretProvider`
|
|
26
|
+
- built-in screenshots and videos on failure
|
|
27
|
+
- ESLint rules that keep selectors out of spec files
|
|
28
|
+
- bundled deterministic UI demo app and GitHub Actions workflow
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
- tests live in `cypress/e2e/`
|
|
33
|
+
- shared Cypress commands live in `cypress/support/commands.ts`
|
|
34
|
+
- selectors and page-level helpers live in `cypress/support/pages/`
|
|
35
|
+
- runtime config is loaded from `config/runtime-config.ts`
|
|
36
|
+
- bundled demo app auto-starts during `npm test` and `npm run open` in local `dev` when the default local URL is in use
|
|
37
|
+
- screenshots and videos are written under `reports/`
|
|
38
|
+
|
|
39
|
+
## Project structure
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
cypress-template
|
|
43
|
+
├── cypress
|
|
44
|
+
│ ├── e2e
|
|
45
|
+
│ └── support
|
|
46
|
+
├── config
|
|
47
|
+
├── demo-apps
|
|
48
|
+
├── scripts
|
|
49
|
+
├── .github
|
|
50
|
+
├── cypress.config.ts
|
|
51
|
+
└── package.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
1. Install dependencies.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. Run the example test.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm test
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
In local `dev`, the template starts its bundled demo app automatically when the default local URL is still in use.
|
|
69
|
+
|
|
70
|
+
If you want the interactive Cypress runner:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run open
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you want to run the demo app manually for debugging:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm run demo:ui
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Default local values:
|
|
83
|
+
|
|
84
|
+
- UI base URL: `http://127.0.0.1:3000`
|
|
85
|
+
- username: `tester`
|
|
86
|
+
- password: `Password123!`
|
|
87
|
+
|
|
88
|
+
## Environment and secrets
|
|
89
|
+
|
|
90
|
+
The template supports:
|
|
91
|
+
|
|
92
|
+
- `TEST_ENV=dev`
|
|
93
|
+
- `TEST_ENV=staging`
|
|
94
|
+
- `TEST_ENV=prod`
|
|
95
|
+
|
|
96
|
+
Runtime values are resolved in this order:
|
|
97
|
+
|
|
98
|
+
1. environment-specific variables such as `DEV_UI_BASE_URL`
|
|
99
|
+
2. generic variables such as `UI_BASE_URL`
|
|
100
|
+
3. built-in defaults from `config/environments.ts`
|
|
101
|
+
|
|
102
|
+
Credentials resolve the same way:
|
|
103
|
+
|
|
104
|
+
1. `DEV_APP_USERNAME` or `DEV_APP_PASSWORD`
|
|
105
|
+
2. `APP_USERNAME` or `APP_PASSWORD`
|
|
106
|
+
3. built-in defaults for the selected environment
|
|
107
|
+
|
|
108
|
+
For local overrides, copy:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
.env.example
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
to:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
.env
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If you want to disable the bundled local demo app even in `dev`, use:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
CY_DISABLE_LOCAL_DEMO_APP=true npm test
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If your team later uses a real secret system, replace the implementation behind `config/secret-manager.ts`.
|
|
127
|
+
|
|
128
|
+
## Main commands
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm test
|
|
132
|
+
npm run open
|
|
133
|
+
npm run demo:ui
|
|
134
|
+
npm run lint
|
|
135
|
+
npm run typecheck
|
|
136
|
+
npm run cy:run
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Reports and artifacts
|
|
140
|
+
|
|
141
|
+
Outputs:
|
|
142
|
+
|
|
143
|
+
- Cypress videos: `reports/videos`
|
|
144
|
+
- Cypress screenshots: `reports/screenshots`
|
|
145
|
+
|
|
146
|
+
The default Cypress terminal output is kept as the main reporting path.
|
|
147
|
+
|
|
148
|
+
## Add a new test
|
|
149
|
+
|
|
150
|
+
Create tests under `cypress/e2e/`.
|
|
151
|
+
|
|
152
|
+
Keep the pattern simple:
|
|
153
|
+
|
|
154
|
+
- create data with `DataFactory`
|
|
155
|
+
- interact through custom commands or page modules
|
|
156
|
+
- assert in the test
|
|
157
|
+
|
|
158
|
+
Example shape:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
it("does something", () => {
|
|
162
|
+
const dataFactory = new DataFactory("local");
|
|
163
|
+
const person = dataFactory.person();
|
|
164
|
+
|
|
165
|
+
cy.signIn("tester", "Password123!");
|
|
166
|
+
cy.addPerson(person);
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Extend the framework
|
|
171
|
+
|
|
172
|
+
Common extension points:
|
|
173
|
+
|
|
174
|
+
- update or replace the bundled demo app under `demo-apps/`
|
|
175
|
+
- add page modules under `cypress/support/pages/`
|
|
176
|
+
- add shared custom commands in `cypress/support/commands.ts`
|
|
177
|
+
- extend generic data builders in `cypress/support/data/`
|
|
178
|
+
- add stronger lint rules in `eslint.config.mjs`
|
|
179
|
+
|
|
180
|
+
Recommended rules:
|
|
181
|
+
|
|
182
|
+
- keep selectors in support/page modules or custom commands
|
|
183
|
+
- keep assertions in spec files
|
|
184
|
+
- use Cypress commands for workflows, not giant helper classes
|
|
185
|
+
- keep the data layer generic until the project really needs domain-specific factories
|
|
186
|
+
|
|
187
|
+
## CI
|
|
188
|
+
|
|
189
|
+
The included workflow lives at:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
.github/workflows/cypress-tests.yml
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
It installs dependencies, starts the bundled demo app, runs Cypress tests, and uploads screenshots and videos as artifacts.
|