INTEGRATION TESTING · LESSON 12 OF 24
API tests and owned test data
Test HTTP behavior with a separate data namespace for each scenario.
- request fixture
- status codes
- JSON
- cleanup
Your runnable checkpoint
Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.
Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.
1. Understand the concepts
The request fixture makes HTTP requests without a browser page. Use it to test API contracts or create owned data efficiently. A successful request still needs assertions about status, response shape and meaningful behavior.
The practice server has an in-memory task API only on localhost. Every request must include X-Test-Id, and each namespace has its own records. This is a test-isolation mechanism, not an authentication design.
Create a random scope for each test. Delete only that scope in finally so a failure does not leave data behind. A global reset would let parallel tests erase one another’s records.
The API data and the browser’s local task list are intentionally separate in this lab. API tests do not automatically seed the UI. When integrating a real application, verify that the UI actually reads the backend you are preparing.
2. Build and run
These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.
npm install
npx playwright install chromium
npm run check
npm testtests/lesson.spec.ts
import { test, expect } from '@playwright/test';
import { randomUUID } from 'node:crypto';
test('create, read and remove an API task', async ({ request }) => {
const headers = { 'X-Test-Id': randomUUID() };
try {
const created = await request.post('/api/tasks', { headers, data: { title: 'API task' } });
expect(created.status()).toBe(201);
const task = await created.json();
expect(task).toMatchObject({ title: 'API task', done: false });
expect(typeof task.id).toBe('string');
const read = await request.get(`/api/tasks/${task.id}`, { headers });
expect(read.status()).toBe(200);
const removed = await request.delete(`/api/tasks/${task.id}`, { headers });
expect(removed.status()).toBe(204);
expect((await request.get(`/api/tasks/${task.id}`, { headers })).status()).toBe(404);
} finally { await request.delete('/api/tasks', { headers }); }
});
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 2 : undefined,
timeout: 30000,
expect: { timeout: 5000 },
reporter: [['list'], ['html', { open: 'never' }]],
use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});
package.json
{
"name": "edupoly-playwright-api-tests-and-owned-data",
"version": "1.0.0",
"private": true,
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:ui": "playwright test --ui",
"check": "tsc --noEmit",
"report": "playwright show-report",
"serve": "node server.mjs"
},
"devDependencies": {
"@playwright/test": "1.56.1",
"typescript": "5.9.3",
"@types/node": "22.20.5"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": [
"node"
],
"noEmit": true
},
"include": [
"tests/**/*.ts",
"pages/**/*.ts",
"playwright.config.ts"
]
}
.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env
3. Trace the code and check the result
- randomUUID keeps data ownership separate across workers.
- Status and body assertions check different parts of the contract.
- finally runs even when an assertion fails.
Verify your result
- Create returns 201; delete returns 204; reading the deleted id returns 404.
- Cleanup affects only this test’s namespace.
4. Practice independently
Add a negative test for a blank API title.
Show the explanation
Use a new X-Test-Id and POST { title: " " }. Expect 422, then clean up that namespace.
When something goes wrong
If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.
