SDODS
Getting started

Your first API test

Write and run an API scenario with the shared step library, without a browser.

What you'll learn

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.

@apiThe catalog service answers a product lookup

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.
The scenario

@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.

MaxiHi — I am Maxi. Nadia needs four facts before she ships. Press Run scenario and watch each one turn green. The request really leaves your browser: this sandbox stands in for Demo Shop’s catalog service.
  1. not run: When I send a GET request to "/posts/1"
  2. not run: Then the response status should be 200
  3. not run: And the response JSON path "id" should equal "1"
  4. not run: And the response JSON path "title" should match ".+"
  5. not run: And the response should match the JSON schema "post"
  6. 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 ms

Two 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:

@apiA listing created through the API can be read back

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.
The scenario

@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.

MaxiTwo calls, one scenario. Watch the id the create returns get saved as a variable and reused in the next line. The read-back is where it gets interesting — stay for the last step.
  1. not run: Given I set the variable "title" to "SDODS created this"
  2. not run: When I send a POST request to "/posts" with body:
  3. not run: Then the response status should be 201
  4. not run: And the response JSON path "title" should equal "{{title}}"
  5. not run: When I save the response JSON path "id" as "postId"
  6. not run: And I send a GET request to "/posts/{{postId}}"
  7. 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.

Next steps

Using an AI coding agent? Install SDODS:

Install the SDODS plugin in Claude Code: skills, subagents, commands and the MCP server.

  1. /plugin marketplace add YarlisAISolutions/sdods-skills
  2. /plugin install sdods@sdods
Learn more about using SDODS with AI tools

On this page