ekofyi
Playwright's API Testing Isn't Just a Postman Replacement — It's a Security Swiss Army Knife
Security Research11 min read

Playwright's API Testing Isn't Just a Postman Replacement — It's a Security Swiss Army Knife

Playwright's request context goes far beyond replacing Postman — it's a programmable, scalable tool for API security testing, auth token replay, schema validation, and multi-step attack simulation. Here's how a pentester uses it.

Playwright's API Testing Isn't Just a Postman Replacement — It's a Security Swiss Army Knife

I've been doing API-focused security work for almost three years now — pentesting, reverse-engineering undocumented APIs, building automation that chains dozens of requests together to simulate complex attack paths. The tooling landscape has always felt slightly... off. On one side you have Burp Suite, indispensable for intercepting and replaying but miserable for large-scale automation. On the other, Postman, great for manual exploration but whose collection runner, Newman, has caused me more CI headaches than I care to remember. And then there's the custom script graveyard: one-off Python requests scripts, half-baked Node.js chains, bash curls that nobody but me understands.

Today, a new article on the Playwright Playbook series landed, calling API testing with Playwright "the underrated superpower." I read it, and honestly? It's about time someone said this out loud. Playwright's APIRequestContext has been quietly sitting there, allowing you to do full HTTP testing alongside your browser automation, and it's criminally underappreciated in the security testing community.

So I want to dig deeper. I'll walk through what makes this capability so powerful from a security perspective, show practical code examples, and explain why if you're still using Postman for automation-heavy security work, you're leaving a lot on the table.

What Playwright's API Testing Actually Brings

Most people know Playwright as an end-to-end browser testing framework. You spin up a browser, navigate to a page, click buttons, assert content. That's the surface.

But Playwright has a separate, equally capable HTTP client built in. The request fixture (or apiRequest depending on your version) gives you an APIRequestContext that makes arbitrary HTTP requests, completely independent of any browser page. It handles cookies, follows redirects, supports all HTTP methods, and lets you set custom headers, body, and query parameters. It's like having fetch on steroids, with the same abstractions you already know from the browser side.

The key: you can use the same script, same test runner, same reporting, and same setup/teardown logic for both API and UI operations. That interleaving is where the security superpower lies.

Consider the classic pentest task: you discover a reflected XSS in an endpoint. You want to verify it actually executes in the browser. With separate tools, you'd craft the attack in Burp, copy the URL, open it in a real browser, check the alert. Fragile, slow, manual. With Playwright, you can do:

typescript
const response = await request.post('/comments', {
  data: { body: '<img src=x onerror=alert(1)>' }
});
expect(response.status()).toBe(200);

const page = await browser.newPage();
await page.goto('/comments/view/123');
const dialog = await page.waitForEvent('dialog');
expect(dialog.message()).toContain('1');
await dialog.dismiss();

One flow, one artifact. If it fails, you have a video, a trace, and all the context. That's a game changer for repeatable security assessments, especially when you need to prove impact.

The Authentication Tango

API testing tools always struggle with authentication, especially OAuth flows with redirects, state parameters, and multi-step token exchanges. Most people resort to manually grabbing a token from the browser and pasting it into Postman. That's not automation — that's a ritual.

Playwright can perform the entire login flow in the browser, capture the session token, and then inject it into the API request context. Or, it can do the whole OAuth dance via raw HTTP if you know the endpoints. The ability to mix UI and API means you can choose the most efficient path.

Here's a pattern I've used to test session token handling after a password change:

typescript
// Log in via the UI
const page = await browser.newPage();
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'oldpassword');
await page.click('button[type=submit]');
await page.waitForURL('/dashboard');

// Grab the session token from cookies
const cookies = await context.cookies();
const sessionCookie = cookies.find(c => c.name === 'session');

// Now use the API to change the password
const apiContext = await browser.newContext({
  extraHTTPHeaders: {
    'Cookie': `session=${sessionCookie.value}`
  }
}).request;

await apiContext.post('/api/change-password', {
  data: { old_password: 'oldpassword', new_password: 'newpassword' }
});

// Now try to use the old session token — it should be invalidated
const res = await apiContext.get('/api/profile');
expect(res.status()).toBe(401);

This is exactly the kind of multi-step, stateful test that catches real vulnerabilities. Session invalidation bugs, token reuse after logout, OAuth scope escalation — all testable in a single script without any external tooling.

Schema Validation as a Security Hammer

One of the points the article highlights is schema validation, and it's something I've come to view as essential for API security testing.

Many APIs inadvertently leak fields they shouldn't — internal IDs, error stack traces, admin flags, database query durations — because the backend developer returned the full database row or didn't sanitize the response. When you're manually poking at an API, you might notice these leaks by inspecting the JSON. But in automated regression testing, they slip through unless you're explicitly validating the shape of responses.

Playwright lets you integrate schema validation libraries like Zod or Ajv directly into your tests. Once you define the expected schema, any deviation fails the test — a clear signal that something changed, possibly exposing sensitive data.

typescript
import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string().email(),
  // Explicitly deny extra fields — if the API starts returning 'role' or 'internal_id', fail hard
}).strict();

const res = await request.get('/api/users/me');
const data = await res.json();
expect(() => userSchema.parse(data)).not.toThrow();

That .strict() on the Zod schema is what catches information leaks. A new admin: false field? Test fails. Undocumented version field? Test fails. You get a clean CI failure instead of a silent data leak.

In security testing, I'd take this further — defining expected error response schemas, validating that certain fields are always absent (like password_hash — yes, I've seen that in production), and ensuring enumeration endpoints don't unexpectedly expose PII.

GraphQL: Less Magic, More Control

