Files
claude-skills-reference/engineering-team/playwright-pro/templates/api/graphql.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.9 KiB

GraphQL API Template

Tests query, mutation, and subscription via Playwright's request API.

Prerequisites

  • Valid auth token: {{apiToken}}
  • GraphQL endpoint: {{graphqlEndpoint}}
  • WebSocket endpoint for subscriptions: {{graphqlWsEndpoint}}

TypeScript

import { test, expect } from '@playwright/test';

const GQL_URL = '{{graphqlEndpoint}}';
const headers = {
  'Authorization': `Bearer {{apiToken}}`,
  'Content-Type': 'application/json',
};

async function gql(request: any, query: string, variables = {}) {
  const res = await request.post(GQL_URL, { headers, data: { query, variables } });
  const body = await res.json();
  expect(body.errors).toBeUndefined();
  return body.data;
}

test.describe('GraphQL API', () => {
  // Happy path: query
  test('query fetches {{entityName}} list', async ({ request }) => {
    const data = await gql(request, `
      query Get{{EntityName}}s($limit: Int) {
        {{entityName}}s(limit: $limit) { id name createdAt }
      }
    `, { limit: 10 });
    expect(Array.isArray(data.{{entityName}}s)).toBe(true);
    expect(data.{{entityName}}s.length).toBeLessThanOrEqual(10);
  });

  // Happy path: query single entity
  test('query fetches single {{entityName}} by id', async ({ request }) => {
    const data = await gql(request, `
      query Get{{EntityName}}($id: ID!) {
        {{entityName}}(id: $id) { id name description }
      }
    `, { id: '{{existingEntityId}}' });
    expect(data.{{entityName}}.id).toBe('{{existingEntityId}}');
  });

  // Happy path: mutation creates entity
  test('mutation creates {{entityName}}', async ({ request }) => {
    const data = await gql(request, `
      mutation Create{{EntityName}}($input: {{EntityName}}Input!) {
        create{{EntityName}}(input: $input) { id name }
      }
    `, { input: { name: '{{testEntityName}}', description: '{{testDescription}}' } });
    expect(data.create{{EntityName}}.id).toBeTruthy();
    expect(data.create{{EntityName}}.name).toBe('{{testEntityName}}');
  });

  // Happy path: mutation updates entity
  test('mutation updates {{entityName}}', async ({ request }) => {
    const data = await gql(request, `
      mutation Update{{EntityName}}($id: ID!, $input: {{EntityName}}Input!) {
        update{{EntityName}}(id: $id, input: $input) { id name }
      }
    `, { id: '{{existingEntityId}}', input: { name: '{{updatedName}}' } });
    expect(data.update{{EntityName}}.name).toBe('{{updatedName}}');
  });

  // Happy path: mutation deletes entity
  test('mutation deletes {{entityName}}', async ({ request }) => {
    const data = await gql(request, `
      mutation Delete{{EntityName}}($id: ID!) {
        delete{{EntityName}}(id: $id) { success }
      }
    `, { id: '{{deletableEntityId}}' });
    expect(data.delete{{EntityName}}.success).toBe(true);
  });

  // Error case: invalid query returns errors array
  test('invalid query returns errors', async ({ request }) => {
    const res = await request.post(GQL_URL, {
      headers,
      data: { query: '{ invalidField }' },
    });
    const body = await res.json();
    expect(body.errors).toBeDefined();
    expect(body.errors.length).toBeGreaterThan(0);
  });

  // Error case: unauthorized query
  test('query without auth returns unauthorized error', async ({ request }) => {
    const res = await request.post(GQL_URL, {
      headers: { 'Content-Type': 'application/json' }, // No auth
      data: { query: '{ {{entityName}}s { id } }' },
    });
    const body = await res.json();
    expect(body.errors?.[0]?.extensions?.code).toMatch(/UNAUTHENTICATED|UNAUTHORIZED/);
  });

  // Edge case: subscription via page WebSocket
  test('subscription receives real-time update', async ({ page }) => {
    await page.goto('{{baseUrl}}/dashboard');
    const received: any[] = [];
    await page.evaluate(() => {
      const ws = new WebSocket('{{graphqlWsEndpoint}}');
      ws.onmessage = e => (window as any).__gqlMsg = JSON.parse(e.data);
    });
    // Trigger mutation to fire subscription
    await page.request.post(GQL_URL, {
      headers,
      data: { query: 'mutation { trigger{{EntityName}}Event { id } }' },
    });
    const msg = await page.evaluate(() => (window as any).__gqlMsg);
    expect(msg?.type).toBe('data');
  });
});

JavaScript

const { test, expect } = require('@playwright/test');

const headers = { 'Authorization': `Bearer {{apiToken}}`, 'Content-Type': 'application/json' };

async function gql(request, query, variables = {}) {
  const res = await request.post('{{graphqlEndpoint}}', { headers, data: { query, variables } });
  const body = await res.json();
  expect(body.errors).toBeUndefined();
  return body.data;
}

test.describe('GraphQL API', () => {
  test('query fetches entity list', async ({ request }) => {
    const data = await gql(request, '{ {{entityName}}s { id name } }');
    expect(Array.isArray(data.{{entityName}}s)).toBe(true);
  });

  test('mutation creates entity', async ({ request }) => {
    const data = await gql(request,
      'mutation($input: {{EntityName}}Input!) { create{{EntityName}}(input: $input) { id } }',
      { input: { name: '{{testEntityName}}' } }
    );
    expect(data.create{{EntityName}}.id).toBeTruthy();
  });

  test('invalid query returns errors array', async ({ request }) => {
    const res = await request.post('{{graphqlEndpoint}}', {
      headers,
      data: { query: '{ nonExistentField }' },
    });
    const body = await res.json();
    expect(body.errors?.length).toBeGreaterThan(0);
  });
});

Variants

Variant Description
List query Returns array of entities
Single query Returns entity by ID
Create mutation Returns new entity with ID
Update mutation Returns updated field value
Delete mutation Returns success: true
Invalid query errors[] defined in response
Unauthenticated UNAUTHENTICATED extension code
Subscription Real-time message via WebSocket