@toolstackhq/create-qa-patterns 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/index.js +116 -23
- package/package.json +1 -1
- package/templates/cypress-template/README.md +13 -0
- package/templates/cypress-template/allurerc.mjs +10 -0
- package/templates/cypress-template/config/README.md +5 -0
- package/templates/cypress-template/config/environments.ts +6 -5
- package/templates/cypress-template/config/runtime-config.ts +2 -0
- package/templates/cypress-template/config/secret-manager.ts +16 -6
- package/templates/cypress-template/config/test-env.ts +5 -9
- package/templates/cypress-template/cypress/e2e/README.md +6 -0
- package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +1 -0
- package/templates/cypress-template/cypress/support/README.md +5 -0
- package/templates/cypress-template/cypress/support/app-config.ts +1 -0
- package/templates/cypress-template/cypress/support/commands.ts +1 -0
- package/templates/cypress-template/cypress/support/data/README.md +5 -0
- package/templates/cypress-template/cypress/support/data/data-factory.ts +2 -1
- package/templates/cypress-template/cypress/support/data/id-generator.ts +10 -5
- package/templates/cypress-template/cypress/support/data/seeded-faker.ts +9 -6
- package/templates/cypress-template/cypress/support/e2e.ts +1 -0
- package/templates/cypress-template/cypress/support/pages/README.md +5 -0
- package/templates/cypress-template/cypress/support/pages/login-page.ts +1 -0
- package/templates/cypress-template/cypress/support/pages/people-page.ts +1 -0
- package/templates/cypress-template/cypress.config.ts +17 -1
- package/templates/cypress-template/eslint.config.mjs +1 -1
- package/templates/cypress-template/package-lock.json +2857 -109
- package/templates/cypress-template/package.json +4 -0
- package/templates/cypress-template/scripts/README.md +5 -0
- package/templates/cypress-template/scripts/generate-allure-report.mjs +66 -0
- package/templates/cypress-template/scripts/run-cypress.mjs +1 -0
- package/templates/cypress-template/tsconfig.json +1 -1
- package/templates/playwright-template/.github/workflows/playwright-tests.yml +0 -14
- package/templates/playwright-template/README.md +1 -0
- package/templates/playwright-template/components/README.md +5 -0
- package/templates/playwright-template/config/README.md +5 -0
- package/templates/playwright-template/config/environments.ts +1 -0
- package/templates/playwright-template/config/runtime-config.ts +1 -0
- package/templates/playwright-template/config/secret-manager.ts +1 -0
- package/templates/playwright-template/data/factories/README.md +6 -0
- package/templates/playwright-template/data/factories/data-factory.ts +1 -0
- package/templates/playwright-template/data/generators/README.md +5 -0
- package/templates/playwright-template/data/generators/id-generator.ts +1 -0
- package/templates/playwright-template/data/generators/seeded-faker.ts +4 -3
- package/templates/playwright-template/fixtures/README.md +5 -0
- package/templates/playwright-template/fixtures/test-fixtures.ts +1 -0
- package/templates/playwright-template/pages/README.md +6 -0
- package/templates/playwright-template/pages/base-page.ts +1 -0
- package/templates/playwright-template/pages/login-page.ts +1 -0
- package/templates/playwright-template/pages/people-page.ts +1 -0
- package/templates/playwright-template/playwright.config.ts +1 -0
- package/templates/playwright-template/reporters/README.md +5 -0
- package/templates/playwright-template/reporters/structured-reporter.ts +1 -0
- package/templates/playwright-template/scripts/README.md +5 -0
- package/templates/playwright-template/scripts/generate-allure-report.mjs +1 -0
- package/templates/playwright-template/tests/README.md +7 -0
- package/templates/playwright-template/tests/api-people.spec.ts +1 -0
- package/templates/playwright-template/tests/ui-journey.spec.ts +1 -0
- package/templates/playwright-template/utils/README.md +5 -0
- package/templates/playwright-template/utils/logger.ts +1 -0
- package/templates/playwright-template/utils/test-step.ts +1 -0
package/README.md
CHANGED
|
@@ -34,6 +34,12 @@ Generate the Cypress template explicitly:
|
|
|
34
34
|
create-qa-patterns cypress-template my-project
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Generate without post-create prompts, which is useful for CI or scripted setup:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
create-qa-patterns playwright-template my-project --yes --no-install --no-setup --no-test
|
|
41
|
+
```
|
|
42
|
+
|
|
37
43
|
## Supported templates
|
|
38
44
|
|
|
39
45
|
- `playwright-template`
|
|
@@ -54,6 +60,14 @@ For Playwright projects, the interactive flow also offers:
|
|
|
54
60
|
|
|
55
61
|
- `npx playwright install`
|
|
56
62
|
|
|
63
|
+
For non-interactive automation, the CLI also supports:
|
|
64
|
+
|
|
65
|
+
- `--yes`
|
|
66
|
+
- `--no-install`
|
|
67
|
+
- `--no-setup`
|
|
68
|
+
- `--no-test`
|
|
69
|
+
- `--template <template>`
|
|
70
|
+
|
|
57
71
|
## Prerequisite checks
|
|
58
72
|
|
|
59
73
|
The CLI checks:
|
package/index.js
CHANGED
|
@@ -118,6 +118,14 @@ Usage:
|
|
|
118
118
|
create-qa-patterns
|
|
119
119
|
create-qa-patterns <target-directory>
|
|
120
120
|
create-qa-patterns <template> [target-directory]
|
|
121
|
+
create-qa-patterns --template <template> [target-directory]
|
|
122
|
+
|
|
123
|
+
Options:
|
|
124
|
+
--yes Accept all post-generate prompts
|
|
125
|
+
--no-install Skip npm install
|
|
126
|
+
--no-setup Skip template-specific setup such as Playwright browser install
|
|
127
|
+
--no-test Skip npm test
|
|
128
|
+
--template Explicitly choose a template without using positional arguments
|
|
121
129
|
|
|
122
130
|
Interactive mode:
|
|
123
131
|
When run without an explicit template, the CLI shows an interactive template picker.
|
|
@@ -127,6 +135,58 @@ ${supportedTemplates}
|
|
|
127
135
|
`);
|
|
128
136
|
}
|
|
129
137
|
|
|
138
|
+
function parseCliOptions(args) {
|
|
139
|
+
const options = {
|
|
140
|
+
yes: false,
|
|
141
|
+
noInstall: false,
|
|
142
|
+
noSetup: false,
|
|
143
|
+
noTest: false,
|
|
144
|
+
templateName: null,
|
|
145
|
+
positionalArgs: []
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
149
|
+
const arg = args[index];
|
|
150
|
+
|
|
151
|
+
switch (arg) {
|
|
152
|
+
case "--yes":
|
|
153
|
+
options.yes = true;
|
|
154
|
+
break;
|
|
155
|
+
case "--no-install":
|
|
156
|
+
options.noInstall = true;
|
|
157
|
+
break;
|
|
158
|
+
case "--no-setup":
|
|
159
|
+
options.noSetup = true;
|
|
160
|
+
break;
|
|
161
|
+
case "--no-test":
|
|
162
|
+
options.noTest = true;
|
|
163
|
+
break;
|
|
164
|
+
case "--template": {
|
|
165
|
+
const templateValue = args[index + 1];
|
|
166
|
+
if (!templateValue) {
|
|
167
|
+
throw new Error("Missing value for --template.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const templateName = resolveTemplate(templateValue);
|
|
171
|
+
if (!templateName) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Unsupported template "${templateValue}". Supported templates: ${TEMPLATES.map((template) => template.id).join(", ")}.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
options.templateName = templateName;
|
|
178
|
+
index += 1;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
default:
|
|
182
|
+
options.positionalArgs.push(arg);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return options;
|
|
188
|
+
}
|
|
189
|
+
|
|
130
190
|
function parseNodeVersion(version) {
|
|
131
191
|
const normalized = version.replace(/^v/, "");
|
|
132
192
|
const [major = "0", minor = "0", patch = "0"] = normalized.split(".");
|
|
@@ -347,7 +407,27 @@ async function selectTemplateInteractively() {
|
|
|
347
407
|
});
|
|
348
408
|
}
|
|
349
409
|
|
|
350
|
-
function resolveNonInteractiveArgs(args) {
|
|
410
|
+
function resolveNonInteractiveArgs(args, options = {}) {
|
|
411
|
+
if (options.templateName) {
|
|
412
|
+
if (args.length > 1) {
|
|
413
|
+
throw new Error("Too many arguments. Run `create-qa-patterns --help` for usage.");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (args.length === 0) {
|
|
417
|
+
return {
|
|
418
|
+
templateName: options.templateName,
|
|
419
|
+
targetDirectory: process.cwd(),
|
|
420
|
+
generatedInCurrentDirectory: true
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
templateName: options.templateName,
|
|
426
|
+
targetDirectory: path.resolve(process.cwd(), args[0]),
|
|
427
|
+
generatedInCurrentDirectory: false
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
351
431
|
if (args.length === 0) {
|
|
352
432
|
return {
|
|
353
433
|
templateName: DEFAULT_TEMPLATE,
|
|
@@ -670,20 +750,22 @@ function printSummary(summary) {
|
|
|
670
750
|
}
|
|
671
751
|
|
|
672
752
|
async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
673
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
753
|
const prerequisites = collectPrerequisites();
|
|
754
|
+
const options = summary.options;
|
|
755
|
+
const canPrompt = process.stdin.isTTY && process.stdout.isTTY;
|
|
678
756
|
|
|
679
757
|
if (prerequisites.npm) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (shouldInstallDependencies) {
|
|
683
|
-
await runCommand("npm", ["install"], targetDirectory);
|
|
684
|
-
summary.npmInstall = "completed";
|
|
685
|
-
} else {
|
|
758
|
+
if (options.noInstall) {
|
|
686
759
|
summary.npmInstall = "skipped";
|
|
760
|
+
} else {
|
|
761
|
+
const shouldInstallDependencies = options.yes ? true : canPrompt ? await askYesNo("Run npm install now?", true) : false;
|
|
762
|
+
|
|
763
|
+
if (shouldInstallDependencies) {
|
|
764
|
+
await runCommand("npm", ["install"], targetDirectory);
|
|
765
|
+
summary.npmInstall = "completed";
|
|
766
|
+
} else {
|
|
767
|
+
summary.npmInstall = canPrompt ? "skipped" : "not-run";
|
|
768
|
+
}
|
|
687
769
|
}
|
|
688
770
|
} else {
|
|
689
771
|
process.stdout.write(`${colors.yellow("Skipping")} npm install prompt because npm is not available.\n`);
|
|
@@ -691,8 +773,10 @@ async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
|
691
773
|
}
|
|
692
774
|
|
|
693
775
|
if (template.setup) {
|
|
694
|
-
if (
|
|
695
|
-
|
|
776
|
+
if (options.noSetup) {
|
|
777
|
+
summary.extraSetup = "skipped";
|
|
778
|
+
} else if (prerequisites[template.setup.availability]) {
|
|
779
|
+
const shouldRunExtraSetup = options.yes ? true : canPrompt ? await askYesNo(template.setup.prompt, true) : false;
|
|
696
780
|
|
|
697
781
|
if (shouldRunExtraSetup) {
|
|
698
782
|
try {
|
|
@@ -711,7 +795,7 @@ async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
|
711
795
|
}
|
|
712
796
|
}
|
|
713
797
|
} else {
|
|
714
|
-
summary.extraSetup = "skipped";
|
|
798
|
+
summary.extraSetup = canPrompt ? "skipped" : "not-run";
|
|
715
799
|
}
|
|
716
800
|
} else {
|
|
717
801
|
process.stdout.write(
|
|
@@ -722,13 +806,17 @@ async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
|
722
806
|
}
|
|
723
807
|
|
|
724
808
|
if (prerequisites.npm) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (shouldRunTests) {
|
|
728
|
-
await runCommand("npm", ["test"], targetDirectory);
|
|
729
|
-
summary.testRun = "completed";
|
|
730
|
-
} else {
|
|
809
|
+
if (options.noTest) {
|
|
731
810
|
summary.testRun = "skipped";
|
|
811
|
+
} else {
|
|
812
|
+
const shouldRunTests = options.yes ? true : canPrompt ? await askYesNo("Run npm test now?", false) : false;
|
|
813
|
+
|
|
814
|
+
if (shouldRunTests) {
|
|
815
|
+
await runCommand("npm", ["test"], targetDirectory);
|
|
816
|
+
summary.testRun = "completed";
|
|
817
|
+
} else {
|
|
818
|
+
summary.testRun = canPrompt ? "skipped" : "not-run";
|
|
819
|
+
}
|
|
732
820
|
}
|
|
733
821
|
} else {
|
|
734
822
|
process.stdout.write(`${colors.yellow("Skipping")} npm test prompt because npm is not available.\n`);
|
|
@@ -737,16 +825,20 @@ async function runPostGenerateActions(template, targetDirectory, summary) {
|
|
|
737
825
|
}
|
|
738
826
|
|
|
739
827
|
async function main() {
|
|
740
|
-
const
|
|
828
|
+
const rawArgs = process.argv.slice(2);
|
|
741
829
|
|
|
742
830
|
assertSupportedNodeVersion();
|
|
743
831
|
|
|
744
|
-
if (
|
|
832
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
745
833
|
printHelp();
|
|
746
834
|
return;
|
|
747
835
|
}
|
|
748
836
|
|
|
749
|
-
const
|
|
837
|
+
const options = parseCliOptions(rawArgs);
|
|
838
|
+
const args = options.positionalArgs;
|
|
839
|
+
const { templateName, targetDirectory, generatedInCurrentDirectory } = options.templateName
|
|
840
|
+
? resolveNonInteractiveArgs(args, options)
|
|
841
|
+
: await resolveScaffoldArgs(args);
|
|
750
842
|
const template = getTemplate(templateName);
|
|
751
843
|
|
|
752
844
|
if (!template) {
|
|
@@ -755,6 +847,7 @@ async function main() {
|
|
|
755
847
|
|
|
756
848
|
const prerequisites = collectPrerequisites();
|
|
757
849
|
const summary = createSummary(template, targetDirectory, generatedInCurrentDirectory);
|
|
850
|
+
summary.options = options;
|
|
758
851
|
printPrerequisiteWarnings(prerequisites);
|
|
759
852
|
await scaffoldProject(template, targetDirectory, prerequisites);
|
|
760
853
|
summary.gitInit = prerequisites.git ? "completed" : "unavailable";
|
package/package.json
CHANGED
|
@@ -21,9 +21,11 @@ This is a Cypress + TypeScript automation framework template for a small determi
|
|
|
21
21
|
- Cypress-native custom commands for common user actions
|
|
22
22
|
- page modules that own selectors and keep spec files focused on behavior
|
|
23
23
|
- generic `DataFactory` helpers for repeatable UI data
|
|
24
|
+
- folder-level `README.md` guides and file-header comments for easier onboarding
|
|
24
25
|
- multi-environment runtime config with `dev`, `staging`, and `prod`
|
|
25
26
|
- env-based secret resolution with a replaceable `SecretProvider`
|
|
26
27
|
- built-in screenshots and videos on failure
|
|
28
|
+
- optional single-file Allure report
|
|
27
29
|
- ESLint rules that keep selectors out of spec files
|
|
28
30
|
- bundled deterministic UI demo app and GitHub Actions workflow
|
|
29
31
|
|
|
@@ -79,6 +81,12 @@ If you want to run the demo app manually for debugging:
|
|
|
79
81
|
npm run demo:ui
|
|
80
82
|
```
|
|
81
83
|
|
|
84
|
+
If you want an Allure report after a run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm run report:allure
|
|
88
|
+
```
|
|
89
|
+
|
|
82
90
|
Default local values:
|
|
83
91
|
|
|
84
92
|
- UI base URL: `http://127.0.0.1:3000`
|
|
@@ -134,6 +142,7 @@ npm run demo:ui
|
|
|
134
142
|
npm run lint
|
|
135
143
|
npm run typecheck
|
|
136
144
|
npm run cy:run
|
|
145
|
+
npm run report:allure
|
|
137
146
|
```
|
|
138
147
|
|
|
139
148
|
## Reports and artifacts
|
|
@@ -142,9 +151,13 @@ Outputs:
|
|
|
142
151
|
|
|
143
152
|
- Cypress videos: `reports/videos`
|
|
144
153
|
- Cypress screenshots: `reports/screenshots`
|
|
154
|
+
- Allure single file: `reports/allure/index.html`
|
|
155
|
+
- raw Allure results: `allure-results`
|
|
145
156
|
|
|
146
157
|
The default Cypress terminal output is kept as the main reporting path.
|
|
147
158
|
|
|
159
|
+
If you only want Cypress's built-in output, remove the `allureCypress(...)` call in `cypress.config.ts`.
|
|
160
|
+
|
|
148
161
|
## Add a new test
|
|
149
162
|
|
|
150
163
|
Create tests under `cypress/e2e/`.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Defines the built-in environment defaults used when env vars are not provided.
|
|
1
2
|
import type { TestEnvironment } from "./test-env";
|
|
2
3
|
|
|
3
4
|
type EnvironmentDefaults = {
|
|
@@ -8,7 +9,7 @@ type EnvironmentDefaults = {
|
|
|
8
9
|
};
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const DEFAULTS: Record<TestEnvironment, EnvironmentDefaults> = {
|
|
12
13
|
dev: {
|
|
13
14
|
uiBaseUrl: "http://127.0.0.1:3000",
|
|
14
15
|
credentials: {
|
|
@@ -20,18 +21,18 @@ const environmentDefaults: Record<TestEnvironment, EnvironmentDefaults> = {
|
|
|
20
21
|
uiBaseUrl: "https://staging-ui.example.internal",
|
|
21
22
|
credentials: {
|
|
22
23
|
username: "staging-user",
|
|
23
|
-
password: "
|
|
24
|
+
password: "replace-me"
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
27
|
prod: {
|
|
27
28
|
uiBaseUrl: "https://ui.example.internal",
|
|
28
29
|
credentials: {
|
|
29
30
|
username: "prod-user",
|
|
30
|
-
password: "
|
|
31
|
+
password: "replace-me"
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
export function getEnvironmentDefaults(
|
|
36
|
-
return
|
|
36
|
+
export function getEnvironmentDefaults(testEnv: TestEnvironment): EnvironmentDefaults {
|
|
37
|
+
return DEFAULTS[testEnv];
|
|
37
38
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Builds the runtime configuration object that tests and fixtures consume.
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
|
|
3
4
|
import dotenv from "dotenv";
|
|
@@ -27,6 +28,7 @@ export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
|
|
|
27
28
|
export function loadRuntimeConfig(): RuntimeConfig {
|
|
28
29
|
const defaults = getEnvironmentDefaults(environment);
|
|
29
30
|
const secretManager = new SecretManager(new EnvSecretProvider());
|
|
31
|
+
|
|
30
32
|
const uiBaseUrl =
|
|
31
33
|
process.env[`${environment.toUpperCase()}_UI_BASE_URL`] ??
|
|
32
34
|
process.env.UI_BASE_URL ??
|
|
@@ -1,19 +1,29 @@
|
|
|
1
|
+
// Minimal secret abstraction so env-based secrets can later be replaced cleanly.
|
|
1
2
|
import type { TestEnvironment } from "./test-env";
|
|
2
3
|
|
|
3
4
|
export interface SecretProvider {
|
|
4
|
-
|
|
5
|
+
getSecret(key: string, testEnv: TestEnvironment): string | undefined;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export class EnvSecretProvider implements SecretProvider {
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
getSecret(key: string, testEnv: TestEnvironment): string | undefined {
|
|
10
|
+
const envPrefix = testEnv.toUpperCase();
|
|
11
|
+
return process.env[`${envPrefix}_${key}`] ?? process.env[key];
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export class SecretManager {
|
|
14
|
-
constructor(private readonly
|
|
16
|
+
constructor(private readonly provider: SecretProvider) {}
|
|
15
17
|
|
|
16
|
-
|
|
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);
|
|
18
28
|
}
|
|
19
29
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const testEnvironmentSchema = z.enum(["dev", "staging", "prod"]);
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
const testEnv = process.env.TEST_ENV ?? "dev";
|
|
7
|
-
|
|
8
|
-
if (!testEnvironmentValues.has(testEnv as TestEnvironment)) {
|
|
9
|
-
throw new Error(`Unsupported TEST_ENV "${testEnv}". Expected one of: dev, staging, prod.`);
|
|
10
|
-
}
|
|
5
|
+
export type TestEnvironment = z.infer<typeof testEnvironmentSchema>;
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
export function loadTestEnvironment(): TestEnvironment {
|
|
8
|
+
return testEnvironmentSchema.parse(process.env.TEST_ENV ?? "dev");
|
|
13
9
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// Generic data builders that keep tests readable and deterministic.
|
|
2
2
|
import { createSeededFaker } from "./seeded-faker";
|
|
3
|
+
import { IdGenerator } from "./id-generator";
|
|
3
4
|
|
|
4
5
|
export type PersonRecord = {
|
|
5
6
|
personId: string;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
// Deterministic id generator for repeatable local and CI runs.
|
|
1
2
|
export class IdGenerator {
|
|
2
3
|
private readonly counters = new Map<string, number>();
|
|
3
4
|
|
|
4
|
-
constructor(private readonly
|
|
5
|
+
constructor(private readonly runId: string) {}
|
|
5
6
|
|
|
6
7
|
next(prefix: string): string {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
this.
|
|
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
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
nextSequence(prefix: string): number {
|
|
14
|
+
const counter = (this.counters.get(prefix) ?? 0) + 1;
|
|
15
|
+
this.counters.set(prefix, counter);
|
|
16
|
+
return counter;
|
|
12
17
|
}
|
|
13
18
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
// Seeded faker wrapper so generated values stay stable for a given run id.
|
|
1
2
|
import { Faker, en } from "@faker-js/faker";
|
|
2
3
|
|
|
3
|
-
function
|
|
4
|
-
return
|
|
4
|
+
function hashSeed(value: string): number {
|
|
5
|
+
return value.split("").reduce((seed, character) => {
|
|
6
|
+
return ((seed << 5) - seed + character.charCodeAt(0)) | 0;
|
|
7
|
+
}, 0);
|
|
5
8
|
}
|
|
6
9
|
|
|
7
|
-
export function createSeededFaker(
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
return
|
|
10
|
+
export function createSeededFaker(seedInput: string): Faker {
|
|
11
|
+
const instance = new Faker({ locale: [en] });
|
|
12
|
+
instance.seed(Math.abs(hashSeed(seedInput)));
|
|
13
|
+
return instance;
|
|
11
14
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Central Cypress configuration for specs, retries, artifacts, and runtime env values.
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
|
|
4
|
+
import { allureCypress } from "allure-cypress/reporter";
|
|
1
5
|
import { defineConfig } from "cypress";
|
|
2
6
|
|
|
3
7
|
import { loadRuntimeConfig } from "./config/runtime-config";
|
|
@@ -9,7 +13,19 @@ export default defineConfig({
|
|
|
9
13
|
baseUrl: runtimeConfig.uiBaseUrl,
|
|
10
14
|
specPattern: "cypress/e2e/**/*.cy.ts",
|
|
11
15
|
supportFile: "cypress/support/e2e.ts",
|
|
12
|
-
setupNodeEvents(
|
|
16
|
+
setupNodeEvents(on, config) {
|
|
17
|
+
// Keep Cypress terminal output as the default path most users expect.
|
|
18
|
+
// Remove the Allure line below if you prefer to stay with Cypress output only.
|
|
19
|
+
allureCypress(on, config, {
|
|
20
|
+
resultsDir: "allure-results",
|
|
21
|
+
environmentInfo: {
|
|
22
|
+
os_platform: os.platform(),
|
|
23
|
+
os_release: os.release(),
|
|
24
|
+
os_version: os.version(),
|
|
25
|
+
node_version: process.version
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
13
29
|
config.env = {
|
|
14
30
|
...config.env,
|
|
15
31
|
testEnv: runtimeConfig.testEnv,
|