Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问许可证需确认审计异常

playwright-api-testingPlaywright API 测试

Agent Skill

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

总安装

2,876

周安装

102

GitHub Stars

106

下载量

541
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:playwright-api-testing(Playwright API 测试)
来源仓库:https://github.com/pramoddutta/qaskills
仓库路径:skills/playwright-api-testing
安装命令:
npx skills add https://github.com/pramoddutta/qaskills --skill 'Playwright API Testing'
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/pramoddutta/qaskills --skill 'Playwright API Testing'

简介

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明,适合编写 RESTful API 测试用例时使用。

  • 适用于接口可用性检查、状态码验证或数据格式校验等后端测试场景。
  • 通过 npx skills add 命令安装,需确认目标 API 地址与认证方式。
  • 建议结合 Postman 或 Swagger 文档交叉验证接口契约正确性。
  • playwright-api-testing 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Playwright API Testing Skill

You are an expert QA automation engineer specializing in API testing using Playwright's built-in APIRequestContext. When the user asks you to write, review, or debug API tests with Playwright, follow these detailed instructions.

Core Principles

  1. Playwright-native API testing -- Use APIRequestContext instead of external HTTP libraries.
  2. Type safety -- Define interfaces for all request/response payloads.
  3. Isolation -- Each test manages its own data lifecycle (create, verify, clean up).
  4. Comprehensive validation -- Check status codes, headers, response body structure, and timing.
  5. Reusable abstractions -- Build API client classes for each service domain.

Project Structure

tests/
  api/
    auth/
      auth-api.spec.ts
    users/
      users-api.spec.ts
      users-crud.spec.ts
    products/
      products-api.spec.ts
  fixtures/
    api.fixture.ts
    auth-api.fixture.ts
  models/
    user.model.ts
    product.model.ts
    api-response.model.ts
  clients/
    base-api-client.ts
    users-api-client.ts
    products-api-client.ts
  utils/
    api-helpers.ts
    schema-validator.ts
playwright.config.ts

Configuration

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/api',
  fullyParallel: true,
  retries: process.env.CI ? 1 : 0,
  reporter: [
    ['html'],
    ['json', { outputFile: 'test-results/api-results.json' }],
  ],
  use: {
    baseURL: process.env.API_BASE_URL || 'http://localhost:3000/api',
    extraHTTPHeaders: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
  },
});

Response Models

Define TypeScript interfaces for all API payloads:

// models/user.model.ts
export interface User {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'user' | 'viewer';
  createdAt: string;
  updatedAt: string;
}

export interface CreateUserRequest {
  email: string;
  name: string;
  password: string;
  role?: 'admin' | 'user' | 'viewer';
}

export interface UpdateUserRequest {
  name?: string;
  role?: 'admin' | 'user' | 'viewer';
}

export interface UserListResponse {
  data: User[];
  total: number;
  page: number;
  pageSize: number;
}

export interface ApiError {
  statusCode: number;
  message: string;
  error: string;
  details?: Record<string, string[]>;
}

Base API Client

// clients/base-api-client.ts
import { APIRequestContext, APIResponse } from '@playwright/test';

export class BaseApiClient {
  protected readonly request: APIRequestContext;
  protected readonly basePath: string;

  constructor(request: APIRequestContext, basePath: string) {
    this.request = request;
    this.basePath = basePath;
  }

  protected async get(path: string, params?: Record<string, string>): Promise<APIResponse> {
    const url = params
      ? `${this.basePath}${path}?${new URLSearchParams(params)}`
      : `${this.basePath}${path}`;
    return this.request.get(url);
  }

  protected async post(path: string, data: unknown): Promise<APIResponse> {
    return this.request.post(`${this.basePath}${path}`, { data });
  }

  protected async put(path: string, data: unknown): Promise<APIResponse> {
    return this.request.put(`${this.basePath}${path}`, { data });
  }

  protected async patch(path: string, data: unknown): Promise<APIResponse> {
    return this.request.patch(`${this.basePath}${path}`, { data });
  }

