Aller au contenu

Frameworks et Playground

Remarques pour connecter certains frameworks à un navigateur hébergé, et le Playground du tableau de bord pour essayer un script à côté de la vue en direct.

Frameworks

Tout ce qui s’attache à Chrome via CDP fonctionne avec le connectUrl. Comme celui-ci est à usage unique, un framework qui se reconnecte de lui-même a besoin d’une nouvelle session pour chaque connexion. Le navigateur démarre au moment où vous vous connectez, généralement en quelques secondes (voir la remarque sur le délai de connexion plus haut) ; il n’y a aucun statut à interroger au préalable.

javascript
// Patchright (optional; a Playwright fork): npm i patchright
import { chromium } from "patchright";
const browser = await chromium.connectOverCDP(connectUrl, { timeout: 120_000 });
const page = browser.contexts()[0].pages()[0];
python
# Browser Use
from browser_use import Agent, Browser
agent = Agent(task="Find the cheapest flight to Lisbon next Friday", llm=llm, browser=Browser(cdp_url=connect_url))
await agent.run()

# Crawl4AI
from crawl4ai import AsyncWebCrawler, BrowserConfig
config = BrowserConfig(browser_mode="custom", cdp_url=connect_url, use_managed_browser=True)
async with AsyncWebCrawler(config=config) as crawler:
    result = await crawler.arun("https://example.com")
javascript
// Stagehand v4
import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
const stagehand = await Stagehand.create({ browser: await localBrowser.connect({ cdpUrl: connectUrl }) });

setContent et exposeFunction

Le moteur n’envoie jamais les événements de console au client, car c’est justement leur transmission que mesurent les pages qui cherchent à repérer un client d’automatisation. Deux appels du driver en dépendent :

  • Dans Playwright, page.setContent() attend un événement de console qui n’arrive jamais, et finit donc en timeout (dans Puppeteer, il fonctionne). Ouvrez plutôt le HTML sous forme d’URL data:, ou écrivez-le vous-même dans la page, comme ci-dessous.
  • Dans Playwright comme dans Puppeteer, exposeFunction() et exposeBinding() fonctionnent dans la page ouverte au moment de l’appel, et disparaissent dès que cette page navigue. Appelez-les après la dernière navigation, ou faites plutôt remonter les résultats avec evaluate.
javascript
// Instead of await page.setContent(html):

// Short HTML: a data: URL (the page gets no origin of its own, so no cookies or storage)
await page.goto("data:text/html," + encodeURIComponent(html));

// Any HTML: write it into a page you have navigated to; it keeps that page's origin
await page.evaluate((html) => {
  document.open();
  document.write(html);
  document.close();
}, html);
python
# Instead of page.set_content(html):
from urllib.parse import quote

page.goto("data:text/html," + quote(html))
# or
page.evaluate("html => { document.open(); document.write(html); document.close(); }", html)

Un helper pour démarrer

Création avec les nouvelles tentatives décrites plus haut, connexion et arrêt systématique, dans une seule fonction :

typescript
// clearcote-hosted.ts
import { chromium, type Browser } from "playwright"; // or "patchright"

const API = "https://www.clearcotelabs.com/api/v1/browsers";
const AUTH = { authorization: "Bearer " + process.env.CLEARCOTE_API_KEY };
const RETRY_CODES = new Set(["NO_CAPACITY", "NO_WORKER"]);

export async function createSession(options: Record<string, unknown> = {}, attempts = 5) {
  for (let i = 0; ; i++) {
    const res = await fetch(API, {
      method: "POST",
      headers: { ...AUTH, "content-type": "application/json" },
      body: JSON.stringify(options),
    });
    const body = await res.json().catch(() => ({}));
    if (res.ok) return body as { id: string; connectUrl: string; worker: string };
    const retry = RETRY_CODES.has(body.code) || (res.status === 429 && !body.code) || [500, 502, 504].includes(res.status);
    if (!retry || i + 1 >= attempts) throw new Error([res.status, body.code, body.error].filter(Boolean).join(" "));
    await new Promise((r) => setTimeout(r, Math.min(15_000, 1000 * 2 ** i) * (0.5 + Math.random())));
  }
}

export async function withBrowser<T>(options: Record<string, unknown>, work: (browser: Browser) => Promise<T>) {
  const session = await createSession(options);
  try {
    const browser = await chromium.connectOverCDP(session.connectUrl, { timeout: 120_000 });
    try {
      return await work(browser);
    } finally {
      await browser.close().catch(() => {});
    }
  } finally {
    // Ends the session if closing the browser did not (a no-op otherwise).
    await fetch(API + "/" + session.id, { method: "DELETE", headers: AUTH }).catch(() => {});
  }
}

