# Network (/docs/sims/conditions/network)



<DemoTabs ids="[&#x22;network&#x22;, &#x22;fault&#x22;]" />

`device.network` shapes what the app's own requests see: offline, a preset speed, a flapping link or one host that fails. The rest of the Mac's traffic is untouched, and the network is back online when the lease ends.

```ts title="e2e/network-offline.e2e.ts"
import { expect, test } from "@delacour/warden-e2e";

test("cold start offline shows the banner, going online recovers the fetch", async ({ app, device, screen }) => {
  await device.network.set("offline"); // before the app opens: a cold start offline
  await app.open();
  await expect(screen.getByTestId("conditions-offline-banner")).toBeVisible({ timeout: 15_000 });

  await device.network.set("online");
  await expect(screen.getByTestId("conditions-offline-banner")).toBeHidden({ timeout: 15_000 });
});
```

## Set a condition [#set-a-condition]

`device.network.set(condition)` takes `"online"`, `"offline"`, a preset or a custom shape.

| Preset  | Latency | Jitter | Down     | Up       | Loss |
| ------- | ------- | ------ | -------- | -------- | ---- |
| `edge`  | 400 ms  | 25 ms  | 240 kbps | 200 kbps | 0    |
| `3g`    | 100 ms  | 10 ms  | 780 kbps | 330 kbps | 0    |
| `4g`    | 170 ms  | 15 ms  | 9 Mbps   | 9 Mbps   | 0    |
| `lte`   | 50 ms   | 5 ms   | 50 Mbps  | 10 Mbps  | 0    |
| `wifi`  | 2 ms    | 0      | 40 Mbps  | 33 Mbps  | 0    |
| `slow`  | 2000 ms | 100 ms | 400 kbps | 400 kbps | 0    |
| `lossy` | 500 ms  | 50 ms  | 1 Mbps   | 1 Mbps   | 10%  |

`{ kind: "custom", latencyMs?, jitterMs?, downKbps?, upKbps?, lossPct? }` sets your own.

```ts
await device.network.set("3g");
await device.network.during("offline", async () => {
  // the previous condition is back afterwards, also when this throws
});
await device.network.reset(); // back to the suite's conditions.network (online without one)
```

| Member                                         | Does                                                                                                                      |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `set(c, { relaunch? })`                        | change the network. A running flap stops first                                                                            |
| `during(c, fn)`                                | run `fn` under `c`, then restore                                                                                          |
| `flap({ up, down, jitterPct?, seed?, base? })` | alternate `base` (default online) and offline until `stop()` or the end of the test; returns `{ stop(), done, status() }` |
| `fault(rule \| rules)`                         | per-host faults; `[]` clears                                                                                              |
| `status()`                                     | `{ condition, faults, flapping }`                                                                                         |
| `reset()`                                      | stop the flap, clear faults, back to the baseline                                                                         |

## Flapping [#flapping]

```ts
const flap = await device.network.flap({ up: "1s", down: "1s" });
// ... exercise the retry path ...
await flap.stop();
```

A fixed `seed` replays the same sequence. A flap and faults do not combine: reset one before setting the other.

## Faults [#faults]

A fault applies to every request whose host matches a glob.

```ts
await device.network.fault({ match: "api.example.com", action: { kind: "reset" } });
```

Actions: `{ kind: "status", code }`, `{ kind: "reset" }`, `{ kind: "delay", ms }`, `{ kind: "blackhole" }`.

HTTPS requests are tunnelled without being opened, so a `status` fault cannot answer one: an `https://` request matching it is reset, and `fault()` returns a note saying so. Use `reset` (or `delay` / `blackhole`) for HTTPS hosts and `status` for plain HTTP.

## How it reaches the app [#how-it-reaches-the-app]

|                       | iOS simulator                                                                                                               | Android emulator                           |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| Speed, delay, offline | per-app proxy (a dylib routes the app's `NSURLSession` traffic through the lease's shaping proxy) and a reachability signal | native: `adb emu network`, airplane mode   |
| Loss, faults          | the proxy                                                                                                                   | the proxy, through the system `http_proxy` |

A simulator shares the Mac's network, so on iOS a condition reaches only an app that was launched with warden's network dylib. A running app without it fails `E_NETWORK_RELAUNCH`. Either declare `conditions.network` for the suite ([suite baseline](/docs/sims/conditions#suite-baseline-the-conditions-key)), so every launch carries the dylib, or pass `{ relaunch: true }`.

The shaping proxy binds loopback, and only while it shapes traffic.

## In a suite [#in-a-suite]

```ts title="warden.config.ts"
conditions: { network: "online" },
```

`network` takes `"online"`, `"offline"`, a preset or `{ latencyMs, jitterMs, downKbps, upKbps, lossPct }`. Every spec starts from it, and `reset()` returns to it.

## By hand [#by-hand]

```bash
warden ui network set 3g
warden ui network flap --up 10s --down 3s
warden ui network fault api.example.com reset
warden ui network status
warden ui network reset
```

`warden ui network log` is the request log of the dev client. See [warden ui](/docs/sims/ui) for every flag, including `--relaunch`.

## Limitations [#limitations]

* iOS shapes the app's `NSURLSession` traffic only. Raw sockets, `NWConnection` and `CFStream` bypass the proxy.
* Android loss and faults go through the system `http_proxy`: an app that ignores it (a raw socket, `Proxy.NO_PROXY`) is not covered. Speed, delay and offline are native and reach everything.
* A `status` fault cannot answer an HTTPS request (it is reset).
* Shaping applies to new traffic through the proxy, and an open connection keeps its cost: after warming a connection, a 3g request pays one round trip (the preset's latency), not the three or four of a cold connection. Assert an ordering with a margin, not an absolute number.