  protected async delete(path: string): Promise<APIResponse> {
    return this.request.delete(`${this.basePath}${path}`);
  }
}

Domain-Specific API Client

// clients/users-api-client.ts
import { APIRequestContext, APIResponse } from '@playwright/test';
import { BaseApiClient } from './base-api-client';
import { CreateUserRequest, UpdateUserRequest } from '../models/user.model';

export class UsersApiClient extends BaseApiClient {
  constructor(request: APIRequestContext) {
    super(request, '/users');
  }

  async list(page = 1, pageSize = 10): Promise<APIResponse> {
    return this.get('', { page: String(page), pageSize: String(pageSize) });
  }

  async getById(id: string): Promise<APIResponse> {
    return this.get(`/${id}`);
  }

  async create(user: CreateUserRequest): Promise<APIResponse> {
    return this.post('', user);
  }

  async update(id: string, data: UpdateUserRequest): Promise<APIResponse> {
    return this.patch(`/${id}`, data);
  }

  async remove(id: string): Promise<APIResponse> {
    return this.delete(`/${id}`);
  }

  async search(query: string): Promise<APIResponse> {
    return this.get('/search', { q: query });
  }
}

Custom Fixtures

// fixtures/api.fixture.ts
import { test as base } from '@playwright/test';
import { UsersApiClient } from '../clients/users-api-client';
import { ProductsApiClient } from '../clients/products-api-client';

type ApiFixtures = {
  usersApi: UsersApiClient;
  productsApi: ProductsApiClient;
  authToken: string;
};

export const test = base.extend<ApiFixtures>({
  usersApi: async ({ request }, use) => {
    await use(new UsersApiClient(request));
  },

  productsApi: async ({ request }, use) => {
    await use(new ProductsApiClient(request));
  },

  authToken: async ({ request }, use) => {
    const response = await request.post('/auth/login', {
      data: {
        email: 'admin@example.com',
        password: 'AdminPass123!',
      },
    });
    const body = await response.json();
    await use(body.token);
  },
});

export { expect } from '@playwright/test';

Writing API Tests

CRUD Operations

import { test, expect } from '../fixtures/api.fixture';
import { CreateUserRequest, User } from '../models/user.model';

test.describe('Users API - CRUD', () => {
  let createdUserId: string;

  const newUser: CreateUserRequest = {
    email: `test-${Date.now()}@example.com`,
    name: 'Test User',
    password: 'SecurePass123!',
    role: 'user',
  };

  test('POST /users - should create a new user', async ({ usersApi }) => {
    const response = await usersApi.create(newUser);

    expect(response.status()).toBe(201);

    const body: User = await response.json();
    expect(body.id).toBeTruthy();
    expect(body.email).toBe(newUser.email);
    expect(body.name).toBe(newUser.name);
    expect(body.role).toBe('user');
    expect(body.createdAt).toBeTruthy();

    createdUserId = body.id;
  });

  test('GET /users/:id - should retrieve the user', async ({ usersApi }) => {
    // First create a user
    const createResponse = await usersApi.create({
      ...newUser,
      email: `get-test-${Date.now()}@example.com`,
    });
    const created: User = await createResponse.json();

    const response = await usersApi.getById(created.id);
    expect(response.status()).toBe(200);

    const body: User = await response.json();
    expect(body.id).toBe(created.id);
    expect(body.email).toBe(created.email);
  });

  test('PATCH /users/:id - should update the user', async ({ usersApi }) => {
    const createResponse = await usersApi.create({
      ...newUser,
      email: `update-test-${Date.now()}@example.com`,
    });
    const created: User = await createResponse.json();

    const response = await usersApi.update(created.id, { name: 'Updated Name' });
    expect(response.status()).toBe(200);

    const body: User = await response.json();
    expect(body.name).toBe('Updated Name');
  });

  test('DELETE /users/:id - should delete the user', async ({ usersApi }) => {
    const createResponse = await usersApi.create({
      ...newUser,
      email: `delete-test-${Date.now()}@example.com`,
    });
    const created: User = await createResponse.json();

    const deleteResponse = await usersApi.remove(created.id);
    expect(deleteResponse.status()).toBe(204);

    const getResponse = await usersApi.getById(created.id);
    expect(getResponse.status()).toBe(404);
  });
});

