@toolstackhq/create-qa-patterns 1.0.13 → 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.
Files changed (123) hide show
  1. package/README.md +23 -0
  2. package/index.js +282 -738
  3. package/lib/args.js +139 -0
  4. package/lib/constants.js +115 -0
  5. package/lib/interactive.js +131 -0
  6. package/lib/local-env.js +65 -0
  7. package/lib/metadata.js +329 -0
  8. package/lib/output.js +326 -0
  9. package/lib/prereqs.js +72 -0
  10. package/lib/scaffold.js +120 -0
  11. package/lib/templates.js +40 -0
  12. package/package.json +5 -3
  13. package/templates/cypress-template/.env.example +2 -2
  14. package/templates/cypress-template/.github/workflows/cypress-tests.yml +2 -2
  15. package/templates/cypress-template/README.md +29 -6
  16. package/templates/cypress-template/allurerc.mjs +1 -1
  17. package/templates/cypress-template/config/environments.ts +13 -11
  18. package/templates/cypress-template/config/runtime-config.ts +17 -12
  19. package/templates/cypress-template/config/secret-manager.ts +1 -1
  20. package/templates/cypress-template/config/test-env.ts +3 -3
  21. package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +12 -10
  22. package/templates/cypress-template/cypress/support/app-config.ts +5 -5
  23. package/templates/cypress-template/cypress/support/commands.ts +7 -7
  24. package/templates/cypress-template/cypress/support/data/data-factory.ts +6 -4
  25. package/templates/cypress-template/cypress/support/data/id-generator.ts +1 -1
  26. package/templates/cypress-template/cypress/support/data/seeded-faker.ts +2 -2
  27. package/templates/cypress-template/cypress/support/e2e.ts +2 -2
  28. package/templates/cypress-template/cypress/support/pages/login-page.ts +4 -4
  29. package/templates/cypress-template/cypress/support/pages/people-page.ts +10 -10
  30. package/templates/cypress-template/cypress.config.ts +9 -9
  31. package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +1 -1
  32. package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +44 -41
  33. package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +31 -3
  34. package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +5 -5
  35. package/templates/cypress-template/eslint.config.mjs +53 -45
  36. package/templates/cypress-template/package.json +6 -5
  37. package/templates/cypress-template/scripts/ensure-local-env.mjs +36 -0
  38. package/templates/cypress-template/scripts/generate-allure-report.mjs +16 -10
  39. package/templates/cypress-template/scripts/run-cypress.mjs +33 -24
  40. package/templates/cypress-template/scripts/run-tests.sh +1 -0
  41. package/templates/cypress-template/tsconfig.json +7 -1
  42. package/templates/playwright-template/.env.example +6 -6
  43. package/templates/playwright-template/.github/workflows/playwright-tests.yml +14 -5
  44. package/templates/playwright-template/README.md +25 -5
  45. package/templates/playwright-template/allurerc.mjs +1 -1
  46. package/templates/playwright-template/components/flash-message.ts +2 -2
  47. package/templates/playwright-template/config/environments.ts +16 -14
  48. package/templates/playwright-template/config/runtime-config.ts +17 -12
  49. package/templates/playwright-template/config/secret-manager.ts +1 -1
  50. package/templates/playwright-template/config/test-env.ts +3 -3
  51. package/templates/playwright-template/data/factories/data-factory.ts +6 -4
  52. package/templates/playwright-template/data/generators/id-generator.ts +1 -1
  53. package/templates/playwright-template/data/generators/seeded-faker.ts +2 -2
  54. package/templates/playwright-template/demo-apps/api-demo-server/src/server.js +9 -9
  55. package/templates/playwright-template/demo-apps/api-demo-server/src/store.js +1 -1
  56. package/templates/playwright-template/demo-apps/ui-demo-app/public/styles.css +1 -1
  57. package/templates/playwright-template/demo-apps/ui-demo-app/src/server.js +44 -41
  58. package/templates/playwright-template/demo-apps/ui-demo-app/src/store.js +31 -3
  59. package/templates/playwright-template/demo-apps/ui-demo-app/src/templates.js +5 -5
  60. package/templates/playwright-template/eslint.config.mjs +40 -40
  61. package/templates/playwright-template/fixtures/test-fixtures.ts +27 -12
  62. package/templates/playwright-template/lint/architecture-plugin.cjs +36 -31
  63. package/templates/playwright-template/package.json +7 -6
  64. package/templates/playwright-template/pages/base-page.ts +4 -4
  65. package/templates/playwright-template/pages/login-page.ts +9 -9
  66. package/templates/playwright-template/pages/people-page.ts +21 -17
  67. package/templates/playwright-template/playwright.config.ts +22 -19
  68. package/templates/playwright-template/reporters/structured-reporter.ts +11 -8
  69. package/templates/playwright-template/scripts/ensure-local-env.mjs +37 -0
  70. package/templates/playwright-template/scripts/generate-allure-report.mjs +16 -10
  71. package/templates/playwright-template/scripts/run-tests.sh +1 -0
  72. package/templates/playwright-template/tests/api-people.spec.ts +8 -6
  73. package/templates/playwright-template/tests/ui-journey.spec.ts +13 -8
  74. package/templates/playwright-template/tsconfig.json +3 -11
  75. package/templates/playwright-template/utils/logger.ts +12 -8
  76. package/templates/playwright-template/utils/test-step.ts +5 -5
  77. package/templates/wdio-template/.env.example +14 -0
  78. package/templates/wdio-template/.github/workflows/wdio-tests.yml +46 -0
  79. package/templates/wdio-template/README.md +241 -0
  80. package/templates/wdio-template/allurerc.mjs +10 -0
  81. package/templates/wdio-template/components/README.md +5 -0
  82. package/templates/wdio-template/components/flash-message.ts +16 -0
  83. package/templates/wdio-template/config/README.md +5 -0
  84. package/templates/wdio-template/config/environments.ts +40 -0
  85. package/templates/wdio-template/config/runtime-config.ts +53 -0
  86. package/templates/wdio-template/config/secret-manager.ts +29 -0
  87. package/templates/wdio-template/config/test-env.ts +9 -0
  88. package/templates/wdio-template/data/README.md +9 -0
  89. package/templates/wdio-template/data/factories/README.md +6 -0
  90. package/templates/wdio-template/data/factories/data-factory.ts +36 -0
  91. package/templates/wdio-template/data/generators/README.md +5 -0
  92. package/templates/wdio-template/data/generators/id-generator.ts +18 -0
  93. package/templates/wdio-template/data/generators/seeded-faker.ts +14 -0
  94. package/templates/wdio-template/demo-apps/ui-demo-app/public/styles.css +120 -0
  95. package/templates/wdio-template/demo-apps/ui-demo-app/src/server.js +152 -0
  96. package/templates/wdio-template/demo-apps/ui-demo-app/src/store.js +71 -0
  97. package/templates/wdio-template/demo-apps/ui-demo-app/src/templates.js +121 -0
  98. package/templates/wdio-template/eslint.config.mjs +86 -0
  99. package/templates/wdio-template/lint/architecture-plugin.cjs +123 -0
  100. package/templates/wdio-template/package-lock.json +11058 -0
  101. package/templates/wdio-template/package.json +44 -0
  102. package/templates/wdio-template/pages/README.md +6 -0
  103. package/templates/wdio-template/pages/base-page.ts +15 -0
  104. package/templates/wdio-template/pages/login-page.ts +27 -0
  105. package/templates/wdio-template/pages/people-page.ts +54 -0
  106. package/templates/wdio-template/reporters/README.md +5 -0
  107. package/templates/wdio-template/reporters/structured-reporter.ts +78 -0
  108. package/templates/wdio-template/scripts/README.md +5 -0
  109. package/templates/wdio-template/scripts/ensure-local-env.mjs +36 -0
  110. package/templates/wdio-template/scripts/generate-allure-report.mjs +72 -0
  111. package/templates/wdio-template/scripts/run-tests.sh +7 -0
  112. package/templates/wdio-template/scripts/run-wdio.mjs +114 -0
  113. package/templates/wdio-template/tests/README.md +7 -0
  114. package/templates/wdio-template/tests/ui-journey.spec.ts +52 -0
  115. package/templates/wdio-template/tsconfig.json +22 -0
  116. package/templates/wdio-template/utils/README.md +5 -0
  117. package/templates/wdio-template/utils/logger.ts +60 -0
  118. package/templates/wdio-template/utils/test-step.ts +20 -0
  119. package/templates/wdio-template/wdio.conf.ts +58 -0
  120. package/tests/args.test.js +58 -0
  121. package/tests/local-env.test.js +70 -0
  122. package/tests/metadata.test.js +147 -0
  123. package/tests/templates.test.js +44 -0
