Files
claude-skills-reference/engineering-team/playwright-pro/templates/api/rest-crud.md
Alireza Rezvani d33d03da50 feat: add playwright-pro plugin — production-grade Playwright testing toolkit (#254)
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>
2026-03-05 13:50:05 +01:00

5.0 KiB

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

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

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