API Steps

The API step group lets webship-js scenarios talk to an HTTP API directly, without leaving the cucumber-js runtime. Requests are sent with axios rather than through the browser, so they run fast and never depend on page state - yet the steps live in the same suite as your UI flows, so QA and automation teams can cover backend contracts and front-end behavior in one place. A typical pattern is a UI action followed by a REST-level assertion that the backend recorded it.

The group covers the whole request lifecycle in plain Gherkin: set a base URL, add headers (including Basic Authentication), build a request body from an inline JSON string, a data table, a doc string, or form data, send any HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS), then assert on the status code, response text, JSON properties (with dot-notation paths like user.role), and response headers. A placeholder mechanism ({{name}} tokens) lets you reuse values across URLs, headers, bodies, and expected values.

Request state (headers, body, authorization, placeholders, last response) is reset automatically before every scenario. Requests never throw on a non-2xx status - the response is captured whatever the code, so you can assert 404 or 500 outcomes just as easily as 200. Every assertion step gives a friendly error if no request has been sent yet.

Quick reference

StepWhat it does
Given the API base URL is "https://jsonplaceholder.typicode.com"Sets the base URL for all following requests (full URL or path relative to LAUNCH_URL).
Given I am authenticating as "admin" with "password123" passwordAdds a Basic Authentication header to subsequent requests.
Given I set header "Content-Type" with value "application/json"Sets a single HTTP request header.
Given I set the header "Content-Type" to "application/json"Sets a request header - alternative syntax.
Given I set the following headers:Sets multiple headers from a data table.
Given I set the request body to '{"name": "John", "email": "john@example.com"}'Sets a JSON request body from an inline string.
Given I set the request body with:Builds the request body from a key/value table.
When I send a GET request to "/users"Sends an HTTP request (any method) to a relative URL.
When I send a POST request to "/users" with values:Sends a request with JSON fields from a table.
When I send a POST request to "/users" with body:Sends a request with a raw body from a doc string.
When I send a POST request to "/login" with form data:Sends a URL-encoded form submission.
Then the API response code should be 200Asserts the response status code.
Then the API response should contain "success"Asserts the response body contains text (case-insensitive).
Then the API response should not contain "error"Asserts the response body does not contain text.
Then the API response should contain json:Asserts the response contains the JSON keys/values from a doc string.
Then the JSON response should have "name" equal to "John Doe"Asserts a JSON property equals a value (dot paths supported).
Then the JSON response should have property "id"Asserts a JSON property exists.
Then the JSON response should not have property "password"Asserts a JSON property does not exist.
Then the response should be valid JSONAsserts the response body parsed as JSON.
Then the response header "Content-Type" should be "application/json"Asserts a response header value.
Then print API responsePrints the last request method, URL, status, and body to stdout (debugging).
Given I set placeholder "{{userId}}" to "123"Registers a placeholder replaced in URLs, headers, bodies, and expectations.

Steps in detail

Given the API base URL is "https://jsonplaceholder.typicode.com"

