The Testing Academy · Class Notes Wednesday, 2 September (IST)
Live class · study guide

API testing starts here, and the status codes that will fail your first assertion

The framework is done, so the batch turns to API testing. A fast recap of the three-layer picture and URL anatomy, the honest answer to why Playwright instead of Postman or Rest Assured, then the first live request: a ping that returned No tests found until the config learned about a second test directory. Homework is the RESTful Booker collection.

By Pramod Dutta, The Testing Academy. Study notes from the live Playwright 2x class, rebuilt from the session recording. The Eraser deck was not reachable while these notes were written, so the code here was reconstructed from the session and then executed before publishing, on a real Playwright project against the live RESTful Booker API. Every status code on this page was measured rather than remembered, and three of them are not what you would guess.

01

Where this sits

The framework is finished. Fixtures, the base page, data-driven runs, the reporting, all of it works, and it has already run in Jenkins. So the batch turns to the last big block of the syllabus: API testing inside the same framework.

That last phrase is the point. Not a second tool, not a second repo, the same framework.

Still pending and being scheduled this week: Jenkins parts 3 and 4, and a GitHub Actions session. Separately, tonight there is an extra session on Introduction to Agent Skills, with a certification, alongside the Claude Code 101 and AI Fluency certificates the batch already has. All three take one to two hours together.

02

The three layers, and where an API sits

Every web and mobile application is three layers.

UI layer the login box you can see visible Business layer POST /users/login this is what we test Database layer where the row actually lives hidden request response never happens a username and password typed in the browser do not reach the database directly the API is the layer in between, and it is the layer with the business rules in it
The UI is what you see. The database is where the data lives. The API in between is the only road between them, which is why testing it covers so much.

The analogy used in class: you are in a restaurant, the cook does not speak your language, so you need a waiter. You give the waiter an order, the waiter comes back with food. Request, response. The API is the waiter.

You can watch this happen. Open any site, DevTools, Network tab, submit a login with the wrong password, and you will see the request appear: a URL, a method, a payload with what you typed, and a status code coming back. That is the layer we are about to automate.

03

URL anatomy, and the two kinds of parameter

Worth being precise here, because interviewers ask it as a naming quiz.

Code
https://restful-booker.herokuapp.com/booking/1
|____|  |______________|_____________|________|
protocol   subdomain     main domain   path param
        |________________________|
                base URL
  • Path parameter starts with a slash: /booking/1. It identifies a specific thing.
  • Query parameter starts with a question mark: /booking?firstname=John. It filters.
  • Endpoint is the path after the base URL. Some people include the path parameter in that, some do not, and both readings are accepted.
04

The methods, and what each one is for

MethodJobOn a bookings API
GETFetch. One thing, or all of themRead booking 101, or list every booking id
POSTCreate. Needs a payloadCreate a new booking from a JSON body
PUTFull update. Send every fieldReplace booking 101 entirely
PATCHPartial update. Send only what changesChange only the first name on booking 101
DELETERemoveDelete booking 101

PUT against PATCH is the pair that gets asked. Full replace against partial edit.

Status codes come back in families: 1xx informational, 2xx success, 3xx redirect, 4xx you got it wrong, 5xx the server got it wrong. A 401 on a login means the credentials were rejected, and that is the server telling the client what happened.

05

Before you can write a single request

Five questions, every time:

  1. What is the base URL?
  2. Are there parameters, path or query?
  3. Is there a payload, and what shape is the JSON?
  4. What headers does it need? Usually Content-Type: application/json.
  5. Does it need authentication, a token or a key?

For the ping request the answers are: base URL yes, no params, no payload, no special header, no auth. That is why it is the one to start with.

06

Why Playwright and not Postman or Rest Assured

The question came up in class and it is a real interview question, so here is the honest comparison.

PlaywrightPostmanRest Assured
API and UI tests in one frameworkYesNo, they live apartNo, separate library
TypeScriptNativePartial scriptingJava, not TS
Trace viewerYesNoNo
Reuses saved auth stateYesManualManual
JSON schema validationYes, with a libraryPartialNeeds a plugin
Cost in CIFreeLicensed above a pointFree

The first row is the one that matters. Your API tests and your UI tests live in the same repo, run in the same command, and report into the same trace. Everything else is a bonus.

That is not an argument against Postman. Postman is still where you do the manual exploration, and today's homework is a Postman collection. It is an argument against Postman as the place your automation lives.

07

The pyramid, and why API tests earn their place

UI few, slow, flaky API more, faster, stable, most of the business rules Unit many, fastest, cheapest, written by developers cost of a bug the later it is caught, the more it costs to fix caught by a developer: cheapest caught by the end user: worst
API tests sit where the coverage is highest and the flakiness is lowest. That is the whole argument for spending time here.

The follow-up question in class was sharp: if the UI is already built, why not just test through the UI? Because a UI test is slow, brittle and expensive to maintain, and it exercises the same business rule that an API test can check in a fraction of the time. Test through the UI when the thing you are testing is the UI.

