Your first API test
Write and run an API scenario with the shared step library, without a browser.
How an API scenario is tagged, which shared steps send requests and assert on responses, and how
sdods run -l api executes it without launching a browser.
Try one before you write one
Nadia ships on Friday and wants four facts about the catalog service before she does. The scenario
below is the answer, and it is live: press Run scenario and every step really executes — the
request leaves your browser for the same public sandbox envs/staging.yaml points at, and the
exchange it attaches is the one the report would carry. Edit the Gherkin, break an assertion on
purpose, and read what the failure tells you.
Nadia runs the Friday release at Demo Shop. Before she ships, she wants proof that the catalog service still returns a product by id, in the shape the storefront expects, fast enough that the page does not stall.
Done means
- Asking for a known product returns 200, not a redirect or an error page.
- The record carries the id that was asked for and a non-empty title.
- The shape matches the contract the storefront was built against.
- The answer arrives inside the two-second budget on the product page.
@api@smoke· lint clean · 6 steps
No request yet. Every API step attaches its request and response here — the same two files the HTML report shows against the scenario.
- not run: When I send a GET request to "/posts/1"
- not run: Then the response status should be 200
- not run: And the response JSON path "id" should equal "1"
- not run: And the response JSON path "title" should match ".+"
- not run: And the response should match the JSON schema "post"
- not run: And the response time should be under 2000 ms
Run the same scenario on your machine:
sdods run -p demo-shop -e staging -l api -t @smoke
Write the feature
Create projects/demo-shop/features/api/posts.feature:
@api @smoke
Feature: Posts API
Scenario: Read a single post
When I send a GET request to "/posts/1"
Then the response status should be 200
And the response JSON path "id" should equal "1"
And the response time should be under 2000 msTwo tags matter: @api selects the layer, @smoke selects the suite. sdods lint refuses a scenario without exactly one of each.
Send a body and chain values
Support keeps getting tickets that say "I created a listing and it vanished". One scenario answers it: create the listing, keep the id the service handed back, and ask for it again.
@api @regression
Feature: Posts API
Scenario: Create then read
When I send a POST request to "/posts" with body:
"""json
{ "title": "SDODS", "body": "hello", "userId": 1 }
"""
Then the response status should be 201
When I save the response JSON path "id" as "postId"
And I send a GET request to "/posts/{{postId}}"
Then the response status should be one of "200, 404"{{postId}} is rendered from variables captured in the same scenario, plus vars from the
environment file and any loaded dataset row.
The last line says one of "200, 404" on purpose. JSONPlaceholder answers a create with 201 and
a server-assigned id but never stores the record, so the read-back is a 404. Against your own API
that line is should be 200 — the point is that a scenario states what the service actually
guarantees rather than what you wish it did. Run it and watch the id travel:
Raj, who runs support, keeps getting tickets that say “I created a listing and it vanished”. He wants one scenario that creates a listing and immediately asks for it by the id the service handed back.
Done means
- Creating a listing returns 201 with a server-assigned id.
- The id flows into the next request without anyone copying it by hand.
- The read-back is asserted honestly — this public sandbox never stores what you post.
@api@regression· lint clean · 7 steps
No request yet. Every API step attaches its request and response here — the same two files the HTML report shows against the scenario.
- not run: Given I set the variable "title" to "SDODS created this"
- not run: When I send a POST request to "/posts" with body:
- not run: Then the response status should be 201
- not run: And the response JSON path "title" should equal "{{title}}"
- not run: When I save the response JSON path "id" as "postId"
- not run: And I send a GET request to "/posts/{{postId}}"
- not run: Then the response status should be one of "200, 404"
Run the same scenario on your machine:
sdods run -p demo-shop -e staging -l api -t @regression
Validate against a schema
Then the response should match the JSON schema "schemas/post.schema.json"JSON Schema (via ajv), Zod modules and OpenAPI operations are supported. See the step library.
Run it
bun run sdods run -p demo-shop -e staging -l api
bun run sdods run -p demo-shop -e staging -l api -t @smoke-l api runs the target demo-shop--api, which has no browser device. The base URL, headers and authentication come from envs/staging.yaml (api.baseUrl, api.headers, api.auth).
What you get
Every request attaches request.json and response.json (with Authorization redacted) to the scenario, so the HTML report and the run viewer show the exact exchange.