> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.chaser.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.chaser.sh/_mcp/server.

# Common Patterns

Recipes for frequent tasks.

## Batch scraping

Create a session, scrape a list of URLs, delete the session:

```typescript
import { chromium } from "patchright";

const urls = [
  "https://www.g2.com/categories/crm",
  "https://www.g2.com/categories/marketing-automation",
  "https://www.g2.com/categories/analytics",
];

// Create session
const session = await fetch("https://api.chaser.sh/v1/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CHASER_KEY}`,
    "Content-Type": "application/json",
  },
    body: JSON.stringify({ ttl_seconds: 3600 }),
}).then((r) => r.json());

const browser = await chromium.connectOverCDP(session.cdp_url);
const page = browser.contexts()[0].pages()[0] ?? await browser.contexts()[0].newPage();

for (const url of urls) {
  await page.goto(url, { waitUntil: "networkidle" });
  const title = await page.title();
  console.log(`${url} → ${title}`);
}

// Clean up
await fetch(`https://api.chaser.sh/v1/sessions/${session.id}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.CHASER_KEY}` },
});
```

## Parallel sessions

Run multiple sessions concurrently for throughput:

```typescript
import { chromium } from "patchright";

async function scrapeUrl(url: string) {
  const session = await fetch("https://api.chaser.sh/v1/sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CHASER_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ttl_seconds: 1800 }),
  }).then((r) => r.json());

  const browser = await chromium.connectOverCDP(session.cdp_url);
  const page = browser.contexts()[0].pages()[0] ?? await browser.contexts()[0].newPage();
  await page.goto(url);

  const title = await page.title();

  await fetch(`https://api.chaser.sh/v1/sessions/${session.id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${process.env.CHASER_KEY}` },
  });

  return { url, title };
}

const urls = [/* ... */];
const results = await Promise.all(urls.map(scrapeUrl));
```

The workspace limit is 10 concurrent sessions. Stay under that, or queue creations.

## Extracting data with `evaluate`

```typescript
const data = await page.evaluate(() => {
  const products = document.querySelectorAll(".product-card");
  return Array.from(products).map((el) => ({
    name: el.querySelector(".product-name")?.textContent?.trim(),
    price: el.querySelector(".product-price")?.textContent?.trim(),
    rating: el.querySelector(".product-rating")?.textContent?.trim(),
  }));
});
```

## Setting cookies before navigation

The session arrives with a clean state. If you need pre-set cookies:

```typescript
await context.addCookies([
  { name: "session", value: "abc123", domain: ".example.com", path: "/" },
]);

await page.goto("https://www.example.com/dashboard");
```

## Waiting for elements

```typescript
await page.waitForSelector(".results-list", { timeout: 10000 });
await page.waitForLoadState("networkidle");
```

## Session reuse

A session can handle hundreds of navigations within its TTL. Don't create a new session per page — create one session, navigate many times, delete it when you're done.

## Error handling

```typescript
const session = await fetch(/* ... */).then((r) => r.json());

try {
  const browser = await chromium.connectOverCDP(session.cdp_url);
  const page = browser.contexts()[0].pages()[0] ?? await browser.contexts()[0].newPage();
  await page.goto(url, { timeout: 30000 });
} catch (err) {
  console.error("Session failed:", err);
} finally {
  // Always clean up
  await fetch(`https://api.chaser.sh/v1/sessions/${session.id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${process.env.CHASER_KEY}` },
  });
}
```