écrits/tutorial/2026/05
Tutorial19 mai 2026·30 min

Créer des agents navigateur IA avec Stagehand par Browserbase en 2026

Apprenez à créer des agents navigateur IA prêts pour la production avec Stagehand et Browserbase. Ce tutoriel complet couvre l'automatisation web en langage naturel, l'extraction de données structurées, l'observabilité et le déploiement d'agents dans le cloud en TypeScript.

Le navigateur est la nouvelle API. Stagehand transforme Playwright en un framework natif IA : au lieu de sélecteurs CSS fragiles, vous écrivez des instructions en langage naturel comme act("clique sur le bouton de connexion") et extract("le prix sous forme de nombre"). Dans ce tutoriel, nous allons créer un agent de production qui scrape, remplit des formulaires et s'exécute de manière fiable dans le cloud sur Browserbase.

Ce que vous allez construire

Un agent TypeScript qui :

  1. Lance un véritable navigateur cloud via Browserbase.
  2. Navigue jusqu'à une page produit et extrait des données structurées avec Zod.
  3. Exécute une tâche multi-étapes (recherche, filtrage, pagination) à l'aide d'actions en langage naturel.
  4. Observe les actions possibles avant de décider quoi faire.
  5. Journalise chaque étape avec la rediffusion complète de la session pour le débogage.

À la fin, vous saurez quand utiliser act, extract, observe ou Playwright brut, et comment garder un agent fiable sur des milliers d'exécutions.


Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ et pnpm ou npm
  • Un compte Browserbase (l'offre gratuite suffit) avec clé API et identifiant de projet
  • Une clé API OpenAI ou Anthropic (Stagehand supporte les deux)
  • Une familiarité de base avec TypeScript et async/await
  • Un éditeur de code (VS Code recommandé)

Inutile de connaître Playwright en profondeur. Stagehand l'enveloppe, et les primitives IA prennent en charge l'essentiel du travail.


Pourquoi Stagehand plutôt que Playwright nu ?

Playwright fonctionne très bien jusqu'à ce que le DOM bouge. Une classe se renomme, un test A/B réorganise les boutons, et votre script casse à 3 h du matin.

Stagehand remplace les parties fragiles par trois primitives IA :

PrimitiveRôleQuand l'utiliser
act(instruction)Exécute une action décrite en langage naturelClics, saisie, navigation UI
extract(instruction, schema)Récupère des données structurées, validées par ZodPrix, listes, tableaux
observe(instruction)Renvoie les actions candidates sans les exécuterPlanification, exécutions à blanc, boucles d'agent

Pour tout le reste, vous pouvez toujours utiliser Playwright brut avec page.goto, page.waitForSelector, etc. L'approche hybride garde les parties déterministes économiques et les parties floues robustes.


Étape 1 : Mise en place du projet

Créez un nouveau projet et installez Stagehand avec Zod pour la validation de schéma.

mkdir stagehand-agent && cd stagehand-agent
pnpm init
pnpm add @browserbasehq/stagehand zod
pnpm add -D typescript tsx @types/node
npx tsc --init

Ouvrez tsconfig.json et assurez-vous que ces options sont définies pour que top-level await fonctionne :

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true
  }
}

Ajoutez "type": "module" à votre package.json afin que Node traite les fichiers en ESM.


Étape 2 : Configurer Browserbase et le LLM

Créez un fichier .env. Ne le commitez jamais.

BROWSERBASE_API_KEY=bb_xxxxxxxxxxxxxxxxxxxxxxx
BROWSERBASE_PROJECT_ID=prj_xxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

Puis créez src/client.ts pour centraliser l'instance Stagehand :

import { Stagehand } from "@browserbasehq/stagehand";
 
export function createStagehand() {
  return new Stagehand({
    env: "BROWSERBASE",
    apiKey: process.env.BROWSERBASE_API_KEY,
    projectId: process.env.BROWSERBASE_PROJECT_ID,
    modelName: "gpt-4o-mini",
    modelClientOptions: {
      apiKey: process.env.OPENAI_API_KEY,
    },
    verbose: 1,
  });
}

Quelques notes sur la configuration :

  • env: "BROWSERBASE" s'exécute dans le cloud. Passez à "LOCAL" pour utiliser Chromium localement pendant le développement.
  • modelName accepte tout modèle supporté par votre fournisseur. Pour la plupart des flux, gpt-4o-mini représente le bon compromis coût/précision. Utilisez gpt-4o ou claude-sonnet-4 lorsque la qualité d'extraction est critique.
  • verbose: 1 affiche les étapes de raisonnement. Passez-le à 2 pendant le débogage.

Étape 3 : Votre première action en langage naturel

Commençons par le hello-world canonique : chercher "Stagehand Browserbase" sur Google et lire le premier résultat.

Créez src/01-act.ts :

import "dotenv/config";
import { createStagehand } from "./client.js";
 
