Browser-context HTTP
tab.request() makes an HTTP call that inherits the browser's cookies and
session. It needs no feature flag. The builder mirrors pydoll's shape:
use serde_json::json;
// GET (inherits cookies + same-origin CORS of the current page)
let resp = tab.request().get("https://example.com/api/data").send().await?;
println!("{} {}", resp.status(), resp.text()?);
// POST a JSON body
let resp = tab
.request()
.post("https://example.com/api/echo")
.header("X-Trace", "1")
.json(&json!({ "key": "value" }))?
.send()
.await?;
let parsed: serde_json::Value = resp.json()?;
get / post / put / delete / head / patch set the
method and URL; header appends a header; body sets raw bytes;
json serializes a value and sets Content-Type: application/json.
send returns a Response exposing status / headers /
text / json / bytes.
A non-2xx status is not an error — the Response carries the status.
Only a thrown fetch (network failure, CORS block) or a failed privileged
load surfaces a
ZendriverError::Request.
Default path: in-page fetch
By default send() runs fetch in the page via evaluate_main, so the
request behaves exactly like one the page itself made — same cookies, same
CORS rules. Because of that it needs a loaded document of the right
origin: navigate to the origin first, then make same-origin calls. On
about:blank a cross-origin call is a null-origin request and will be
CORS-blocked.
The request URL, headers, and body are embedded into the generated JS via
serde_json, so arbitrary url / header / body values can't break out of the
JS string — there is no injection surface. The body round-trips as base64 so
binary payloads survive intact.
Opt-in: bypass_cors()
bypass_cors routes through Chrome's privileged
Network.loadNetworkResource instead — it ignores page CORS and works
without a same-origin document, while still inheriting session cookies. It
is GET-only in this version; for other methods use the default fetch
path.
// Reach a cross-origin endpoint that the in-page fetch would be blocked on.
let resp = tab
.request()
.get("https://other-origin.example/resource")
.bypass_cors()
.send()
.await?;
Full example
//! Demonstrates the browser-context HTTP API (`tab.request()`). //! //! `tab.request()` runs `fetch` inside the page, so the request inherits the //! page's cookies and same-origin CORS rules. Use `.bypass_cors()` to route //! through Chrome's privileged `Network.loadNetworkResource` path instead //! (GET only; bypasses CORS). //! //! Sequence: //! 1. Navigate to example.com so that cookies / origin context is //! established. //! 2. Issue a GET to `https://httpbin.org/get` — prints status + truncated //! body. //! 3. Issue a POST with a JSON body to `https://httpbin.org/post` — prints //! status + truncated body. //! 4. Close the browser. //! //! No special cargo features required: //! `cargo run --example browser_request`. use serde_json::json; use zendriver::Browser; #[tokio::main] #[allow(clippy::result_large_err)] // example boundary; users wrap in their own Error async fn main() -> zendriver::Result<()> { tracing_subscriber::fmt::init(); let browser = Browser::builder().headless(true).launch().await?; let tab = browser.main_tab(); // Navigate first so the page context (cookies / origin) is established. tab.goto("https://example.com").await?; tab.wait_for_load().await?; // ── GET ────────────────────────────────────────────────────────────────── let resp = tab.request().get("https://httpbin.org/get").send().await?; let body = resp.text()?; println!( "GET status={} body_len={} snippet={:.80}", resp.status(), body.len(), body ); // ── POST with JSON body ─────────────────────────────────────────────────── let resp = tab .request() .post("https://httpbin.org/post") .json(&json!({"hello": "zendriver"}))? .send() .await?; let body = resp.text()?; println!( "POST status={} body_len={} snippet={:.80}", resp.status(), body.len(), body ); browser.close().await?; Ok(()) }