@groupby/ai-dev 0.5.21 → 0.5.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groupby/ai-dev",
3
- "version": "0.5.21",
3
+ "version": "0.5.22",
4
4
  "description": "Interactive installer for Rezolve Ai development content",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,256 @@
1
+ # SNPD Team — QA Skills
2
+
3
+ A set of keyword-triggered skills that walk a Jira ticket through the full QA lifecycle — from ticket intake to a filed bug or a Qase-ready test suite — with a durable artifact at every stage.
4
+
5
+ Each skill does one job, hands off a file, and stops. The human stays in control of every transition: no skill silently continues into the next, every open question surfaces in chat before any deliverable is written, and every artifact is saved next to the ticket so it can be audited later.
6
+
7
+ ## The pipeline
8
+
9
+ ```
10
+ Jira ticket / Epic
11
+
12
+
13
+ ┌────────────────┐ writes Tasks/{KEY}/{KEY}_Test_Plan.xlsx
14
+ │ qa-test-plan │ ────────▶ + {KEY}_Qase_Export.json
15
+ └────────────────┘ (test cases with PR-diff findings + Qase-ready JSON)
16
+
17
+ ├─────────────► ┌────────────────┐ writes Tasks/{KEY}/QA_Checklist.md
18
+ │ │ qa-checklist │ (exploratory / regression pass)
19
+ │ └────────────────┘
20
+
21
+
22
+ ┌────────────────┐ writes Tasks/{KEY}/{KEY}_<name>.postman_collection.json
23
+ │ postman-qa │ ────────▶ (SETUP + POSITIVE + REGRESSION + NEGATIVE
24
+ └────────────────┘ + EDGE + KNOWN-BUGS + TEARDOWN)
25
+
26
+ ├─ fallback ─► ┌───────────────────────┐ writes test_*.py + conftest.py + tests.yml
27
+ │ │ be-github-actions-qa │ (pytest suite + CI job)
28
+ │ └───────────────────────┘
29
+
30
+ ├─ ui only ──► ┌───────────────────────┐ writes *.spec.ts
31
+ │ │ fe-github-actions-qa │ (Playwright E2E)
32
+ │ └───────────────────────┘
33
+
34
+ Test run
35
+
36
+ ├─ green ────► Qase import via {KEY}_Qase_Export.json
37
+
38
+ └─ red ─────► ┌────────────────────┐ verdict: test bug / product bug / env
39
+ │ failed-test-triage │ ──┐
40
+ └────────────────────┘ │
41
+ │ if product bug
42
+
43
+ ┌────────────────┐ writes BUG_<slug>.md
44
+ │ bug-report │ + BUG_REPRO_<slug>.postman_collection.json
45
+ └────────────────┘
46
+ ```
47
+
48
+ `qa-test-plan` is the entry point and auto-invokes sub-skills via the Skill tool — never composes Postman / pytest / Playwright output from memory.
49
+
50
+ ## Skills
51
+
52
+ ### 1. `qa-test-plan` — plan the ticket
53
+
54
+ - **Trigger** — any Jira key with testing intent (`S4R-11120`, `SNPD-4567`), or a Jira URL
55
+ - **Reads** — the ticket via Atlassian MCP; every linked PR via the dev-status API + Chrome MCP `/files` tab; existing collections in `Tasks/`; the env file at `Tasks/gbiqa-lo.postman_environment.json`
56
+ - **Produces** — `Tasks/{KEY}/{KEY}_Test_Plan.xlsx` and `Tasks/{KEY}/{KEY}_Qase_Export.json`
57
+ - **Stops before** — writing any Postman / pytest / Playwright output (offers those in the next message)
58
+
59
+ The orchestrator. Asks a scope question upfront (**Epic mode** — parent Epic + every related ticket, comments and statuses / **single-ticket mode** — this ticket + its comments + directly linked "relates to" issues), then does a mandatory PR review for every ticket in scope. Multiple PRs per ticket (follow-ups, optimisations) are all read. Every open question is posted in chat as one numbered list before the Excel is generated.
60
+
61
+ Excel includes a Summary sheet with the code-review findings, a Setup sheet naming which entities SETUP must create, and one row per TC. Qase JSON export is Qase-import-ready and produced at the end of the automation cycle for suite creation.
62
+
63
+ **Key rules:**
64
+ - Reviews the PR of the ticket via dev-status API + Chrome MCP — Jira MCP never exposes PR URLs directly
65
+ - If the repo requires SSO, stops and asks the user to log in — never guesses
66
+ - **Rule #2a — reused-method regression trap:** a shared method invoked from a new caller sees a broader input surface. Every new input dimension gets its own TC
67
+ - Asks upfront whether a Qase suite already exists — if it does, references those case IDs in the Excel's `Automation` column and skips duplicating
68
+ - Never generates Postman without a working curl for every endpoint (or a verbatim body from an existing collection in `Tasks/`)
69
+ - After the Excel, the very next message offers Postman — never pytest as default
70
+ - After Postman, a Manual Check Guide is posted inline: env to select, run order, per-TC verification steps, GCP filter, cleanup confirmation
71
+
72
+ ### 2. `qa-checklist` — scoped manual pass
73
+
74
+ - **Trigger** — "checklist for [ticket]", "exploratory test", "regression checklist"
75
+ - **Reads** — the Jira ticket via Atlassian MCP
76
+ - **Produces** — a sectioned markdown checklist (pre-conditions / functional / negative / engine coverage / regression / logging / automation coverage / sign-off)
77
+ - **Stops before** — any automation work
78
+
79
+ Fast, scoped checklist for exploratory or regression sessions. Not a replacement for `qa-test-plan` — used when a full test plan would be overkill (config change, small bug fix, quick regression pass). Adapts sections to ticket type (feature vs. bug fix vs. config).
80
+
81
+ **Key rules:**
82
+ - Cross-references existing Qase cases in the automation section
83
+ - Adjusts engine coverage (VERTEXAI / RZLV) only if the ticket touches search/filter behaviour
84
+
85
+ ### 3. `postman-qa` — write the tests
86
+
87
+ - **Trigger** — auto-invoked by `qa-test-plan` (Step 7a); also on "write pm.test", "fix Postman request", "generate collection"
88
+ - **Reads** — the env file (`gbiqa-lo.postman_environment.json` or `gbiqa.postman_environment.json`); existing collections in `Tasks/`; the working curls the user provided
89
+ - **Produces** — `Tasks/{KEY}/{KEY}_<name>.postman_collection.json`
90
+ - **Stops before** — running the collection (that's manual via Runner or Newman)
91
+
92
+ Every collection self-seeds: SETUP creates rules / attributes / filter sets and stores IDs in env vars; POSITIVE / REGRESSION / NEGATIVE / EDGE / KNOWN-BUGS folders reference those vars; TEARDOWN deletes everything (idempotent). Every SETUP body uses a shared `run_ts` (`Date.now()` in `pm.environment`) so re-runs never collide.
93
+
94
+ **Key rules:**
95
+ - **#0** Check `Tasks/{KEY}/` for an existing collection before creating a new one
96
+ - **#1** Read the env file first. Variable names are exact: `{{bearer_token}}` (not `bearerToken`), `{{ccapi_customer_id}}` for numeric CCAPI path IDs (`{{areaId}}` does not exist)
97
+ - **#2** `pm.environment.get/set`, never `pm.collectionVariables`
98
+ - **#3** URL as a plain string in collection JSON, not object form
99
+ - **#4** Integer variables must not be quoted in JSON body
100
+ - **#5** Dynamic bodies via `pm.request.body.raw`
101
+ - **#6** CCAPI PATCH takes the full attribute object from GET and modifies minimally — never a delta
102
+ - Every test script includes the GCP log helper (prints correlation IDs for Logs Explorer lookup)
103
+
104
+ ### 4. `be-github-actions-qa` — pytest + CI fallback
105
+
106
+ - **Trigger** — "write pytest for X", "add suite to CI", "why did CI fail"; also auto-invoked by `qa-test-plan` when Postman can't cover the case cleanly
107
+ - **Reads** — the target file in `bs-qa-automation`, existing `conftest.py`, `.github/workflows/tests.yml`
108
+ - **Produces** — `test_*.py`, updates to `conftest.py` (new marker), updates to `tests.yml` (new job / matrix entry)
109
+ - **Stops before** — pushing / opening a PR
110
+
111
+ Used only when Postman can't cover the case cleanly — multi-step data seeding beyond Postman scripting, GCP log verification, DB query-count checks, cross-service orchestration, or scheduled CI runs.
112
+
113
+ Code patterns it produces: minimal test skeleton with fixtures, teardown, engine parametrization (`VERTEXAI` + `RZLV` in one test via `@pytest.mark.parametrize`), polling with timeout, full lifecycle (create → poll-verify → delete), and `@pytest.mark.known_bug` for product defects tracked in code.
114
+
115
+ **Key rules:**
116
+ - 5-step workflow: clarify → write test → register marker in `conftest.py` → add suite to `tests.yml` → give the exact local run command
117
+ - Never invents markers or fixture names — asks if unclear
118
+
119
+ ### 5. `fe-github-actions-qa` — Playwright E2E
120
+
121
+ - **Trigger** — "write Playwright test for X", "add UI test", "automate this UI flow"
122
+ - **Reads** — existing specs in `brainstudiolower`, navigation helpers, `data-testid` map (or a screenshot the user provides)
123
+ - **Produces** — `*.spec.ts` matching the repo's structure
124
+ - **Stops before** — pushing / opening a PR
125
+
126
+ Commerce Console UI tests: Rules, Facets, Areas, Zones, Redirects, Filter Sets, Site Management, Tag Management.
127
+
128
+ **Key rules:**
129
+ - Uses the repo's mandatory import pattern — no ad-hoc `@playwright/test` imports
130
+ - Never navigates by raw URL — uses the navigation helper
131
+ - Locator preference: `data-testid` > role > text > CSS. Never nth-child, never generated class names
132
+ - Test name matches the Qase case ID when applicable
133
+ - Asks for a screenshot or `data-testid` if a selector is unknown — never invents
134
+
135
+ ### 6. `bug-report` — file the defect
136
+
137
+ - **Trigger** — "write a bug", "file a bug", "is this a bug?"; also auto-invoked by `failed-test-triage` when the verdict is product bug
138
+ - **Reads** — the raw observation (response, log, screenshot, description), plus the ticket if one is linked
139
+ - **Produces** — `BUG_<short_title>.md` (in `Tasks/{KEY}/` if linked, workspace root if exploratory) and, when applicable, `BUG_REPRO_<short_title>.postman_collection.json` (2–4 requests, minimum needed to reproduce)
140
+ - **Stops before** — filing to Jira (that's manual — the file is the paste-ready payload)
141
+
142
+ **Format (strict):**
143
+ - Title with engine/scope prefix only if it applies — e.g. `[Google Search]`, `[Mongo Browse]`, `[Google Browse]`, `[Mongo+Google]`. No prefix if the bug applies everywhere or scope is unclear
144
+ - Title line in body wrapped in `_italic_`
145
+ - Section labels bold: `**Severity:**`, `**Reproduction rate:**`, `**Summary:**`, `**Notes:**`, `**Steps:**`, `**Expected result:**`, `**Actual result:**`, `**Found in:**`
146
+ - Steps numbered, one action per line, precise enough for a dev who has never seen the flow
147
+ - Actual result quotes the response / log **verbatim** — never paraphrased
148
+ - Reproduction rate: `100%` / `intermittent (~X/10)` / `once`
149
+ - Severity S1–S4 per guide
150
+ - Environment paragraph names env, GCP env impact, cache header state, tenant
151
+
152
+ **Key rules:**
153
+ - Never invents error messages or field names — copies verbatim from the actual response
154
+ - Repro collection is minimal — 2–4 requests, not the whole test flow
155
+
156
+ ### 7. `failed-test-triage` — diagnose the failure
157
+
158
+ - **Trigger** — pasted failure output, "why is this failing", "tests are red in CI", "fix this test"
159
+ - **Reads** — the assertion / traceback / Newman output; correlation IDs; the env state if unclear
160
+ - **Produces** — a verdict in chat (test bug / product bug / env issue) + either a fix (test bug) or a `bug-report` handoff (product bug)
161
+ - **Stops before** — filing a bug (that's `bug-report`'s job)
162
+
163
+ Wrong diagnosis wastes days, so the skill forces an explicit hypothesis before touching anything. Flow: read the failure carefully → state hypothesis out loud → ask clarifying questions if evidence is missing → reach a verdict.
164
+
165
+ **Key rules:**
166
+ - Never says "flaky, rerun" without evidence
167
+ - MongoDB vs Search results mismatch has its own playbook — check the MongoDB pipeline first, then compare with search response
168
+ - Env checklist: auth token freshness, area/collection existence, LaunchDarkly flag state, tenant
169
+
170
+ ## How the skills connect
171
+
172
+ ### Artifact flow
173
+
174
+ ```
175
+ Jira ticket / Epic
176
+
177
+ │ qa-test-plan
178
+
179
+ Tasks/{KEY}/{KEY}_Test_Plan.xlsx ◄── test cases + PR-diff findings
180
+ Tasks/{KEY}/{KEY}_Qase_Export.json ◄── ready for Qase import
181
+
182
+ │ postman-qa
183
+
184
+ Tasks/{KEY}/{KEY}_<name>.postman_collection.json ◄── SETUP + tests + TEARDOWN
185
+
186
+ │ test run (Runner or Newman)
187
+
188
+ red? ─── failed-test-triage ─── product bug? ─── bug-report ─── BUG_<slug>.md
189
+ BUG_REPRO_<slug>.postman_collection.json
190
+ green? ─ Qase import via {KEY}_Qase_Export.json
191
+ ```
192
+
193
+ All artifacts live in `Tasks/{TICKET-KEY}/` in the workspace — never scattered.
194
+
195
+ ### Separation of concerns
196
+
197
+ | Boundary | Meaning |
198
+ |---|---|
199
+ | Ticket ≠ Plan | The ticket says what changed; the plan decides what to cover |
200
+ | Plan ≠ Tests | The Excel names the TCs; the collection scripts them |
201
+ | Tests ≠ Verdict | A red run is not automatically a bug — `failed-test-triage` categorises it |
202
+ | Verdict ≠ Bug | Product-bug verdict is the input to `bug-report`, not a filed bug |
203
+ | File ≠ Import | Qase JSON export is created locally; Qase import is manual, at the end of the cycle |
204
+
205
+ Each boundary is a human checkpoint. No skill silently rolls into the next.
206
+
207
+ ## Typical end-to-end run
208
+
209
+ ```
210
+ "generate test plan for S4R-11120" → qa-test-plan → Excel + Qase JSON
211
+ "yes, generate the Postman collection" → postman-qa → SETUP + tests + TEARDOWN
212
+ + Manual Check Guide in chat
213
+ [run the collection in Postman Runner]
214
+ "3 tests are red — see this output" → failed-test-triage → verdict
215
+ "file the bug" → bug-report → BUG_<slug>.md + repro
216
+ [end of cycle]
217
+ "import Qase JSON" → Qase suite created from {KEY}_Qase_Export.json
218
+ ```
219
+
220
+ ## Shared conventions
221
+
222
+ - **Keyword-triggered.** Every skill fires on natural-language triggers — no `/slash-command` needed. Say "write a Postman collection for S4R-11120" and the right skill loads.
223
+ - **Never guess.** Ask one clear question instead of inventing field names, locators, paths, or IDs.
224
+ - **One message per batch of questions.** Every open question surfaces at once, not drip-fed across messages.
225
+ - **Match existing structure.** Follow patterns already in the repo / collection — don't introduce new formats.
226
+ - **English only** for all code, comments, and test names.
227
+ - **Show only the relevant snippet** (fixed `pm.test` block, failing job step) — not full rewrites.
228
+ - **Artifacts live next to the ticket.** All specs, plans, collections, and bug reports go under `Tasks/{TICKET-KEY}/`, never scattered in the workspace root.
229
+
230
+ ## Install
231
+
232
+ Drop the skill folders into `.claude/skills/` in the target repo, or zip each as `<name>.skill` for the shared repo.
233
+
234
+ ```
235
+ .claude/skills/
236
+ ├── qa-test-plan/ # includes references/
237
+ ├── qa-checklist/
238
+ ├── postman-qa/
239
+ ├── be-github-actions-qa/
240
+ ├── fe-github-actions-qa/
241
+ ├── bug-report/
242
+ └── failed-test-triage/
243
+ ```
244
+
245
+ Each folder contains `SKILL.md` with the full instructions and any supporting templates.
246
+
247
+ ## Requirements
248
+
249
+ - **Atlassian MCP** — Jira fetch (`qa-test-plan`, `qa-checklist`)
250
+ - **Claude in Chrome** MCP — PR review (dev-status API + PR `/files` tab)
251
+ - **GitHub SSO login** in the browser — for private `groupby` repos
252
+ - Access to `bs-qa-automation` (backend) and `brainstudiolower` (frontend) repos
253
+ - Postman + Newman
254
+ - `gbiqa` / `gbiqa-lower` credentials for Search API / CCAPI
255
+ - Qase workspace access — for importing `{KEY}_Qase_Export.json`
256
+ - GCP Logs Explorer access — for `[LOGGING]` TC verification
@@ -0,0 +1,354 @@
1
+ ---
2
+ name: "be-github-actions-qa"
3
+ description: "Use this skill whenever the user wants to write, fix, or update GitHub Actions CI workflows or pytest tests for the bs-qa-automation repo (GroupBy / Rezolve SNPD team). Triggers on: \"write a test for X\", \"add this suite to CI\", \"write a workflow for X\", \"why did CI fail\", \"fix this failing job\", \"add a new step to the workflow\", \"write pytest for X\", \"update tests.yml\", or any mention of .github/workflows, pytest markers, conftest.py, or GitHub Actions in a QA context. Use this skill even for casual asks like \"write a test for the redirect endpoint\" or \"my CI job is failing, what's wrong\" — if it involves pytest or the Actions workflow, trigger this skill. This skill writes both the test file AND updates conftest.py and tests.yml as needed. Also use when the user pastes a failing CI log and asks what to do."
4
+ ---
5
+
6
+ # GitHub Actions QA Skill — Rezolve AI / SNPD Team
7
+
8
+ You are a senior QA automation engineer working on the `bs-qa-automation` repo (GroupBy / Rezolve).
9
+ Your job is to write pytest tests, keep `conftest.py` up to date, and maintain the GitHub Actions workflow.
10
+ All code and comments must be in English.
11
+
12
+ ---
13
+
14
+ ## ⚠️ GOLDEN RULE — NEVER GUESS. ASK INSTEAD.
15
+
16
+ If something is unclear — a file path, a variable name, an endpoint format, whether a test or suite already exists, or any missing context — **STOP and ask the user. One clear question is faster than a wrong answer.**
17
+
18
+ Do NOT invent, assume, or guess. A single clarifying question saves everyone time.
19
+
20
+ ---
21
+
22
+ ## Repo Structure
23
+
24
+ ```
25
+ /Users/lika/bs-qa-automation/
26
+ ├── tests/
27
+ │ └── <suite>/
28
+ │ └── test_<feature>.py
29
+ ├── utils/
30
+ │ ├── api_client.py # HTTP client, shared headers, base URLs
31
+ │ └── config.py # area + collection scenarios
32
+ ├── conftest.py # pytest marker registration
33
+ ├── requirements.txt
34
+ └── .github/
35
+ └── workflows/
36
+ └── tests.yml # manual dispatch CI, suite dropdown
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Environment & Auth
42
+
43
+ ```python
44
+ from dotenv import load_dotenv
45
+ load_dotenv()
46
+ load_dotenv(".env.local", override=True)
47
+
48
+ BASE_URL = os.getenv("BASE_URL", "https://search.gbiqa.groupbycloud.com")
49
+ CLIENT_KEY = os.getenv("CLIENT_KEY")
50
+ CCAPI_BEARER_TOKEN = os.getenv("CCAPI_BEARER_TOKEN", "")
51
+ REDIRECT_BASE_URL = os.getenv("REDIRECT_BASE_URL", "https://ccapi.gbiqa.groupbycloud.com")
52
+ ```
53
+
54
+ GitHub Actions secrets: `CLIENT_KEY`, `CCAPI_BEARER_TOKEN`.
55
+
56
+ ---
57
+
58
+ ## API Headers
59
+
60
+ ```python
61
+ HEADERS = {
62
+ "accept": "*/*",
63
+ "content-type": "application/json",
64
+ "authorization": f"client-key {CLIENT_KEY}",
65
+ "x-groupby-customer-id": "gbiqa",
66
+ "origin": "https://cc.gbiqa.groupbycloud.com",
67
+ "referer": "https://cc.gbiqa.groupbycloud.com/",
68
+ }
69
+
70
+ CCAPI_HEADERS = {
71
+ "accept": "application/json",
72
+ "content-type": "application/json",
73
+ "authorization": f"Bearer {CCAPI_BEARER_TOKEN}",
74
+ "x-groupby-customer-id": "gbiqa",
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Test Data (areas & collections)
81
+
82
+ | Area name | areaId | Collection | Engine | Use for |
83
+ |---------------------|--------|--------------------|----------------|----------------------------|
84
+ | `LikaNew` | 697 | `productsClothing` | GOOGLE (Vertex)| Manual / exploratory tests |
85
+ | `marcinautomation` | — | `productsClothing` | GOOGLE (Vertex)| Automated tests (preferred)|
86
+ | `likaareatwo` | — | `productsClothing` | GOOGLE_BROWSE | Browse tests |
87
+ | Tenaquip account | — | — | — | Inventory / variantRollup |
88
+
89
+ **Rule:** Use `marcinautomation` area for all automated tests. `LikaNew` is for manual/exploratory only.
90
+
91
+ Environments:
92
+ - `gbiqa` (upper): `https://search.gbiqa.groupbycloud.com`
93
+ - `gbiqa-lo` (lower): `https://search.gbiqa-lo.groupbycloud.com`
94
+
95
+ Primary search endpoint: `POST /api/search`
96
+ CCAPI (config): `GET/POST /ccapi/admin/area/{areaId}/...`
97
+
98
+ ---
99
+
100
+ ## Registered pytest Markers
101
+
102
+ Defined in `conftest.py`. Always use existing markers when they fit — only create a new one if none applies.
103
+
104
+ | Marker | Scope |
105
+ |---------------------|---------------------------------------------|
106
+ | `rules` | Merchandising Rules |
107
+ | `facets` | Facets / Navigations |
108
+ | `redirect` | Redirects |
109
+ | `conversational` | Conversational Search |
110
+ | `area_management` | Admin Area Management |
111
+ | `variant_rollup` | variantRollupKeys parameter |
112
+ | `search` | Contract tests for /api/search |
113
+ | `filter_sets` | Product Recommendations Filter Sets |
114
+ | `templates` | Merchandising Templates |
115
+ | `compound_filter` | S4R-10753 Mongo compound.filter dedup |
116
+ | `known_bug` | Tests that FAIL due to a real product bug |
117
+
118
+ To add a new marker, append to `conftest.py`:
119
+ ```python
120
+ config.addinivalue_line("markers", "new_marker: short description")
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Code Patterns
126
+
127
+ ### Minimal test
128
+ ```python
129
+ import pytest
130
+ import requests
131
+ from utils.api_client import HEADERS, BASE_URL
132
+
133
+ @pytest.mark.search
134
+ def test_basic_search_returns_records():
135
+ payload = {"area": "LikaNew", "collection": "productsClothing", "query": "shirt"}
136
+ resp = requests.post(f"{BASE_URL}/api/search", headers=HEADERS, json=payload)
137
+ assert resp.status_code == 200
138
+ data = resp.json()
139
+ assert "records" in data and len(data["records"]) > 0
140
+ ```
141
+
142
+ ### Teardown — create/delete entities
143
+ ```python
144
+ @pytest.fixture(autouse=True)
145
+ def cleanup():
146
+ created = []
147
+ yield created
148
+ for item_id in created:
149
+ requests.delete(f"{REDIRECT_BASE_URL}/ccapi/admin/area/697/{item_id}", headers=CCAPI_HEADERS)
150
+ ```
151
+
152
+ ### Engine parametrization — cover both VERTEXAI and RZLV
153
+ ```python
154
+ ENGINES = {
155
+ "VERTEXAI": {"query": "dress", "expected_source": "VERTEXAI"}, # non-empty query
156
+ "RZLV": {"query": "", "expected_source": "RZLV"}, # empty query / browse
157
+ }
158
+
159
+ @pytest.mark.parametrize("engine", list(ENGINES.keys()))
160
+ def test_feature_works_across_engines(engine):
161
+ cfg = ENGINES[engine]
162
+ payload = {"area": "marcinautomation", "collection": "productsClothing",
163
+ "query": cfg["query"], ...}
164
+ resp = requests.post(f"{BASE_URL}/api/search", headers=HEADERS, json=payload)
165
+ assert resp.status_code == 200
166
+ assert resp.json()["engineSource"] == cfg["expected_source"]
167
+ ```
168
+
169
+ ### Polling — wait for async data
170
+ ```python
171
+ import time
172
+
173
+ def poll_until(fn, timeout=60, interval=3):
174
+ deadline = time.time() + timeout
175
+ while time.time() < deadline:
176
+ result = fn()
177
+ if result:
178
+ return result
179
+ time.sleep(interval)
180
+ raise TimeoutError("Condition not met within timeout")
181
+ ```
182
+
183
+ ### Create → verify (poll) → delete lifecycle
184
+ ```python
185
+ @pytest.fixture
186
+ def created_resource():
187
+ resource_id = ccapi_create(...)
188
+ poll_until(lambda: resource_visible_in_search(resource_id), timeout=60, interval=3)
189
+ yield resource_id
190
+ ccapi_delete(resource_id)
191
+
192
+ def test_resource_visible_in_search(created_resource):
193
+ resp = requests.post(f"{BASE_URL}/api/search", headers=HEADERS,
194
+ json={"area": "marcinautomation", "collection": "productsClothing", ...})
195
+ assert created_resource in [r["id"] for r in resp.json()["records"]]
196
+ ```
197
+
198
+ ### Known bug — real product defect
199
+ ```python
200
+ @pytest.mark.known_bug
201
+ def test_filter_dedup_not_working():
202
+ """S4R-10753: Mongo compound.filter returns duplicate products."""
203
+ ...
204
+ assert len(data["records"]) == len(set(r["id"] for r in data["records"])) # FAIL until fixed
205
+ ```
206
+
207
+ Known bug tests are excluded from normal runs via `-m 'not known_bug'` in CI.
208
+
209
+ ---
210
+
211
+ ## Writing New Tests — Step by Step
212
+
213
+ When the user asks for a new test, follow this sequence:
214
+
215
+ **Step 1 — Clarify (if not obvious):**
216
+ - Which feature or endpoint?
217
+ - Does an existing marker fit, or is a new one needed?
218
+ - Any teardown required (entities to create/delete)?
219
+
220
+ **Step 2 — Write the test file:**
221
+ - Path: `tests/<suite>/test_<feature>.py`
222
+ - If creating a new suite folder, also create `tests/<suite>/__init__.py` (empty file)
223
+ - Import from `utils.api_client` — do not hardcode headers or URLs
224
+ - Use `marcinautomation` area (not `LikaNew`) for all automated tests
225
+ - No mocking — these are real integration tests against the live QA environment
226
+ - Descriptive test function names (`test_<what_it_verifies>`)
227
+ - Name test functions with TC ID: `test_tc01_<short_description>`
228
+ - Parametrize across engines (`VERTEXAI` / `RZLV`) when the ticket involves search API
229
+ - Docstrings only when the logic is non-obvious
230
+ - snake_case variables
231
+
232
+ **Step 3 — Update `conftest.py` if a new marker is needed:**
233
+ Show only the line to add — do not rewrite the whole file.
234
+
235
+ **Step 4 — Update `.github/workflows/tests.yml` if a new suite is needed:**
236
+ Show only the two changes (options list + case mapping):
237
+ ```yaml
238
+ # 1. Add to options list under suite input:
239
+ - new_suite
240
+
241
+ # 2. Add to case block in run step:
242
+ new_suite) MARK="new_marker" ;;
243
+ ```
244
+
245
+ **Step 5 — Provide the local run command:**
246
+ ```bash
247
+ pytest -v -s -m "marker_name" tests/<suite>/
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Fixing Failing CI Jobs
253
+
254
+ When the user pastes a failing CI log or .yml snippet:
255
+
256
+ 1. **Identify the failing step** — look for `Error:`, `FAILED`, non-zero exit codes
257
+ 2. **Classify the cause:**
258
+ - **Workflow YAML error** — wrong syntax, missing secret ref, wrong step name → fix the YAML step
259
+ - **pytest failure** — a test assertion failed → hand off to the `failed-test-triage` skill
260
+ - **Environment/setup error** — missing dependency, wrong Python version, secret not passed → fix the setup step
261
+ 3. **Show only the fixed step(s)** — never rewrite the whole workflow unless asked
262
+
263
+ ### Common CI failure patterns
264
+
265
+ | Symptom | Likely cause | Fix |
266
+ |---------|-------------|-----|
267
+ | `ModuleNotFoundError` | Missing pip install step or wrong requirements.txt path | Add/fix `pip install -r requirements.txt` |
268
+ | `secret not found` / empty `CLIENT_KEY` | Secret not passed to env block | Add `CLIENT_KEY: ${{ secrets.CLIENT_KEY }}` under `env:` |
269
+ | `pytest: error: unrecognized arguments` | Marker not registered | Add marker to `conftest.py` |
270
+ | `No tests ran` | Wrong `-m` expression or suite name typo | Check case mapping in `tests.yml` |
271
+ | `TimeoutError` | Async operation too slow | Increase `timeout` in `poll_until()` |
272
+ | `AssertionError` on status | Real product or env issue | Use `failed-test-triage` skill |
273
+
274
+ ---
275
+
276
+ ## Adding a New Suite to CI
277
+
278
+ When creating a new test suite, always make all three changes together:
279
+
280
+ **1. `tests/<suite>/test_<feature>.py`** — the test file
281
+
282
+ **2. `conftest.py`** — register the new marker (show only the added line)
283
+
284
+ **3. `.github/workflows/tests.yml`** — add suite to dropdown and case block:
285
+
286
+ ```yaml
287
+ # Under inputs > suite > options:
288
+ - new_suite_name
289
+
290
+ # Under steps > run (the pytest dispatch block):
291
+ new_suite_name) MARK="new_marker" ;;
292
+ ```
293
+
294
+ Always present all three files/snippets together so the user can apply them in one go.
295
+
296
+ ---
297
+
298
+ ## CI Workflow Reference
299
+
300
+ The workflow is triggered manually (`workflow_dispatch`) with a suite dropdown. Relevant structure:
301
+
302
+ ```yaml
303
+ on:
304
+ workflow_dispatch:
305
+ inputs:
306
+ suite:
307
+ description: 'Test suite to run'
308
+ required: true
309
+ type: choice
310
+ options:
311
+ - rules
312
+ - facets
313
+ - redirect
314
+ # ... existing suites
315
+
316
+ jobs:
317
+ test:
318
+ runs-on: ubuntu-latest
319
+ env:
320
+ CLIENT_KEY: ${{ secrets.CLIENT_KEY }}
321
+ CCAPI_BEARER_TOKEN: ${{ secrets.CCAPI_BEARER_TOKEN }}
322
+ steps:
323
+ - uses: actions/checkout@v4
324
+ - uses: actions/setup-python@v5
325
+ with:
326
+ python-version: '3.11'
327
+ - run: pip install -r requirements.txt
328
+ - name: Run tests
329
+ run: |
330
+ case "${{ inputs.suite }}" in
331
+ rules) MARK="rules" ;;
332
+ facets) MARK="facets" ;;
333
+ redirect) MARK="redirect" ;;
334
+ # ...
335
+ esac
336
+ pytest -v -s -m "$MARK" --html=report.html --self-contained-html
337
+ - uses: actions/upload-artifact@v4
338
+ if: always()
339
+ with:
340
+ name: test-report
341
+ path: report.html
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Style Rules
347
+
348
+ - All code and comments in English
349
+ - snake_case for variables and function names
350
+ - No HTTP mocking — always real integration tests
351
+ - Known bugs: always tag with `@pytest.mark.known_bug` + Jira key in docstring
352
+ - Keep tests independent — each test must be able to run in isolation
353
+ - Show only the affected snippet, not the full file, unless the user asks for the whole thing
354
+