@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
@@ -1,25 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Starts the local demo app when needed, then launches Cypress in run or open mode.
4
- import process from "node:process";
5
- import path from "node:path";
6
- import { spawn } from "node:child_process";
4
+ import process from 'node:process';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
7
 
8
- import dotenv from "dotenv";
8
+ import dotenv from 'dotenv';
9
9
 
10
- const mode = process.argv[2] ?? "run";
10
+ const mode = process.argv[2] ?? 'run';
11
11
  const args = process.argv.slice(3);
12
12
  const cwd = process.cwd();
13
- const healthUrl = "http://127.0.0.1:3000/health";
14
- const environment = process.env.TEST_ENV ?? "dev";
13
+ const healthUrl = 'http://127.0.0.1:3000/health';
14
+ const environment = process.env.TEST_ENV ?? 'dev';
15
15
  const environmentDefaults = {
16
- dev: "http://127.0.0.1:3000",
17
- staging: "https://staging-ui.example.internal",
18
- prod: "https://ui.example.internal"
16
+ dev: 'http://127.0.0.1:3000',
17
+ staging: 'https://staging-ui.example.internal',
18
+ prod: 'https://ui.example.internal'
19
19
  };
20
20
 
21
- dotenv.config({ path: path.resolve(cwd, ".env") });
22
- dotenv.config({ path: path.resolve(cwd, `.env.${environment}`), override: true });
21
+ dotenv.config({ path: path.resolve(cwd, '.env') });
22
+ dotenv.config({
23
+ path: path.resolve(cwd, `.env.${environment}`),
24
+ override: true
25
+ });
23
26
 
24
27
  const uiBaseUrl =
25
28
  process.env[`${environment.toUpperCase()}_UI_BASE_URL`] ??
@@ -28,18 +31,18 @@ const uiBaseUrl =
28
31
  environmentDefaults.dev;
29
32
 
30
33
  const shouldAutoStartDemoApp =
31
- environment === "dev" &&
34
+ environment === 'dev' &&
32
35
  uiBaseUrl === environmentDefaults.dev &&
33
- process.env.CY_DISABLE_LOCAL_DEMO_APP !== "true";
36
+ process.env.CY_DISABLE_LOCAL_DEMO_APP !== 'true';
34
37
 
35
38
  function getCommandName(command) {
36
- return process.platform === "win32" ? `${command}.cmd` : command;
39
+ return process.platform === 'win32' ? `${command}.cmd` : command;
37
40
  }
38
41
 
39
42
  function spawnCommand(command, commandArgs, options = {}) {
40
43
  return spawn(getCommandName(command), commandArgs, {
41
44
  cwd,
42
- stdio: "inherit",
45
+ stdio: 'inherit',
43
46
  ...options
44
47
  });
45
48
  }
@@ -68,7 +71,7 @@ function killChild(child) {
68
71
  return;
69
72
  }
70
73
 
71
- child.kill("SIGTERM");
74
+ child.kill('SIGTERM');
72
75
  }
73
76
 