@@ -0,0 +1,120 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const { renderTemplateFile } = require('./metadata');
4
+
5
+ function sleep(ms) {
6
+ return new Promise((resolve) => setTimeout(resolve, ms));
7
+ }
8
+
9
+ function ensureScaffoldTarget(targetDirectory) {
10
+ if (!fs.existsSync(targetDirectory)) {
11
+ fs.mkdirSync(targetDirectory, { recursive: true });
12
+ return;
13
+ }
14
+
15
+ const entries = fs
16
+ .readdirSync(targetDirectory)
17
+ .filter((entry) => !['.git', '.DS_Store'].includes(entry));
18
+
19
+ if (entries.length > 0) {
20
+ throw new Error(`Target directory is not empty: ${targetDirectory}`);
21
+ }
22
+ }
23
+
24
+ function customizeProject(targetDirectory, template, options) {
25
+ const packageJsonPath = path.join(targetDirectory, 'package.json');
26
+ const packageLockPath = path.join(targetDirectory, 'package-lock.json');
27
+ const gitignorePath = path.join(targetDirectory, '.gitignore');
28
+
29
+ if (fs.existsSync(packageJsonPath)) {
30
+ fs.writeFileSync(
31
+ packageJsonPath,
32
+ renderTemplateFile(template, 'package.json', targetDirectory, options),
33
+ 'utf8'
34
+ );
35
+ }
36
+
37
+ if (fs.existsSync(packageLockPath)) {
38
+ fs.writeFileSync(
39
+ packageLockPath,
40
+ renderTemplateFile(
41
+ template,
42
+ 'package-lock.json',
43
+ targetDirectory,
44
+ options
45
+ ),
46
+ 'utf8'
47
+ );
48
+ }
49
+
50
+ if (!fs.existsSync(gitignorePath)) {
51
+ fs.writeFileSync(gitignorePath, options.defaultGitignore, 'utf8');
52
+ }
53
+ }
54
+
55
+ async function scaffoldProject(
56
+ template,
57
+ targetDirectory,
58
+ prerequisites,
59
+ options
60
+ ) {
61
+ const {
62
+ createLocalCredentials,
63
+ defaultGitignore,
64
+ getTemplateDirectory,
65
+ initializeGitRepository,
66
+ renderProgress,
67
+ toPackageName,
68
+ writeGeneratedLocalEnv
69
+ } = options;
70
+ const templateDirectory = getTemplateDirectory(template.id);
71
+
72
+ if (!fs.existsSync(templateDirectory)) {
73
+ throw new Error(`Template files are missing for "${template.id}".`);
74
+ }
75
+
76
+ const steps = [
77
+ 'Validating target directory',
78
+ 'Copying template files',
79
+ 'Customizing project files',
80
+ 'Finalizing scaffold'
81
+ ];
82
+
83
+ renderProgress(0, steps.length, 'Preparing scaffold');
84
+ ensureScaffoldTarget(targetDirectory);
85
+ await sleep(60);
86
+
87
+ renderProgress(1, steps.length, steps[0]);
88
+ await sleep(80);
89
+
90
+ fs.cpSync(templateDirectory, targetDirectory, { recursive: true });
91
+ renderProgress(2, steps.length, steps[1]);
92
+ await sleep(80);
93
+
94
+ customizeProject(targetDirectory, template, {
95
+ defaultGitignore,
96
+ getTemplateDirectory,
97
+ toPackageName
98
+ });
99
+ renderProgress(3, steps.length, steps[2]);
100
+ await sleep(80);
101
+
102
+ if (prerequisites.git) {
103
+ initializeGitRepository(targetDirectory);
104
+ }
105
+
106
+ const localEnv = writeGeneratedLocalEnv(
107
+ targetDirectory,
108
+ template.id,
109
+ createLocalCredentials(targetDirectory)
110
+ );
111
+
112
+ renderProgress(4, steps.length, steps[3]);
113
+ await sleep(60);
114
+ process.stdout.write('\n');
115
+ return localEnv;
116
+ }
117
+
118
+ module.exports = {
119
+ scaffoldProject
120
+ };
@@ -0,0 +1,40 @@
1
+ const path = require('node:path');
2
+
3
+ function createTemplateAliases(templates) {
4
+ return new Map(
5
+ templates.flatMap((template) => [
6
+ [template.id, template.id],
7
+ ...template.aliases.map((alias) => [alias, template.id])
8
+ ])
9
+ );
10
+ }
11
+
12
+ function resolveTemplate(templateAliases, value) {
13
+ return templateAliases.get(value);
14
+ }
15
+
16
+ function getTemplate(templates, templateId) {
17
+ return templates.find((template) => template.id === templateId);
18
+ }
19
+
20
+ function toPackageName(targetDirectory, template) {
21
+ const baseName = path.basename(targetDirectory).toLowerCase();
22
+ const normalized = baseName
23
+ .replace(/[^a-z0-9-_]+/g, '-')
24
+ .replace(/^-+|-+$/g, '')
25
+ .replace(/-{2,}/g, '-');
26
+
27
+ return normalized || template.defaultPackageName || 'qa-patterns-template';
28
+ }
29
+
30
+ function getTemplateDirectory(rootDirectory, templateId) {
31
+ return path.resolve(rootDirectory, 'templates', templateId);
32
+ }
33
+
34
+ module.exports = {
35
+ createTemplateAliases,
36
+ getTemplate,
37
+ getTemplateDirectory,
38
+ resolveTemplate,
39
+ toPackageName
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolstackhq/create-qa-patterns",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "CLI for generating QA framework templates.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,15 +16,17 @@
16
16
  },
