@toolstackhq/create-qa-patterns 1.0.12 → 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.
Files changed (58) hide show
  1. package/README.md +14 -0
  2. package/index.js +116 -23
  3. package/package.json +1 -1
  4. package/templates/cypress-template/README.md +13 -0
  5. package/templates/cypress-template/allurerc.mjs +10 -0
  6. package/templates/cypress-template/config/README.md +5 -0
  7. package/templates/cypress-template/config/environments.ts +1 -0
  8. package/templates/cypress-template/config/runtime-config.ts +1 -0
  9. package/templates/cypress-template/config/secret-manager.ts +1 -0
  10. package/templates/cypress-template/cypress/e2e/README.md +6 -0
  11. package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +1 -0
  12. package/templates/cypress-template/cypress/support/README.md +5 -0
  13. package/templates/cypress-template/cypress/support/app-config.ts +1 -0
  14. package/templates/cypress-template/cypress/support/commands.ts +1 -0
  15. package/templates/cypress-template/cypress/support/data/README.md +5 -0
  16. package/templates/cypress-template/cypress/support/data/data-factory.ts +1 -0
  17. package/templates/cypress-template/cypress/support/data/id-generator.ts +1 -0
  18. package/templates/cypress-template/cypress/support/data/seeded-faker.ts +1 -0
  19. package/templates/cypress-template/cypress/support/e2e.ts +1 -0
  20. package/templates/cypress-template/cypress/support/pages/README.md +5 -0
  21. package/templates/cypress-template/cypress/support/pages/login-page.ts +1 -0
  22. package/templates/cypress-template/cypress/support/pages/people-page.ts +1 -0
  23. package/templates/cypress-template/cypress.config.ts +17 -1
  24. package/templates/cypress-template/eslint.config.mjs +1 -1
  25. package/templates/cypress-template/package-lock.json +2857 -109
  26. package/templates/cypress-template/package.json +4 -0
  27. package/templates/cypress-template/scripts/README.md +5 -0
  28. package/templates/cypress-template/scripts/generate-allure-report.mjs +66 -0
  29. package/templates/cypress-template/scripts/run-cypress.mjs +1 -0
  30. package/templates/cypress-template/tsconfig.json +1 -1
  31. package/templates/playwright-template/README.md +1 -0
  32. package/templates/playwright-template/components/README.md +5 -0
  33. package/templates/playwright-template/config/README.md +5 -0
  34. package/templates/playwright-template/config/environments.ts +1 -0
  35. package/templates/playwright-template/config/runtime-config.ts +1 -0
  36. package/templates/playwright-template/config/secret-manager.ts +1 -0
  37. package/templates/playwright-template/data/factories/README.md +6 -0
  38. package/templates/playwright-template/data/factories/data-factory.ts +1 -0
  39. package/templates/playwright-template/data/generators/README.md +5 -0
  40. package/templates/playwright-template/data/generators/id-generator.ts +1 -0
  41. package/templates/playwright-template/data/generators/seeded-faker.ts +1 -0
  42. package/templates/playwright-template/fixtures/README.md +5 -0
  43. package/templates/playwright-template/fixtures/test-fixtures.ts +1 -0
  44. package/templates/playwright-template/pages/README.md +6 -0
  45. package/templates/playwright-template/pages/base-page.ts +1 -0
  46. package/templates/playwright-template/pages/login-page.ts +1 -0
  47. package/templates/playwright-template/pages/people-page.ts +1 -0
  48. package/templates/playwright-template/playwright.config.ts +1 -0
  49. package/templates/playwright-template/reporters/README.md +5 -0
  50. package/templates/playwright-template/reporters/structured-reporter.ts +1 -0
  51. package/templates/playwright-template/scripts/README.md +5 -0
  52. package/templates/playwright-template/scripts/generate-allure-report.mjs +1 -0
  53. package/templates/playwright-template/tests/README.md +7 -0
  54. package/templates/playwright-template/tests/api-people.spec.ts +1 -0
  55. package/templates/playwright-template/tests/ui-journey.spec.ts +1 -0
  56. package/templates/playwright-template/utils/README.md +5 -0
  57. package/templates/playwright-template/utils/logger.ts +1 -0
  58. 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
- const shouldInstallDependencies = await askYesNo("Run npm install now?", true);
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 (prerequisites[template.setup.availability]) {
695
- const shouldRunExtraSetup = await askYesNo(template.setup.prompt, true);
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
- const shouldRunTests = await askYesNo("Run npm test now?", false);
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 args = process.argv.slice(2);
828
+ const rawArgs = process.argv.slice(2);
741
829
 
742
830
  assertSupportedNodeVersion();
743
831
 
744
- if (args.includes("--help") || args.includes("-h")) {
832
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
745
833
  printHelp();
746
834
  return;
747
835
  }
748
836
 
749
- const { templateName, targetDirectory, generatedInCurrentDirectory } = await resolveScaffoldArgs(args);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolstackhq/create-qa-patterns",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "CLI for generating QA framework templates.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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/`.
@@ -0,0 +1,10 @@
1
+ export default {
2
+ name: "qa-patterns Cypress Template",
3
+ plugins: {
4
+ awesome: {
5
+ options: {
6
+ singleFile: true
7
+ }
8
+ }
9
+ }
10
+ };
@@ -0,0 +1,5 @@
1
+ # Config
2
+
3
+ This folder resolves environment-specific values for the Cypress template.
4
+
5
+ - Keep defaults, secret lookup, and runtime parsing here.
@@ -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 = {
@@ -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";
@@ -1,3 +1,4 @@
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 {
@@ -0,0 +1,6 @@
1
+ # E2E Specs
2
+
3
+ This folder holds Cypress scenarios.
4
+
5
+ - Keep specs focused on workflow validation.
6
+ - Use custom commands and page modules instead of raw selectors.
@@ -1,3 +1,4 @@
1
+ // Starter Cypress scenario that demonstrates the preferred spec style for this template.
1
2
  import { DataFactory } from "../support/data/data-factory";
2
3
  import { loadAppConfig } from "../support/app-config";
3
4
  import { peoplePage } from "../support/pages/people-page";
@@ -0,0 +1,5 @@
1
+ # Support
2
+
3
+ This folder contains the reusable Cypress support layer.
4
+
5
+ - Put custom commands, shared config, page modules, and test data helpers here.
@@ -1,3 +1,4 @@
1
+ // Reads runtime values from Cypress env so specs stay clean and framework-aware.
1
2
  type Credentials = {
2
3
  username: string;
3
4
  password: string;
@@ -1,3 +1,4 @@
1
+ // Registers the custom Cypress commands that the starter specs rely on.
1
2
  import type { PersonRecord } from "./data/data-factory";
2
3
  import { loginPage } from "./pages/login-page";
3
4
  import { peoplePage } from "./pages/people-page";
@@ -0,0 +1,5 @@
1
+ # Data
2
+
3
+ This folder holds generic test data builders and generators.
4
+
5
+ - Keep data simple and readable for starter projects.
@@ -1,3 +1,4 @@
1
+ // Generic data builders that keep tests readable and deterministic.
1
2
  import { createSeededFaker } from "./seeded-faker";
2
3
  import { IdGenerator } from "./id-generator";
3
4
 
@@ -1,3 +1,4 @@
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
 
@@ -1,3 +1,4 @@
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
4
  function hashSeed(value: string): number {
@@ -1 +1,2 @@
1
+ import "allure-cypress";
1
2
  import "./commands";
@@ -0,0 +1,5 @@
1
+ # Pages
2
+
3
+ This folder holds Cypress page modules.
4
+
5
+ - Keep selectors and reusable UI actions here instead of in spec files.
@@ -1,3 +1,4 @@
1
+ // Page module for the login screen used by custom Cypress commands.
1
2
  export const loginPage = {
2
3
  visit(): Cypress.Chainable {
3
4
  return cy.visit("/login");
@@ -1,3 +1,4 @@
1
+ // Page module for the people screen used in the starter Cypress journey.
1
2
  import type { PersonRecord } from "../data/data-factory";
2
3
 
3
4
  export const peoplePage = {
@@ -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(_on, config) {
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,
@@ -22,7 +22,7 @@ const nodeGlobals = {
22
22
 
23
23
  export default [
24
24
  {
25
- ignores: ["demo-apps/**", "node_modules/**", "reports/**"]
25
+ ignores: ["demo-apps/**", "node_modules/**", "reports/**", "allure-results/**", "allure-report/**"]
26
26
  },
27
27
  js.configs.recommended,
28
28
  {