Screenshots to a PDF report
Screenshot several pages in the session's browser, then combine them into one PDF with Python (Pillow) in the session's shell.
When to use it: You want one PDF of several pages, for a report or a record.
| Uses | the actions API (goto, screenshot), files.write, exec (Python, Pillow), files.read |
|---|---|
| Needs | a plan with shell sessions; no model |
| Site | books.toscrape.com, quotes.toscrape.com and httpbin.org |
| Output | output/shot-N.png and output/report.pdf |
| Languages | Node and Python |
combine.py is the Python script both versions upload and run in the session.
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.
/** * Screenshots to a PDF report: screenshot several pages in the session's browser, then combine them into one PDF with * Python (Pillow) in the session's shell. * * npx tsx node/index.ts (BOXLINE_API_KEY; URLS: comma-separated pages; a plan with shell sessions) * * Writes output/shot-N.png and output/report.pdf. */import { mkdirSync, readFileSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const urls = (process.env.URLS ?? "https://books.toscrape.com/,https://quotes.toscrape.com/,https://httpbin.org/").split(",").map((u) => u.trim()).filter(Boolean);const bx = new Boxline();const session = await bx.sessions.create({ shell: true, timeout: 600, userMetadata: { example: "screenshots-to-pdf" } });console.log(`Session: ${session.id}`);try { mkdirSync(out, { recursive: true }); const shots: string[] = []; const pages: { url: string; title: string; file: string }[] = []; for (const [i, url] of urls.entries()) { // The actions API: open the page and screenshot it (base64 PNG), then keep it in the workspace for the shell. const { title } = await session.goto(url); const shot = await session.screenshot(); const png = Buffer.from(shot.data, "base64"); const file = `output/shot-${i + 1}.png`; await session.files.write(file, png); writeFileSync(join(out, `shot-${i + 1}.png`), png); shots.push(file); pages.push({ url, title, file }); console.log(`Saved ${file}: ${title}`); } await session.files.write("combine.py", readFileSync(new URL("../combine.py", import.meta.url))); const r = await session.exec(`python3 combine.py ${shots.join(" ")}`); if (r.exitCode !== 0) throw new Error(`Pillow failed: ${r.stderr.trim()}`); const pdf = await session.files.read("output/report.pdf"); writeFileSync(join(out, "report.pdf"), pdf); console.log(`output/report.pdf: ${r.stdout.trim()} pages, ${Math.round(pdf.length / 1024)} KB`); writeFileSync(join(out, "result.json"), JSON.stringify({ pages, pdfPages: Number(r.stdout.trim()), pdfBytes: pdf.length }, null, 2));} finally { await session.stop();}"""Screenshots to a PDF report: screenshot several pages in the session's browser, then combine them into one PDFwith Python (Pillow) in the session's shell. python python/main.py (BOXLINE_API_KEY; URLS: comma-separated pages; a plan with shell sessions)Writes output/shot-N.png and output/report.pdf."""import jsonimport osfrom pathlib import Pathfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))urls = [u.strip() for u in os.environ.get("URLS", "https://books.toscrape.com/,https://quotes.toscrape.com/,https://httpbin.org/").split(",") if u.strip()]combine = (Path(__file__).parent.parent / "combine.py").read_bytes()bx = Boxline()with bx.sessions.create(shell=True, timeout=600, user_metadata={"example": "screenshots-to-pdf"}) as session: print(f"Session: {session.id}", flush=True) out.mkdir(parents=True, exist_ok=True) shots, pages = [], [] for i, url in enumerate(urls, start=1): # The actions API: open the page and screenshot it (PNG bytes), then keep it in the workspace for the shell. title = session.goto(url)["title"] png = session.screenshot() file = f"output/shot-{i}.png" session.files.write(file, png) (out / f"shot-{i}.png").write_bytes(png) shots.append(file) pages.append({"url": url, "title": title, "file": file}) print(f"Saved {file}: {title}") session.files.write("combine.py", combine) r = session.exec(f"python3 combine.py {' '.join(shots)}") if r["exitCode"] != 0: raise SystemExit(f"Pillow failed: {r['stderr'].strip()}") pdf = session.files.read("output/report.pdf") (out / "report.pdf").write_bytes(pdf) print(f"output/report.pdf: {r['stdout'].strip()} pages, {len(pdf) // 1024} KB") (out / "result.json").write_text(json.dumps({"pages": pages, "pdfPages": int(r["stdout"].strip()), "pdfBytes": len(pdf)}, indent=2, ensure_ascii=False))"""Runs in the session's shell: combines the screenshots given as arguments into output/report.pdf, one page each,with Pillow (installed in the shell). Prints the number of pages."""import sysfrom PIL import Imagepages = [Image.open(path).convert("RGB") for path in sys.argv[1:]]pages[0].save("output/report.pdf", save_all=True, append_images=pages[1:], resolution=96)print(len(pages))Inputs
Environment variables the example reads:
URLS: the pages, comma-separated (defaulthttps://books.toscrape.com/,https://quotes.toscrape.com/,https://httpbin.org/).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/screenshots-to-pdf. 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 tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython 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.
Report.pdf has one page per screenshot, and every screenshot is a PNG of a loaded page.
Source
github.com/Boxline-dev/examples/screenshots-to-pdf 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.