> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getgriffinapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Assertions

> Validate API responses and browser page state with a fluent assertion DSL

Assertions let you validate HTTP responses and browser page state — status codes, response bodies, headers, latency, page URLs, extracted text, and more. They use a fluent API that reads naturally:

```typescript theme={null}
Assert(state["my-request"].status).equals(200)
Assert(state["my-request"].body["name"]).not.isEmpty()
```

## API request assertion subjects

You can assert on four properties of any HTTP request node:

### Status code

```typescript theme={null}
Assert(state["req"].status).equals(200)
Assert(state["req"].status).greaterThanOrEqual(200)
Assert(state["req"].status).lessThan(300)
```

### Response body

Access nested properties with bracket notation. Use `.at(index)` for array elements.

For steps with `response_format: NoContent` (e.g. 204 No Content), there is no body — assert only on `state["req"].status` (e.g. `.equals(204)`).

```typescript theme={null}
// Simple property
Assert(state["req"].body["message"]).equals("ok")

// Nested property
Assert(state["req"].body["data"]["user"]["name"]).equals("Alice")

// Array element
Assert(state["req"].body["items"].at(0)["id"]).isDefined()

// Check the whole body
Assert(state["req"].body["data"]).not.isEmpty()
```

### Headers

```typescript theme={null}
Assert(state["req"].headers["content-type"]).contains("application/json")
Assert(state["req"].headers["x-request-id"]).isDefined()
```

### Latency

Latency is measured in milliseconds:

```typescript theme={null}
Assert(state["req"].latency).lessThan(2000)
Assert(state["req"].latency).lessThanOrEqual(500)
```

## Browser assertion subjects

Browser nodes expose different properties than API request nodes. See [Browser Monitors](/writing-tests/browser-monitors) for full details.

### Page URL

```typescript theme={null}
Assert(state["browser_node"].page.url).contains("/dashboard")
Assert(state["browser_node"].page.url).startsWith("https://")
```

### Page title

```typescript theme={null}
Assert(state["browser_node"].page.title).equals("Dashboard")
Assert(state["browser_node"].page.title).contains("App")
```

### Duration

Total time for all browser steps, in milliseconds:

```typescript theme={null}
Assert(state["browser_node"].duration).lessThan(5000)
```

### Console errors

JavaScript errors captured from the browser console:

```typescript theme={null}
Assert(state["browser_node"].console.errors).isEmpty()
```

### Extracted text

Values captured by `extractText()` steps:

```typescript theme={null}
Assert(state["browser_node"].extracts["heading"]).equals("Welcome")
Assert(state["browser_node"].extracts["price"]).contains("$")
```

## Predicates

### Comparison

| Method                   | Description           |
| ------------------------ | --------------------- |
| `.equals(value)`         | Exact equality        |
| `.greaterThan(n)`        | Greater than          |
| `.lessThan(n)`           | Less than             |
| `.greaterThanOrEqual(n)` | Greater than or equal |
| `.lessThanOrEqual(n)`    | Less than or equal    |

### String

| Method             | Description        |
| ------------------ | ------------------ |
| `.contains(str)`   | Contains substring |
| `.startsWith(str)` | Starts with prefix |
| `.endsWith(str)`   | Ends with suffix   |

### Existence

| Method         | Description                                     |
| -------------- | ----------------------------------------------- |
| `.isNull()`    | Value is null                                   |
| `.isDefined()` | Value is not null                               |
| `.isEmpty()`   | Value is empty (empty string, array, or object) |
| `.isTrue()`    | Value is true                                   |
| `.isFalse()`   | Value is false                                  |

## Negation

Prefix any predicate with `.not` to negate it:

```typescript theme={null}
Assert(state["req"].body["error"]).not.isDefined()
Assert(state["req"].status).not.equals(500)
Assert(state["req"].body["name"]).not.isEmpty()
Assert(state["req"].body["items"]).not.isNull()
```

## Using with the sequential builder

The sequential builder provides a callback with a type-safe `state` proxy:

```typescript theme={null}
builder
  .request("users", { ... })
  .assert((state) => [
    Assert(state["users"].status).equals(200),
    Assert(state["users"].body["count"]).greaterThan(0),
  ])
```

The `state` proxy only exposes nodes that have been defined above the assertion, giving you compile-time autocomplete.

## Using with the graph builder

With the graph builder, create a state proxy manually using `createStateProxy`:

```typescript theme={null}
import { createStateProxy, Assert, Assertion } from "@griffin-app/griffin";

const state = createStateProxy(["get-users", "create-user"]);

builder.addNode("validate", Assertion([
  Assert(state["get-users"].status).equals(200),
  Assert(state["create-user"].status).equals(201),
]))
```

## Multiple assertions

You can add multiple assertion nodes to validate different parts of your flow:

```typescript theme={null}
builder
  .request("create", { ... })
  .assert((state) => [
    Assert(state["create"].status).equals(201),
  ])
  .request("read", { ... })
  .assert((state) => [
    Assert(state["read"].status).equals(200),
    Assert(state["read"].body["name"]).equals("Test"),
  ])
```

A monitor run is marked as **failed** if any assertion in any assertion node fails.