// await withBrowser({ profile: { name: "shop", persist: true }, country: "de" }, async (browser) => {
//   const page = browser.contexts()[0].pages()[0];
//   await page.goto("https://example.com");
// });

Uploader des fichiers

Une page qui demande un fichier — un <input type="file">, un bouton « parcourir », une zone de glisser-déposer — en accepte un de la même façon dans un navigateur hébergé que dans un navigateur local. Le fichier est lu sur votre machine et ses octets transitent par la connexion : le navigateur ne voit donc jamais de chemin sur votre disque, et vous pouvez uploader un fichier qui n’existe que là où s’exécute votre script. La limite est de 50 MB par fichier.

Playwright est le plus simple. setInputFiles place un fichier directement dans l’input, sans aucun clic, et fonctionne même quand l’input est caché — beaucoup de sites masquent le véritable input derrière un bouton stylé, visez donc l’input lui-même plutôt que de cliquer.

javascript
// Node.js + Playwright
await page.setInputFiles("input[type=file]", "C:/Users/me/invoice.pdf");

// or build the file in your script, with nothing on disk:
await page.setInputFiles("input[type=file]", {
  name: "invoice.pdf",
  mimeType: "application/pdf",
  buffer: Buffer.from("%PDF-1.4 …"),
});
python
# Python + Playwright
page.set_input_files("input[type=file]", "/home/me/invoice.pdf")

Quand un bouton ouvre la boîte de dialogue de fichiers du système et qu’il n’y a aucun input à viser, interceptez plutôt le sélecteur de fichiers :

javascript
const [chooser] = await Promise.all([
  page.waitForEvent("filechooser"),
  page.click("text=Browse"),           // the page's own button
]);
await chooser.setFiles("C:/Users/me/invoice.pdf");

Puppeteer. elementHandle.uploadFile() et fileChooser.accept() ne sont pas disponibles sur les navigateurs hébergés : ils indiquent un chemin que le serveur doit lire, qui pourrait pointer n’importe où sur le worker, si bien que la passerelle les refuse. Construisez le fichier dans la page à partir des octets que vous envoyez, et transmettez-le à l’input :

javascript
// Puppeteer: construct the file in the page
const b64 = fs.readFileSync("invoice.pdf").toString("base64");
await page.evaluate(({ b64, name, type }) => {
  const data = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
  const dt = new DataTransfer();
  dt.items.add(new File([data], name, { type }));
  const input = document.querySelector("input[type=file]");
  input.files = dt.files;
  input.dispatchEvent(new Event("change", { bubbles: true }));
}, { b64, name: "invoice.pdf", type: "application/pdf" });

Le même DataTransfer pilote une zone de glisser-déposer : envoyez-y dragenter, dragover et drop en leur faisant porter cet objet. Un vrai glissement CDP avec un chemin local (Input.dispatchDragEvent avec des fichiers) est refusé pour la même raison que uploadFile().

Le Playground

Le Playground du tableau de bord exécute un script sur l’un de ces navigateurs, avec la vue en direct, la console et les captures d’écran à côté, et le panneau « Use in your code » situé en dessous affiche les mêmes options de session que le code ci-dessus. Son objet page est un petit helper qui passe directement par CDP, pas par Playwright : un script du Playground est donc une ébauche à transposer, pas un fichier à coller tel quel :

HelperEffet
page.goto(url, { timeout? })Navigue et attend l’événement load.
page.click(sel) · page.type(sel, text) · page.press(key)Vraies saisies souris et clavier, après avoir fait défiler l’élément jusqu’à ce qu’il soit visible.
page.evaluate(fn, ...args)Exécute une fonction dans la page et en renvoie le résultat JSON.
page.waitForSelector(sel, { timeout? }) · page.waitForNavigation()Attend un élément, ou le prochain chargement de page.
page.scroll(px) · page.screenshot({ fullPage? })Défilement à la molette ; un JPEG qui arrive dans l’onglet « Screenshots ».
page.title() · page.url() · page.content()Le titre, l’adresse et le HTML du document.
log(...values) · sleep(ms)Affiche dans la console (les objets sont mis en forme) ; met en pause.
cdp(method, params)Une commande CDP brute envoyée au navigateur (Target.*, Browser.*, Storage.*).
page.cdp(method, params)Une commande CDP brute envoyée à la page (Page.*, Runtime.*, DOM.*, Network.*).

Les erreurs indiquent la ligne du script dont elles proviennent. « Share » copie un lien qui transporte le script et ses options de session dans l’URL : rien n’est donc stocké de notre côté, et quiconque l’ouvre l’exécute sur son propre solde.