Sessions and Playwright
Start a session, connect Playwright to its browser over CDP, read a page, and stop the session. The same page is then read through the actions API, which drives the browser without Playwright.
When to use it: Your first session: you know Playwright, or want to see the actions API next to it.
| Uses | sessions, Playwright over CDP (connectUrl), the actions API (content) |
|---|---|
| Needs | any plan; no shell, no model |
| Site | books.toscrape.com, a demo shop made for scraping practice |
| Output | output/result.json: the page title and the first 5 books with their prices |
| Languages | Node and Python |
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * Sessions and Playwright: start a session, connect Playwright to its browser, read a page, stop the session. * * npx tsx node/index.ts (BOXLINE_API_KEY in the environment) * * Writes output/result.json: the page title and the first 5 books with their prices. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";import { chromium } from "playwright-core";const out = process.env.OUTPUT_DIR ?? "output";const bx = new Boxline(); // reads BOXLINE_API_KEY, and BOXLINE_API_URL for another APIconst session = await bx.sessions.create({ timeout: 300, userMetadata: { example: "sessions-and-playwright" } });console.log(`Session: ${session.id}`);const browser = await chromium.connectOverCDP(session.connectUrl!); // a signed URL: treat it like a passwordtry { const page = browser.contexts()[0]!.pages()[0]!; await page.goto("https://books.toscrape.com/"); const title = await page.title(); const books = await page.$$eval("article.product_pod", (items) => items.slice(0, 5).map((item) => ({ title: item.querySelector("h3 a")?.getAttribute("title") ?? "", price: item.querySelector(".price_color")?.textContent?.trim() ?? "", })), ); console.table(books); // The actions API drives the same browser without Playwright: one HTTP call, here for the page as text. const { title: actionsTitle, content } = await session.content("text"); console.log(`The actions API sees "${actionsTitle}" (${content.length} characters of text)`); mkdirSync(out, { recursive: true }); writeFileSync(join(out, "result.json"), JSON.stringify({ sessionId: session.id, title, books, actionsTitle }, null, 2));} finally { await session.stop(); // saves the session and ends its billing await browser.close().catch(() => undefined);}"""Sessions and Playwright: start a session, connect Playwright to its browser, read a page, stop the session. python python/main.py (BOXLINE_API_KEY in the environment)Writes output/result.json: the page title and the first 5 books with their prices."""import jsonimport osfrom pathlib import Pathfrom playwright.sync_api import sync_playwrightfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))bx = Boxline() # reads BOXLINE_API_KEY, and BOXLINE_API_URL for another API# Leaving the `with` block stops the session (saving it, and ending its billing).with bx.sessions.create(timeout=300, user_metadata={"example": "sessions-and-playwright"}) as session: print(f"Session: {session.id}", flush=True) with sync_playwright() as p: browser = p.chromium.connect_over_cdp(session.connect_url) # a signed URL: treat it like a password page = browser.contexts[0].pages[0] page.goto("https://books.toscrape.com/") title = page.title() books = page.eval_on_selector_all( "article.product_pod", """items => items.slice(0, 5).map(item => ({ title: item.querySelector("h3 a").getAttribute("title"), price: item.querySelector(".price_color").textContent.trim(), }))""", ) for book in books: print(f"{book['price']:>8} {book['title']}") # The actions API drives the same browser without Playwright: one HTTP call, here for the page as text. page_text = session.content("text") print(f'The actions API sees "{page_text["title"]}" ({len(page_text["content"])} characters of text)') out.mkdir(parents=True, exist_ok=True) result = {"sessionId": session.id, "title": title, "books": books, "actionsTitle": page_text["title"]} (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/sessions-and-playwright. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk playwright-core tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdk playwrightpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
There are 5 books, the first is "A Light in the Attic" at £51.77, every book has a £ price, and Playwright and the actions API saw the same page.
Source
github.com/Boxline-dev/examples/sessions-and-playwright on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.