08

Manual first, then automate

Automation does not replace the manual pass, it consumes its output.

Requirement docs, Confluence, Jira Test plan Test cases one per rule Execute manually Postman, Bruno, Swagger Automate in Playwright takes the finished test cases as its input Prerequisite for the automation step: a working base framework, which you now have
Automation is the last step, not the only step. It starts from test cases somebody already wrote.

Asked in class: what if there is no documentation at all, no payload, nothing? Then you write the requirement yourself. Open the app, DevTools, Network tab, do the thing, then right click the request and Copy as cURL. Collect those, and you have a spec. It is a legitimate answer in an interview, and it is what people actually do.

09

The first request

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

test('ping request', async ({ request }) => {
  const response = await request.get('/ping');
  expect(response.status()).toBe(201);
});

Three things to notice. There is no page fixture, because there is no browser and no DOM. There is no --headed, for the same reason. And request is a fixture Playwright already ships, so nothing needed installing.

request exposes get, post, put, patch and delete. That is the whole surface for now.

10

No tests found, and the config that fixes it

This is the part that ate ten minutes of class, and it is worth keeping because it will happen to you.

The test above sat in source/api/. Running it gave:

Code
Error: No tests found.

Passing the full path to the file did not help. Saving again did not help. The file was fine.

The config's testDir pointed at source/test. Playwright only collects tests underneath testDir, so a spec outside it does not exist as far as the runner is concerned, no matter what path you type on the command line.

The fix is a config that switches on an environment variable, so one project serves both kinds of test:

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

const isApi = process.env.ENV === 'api';

export default defineConfig({
  testDir: isApi ? './source/api' : './source/test',
  use: isApi
    ? { baseURL: 'https://restful-booker.herokuapp.com' }
    : {
        baseURL: 'https://your-ui-under-test.example',
        screenshot: 'only-on-failure',
        video: 'retain-on-failure',
      },
});

Then run it with the variable set:

Code
ENV=api npx playwright test
Code
Running 1 test using 1 worker
  1 passed (1.4s)

Two payoffs beyond just working. The API baseURL now lives in config, so specs hold /ping rather than the full URL. And ENV=api runs only the API directory, so a UI spec cannot accidentally join the run.

Screenshots and video are set only on the UI branch on purpose. API tests are blind: no browser, no page, nothing to photograph. Leaving those options on the API side costs you nothing but means nothing either. The trace still works, and the trace is what you actually want when a response body is wrong.

11

What the request fixture is doing

request the fixture you ask for APIRequestContext what it hands you HTTP request out to the server Response status, headers, JSON body and then you assert on the response. Naming these two pieces in an interview is worth doing.
Ask for request, get an APIRequestContext, which issues the call and hands back a response to assert on.
12

The status codes that will fail your first assertion

The class project is RESTful Booker, a hotel-booking API. It is a genuinely good thing to put on a CV if you have no API work to point at.

It is also full of status codes that are not what you would guess. Every row below was measured against the live API while writing these notes, not recalled:

#RequestStatusWatch out
1GET /ping201A health check that returns Created. Not 200
2GET /booking200Returns an array of ids only, not full bookings
3GET /booking/{id}200
4POST /booking200A create that returns OK, not 201. No token needed
5PUT /booking/{id}200403 without a token
6PATCH /booking/{id}200403 without a token
7DELETE /booking/{id}201403 without a token, and a delete that returns Created

Assume 200 on the ping and the run fails immediately:

Code
Error: expect(received).toBe(expected)
Expected: 200
Received: 201

Three of the seven need a token, and the class did not get to that part. Creating a booking needs nothing, which is what was shown. But PUT, PATCH and DELETE all return 403 Forbidden on their own. Get a token first, then send it as a cookie:

POST /auth with {"username":"admin","password":"password123"} returns {"token":"..."}, and the write requests take it as a Cookie: token=... header. If three of your seven come back 403 tonight, that is why, and it is the API behaving correctly rather than you doing it wrong.

13

Tasks and announcements

Today's task. Install Postman, create a fresh workspace, and import all seven RESTful Booker requests into one collection using Copy as cURL and Postman's Import button. Name them 01 ping, 02 get all, and so on. Next class automates this collection, so come with it built.

  • Build it yourself. The finished collection was deliberately not shared. It gets shared next class, after everyone has made their own.
  • Watch the two web fundamentals videos if you have not. Recommended for everyone, not just manual testers.
  • Tonight: Introduction to Agent Skills, with certification. If Claude Code 101 and AI Fluency are still outstanding, all three together are one to two hours.
  • Coming next: the full CRUD run, then an API helper utility, then API fixtures, then JSON path and schema validation. Five pieces in total, and the framework is only finished when the fixtures land.
  • Pending and being scheduled this week: Jenkins parts 3 and 4, plus GitHub Actions.
  • Questions go on SDET Club rather than chat, tagged, so they get a proper answer.