Authentication Testing

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

test.describe('Authentication API', () => {
  test('should login with valid credentials', async ({ request }) => {
    const response = await request.post('/auth/login', {
      data: {
        email: 'admin@example.com',
        password: 'AdminPass123!',
      },
    });

    expect(response.status()).toBe(200);
    const body = await response.json();
    expect(body.token).toBeTruthy();
    expect(body.expiresIn).toBeGreaterThan(0);
    expect(body.user.email).toBe('admin@example.com');
  });

  test('should reject invalid credentials', async ({ request }) => {
    const response = await request.post('/auth/login', {
      data: {
        email: 'admin@example.com',
        password: 'wrongpassword',
      },
    });

    expect(response.status()).toBe(401);
    const body = await response.json();
    expect(body.message).toBe('Invalid credentials');
  });

  test('should access protected endpoint with token', async ({ request }) => {
    // Login first
    const loginResponse = await request.post('/auth/login', {
      data: {
        email: 'admin@example.com',
        password: 'AdminPass123!',
      },
    });
    const { token } = await loginResponse.json();

    // Use the token
    const response = await request.get('/users/me', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    expect(response.status()).toBe(200);
    const user = await response.json();
    expect(user.email).toBe('admin@example.com');
  });

  test('should reject expired or invalid token', async ({ request }) => {
    const response = await request.get('/users/me', {
      headers: {
        Authorization: 'Bearer invalid.token.here',
      },
    });

    expect(response.status()).toBe(401);
  });
});

Error Handling and Validation

test.describe('Users API - Validation', () => {
  test('should return 400 for missing required fields', async ({ request }) => {
    const response = await request.post('/users', {
      data: { name: 'No Email User' },
    });

    expect(response.status()).toBe(400);
    const body = await response.json();
    expect(body.details).toHaveProperty('email');
  });

  test('should return 400 for invalid email format', async ({ request }) => {
    const response = await request.post('/users', {
      data: {
        email: 'not-an-email',
        name: 'Bad Email User',
        password: 'SecurePass123!',
      },
    });

    expect(response.status()).toBe(400);
    const body = await response.json();
    expect(body.details.email).toContain('must be a valid email');
  });

  test('should return 409 for duplicate email', async ({ usersApi }) => {
    const email = `duplicate-${Date.now()}@example.com`;
    const userData = { email, name: 'First', password: 'Pass123!' };

    await usersApi.create(userData);
    const response = await usersApi.create(userData);

    expect(response.status()).toBe(409);
  });

  test('should return 404 for non-existent resource', async ({ usersApi }) => {
    const response = await usersApi.getById('non-existent-id');
    expect(response.status()).toBe(404);
  });
});

Pagination and Filtering

test.describe('Users API - Pagination', () => {
  test('should return paginated results', async ({ usersApi }) => {
    const response = await usersApi.list(1, 5);

    expect(response.status()).toBe(200);
    const body = await response.json();
    expect(body.data.length).toBeLessThanOrEqual(5);
    expect(body.page).toBe(1);
    expect(body.pageSize).toBe(5);
    expect(body.total).toBeGreaterThanOrEqual(0);
  });

  test('should return correct page', async ({ usersApi }) => {
    const page1 = await (await usersApi.list(1, 2)).json();
    const page2 = await (await usersApi.list(2, 2)).json();

    const page1Ids = page1.data.map((u: { id: string }) => u.id);
    const page2Ids = page2.data.map((u: { id: string }) => u.id);
    const overlap = page1Ids.filter((id: string) => page2Ids.includes(id));
    expect(overlap).toHaveLength(0);
  });
});

Response Header Validation

test('should return correct response headers', async ({ request }) => {
  const response = await request.get('/users');

  expect(response.headers()['content-type']).toContain('application/json');
  expect(response.headers()['x-request-id']).toBeTruthy();
  expect(response.headers()['cache-control']).toBeDefined();

  // Security headers
  expect(response.headers()['x-content-type-options']).toBe('nosniff');
  expect(response.headers()['x-frame-options']).toBe('DENY');
});

Response Time Assertions

test('should respond within acceptable time', async ({ request }) => {
  const start = Date.now();
  const response = await request.get('/health');
  const duration = Date.now() - start;

  expect(response.status()).toBe(200);
  expect(duration).toBeLessThan(500); // 500ms threshold
});

Best Practices

  1. Use unique test data -- Include timestamps or UUIDs in emails and names to avoid collisions.
  2. Clean up after tests -- Delete resources you create to keep the test environment clean.
  3. Validate response schemas -- Check not just values but the shape of the response.
  4. Test both happy and unhappy paths -- Always test error cases and edge cases.
  5. Use environment variables -- Never hardcode URLs or credentials.
  6. Group tests logically -- Organize by resource or feature, not by HTTP method.
  7. Use fixtures for authentication -- Avoid repeating login logic in every test.
  8. Check response times -- API performance is part of correctness.
  9. Test idempotency -- Verify that repeated identical requests produce consistent results.
  10. Version your API tests -- When testing versioned APIs, organize tests by version.

Anti-Patterns to Avoid

  1. Chaining test dependencies -- Each test must create its own data.
  2. Ignoring response headers -- Headers carry important metadata.
  3. Testing only status codes -- Always validate the response body too.
  4. Using hardcoded IDs -- IDs should come from test setup, not hardcoded values.
  5. Skipping error scenarios -- Error handling tests catch more bugs than happy-path tests.
  6. Not testing with different roles -- API authorization must be tested per role.
  7. Mixing UI and API tests -- Keep API tests separate from E2E browser tests.
  8. Not verifying side effects -- If POST creates a resource, GET it to confirm.
  9. Ignoring rate limiting -- Test that rate limits are enforced and handle 429 responses.
  10. Not testing with large payloads -- Ensure APIs handle boundary sizes correctly.

Advanced Patterns

Parallel API Test with Context Isolation

test.describe.parallel('Isolated API tests', () => {
  test('test A creates and deletes user A', async ({ request }) => {
    const res = await request.post('/users', {
      data: { email: `a-${Date.now()}@test.com`, name: 'A', password: 'Pass123!' },
    });
    const user = await res.json();
    await request.delete(`/users/${user.id}`);
  });

  test('test B creates and deletes user B', async ({ request }) => {
    const res = await request.post('/users', {
      data: { email: `b-${Date.now()}@test.com`, name: 'B', password: 'Pass123!' },
    });
    const user = await res.json();
    await request.delete(`/users/${user.id}`);
  });
});

Custom Request Context with Auth

test('admin-only endpoint', async ({ playwright }) => {
  const adminContext = await playwright.request.newContext({
    baseURL: 'http://localhost:3000/api',
    extraHTTPHeaders: {
      Authorization: 'Bearer admin-token-here',
    },
  });

  const response = await adminContext.get('/admin/settings');
  expect(response.status()).toBe(200);

  await adminContext.dispose();
});

File Upload via API

import * as fs from 'fs';
import * as path from 'path';

test('should upload a file via API', async ({ request }) => {
  const filePath = path.resolve('test-data/sample.pdf');
  const fileBuffer = fs.readFileSync(filePath);

  const response = await request.post('/files/upload', {
    multipart: {
      file: {
        name: 'sample.pdf',
        mimeType: 'application/pdf',
        buffer: fileBuffer,
      },
      description: 'Test upload',
    },
  });

  expect(response.status()).toBe(201);
  const body = await response.json();
  expect(body.filename).toBe('sample.pdf');
  expect(body.size).toBeGreaterThan(0);
});

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

34.27%
按下载量换算185

Claude

29.5%
按下载量换算160

Cursor

18.18%
按下载量换算98

Gemini CLI

10.18%
按下载量换算55

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

未通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills