Docs
Dashboard
Examples / Browser + shellBeta

Download and add up

The browser downloads a CSV report, Python in the shell adds up its revenue column and saves the total, and the browser types the total into a form. Browser, shell and files share one /workspace.

When to use it: A job moves between the browser and code: download, compute, then type the result back.

UsesPlaywright over CDP, the downloads folder, files.waitFor, exec (Python), files.readText
Needsa plan with shell sessions; no model
Siteyour report page and form; without them a demo report made in the browser and httpbin.org's demo form. The runner uses its stand-in
Outputoutput/report.csv, output/total.txt and output/result.json
LanguagesNode 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.

/** * Download and add up: the browser downloads a CSV report, Python in the shell adds up its revenue column and saves * the total, and the browser types the total into a form. One session: browser, shell and files share /workspace. * *   npx tsx node/index.ts            (BOXLINE_API_KEY; REPORT_PAGE_URL: a page with a CSV link, FORM_URL; a shell plan) * * Without REPORT_PAGE_URL a demo report page is made in the browser. Writes output/report.csv, output/total.txt and * output/result.json (with what the form page said back). */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 reportPage = process.env.REPORT_PAGE_URL;const formUrl = process.env.FORM_URL ?? "https://httpbin.org/forms/post";const bx = new Boxline();const session = await bx.sessions.create({ shell: true, timeout: 600, userMetadata: { example: "download-and-add-up" } });console.log(`Session: ${session.id}`);const browser = await chromium.connectOverCDP(session.connectUrl!);try {  // 1. The browser downloads the report; downloads land in the workspace's downloads/ folder.  const page = browser.contexts()[0]!.pages()[0]!;  if (reportPage) await page.goto(reportPage);  else {    const csv = "month,revenue\nJan,1200\nFeb,1850\nMar,2410\n";    await page.setContent(`<h1>Quarterly report</h1><a download="report.csv" href="data:text/csv,${encodeURIComponent(csv)}">Download report</a>`);  }  await page.getByRole("link", { name: /download/i }).first().click();  const file = await session.files.waitFor("downloads/*.csv", 30_000); // waits until it has finished writing  console.log(`Downloaded ${file.path} (${file.size} bytes)`);  // 2. The shell: Python adds up the revenue column and writes the total.  const py = await session.exec(`mkdir -p output && python3 - "${file.path}" <<'PY'import csv, systotal = sum(float(row["revenue"]) for row in csv.DictReader(open(sys.argv[1])))open("output/total.txt", "w").write(f"{total:g}")print(f"total revenue: {total:g}")PY`);  if (py.exitCode !== 0) throw new Error(`Python failed: ${py.stderr}`);  const total = (await session.files.readText("output/total.txt")).trim(); // the files API reads what the shell wrote  console.log(py.stdout.trim());  // 3. The browser types the total into the form's first text box and submits it.  await page.goto(formUrl);  await page.locator("form input:not([type]), form input[type=text]").first().fill(total);  await Promise.all([page.waitForURL((u) => u.href !== formUrl), page.locator("form button, form [type=submit]").first().click()]);  const pageSays = (await page.locator("body").innerText()).trim().slice(0, 1000);  console.log(`The form page says: ${pageSays.replace(/\s+/g, " ").slice(0, 160)}`);  mkdirSync(out, { recursive: true });  writeFileSync(join(out, "report.csv"), await session.files.read(file.path));  writeFileSync(join(out, "total.txt"), total + "\n");  writeFileSync(join(out, "result.json"), JSON.stringify({ report: file.path, total, form: formUrl, pageSays }, null, 2));} finally {  await session.stop();  await browser.close().catch(() => undefined);}

Inputs

Environment variables the example reads:

  • REPORT_PAGE_URL: a page with a link to a CSV that has a revenue column.
  • FORM_URL: the form (its first text box gets the total) (default https://httpbin.org/forms/post).

Run it

Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/download-and-add-up. 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

Then 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.

The total matches the CSV, and the form page echoed it (on the stand-in: the form received 6200).

Source

github.com/Boxline-dev/examples/download-and-add-up 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.