# Browser interactions and HTTP Use Playwright for controls and `ctx.http` for requests made by the website. ## Interact with the page ```ts await ctx.step("open-bookings", async () => { const page = await ctx.browser(); await page.getByRole("link", { name: "Bookings" }).click(); await page.getByRole("heading", { name: "Your bookings" }).waitFor(); }); ``` ## Read JSON through the browser ```ts const bookings = await ctx.step("read-bookings", async () => { const response = await ctx.http.get("/api/bookings"); if (response.status !== 200) { throw new FlowError("BOOKINGS_UNAVAILABLE", "Could not read bookings"); } return Booking.array().parse(JSON.parse(response.body)); }); ``` Import `FlowError` from `@browserlayer/sdk` and your `Booking` schema. Navigate to the website before making relative requests. | Response field | Type | | -------------- | ----------------------------- | | `url` | string | | `status` | number | | `headers` | string map | | `body` | string; parse JSON explicitly | Requests run inside the page with its cookies and origin restrictions. HTTP error statuses are returned normally. Transport failures throw `BROWSER_REQUEST_FAILED`. There are no automatic request retries. ## Submit a request ```ts await ctx.step("submit", () => ctx.http.request("/api/preferences", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ language: "en" }), timeoutMs: 20_000, }), ); ``` ## Observe after an action ```ts await ctx.observe( "confirmation", async () => { const page = await ctx.browser(); if (await page.getByText("Preferences saved").isVisible()) return true; return undefined; }, { timeoutMs: 20_000 }, ); ``` Observation polls without repeating the preceding action. Keep it read-only. Timeout throws `OBSERVATION_TIMEOUT`.