@brunosps00/dev-workflow 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/scaffold/en/commands/dw-fix-qa.md +34 -13
- package/scaffold/en/commands/dw-run-qa.md +124 -23
- package/scaffold/pt-br/commands/dw-fix-qa.md +34 -13
- package/scaffold/pt-br/commands/dw-run-qa.md +124 -23
- package/scaffold/skills/api-testing-recipes/SKILL.md +104 -0
- package/scaffold/skills/api-testing-recipes/recipes/dotnet-webapp-factory.md +168 -0
- package/scaffold/skills/api-testing-recipes/recipes/http-rest-client.md +130 -0
- package/scaffold/skills/api-testing-recipes/recipes/pytest-httpx.md +157 -0
- package/scaffold/skills/api-testing-recipes/recipes/rust-reqwest.md +173 -0
- package/scaffold/skills/api-testing-recipes/recipes/supertest-node.md +153 -0
- package/scaffold/skills/api-testing-recipes/references/auth-patterns.md +138 -0
- package/scaffold/skills/api-testing-recipes/references/log-conventions.md +117 -0
- package/scaffold/skills/api-testing-recipes/references/matrix-conventions.md +68 -0
- package/scaffold/skills/api-testing-recipes/references/openapi-driven.md +97 -0
|
@@ -9,7 +9,7 @@ Você é um assistente IA especializado em Quality Assurance. Sua tarefa é vali
|
|
|
9
9
|
## Posição no Pipeline
|
|
10
10
|
**Antecessor:** `/dw-run-plan` ou `/dw-run-task` | **Sucessor:** `/dw-code-review` (auto-fixes bugs internally before completing)
|
|
11
11
|
|
|
12
|
-
<critical>
|
|
12
|
+
<critical>Em modo UI, use o Playwright MCP para todos os testes E2E. Em modo API (sem UI no projeto OU flag `--api`), use a skill bundled `api-testing-recipes` para gerar scripts `.http` / pytest+httpx / supertest / WebApplicationFactory / reqwest e capturar logs de request/response como evidência.</critical>
|
|
13
13
|
<critical>Verifique TODOS os requisitos do PRD e TechSpec antes de aprovar</critical>
|
|
14
14
|
<critical>O QA NÃO está completo até que TODAS as verificações passem</critical>
|
|
15
15
|
<critical>Documente TODOS os bugs encontrados com screenshots de evidência</critical>
|
|
@@ -20,9 +20,10 @@ Você é um assistente IA especializado em Quality Assurance. Sua tarefa é vali
|
|
|
20
20
|
|
|
21
21
|
Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apoio operacional sem substituir este comando:
|
|
22
22
|
|
|
23
|
-
- `webapp-testing`: apoio para estruturar fluxos de teste, retestes, screenshots e logs quando complementar ao Playwright MCP
|
|
24
|
-
- `vercel-react-best-practices`: use apenas se o frontend sob teste for React/Next.js e houver indicação de regressão relacionada a renderização, fetching, hidratação ou performance percebida
|
|
25
|
-
- `ui-ux-pro-max`: use quando validar consistência de design, paletas de cores, tipografia, espaçamento e hierarquia visual contra padrões da indústria
|
|
23
|
+
- `webapp-testing`: (modo UI) apoio para estruturar fluxos de teste, retestes, screenshots e logs quando complementar ao Playwright MCP
|
|
24
|
+
- `vercel-react-best-practices`: (modo UI) use apenas se o frontend sob teste for React/Next.js e houver indicação de regressão relacionada a renderização, fetching, hidratação ou performance percebida
|
|
25
|
+
- `ui-ux-pro-max`: (modo UI) use quando validar consistência de design, paletas de cores, tipografia, espaçamento e hierarquia visual contra padrões da indústria
|
|
26
|
+
- `api-testing-recipes`: **(modo API — SEMPRE)** snippets validados para `.http`, pytest+httpx, supertest, WebApplicationFactory, reqwest. Compõe um arquivo de teste por RF em `QA/scripts/api/` e logs JSONL em `QA/logs/api/` segundo seus references
|
|
26
27
|
|
|
27
28
|
## Ferramentas de Análise
|
|
28
29
|
|
|
@@ -38,12 +39,13 @@ Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apo
|
|
|
38
39
|
## Objetivos
|
|
39
40
|
|
|
40
41
|
1. Validar implementação contra PRD, TechSpec e Tasks
|
|
41
|
-
2.
|
|
42
|
-
3.
|
|
43
|
-
4.
|
|
44
|
-
5.
|
|
45
|
-
6.
|
|
46
|
-
7.
|
|
42
|
+
2. **Detectar modo** (UI vs API-only) e escolher o caminho de execução certo
|
|
43
|
+
3. Executar testes E2E via Playwright MCP (modo UI) OU via skill `api-testing-recipes` (modo API)
|
|
44
|
+
4. Cobrir cenários positivos, negativos, limites e regressões relevantes
|
|
45
|
+
5. Verificar acessibilidade (modo UI = WCAG 2.2; modo API = formato de erro e contratos de superfície)
|
|
46
|
+
6. Realizar verificações visuais (somente modo UI — pulado em modo API)
|
|
47
|
+
7. Documentar bugs encontrados
|
|
48
|
+
8. Gerar relatório final de QA
|
|
47
49
|
|
|
48
50
|
## Localização dos Arquivos
|
|
49
51
|
|
|
@@ -56,10 +58,13 @@ Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apo
|
|
|
56
58
|
- Pasta de evidências QA (obrigatória): `{{PRD_PATH}}/QA/`
|
|
57
59
|
- Relatório de Saída: `{{PRD_PATH}}/QA/qa-report.md`
|
|
58
60
|
- Bugs encontrados: `{{PRD_PATH}}/QA/bugs.md`
|
|
59
|
-
- Screenshots: `{{PRD_PATH}}/QA/screenshots/`
|
|
60
|
-
- Logs (console/rede): `{{PRD_PATH}}/QA/logs/`
|
|
61
|
-
-
|
|
61
|
+
- Screenshots (modo UI): `{{PRD_PATH}}/QA/screenshots/`
|
|
62
|
+
- Logs — UI (console/rede): `{{PRD_PATH}}/QA/logs/`
|
|
63
|
+
- Logs — API (JSONL request/response): `{{PRD_PATH}}/QA/logs/api/`
|
|
64
|
+
- Scripts de teste Playwright (modo UI): `{{PRD_PATH}}/QA/scripts/`
|
|
65
|
+
- Scripts de teste API (modo API — `.http` / pytest+httpx / supertest / etc.): `{{PRD_PATH}}/QA/scripts/api/`
|
|
62
66
|
- Checklist consolidado: `{{PRD_PATH}}/QA/checklist.md`
|
|
67
|
+
- Receitas de API testing (skill): `.agents/skills/api-testing-recipes/`
|
|
63
68
|
|
|
64
69
|
## Contexto Multi-Projeto
|
|
65
70
|
|
|
@@ -74,6 +79,43 @@ Consulte `.dw/rules/` para URLs e frameworks específicos do projeto.
|
|
|
74
79
|
|
|
75
80
|
## Etapas do Processo
|
|
76
81
|
|
|
82
|
+
### 0. Detecção de Modo (UI vs API) — Obrigatório PRIMEIRO
|
|
83
|
+
|
|
84
|
+
Decida se o projeto tem UI testável ou e API-only antes de qualquer setup de browser/API. O modo escolhido dirige todas as etapas seguintes.
|
|
85
|
+
|
|
86
|
+
**Auto-detecção (mesma matriz usada por `/dw-dockerize`):**
|
|
87
|
+
|
|
88
|
+
| Sinal | Modo UI | Modo API |
|
|
89
|
+
|-------|---------|----------|
|
|
90
|
+
| `package.json` deps | `next`, `vite`, `react`, `vue`, `svelte`, `@angular/*`, `nuxt`, `astro`, `solid-js`, `remix` | nenhum dos acima |
|
|
91
|
+
| `pyproject.toml` / `requirements*.txt` | `jinja2`, `django` (full), `flask` + `flask_login`/`render_template` | `fastapi`, `flask` (so JSON), `starlette`, `litestar` |
|
|
92
|
+
| `*.csproj` | `Microsoft.AspNetCore.Mvc`, Razor, Blazor | `Microsoft.AspNetCore.Mvc.Core` so, templates de minimal API |
|
|
93
|
+
| `Cargo.toml` | `yew`, `leptos`, `dioxus`, `sycamore` | `axum`, `actix-web`, `rocket`, `warp` (sem template engine) |
|
|
94
|
+
|
|
95
|
+
Se NENHUM sinal de UI bater → **modo API**. Se pelo menos um bate → **modo UI** (default).
|
|
96
|
+
|
|
97
|
+
**Override manual (flags):**
|
|
98
|
+
|
|
99
|
+
- `--api` força modo API (útil para rodar testes API headless dentro de um projeto fullstack onde a UI nao importa nesta rodada).
|
|
100
|
+
- `--ui` força modo UI (gera erro claro se nenhuma dep de UI for detectada — evita rodar testes de browser contra repo backend-only sem querer).
|
|
101
|
+
- `--from-openapi <path-or-url>` adiciona baseline OpenAPI em cima do modo API (veja `.agents/skills/api-testing-recipes/references/openapi-driven.md`).
|
|
102
|
+
|
|
103
|
+
**Efeito nas etapas seguintes:**
|
|
104
|
+
|
|
105
|
+
| Etapa | Modo UI | Modo API |
|
|
106
|
+
|-------|---------|----------|
|
|
107
|
+
| 2 — Preparação do Ambiente | Playwright + browser setup completo | Setup de cliente API, sem browser; cria `QA/scripts/api/` e `QA/logs/api/` |
|
|
108
|
+
| 3 — Verificação de Páginas do Menu | obrigatório, bloqueante | **pulado** |
|
|
109
|
+
| 4 — Testes E2E | Playwright MCP | skill `api-testing-recipes` (recipe por stack) |
|
|
110
|
+
| 5 — Acessibilidade | WCAG 2.2 com browser tools | checks de superfície API (formato de erro, semântica de status, detecção de leak) |
|
|
111
|
+
| 6 — Verificações Visuais | obrigatório (mobile + desktop) | **pulado** |
|
|
112
|
+
| 7-8 — Documentação de Bugs + Relatório | screenshots como evidência | logs JSONL como evidência (`evidence_type: api-log`) |
|
|
113
|
+
| 9 — Loop Fix-Retest | mesmo formato; replay do Playwright | mesmo formato; replay da recipe e gravação de nova linha de log |
|
|
114
|
+
|
|
115
|
+
Registre o modo escolhido no frontmatter do relatório QA (`mode: ui | api | mixed`). Em caso de dúvida, pergunte ao usuário antes de prosseguir — nunca caia em fallback silencioso.
|
|
116
|
+
|
|
117
|
+
<critical>Se nenhum sinal de UI nem de API for detectável (ex.: repo vazio), aborte com: "Não é possivel determinar o modo do QA. Rode `/dw-analyze-project` primeiro OU passe `--ui` ou `--api` explicitamente."</critical>
|
|
118
|
+
|
|
77
119
|
### 1. Análise de Documentação (Obrigatório)
|
|
78
120
|
|
|
79
121
|
- Ler o PRD e extrair TODOS os requisitos funcionais numerados (RF-XX)
|
|
@@ -109,9 +151,11 @@ Se NENHUMA credencial for encontrada, PARE e pergunte ao usuário antes de conti
|
|
|
109
151
|
- Confirmar que a página carregou corretamente com `browser_snapshot`
|
|
110
152
|
- Se sessão persistente, import de auth, inspeção de rede além do MCP ou reprodução browser-first forem necessários, complementar com `webapp-testing`
|
|
111
153
|
|
|
112
|
-
### 3. Verificação de Páginas do Menu (
|
|
154
|
+
### 3. Verificação de Páginas do Menu (Somente modo UI — Obrigatório, Executar ANTES dos testes de RF)
|
|
113
155
|
|
|
114
|
-
|
|
156
|
+
**Em modo API, esta etapa é PULADA.** Superfícies de API não têm menus; o check equivalente (todo endpoint anunciado existe e responde) está dobrado dentro da Etapa 4-API.
|
|
157
|
+
|
|
158
|
+
<critical>(modo UI) ANTES de testar RFs individuais, verificar que CADA item do menu do módulo leva a uma página FUNCIONAL e ÚNICA. Esta verificação é bloqueante — se falhar, o QA NÃO pode ser aprovado.</critical>
|
|
115
159
|
|
|
116
160
|
Para cada item do menu do módulo:
|
|
117
161
|
1. Navegar para a página via `browser_navigate`
|
|
@@ -146,7 +190,11 @@ digraph menu_check {
|
|
|
146
190
|
}
|
|
147
191
|
```
|
|
148
192
|
|
|
149
|
-
### 4. Testes E2E
|
|
193
|
+
### 4. Testes E2E (Obrigatório, mode-aware)
|
|
194
|
+
|
|
195
|
+
Esta etapa tem dois branches; escolha conforme o modo da Etapa 0.
|
|
196
|
+
|
|
197
|
+
#### 4-UI (modo UI) — Playwright MCP
|
|
150
198
|
|
|
151
199
|
Utilize as ferramentas do Playwright MCP para testar cada fluxo:
|
|
152
200
|
|
|
@@ -179,6 +227,39 @@ Para cada requisito funcional do PRD:
|
|
|
179
227
|
<critical>Não basta validar apenas o caminho feliz. Cada requisito deve ser exercitado contra seus estados de borda e suas regressões mais prováveis</critical>
|
|
180
228
|
<critical>Se um requisito não puder ser completamente validado via E2E, o QA deve ser marcado como REJEITADO ou BLOQUEADO, nunca APROVADO</critical>
|
|
181
229
|
|
|
230
|
+
#### 4-API (modo API) — skill `api-testing-recipes`
|
|
231
|
+
|
|
232
|
+
Use a skill bundled `api-testing-recipes` para compor os testes. A skill escolhe a recipe certa por stack (default `.http` / REST Client; `pytest+httpx`, `supertest`, `WebApplicationFactory`, `reqwest` por linguagem) e grava scripts e logs JSONL como evidência.
|
|
233
|
+
|
|
234
|
+
Processo:
|
|
235
|
+
|
|
236
|
+
1. **Leia** `.agents/skills/api-testing-recipes/SKILL.md` e selecione a recipe que casa com o stack backend primário do projeto. Default em `recipes/http-rest-client.md` a menos que o projeto já rode `pytest`/`vitest`/`dotnet test`/`cargo test`, caso em que prefira a recipe especifica do stack para os testes QA viverem ao lado dos testes unitários.
|
|
237
|
+
2. **Para cada requisito funcional (RF-XX) do PRD**, derive a matriz seguindo `.agents/skills/api-testing-recipes/references/matrix-conventions.md`:
|
|
238
|
+
- 200 happy path
|
|
239
|
+
- 4xx — validação (campo faltando, tipo errado, fora de range)
|
|
240
|
+
- 4xx — auth (sem token, expirado, malformado)
|
|
241
|
+
- 4xx — autorização (token válido, role errada)
|
|
242
|
+
- 4xx — not found
|
|
243
|
+
- 4xx — conflict
|
|
244
|
+
- 5xx — server error (so se reproduzível sinteticamente)
|
|
245
|
+
- **Contract drift** (formato da response vs OpenAPI / TS types) — obrigatório
|
|
246
|
+
- **Authorization cross-tenant** (token de outra org) — obrigatório se multi-tenant
|
|
247
|
+
3. **Gere um arquivo por RF** em `{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].<ext>` usando a estrutura da recipe. Encaminhe credenciais segundo os padrões em `.agents/skills/api-testing-recipes/references/auth-patterns.md` (NUNCA hardcode tokens).
|
|
248
|
+
4. **Execute** cada request (`curl` para `.http`; o runner do projeto para stack-specific). Para CADA request, anexe uma linha JSONL em `{{PRD_PATH}}/QA/logs/api/RF-XX-[slug].log` segundo `references/log-conventions.md`. Redact headers `Authorization`/`Cookie`/`X-API-Key` e qualquer campo de response que case com `password*`/`secret*`/`*_hash`/`token*`.
|
|
249
|
+
5. **Asserte** por expectativa da matriz:
|
|
250
|
+
- Status code casa com o esperado
|
|
251
|
+
- Response body casa com o schema (use `jq` em `.http`, matchers do framework por stack)
|
|
252
|
+
- Headers obrigatórios presentes (ex.: `Content-Type: application/json`)
|
|
253
|
+
- Sem campos internos vazados
|
|
254
|
+
6. **Marque o requisito** como APROVADO ou REPROVADO com resumo de uma linha citando o caminho do log e (se REPROVADO) o número da linha JSONL que falhou.
|
|
255
|
+
7. **Opcional**: se o projeto expõe spec OpenAPI (`openapi.yaml`, `openapi.json`, runtime `/openapi.json`), siga `references/openapi-driven.md` para gerar baseline. Use a flag `--from-openapi <path-or-url>` para deixar explícito.
|
|
256
|
+
|
|
257
|
+
Nota sobre baseline OpenAPI: se `--from-openapi` for usado, os testes gerados ficam ao lado dos derivados a mão, com filename `openapi-RF-XX-[path-slug].<ext>`. Endpoints da spec sem mapeamento para nenhum RF viram lacuna documental no relatório QA (`openapi-no-rf-*`).
|
|
258
|
+
|
|
259
|
+
<critical>(modo API) Todo endpoint que muta ou lê dados tenant-scoped DEVE ter teste de negacao cross-tenant. Pular so e permitido em sistemas explicitamente single-tenant e tem que ser registrado como `pytest.skip`/`it.skip`/equivalente com motivo.</critical>
|
|
260
|
+
<critical>(modo API) Logs sao evidência. Toda afirmacao de PASS ou FAIL no relatorio QA deve citar uma linha JSONL em `QA/logs/api/`. Sem log = sem evidência = QA nao pode ser APROVADO.</critical>
|
|
261
|
+
<critical>(modo API) NUNCA hardcode tokens ou credenciais em scripts commitados. Use referencias `@variavel`/env-var.</critical>
|
|
262
|
+
|
|
182
263
|
### 4.1. Matriz mínima obrigatória por requisito
|
|
183
264
|
|
|
184
265
|
Para cada RF, o QA deve responder explicitamente:
|
|
@@ -201,9 +282,9 @@ Exemplos de edge cases que devem ser considerados sempre que relevantes:
|
|
|
201
282
|
- reentrada/ações repetidas
|
|
202
283
|
- falhas de API, loading e estados intermediários
|
|
203
284
|
|
|
204
|
-
### 5.
|
|
285
|
+
### 5. Acessibilidade / Checks de Superfície API (Obrigatório, mode-aware)
|
|
205
286
|
|
|
206
|
-
|
|
287
|
+
Em **modo UI**, verificar para cada tela/componente (WCAG 2.2):
|
|
207
288
|
|
|
208
289
|
- [ ] Navegação por teclado funciona (Tab, Enter, Escape)
|
|
209
290
|
- [ ] Elementos interativos têm labels descritivos
|
|
@@ -217,13 +298,26 @@ Verificar para cada tela/componente (WCAG 2.2):
|
|
|
217
298
|
Use `browser_press_key` para testar navegação por teclado.
|
|
218
299
|
Use `browser_snapshot` para verificar labels e estrutura semântica.
|
|
219
300
|
|
|
220
|
-
|
|
301
|
+
**Em modo API**, o checklist WCAG acima é SUBSTITUÍDO por checks de superfície API:
|
|
302
|
+
|
|
303
|
+
- [ ] Todo endpoint retorna o `Content-Type` correto
|
|
304
|
+
- [ ] Erros seguem formato consistente (ex.: `{ "error": { "code": "...", "message": "..." } }`)
|
|
305
|
+
- [ ] `401` (auth missing/invalid) é distinto de `403` (auth presente mas não autorizado)
|
|
306
|
+
- [ ] Responses de erro NÃO vazam stack traces, IDs internos, fragmentos SQL ou pistas de ambiente
|
|
307
|
+
- [ ] Campos sensíveis (`password*`, `*_hash`, `secret*`, `token*`) NUNCA aparecem em response body
|
|
308
|
+
- [ ] Endpoints com rate limit retornam `429` com header `Retry-After` (quando aplicável)
|
|
309
|
+
|
|
310
|
+
Cada check FALHADO vira bug HIGH em `QA/bugs.md` com `evidence_type: api-log` apontando para a linha JSONL do erro.
|
|
311
|
+
|
|
312
|
+
### 6. Verificações Visuais (Somente modo UI — Obrigatório)
|
|
313
|
+
|
|
314
|
+
**Em modo API, esta etapa é PULADA.** O relatório QA omite a seção "Visual" inteira.
|
|
221
315
|
|
|
222
316
|
- Capturar screenshots das telas principais com `browser_take_screenshot` e salvar em `{{PRD_PATH}}/QA/screenshots/`
|
|
223
317
|
- Verificar layouts em diferentes estados (vazio, com dados, erro, loading)
|
|
224
318
|
- Documentar inconsistências visuais encontradas
|
|
225
319
|
|
|
226
|
-
### 6.1. Validação Mobile (Obrigatório)
|
|
320
|
+
### 6.1. Validação Mobile (Somente modo UI — Obrigatório)
|
|
227
321
|
|
|
228
322
|
<critical>TODA verificação visual DEVE incluir testes em viewport mobile (375px) ALÉM do desktop (1440px). A aprovação do QA REQUER que AMBAS as resoluções estejam funcionais e visualmente aceitáveis. Se o layout mobile estiver quebrado, inutilizável ou visualmente degradado, o QA NÃO pode ser aprovado.</critical>
|
|
229
323
|
|
|
@@ -246,13 +340,15 @@ Para cada bug encontrado, criar entrada em `{{PRD_PATH}}/QA/bugs.md`:
|
|
|
246
340
|
|
|
247
341
|
- **Severidade:** Alta/Média/Baixa
|
|
248
342
|
- **RF Afetado:** RF-XX
|
|
249
|
-
- **Componente:** [componente/página]
|
|
343
|
+
- **Componente:** [componente/página ou caminho do endpoint]
|
|
344
|
+
- **Modo:** ui | api
|
|
250
345
|
- **Passos para Reproduzir:**
|
|
251
346
|
1. [passo 1]
|
|
252
347
|
2. [passo 2]
|
|
253
348
|
- **Resultado Esperado:** [o que deveria acontecer]
|
|
254
349
|
- **Resultado Atual:** [o que acontece]
|
|
255
|
-
- **
|
|
350
|
+
- **Tipo de evidência:** screenshot | api-log
|
|
351
|
+
- **Caminho da evidência:** `QA/screenshots/[arquivo].png` (modo UI) OU `QA/logs/api/RF-XX-[slug].log#L<linha>` (modo API)
|
|
256
352
|
- **Status:** Aberto
|
|
257
353
|
```
|
|
258
354
|
|
|
@@ -294,10 +390,15 @@ Gerar relatório em `{{PRD_PATH}}/QA/qa-report.md`:
|
|
|
294
390
|
[Parecer final do QA]
|
|
295
391
|
```
|
|
296
392
|
|
|
297
|
-
### 9. Loop QA Fix-Retest (Automático)
|
|
393
|
+
### 9. Loop QA Fix-Retest (Automático, mode-aware)
|
|
298
394
|
|
|
299
395
|
<critical>O QA NÃO termina no primeiro relatório. Se bugs forem encontrados, entre em um loop automático de fix-retest até que o QA seja APROVADO ou explicitamente BLOQUEADO.</critical>
|
|
300
396
|
|
|
397
|
+
**Comportamento mode-aware:** a estrutura do loop (max 5 ciclos, commit atômico por fix, regression checks, critérios de saída) é idêntica nos dois modos. O que muda é a EVIDÊNCIA replayada:
|
|
398
|
+
|
|
399
|
+
- modo UI: re-executar o fluxo Playwright, capturar nova screenshot `BUG-NN-retest.png`.
|
|
400
|
+
- modo API: re-executar a mesma `.http`/recipe via runner da recipe, anexar nova linha em `QA/logs/api/BUG-NN-retest.log` com `verdict: "PASS"` (fecha o bug) ou `verdict: "FAIL"` (segue o ciclo).
|
|
401
|
+
|
|
301
402
|
Após gerar o relatório inicial de QA:
|
|
302
403
|
|
|
303
404
|
```dot
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-testing-recipes
|
|
3
|
+
description: Validated API-testing snippets (.http, pytest+httpx, supertest, WebApplicationFactory, reqwest) used by /dw-run-qa and /dw-fix-qa when the project has no UI. Default format is .http (REST Client) for IDE portability.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Grep
|
|
8
|
+
- Glob
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# api-testing-recipes
|
|
12
|
+
|
|
13
|
+
Curated library of **API-testing snippets** that `/dw-run-qa` and `/dw-fix-qa` use when a project is API-only (no Playwright). Each recipe is a ready-to-customize block per stack; the default is `.http` (REST Client) for maximum portability across IDEs.
|
|
14
|
+
|
|
15
|
+
## Why a skill (not inline)
|
|
16
|
+
|
|
17
|
+
- Each recipe is independently maintainable. Bumping `pytest` or `supertest` patterns is a one-file change.
|
|
18
|
+
- Discoverable by AI agents in any project the user installs dev-workflow into.
|
|
19
|
+
- Reusable by future commands (e.g., `dw-bench-api`, `dw-contract-test`) without duplication.
|
|
20
|
+
|
|
21
|
+
## When to Use
|
|
22
|
+
|
|
23
|
+
Read this skill when:
|
|
24
|
+
|
|
25
|
+
- `/dw-run-qa` detected API mode (no UI deps in the manifest) or was invoked with `--api`.
|
|
26
|
+
- `/dw-fix-qa` is retesting a bug whose `evidence_type` is `api-log`.
|
|
27
|
+
- Generating a baseline test suite from an OpenAPI spec.
|
|
28
|
+
- Authoring contract checks against a backend.
|
|
29
|
+
|
|
30
|
+
Do NOT use when:
|
|
31
|
+
|
|
32
|
+
- The project has a UI and `/dw-run-qa` is in UI mode → use Playwright MCP instead.
|
|
33
|
+
- The user wants browser-level acceptance (forms, navigation, accessibility) — that's Playwright territory.
|
|
34
|
+
|
|
35
|
+
## Available Recipes
|
|
36
|
+
|
|
37
|
+
| Format | When to use | Recipe path |
|
|
38
|
+
|--------|-------------|-------------|
|
|
39
|
+
| `.http` (REST Client) — DEFAULT | Universal. Reads in VSCode (REST Client), JetBrains (HTTP Client), Neovim (rest.nvim, kulala), Zed. Stack-agnostic. Best for projects without an existing test runner, or when devs read tests in their IDE. | `recipes/http-rest-client.md` |
|
|
40
|
+
| `pytest + httpx` | Python project (FastAPI, Starlette, Flask). Already runs `pytest` in CI. Async client matches FastAPI's async-first design. | `recipes/pytest-httpx.md` |
|
|
41
|
+
| `supertest` (Node/TS) | Node/TS project (Fastify, Express, NestJS). Already runs `vitest`/`jest`. Integrates with the app's test setup. | `recipes/supertest-node.md` |
|
|
42
|
+
| `WebApplicationFactory<T>` (.NET) | C# project (ASP.NET Core minimal API or MVC). Built-in support for in-process testing without HTTP overhead. | `recipes/dotnet-webapp-factory.md` |
|
|
43
|
+
| `reqwest + tokio::test` (Rust) | Rust project (Axum, Actix-web, Rocket). Async client matches Axum's tower-based design. | `recipes/rust-reqwest.md` |
|
|
44
|
+
|
|
45
|
+
Picking order:
|
|
46
|
+
1. Default to `.http` unless the project already has an established test runner.
|
|
47
|
+
2. If the project has a test runner (`pytest`, `vitest`, `dotnet test`, `cargo test`), prefer the stack-specific recipe so QA tests live alongside unit tests.
|
|
48
|
+
3. The user can override during the interview/run with `--format=http|pytest|supertest|dotnet|rust`.
|
|
49
|
+
|
|
50
|
+
## How to Compose
|
|
51
|
+
|
|
52
|
+
The composing command (`/dw-run-qa` API mode) follows this loop:
|
|
53
|
+
|
|
54
|
+
1. **Pick the recipe** based on the rules above.
|
|
55
|
+
2. **Read the recipe file** (`recipes/<name>.md`) for the variable conventions, test-matrix shape, and an example block.
|
|
56
|
+
3. **For each requirement (RF-XX) in the PRD**, derive a test matrix per `references/matrix-conventions.md`:
|
|
57
|
+
- 200 happy path
|
|
58
|
+
- 4xx — validation, auth, not found, conflict
|
|
59
|
+
- 5xx — server error (synthetic)
|
|
60
|
+
- Contract drift — response shape vs OpenAPI / TS types
|
|
61
|
+
- Authorization cross-tenant
|
|
62
|
+
4. **Generate** one file per RF in `{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].<ext>` using the recipe's structure. Wire credentials via the patterns in `references/auth-patterns.md` (NEVER hardcode tokens).
|
|
63
|
+
5. **Execute** each request:
|
|
64
|
+
- `.http` → `curl` (Bash) or the in-IDE runner during interactive review.
|
|
65
|
+
- Stack-specific → the project's test runner (`pytest <files>`, `vitest run <files>`, `dotnet test --filter`, `cargo test`).
|
|
66
|
+
6. **Log** every request/response per `references/log-conventions.md` to `{{PRD_PATH}}/QA/logs/api/RF-XX-[slug].log` (one JSONL line per request).
|
|
67
|
+
7. **Assert** per matrix expectation: status code, response shape (use `jq` for `.http`, framework matchers per stack), headers.
|
|
68
|
+
8. **Mark** PASS/FAIL per RF, citing the log path as evidence.
|
|
69
|
+
|
|
70
|
+
## OpenAPI-Driven Mode
|
|
71
|
+
|
|
72
|
+
If the project exposes OpenAPI (`openapi.yaml`/`openapi.json` static, or `/openapi.json` in runtime for FastAPI), follow `references/openapi-driven.md` to:
|
|
73
|
+
- Generate a baseline of 200/4xx tests per endpoint automatically.
|
|
74
|
+
- Detect contract drift by diffing live responses against the spec.
|
|
75
|
+
- Skip endpoints marked `x-internal: true` or those without examples.
|
|
76
|
+
|
|
77
|
+
## Variable Conventions
|
|
78
|
+
|
|
79
|
+
Every recipe uses three variable layers:
|
|
80
|
+
|
|
81
|
+
- **`@base`** — base URL (`http://localhost:3000` in dev). Set once per file.
|
|
82
|
+
- **`@token_admin` / `@token_user` / `@token_guest`** — credential tokens, captured from a login response or read from `.env` / `QA/test-credentials.md`.
|
|
83
|
+
- **`@<resource>_id`** — IDs created during a multi-step flow (e.g., create → fetch → update → delete on the same RF).
|
|
84
|
+
|
|
85
|
+
Per-recipe details in `references/auth-patterns.md`.
|
|
86
|
+
|
|
87
|
+
## References
|
|
88
|
+
|
|
89
|
+
- `references/matrix-conventions.md` — how to derive the {200, 4xx, 5xx, contract drift, authz cross-tenant} matrix from a PRD requirement.
|
|
90
|
+
- `references/auth-patterns.md` — how to capture and reuse JWT / cookie / API-key credentials in scripts; refresh-token patterns; scoped credentials per role.
|
|
91
|
+
- `references/openapi-driven.md` — generating a baseline test suite from an OpenAPI spec; detecting contract drift.
|
|
92
|
+
- `references/log-conventions.md` — JSONL log shape (one line per request: timestamp, method, url, status, request_headers, request_body, response_headers, response_body, ms).
|
|
93
|
+
|
|
94
|
+
## Rules
|
|
95
|
+
|
|
96
|
+
- **Default to `.http`** unless the project already has a test runner.
|
|
97
|
+
- **Never hardcode credentials**. Always use `@variable` references that resolve to env vars or files outside git.
|
|
98
|
+
- **Always log request + response** so the bug evidence is reproducible without re-running.
|
|
99
|
+
- **One file per RF**. Don't pile every requirement into one giant test file.
|
|
100
|
+
- **PASS/FAIL per RF, never per request**. A request that returns 401 when the matrix says it should is a PASS for that case.
|
|
101
|
+
|
|
102
|
+
## Inspired by
|
|
103
|
+
|
|
104
|
+
Hand-curated by dev-workflow. `.http` syntax follows the JetBrains HTTP Client / VSCode REST Client conventions. Per-stack recipes adapt patterns from each ecosystem's official testing docs (FastAPI testing tutorial, NestJS testing recipes, Microsoft.AspNetCore.Mvc.Testing docs, Axum testing examples).
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Recipe: `WebApplicationFactory<T>` + xUnit (.NET)
|
|
2
|
+
|
|
3
|
+
Use for ASP.NET Core minimal API or MVC. Microsoft's official integration-testing pattern. Runs the full pipeline (DI, middleware, filters) in-process — no Kestrel port, no flake.
|
|
4
|
+
|
|
5
|
+
## File shape
|
|
6
|
+
|
|
7
|
+
`{{PRD_PATH}}/QA/scripts/api/RF_XX_[Slug]Tests.cs`
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
using System.Net;
|
|
11
|
+
using System.Net.Http.Headers;
|
|
12
|
+
using System.Net.Http.Json;
|
|
13
|
+
using Microsoft.AspNetCore.Mvc.Testing;
|
|
14
|
+
using Xunit;
|
|
15
|
+
|
|
16
|
+
namespace YourProject.QA.Api;
|
|
17
|
+
|
|
18
|
+
public class RF_XX_CreateUserTests : IClassFixture<WebApplicationFactory<Program>>
|
|
19
|
+
{
|
|
20
|
+
private readonly WebApplicationFactory<Program> _factory;
|
|
21
|
+
private readonly string _tokenAdmin = Environment.GetEnvironmentVariable("QA_TOKEN_ADMIN") ?? "";
|
|
22
|
+
private readonly string _tokenOtherOrg = Environment.GetEnvironmentVariable("QA_TOKEN_OTHER_ORG") ?? "";
|
|
23
|
+
|
|
24
|
+
public RF_XX_CreateUserTests(WebApplicationFactory<Program> factory) => _factory = factory;
|
|
25
|
+
|
|
26
|
+
private HttpClient Client(string? token = null)
|
|
27
|
+
{
|
|
28
|
+
var c = _factory.CreateClient();
|
|
29
|
+
if (!string.IsNullOrEmpty(token))
|
|
30
|
+
c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
31
|
+
return c;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private record CreateUserDto(string Email, string Name);
|
|
35
|
+
private record UserResponse(string Id, string Email, string Name, DateTime CreatedAt);
|
|
36
|
+
|
|
37
|
+
[Fact]
|
|
38
|
+
public async Task HappyPath_Returns201()
|
|
39
|
+
{
|
|
40
|
+
var r = await Client(_tokenAdmin).PostAsJsonAsync("/users",
|
|
41
|
+
new CreateUserDto($"qa-{Guid.NewGuid():N}@example.com", "QA"));
|
|
42
|
+
Assert.Equal(HttpStatusCode.Created, r.StatusCode);
|
|
43
|
+
var body = await r.Content.ReadFromJsonAsync<UserResponse>();
|
|
44
|
+
Assert.NotNull(body);
|
|
45
|
+
Assert.NotNull(body!.Id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[Theory]
|
|
49
|
+
[InlineData("{\"name\":\"No email\"}", "email")]
|
|
50
|
+
[InlineData("{\"email\":\"no-name@x.com\"}", "name")]
|
|
51
|
+
[InlineData("{\"email\":\"not-an-email\",\"name\":\"X\"}", "email")]
|
|
52
|
+
public async Task Validation_Returns422_AndMentionsField(string payload, string field)
|
|
53
|
+
{
|
|
54
|
+
var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
|
|
55
|
+
var r = await Client(_tokenAdmin).PostAsync("/users", content);
|
|
56
|
+
Assert.Equal(HttpStatusCode.UnprocessableEntity, r.StatusCode);
|
|
57
|
+
var msg = await r.Content.ReadAsStringAsync();
|
|
58
|
+
Assert.Contains(field, msg.ToLower());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[Fact]
|
|
62
|
+
public async Task NoToken_Returns401()
|
|
63
|
+
{
|
|
64
|
+
var r = await Client().PostAsJsonAsync("/users", new CreateUserDto("x@y.com", "x"));
|
|
65
|
+
Assert.Equal(HttpStatusCode.Unauthorized, r.StatusCode);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[Fact]
|
|
69
|
+
public async Task CrossTenant_Returns403Or404()
|
|
70
|
+
{
|
|
71
|
+
if (string.IsNullOrEmpty(_tokenOtherOrg)) return;
|
|
72
|
+
// assume a known id from another tenant; in a real suite, create one in setup
|
|
73
|
+
var r = await Client(_tokenOtherOrg).GetAsync("/users/00000000-0000-0000-0000-000000000001");
|
|
74
|
+
Assert.True(r.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.NotFound);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[Fact]
|
|
78
|
+
public async Task Contract_HasRequiredFields_NoLeaks()
|
|
79
|
+
{
|
|
80
|
+
var create = await Client(_tokenAdmin).PostAsJsonAsync("/users",
|
|
81
|
+
new CreateUserDto($"contract-{Guid.NewGuid():N}@example.com", "Contract"));
|
|
82
|
+
var created = await create.Content.ReadFromJsonAsync<UserResponse>();
|
|
83
|
+
var get = await Client(_tokenAdmin).GetAsync($"/users/{created!.Id}");
|
|
84
|
+
Assert.Equal(HttpStatusCode.OK, get.StatusCode);
|
|
85
|
+
|
|
86
|
+
var raw = await get.Content.ReadAsStringAsync();
|
|
87
|
+
foreach (var field in new[] { "id", "email", "name", "created_at" })
|
|
88
|
+
Assert.Contains(field, raw, StringComparison.OrdinalIgnoreCase);
|
|
89
|
+
foreach (var leak in new[] { "password_hash", "internal_id", "_raw" })
|
|
90
|
+
Assert.DoesNotContain(leak, raw, StringComparison.OrdinalIgnoreCase);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
Project file (`*.QA.csproj` or extend the existing test project):
|
|
98
|
+
|
|
99
|
+
```xml
|
|
100
|
+
<ItemGroup>
|
|
101
|
+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.*" />
|
|
102
|
+
<PackageReference Include="xunit" Version="2.9.*" />
|
|
103
|
+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.*" />
|
|
104
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.*" />
|
|
105
|
+
</ItemGroup>
|
|
106
|
+
<ItemGroup>
|
|
107
|
+
<InternalsVisibleTo Include="$(AssemblyName)" />
|
|
108
|
+
</ItemGroup>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `Program` class must be public (for `WebApplicationFactory<Program>`). For minimal APIs, add at the bottom of `Program.cs`:
|
|
112
|
+
|
|
113
|
+
```csharp
|
|
114
|
+
public partial class Program { }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Running
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# all RF tests
|
|
121
|
+
dotnet test --filter FullyQualifiedName~YourProject.QA.Api
|
|
122
|
+
|
|
123
|
+
# one RF
|
|
124
|
+
dotnet test --filter FullyQualifiedName~RF_XX_CreateUserTests
|
|
125
|
+
|
|
126
|
+
# log to QA/logs/api/
|
|
127
|
+
dotnet test --filter FullyQualifiedName~YourProject.QA.Api \
|
|
128
|
+
--logger "console;verbosity=detailed" 2>&1 \
|
|
129
|
+
| tee "QA/logs/api/run-$(date +%F).log"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Logging request/response
|
|
133
|
+
|
|
134
|
+
Use a custom `DelegatingHandler` registered on the factory's client:
|
|
135
|
+
|
|
136
|
+
```csharp
|
|
137
|
+
public class LoggingHandler : DelegatingHandler
|
|
138
|
+
{
|
|
139
|
+
private static readonly string LogPath = "QA/logs/api/RF-XX-create-user.log";
|
|
140
|
+
|
|
141
|
+
protected override async Task<HttpResponseMessage> SendAsync(
|
|
142
|
+
HttpRequestMessage req, CancellationToken ct)
|
|
143
|
+
{
|
|
144
|
+
var sw = Stopwatch.StartNew();
|
|
145
|
+
var res = await base.SendAsync(req, ct);
|
|
146
|
+
sw.Stop();
|
|
147
|
+
Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!);
|
|
148
|
+
var entry = new {
|
|
149
|
+
ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
|
150
|
+
method = req.Method.Method,
|
|
151
|
+
url = req.RequestUri?.ToString(),
|
|
152
|
+
status = (int)res.StatusCode,
|
|
153
|
+
ms = sw.ElapsedMilliseconds,
|
|
154
|
+
};
|
|
155
|
+
await File.AppendAllTextAsync(LogPath,
|
|
156
|
+
System.Text.Json.JsonSerializer.Serialize(entry) + "\n", ct);
|
|
157
|
+
return res;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Pros / cons
|
|
163
|
+
|
|
164
|
+
- **Pro**: in-process — full DI graph, no port, deterministic.
|
|
165
|
+
- **Pro**: `[Theory]` + `[InlineData]` covers the 4xx matrix.
|
|
166
|
+
- **Pro**: same project as unit tests; `dotnet test` runs both.
|
|
167
|
+
- **Con**: requires `Program` to be partial and public.
|
|
168
|
+
- **Con**: tied to `Microsoft.AspNetCore.Mvc.Testing` package versions.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Recipe: `.http` (REST Client) — DEFAULT
|
|
2
|
+
|
|
3
|
+
Universal API-testing format. One file per RF. Read by VSCode REST Client, JetBrains HTTP Client, Neovim rest.nvim/kulala, Zed Assistant. No test runner needed.
|
|
4
|
+
|
|
5
|
+
## File shape
|
|
6
|
+
|
|
7
|
+
`{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].http`
|
|
8
|
+
|
|
9
|
+
```http
|
|
10
|
+
### RF-XX [slug] — happy path
|
|
11
|
+
# @name create_user
|
|
12
|
+
POST {{base}}/users
|
|
13
|
+
Authorization: Bearer {{token_admin}}
|
|
14
|
+
Content-Type: application/json
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
"email": "qa-{{$randomInt 1 999999}}@example.com",
|
|
18
|
+
"name": "QA User"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
> {%
|
|
22
|
+
client.test("status is 201", () => client.assert(response.status === 201));
|
|
23
|
+
client.test("response has id", () => client.assert(response.body.id != null));
|
|
24
|
+
client.global.set("created_user_id", response.body.id);
|
|
25
|
+
%}
|
|
26
|
+
|
|
27
|
+
### RF-XX — 4xx validation: missing email
|
|
28
|
+
POST {{base}}/users
|
|
29
|
+
Authorization: Bearer {{token_admin}}
|
|
30
|
+
Content-Type: application/json
|
|
31
|
+
|
|
32
|
+
{ "name": "No email" }
|
|
33
|
+
|
|
34
|
+
> {%
|
|
35
|
+
client.test("status is 422", () => client.assert(response.status === 422));
|
|
36
|
+
client.test("error mentions email", () => client.assert(response.body.error.message.toLowerCase().includes("email")));
|
|
37
|
+
%}
|
|
38
|
+
|
|
39
|
+
### RF-XX — 4xx auth: missing token
|
|
40
|
+
POST {{base}}/users
|
|
41
|
+
Content-Type: application/json
|
|
42
|
+
|
|
43
|
+
{ "email": "x@y.com", "name": "x" }
|
|
44
|
+
|
|
45
|
+
> {%
|
|
46
|
+
client.test("status is 401", () => client.assert(response.status === 401));
|
|
47
|
+
%}
|
|
48
|
+
|
|
49
|
+
### RF-XX — 4xx authz: cross-tenant access
|
|
50
|
+
GET {{base}}/users/{{created_user_id}}
|
|
51
|
+
Authorization: Bearer {{token_other_org_admin}}
|
|
52
|
+
|
|
53
|
+
> {%
|
|
54
|
+
client.test("status is 403 or 404", () =>
|
|
55
|
+
client.assert(response.status === 403 || response.status === 404));
|
|
56
|
+
%}
|
|
57
|
+
|
|
58
|
+
### RF-XX — contract drift: response shape vs OpenAPI
|
|
59
|
+
GET {{base}}/users/{{created_user_id}}
|
|
60
|
+
Authorization: Bearer {{token_admin}}
|
|
61
|
+
|
|
62
|
+
> {%
|
|
63
|
+
client.test("has required fields", () => {
|
|
64
|
+
["id", "email", "name", "created_at"].forEach(f =>
|
|
65
|
+
client.assert(response.body[f] != null, `missing ${f}`));
|
|
66
|
+
});
|
|
67
|
+
client.test("no leaked internal fields", () => {
|
|
68
|
+
["password_hash", "internal_id", "_raw"].forEach(f =>
|
|
69
|
+
client.assert(response.body[f] === undefined, `leaked ${f}`));
|
|
70
|
+
});
|
|
71
|
+
%}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Variables
|
|
75
|
+
|
|
76
|
+
Set once at the top of the file (or in a `http-client.env.json` next to it):
|
|
77
|
+
|
|
78
|
+
```http
|
|
79
|
+
@base = {{$dotenv API_BASE_URL}}
|
|
80
|
+
@token_admin = {{$dotenv QA_TOKEN_ADMIN}}
|
|
81
|
+
@token_user = {{$dotenv QA_TOKEN_USER}}
|
|
82
|
+
@token_other_org_admin = {{$dotenv QA_TOKEN_OTHER_ORG}}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or, if the project uses login-based auth, capture the token in a setup request and reference it in subsequent requests:
|
|
86
|
+
|
|
87
|
+
```http
|
|
88
|
+
### Setup — login as admin
|
|
89
|
+
# @name login_admin
|
|
90
|
+
POST {{base}}/auth/login
|
|
91
|
+
Content-Type: application/json
|
|
92
|
+
|
|
93
|
+
{ "email": "{{$dotenv QA_ADMIN_EMAIL}}", "password": "{{$dotenv QA_ADMIN_PASSWORD}}" }
|
|
94
|
+
|
|
95
|
+
> {% client.global.set("token_admin", response.body.access_token); %}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Execution from `dw-run-qa` (CLI fallback)
|
|
99
|
+
|
|
100
|
+
When running outside an IDE (e.g., from the agent in headless mode), parse and execute via `curl`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# For each ### block, extract method/url/headers/body and execute:
|
|
104
|
+
curl -sS -X POST "$BASE/users" \
|
|
105
|
+
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
106
|
+
-H "Content-Type: application/json" \
|
|
107
|
+
-d '{"email":"qa-1@example.com","name":"QA"}' \
|
|
108
|
+
-w '\n%{http_code} %{time_total}s\n' \
|
|
109
|
+
| tee -a "QA/logs/api/RF-XX-create-user.log"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The `dw-run-qa` agent does this loop automatically and writes to the JSONL log per `references/log-conventions.md`.
|
|
113
|
+
|
|
114
|
+
## Assertions
|
|
115
|
+
|
|
116
|
+
Use the inline `> {% ... %}` post-response handler when running in an IDE. For headless `curl` execution, use `jq`:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
RESP=$(curl -sS ...)
|
|
120
|
+
STATUS=$(echo "$RESP" | head -1 | awk '{print $2}')
|
|
121
|
+
[ "$STATUS" = "201" ] || { echo "FAIL: expected 201, got $STATUS"; exit 1; }
|
|
122
|
+
echo "$RESP" | jq -e '.id != null' >/dev/null || { echo "FAIL: missing id"; exit 1; }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Pros / cons
|
|
126
|
+
|
|
127
|
+
- **Pro**: zero install, opens in any IDE, devs read it without running a test runner.
|
|
128
|
+
- **Pro**: each request is a single block, easy to copy-paste into incident tickets.
|
|
129
|
+
- **Con**: no native fixture/teardown — multi-request flows rely on `client.global.set` for state.
|
|
130
|
+
- **Con**: parallel execution requires per-block uniqueness in resource names (use `{{$randomInt}}` or `{{$timestamp}}`).
|