74
77
  async function run() {
@@ -76,16 +79,20 @@ async function run() {
76
79
 
77
80
  try {
78
81
  if (shouldAutoStartDemoApp) {
79
- demoAppProcess = spawnCommand("npm", ["run", "demo:ui"]);
82
+ demoAppProcess = spawnCommand('npm', ['run', 'demo:ui']);
80
83
  await waitForHealthcheck(healthUrl);
81
84
  }
82
85
 
83
- const cypressCommand = mode === "open" ? "open" : "run";
84
- const cypressProcess = spawnCommand("npx", ["cypress", cypressCommand, ...args]);
86
+ const cypressCommand = mode === 'open' ? 'open' : 'run';
87
+ const cypressProcess = spawnCommand('npx', [
88
+ 'cypress',
89
+ cypressCommand,
90
+ ...args
91
+ ]);
85
92
 
86
93
  const exitCode = await new Promise((resolve) => {
87
- cypressProcess.on("close", resolve);
88
- cypressProcess.on("error", () => resolve(1));
94
+ cypressProcess.on('close', resolve);
95
+ cypressProcess.on('error', () => resolve(1));
89
96
  });
90
97
 
91
98
  if (exitCode !== 0) {
@@ -96,11 +103,13 @@ async function run() {
96
103
  }
97
104
  }
98
105
 
99
- for (const signal of ["SIGINT", "SIGTERM"]) {
106
+ for (const signal of ['SIGINT', 'SIGTERM']) {
100
107
  process.on(signal, () => process.exit(1));
101
108
  }
102
109
 
103
110
  run().catch((error) => {
104
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
111
+ process.stderr.write(
112
+ `${error instanceof Error ? error.message : String(error)}\n`
113
+ );
105
114
  process.exit(1);
106
115
  });
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
+ node ./scripts/ensure-local-env.mjs
4
5
  npm run lint
5
6
  npm run typecheck
6
7
  npx cypress run "$@"
@@ -11,5 +11,11 @@
11
11
  "noEmit": true,
12
12
  "types": ["cypress", "node"]
13
13
  },
14
- "include": ["cypress/**/*.ts", "cypress/**/*.d.ts", "config/**/*.ts", "cypress.config.ts", "allurerc.mjs"]
14
+ "include": [
15
+ "cypress/**/*.ts",
16
+ "cypress/**/*.d.ts",
17
+ "config/**/*.ts",
18
+ "cypress.config.ts",
19
+ "allurerc.mjs"
20
+ ]
15
21
  }
@@ -3,15 +3,15 @@ TEST_RUN_ID=local
3
3
 
4
4
  DEV_UI_BASE_URL=http://127.0.0.1:3000
5
5
  DEV_API_BASE_URL=http://127.0.0.1:3001
6
- DEV_APP_USERNAME=tester
7
- DEV_APP_PASSWORD=Password123!
6
+ DEV_APP_USERNAME=generate-a-local-username
7
+ DEV_APP_PASSWORD=generate-a-local-password
8
8
 
9
9
  STAGING_UI_BASE_URL=https://staging-ui.example.internal
10
10
  STAGING_API_BASE_URL=https://staging-api.example.internal
11
- STAGING_APP_USERNAME=staging-user
12
- STAGING_APP_PASSWORD=replace-me
11
+ STAGING_APP_USERNAME=your-staging-username
12
+ STAGING_APP_PASSWORD=your-staging-password
13
13
 
14
14
  PROD_UI_BASE_URL=https://ui.example.internal
15
15
  PROD_API_BASE_URL=https://api.example.internal
16
- PROD_APP_USERNAME=prod-user
17
- PROD_APP_PASSWORD=replace-me
16
+ PROD_APP_USERNAME=your-prod-username
17
+ PROD_APP_PASSWORD=your-prod-password
@@ -4,9 +4,9 @@ on:
4
4
  workflow_dispatch:
5
5
  push:
6
6
  paths:
7
- - "templates/playwright-template/**"
8
- - "test-apps/**"
9
- - "package.json"
7
+ - 'templates/playwright-template/**'
8
+ - 'test-apps/**'
9
+ - 'package.json'
10
10
 
11
11
  jobs:
12
12
  playwright:
@@ -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 workspace dependencies
23
23
  run: npm ci
@@ -50,13 +50,18 @@ jobs:
50
50
 
51
51
  playwright-docker:
52
52
  runs-on: ubuntu-latest
53
+ env:
54
+ DEV_APP_USERNAME: ci-docker-user
55
+ DEV_APP_PASSWORD: ci-docker-password-A1!
56
+ UI_DEMO_USERNAME: ci-docker-user
57
+ UI_DEMO_PASSWORD: ci-docker-password-A1!
53
58
 
54
59
  steps:
55
60
  - uses: actions/checkout@v4
56
61
 
57
62
  - uses: actions/setup-node@v4
58
63
  with:
59
- node-version: "20"
64
+ node-version: '20'
60
65
 
61
66
  - name: Install workspace dependencies
62
67
  run: npm ci
@@ -91,6 +96,10 @@ jobs:
91
96
  -e TEST_RUN_ID=ci-docker \
92
97
  -e DEV_UI_BASE_URL=http://host.docker.internal:3000 \
93
98
  -e DEV_API_BASE_URL=http://host.docker.internal:3001 \
99
+ -e DEV_APP_USERNAME=${DEV_APP_USERNAME} \
100
+ -e DEV_APP_PASSWORD=${DEV_APP_PASSWORD} \
101
+ -e UI_DEMO_USERNAME=${UI_DEMO_USERNAME} \
102
+ -e UI_DEMO_PASSWORD=${UI_DEMO_PASSWORD} \
94
103
  -v "${GITHUB_WORKSPACE}/templates/playwright-template/reports:/workspace/reports" \
95
104
  -v "${GITHUB_WORKSPACE}/templates/playwright-template/allure-results:/workspace/allure-results" \
96
105
  -v "${GITHUB_WORKSPACE}/templates/playwright-template/test-results:/workspace/test-results" \
@@ -13,6 +13,7 @@ This is a Playwright + TypeScript automation framework template for UI and API t
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 and Docker](#ci-and-docker)
17
18
 
18
19
  ## Feature set
@@ -88,8 +89,7 @@ Default local values:
88
89
 
89
90
  - UI base URL: `http://127.0.0.1:3000`
90
91
  - API base URL: `http://127.0.0.1:3001`
91
- - username: `tester`
92
- - password: `Password123!`
92
+ - credentials: generated into local `.env` on first run
93
93
 
94
94
  ## Environment and secrets
95
95
 
@@ -109,7 +109,7 @@ The same pattern is used for credentials:
109
109
 
110
110
  1. `DEV_APP_USERNAME` or `DEV_APP_PASSWORD`
111
111
  2. `APP_USERNAME` or `APP_PASSWORD`
112
- 3. built-in defaults for the selected environment
112
+ 3. built-in empty defaults for the selected environment
113
113
 
114
114
  For local overrides, copy:
115
115
 
@@ -128,6 +128,8 @@ The template loads:
128
128
  - `.env`
129
129
  - `.env.<TEST_ENV>`
130
130
 
131
+ On the first local run, the template also creates a `.env` file with random demo credentials if one does not already exist.
132
+
131
133
  Example:
132
134
 
133
135
  ```bash
@@ -191,7 +193,7 @@ If you only want Playwright reporting, remove the `allure-playwright` reporter e
191
193
  Create tests under `tests/` and import the shared fixtures:
192
194
 
193
195
  ```ts
194
- import { expect, test } from "../fixtures/test-fixtures";
196
+ import { expect, test } from '../fixtures/test-fixtures';
195
197
  ```
196
198
 
197
199
  Keep the pattern simple:
@@ -203,7 +205,7 @@ Keep the pattern simple:
203
205
  Example shape:
204
206
 
205
207
  ```ts
206
- test("do something @smoke", async ({ dataFactory, loginPage }) => {
208
+ test('do something @smoke', async ({ dataFactory, loginPage }) => {
207
209
  const person = dataFactory.person();
208
210
  // use page objects here
209
211
  });
@@ -228,6 +230,24 @@ Recommended rules:
228
230
  - prefer semantic selectors such as `getByRole`, `getByLabel`, and `data-testid`
229
231
  - keep the data layer generic until the project really needs domain-specific factories
230
232
 
233
+ ## Template upgrades
234
+
235
+ This project includes a `.qa-patterns.json` metadata file so future CLI versions can compare the current project against the managed template baseline.
236
+
237
+ Check for available safe updates:
238
+
239
+ ```bash
240
+ npx -y @toolstackhq/create-qa-patterns upgrade check .
241
+ ```
242
+
243
+ Apply only safe managed-file updates:
244
+
245
+ ```bash
246
+ npx -y @toolstackhq/create-qa-patterns upgrade apply --safe .
247
+ ```
248
+
249
+ 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.
250
+
231
251
  ## CI and Docker
232
252
 
233
253
  The CI entrypoint is:
@@ -1,5 +1,5 @@
1
1
  export default {
2
- name: "qa-patterns Playwright Template",
2
+ name: 'qa-patterns Playwright Template',
3
3
  plugins: {
4
4
  awesome: {
5
5
  options: {
@@ -1,10 +1,10 @@
1
- import type { Locator, Page } from "@playwright/test";
1
+ import type { Locator, Page } from '@playwright/test';
2
2
 
3
3
  export class FlashMessage {
4
4
  private readonly message: Locator;
5
5
 
6
6
  constructor(page: Page) {
7
- this.message = page.getByTestId("flash-message");
7
+ this.message = page.getByTestId('flash-message');
8
8
  }
9
9
 
10
10
  async getText(): Promise<string | null> {
@@ -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;
@@ -12,31 +12,33 @@ type EnvironmentDefaults = {
12
12
 
13
13
  const DEFAULTS: Record<TestEnvironment, EnvironmentDefaults> = {
14
14
  dev: {
15
- uiBaseUrl: "http://127.0.0.1:3000",
16
- apiBaseUrl: "http://127.0.0.1:3001",
15
+ uiBaseUrl: 'http://127.0.0.1:3000',
16
+ apiBaseUrl: 'http://127.0.0.1:3001',
17
17
  credentials: {
18
- username: "tester",
19
- password: "Password123!"
18
+ username: '',
19
+ password: ''
20
20
  }
21
21
  },
22
22
  staging: {
23
- uiBaseUrl: "https://staging-ui.example.internal",
24
- apiBaseUrl: "https://staging-api.example.internal",
23
+ uiBaseUrl: 'https://staging-ui.example.internal',
24
+ apiBaseUrl: 'https://staging-api.example.internal',
25
25
  credentials: {
26
- username: "staging-user",
27
- password: "replace-me"
26
+ username: '',
27
+ password: ''
28
28
  }
29
29
  },
30
30
  prod: {
31
- uiBaseUrl: "https://ui.example.internal",
32
- apiBaseUrl: "https://api.example.internal",
31
+ uiBaseUrl: 'https://ui.example.internal',
32
+ apiBaseUrl: 'https://api.example.internal',
33
33
  credentials: {
34
- username: "prod-user",
35
- password: "replace-me"
34
+ username: '',
35
+ password: ''
36
36
  }
37
37
  }
38
38
  };
39
39
 
40
- export function getEnvironmentDefaults(testEnv: TestEnvironment): EnvironmentDefaults {
40
+ export function getEnvironmentDefaults(
41
+ testEnv: TestEnvironment
42
+ ): EnvironmentDefaults {
41
43
  return DEFAULTS[testEnv];
42
44
  }
@@ -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
  apiBaseUrl: z.string().url(),
@@ -41,14 +44,16 @@ export function loadRuntimeConfig(): RuntimeConfig {
41
44
 
42
45
  return runtimeConfigSchema.parse({
43
46
  testEnv: environment,
44
- testRunId: process.env.TEST_RUN_ID ?? "local",
47
+ testRunId: process.env.TEST_RUN_ID ?? 'local',
45
48
  uiBaseUrl,
46
49
  apiBaseUrl,
47
50
  credentials: {
48
51
  username:
49
- secretManager.getOptionalSecret("APP_USERNAME", environment) ?? defaults.credentials.username,
52
+ secretManager.getOptionalSecret('APP_USERNAME', environment) ??
53
+ defaults.credentials.username,
50
54
  password:
51
- secretManager.getOptionalSecret("APP_PASSWORD", environment) ?? defaults.credentials.password
55
+ secretManager.getOptionalSecret('APP_PASSWORD', environment) ??
56
+ defaults.credentials.password
52
57
  }
53
58
  });
54
59
  }
@@ -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,6 +1,6 @@
1
1
  // Generic data builders that keep tests readable and deterministic.
2
- import { createSeededFaker } from "../generators/seeded-faker";
3
- import { IdGenerator } from "../generators/id-generator";
2
+ import { createSeededFaker } from '../generators/seeded-faker';
3
+ import { IdGenerator } from '../generators/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,25 +1,25 @@
1
- const express = require("express");
1
+ const express = require('express');
2
2
 
3
- const { createPerson, state } = require("./store");
3
+ const { createPerson, state } = require('./store');
4
4
 
5
5
  const app = express();
6
- const host = process.env.HOST || "0.0.0.0";
7
- const port = Number(process.env.PORT || "3001");
6
+ const host = process.env.HOST || '0.0.0.0';
7
+ const port = Number(process.env.PORT || '3001');
8
8
 
9
9
  app.use(express.json());
10
10
 
11
- app.get("/health", (_request, response) => {
12
- response.json({ status: "ok" });
11
+ app.get('/health', (_request, response) => {
12
+ response.json({ status: 'ok' });
13
13
  });
14
14
 
15
- app.get("/people", (_request, response) => {
15
+ app.get('/people', (_request, response) => {
16
16
  response.json(state.people);
17
17
  });
18
18
 
19
- app.post("/people", (request, response) => {
19
+ app.post('/people', (request, response) => {
20
20
  const { name, role, email } = request.body;
21
21
  if (!name || !role || !email) {
22
- response.status(400).json({ error: "name, role, and email are required" });
22
+ response.status(400).json({ error: 'name, role, and email are required' });
23
23
  return;
24
24
  }
25
25
 
@@ -7,7 +7,7 @@ const state = {
7
7
 
8
8
  function nextId() {
9
9
  state.counters.person += 1;
10
- return `person-${String(state.counters.person).padStart(4, "0")}`;
10
+ return `person-${String(state.counters.person).padStart(4, '0')}`;
11
11
  }
12
12
 
13
13
  function getPerson(personId) {
@@ -1,6 +1,6 @@
1
1
  body {
2
2
  margin: 0;
3
- font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
3
+ font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
4
4
  background: linear-gradient(180deg, #f5f7fb 0%, #eef2f7 100%);
5
5
  color: #102038;
6
6
  }