async function main() {
  const stagehand = createStagehand();
  await stagehand.init();
 
  const page = stagehand.page;
 
  await page.goto("https://www.google.com");
  await page.act("accept cookies if a banner is showing");
  await page.act("type 'Stagehand Browserbase' into the search box and press Enter");
  await page.act("click the first organic result");
 
  console.log("Final URL:", page.url());
 
  await stagehand.close();
}
 
main();

Lancez-le :

npx tsx src/01-act.ts

Deux choses vont se produire. D'abord, une session Browserbase s'ouvre (vous pouvez la regarder en direct depuis le tableau de bord). Ensuite, Stagehand transforme chaque instruction anglaise en un petit plan Playwright et l'exécute.

Remarquez que nous n'avons écrit aucun sélecteur.


Étape 4 : Extraction structurée avec Zod

L'extraction est l'endroit où Stagehand brille pour le scraping. Définissez ce que vous voulez avec Zod et l'agent gère le reste.

Créez src/02-extract.ts :

import "dotenv/config";
import { z } from "zod";
import { createStagehand } from "./client.js";
 
const ProductSchema = z.object({
  title: z.string(),
  priceUsd: z.number().describe("Price in US dollars, numeric only"),
  rating: z.number().min(0).max(5).optional(),
  inStock: z.boolean(),
  bulletPoints: z.array(z.string()).max(8),
});
 
async function main() {
  const stagehand = createStagehand();
  await stagehand.init();
 
  const page = stagehand.page;
  await page.goto("https://www.example-shop.com/products/widget-pro");
 
  const product = await page.extract({
    instruction: "Extract the product title, price, rating, stock status, and the bullet points under 'About this item'",
    schema: ProductSchema,
  });
 
  console.log(product);
 
  await stagehand.close();
}
 
main();

Les appels .describe() sur chaque champ Zod ne sont pas décoratifs. Le LLM les lit et les utilise comme instructions. Considérez-les comme partie intégrante de votre prompt.

Si l'extraction renvoie n'importe quoi, la cause se résume presque toujours à l'une des trois suivantes :

  • Le schéma est trop lâche. Ajoutez des indices .describe().
  • L'instruction est trop vague. Ancrez-la sur une zone visible de la page.
  • Le modèle est trop petit. Passez de gpt-4o-mini à gpt-4o pour cet appel spécifique.

Étape 5 : Observer avant d'agir

observe retourne les actions que le modèle pense disponibles, sans les exécuter. C'est ainsi que l'on construit des boucles d'agent sans dérapage de coûts ni erreurs destructrices.

Créez src/03-observe.ts :

import "dotenv/config";
import { createStagehand } from "./client.js";
 
async function main() {
  const stagehand = createStagehand();
  await stagehand.init();
  const page = stagehand.page;
 
  await page.goto("https://news.ycombinator.com");
 
  const candidates = await page.observe({
    instruction: "Find all clickable links to story comment threads on this page",
  });
 
  console.log(`Found ${candidates.length} candidate actions`);
  for (const c of candidates.slice(0, 5)) {
    console.log("-", c.description);
  }
 
  await page.act(candidates[0]);
  console.log("Navigated to:", page.url());
 
  await stagehand.close();
}
 
main();

Deux raisons pour lesquelles cela compte en production :

  1. Idempotence. Passez directement le ObserveResult résolu à act et la seconde exécution utilise le locator mis en cache, économisant un appel LLM redondant.
  2. Sécurité. Vous pouvez inspecter l'action proposée, la journaliser, voire exiger une confirmation humaine, avant toute mutation de la page.

Étape 6 : Construire un agent multi-étapes

Combinons maintenant tout cela en un petit agent qui recherche sur un site d'emploi, filtre par télétravail, et extrait les dix premières offres.

Créez src/04-agent.ts :

import "dotenv/config";
import { z } from "zod";
import { createStagehand } from "./client.js";
 
const JobsSchema = z.object({
  jobs: z.array(
    z.object({
      title: z.string(),
      company: z.string(),
      location: z.string(),
      url: z.string().url(),
    })
  ).max(10),
});
 
async function main() {
  const stagehand = createStagehand();
  await stagehand.init();
  const page = stagehand.page;
 
  await page.goto("https://example-jobs.dev");
  await page.act("search for 'typescript' in the main search field and submit");
  await page.act("apply the 'Remote' filter from the location facet");
  await page.act("sort results by most recent");
 
  await page.waitForLoadState("networkidle");
 
  const { jobs } = await page.extract({
    instruction: "Extract the first 10 visible job listings",
    schema: JobsSchema,
  });
 
  console.table(jobs);
 
  await stagehand.close();
}
 
main();

