@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,241 @@
1
+ # WebdriverIO Template
2
+
3
+ This is a WebdriverIO + TypeScript automation framework template for UI tests.
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
+ - [Template upgrades](#template-upgrades)
17
+ - [CI](#ci)
18
+
19
+ ## Feature set
20
+
21
+ - WebdriverIO + TypeScript setup
22
+ - Mocha-based specs with page objects and step logging
23
+ - generic data factory pattern with `DataFactory`
24
+ - folder-level `README.md` guides and file-header comments for easier onboarding
25
+ - multi-environment runtime config with `dev`, `staging`, and `prod`
26
+ - env-based secret resolution with a replaceable `SecretProvider`
27
+ - WebdriverIO spec reporter by default
28
+ - optional Allure single-file report
29
+ - screenshots and structured logs for debugging
30
+ - ESLint rules that protect framework conventions
31
+ - GitHub Actions workflow for the template
32
+
33
+ ## How it works
34
+
35
+ - specs in `tests/` own the workflow and assertions
36
+ - page objects in `pages/` own locators and browser actions
37
+ - runtime config is loaded from `config/runtime-config.ts`
38
+ - application URLs and credentials are resolved from `TEST_ENV`
39
+ - the bundled demo app auto-starts during `npm test` in local `dev` mode when the default local URL is in use
40
+ - reports and artifacts are written under `reports/`, `allure-results/`, and `test-results/`
41
+
42
+ ## Project structure
43
+
44
+ ```text
45
+ wdio-template
46
+ ├── tests
47
+ ├── pages
48
+ ├── components
49
+ ├── data
50
+ ├── config
51
+ ├── reporters
52
+ ├── utils
53
+ ├── lint
54
+ ├── scripts
55
+ ├── wdio.conf.ts
56
+ └── package.json
57
+ ```
58
+
59
+ ## Quick start
60
+
61
+ 1. Install dependencies.
62
+
63
+ ```bash
64
+ npm install
65
+ ```
66
+
67
+ 2. Run tests.
68
+
69
+ ```bash
70
+ npm test
71
+ ```
72
+
73
+ In local `dev`, the template starts its bundled demo app automatically before the tests run.
74
+
75
+ If you want to run the demo app manually for debugging:
76
+
77
+ ```bash
78
+ npm run demo:ui
79
+ ```
80
+
81
+ Default local values:
82
+
83
+ - UI base URL: `http://127.0.0.1:3000`
84
+ - credentials: generated into local `.env` on first run
85
+
86
+ ## Environment and secrets
87
+
88
+ The template supports:
89
+
90
+ - `TEST_ENV=dev`
91
+ - `TEST_ENV=staging`
92
+ - `TEST_ENV=prod`
93
+
94
+ Runtime values are resolved in this order:
95
+
96
+ 1. environment-specific variables such as `DEV_UI_BASE_URL`
97
+ 2. generic variables such as `UI_BASE_URL`
98
+ 3. built-in defaults from `config/environments.ts`
99
+
100
+ The same pattern is used for credentials:
101
+
102
+ 1. `DEV_APP_USERNAME` or `DEV_APP_PASSWORD`
103
+ 2. `APP_USERNAME` or `APP_PASSWORD`
104
+ 3. built-in empty defaults for the selected environment
105
+
106
+ For local overrides, copy:
107
+
108
+ ```bash
109
+ .env.example
110
+ ```
111
+
112
+ to:
113
+
114
+ ```bash
115
+ .env
116
+ ```
117
+
118
+ The template loads:
119
+
120
+ - `.env`
121
+ - `.env.<TEST_ENV>`
122
+
123
+ On the first local run, the template also creates a `.env` file with random demo credentials if one does not already exist.
124
+
125
+ Example:
126
+
127
+ ```bash
128
+ TEST_ENV=staging \
129
+ STAGING_UI_BASE_URL=https://staging-ui.example.internal \
130
+ STAGING_APP_USERNAME=my-user \
131
+ STAGING_APP_PASSWORD=my-password \
132
+ npm test
133
+ ```
134
+
135
+ If you want to disable the bundled local demo app even in `dev`, use:
136
+
137
+ ```bash
138
+ WDIO_DISABLE_LOCAL_DEMO_APP=true npm test
139
+ ```
140
+
141
+ If your team uses a real secret system later, replace the implementation behind `config/secret-manager.ts`.
142
+
143
+ ## Main commands
144
+
145
+ ```bash
146
+ npm test
147
+ npm run test:smoke
148
+ npm run test:regression
149
+ npm run test:critical
150
+ npm run demo:ui
151
+ npm run lint
152
+ npm run typecheck
153
+ npm run report:allure
154
+ ```
155
+
156
+ ## Reports and artifacts
157
+
158
+ Optional Allure report:
159
+
160
+ ```bash
161
+ npm run report:allure
162
+ ```
163
+
164
+ Outputs:
165
+
166
+ - Allure single file: `reports/allure/index.html`
167
+ - structured event log: `reports/logs/wdio-events.jsonl`
168
+ - raw Allure results: `allure-results`
169
+ - failure screenshots: `test-results`
170
+
171
+ If you only want WebdriverIO's built-in terminal reporting, remove the `allure` reporter entry in `wdio.conf.ts`.
172
+
173
+ ## Add a new test
174
+
175
+ Create specs under `tests/`.
176
+
177
+ Keep the pattern simple:
178
+
179
+ - create data with `DataFactory`
180
+ - interact through page objects
181
+ - assert in the spec
182
+
183
+ Example shape:
184
+
185
+ ```ts
186
+ describe('do something @smoke', () => {
187
+ it('uses page objects and data factories', async () => {
188
+ // use page objects here
189
+ });
190
+ });
191
+ ```
192
+
193
+ ## Extend the framework
194
+
195
+ Common extension points:
196
+
197
+ - update or replace the bundled demo app under `demo-apps/`
198
+ - add page objects under `pages/`
199
+ - add reusable UI pieces under `components/`
200
+ - add more generic builders under `data/factories/`
201
+ - add stronger custom lint rules in `lint/architecture-plugin.cjs`
202
+ - add custom reporters under `reporters/`
203
+
204
+ Recommended rules:
205
+
206
+ - keep selectors in page objects
207
+ - keep assertions in spec files
208
+ - prefer stable selectors such as labels, button text, and `data-testid`
209
+ - keep the data layer generic until the project really needs domain-specific factories
210
+
211
+ ## Template upgrades
212
+
213
+ This project includes a `.qa-patterns.json` metadata file so future CLI versions can compare the current project against the managed template baseline.
214
+
215
+ Check for available safe updates:
216
+
217
+ ```bash
218
+ npx -y @toolstackhq/create-qa-patterns upgrade check .
219
+ ```
220
+
221
+ Apply only safe managed-file updates:
222
+
223
+ ```bash
224
+ npx -y @toolstackhq/create-qa-patterns upgrade apply --safe .
225
+ ```
226
+
227
+ 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.
228
+
229
+ ## CI
230
+
231
+ The CI entrypoint is:
232
+
233
+ ```bash
234
+ scripts/run-tests.sh
235
+ ```
236
+
237
+ The bundled workflow lives in:
238
+
239
+ ```bash
240
+ .github/workflows/wdio-tests.yml
241
+ ```
@@ -0,0 +1,10 @@
1
+ export default {
2
+ name: 'qa-patterns Playwright Template',
3
+ plugins: {
4
+ awesome: {
5
+ options: {
6
+ singleFile: true
7
+ }
8
+ }
9
+ }
10
+ };
@@ -0,0 +1,5 @@
1
+ # Components
2
+
3
+ This folder holds reusable UI fragments shared by multiple pages.
4
+
5
+ - Use components for repeated widgets such as flash messages or shared panels.
@@ -0,0 +1,16 @@
1
+ import type { Locator, Page } from '@playwright/test';
2
+
3
+ export class FlashMessage {
4
+ private readonly message: Locator;
5
+
6
+ constructor(page: Page) {
7
+ this.message = page.getByTestId('flash-message');
8
+ }
9
+
10
+ async getText(): Promise<string | null> {
11
+ if (!(await this.message.count())) {
12
+ return null;
13
+ }
14
+ return this.message.textContent();
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ # Config
2
+
3
+ This folder resolves environment-specific runtime values.
4
+
5
+ - Keep environment defaults, secret lookup, and runtime parsing here.
@@ -0,0 +1,40 @@
1
+ // Defines the built-in environment defaults used when env vars are not provided.
2
+ import type { TestEnvironment } from './test-env';
3
+
4
+ type EnvironmentDefaults = {
5
+ uiBaseUrl: string;
6
+ credentials: {
7
+ username: string;
8
+ password: string;
9
+ };
10
+ };
11
+
12
+ const DEFAULTS: Record<TestEnvironment, EnvironmentDefaults> = {
13
+ dev: {
14
+ uiBaseUrl: 'http://127.0.0.1:3000',
15
+ credentials: {
16
+ username: '',
17
+ password: ''
18
+ }
19
+ },
20
+ staging: {
21
+ uiBaseUrl: 'https://staging-ui.example.internal',
22
+ credentials: {
23
+ username: '',
24
+ password: ''
25
+ }
26
+ },
27
+ prod: {
28
+ uiBaseUrl: 'https://ui.example.internal',
29
+ credentials: {
30
+ username: '',
31
+ password: ''
32
+ }
33
+ }
34
+ };
35
+
36
+ export function getEnvironmentDefaults(
37
+ testEnv: TestEnvironment
38
+ ): EnvironmentDefaults {
39
+ return DEFAULTS[testEnv];
40
+ }
@@ -0,0 +1,53 @@
1
+ // Builds the runtime configuration object that tests and fixtures consume.
2
+ import path from 'node:path';
3
+
4
+ import dotenv from 'dotenv';
5
+ import { z } from 'zod';
6
+
7
+ import { getEnvironmentDefaults } from './environments';
8
+ import { EnvSecretProvider, SecretManager } from './secret-manager';
9
+ import { loadTestEnvironment } from './test-env';
10
+
11
+ const environment = loadTestEnvironment();
12
+
13
+ dotenv.config({ path: path.resolve(process.cwd(), '.env') });
14
+ dotenv.config({
15
+ path: path.resolve(process.cwd(), `.env.${environment}`),
16
+ override: true
17
+ });
18
+
19
+ const runtimeConfigSchema = z.object({
20
+ testEnv: z.enum(['dev', 'staging', 'prod']),
21
+ testRunId: z.string().min(1),
22
+ uiBaseUrl: z.string().url(),
23
+ credentials: z.object({
24
+ username: z.string().min(1),
25
+ password: z.string().min(1)
26
+ })
27
+ });
28
+
29
+ export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
30
+
31
+ export function loadRuntimeConfig(): RuntimeConfig {
32
+ const defaults = getEnvironmentDefaults(environment);
33
+ const secretManager = new SecretManager(new EnvSecretProvider());
34
+
35
+ const uiBaseUrl =
36
+ process.env[`${environment.toUpperCase()}_UI_BASE_URL`] ??
37
+ process.env.UI_BASE_URL ??
38
+ defaults.uiBaseUrl;
39
+
40
+ return runtimeConfigSchema.parse({
41
+ testEnv: environment,
42
+ testRunId: process.env.TEST_RUN_ID ?? 'local',
43
+ uiBaseUrl,
44
+ credentials: {
45
+ username:
46
+ secretManager.getOptionalSecret('APP_USERNAME', environment) ??
47
+ defaults.credentials.username,
48
+ password:
49
+ secretManager.getOptionalSecret('APP_PASSWORD', environment) ??
50
+ defaults.credentials.password
51
+ }
52
+ });
53
+ }
@@ -0,0 +1,29 @@
1
+ // Minimal secret abstraction so env-based secrets can later be replaced cleanly.
2
+ import type { TestEnvironment } from './test-env';
3
+
4
+ export interface SecretProvider {
5
+ getSecret(key: string, testEnv: TestEnvironment): string | undefined;
6
+ }
7
+
8
+ export class EnvSecretProvider implements SecretProvider {
9
+ getSecret(key: string, testEnv: TestEnvironment): string | undefined {
10
+ const envPrefix = testEnv.toUpperCase();
11
+ return process.env[`${envPrefix}_${key}`] ?? process.env[key];
12
+ }
13
+ }
14
+
15
+ export class SecretManager {
16
+ constructor(private readonly provider: SecretProvider) {}
17
+
18
+ getRequiredSecret(key: string, testEnv: TestEnvironment): string {
19
+ const value = this.provider.getSecret(key, testEnv);
20
+ if (!value) {
21
+ throw new Error(`Missing secret "${key}" for TEST_ENV=${testEnv}`);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ getOptionalSecret(key: string, testEnv: TestEnvironment): string | undefined {
27
+ return this.provider.getSecret(key, testEnv);
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+
3
+ export const testEnvironmentSchema = z.enum(['dev', 'staging', 'prod']);
4
+
5
+ export type TestEnvironment = z.infer<typeof testEnvironmentSchema>;
6
+
7
+ export function loadTestEnvironment(): TestEnvironment {
8
+ return testEnvironmentSchema.parse(process.env.TEST_ENV ?? 'dev');
9
+ }
@@ -0,0 +1,9 @@
1
+ # Data Layer
2
+
3
+ Keep the starter data layer generic.
4
+
5
+ - Start with `DataFactory.person()` or similarly small builders.
6
+ - Add domain-specific factories only when the project actually needs them.
7
+ - Introduce stricter validation later only if your team actually needs it.
8
+
9
+ The template should feel approachable before it feels comprehensive.
@@ -0,0 +1,6 @@
1
+ # Factories
2
+
3
+ This folder exposes small, readable builders for test data.
4
+
5
+ - Start generic.
6
+ - Add domain-specific builders only when the project needs them.
@@ -0,0 +1,36 @@
1
+ // Generic data builders that keep tests readable and deterministic.
2
+ import { createSeededFaker } from '../generators/seeded-faker';
3
+ import { IdGenerator } from '../generators/id-generator';
4
+
5
+ export type PersonRecord = {
6
+ personId: string;
7
+ name: string;
8
+ role: string;
9
+ email: string;
10
+ };
11
+
12
+ export class DataFactory {
13
+ private readonly idGenerator: IdGenerator;
14
+
15
+ constructor(private readonly testRunId: string) {
16
+ this.idGenerator = new IdGenerator(testRunId);
17
+ }
18
+
19
+ person(overrides?: Partial<PersonRecord>): PersonRecord {
20
+ const personId = overrides?.personId ?? this.idGenerator.next('person');
21
+ const seededFaker = createSeededFaker(`${this.testRunId}:${personId}`);
22
+ const firstName = seededFaker.person.firstName();
23
+ const lastName = seededFaker.person.lastName();
24
+ const name = `${firstName} ${lastName}`;
25
+
26
+ return {
27
+ personId,
28
+ name,
29
+ role: overrides?.role ?? seededFaker.person.jobTitle(),
30
+ email:
31
+ overrides?.email ??
32
+ `${firstName}.${lastName}.${personId}@example.test`.toLowerCase(),
33
+ ...overrides
34
+ };
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ # Generators
2
+
3
+ This folder contains low-level helpers that factories build on.
4
+
5
+ - Keep seeded randomness and id generation here.
@@ -0,0 +1,18 @@
1
+ // Deterministic id generator for repeatable local and CI runs.
2
+ export class IdGenerator {
3
+ private readonly counters = new Map<string, number>();
4
+
5
+ constructor(private readonly runId: string) {}
6
+
7
+ next(prefix: string): string {
8
+ const counter = (this.counters.get(prefix) ?? 0) + 1;
9
+ this.counters.set(prefix, counter);
10
+ return `${prefix}-${this.runId}-${String(counter).padStart(4, '0')}`;
11
+ }
12
+
13
+ nextSequence(prefix: string): number {
14
+ const counter = (this.counters.get(prefix) ?? 0) + 1;
15
+ this.counters.set(prefix, counter);
16
+ return counter;
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ // Seeded faker wrapper so generated values stay stable for a given run id.
2
+ import { Faker, en } from '@faker-js/faker';
3
+
4
+ function hashSeed(value: string): number {
5
+ return value.split('').reduce((seed, character) => {
6
+ return ((seed << 5) - seed + character.charCodeAt(0)) | 0;
7
+ }, 0);
8
+ }
9
+
10
+ export function createSeededFaker(seedInput: string): Faker {
11
+ const instance = new Faker({ locale: [en] });
12
+ instance.seed(Math.abs(hashSeed(seedInput)));
13
+ return instance;
14
+ }
@@ -0,0 +1,120 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
4
+ background: linear-gradient(180deg, #f5f7fb 0%, #eef2f7 100%);
5
+ color: #102038;
6
+ }
7
+
8
+ .layout {
9
+ max-width: 1080px;
10
+ margin: 0 auto;
11
+ padding: 32px 24px 48px;
12
+ }
13
+
14
+ .header {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ gap: 16px;
19
+ margin-bottom: 24px;
20
+ }
21
+
22
+ .brand {
23
+ font-size: 1.5rem;
24
+ font-weight: 700;
25
+ }
26
+
27
+ .nav {
28
+ display: flex;
29
+ gap: 12px;
30
+ flex-wrap: wrap;
31
+ }
32
+
33
+ .nav a {
34
+ text-decoration: none;
35
+ color: #102038;
36
+ background: #d8e6ff;
37
+ padding: 10px 14px;
38
+ border-radius: 999px;
39
+ }
40
+
41
+ .card-grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
44
+ gap: 16px;
45
+ margin-bottom: 24px;
46
+ }
47
+
48
+ .card,
49
+ .panel {
50
+ background: #ffffff;
51
+ border-radius: 18px;
52
+ padding: 20px;
53
+ box-shadow: 0 20px 40px rgba(16, 32, 56, 0.08);
54
+ }
55
+
56
+ .panel-grid {
57
+ display: grid;
58
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
59
+ gap: 24px;
60
+ }
61
+
62
+ form {
63
+ display: grid;
64
+ gap: 14px;
65
+ }
66
+
67
+ label {
68
+ display: grid;
69
+ gap: 6px;
70
+ font-weight: 600;
71
+ }
72
+
73
+ input,
74
+ select,
75
+ button {
76
+ font: inherit;
77
+ padding: 10px 12px;
78
+ border-radius: 12px;
79
+ border: 1px solid #c4d3eb;
80
+ }
81
+
82
+ button {
83
+ background: #174ea6;
84
+ color: #ffffff;
85
+ border: none;
86
+ font-weight: 700;
87
+ cursor: pointer;
88
+ }
89
+
90
+ table {
91
+ width: 100%;
92
+ border-collapse: collapse;
93
+ }
94
+
95
+ th,
96
+ td {
97
+ text-align: left;
98
+ padding: 12px 8px;
99
+ border-bottom: 1px solid #e1e9f5;
100
+ }
101
+
102
+ .flash-message {
103
+ background: #dff6e7;
104
+ border: 1px solid #9dd5ae;
105
+ color: #174f2c;
106
+ padding: 12px 14px;
107
+ border-radius: 14px;
108
+ margin-bottom: 16px;
109
+ }
110
+
111
+ .login-shell {
112
+ min-height: 100vh;
113
+ display: grid;
114
+ place-items: center;
115
+ padding: 24px;
116
+ }
117
+
118
+ .login-card {
119
+ width: min(420px, 100%);
120
+ }