17
17
  "files": [
18
18
  "index.js",
19
+ "lib",
19
20
  "templates",
20
21
  "README.md",
21
- "LICENSE"
22
+ "LICENSE",
23
+ "tests"
22
24
  ],
23
25
  "bin": {
24
26
  "create-qa-patterns": "./index.js"
25
27
  },
26
28
  "scripts": {
27
29
  "start": "node ./index.js",
28
- "test": "node ./index.js --help"
30
+ "test": "node --test ./tests/*.test.js"
29
31
  }
30
32
  }
@@ -1,5 +1,5 @@
1
1
  TEST_ENV=dev
2
2
  TEST_RUN_ID=local
3
3
  DEV_UI_BASE_URL=http://127.0.0.1:3000
4
- DEV_APP_USERNAME=tester
5
- DEV_APP_PASSWORD=Password123!
4
+ DEV_APP_USERNAME=generate-a-local-username
5
+ DEV_APP_PASSWORD=generate-a-local-password
@@ -17,7 +17,7 @@ jobs:
17
17
 
18
18
  - uses: actions/setup-node@v4
19
19
  with:
20
- node-version: "20"
20
+ node-version: '20'
21
21
 
22
22
  - name: Install template dependencies
23
23
  working-directory: templates/cypress-template
@@ -35,7 +35,7 @@ jobs:
35
35
  working-directory: templates/cypress-template
