Automation examples

Each example starts a profile through the local API, connects to its DevTools endpoint, opens a page and stops the profile. Set BLEND_API and BLEND_TOKEN from Settings → API. Only automate sites you own or are allowed to test.

Node.js + Playwright

examples/node-playwright.mjs

/* global fetch */
// Blend + Playwright (Chromium over CDP). Usage: BLEND_TOKEN=... node node-playwright.mjs
import { chromium } from 'playwright-core';

const API = process.env.BLEND_API ?? 'http://127.0.0.1:3001/api/v1';
const TOKEN = process.env.BLEND_TOKEN;
const TARGET = process.env.TARGET_URL ?? 'https://example.com';
if (!TOKEN) throw new Error('Set BLEND_TOKEN (Settings → API in the Blend app, or `pnpm blend token`)');

async function api(method, path, body) {
  const r = await fetch(API + path, { method, headers: { authorization: `Bearer ${TOKEN}`, 'content-type': 'application/json' }, body: body && JSON.stringify(body) });
  const j = await r.json();
  if (!r.ok) throw new Error(`${method} ${path}: ${j.error}`);
  return j;
}

const profile = await api('POST', '/profiles', { name: `example-playwright-${Date.now()}` });
try {
  await api('POST', `/profiles/${profile.id}/start`, { headless: process.env.HEADLESS === '1' });
  const { browserURL } = await api('GET', `/profiles/${profile.id}/debug`);
  const browser = await chromium.connectOverCDP(browserURL);
  const context = browser.contexts()[0]; // the profile's own context (cookies, storage, fingerprint)
  const page = await context.newPage();
  await page.goto(TARGET);
  console.log(JSON.stringify({ framework: 'playwright', title: await page.title(), heading: await page.locator('h1').first().innerText() }));
  await page.close();
  await browser.close(); // closes the CDP connection only; the profile keeps running until /stop
} finally {
  await api('POST', `/profiles/${profile.id}/stop`).catch(() => {});
  await api('DELETE', `/profiles/${profile.id}`).catch(() => {});
}

Node.js + Puppeteer

examples/node-puppeteer.mjs

/* global fetch, navigator */
// Blend + Puppeteer: start a profile and drive it. Usage: BLEND_TOKEN=... node node-puppeteer.mjs
import puppeteer from 'puppeteer-core';

const API = process.env.BLEND_API ?? 'http://127.0.0.1:3001/api/v1';
const TOKEN = process.env.BLEND_TOKEN;
const TARGET = process.env.TARGET_URL ?? 'https://example.com';
if (!TOKEN) throw new Error('Set BLEND_TOKEN (Settings → API in the Blend app, or `pnpm blend token`)');

async function api(method, path, body) {
  const r = await fetch(API + path, { method, headers: { authorization: `Bearer ${TOKEN}`, 'content-type': 'application/json' }, body: body && JSON.stringify(body) });
  const j = await r.json();
  if (!r.ok) throw new Error(`${method} ${path}: ${j.error}`);
  return j;
}

const profile = await api('POST', '/profiles', { name: `example-puppeteer-${Date.now()}` });
try {
  await api('POST', `/profiles/${profile.id}/start`, { headless: process.env.HEADLESS === '1' });
  const { wsEndpoint } = await api('GET', `/profiles/${profile.id}/debug`);
  const browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint, defaultViewport: null });
  const page = await browser.newPage();
  await page.goto(TARGET, { waitUntil: 'domcontentloaded' });
  console.log(JSON.stringify({ framework: 'puppeteer', title: await page.title(), userAgent: await page.evaluate(() => navigator.userAgent) }));
  await browser.disconnect();
} finally {
  await api('POST', `/profiles/${profile.id}/stop`).catch(() => {});
  await api('DELETE', `/profiles/${profile.id}`).catch(() => {});
}

Python + Playwright

examples/python_playwright.py

"""Blend + Playwright for Python. Usage: BLEND_TOKEN=... python python_playwright.py"""
import json
import os
import time

import requests
from playwright.sync_api import sync_playwright

API = os.environ.get("BLEND_API", "http://127.0.0.1:3001/api/v1")
TOKEN = os.environ.get("BLEND_TOKEN") or exit("Set BLEND_TOKEN (Settings -> API in the Blend app)")
TARGET = os.environ.get("TARGET_URL", "https://example.com")


def api(method, path, body=None):
    r = requests.request(method, API + path, json=body, headers={"Authorization": f"Bearer {TOKEN}"}, timeout=120)
    data = r.json()
    if not r.ok:
        raise RuntimeError(f"{method} {path}: {data.get('error')}")
    return data


profile = api("POST", "/profiles", {"name": f"example-py-playwright-{int(time.time())}"})
try:
    api("POST", f"/profiles/{profile['id']}/start", {"headless": os.environ.get("HEADLESS") == "1"})
    debug = api("GET", f"/profiles/{profile['id']}/debug")
    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp(debug["browserURL"])
        page = browser.contexts[0].new_page()
        page.goto(TARGET)
        print(json.dumps({"framework": "python-playwright", "title": page.title()}))
        page.close()
        browser.close()
finally:
    for method, path in (("POST", "/stop"), ("DELETE", "")):
        try:
            api(method, f"/profiles/{profile['id']}{path}")
        except Exception:
            pass

Python + Selenium

examples/python_selenium.py

"""Blend + Selenium (attach to a running profile). Usage: BLEND_TOKEN=... python python_selenium.py"""
import json
import os
import time

import requests
from selenium import webdriver

API = os.environ.get("BLEND_API", "http://127.0.0.1:3001/api/v1")
TOKEN = os.environ.get("BLEND_TOKEN") or exit("Set BLEND_TOKEN (Settings -> API in the Blend app)")
TARGET = os.environ.get("TARGET_URL", "https://example.com")


def api(method, path, body=None):
    r = requests.request(method, API + path, json=body, headers={"Authorization": f"Bearer {TOKEN}"}, timeout=120)
    data = r.json()
    if not r.ok:
        raise RuntimeError(f"{method} {path}: {data.get('error')}")
    return data


profile = api("POST", "/profiles", {"name": f"example-selenium-{int(time.time())}"})
try:
    api("POST", f"/profiles/{profile['id']}/start", {"headless": os.environ.get("HEADLESS") == "1"})
    debug = api("GET", f"/profiles/{profile['id']}/debug")
    version = requests.get(debug["browserURL"] + "/json/version", timeout=10).json()["Browser"].split("/")[1]
    options = webdriver.ChromeOptions()
    options.debugger_address = debug["debuggerAddress"]  # attach; do not launch a new browser
    options.browser_version = version  # Selenium Manager fetches the matching ChromeDriver
    if os.environ.get("CHROMEDRIVER"):
        service = webdriver.ChromeService(executable_path=os.environ["CHROMEDRIVER"])
    else:
        service = webdriver.ChromeService()
    driver = webdriver.Chrome(options=options, service=service)
    driver.get(TARGET)
    print(json.dumps({"framework": "selenium", "title": driver.title}))
    driver.quit()  # with debuggerAddress this ends the WebDriver session; Blend stops the browser below
finally:
    for method, path in (("POST", "/stop"), ("DELETE", "")):
        try:
            api(method, f"/profiles/{profile['id']}{path}")
        except Exception:
            pass