@toolstackhq/create-qa-patterns 1.0.6 → 1.0.8

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.
Files changed (34) hide show
  1. package/README.md +15 -2
  2. package/index.js +130 -48
  3. package/package.json +1 -1
  4. package/templates/cypress-template/.env.example +5 -0
  5. package/templates/cypress-template/.github/workflows/cypress-tests.yml +47 -0
  6. package/templates/cypress-template/README.md +195 -0
  7. package/templates/cypress-template/config/environments.ts +37 -0
  8. package/templates/cypress-template/config/runtime-config.ts +46 -0
  9. package/templates/cypress-template/config/secret-manager.ts +19 -0
  10. package/templates/cypress-template/config/test-env.ts +13 -0
  11. package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +20 -0
  12. package/templates/cypress-template/cypress/support/app-config.ts +23 -0
  13. package/templates/cypress-template/cypress/support/commands.d.ts +15 -0
  14. package/templates/cypress-template/cypress/support/commands.ts +17 -0
  15. package/templates/cypress-template/cypress/support/data/data-factory.ts +33 -0
  16. package/templates/cypress-template/cypress/support/data/id-generator.ts +13 -0
  17. package/templates/cypress-template/cypress/support/data/seeded-faker.ts +11 -0
  18. package/templates/cypress-template/cypress/support/e2e.ts +1 -0
  19. package/templates/cypress-template/cypress/support/pages/login-page.ts +23 -0
  20. package/templates/cypress-template/cypress/support/pages/people-page.ts +59 -0
  21. package/templates/cypress-template/cypress.config.ts +32 -0
  22. package/templates/cypress-template/demo-apps/ui-demo-app/package.json +10 -0
  23. package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +120 -0
  24. package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +149 -0
  25. package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +43 -0
  26. package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +121 -0
  27. package/templates/cypress-template/eslint.config.mjs +93 -0
  28. package/templates/cypress-template/package-lock.json +3758 -0
  29. package/templates/cypress-template/package.json +31 -0
  30. package/templates/cypress-template/scripts/run-cypress.mjs +105 -0
  31. package/templates/cypress-template/scripts/run-tests.sh +6 -0
  32. package/templates/cypress-template/tsconfig.json +15 -0
  33. package/templates/playwright-template/package-lock.json +533 -25
  34. package/templates/playwright-template/scripts/generate-allure-report.mjs +9 -1
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 Playwright browser installation
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
- playwright-template
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. Playwright browser installation will be unavailable.\n`);
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.npm || !prerequisites.npx || !prerequisites.docker) {
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(templateName, targetDirectory, generatedInCurrentDirectory, demoAppsManagedByTemplate) {
221
+ function createSummary(template, targetDirectory, generatedInCurrentDirectory) {
178
222
  return {
179
- templateName,
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
- playwrightInstall: "not-run",
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(`Unsupported template "${args[0]}". Use "playwright-template".`);
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 || "playwright-template";
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(templateName, targetDirectory) {
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(templateName, targetDirectory, generatedInCurrentDirectory) {
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 : templateName} in ${targetDirectory}
598
+ Generated ${template ? template.label : template.id} in ${targetDirectory}
534
599
  \n`);
535
600
 