Quelques patterns de production à souligner :

  • Mélangez Playwright brut et appels IA. waitForLoadState("networkidle") est déterministe et gratuit. Utilisez-le.
  • Limitez la taille des tableaux. Le .max(10) sur le schéma empêche le modèle de renvoyer des centaines de lignes et de faire exploser votre budget de contexte.
  • Gardez des instructions atomiques. Un act par action logique. Les instructions chaînées sont plus difficiles à planifier de manière fiable pour le modèle.

Étape 7 : Persistance des sessions et authentification

Les vrais workflows nécessitent une authentification. Browserbase prend en charge les contextes persistants : vous vous connectez une fois et réutilisez la session sur plusieurs exécutions.

import { Stagehand } from "@browserbasehq/stagehand";
 
const stagehand = new Stagehand({
  env: "BROWSERBASE",
  apiKey: process.env.BROWSERBASE_API_KEY,
  projectId: process.env.BROWSERBASE_PROJECT_ID,
  browserbaseSessionCreateParams: {
    projectId: process.env.BROWSERBASE_PROJECT_ID!,
    browserSettings: {
      context: {
        id: "ctx_persistent_login",
        persist: true,
      },
    },
  },
});

À la première exécution, effectuez manuellement le flux de connexion via des appels act. Aux exécutions suivantes, cookies et stockage local sont restaurés automatiquement. Traitez l'identifiant de contexte comme un secret car il accorde un accès repris à tous les sites où vous vous êtes authentifié.


Étape 8 : Observabilité et rediffusion de session

Chaque session Browserbase enregistre la vidéo, les logs réseau et une timeline d'instantanés DOM. Quand quelque chose échoue, l'URL dans le tableau de bord vous indique exactement où l'agent s'est perdu.

Ajoutez un petit helper qui affiche l'URL de rediffusion à chaque exécution :

const session = await stagehand.context.browser?.sessionId;
console.log(`Replay: https://www.browserbase.com/sessions/${session}`);

Pour les agents multi-étapes, journalisez également les sorties de observe et extract dans un fichier. La combinaison du raisonnement LLM et de la rediffusion vidéo réduit le temps de débogage d'heures à quelques minutes.


Étape 9 : Astuces de coût et de fiabilité

Quelques règles apprises à la dure en exploitant des agents à l'échelle :

  • Mettez en cache les résultats d'observe. Réutiliser un locator résolu coûte zéro token LLM.
  • Utilisez un petit modèle pour les actions et un plus gros pour l'extraction. Vous pouvez surcharger modelName par appel.
  • Définissez des timeouts partout. Chaque appel act, extract ou Playwright doit avoir une borne supérieure.
  • Préférez l'extraction aux captures d'écran. Les entrées image sont coûteuses et lentes. L'extraction textuelle avec un bon schéma est plus rapide et plus fiable.
  • Parallélisez via les sessions Browserbase. Elles sont isolées par défaut, vous pouvez donc lancer des dizaines d'agents concurrents sans collisions de sélecteurs.

Tester votre agent

Stagehand se marie bien avec Vitest. La recette que nous utilisons :

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createStagehand } from "../src/client.js";
 
let stagehand: ReturnType<typeof createStagehand>;
 
beforeAll(async () => {
  stagehand = createStagehand();
  await stagehand.init();
});
 
afterAll(async () => {
  await stagehand.close();
});
 
describe("product extraction", () => {
  it("extracts a price as a number", async () => {
    await stagehand.page.goto("https://example-shop.com/products/widget");
    const { priceUsd } = await stagehand.page.extract({
      instruction: "Extract the visible product price in USD",
      schema: z.object({ priceUsd: z.number() }),
    });
    expect(priceUsd).toBeGreaterThan(0);
  });
});

Lancez avec vitest --no-file-parallelism afin que plusieurs tests ne se marchent pas sur la même session Browserbase.


Dépannage

L'agent clique sur le mauvais élément. Resserrez l'instruction. "Clique sur le CTA principal dans la section hero" vaut mieux que "clique sur le bouton".

L'extraction renvoie des champs vides. Vérifiez que les données sont bien dans le DOM, pas chargées paresseusement derrière un intersection observer. Ajoutez await page.waitForLoadState("networkidle") ou faites défiler la section en vue d'abord.

Limites de débit atteintes. Utilisez un modèle plus petit pour act, regroupez les appels extract en une seule instruction avec un schéma plus riche, et mettez en cache les résultats d'observe.

Captchas. Browserbase intègre une résolution automatique. Activez-la via browserSettings.solveCaptchas: true à la création de la session.


Étapes suivantes


Conclusion

Vous disposez maintenant d'un agent TypeScript qui parcourt le web comme une personne, extrait les données comme une API et se débogue comme un test unitaire. Le modèle mental est simple : Playwright déterministe pour les parties que vous contrôlez, act et extract pour celles qui changent.

Le navigateur n'est plus une interface hostile à l'automatisation. Avec Stagehand et Browserbase, c'est juste un outil de plus que votre code peut saisir, avec la résilience d'un LLM et la vitesse d'un Chromium headless.