Skip to content

How to Add a New API Test Case

How to Add a New API Test Case

๐Ÿ“ 1. Create or Extend an API Controller

All API logic is abstracted under the controllers/ folder. This helps separate test logic from API interaction and makes your code more reusable and testable.

๐Ÿ”ง Example: Add a new GET endpoint

๐Ÿ“„ controllers/documents/getDocument.ts

import { getSharedApiContext } from "../../utils/functions/apiContext";

export async function getDocument(documentId?: string) {
  const id = documentId || process.env.DOCUMENT_ID;
  const apiContext = await getSharedApiContext();
  return apiContext.get(`/api/documents/${id}`);
}

export async function getAllDocuments() {
  const apiContext = await getSharedApiContext();
  return apiContext.get(`/api/documents`);
}

โœ… Place related endpoints in the same file (e.g., /documents calls in one file).

๐Ÿงช 2. Add the Corresponding Test Spec

All API test cases go in tests/api/.

๐Ÿงพ Template Structure

import { test, expect } from "@playwright/test";
import { getDocument } from "../../controllers/documents/getDocument";

test.describe("Document API Tests", () => {
  test("Validate get document by ID @api", async () => {
    const response = await getDocument("sample-document-id");
    expect(response.status()).toBe(200);

    const body = await response.json();
    expect(body).toHaveProperty("documentId");
  });
});

๐Ÿงผ 3. Use beforeEach and afterEach for Setup and Cleanup

Use beforeEach to prepare the environment (e.g., create a document), and afterEach to clean up.

test.beforeEach(async () => {
  const response = await createDocument("pdf", "MyFile.pdf");
  const body = await response.json();
  documentId = body.documentId;
});

test.afterEach(async () => {
  await deleteDocumentById(documentId);
});

๐Ÿงฑ 4. Create Step-Based Assertions

Use test.step() blocks for detailed traceability:

test("Create a spell check execution @api", async () => {
  await test.step("Send request to create execution", async () => {
    const response = await createSpellCheckExecution(documentId);
    expect(response.status()).toBe(201);
    const body = await response.json();
    expect(body).toHaveProperty("id");
  });
});

๐Ÿงช 5. Tag and Execute the Test

Add @api to your test description: test("Validate API response @api", async () => { ... }); Then run with: npx playwright test -g "@api"

๐Ÿงพ 6. Example: Full Lifecycle Test Case

test.describe("Spell Check API Tests", () => {
  let documentId: string;
  let spellCheckExecutionId: string;

  test.beforeEach(async () => {
    const res = await createDocument("pdf", "MyTestFile.pdf");
    const body = await res.json();
    documentId = body.documentId;
  });

  test("Create a spell check execution @api", async () => {
    const res = await createSpellCheckExecution(documentId);
    expect(res.status()).toBe(201);
    const body = await res.json();
    spellCheckExecutionId = body.id;
  });

  test.afterEach(async () => {
    if (spellCheckExecutionId) {
      await deleteSpellCheckExecution(documentId, spellCheckExecutionId);
    }
    await deleteDocumentById(documentId);
  });
});

๐Ÿ“Œ Best Practices

Practice Why It Matters
Use controllers Keeps test files clean and promotes reusability
Always assert response status Ensures basic validation of HTTP call
Use beforeEach / afterEach Avoids test pollution and ensures isolation
Add @api tag For targeted test execution and reporting
Use test.step() Improves trace readability
Log with console.log() or expect.soft() For non-blocking insights during test runs
Back to top