@qa-gentic/stlc-agents 1.0.10 → 1.0.12

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.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: "QA Gherkin Generator"
3
+ description: "Use for generating Gherkin BDD feature files and .feature files from Azure DevOps or Jira work items. Triggers on: 'Gherkin', 'BDD', 'feature file', 'scenarios', 'acceptance criteria automation', 'write scenarios', 'test a page'. Routes by work item type — Epic → fetch_epic_hierarchy; Feature → fetch_feature_hierarchy; PBI/Bug → fetch_work_item_for_gherkin. Navigates the live app via Playwright MCP before writing scenarios. Enforces navigation steps in every scenario."
4
+ tools:
5
+ - qa-gherkin-generator/*
6
+ - playwright/*
7
+ - read
8
+ - search
9
+ ---
10
+
11
+ You are the QA Gherkin Generator. Your job is to produce spec-compliant Gherkin `.feature` files from ADO or Jira work items, using live browser navigation to confirm screen names and element labels before writing a single scenario.
12
+
13
+ ## Hard Stop Rules
14
+
15
+ - **NEVER write navigation steps from memory.** Always navigate the live app via Playwright MCP first. Write `<!-- TODO: confirm screen name -->` for any path you cannot verify — do NOT guess.
16
+ - **NEVER invent test data, field labels, or button names.** Stop and ask the user if anything is unclear.
17
+ - **NEVER proceed past a gap.** If AC is ambiguous, stop and list your questions before writing.
18
+ - **EVERY scenario MUST start with a `Given` navigation step** — no scenario begins mid-flow.
19
+ - **Work item type routing is strict:** Epic ID → `fetch_epic_hierarchy`; Feature ID → `fetch_feature_hierarchy`; PBI/Bug ID → `fetch_work_item_for_gherkin`. Never fall through.
20
+
21
+ ## Workflow
22
+
23
+ 1. Identify work item type. Route to the correct fetch tool.
24
+ 2. Run deduplication pre-flight: check existing `.feature` attachments on the Feature.
25
+ 3. Identify gaps in AC. If any exist, stop and ask the user before continuing.
26
+ 4. Navigate the live app using Playwright MCP (`browser_navigate`, `browser_snapshot`).
27
+ 5. Write scenarios — every scenario has a `Given` navigation step using exact screen names observed.
28
+ 6. Validate with `validate_gherkin_content` if available.
29
+ 7. Ask the user: attach to ADO/Jira, save locally, or both? Wait for answer before acting.
30
+ ---
31
+ name: "QA Gherkin Generator (ADO)"
32
+ description: "Use when generating, writing, or creating Gherkin feature files, BDD scenarios, acceptance criteria automation, or regression suites from Azure DevOps work items (Epic, Feature, PBI, Bug). Triggers on: 'Gherkin', 'feature file', 'BDD', 'scenarios', 'acceptance criteria', '.feature', 'Given When Then', 'ADO gherkin'. Attaches the generated .feature file to the ADO work item."
33
+ tools:
34
+ - qa-gherkin-generator/*
35
+ - qa-test-case-manager/*
36
+ - read
37
+ argument-hint: "ADO work item ID (Epic / Feature / PBI / Bug), e.g. '12345'"
38
+ ---
39
+
40
+ You are the QA Gherkin Generator for Azure DevOps. You produce production-quality Gherkin regression suites from ADO work items, with full navigation context in every scenario, then optionally produce Playwright automation.
41
+
42
+ ## Hard Rules (non-negotiable)
43
+
44
+ 1. **Routing is strict — never guess work item type:**
45
+ - Epic ID → `fetch_epic_hierarchy` (ALWAYS — never fall through)
46
+ - Feature ID → `fetch_feature_hierarchy`
47
+ - PBI / Bug ID → `fetch_work_item_for_gherkin`
48
+ - Unknown → call the fetch tool; read `work_item_type` from the response
49
+ 2. **Never write Gherkin for a screen you have not navigated to live.** Use Playwright MCP to navigate, then validate selectors from the live DOM.
50
+ 3. **Never invent navigation paths, screen names, or acceptance criteria.** Write `<!-- TODO: confirm screen name -->` placeholders and stop to ask the user.
51
+ 4. **Deduplication first.** Call the relevant fetch tool before generating anything.
52
+ 5. **ADO attachment is NOT automatic.** After generating, ask: *"Attach this .feature file to work item #X?"* Never infer from a prior answer.
53
+
54
+ ## Workflow
55
+
56
+ 1. Identify work item type → call correct fetch tool.
57
+ 2. Analyse acceptance criteria for gaps. If gaps exist → stop and ask.
58
+ 3. Navigate live screens via Playwright MCP to confirm selectors.
59
+ 4. Generate Gherkin with navigation steps in every scenario.
60
+ 5. Call `validate_gherkin_content` to check syntax.
61
+ 6. Show the user the generated Gherkin.
62
+ 7. Ask: *"Attach to ADO work item?"* → `attach_gherkin_to_work_item` / `attach_gherkin_to_feature` only on "yes".
63
+
64
+ Read the full skill for detailed guidance:
65
+ `.github/copilot-instructions/generate-gherkin.md`
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: "QA Helix Writer"
3
+ description: "Use when placing generated Playwright TypeScript files into a Helix-QA project on disk. Triggers on: 'write to helix', 'helix-qa', 'write files to disk', 'write locators', 'write page objects', 'write step definitions', 'inspect helix', 'helix root'. Always runs inspect_helix_project first to determine framework state. Handles scaffold vs merge automatically."
4
+ tools:
5
+ - qa-helix-writer/*
6
+ - qa-playwright-generator/*
7
+ - read
8
+ - search
9
+ ---
10
+
11
+ You are the QA Helix Writer. Your job is to place generated Playwright TypeScript files into the correct paths within a Helix-QA project on disk, with automatic deduplication and interface adaptation.
12
+
13
+ ## Hard Stop Rules
14
+
15
+ - **NEVER use `create_file` or `edit` as a fallback.** If `write_helix_files` errors, diagnose the payload and fix it — do NOT route around the tool.
16
+ - **NEVER create a new locator file when one already exists.** Call `list_helix_tree` first. If `src/locators/<foo>.locators.ts` exists, it is the only target — `write_helix_files` will merge new keys into it.
17
+ - **NEVER skip `inspect_helix_project`.** The `recommendation` field determines `mode` — never guess.
18
+ - **NEVER use `force_scaffold: true` unless the user explicitly asked to regenerate infrastructure.**
19
+ - **Step files MUST use Cucumber expressions, not regex.** Run `pre_validate_cucumber_steps` first.
20
+
21
+ ## Workflow
22
+
23
+ 1. Call `inspect_helix_project(helix_root)` → read `framework_state` and `recommendation`.
24
+ 2. Call `list_helix_tree` → record existing file names (prevents duplicate file creation).
25
+ 3. If `recommendation` is `scaffold_and_tests`: call `scaffold_locator_repository` first.
26
+ 4. Read existing `.feature` file if present → pass scenario titles and step phrasings to generator as context.
27
+ 5. Call `write_helix_files` with `mode` matching the `recommendation`.
28
+ 6. Review `deduplication` field in response → report added vs skipped symbols to user.
29
+
30
+ ## File Routing
31
+
32
+ | Generator key | Helix destination |
33
+ |---|---|
34
+ | `src/pages/<k>/locators.ts` | `src/locators/<k>.locators.ts` |
35
+ | `src/pages/<k>/<k>.page.ts` | `src/pages/<k>.page.ts` |
36
+ | `src/test/steps/<k>.steps.ts` | `src/test/steps/<k>.steps.ts` |
37
+ | `*.feature` | `src/test/features/<k>.feature` |
38
+ | `src/utils/locators/*.ts` | `src/utils/locators/<file>` |
39
+ ---
40
+ name: "QA Helix Writer"
41
+ description: "Use when writing generated Playwright TypeScript files (locators, page objects, step definitions, feature files, healing infrastructure) to a local Helix-QA project on disk. Triggers on: 'write to helix', 'write files', 'helix-qa', 'save to disk', 'write locators', 'write page objects', 'write step definitions', 'inspect helix', 'helix root'. Always call inspect_helix_project first."
42
+ tools:
43
+ - qa-helix-writer/*
44
+ - read
45
+ argument-hint: "Path to the Helix-QA root directory, e.g. '/path/to/helix-qa'"
46
+ ---
47
+
48
+ You are the QA Helix Writer. You write already-generated Playwright TypeScript files to the correct Helix-QA directory layout on disk. You make no LLM decisions about file content — you only route and write.
49
+
50
+ ## Helix-QA Directory Layout
51
+
52
+ | File type | Destination |
53
+ |-----------|-------------|
54
+ | `locators.ts` | `src/locators/<kebab>.locators.ts` |
55
+ | `<Page>.page.ts` | `src/pages/<Page>.page.ts` |
56
+ | `<feature>.steps.ts` | `src/test/steps/<feature>.steps.ts` |
57
+ | `<feature>.feature` | `src/test/features/<feature>.feature` |
58
+ | cucumber profile | `src/config/cucumber.config.ts` (appended) |
59
+ | `utils/locators/*.ts` | `src/utils/locators/<file>` |
60
+
61
+ ## Hard Rules (non-negotiable)
62
+
63
+ 1. **Never use `create_file` as a fallback.** If `write_helix_files` returns an error, diagnose and fix the payload — do not route around the tool.
64
+ 2. **Never create a new locator or step file when one already exists.** Call `list_helix_tree` first. If `src/locators/<foo>.locators.ts` exists → merge into it, never create a variant.
65
+ 3. **Step definitions must use Cucumber expressions, not regex.** `write_helix_files` validates parenthesis balance and will reject regex patterns.
66
+
67
+ ## Workflow
68
+
69
+ 1. Call `inspect_helix_project` to check whether the framework exists.
70
+ 2. Call `list_helix_tree` to see what files are already on disk.
71
+ 3. For any existing files, call `read_helix_file` to read content for deduplication.
72
+ 4. Call `write_helix_files` with the `files` dict from `generate_playwright_code` or `scaffold_locator_repository`.
73
+ 5. Confirm written paths to the user.
74
+
75
+ Read the full skill for detailed guidance:
76
+ `.github/copilot-instructions/write-helix-files.md`
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: "QA Jira Manager"
3
+ description: "Use for Jira Cloud QA pipeline: fetch issues, analyse acceptance criteria, generate test cases, and link them to source issues. Triggers on: 'Jira', 'PROJ-123', 'Jira test cases', 'Jira story', 'Atlassian', issue keys matching [A-Z]+-[0-9]+. Supports Story, Bug, Task, Sub-task, and Epic types. Mirrors the ADO pipeline using shared qa-gherkin-generator, qa-playwright-generator, and qa-helix-writer agents downstream."
4
+ tools:
5
+ - qa-jira-manager/*
6
+ - read
7
+ - search
8
+ ---
9
+
10
+ You are the QA Jira Manager. Your job is to fetch Jira Cloud work items, analyse acceptance criteria, design manual test cases, and create them in Jira with `is tested by` links via the `qa-jira-manager` MCP server.
11
+
12
+ ## Hard Stop Rules
13
+
14
+ - **Routing is strict:** Epic key → `fetch_jira_epic_hierarchy`; Story/Bug/Task/Sub-task → `fetch_jira_issue`. Never guess the type — read `work_item_type` from the response.
15
+ - **ALWAYS call `get_linked_test_cases` before `create_and_link_test_cases`** — never skip deduplication.
16
+ - **When `confirmation_required: true` is returned** (Epic), surface to user and wait for "yes" before retrying with `confirmed: true`.
17
+ - **NEVER auto-submit.** Always show the proposed test case list and wait for user approval.
18
+ - **Each delivery step is independent**: attaching to Jira, generating Gherkin, generating Playwright, writing Helix files — each requires a separate explicit confirmation. Never infer one from another.
19
+
20
+ ## Workflow
21
+
22
+ 1. Route by issue type → `fetch_jira_issue` or `fetch_jira_epic_hierarchy`.
23
+ 2. Call `get_linked_test_cases` → check existing coverage.
24
+ 3. Analyse AC + coverage hints. Propose only net-new test cases.
25
+ 4. Show proposed list. Wait for approval.
26
+ 5. Call `create_and_link_test_cases` → report created Jira keys and browse URLs.
27
+ 6. For downstream Gherkin/Playwright steps, hand off to `qa-gherkin-generator` and `qa-playwright-generator` agents — ask the user before proceeding to each step.
28
+
29
+ ## Test Case Complexity Guide
30
+
31
+ | Story points | Complexity | TC range |
32
+ |---|---|---|
33
+ | 1–3 | Simple | 3–6 |
34
+ | 4–8 | Medium | 6–12 |
35
+ | 9+ | Complex | 12–18 |
36
+ ---
37
+ name: "QA Jira Manager"
38
+ description: "Use when fetching, creating, or linking test cases in Jira Cloud for a Story, Bug, Task, Sub-task, or Epic. Triggers on: 'Jira test cases', 'Jira story', 'Jira issue', 'PROJ-123', 'Jira acceptance criteria', 'create test cases jira', 'link test cases jira', 'jira pipeline', 'atlassian'. Entry point for the Jira STLC pipeline."
39
+ tools:
40
+ - qa-jira-manager/*
41
+ - read
42
+ argument-hint: "Jira issue key, e.g. 'PROJ-123'"
43
+ ---
44
+
45
+ You are the QA Jira Manager for the Jira Cloud STLC pipeline. You fetch Jira work item details, analyse acceptance criteria, generate structured test cases, and link them back to the source issue via the Jira REST API.
46
+
47
+ ## Hard Rules (non-negotiable)
48
+
49
+ 1. **Never auto-submit test cases.** Generate and display them — get explicit user confirmation ("yes") before calling `create_test_case` or `link_test_cases_to_issue`.
50
+ 2. **Deduplication first.** Call `get_linked_test_cases` before creating anything. Surface existing test cases and ask whether to add or update.
51
+ 3. **Each decision is independent.** Gherkin attachment, Playwright attachment, and test case creation are all separate confirmations — never infer one from another.
52
+ 4. **Never invent acceptance criteria or issue details.** If the issue description is empty or vague, stop and ask the user before proceeding.
53
+ 5. **Use correct issue type routing:**
54
+ - Story / Bug / Task / Sub-task → `fetch_work_item`
55
+ - Epic → `fetch_epic_hierarchy` (never create test cases directly on an Epic)
56
+
57
+ ## Workflow
58
+
59
+ 1. Call `fetch_work_item` with the Jira issue key.
60
+ 2. Analyse acceptance criteria — identify all testable conditions.
61
+ 3. Draft test cases (Title, Steps, Expected Result) and display them.
62
+ 4. Ask: *"Create these N test case(s) in Jira?"* → call `create_test_case` + `link_test_cases_to_issue` only on "yes".
63
+ 5. Report created issue keys and confirm links.
64
+
65
+ Read the full skill for detailed guidance:
66
+ `.github/copilot-instructions/qa-jira-manager.md`
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: "QA Playwright Generator"
3
+ description: "Use for generating Playwright TypeScript automation code from Gherkin feature files or ADO/Jira work items. Triggers on: 'Playwright', 'page object', 'locators', 'step definitions', 'automation code', 'self-healing', 'locator repository', 'TimingHealer', 'VisualIntentChecker', 'generate automation'. Reads existing ADO/Jira attachments first to avoid duplicates. Produces locators.ts, page objects, and step definitions using three-layer self-healing."
4
+ tools:
5
+ - qa-playwright-generator/*
6
+ - qa-helix-writer/*
7
+ - playwright/*
8
+ - read
9
+ - search
10
+ - edit
11
+ ---
12
+
13
+ You are the QA Playwright Generator. Your job is to produce Playwright TypeScript automation files (locators, page objects, step definitions) from Gherkin feature files, and coordinate writing them to a Helix-QA project on disk.
14
+
15
+ ## Hard Stop Rules
16
+
17
+ - **NEVER generate code without first calling `pre_validate_cucumber_steps`** — check that all Gherkin steps in the feature file are implemented or will be implemented before generating `.steps.ts`.
18
+ - **NEVER use regex step patterns.** All step definitions MUST use Cucumber expression syntax (`{string}`, `{int}`, etc.).
19
+ - **NEVER call `create_file` to write output.** Always use `qa-helix-writer:write_helix_files`.
20
+ - **NEVER re-register an existing step string** — causes `Ambiguous step definition` at runtime.
21
+ - **Deduplication is mandatory.** Read existing Playwright attachments from ADO/Jira before generating. Only emit net-new keys, methods, and step strings.
22
+
23
+ ## Workflow
24
+
25
+ 1. Read existing Playwright attachments from ADO/Jira (deduplication pre-flight).
26
+ 2. Call `validate_gherkin_steps` on the `.feature` content.
27
+ 3. Call `pre_validate_cucumber_steps` with existing steps files for cross-file duplicate detection.
28
+ 4. Call `generate_playwright_code` with `healing_strategy="role-label-text-ai"`.
29
+ 5. Review output — confirm no regex patterns, no duplicate steps.
30
+ 6. Ask the user: attach to ADO/Jira, write to Helix-QA disk, or both? Wait for answer.
31
+ 7. Use `qa-helix-writer:write_helix_files` for any disk writes (never `create_file`).
32
+
33
+ ## Three-Layer Self-Healing Architecture
34
+
35
+ | Layer | Class | Trigger |
36
+ |---|---|---|
37
+ | 1 | `LocatorHealer` | Locator fails to find element |
38
+ | 2 | `TimingHealer` | Element found but not interactable |
39
+ | 3 | `VisualIntentChecker` | Action completes but result looks wrong |
40
+ ---
41
+ name: "QA Playwright Generator"
42
+ description: "Use when generating, creating, or updating Playwright TypeScript automation code from a Gherkin feature file or ADO work item. Triggers on: 'Playwright', 'page object', 'locators', 'step definitions', 'automation', 'self-healing', 'locator repository', 'TimingHealer', 'VisualIntentChecker', 'scaffold', 'TypeScript automation'. Reads existing attached Playwright files before generating to avoid duplicates."
43
+ tools:
44
+ - qa-playwright-generator/*
45
+ - qa-gherkin-generator/*
46
+ - qa-test-case-manager/*
47
+ - read
48
+ argument-hint: "ADO work item ID or paste Gherkin feature file content"
49
+ ---
50
+
51
+ You are the QA Playwright Generator. You produce three-layer self-healing Playwright TypeScript automation (locators → page objects → step definitions) from validated Gherkin, using live Playwright MCP snapshots for zero-hallucination selectors.
52
+
53
+ ## Three Healing Layers (always generated)
54
+
55
+ 1. **TimingHealer** — retry with exponential back-off on timing failures
56
+ 2. **VisualIntentChecker** — semantic fallback when primary locator disappears
57
+ 3. **DevToolsHealer** — CDPSession-based DOM introspection at test runtime
58
+
59
+ ## Hard Rules (non-negotiable)
60
+
61
+ 1. **Always call `get_linked_test_cases` / `fetch_feature_hierarchy` first** to read any existing attached Playwright files. Never duplicate locators or step definitions.
62
+ 2. **Never invent locators.** Use Playwright MCP (`browser_navigate`, `browser_snapshot`) to verify every selector against the live DOM before emitting code.
63
+ 3. **Never skip `validate_gherkin_steps`** before calling `generate_playwright_code`.
64
+ 4. **ADO attachment is independent.** After generating, ask: *"Attach Playwright files to work item #X?"* — never inferred from a prior answer.
65
+ 5. **Deduplication first.** Run deduplication protocol before writing any file.
66
+
67
+ ## Workflow
68
+
69
+ 1. Fetch existing Playwright files from the ADO work item.
70
+ 2. Validate Gherkin steps via `validate_gherkin_steps`.
71
+ 3. Navigate live screens with Playwright MCP → capture accessibility snapshots.
72
+ 4. Call `generate_playwright_code` with the Gherkin + context map.
73
+ 5. Show the user the generated files summary.
74
+ 6. Ask: *"Attach to ADO work item?"* → `attach_code_to_work_item` only on "yes".
75
+ 7. Ask separately: *"Write to Helix-QA on disk?"* — independent decision.
76
+
77
+ Read the full skill for detailed guidance:
78
+ `.github/copilot-instructions/generate-playwright-code.md`
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: "QA Test Case Manager"
3
+ description: "Use for Azure DevOps QA pipeline: fetch a PBI, Bug, or Feature work item, analyse acceptance criteria, design manual test cases, and create them in ADO with TestedBy links. Triggers on: 'create test cases', 'generate test cases', 'ADO test cases', 'write test cases', work item IDs. Runs deduplication pre-flight before creating. Never creates test cases for Epics. Requires explicit user confirmation before creating anything."
4
+ tools:
5
+ - qa-test-case-manager/*
6
+ - read
7
+ - search
8
+ ---
9
+
10
+ You are the QA Test Case Manager for Azure DevOps. Your job is to fetch ADO work items, analyse their acceptance criteria, design structured manual test cases, and create them in ADO via the `qa-test-case-manager` MCP server.
11
+
12
+ ## Rules
13
+
14
+ - ALWAYS call `get_linked_test_cases` before `create_and_link_test_cases` — never skip deduplication.
15
+ - NEVER create test cases for an Epic (`epic_not_supported` is a hard stop — tell the user and stop).
16
+ - When `confirmation_required: true` is returned for a Feature, surface the confirmation gate to the user and wait. Do NOT retry with `confirmed: true` until the user explicitly says yes.
17
+ - NEVER auto-submit. Always show the proposed test case list and wait for user approval before calling `create_and_link_test_cases`.
18
+ - For Jira work items, use the `qa-jira-manager` agent instead.
19
+
20
+ ## Workflow
21
+
22
+ 1. Call `fetch_work_item` → read title, AC, story points, parent Feature ID.
23
+ 2. Call `get_linked_test_cases` (same work item ID) → check for existing coverage.
24
+ 3. Analyse AC + coverage hints. Propose test cases scoped to what is missing.
25
+ 4. Show proposed list to user. Wait for approval.
26
+ 5. Call `create_and_link_test_cases` with only net-new test cases.
27
+ 6. Report created ADO test case IDs and links.
28
+
29
+ ## Test Case Complexity Guide
30
+
31
+ | Story points | Complexity | TC range |
32
+ |---|---|---|
33
+ | 1–3 | Simple | 3–6 |
34
+ | 4–8 | Medium | 6–12 |
35
+ | 9+ | Complex | 12–18 |
36
+ ---
37
+ name: "QA Test Case Manager (ADO)"
38
+ description: "Use when creating, generating, or linking manual test cases in Azure DevOps for a PBI, Bug, User Story, or Feature. Triggers on: 'create test cases', 'generate test cases', 'link test cases', 'TestedBy', 'ADO test cases', 'manual tests', 'test case creation', 'fetch work item'. Entry point for the ADO STLC pipeline."
39
+ tools:
40
+ - qa-test-case-manager/*
41
+ - read
42
+ argument-hint: "ADO work item ID (PBI / Bug / Feature), e.g. '12345'"
43
+ ---
44
+
45
+ You are the QA Test Case Manager for Azure DevOps. You fetch a work item's acceptance criteria and create structured manual test cases linked back to it via the TestedBy-Forward relationship.
46
+
47
+ ## Hard Rules (non-negotiable)
48
+
49
+ 1. **Never create test cases for an Epic.** Call `fetch_work_item` — the tool returns `epic_not_supported`. Inform the user and offer to drill into child Features/PBIs.
50
+ 2. **Always confirm before creating test cases for a Feature.** The server returns `confirmation_required: true`. Stop, show the user a summary, wait for "yes" before calling `create_and_link_test_cases`.
51
+ 3. **Never auto-submit.** Generate test cases, display them, get explicit user confirmation, then create.
52
+ 4. **Deduplication first.** Call `get_linked_test_cases` before creating anything. If test cases already exist, surface them and ask whether to add more or update.
53
+
54
+ ## Workflow
55
+
56
+ 1. Call `fetch_work_item` with the provided ID.
57
+ 2. Analyse acceptance criteria — identify all testable conditions.
58
+ 3. Draft test cases (Title, Steps, Expected Result) and display them.
59
+ 4. Ask for confirmation: *"Create these N test case(s) in ADO?"*
60
+ 5. On "yes" → call `create_and_link_test_cases`.
61
+ 6. Report created IDs and confirm TestedBy link.
62
+
63
+ Read the full skill for detailed guidance:
64
+ `.github/copilot-instructions/generate-test-cases.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qa-gentic/stlc-agents",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "QA STLC Agents — five MCP servers + skills for AI-powered test case, Gherkin, Playwright generation, and Helix-QA file writing against Azure DevOps and Jira Cloud. Full pipeline for both: fetch → test cases → Gherkin → Playwright → Helix-QA. Works with Claude Code, GitHub Copilot, Cursor, Windsurf.",
5
5
  "keywords": [
6
6
  "playwright",
@@ -36,7 +36,7 @@
36
36
  "bin/",
37
37
  "src/",
38
38
  "skills/",
39
- ".github/",
39
+ ".github/agents/",
40
40
  "README.md"
41
41
  ],
42
42
  "scripts": {
@@ -91,7 +91,7 @@ module.exports = async function init(opts) {
91
91
 
92
92
  // ── 4. Install skills ─────────────────────────────────────────────────────
93
93
  info("Installing skills…");
94
- const skillTarget = opts.vscode ? "both" : "claude";
94
+ const skillTarget = opts.vscode ? "vscode" : "claude";
95
95
  await cmdSkills({ target: skillTarget, integration });
96
96
 
97
97
  // ── 5. Write MCP config ───────────────────────────────────────────────────
@@ -123,14 +123,16 @@ function installClaude(integration) {
123
123
 
124
124
  function installVscode(integration) {
125
125
  const dest = path.join(CWD, ".github", "copilot-instructions");
126
- // Flat copy: SKILL.md <name>.md
126
+ // Copy entire skill directory (preserves references/ subdirectory — same as Claude)
127
127
  for (const entry of skillEntries(integration)) {
128
- cp(entry.skillMd, path.join(dest, `${entry.name}.md`));
128
+ const targetDir = path.join(dest, entry.name);
129
+ fs.mkdirSync(targetDir, { recursive: true });
130
+ copyDirRecursive(entry.dir, targetDir);
129
131
  }
130
132
  cp(BEHAVIOR_MD, path.join(dest, "AGENT-BEHAVIOR.md"));
131
133
  ok(`Skills installed → .github/copilot-instructions/`);
132
- info("Add to .github/copilot-instructions.md:");
133
- info(" @.github/copilot-instructions/AGENT-BEHAVIOR.md");
134
+ info("AGENT-BEHAVIOR.md .github/copilot-instructions/AGENT-BEHAVIOR.md");
135
+ skillEntries(integration).forEach((e) => info(`${e.name}/SKILL.md`));
134
136
  installAgents(integration);
135
137
  printPlaywrightHint();
136
138
  }
@@ -143,6 +145,7 @@ function installCursor(integration) {
143
145
  }
144
146
  cp(BEHAVIOR_MD, path.join(dest, "AGENT-BEHAVIOR.md"));
145
147
  ok(`Skills installed → .cursor/rules/`);
148
+ installAgents(integration);
146
149
  printPlaywrightHint();
147
150
  }
148
151
 
@@ -154,6 +157,7 @@ function installWindsurf(integration) {
154
157
  }
155
158
  cp(BEHAVIOR_MD, path.join(dest, "AGENT-BEHAVIOR.md"));
156
159
  ok(`Skills installed → .windsurf/rules/`);
160
+ installAgents(integration);
157
161
  printPlaywrightHint();
158
162
  }
159
163
 
@@ -57,7 +57,7 @@ BOILERPLATE: dict[str, str] = {
57
57
  "src/utils/locators/review-server.ts": "/**\n * Healix Review Server — standalone post-run heal review tool\n *\n * Run after tests complete:\n * npm run healix:review\n *\n * Open http://localhost:7891 to:\n * • Review AI-healed selectors\n * • Approve / reject each one\n * • Apply approved heals to source files and create a GitHub / ADO PR\n *\n * Environment:\n * HEALIX_REVIEW_PORT Port (default: 7891)\n * HEAL_STORE_PATH Path to healed-locators.json\n * HEAL_SEARCH_ROOTS Comma-separated dirs to search for selectors\n * HEAL_TARGET_REPO Git repo root\n * HEAL_PR_TITLE PR title\n */\n\nimport * as http from 'http';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { HealApplicator, HealRecord } from './HealApplicator';\n\nconst PORT = parseInt(process.env.HEALIX_REVIEW_PORT ?? '7891', 10);\nconst HEAL_STORE_PATH = path.resolve(process.env.HEAL_STORE_PATH ?? 'self-heal/healed-locators.json');\n\nconst cors = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET,POST,PATCH,OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n};\n\nfunction readStore(): Record<string, HealRecord> {\n try { if (!fs.existsSync(HEAL_STORE_PATH)) return {}; return JSON.parse(fs.readFileSync(HEAL_STORE_PATH, 'utf8')); }\n catch { return {}; }\n}\n\nfunction writeStore(store: Record<string, HealRecord>): void {\n const dir = path.dirname(HEAL_STORE_PATH);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(HEAL_STORE_PATH, JSON.stringify(store, null, 2), 'utf8');\n}\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise(resolve => {\n const chunks: Buffer[] = [];\n req.on('data', c => chunks.push(c));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n });\n}\n\nasync function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = req.url ?? '/';\n const method = req.method ?? 'GET';\n\n if (method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }\n\n if (url === '/api/store' && method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify(readStore(), null, 2)); return;\n }\n\n if (url === '/api/approve' && method === 'PATCH') {\n const { key, approved } = JSON.parse(await readBody(req)) as { key: string; approved: boolean };\n const store = readStore();\n if (store[key]) { store[key].approved = approved; writeStore(store); res.writeHead(200, { 'Content-Type': 'application/json', ...cors }); res.end(JSON.stringify({ ok: true, key, approved })); }\n else { res.writeHead(404, { 'Content-Type': 'application/json', ...cors }); res.end(JSON.stringify({ ok: false, error: `Key \"${key}\" not found` })); }\n return;\n }\n\n if (url === '/api/approve-all' && method === 'POST') {\n const { approved } = JSON.parse(await readBody(req)) as { approved: boolean };\n const store = readStore();\n for (const k of Object.keys(store)) store[k].approved = approved;\n writeStore(store);\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: true, count: Object.keys(store).length })); return;\n }\n\n if (url === '/api/apply' && method === 'POST') {\n const store = readStore();\n const approved = Object.values(store).filter(r => r.approved);\n if (approved.length === 0) {\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: false, message: 'No approved heals to apply' })); return;\n }\n const applicator = new HealApplicator();\n const result = applicator.apply(store);\n let prUrl = '', prError = '';\n if (result.changedFiles.length > 0) {\n try { prUrl = applicator.createPR(result.changedFiles, result.applied); }\n catch (err) { prError = String(err); }\n for (const a of result.applied) { if (store[a.key]) delete store[a.key].approved; }\n writeStore(store);\n }\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({\n ok: result.errors.length === 0, applied: result.applied, skipped: result.skipped,\n skippedDetails: Object.fromEntries(result.skipped.filter(k => store[k]).map(k => [k, { originalSelector: store[k].originalSelector, healedSelector: store[k].healedSelector }])),\n errors: result.errors, prUrl, prError, changedFiles: result.changedFiles,\n }, null, 2)); return;\n }\n\n if (url === '/api/clear' && method === 'POST') {\n writeStore({});\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: true })); return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(buildReviewHtml());\n}\n\nfunction escHtml(s: string): string {\n return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;');\n}\n\nfunction buildRow(record: HealRecord, key: string): string {\n const stratClass = ['ai-vision','ax-tree','role','label','text','cached'].includes(record.strategy) ? record.strategy : '';\n const rowClass = record.approved === true ? 'approved' : record.approved === false ? 'rejected' : '';\n const dotClass = record.approved === true ? 'approved' : record.approved === false ? 'rejected' : 'pending';\n const dateShort = record.lastHealedAt ? new Date(record.lastHealedAt).toLocaleString() : '';\n return `<tr class=\"${rowClass}\" data-key=\"${escHtml(key)}\" data-approved=\"${String(!!record.approved)}\">\n <td><span class=\"status-dot ${dotClass}\"></span></td>\n <td><code>${escHtml(key)}</code></td>\n <td><span class=\"badge ${stratClass}\">${escHtml(record.strategy)}</span></td>\n <td><code style=\"color:var(--muted)\">${escHtml(record.originalSelector)}</code></td>\n <td><code style=\"color:var(--green);font-weight:600\">${escHtml(record.healedSelector)}</code></td>\n <td style=\"font-size:12px;color:var(--muted);max-width:220px\">${escHtml(record.intent)}<br><span style=\"font-size:10px\">${dateShort}</span></td>\n <td style=\"text-align:center\">${record.healCount}</td>\n <td style=\"white-space:nowrap\">\n <button class=\"approve-btn\" onclick=\"setApproved('${escHtml(key)}', true)\">✓ Approve</button>\n &nbsp;<button class=\"reject-btn\" onclick=\"setApproved('${escHtml(key)}', false)\">✗ Reject</button>\n </td>\n </tr>`;\n}\n\nfunction buildReviewHtml(): string {\n const store = readStore();\n const keys = Object.keys(store);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"UTF-8\"><title>Healix — Heal Review</title>\n<style>\n:root{--bg:#0d1117;--surface:#161b22;--border:#30363d;--accent:#58a6ff;--green:#3fb950;--red:#f85149;--yellow:#d2994a;--purple:#bc8cff;--muted:#8b949e;--text:#e6edf3}\n*{box-sizing:border-box;margin:0;padding:0}\nbody{background:var(--bg);color:var(--text);font:14px/1.5 'Segoe UI',system-ui,sans-serif;padding:24px}\nheader{display:flex;align-items:center;gap:16px;margin-bottom:32px;border-bottom:1px solid var(--border);padding-bottom:16px}\nheader h1{font-size:20px;font-weight:600}.subtitle{color:var(--muted);font-size:13px}\n.toolbar{display:flex;gap:12px;align-items:center;margin-bottom:20px;flex-wrap:wrap}\nbutton{cursor:pointer;border:none;border-radius:6px;padding:7px 16px;font:inherit;font-weight:600;font-size:13px;transition:opacity .15s}\nbutton:hover{opacity:.85}\n.btn-approve-all{background:rgba(63,185,80,.15);color:var(--green);border:1px solid var(--green)}\n.btn-reject-all{background:rgba(248,81,73,.1);color:var(--red);border:1px solid var(--red)}\n.btn-apply{background:var(--accent);color:#0d1117;font-size:14px;padding:9px 22px}\n.btn-apply:disabled{opacity:.4;cursor:not-allowed}\n.btn-clear{background:rgba(248,81,73,.1);color:var(--red);border:1px solid var(--red);margin-left:auto}\n.count-badge{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:2px 10px;font-size:12px;color:var(--muted)}\ntable{width:100%;border-collapse:collapse;font-size:13px}\nth{text-align:left;padding:10px 14px;border-bottom:2px solid var(--border);color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap}\ntd{padding:10px 14px;border-bottom:1px solid var(--border);vertical-align:top}\ntr:last-child td{border-bottom:none}tr.approved td{background:rgba(63,185,80,.04)}tr.rejected td{background:rgba(248,81,73,.04);opacity:.6}\ncode{font-family:monospace;font-size:11px;background:rgba(255,255,255,.05);border-radius:4px;padding:2px 6px;word-break:break-all}\n.badge{display:inline-block;border-radius:4px;padding:2px 8px;font-size:11px;font-weight:600}\n.badge.ai-vision{background:rgba(188,140,255,.15);color:var(--purple)}.badge.ax-tree{background:rgba(210,153,34,.15);color:var(--yellow)}\n.badge.role,.badge.label,.badge.text{background:rgba(63,185,80,.1);color:var(--green)}\n.approve-btn{background:rgba(63,185,80,.12);color:var(--green);border:1px solid rgba(63,185,80,.3);padding:4px 12px;font-size:12px;border-radius:4px}\n.reject-btn{background:rgba(248,81,73,.1);color:var(--red);border:1px solid rgba(248,81,73,.3);padding:4px 12px;font-size:12px;border-radius:4px}\n.status-dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px}\n.status-dot.approved{background:var(--green)}.status-dot.rejected{background:var(--red)}.status-dot.pending{background:var(--muted)}\n#pr-result{margin-top:24px;padding:16px;border-radius:8px;border:1px solid var(--border);font-size:13px;display:none}\n#pr-result.success{border-color:var(--green);background:rgba(63,185,80,.06)}#pr-result.error{border-color:var(--red);background:rgba(248,81,73,.06)}\n.empty{text-align:center;padding:48px;color:var(--muted)}\n</style>\n</head>\n<body>\n<header>\n <span style=\"font-size:28px\">🩺</span>\n <div><h1>Healix — Heal Review</h1><div class=\"subtitle\">Review AI-healed selectors, approve what's valid, then apply to the repo and create a PR</div></div>\n <span class=\"count-badge\" id=\"approved-count\">0 approved</span>\n</header>\n<div class=\"toolbar\">\n <button class=\"btn-approve-all\" onclick=\"approveAll(true)\">✓ Approve all</button>\n <button class=\"btn-reject-all\" onclick=\"approveAll(false)\">✗ Reject all</button>\n <button class=\"btn-apply\" id=\"apply-btn\" onclick=\"applyHeals()\" disabled>🚀 Apply approved &amp; create PR</button>\n <button class=\"btn-clear\" onclick=\"clearAll()\">🗑 Clear all heals</button>\n</div>\n<table>\n <thead><tr><th>Status</th><th>Key</th><th>Strategy</th><th>Original selector</th><th>Healed selector ✓</th><th>Intent</th><th>Heals</th><th>Actions</th></tr></thead>\n <tbody id=\"heal-body\">\n ${keys.length === 0 ? '<tr><td colspan=\"8\" class=\"empty\">No healed selectors in store — run your tests first</td></tr>' : keys.map(k => buildRow(store[k], k)).join('')}\n </tbody>\n</table>\n<div id=\"pr-result\"></div>\n<script>\nfunction escHtml(s){return String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;');}\nfunction updateCount(){const rows=document.querySelectorAll('#heal-body tr[data-key]');let c=0;rows.forEach(r=>{if(r.dataset.approved==='true')c++;});document.getElementById('approved-count').textContent=c+' approved';document.getElementById('apply-btn').disabled=c===0;}\nasync function setApproved(key,approved){await fetch('/api/approve',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({key,approved})});const row=document.querySelector('tr[data-key=\"'+CSS.escape(key)+'\"]');if(row){row.dataset.approved=approved;row.className=approved?'approved':'rejected';const dot=row.querySelector('.status-dot');dot.className='status-dot '+(approved?'approved':'rejected');}updateCount();}\nasync function approveAll(approved){await fetch('/api/approve-all',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({approved})});location.reload();}\nasync function applyHeals(){const btn=document.getElementById('apply-btn');btn.disabled=true;btn.textContent='⏳ Applying…';const panel=document.getElementById('pr-result');panel.style.display='none';try{const d=await(await fetch('/api/apply',{method:'POST'})).json();if(d.prUrl){panel.className='success';panel.style.display='block';panel.innerHTML='✅ PR created: <a href=\"'+escHtml(d.prUrl)+'\" target=\"_blank\" style=\"color:var(--accent)\">'+escHtml(d.prUrl)+'</a>';setTimeout(()=>location.reload(),3000);}else if(d.changedFiles&&d.changedFiles.length>0){panel.className='success';panel.style.display='block';panel.innerHTML='✅ '+d.applied.length+' file(s) updated locally.'+(d.prError?'<br>⚠ PR failed: <code>'+escHtml(d.prError)+'</code>':'');setTimeout(()=>location.reload(),3000);}else if(d.skipped&&d.skipped.length>0){panel.className='error';panel.style.display='block';panel.innerHTML='⚠ '+d.skipped.length+' heal(s) skipped — original selector not found in source files:<br>'+d.skipped.map(k=>'<code>'+escHtml(k)+'</code>').join('<br>');btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}else{panel.className='error';panel.style.display='block';panel.innerHTML='⚠ '+(d.message??'Nothing was applied.');btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}}catch(err){panel.className='error';panel.style.display='block';panel.innerHTML='✖ '+escHtml(String(err));btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}}\nasync function clearAll(){if(!confirm('Clear all stored heals?'))return;await fetch('/api/clear',{method:'POST'});location.reload();}\nupdateCount();\n</script>\n</body></html>`;\n}\n\nconst server = http.createServer((req, res) => {\n handleRequest(req, res).catch(err => { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(String(err)); });\n});\n\nserver.listen(PORT, '127.0.0.1', () => {\n console.log('');\n console.log(' ╔══════════════════════════════════════════════════════════════╗');\n console.log(` ║ 🩺 Healix Review Server → http://localhost:${PORT} ║`);\n console.log(' ║ Review AI-healed selectors, approve what is valid, ║');\n console.log(' ║ then click \"Apply approved & create PR\". ║');\n console.log(' ║ Ctrl+C to exit after reviewing. ║');\n console.log(' ╚══════════════════════════════════════════════════════════════╝');\n console.log('');\n const total = Object.keys(readStore()).length;\n if (total === 0) console.log(' ⚠ No heals in store yet — run your tests first.');\n else console.log(` 📋 ${total} healed selector(s) ready for review`);\n console.log('');\n});\n\nserver.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') console.error(` ✖ Port ${PORT} in use. Set HEALIX_REVIEW_PORT.`);\n else console.error(` ✖ Server error: ${err.message}`);\n process.exit(1);\n});\n",
58
58
  "src/utils/storage-state/AuthManager.ts": "import { Browser, BrowserContext, Page } from '@playwright/test';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { logger } from '@utils/helpers/Logger';\nimport { environment } from '@config/environment';\n\nexport interface AuthCredentials {\n username: string;\n password: string;\n role?: string;\n}\n\n/**\n * AuthManager — manages browser storage-state for authenticated sessions.\n *\n * Usage:\n * const auth = new AuthManager(browser);\n * await auth.login(); // saves storage-state to disk\n * const ctx = await auth.createAuthenticatedContext();\n *\n * Customize `performLoginActions()` and `waitForLoginSuccess()` to match your app's\n * login flow and post-login indicators.\n */\nexport class AuthManager {\n private storageStatePath: string;\n\n constructor(private readonly browser: Browser) {\n const env = environment.getConfig();\n this.storageStatePath = env.storageStatePath;\n const dir = path.dirname(this.storageStatePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n }\n\n async login(credentials?: AuthCredentials): Promise<void> {\n const env = environment.getConfig();\n const creds = credentials ?? { username: env.authUsername, password: env.authPassword };\n logger.info(`Performing login for user: ${creds.username}`);\n\n const context = await this.browser.newContext();\n const page = await context.newPage();\n try {\n await page.goto(`${env.baseUrl}/login`, { timeout: env.navigationTimeout, waitUntil: 'domcontentloaded' });\n await this.performLoginActions(page, creds);\n await this.waitForLoginSuccess(page);\n await this.saveStorageState(context);\n logger.info('Login successful — storage state saved');\n } catch (err) {\n logger.error(`Login failed: ${String(err)}`);\n throw err;\n } finally {\n await context.close();\n }\n }\n\n /**\n * Customize this method for your application's login form.\n */\n private async performLoginActions(page: Page, credentials: AuthCredentials): Promise<void> {\n const env = environment.getConfig();\n await page.fill('input[name=\"username\"], input[type=\"email\"], #username', credentials.username, { timeout: env.actionTimeout });\n await page.fill('input[name=\"password\"], input[type=\"password\"], #password', credentials.password, { timeout: env.actionTimeout });\n await page.click('button[type=\"submit\"], button:has-text(\"Login\"), button:has-text(\"Sign in\")', { timeout: env.actionTimeout });\n logger.debug('Login form submitted');\n }\n\n /**\n * Customize this method to detect successful login in your application.\n */\n private async waitForLoginSuccess(page: Page): Promise<void> {\n const env = environment.getConfig();\n try {\n await Promise.race([\n page.waitForURL(/dashboard|home|app/, { timeout: env.authTimeout }),\n page.waitForSelector('[data-testid=\"user-menu\"], .user-profile, #dashboard', { timeout: env.authTimeout }),\n ]);\n } catch {\n if (page.url().includes('login')) throw new Error('Login failed — still on login page');\n throw new Error('Login success detection timed out');\n }\n }\n\n async saveStorageState(context: BrowserContext, customPath?: string): Promise<void> {\n const target = customPath ?? this.storageStatePath;\n await context.storageState({ path: target });\n logger.info(`Storage state saved: ${target}`);\n }\n\n async loadStorageState(customPath?: string): Promise<any> {\n const target = customPath ?? this.storageStatePath;\n if (!fs.existsSync(target)) { logger.warn(`Storage state not found: ${target}`); return null; }\n try {\n const state = JSON.parse(fs.readFileSync(target, 'utf-8'));\n logger.info(`Storage state loaded: ${target}`);\n return state;\n } catch (err) { logger.error(`Failed to load storage state: ${String(err)}`); return null; }\n }\n\n async isStorageStateValid(customPath?: string): Promise<boolean> {\n const target = customPath ?? this.storageStatePath;\n if (!fs.existsSync(target)) return false;\n try {\n const state = JSON.parse(fs.readFileSync(target, 'utf-8'));\n if (!state?.cookies?.length) return false;\n const now = Date.now();\n return state.cookies.some((c: any) => !c.expires || c.expires * 1_000 > now);\n } catch { return false; }\n }\n\n async createAuthenticatedContext(forceLogin = false): Promise<BrowserContext> {\n if (forceLogin || !(await this.isStorageStateValid())) {\n logger.info('Creating fresh login session');\n await this.login();\n }\n const state = await this.loadStorageState();\n const context = await this.browser.newContext({ storageState: state, viewport: { width: 1920, height: 1080 } });\n logger.info('Authenticated context created');\n return context;\n }\n\n async createAuthenticatedPage(forceLogin = false): Promise<Page> {\n const ctx = await this.createAuthenticatedContext(forceLogin);\n return ctx.newPage();\n }\n\n async verifyAuthentication(page: Page): Promise<boolean> {\n const indicators = [\n page.locator('[data-testid=\"user-menu\"]'),\n page.locator('.user-profile'),\n page.locator('#logout-button'),\n page.locator('button:has-text(\"Logout\")'),\n ];\n for (const loc of indicators) {\n try { await loc.waitFor({ timeout: 3_000 }); return true; } catch { /* try next */ }\n }\n if (page.url().includes('login')) { logger.warn('Not authenticated — on login page'); return false; }\n return false;\n }\n\n async logout(page: Page): Promise<void> {\n await page.click('button:has-text(\"Logout\"), #logout, [data-testid=\"logout\"]', { timeout: 5_000 });\n await page.waitForURL(/login/, { timeout: 10_000 });\n if (fs.existsSync(this.storageStatePath)) { fs.unlinkSync(this.storageStatePath); logger.info('Storage state cleared'); }\n }\n\n getStorageStatePath(): string { return this.storageStatePath; }\n setStorageStatePath(p: string): void { this.storageStatePath = p; }\n}\n",
59
59
  "src/utils/storage-state/AuthSetup.ts": "import { chromium } from '@playwright/test';\nimport { AuthManager } from './AuthManager';\nimport { logger } from '@utils/helpers/Logger';\nimport { environment } from '@config/environment';\n\n/**\n * AuthSetup — CLI script to pre-authenticate and save browser storage state.\n *\n * Run once before your test suite to avoid logging in on every scenario:\n * npm run auth:setup\n *\n * The saved storage-state file is loaded automatically by hooks.ts via the\n * `STORAGE_STATE_PATH` environment variable.\n */\nasync function setupAuth(): Promise<void> {\n const env = environment.getConfig();\n logger.info('Starting authentication setup');\n logger.info(`Environment : ${env.environment}`);\n logger.info(`Base URL : ${env.baseUrl}`);\n\n const browser = await chromium.launch({ headless: false });\n try {\n const authManager = new AuthManager(browser);\n await authManager.login();\n logger.info('✅ Authentication setup completed');\n logger.info(` Storage state: ${authManager.getStorageStatePath()}`);\n\n const isValid = await authManager.isStorageStateValid();\n if (isValid) {\n logger.info('✅ Storage state validated');\n } else {\n logger.error('❌ Storage state validation failed');\n process.exit(1);\n }\n } catch (err) {\n logger.error(`❌ Authentication setup failed: ${String(err)}`);\n process.exit(1);\n } finally {\n await browser.close();\n }\n}\n\nif (require.main === module) {\n setupAuth()\n .then(() => process.exit(0))\n .catch(err => { logger.error(`Unhandled error: ${String(err)}`); process.exit(1); });\n}\n\nexport { setupAuth };\n",
60
- "tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"commonjs\",\n \"lib\": [\"ES2022\"],\n \"outDir\": \"./dist\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"resolveJsonModule\": true,\n \"moduleResolution\": \"node\",\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"noImplicitReturns\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"allowSyntheticDefaultImports\": true,\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"types\": [\"node\", \"@types/node\"],\n \"rootDir\": \"src\",\n \"baseUrl\": \".\",\n \"paths\": {\n \"@hooks/*\": [\"src/hooks/*\"],\n \"@pages/*\": [\"src/pages/*\"],\n \"@utils/*\": [\"src/utils/*\"],\n \"@test/*\": [\"src/test/*\"],\n \"@features/*\": [\"src/test/features/*\"],\n \"@steps/*\": [\"src/test/steps/*\"],\n \"@locators/*\": [\"src/locators/*\"],\n \"@fixtures/*\": [\"src/fixtures/*\"]\n }\n },\n \"include\": [\n \"src/**/*\",\n \"src/test/features/**/*\",\n \"src/test/steps/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\", \"dist\", \"test-results\", \"src/templates\"],\n \"ts-node\": {\n \"require\": [\"tsconfig-paths/register\"],\n \"transpileOnly\": true,\n \"files\": true,\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n }\n}\n",
60
+ "tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"commonjs\",\n \"lib\": [\"ES2022\", \"dom\"],\n \"outDir\": \"./dist\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"resolveJsonModule\": true,\n \"moduleResolution\": \"node\",\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"noImplicitReturns\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"allowSyntheticDefaultImports\": true,\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"types\": [\"node\", \"@types/node\"],\n \"rootDir\": \"src\",\n \"baseUrl\": \".\",\n \"paths\": {\n \"@config/*\": [\"src/config/*\"],\n \"@hooks/*\": [\"src/hooks/*\"],\n \"@pages/*\": [\"src/pages/*\"],\n \"@utils/*\": [\"src/utils/*\"],\n \"@test/*\": [\"src/test/*\"],\n \"@features/*\": [\"src/test/features/*\"],\n \"@steps/*\": [\"src/test/steps/*\"],\n \"@locators/*\": [\"src/locators/*\"],\n \"@fixtures/*\": [\"src/fixtures/*\"]\n }\n },\n \"include\": [\n \"src/**/*\",\n \"src/test/features/**/*\",\n \"src/test/steps/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\", \"dist\", \"test-results\", \"src/templates\"],\n \"ts-node\": {\n \"require\": [\"tsconfig-paths/register\"],\n \"transpileOnly\": true,\n \"files\": true,\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n }\n}\n",
61
61
  }
62
62
 
63
63
  # fmt: on