36
36
  install: false
37
37
  start: npm run demo:ui
38
- wait-on: "http://127.0.0.1:3000/health"
38
+ wait-on: 'http://127.0.0.1:3000/health'
39
39
  command: npm run cy:run
40
40
 
41
41
  - name: Upload Cypress artifacts
@@ -13,6 +13,7 @@ This is a Cypress + TypeScript automation framework template for a small determi
13
13
  - [Reports and artifacts](#reports-and-artifacts)
14
14
  - [Add a new test](#add-a-new-test)
15
15
  - [Extend the framework](#extend-the-framework)
16
+ - [Template upgrades](#template-upgrades)
16
17
  - [CI](#ci)
17
18
 
18
19
  ## Feature set
@@ -90,8 +91,7 @@ npm run report:allure
90
91
  Default local values:
91
92
 
92
93
  - UI base URL: `http://127.0.0.1:3000`
93
- - username: `tester`
94
- - password: `Password123!`
94
+ - credentials: generated into local `.env` on first run
95
95
 
96
96
  ## Environment and secrets
97
97
 
@@ -111,7 +111,7 @@ Credentials resolve the same way:
111
111
 
112
112
  1. `DEV_APP_USERNAME` or `DEV_APP_PASSWORD`
113
113
  2. `APP_USERNAME` or `APP_PASSWORD`
114
- 3. built-in defaults for the selected environment
114
+ 3. built-in empty defaults for the selected environment
115
115
 
116
116
  For local overrides, copy:
117
117
 
@@ -125,6 +125,8 @@ to:
125
125
  .env
126
126
  ```
127
127
 
128
+ On the first local run, the template also creates a `.env` file with random demo credentials if one does not already exist.
129
+
128
130
  If you want to disable the bundled local demo app even in `dev`, use:
129
131
 
130
132
  ```bash
@@ -171,11 +173,14 @@ Keep the pattern simple:
171
173
  Example shape:
172
174
 
173
175
  ```ts
174
- it("does something", () => {
175
- const dataFactory = new DataFactory("local");
176
+ import { loadAppConfig } from '../support/app-config';
177
+
178
+ it('does something', () => {
179
+ const appConfig = loadAppConfig();
180
+ const dataFactory = new DataFactory('local');
176
181
  const person = dataFactory.person();
177
182
 
178
- cy.signIn("tester", "Password123!");
183
+ cy.signIn(appConfig.credentials.username, appConfig.credentials.password);
179
184
  cy.addPerson(person);
180
185
  });
181
186
  ```
@@ -197,6 +202,24 @@ Recommended rules:
197
202
  - use Cypress commands for workflows, not giant helper classes
198
203
  - keep the data layer generic until the project really needs domain-specific factories
199
204
 
205
+ ## Template upgrades
206
+
207
+ This project includes a `.qa-patterns.json` metadata file so future CLI versions can compare the current project against the managed template baseline.
208
+
209
+ Check for available safe updates:
210
+
211
+ ```bash
212
+ npx -y @toolstackhq/create-qa-patterns upgrade check .
213
+ ```
214
+
215
+ Apply only safe managed-file updates:
216
+
217
+ ```bash
218
+ npx -y @toolstackhq/create-qa-patterns upgrade apply --safe .
219
+ ```
220
+
221
+ The upgrade flow is conservative. It updates framework infrastructure such as config, scripts, workflows, and package metadata when those files are still unchanged from the generated baseline. If you changed a managed file yourself, the CLI reports a conflict instead of overwriting it.
222
+
200
223
  ## CI
201
224
 
202
225
  The included workflow lives at:
@@ -1,5 +1,5 @@
1
1
  export default {
2
- name: "qa-patterns Cypress Template",
2
+ name: 'qa-patterns Cypress Template',
3
3
  plugins: {
4
4
  awesome: {
5
5
  options: {
@@ -1,5 +1,5 @@
1
1
  // Defines the built-in environment defaults used when env vars are not provided.
2
- import type { TestEnvironment } from "./test-env";
2
+ import type { TestEnvironment } from './test-env';
3
3
 
4
4
  type EnvironmentDefaults = {
5
5
  uiBaseUrl: string;
@@ -11,28 +11,30 @@ type EnvironmentDefaults = {
11
11
 
12
12
  const DEFAULTS: Record<TestEnvironment, EnvironmentDefaults> = {
13
13
  dev: {
14
- uiBaseUrl: "http://127.0.0.1:3000",
14
+ uiBaseUrl: 'http://127.0.0.1:3000',
15
15
  credentials: {
16
- username: "tester",
17
- password: "Password123!"
16
+ username: '',
17
+ password: ''
18
18
  }
19
19
  },
20
20
  staging: {
21
- uiBaseUrl: "https://staging-ui.example.internal",
21
+ uiBaseUrl: 'https://staging-ui.example.internal',
22
22
  credentials: {
23
- username: "staging-user",
24
- password: "replace-me"
23
+ username: '',
24
+ password: ''
25
25
  }
26
26
  },
27
27
  prod: {
28
- uiBaseUrl: "https://ui.example.internal",
28
+ uiBaseUrl: 'https://ui.example.internal',
29
29
  credentials: {
30
- username: "prod-user",
31
- password: "replace-me"
30
+ username: '',
31
+ password: ''
32
32
  }
33
33
  }
34
34
  };
35
35
 
36
- export function getEnvironmentDefaults(testEnv: TestEnvironment): EnvironmentDefaults {
36
+ export function getEnvironmentDefaults(
37
+ testEnv: TestEnvironment
38
+ ): EnvironmentDefaults {
37
39
  return DEFAULTS[testEnv];
38
40
  }
@@ -1,20 +1,23 @@
1
1
  // Builds the runtime configuration object that tests and fixtures consume.
2
- import path from "node:path";
2
+ import path from 'node:path';
3
3
 
4
- import dotenv from "dotenv";
5
- import { z } from "zod";
4
+ import dotenv from 'dotenv';
5
+ import { z } from 'zod';
6
6
 
7
- import { getEnvironmentDefaults } from "./environments";
8
- import { EnvSecretProvider, SecretManager } from "./secret-manager";
9
- import { loadTestEnvironment } from "./test-env";
7
+ import { getEnvironmentDefaults } from './environments';
8
+ import { EnvSecretProvider, SecretManager } from './secret-manager';
9
+ import { loadTestEnvironment } from './test-env';
10
10
 
11
11
  const environment = loadTestEnvironment();
12
12
 
13
- dotenv.config({ path: path.resolve(process.cwd(), ".env") });
14
- dotenv.config({ path: path.resolve(process.cwd(), `.env.${environment}`), override: true });
13
+ dotenv.config({ path: path.resolve(process.cwd(), '.env') });
14
+ dotenv.config({
15
+ path: path.resolve(process.cwd(), `.env.${environment}`),
16
+ override: true
17
+ });
15
18
 
16
19
  const runtimeConfigSchema = z.object({
17
- testEnv: z.enum(["dev", "staging", "prod"]),
20
+ testEnv: z.enum(['dev', 'staging', 'prod']),
18
21
  testRunId: z.string().min(1),
19
22
  uiBaseUrl: z.string().url(),
20
23
  credentials: z.object({
@@ -36,13 +39,15 @@ export function loadRuntimeConfig(): RuntimeConfig {
36
39
 
37
40
  return runtimeConfigSchema.parse({
38
41
  testEnv: environment,
39
- testRunId: process.env.TEST_RUN_ID ?? "local",
42
+ testRunId: process.env.TEST_RUN_ID ?? 'local',
40
43
  uiBaseUrl,
41
44
  credentials: {
42
45
  username:
43
- secretManager.getOptionalSecret("APP_USERNAME", environment) ?? defaults.credentials.username,
46
+ secretManager.getOptionalSecret('APP_USERNAME', environment) ??
47
+ defaults.credentials.username,
44
48
  password:
45
- secretManager.getOptionalSecret("APP_PASSWORD", environment) ?? defaults.credentials.password
49
+ secretManager.getOptionalSecret('APP_PASSWORD', environment) ??
50
+ defaults.credentials.password
46
51
  }
47
52
  });
48
53
  }
@@ -1,5 +1,5 @@
1
1
  // Minimal secret abstraction so env-based secrets can later be replaced cleanly.
2
- import type { TestEnvironment } from "./test-env";
2
+ import type { TestEnvironment } from './test-env';
3
3
 
4
4
  export interface SecretProvider {
5
5
  getSecret(key: string, testEnv: TestEnvironment): string | undefined;
@@ -1,9 +1,9 @@
1
- import { z } from "zod";
1
+ import { z } from 'zod';
2
2
 
3
- export const testEnvironmentSchema = z.enum(["dev", "staging", "prod"]);
3
+ export const testEnvironmentSchema = z.enum(['dev', 'staging', 'prod']);
4
4
 
5
5
  export type TestEnvironment = z.infer<typeof testEnvironmentSchema>;
6
6
 
7
7
  export function loadTestEnvironment(): TestEnvironment {
8
- return testEnvironmentSchema.parse(process.env.TEST_ENV ?? "dev");
8
+ return testEnvironmentSchema.parse(process.env.TEST_ENV ?? 'dev');
9
9
  }
@@ -1,21 +1,23 @@
1
1
  // Starter Cypress scenario that demonstrates the preferred spec style for this template.
2
- import { DataFactory } from "../support/data/data-factory";
3
- import { loadAppConfig } from "../support/app-config";
4
- import { peoplePage } from "../support/pages/people-page";
2
+ import { DataFactory } from '../support/data/data-factory';
3
+ import { loadAppConfig } from '../support/app-config';
4
+ import { peoplePage } from '../support/pages/people-page';
5
5
 
6
- describe("UI starter journey", () => {
7
- it("signs in and adds one person", () => {
6
+ describe('UI starter journey', () => {
7
+ it('signs in and adds one person', () => {
8
8
  const appConfig = loadAppConfig();
9
9
  const dataFactory = new DataFactory(appConfig.testRunId);
10
10
  const person = dataFactory.person();
11
11
 
12
12
  cy.signIn(appConfig.credentials.username, appConfig.credentials.password);
13
- peoplePage.welcomeMessage().should("contain", appConfig.credentials.username);
13
+ peoplePage
14
+ .welcomeMessage()
15
+ .should('contain', appConfig.credentials.username);
14
16
 
15
17
  cy.addPerson(person);
16
- peoplePage.flashMessage().should("contain", "Person added");
17
- peoplePage.nameCell(person.personId).should("have.text", person.name);
18
- peoplePage.roleCell(person.personId).should("have.text", person.role);
19
- peoplePage.emailCell(person.personId).should("have.text", person.email);
18
+ peoplePage.flashMessage().should('contain', 'Person added');
19
+ peoplePage.nameCell(person.personId).should('have.text', person.name);
20
+ peoplePage.roleCell(person.personId).should('have.text', person.role);
21
+ peoplePage.emailCell(person.personId).should('have.text', person.email);
20
22
  });
21
23
  });
@@ -11,14 +11,14 @@ export type AppConfig = {
11
11
  };
12
12
 
13
13
  export function loadAppConfig(): AppConfig {
14
- const credentials = Cypress.env("credentials") as Credentials | undefined;
14
+ const credentials = Cypress.env('credentials') as Credentials | undefined;
15
15
 
16
16
  return {
17
- testEnv: String(Cypress.env("testEnv") ?? "dev"),
18
- testRunId: String(Cypress.env("testRunId") ?? "local"),
17
+ testEnv: String(Cypress.env('testEnv') ?? 'dev'),
18
+ testRunId: String(Cypress.env('testRunId') ?? 'local'),
19
19
  credentials: {
20
- username: credentials?.username ?? "tester",
21
- password: credentials?.password ?? "Password123!"
20
+ username: credentials?.username ?? '',
21
+ password: credentials?.password ?? ''
22
22
  }
23
23
  };
24
24
  }
@@ -1,7 +1,7 @@
1
1
  // Registers the custom Cypress commands that the starter specs rely on.
2
- import type { PersonRecord } from "./data/data-factory";
3
- import { loginPage } from "./pages/login-page";
4
- import { peoplePage } from "./pages/people-page";
2
+ import type { PersonRecord } from './data/data-factory';
3
+ import { loginPage } from './pages/login-page';
4
+ import { peoplePage } from './pages/people-page';
5
5
 
6
6
  declare global {
7
7
  namespace Cypress {
@@ -13,17 +13,17 @@ declare global {
13
13
  }
14
14
  }
15
15
 
16
- Cypress.Commands.add("getByTestId", (testId: string) => {
16
+ Cypress.Commands.add('getByTestId', (testId: string) => {
17
17
  return cy.get(`[data-testid='${testId}']`);
18
18
  });
19
19
 
20
- Cypress.Commands.add("signIn", (username: string, password: string) => {
20
+ Cypress.Commands.add('signIn', (username: string, password: string) => {
21
21
  loginPage.visit();
22
22
  loginPage.login(username, password);
23
- peoplePage.heading().should("be.visible");
23
+ peoplePage.heading().should('be.visible');
24
24
  });
25
25
 
26
- Cypress.Commands.add("addPerson", (person: PersonRecord) => {
26
+ Cypress.Commands.add('addPerson', (person: PersonRecord) => {
27
27
  peoplePage.addPerson(person);
28
28
  });
29
29
 
@@ -1,6 +1,6 @@
1
1
  // Generic data builders that keep tests readable and deterministic.
2
- import { createSeededFaker } from "./seeded-faker";
3
- import { IdGenerator } from "./id-generator";
2
+ import { createSeededFaker } from './seeded-faker';
3
+ import { IdGenerator } from './id-generator';
4
4
 
5
5
  export type PersonRecord = {
6
6
  personId: string;
@@ -17,7 +17,7 @@ export class DataFactory {
17
17
  }
18
18
 
19
19
  person(overrides?: Partial<PersonRecord>): PersonRecord {
20
- const personId = overrides?.personId ?? this.idGenerator.next("person");
20
+ const personId = overrides?.personId ?? this.idGenerator.next('person');
21
21
  const seededFaker = createSeededFaker(`${this.testRunId}:${personId}`);
22
22
  const firstName = seededFaker.person.firstName();
23
23
  const lastName = seededFaker.person.lastName();
@@ -27,7 +27,9 @@ export class DataFactory {
27
27
  personId,
28
28
  name,
29
29
  role: overrides?.role ?? seededFaker.person.jobTitle(),
30
- email: overrides?.email ?? `${firstName}.${lastName}.${personId}@example.test`.toLowerCase(),
30
+ email:
31
+ overrides?.email ??
32
+ `${firstName}.${lastName}.${personId}@example.test`.toLowerCase(),
31
33
  ...overrides
32
34
  };
33
35
  }
@@ -7,7 +7,7 @@ export class IdGenerator {
7
7
  next(prefix: string): string {
8
8
  const counter = (this.counters.get(prefix) ?? 0) + 1;
9
9
  this.counters.set(prefix, counter);
10
- return `${prefix}-${this.runId}-${String(counter).padStart(4, "0")}`;
10
+ return `${prefix}-${this.runId}-${String(counter).padStart(4, '0')}`;
11
11
  }
12
12
 
13
13
  nextSequence(prefix: string): number {
@@ -1,8 +1,8 @@
1
1
  // Seeded faker wrapper so generated values stay stable for a given run id.
2
- import { Faker, en } from "@faker-js/faker";
2
+ import { Faker, en } from '@faker-js/faker';
3
3
 
4
4
  function hashSeed(value: string): number {
5
- return value.split("").reduce((seed, character) => {
5
+ return value.split('').reduce((seed, character) => {
6
6
  return ((seed << 5) - seed + character.charCodeAt(0)) | 0;
7
7
  }, 0);
8
8
  }
@@ -1,2 +1,2 @@
1
- import "allure-cypress";
2
- import "./commands";
1
+ import 'allure-cypress';
2
+ import './commands';
@@ -1,19 +1,19 @@
1
1
  // Page module for the login screen used by custom Cypress commands.
2
2
  export const loginPage = {
3
3
  visit(): Cypress.Chainable {
4
- return cy.visit("/login");
4
+ return cy.visit('/login');
5
5
  },
6
6
 
7
7
  usernameInput(): Cypress.Chainable {
8
- return cy.get("#username");
8
+ return cy.get('#username');
9
9
  },
10
10
 
11
11
  passwordInput(): Cypress.Chainable {
12
- return cy.get("#password");
12
+ return cy.get('#password');
13
13
  },
14
14
 
15
15
  submitButton(): Cypress.Chainable {
16
- return cy.contains("button", "Sign in");
16
+ return cy.contains('button', 'Sign in');
17
17
  },
18
18
 
19
19
  login(username: string, password: string): void {
@@ -1,9 +1,9 @@
1
1
  // Page module for the people screen used in the starter Cypress journey.
2
- import type { PersonRecord } from "../data/data-factory";
2
+ import type { PersonRecord } from '../data/data-factory';
3
3
 
4
4
  export const peoplePage = {
5
5
  heading(): Cypress.Chainable {
6
- return cy.contains("h1", "People");
6
+ return cy.contains('h1', 'People');
7
7
  },
8
8
 
9
9
  welcomeMessage(): Cypress.Chainable {
@@ -15,23 +15,23 @@ export const peoplePage = {
15
15
  },
16
16
 
17
17
  personIdInput(): Cypress.Chainable {
18
- return cy.get("#personId");
18
+ return cy.get('#personId');
19
19
  },
20
20
 
21
21
  nameInput(): Cypress.Chainable {
22
- return cy.get("#name");
22
+ return cy.get('#name');
23
23
  },
24
24
 
25
25
  roleInput(): Cypress.Chainable {
26
- return cy.get("#role");
26
+ return cy.get('#role');
27
27
  },
28
28
 
29
29
  emailInput(): Cypress.Chainable {
30
- return cy.get("#email");
30
+ return cy.get('#email');
31
31
  },
32
32
 
33
33
  submitButton(): Cypress.Chainable {
34
- return cy.contains("button", "Add person");
34
+ return cy.contains('button', 'Add person');
35
35
  },
36
36
 
37
37
  personRow(personId: string): Cypress.Chainable {
@@ -39,15 +39,15 @@ export const peoplePage = {
39
39
  },
40
40
 
41
41
  nameCell(personId: string): Cypress.Chainable {
42
- return this.personRow(personId).find("td").eq(0);
42
+ return this.personRow(personId).find('td').eq(0);
43
43
  },
44
44
 
45
45
  roleCell(personId: string): Cypress.Chainable {
46
- return this.personRow(personId).find("td").eq(1);
46
+ return this.personRow(personId).find('td').eq(1);
47
47
  },
48
48
 
49
49
  emailCell(personId: string): Cypress.Chainable {
50
- return this.personRow(personId).find("td").eq(2);
50
+ return this.personRow(personId).find('td').eq(2);
51
51
  },
52
52
 
53
53
  addPerson(person: PersonRecord): void {