The article mentions GraphQL testing, and it's worth emphasizing just how messy GraphQL security testing can be with conventional tools. GraphQL endpoints are a single POST route, introspection queries reveal the schema, and the complexity of nested queries makes manual testing tedious.

Playwright's request context treats GraphQL like any other HTTP request, but the benefit is integration: you can send a sophisticated introspection query, parse the response to discover mutations, and then automatically fuzz them. Or you can chain a GraphQL mutation to set up test data, then verify the side effects via UI.

I've used this to test authorization: create two users via API, have user A send a message to user B via a mutation, then log in as user B in the browser and verify they can't see user A's private data. The whole flow, including GraphQL calls, fits in a single test.

typescript
const query = `
  mutation SendMessage($to: ID!, $body: String!) {
    sendMessage(to: $to, body: $body) {
      id
      body
    }
  }
`;

const res = await request.post('/graphql', {
  data: { query, variables: { to: 2, body: 'secret' } },
  headers: { 'Authorization': `Bearer ${userAToken}` }
});
expect(res.status()).toBe(200);

// Now verify user B cannot see this message
const page = await browser.newPage();
await page.goto('/messages');
await page.evaluate(token => { localStorage.setItem('token', token); }, userBToken);
await page.goto('/messages');
await expect(page.locator('.message-body')).not.toContainText('secret');

The cross-cutting verification is what makes this effective. You're not just checking the GraphQL response — you're checking that the entire system respects the authorization boundary, end-to-end.

Why Postman Falls Short for Security Automation

Let me be blunt: Postman is a fantastic tool for manual API exploration, debugging, and quick-and-dirty checks. I use it all the time to poke at an undocumented endpoint, examine headers, and get a feel for an API. But as soon as you need repeatability, version control, branching strategies, CI integration, parallel execution, or complex conditional logic, Postman's value plummets.

The Newman CLI is an afterthought. Environment variable scoping is a mess. Exporting and importing collections is fragile. And any test that requires a real browser — say, verifying a redirect from an API callback — requires you to break out to another tool entirely.

Playwright, on the other hand, is a first-class programming environment. Your tests are TypeScript (or JavaScript), they live in your repo, they're reviewed like any other code, and they run in every CI provider without custom plugins. The APIRequestContext shares cookies and storage state with the browser, meaning your authenticated API calls and subsequent page loads are in the same session — effortlessly.

The security implication: you can trigger an API call that sends a password reset email, then poll an email API (or even parse the email HTML in the browser if you have a test inbox) to extract the reset link, follow it, and verify the new password works. This is the kind of orchestration that would be a nightmare in Postman but is straightforward in Playwright.

The Automation Engineer's Edge

I work at the intersection of security and automation. My job is to find vulnerabilities, but also to build tooling that makes those findings reproducible, demonstrable, and scalable. Playwright's API testing is a natural fit for that.

You can build a library of "security checks" as Playwright tests: auth token replay, IDOR via parameter substitution, rate limiting, CORS misconfigurations, verbose errors. Each check is a function you can drop into any project. Over time, you accumulate a suite that turns manual pentesting efforts into automated regression tests. The same tests that caught a SQL injection during a pentest can run nightly to ensure it never reappears.

typescript
export async function checkIDOR(baseURL: string, authToken: string) {
  const publicObjects = await fetch(`${baseURL}/api/objects/1`, {
    headers: { Authorization: `Bearer ${authToken}` }
  }).then(r => r.json());
  expect(publicObjects.owner).toBe('user1');

  // Try to access another user's object
  const hidden = await fetch(`${baseURL}/api/objects/2`, {
    headers: { Authorization: `Bearer ${authToken}` }
  });
  expect(hidden.status).toBe(403);
}

Wrap this in a Playwright test, add it to your CI pipeline, and you've got continuous IDOR detection.

This is why I find the "underrated superpower" framing spot on. The industry talks about "shifting left" security, but the tools often lag behind. Playwright gives you a general-purpose HTTP client that's already integrated into your development workflow. No new tool to learn, no separate execution environment — just a new feature in a framework you already trust.

But It's Not Perfect

No tool is. Playwright's APIRequestContext has a few limitations worth noting.

First, it's not a full proxy. If you need to intercept and modify requests on the fly — like an active MITM — you'll still want Burp or mitmproxy. Playwright can be configured to use those proxies, though, so it's not an either-or situation.

Second, the request context doesn't automatically inherit the browser's network conditions, throttling, or offline simulation. If your test depends on those, you'll need to manually configure them or use the browser page to trigger API calls indirectly.

Third, for pure API integration testing without any UI component, Playwright might feel a bit heavy. If all you need is a fast HTTP client with assertions, Vitest with fetch might be simpler. But as soon as you add even a single UI check, Playwright's unified model pays for itself.

Despite these, the overall gains in consistency, traceability, and cross-functionality far outweigh the drawbacks for security-centric testing.

Where This Leaves Us

The article published today on the Playwright Playbook series does a good job of showcasing the basics. But I wanted to push the conversation further, into territory that matters for those of us who see APIs as attack surfaces.

Playwright's API testing isn't just about convenience or replacing Postman. It's about reshaping how we think about security testing — from a separate, manual activity to an integrated, automated discipline. When your end-to-end tests already verify business logic, adding security assertions (schema strictness, auth token handling, privilege boundaries) is a natural next step.

If you're a developer or QA engineer writing Playwright tests, start adding response schema validation. If you're a security engineer, stop writing one-off proof-of-concept scripts and start building reusable Playwright checks that live in the main test suite. The tool is there. The superpower is real. It's time we used it.

Related posts

Written by Eko

If you found this useful, follow @ekofyi on X for more notes like this — or get in touch if you have a problem to solve.