Sets the base URL prepended to every endpoint. Also matches I set the API base URL to "..." and the base URL is "...". A full URL (with http:// or https://) is used as-is; a bare path such as /api/v2 is resolved against the LAUNCH_URL environment variable (default http://localhost:8080). Slashes are normalized so joined URLs never double up. Put this in a Background: so all scenarios in a feature share it.

Background:
  Given the API base URL is "https://jsonplaceholder.typicode.com"

Given I am authenticating as "admin" with "password123" password

Base64-encodes username:password and sets an Authorization: Basic ... header on all following requests, replacing any previous one. Also matches the we are authenticating as ... pronoun. For Bearer tokens, set the header directly with a header step instead.

Given I am authenticating as "admin" with "password123" password
When I send a GET request to "/posts/1"
Then the API response code should be 200

Given I set header "Content-Type" with value "application/json" / Given I set the header "Content-Type" to "application/json"

Two interchangeable phrasings for setting a single request header; the header "Accept" is "application/json" also works. Values run through placeholder replacement, so Bearer {{token}} resolves at request time. Headers persist for the rest of the scenario and are cleared before the next one.

Given I set header "Accept" with value "application/json"
  And I set the header "Authorization" to "Bearer token123"

Given I set the following headers:

Sets several headers at once from a two-column table (name | value). Values support {{placeholder}} tokens. Equivalent to repeating the single-header step, just more compact.

Given I set the following headers:
  | Content-Type | application/json |
  | Accept       | application/json |
  | User-Agent   | WebshipJS/1.0    |

Given I set the request body to '{"name": "John", "email": "john@example.com"}'

Stores a JSON body (single-quoted so the double quotes inside survive) to be sent with the next send a ... request step - typically POST, PUT, or PATCH. The string must parse as JSON or a clear error is raised; placeholders are replaced first. Also matches the request body is '...'.

Given I set the request body to '{"title": "Test Post", "body": "This is a test post", "userId": 1}'
When I send a POST request to "/posts"
Then the API response code should be 201

Given I set the request body with:

Builds the body from a key/value table. Values are auto-typed: true/false become booleans and numeric strings become numbers, so | views | 100 | sends "views": 100, not a string.

Given I set the request body with:
  | title     | My New Post |
  | userId    | 1           |
  | published | true        |
When I send a POST request to "/posts"

When I send a GET request to "/users"

Sends the request using the accumulated base URL, headers, and body. The method is any uppercase HTTP verb - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS - and the endpoint may include a query string or placeholders ("/posts/{{postId}}"). The step never fails on a non-2xx status; the response is stored for the Then assertions that follow.

When I send a GET request to "/posts/999999"
Then the API response code should be 404

When I send a POST request to "/users" with values:

Sends the request with a JSON object built from the table in one step - no separate body step needed; Content-Type: application/json is added automatically. Works with any method, e.g. PUT ... with values: for updates.

When I send a POST request to "/posts" with values:
  | title  | Table Post         |
  | body   | Created with table |
  | userId | 1                  |
Then the API response code should be 201

When I send a POST request to "/users" with body:

Sends the request with a raw body from a doc string. If the doc string parses as JSON it is sent as JSON (and Content-Type: application/json is added when you have not set one); otherwise it is sent as a plain string - useful for non-JSON payloads.

When I send a POST request to "/posts" with body:
  """
  {
    "title": "DocString Post",
    "body": "Created with docstring",
    "userId": 1
  }
  """

When I send a POST request to "/login" with form data:

Sends a classic form submission. Each doc-string line is a key=value pair; the step URL-encodes them and sends the request with Content-Type: application/x-www-form-urlencoded.

When I send a POST request to "/posts" with form data:
  """
  title=Form Data Post
  body=Created with form data
  userId=1
  """

Then the API response code should be 200

Asserts the exact status code of the last response - 200, 201, 204, 404, 500, and so on. Because requests never throw on error statuses, negative-path testing is a first-class citizen.

When I send a DELETE request to "/posts/1"
Then the API response code should be 200

Then the API response should contain "success" / should not contain "error"

Substring checks against the response body (JSON responses are stringified first). The positive check is case-insensitive, and special regex characters in your text are escaped so literal values match safely. The negative variant is handy for asserting that error markers, secrets, or stack traces never leak into a response.

When I send a GET request to "/posts/1"
Then the API response should contain "title"
  And the API response should not contain "password"

Then the API response should contain json:

Partial JSON matching from a doc string: every key in the expected JSON must exist in the response with a deep-equal value, while extra response keys (ids, timestamps) are ignored. Placeholders in the expected JSON are resolved before comparison.

Then the API response should contain json:
  """
  {
    "title": "Test Title",
    "userId": 1
  }
  """

Then the JSON response should have "name" equal to "John Doe"

Asserts one JSON property equals a value. The path supports dot notation for nested objects ("user.role"). Expected values are auto-typed: quoted values compare as strings, true/false as booleans, bare numbers as numbers. Alternative phrasings match the same step: the API response should have "id" equal to 1 and the JSON property "count" should be 42.

When I send a GET request to "/posts/1"
Then the JSON property "userId" should be 1
  And the JSON response should have "id" equal to 1

Then the JSON response should have property "id" / should not have property "password"

Existence checks on a JSON property, with the same dot-notation paths. The positive form also matches the API response should contain property "...", the negative form the API response should not contain property "...". The negative check is ideal for verifying sensitive fields are stripped from responses.

When I send a GET request to "/users/1"
Then the JSON response should have property "address.city"
  And the JSON response should have property "company.name"
  And the JSON response should not have property "password"

Then the response should be valid JSON

Asserts the response body was parsed as a JSON object or array. Also matches the API response should be valid JSON. Use it as a cheap smoke check before deeper property assertions.

When I send a GET request to "/posts"
Then the API response code should be 200
  And the response should be valid JSON

Then the response header "Content-Type" should be "application/json"

Asserts a response header. Despite the should be wording this is a contains check (should contain matches the same step), so "application/json" passes even when the server returns application/json; charset=utf-8. Header names match case-insensitively; the shorter the header "..." should contain "..." also works.

When I send a GET request to "/posts/1"
Then the response header "Content-Type" should be "application/json"

Then print API response

Prints the last request's method, URL, status code, and pretty-printed body to stdout. A debugging aid while writing scenarios - strip it before committing.

When I send a GET request to "/posts/1"
Then print API response

Given I set placeholder "{{userId}}" to "123"

Registers a named token substituted in endpoint URLs, header values, request bodies, expected JSON, and expected property values. Placeholders live for the scenario and are cleared before the next one - use them to keep ids, tokens, or environment-specific values in one place.

Given I set placeholder "{{postId}}" to "1"
When I send a GET request to "/posts/{{postId}}"
Then the JSON property "id" should be {{postId}}

Complete example

Adapted from tests/features/complete-api-testing-examples.feature, running against the public JSONPlaceholder API:

Feature: REST API contract checks

  Background:
    Given the API base URL is "https://jsonplaceholder.typicode.com"

  Scenario: Create a post and verify the JSON contract
    Given I set placeholder "{{newTitle}}" to "Complex API Test"
      And I set the following headers:
        | Content-Type | application/json      |
        | Accept       | application/json      |
        | User-Agent   | WebshipJS-Testing/1.0 |
      And I set the request body to '{"title": "{{newTitle}}", "body": "Testing all features together", "userId": 1}'
     When I send a POST request to "/posts"
     Then the API response code should be 201
      And the response should be valid JSON
      And the API response should contain "Complex API Test"
      And the JSON response should have property "id"
      And the JSON property "userId" should be 1
      And the API response should not contain "password"
      And the response header "Content-Type" should be "application/json"

  Scenario: Missing resource returns 404
    When I send a GET request to "/posts/999999"
    Then the API response code should be 404

Tips

  • Relative base URLs (e.g. Given the API base URL is "/api/v2") resolve against the LAUNCH_URL environment variable, defaulting to http://localhost:8080 - handy for running the same feature against local and CI environments.
  • You can mix UI and API steps in one scenario: drive the UI with browser steps, then hit the API to confirm the backend state. A lighter short-form alternative for smoke tests lives in the REST step group.
  • Request bodies that need fresh dates can use tokens, e.g. "" - they resolve before the request is sent.
  • For HTTP Basic Auth on a browser page navigation (rather than an API request), use the path step Given the basic authentication with the username "admin" and the password "secret" instead.
  • Testing a JSON:API backend? The repo ships a sample payload at examples/example-api.json whose nested shape suits the dot-notation property steps.

Back to all step groups