536
601
  if (!generatedInCurrentDirectory) {
@@ -549,8 +614,8 @@ function printNextSteps(summary) {
549
614
  steps.push("npm install");
550
615
  }
551
616
 
552
- if (summary.playwrightInstall !== "completed") {
553
- steps.push("npx playwright install");
617
+ if (summary.template.setup && summary.extraSetup !== "completed") {
618
+ steps.push(summary.template.setup.nextStep);
554
619
  }
555
620
 
556
621
  if (summary.testRun !== "completed") {
@@ -591,17 +656,20 @@ function formatStatus(status) {
591
656
 
592
657
  function printSummary(summary) {
593
658
  process.stdout.write(`\n${colors.bold("Summary")}\n`);
594
- process.stdout.write(` Template: ${summary.templateName}\n`);
659
+ process.stdout.write(` Template: ${summary.template.id}\n`);
595
660
  process.stdout.write(` Target: ${summary.targetDirectory}\n`);
661
+ process.stdout.write(` Git repository: ${formatStatus(summary.gitInit)}\n`);
596
662
  process.stdout.write(
597
663
  ` Demo apps: ${summary.demoAppsManagedByTemplate ? "bundled and auto-started in dev when using default local URLs" : "external application required"}\n`
598
664
  );
599
665
  process.stdout.write(` npm install: ${formatStatus(summary.npmInstall)}\n`);
600
- process.stdout.write(` Playwright browser install: ${formatStatus(summary.playwrightInstall)}\n`);
666
+ if (summary.template.setup) {
667
+ process.stdout.write(` ${summary.template.setup.summaryLabel}: ${formatStatus(summary.extraSetup)}\n`);
668
+ }
601
669
  process.stdout.write(` npm test: ${formatStatus(summary.testRun)}\n`);
602
670
  }
603
671
 
604
- async function runPostGenerateActions(targetDirectory, summary) {
672
+ async function runPostGenerateActions(template, targetDirectory, summary) {
605
673
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
606
674
  return;
607
675
  }
@@ -622,29 +690,35 @@ async function runPostGenerateActions(targetDirectory, summary) {
622
690
  summary.npmInstall = "unavailable";
623
691
  }
624
692
 
625
- if (prerequisites.npx) {
626
- const shouldInstallPlaywright = await askYesNo("Run npx playwright install now?", true);
693
+ if (template.setup) {
694
+ if (prerequisites[template.setup.availability]) {
695
+ const shouldRunExtraSetup = await askYesNo(template.setup.prompt, true);
627
696
 
628
- if (shouldInstallPlaywright) {
629
- try {
630
- await runCommand("npx", ["playwright", "install"], targetDirectory);
631
- summary.playwrightInstall = "completed";
632
- } catch (error) {
633
- summary.playwrightInstall = "manual-recovery";
634
- printPlaywrightInstallRecovery(targetDirectory);
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
+ }
635
706
 
636
- const shouldContinue = await askYesNo("Continue without completing Playwright browser install?", true);
707
+ const shouldContinue = await askYesNo("Continue without completing setup?", true);
637
708
 
638
- if (!shouldContinue) {
639
- throw error;
709
+ if (!shouldContinue) {
710
+ throw error;
711
+ }
640
712
  }
713
+ } else {
714
+ summary.extraSetup = "skipped";
641
715
  }
642
716
  } else {
643
- summary.playwrightInstall = "skipped";
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";
644
721
  }
645
- } else {
646
- process.stdout.write(`${colors.yellow("Skipping")} Playwright browser install prompt because npx is not available.\n`);
647
- summary.playwrightInstall = "unavailable";
648
722
  }
649
723
 
650
724
  if (prerequisites.npm) {
@@ -673,11 +747,19 @@ async function main() {
673
747
  }
674
748
 
675
749
  const { templateName, targetDirectory, generatedInCurrentDirectory } = await resolveScaffoldArgs(args);
676
- const summary = createSummary(templateName, targetDirectory, generatedInCurrentDirectory, true);
677
- printPrerequisiteWarnings(collectPrerequisites());
678
- await scaffoldProject(templateName, targetDirectory);
679
- printSuccess(templateName, targetDirectory, generatedInCurrentDirectory);
680
- await runPostGenerateActions(targetDirectory, summary);
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);
681
763
  printSummary(summary);
682
764
  printNextSteps(summary);
683
765
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolstackhq/create-qa-patterns",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "CLI for generating QA framework templates.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,5 @@
1
+ TEST_ENV=dev
2
+ TEST_RUN_ID=local
3
+ DEV_UI_BASE_URL=http://127.0.0.1:3000
4
+ DEV_APP_USERNAME=tester
5
+ DEV_APP_PASSWORD=Password123!
@@ -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.
@@ -0,0 +1,37 @@
1
+ import type { TestEnvironment } from "./test-env";
2
+
3
+ type EnvironmentDefaults = {
4
+ uiBaseUrl: string;
5
+ credentials: {
6
+ username: string;
7
+ password: string;
8
+ };
9
+ };
10
+
11
+ const environmentDefaults: Record<TestEnvironment, EnvironmentDefaults> = {
12
+ dev: {
13
+ uiBaseUrl: "http://127.0.0.1:3000",
14
+ credentials: {
15
+ username: "tester",
16
+ password: "Password123!"
17
+ }
18
+ },
19
+ staging: {
20
+ uiBaseUrl: "https://staging-ui.example.internal",
21
+ credentials: {
22
+ username: "staging-user",
23
+ password: "staging-password"
24
+ }
25
+ },
26
+ prod: {
27
+ uiBaseUrl: "https://ui.example.internal",
28
+ credentials: {
29
+ username: "prod-user",
30
+ password: "prod-password"
31
+ }
32
+ }
33
+ };
34
+
35
+ export function getEnvironmentDefaults(environment: TestEnvironment): EnvironmentDefaults {
36
+ return environmentDefaults[environment];
37
+ }