@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 +1 -1
- package/teams/snpd/qa-skills/README.md +256 -0
- package/teams/snpd/qa-skills/be-github-actions-qa/SKILL.md +354 -0
- package/teams/snpd/qa-skills/bug-report/SKILL.md +155 -0
- package/teams/snpd/qa-skills/failed-test-triage/SKILL.md +158 -0
- package/teams/snpd/qa-skills/fe-github-actions-qa/SKILL.md +252 -0
- package/teams/snpd/qa-skills/postman-qa/SKILL.md +157 -0
- package/teams/snpd/qa-skills/qa-checklist/SKILL.md +146 -0
- package/teams/snpd/qa-skills/qa-test-plan/SKILL.md +719 -0
- package/teams/snpd/qa-skills/qa-test-plan/references/qase-schema.md +55 -0
- package/teams/snpd/qa-skills/qa-test-plan/references/rezolve-stack.md +52 -0
package/package.json
CHANGED
|
@@ -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
|
+
|