Complete Claude Code plugin with: - 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report) - 3 specialized agents (test-architect, test-debugger, migration-planner) - 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility) - TestRail MCP server (TypeScript) — 8 tools for bidirectional sync - BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing - Smart hooks (auto-validate tests, auto-detect Playwright projects) - 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests) - Leverages Claude Code built-ins (/batch, /debug, Explore subagent) - Zero-config for core features; TestRail/BrowserStack via env vars - Both TypeScript and JavaScript support throughout Co-authored-by: Leo <leo@openclaw.ai>
153 lines
5.0 KiB
Markdown
153 lines
5.0 KiB
Markdown
# REST CRUD API Template
|
|
|
|
Tests GET, POST, PUT, and DELETE API endpoints directly via Playwright's request API.
|
|
|
|
## Prerequisites
|
|
- Valid auth token: `{{apiToken}}`
|
|
- Base API URL: `{{apiBaseUrl}}`
|
|
- Test entity endpoint: `/{{entityName}}s`
|
|
|
|
---
|
|
|
|
## TypeScript
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('REST CRUD — /{{entityName}}s', () => {
|
|
let createdId: string;
|
|
|
|
const headers = {
|
|
'Authorization': `Bearer {{apiToken}}`,
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
// Happy path: GET list
|
|
test('GET /{{entityName}}s returns list', async ({ request }) => {
|
|
const res = await request.get('{{apiBaseUrl}}/{{entityName}}s', { headers });
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.json();
|
|
expect(Array.isArray(body.data ?? body)).toBe(true);
|
|
});
|
|
|
|
// Happy path: POST creates entity
|
|
test('POST /{{entityName}}s creates new entity', async ({ request }) => {
|
|
const res = await request.post('{{apiBaseUrl}}/{{entityName}}s', {
|
|
headers,
|
|
data: { name: '{{testEntityName}}', description: '{{testDescription}}' },
|
|
});
|
|
expect(res.status()).toBe(201);
|
|
const body = await res.json();
|
|
expect(body.id).toBeTruthy();
|
|
expect(body.name).toBe('{{testEntityName}}');
|
|
createdId = body.id;
|
|
});
|
|
|
|
// Happy path: GET single entity
|
|
test('GET /{{entityName}}s/:id returns entity', async ({ request }) => {
|
|
const res = await request.get(`{{apiBaseUrl}}/{{entityName}}s/{{existingEntityId}}`, { headers });
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.json();
|
|
expect(body.id).toBe('{{existingEntityId}}');
|
|
expect(body.name).toBeTruthy();
|
|
});
|
|
|
|
// Happy path: PUT updates entity
|
|
test('PUT /{{entityName}}s/:id updates entity', async ({ request }) => {
|
|
const res = await request.put(`{{apiBaseUrl}}/{{entityName}}s/{{existingEntityId}}`, {
|
|
headers,
|
|
data: { name: '{{updatedEntityName}}' },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.json();
|
|
expect(body.name).toBe('{{updatedEntityName}}');
|
|
});
|
|
|
|
// Happy path: PATCH partial update
|
|
test('PATCH /{{entityName}}s/:id partially updates entity', async ({ request }) => {
|
|
const res = await request.patch(`{{apiBaseUrl}}/{{entityName}}s/{{existingEntityId}}`, {
|
|
headers,
|
|
data: { description: '{{patchedDescription}}' },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.json();
|
|
expect(body.description).toBe('{{patchedDescription}}');
|
|
});
|
|
|
|
// Happy path: DELETE removes entity
|
|
test('DELETE /{{entityName}}s/:id deletes entity', async ({ request }) => {
|
|
const del = await request.delete(`{{apiBaseUrl}}/{{entityName}}s/{{deletableEntityId}}`, { headers });
|
|
expect(del.status()).toBe(204);
|
|
// Verify gone
|
|
const get = await request.get(`{{apiBaseUrl}}/{{entityName}}s/{{deletableEntityId}}`, { headers });
|
|
expect(get.status()).toBe(404);
|
|
});
|
|
|
|
// Error case: POST with missing required field returns 422
|
|
test('POST with missing required field returns 422', async ({ request }) => {
|
|
const res = await request.post('{{apiBaseUrl}}/{{entityName}}s', {
|
|
headers,
|
|
data: {},
|
|
});
|
|
expect(res.status()).toBe(422);
|
|
const body = await res.json();
|
|
expect(body.errors).toBeTruthy();
|
|
});
|
|
|
|
// Error case: GET non-existent entity returns 404
|
|
test('GET non-existent entity returns 404', async ({ request }) => {
|
|
const res = await request.get('{{apiBaseUrl}}/{{entityName}}s/999999', { headers });
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript
|
|
|
|
```javascript
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
const headers = {
|
|
'Authorization': `Bearer {{apiToken}}`,
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
test.describe('REST CRUD — /{{entityName}}s', () => {
|
|
test('GET list returns 200 and array', async ({ request }) => {
|
|
const res = await request.get('{{apiBaseUrl}}/{{entityName}}s', { headers });
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.json();
|
|
expect(Array.isArray(body.data ?? body)).toBe(true);
|
|
});
|
|
|
|
test('POST creates entity and returns 201', async ({ request }) => {
|
|
const res = await request.post('{{apiBaseUrl}}/{{entityName}}s', {
|
|
headers,
|
|
data: { name: '{{testEntityName}}' },
|
|
});
|
|
expect(res.status()).toBe(201);
|
|
expect((await res.json()).id).toBeTruthy();
|
|
});
|
|
|
|
test('DELETE removes entity, GET returns 404', async ({ request }) => {
|
|
await request.delete(`{{apiBaseUrl}}/{{entityName}}s/{{deletableEntityId}}`, { headers });
|
|
const res = await request.get(`{{apiBaseUrl}}/{{entityName}}s/{{deletableEntityId}}`, { headers });
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Variants
|
|
| Variant | Description |
|
|
|---------|-------------|
|
|
| GET list | 200 + array body |
|
|
| POST create | 201 + id in response |
|
|
| GET single | 200 + correct entity body |
|
|
| PUT update | 200 + updated field in response |
|
|
| PATCH partial | 200 + patched field only changed |
|
|
| DELETE | 204 → subsequent GET returns 404 |
|
|
| POST validation | Missing field → 422 + errors |
|
|
| GET 404 | Non-existent ID → 404 |
|