ego (lite) n’est qu’un navigateur ; ego est votre agent personnel sur tous vos appareils.
Rejoindre la liste d'attente
PlaywrightAuthentificationstorageStateAutomatisation du navigateurSessions connectées

Authentification Playwright : connectez-vous une fois, réutilisez la session et réauthentifiez-vous à son expiration

16 sept. 202614 lecture min.
Personnages pixel art de Playwright et ego (lite) à côté d'un écran de connexion d'ordinateur portable, avec Agent is in control visible

Quand un test ou un scraper Playwright a besoin d'authentification, il n'y a généralement aucune raison de tout recommencer à chaque exécution. Une approche courante consiste à se connecter une fois, enregistrer les cookies et l'état associé du navigateur avec storageState, puis charger cet état dans les exécutions suivantes pour réutiliser la même session. Mais un fichier storageState enregistré ne signifie pas que la connexion tiendra indéfiniment. La session peut expirer, être révoquée par le serveur, ou mal se charger dès le départ.

Si vous automatisez votre propre compte et que vous êtes déjà connecté dans le navigateur que vous utilisez tous les jours, il existe une autre option : réutiliser directement cette session de navigateur existante. ego (lite) permet aux agents IA de travailler dans un navigateur qui a déjà l'état de connexion dont ils ont besoin, tout en isolant chaque tâche dans son propre Space. Si la tâche atteint un code de vérification, une connexion QR ou une autre étape qui vous exige, vous pouvez prendre le contrôle dans le navigateur visible plutôt que d'essayer d'automatiser le MFA.

Quelle que soit l'approche, l'objectif reste le même : s'assurer que votre automatisation dispose réellement d'une session authentifiée valide. Une exécution qui retombe soudain sur la page de connexion, une API qui renvoie 401, ou un élément post-connexion qui n'apparaît jamais peuvent sembler trois problèmes distincts : navigation, permissions, ou un problème de sélecteur, alors qu'ils peuvent tous pointer vers la même session cassée. Dans les sections ci-dessous, nous utilisons un montage local reproductible pour montrer comment enregistrer, réutiliser, vérifier et rafraîchir l'état d'authentification dans Playwright.

À quoi ressemble une connexion Playwright cassée

Dans l'exécution du fixture, une session révoquée a produit les trois symptômes à la fois. Après la suppression de la session côté serveur, un contexte réutilisant l'état enregistré a été redirigé vers la page de connexion avec un paramètre next pointant vers le tableau de bord, une requête de sondage vers le point d'accès JSON authentifié a renvoyé 401, et l'attente de la table du tableau de bord a expiré après les 3 secondes configurées.

Sur le login public the-internet, la meme classe d echec est le rebond. Ouvrir /secure sans session vivante arrive sur /login avec le flash rouge You must login to view the secure area. C est le controle d URL de l encadre ci-dessous : le navigateur est sur le formulaire de connexion, pas sur la page attendue par le locator. Cette capture est ce rebond. Elle ne prouve pas que Logout a supprime un fichier storageState. Logout sur the-internet termine la session de la fenetre courante ; un fichier deja ecrit sur disque peut encore rouvrir /secure tant que le cookie n est pas mort.

the-internet.herokuapp.com/login apres une requete vers la zone securisee, avec un flash rouge You must login to view the secure area
Ouvrir la page protegee sans session vivante a renvoye vers /login. Le flash rouge est le signe. C est une session absente ou morte, pas un locator casse, et pas Logout qui supprime un fichier storageState enregistre.

Le troisième symptôme est le plus coûteux. Un locator qui expire sur un élément post-connexion ne dit pas que le locator est faux ; il dit que la page sous vos yeux n'est pas celle que vous supposiez. Dans l'exécution ci-dessus, la table n'a jamais été rendue parce que le navigateur était resté sur le formulaire de connexion. Traiter cela comme un problème de sélecteur mène à rapiécer une requête qui n'a jamais été cassée.

Quatre causes profondes derrière les échecs d'authentification

Les échecs d'authentification se regroupent en quatre causes, chacune avec une correction différente. Classer avant de rapiécer évite que la correction ne soit qu'un wait de plus.

1. L'état n'a jamais été chargé

Un contexte créé sans storageState démarre déconnecté, peu importe ce qui s'est passé dans une exécution précédente. Le même résultat vient d'enregistrer le fichier au mauvais moment, d'utiliser un chemin relatif qui se résout ailleurs, ou d'exécuter l'étape de setup dans un projet dont l'état n'est pas transmis au projet dépendant. Le signe : un contexte tout neuf se comporte exactement comme celui qui échoue : les deux sont déconnectés.

2. Le cookie est présent mais mort

Un cookie de session peut exister, porter le bon domaine et les bons flags, et être tout de même rejeté par le serveur. Les cookies de session ont leur propre durée de vie, et un serveur peut en révoquer un à tout moment, y compris lors d'un redéploiement, d'un changement de mot de passe ou d'un idle timeout. Le champ expires du cookie est un indice, pas une garantie : la session côté serveur peut mourir plus tôt. C'est le cas que le fixture reproduit avec un appel revoke explicite.

3. Isolation entre contextes

Les cookies et localStorage appartiennent à un contexte de navigateur, pas au navigateur. Enregistrer l'état d'un contexte et s'attendre à ce qu'un contexte configuré autrement le partage échoue en silence. Il en va de même pour les workers parallèles : chaque worker reçoit son propre contexte, donc chaque worker a besoin de son propre fichier de stockage ou de sa propre étape de connexion. Cette isolation est une fonctionnalité. C'est aussi la raison pour laquelle une session qui fonctionne à un endroit semble disparaître à un autre.

4. Chaînes de redirection SSO

Avec l'authentification unique, l'application que vous visez est rarement l'origine qui détient la session. La connexion redirige vers un fournisseur d'identité sur un autre domaine, le fournisseur pose ses propres cookies, et le contrôle revient à l'application avec un code ou un jeton. Enregistrer l'état avant la fin de cette chaîne capture une partie de la session et laisse tomber les cookies de l'IdP, et l'exécution suivante reproduit la redirection. Le schéma fiable consiste à attendre l'URL finale de l'application et un marqueur authentifié avant d'enregistrer, pour que le fichier d'état porte chaque origine impliquée dans la chaîne.

Se connecter une fois et enregistrer l'état correctement

Le flux « se connecter une fois » est court : naviguer, authentifier, confirmer l'état authentifié, puis écrire storageState dans un fichier. L'étape de confirmation est ce qui sépare un fichier d'état qui fonctionne d'un fichier qui, parfois, ne fonctionne pas.

import { chromium } from "playwright";

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

await page.goto("https://app.example.com/login");
await page.fill("input[name=email]", process.env.APP_EMAIL);
await page.fill("input[name=password]", process.env.APP_PASSWORD);
await page.click("button[type=submit]");

// Wait for the authenticated destination, not just for the click to land.
await page.waitForURL("**/dashboard");

// Verify before saving: an authenticated element plus a 200 from the API.
await page.waitForSelector("#revenue-table");
const probe = await context.request.get("https://app.example.com/api/summary");
if (probe.status() !== 200) throw new Error("login did not take");

await context.storageState({ path: "playwright/.auth/user.json" });
await browser.close();

Dans l'exécution du fixture, cette étape a pris 119 millisecondes entre l'ouverture de la page de connexion et l'écriture du fichier d'état. Le fichier contenait un cookie, l'identifiant de session émis par le serveur, et le sondage a renvoyé 200. Ces chiffres appartiennent au fixture, mais c'est la forme du contrôle qui se transfère : vérifier l'URL de destination, vérifier un élément authentifié visible, vérifier un point d'accès authentifié, et seulement ensuite persister.

L execution headed ci-dessous a utilise the-internet.herokuapp.com, pas le tableau de bord du fixture. Les 119 millisecondes restent avec le fixture. La capture montre la meme forme de sauvegarde sur un login public : /secure apres authentification, Logout visible, et Claude qui verifie /tmp/pw-auth.json.

Claude Code a cote d une fenetre Chromium headed sur the-internet.herokuapp.com/secure apres connexion, Logout visible pendant que Claude verifie /tmp/pw-auth.json
Chromium headed est reste sur /secure apres la connexion. Claude verifiait /tmp/pw-auth.json. Ce fichier est ce qu un contexte ulterieur reutilise.

Pour les suites de tests, la même idée s'exprime par un projet de setup qui produit le fichier et des projets dépendants qui le consomment, le schéma que documente le guide officiel d'authentification. Pour les scripts et les scrapers, la séquence ci-dessus est tout le flux. Dans les deux cas, le fichier est un secret d'authentification : il contient des cookies de session vivants, donc traitez-le avec le même soin qu'un mot de passe et tenez-le hors du contrôle de version.

Réutiliser l'état enregistré dans les tests et les scrapers

La réutilisation est un changement d'une ligne à la création du contexte. Dans un script, passez le fichier au contexte. Dans une suite Playwright, définissez-le par projet ou par fichier de test pour que chaque worker parte du même état connecté.

// Script: create the context from the saved state.
const context = await browser.newContext({
  storageState: "playwright/.auth/user.json",
});

// Test: use the state for this file (or configure it per project).
test.use({ storageState: "playwright/.auth/user.json" });

Dans l'exécution du fixture, un nouveau contexte créé à partir du fichier enregistré a atteint le tableau de bord protégé en 6 millisecondes sans étape de connexion, et le sondage authentifié a renvoyé 200 avec la charge utile attendue. Le chiffre intéressant n'est pas les 6 millisecondes ; c'est que rien n'a changé dans le flux, sauf la provenance de l'état du contexte.

La capture de reutilisation est le meme site public. Un nouveau contexte headed a charge /tmp/pw-auth.json, a ignore le formulaire de connexion et a ouvert /secure avec Logout deja sur la page. Les 6 millisecondes restent le temps du fixture.

Claude Code créant un nouveau contexte Playwright à partir de /tmp/pw-auth.json sans remplir le formulaire de connexion, à côté de the-internet.herokuapp.com/secure avec Logout toujours visible
Un nouveau contexte headed a charge storageState et a ouvert /secure directement. Le prompt disait de ne pas remplir le formulaire de connexion ; Logout etait deja sur la page.

Il vaut la peine de savoir ce que le fichier d'état transporte, et ce qu'il ne transporte pas. Il contient les cookies de chaque origine touchée par le contexte et les entrées localStorage par origine. Il ne contient pas IndexedDB, le session storage, ni l'état des service workers. Les applications qui gardent leur jeton dans IndexedDB ont donc besoin d'une étape de capture supplémentaire ou d'une nouvelle connexion ; s'attendre à ce que storageState les couvre est une source fréquente d'échecs fantômes. Les cookies eux-mêmes sont régis par le domaine, le chemin et les flags décrits dans le guide des cookies MDN et la référence Set-Cookie.

Détecter une session expirée ou révoquée

Le détecteur le moins cher est une requête authentifiée faite avant le travail coûteux. La plupart des applications ont un petit point d'accès qui répond avec l'utilisateur courant, un compteur ou un objet de configuration, et son code de statut est le code de statut de la session. Une requête coûte des millisecondes ; découvrir l'échec au milieu d'un scrape coûte toute l'exécution.

async function sessionIsAlive(context) {
  const probe = await context.request.get(
    "https://app.example.com/api/summary",
    { failOnStatusCode: false },
  );
  return probe.status() === 200;
}

if (!(await sessionIsAlive(context))) {
  await loginAndSaveState(context);
}

Ne vous fiez pas au seul champ expires du cookie. Il dit quand le navigateur doit cesser d'envoyer le cookie, ce qui n'est pas la même chose que le moment où le serveur cesse de l'accepter. La révocation côté serveur reste invisible jusqu'à ce que vous demandiez. L'appel revoke du fixture est exactement cette situation : le cookie était encore présent et bien formé, et le serveur a répondu 401.

Se réauthentifier automatiquement

La réauthentification automatique est une petite machine à états : détecter, se reconnecter, rafraîchir l'état enregistré, relancer une fois l'étape échouée, et vérifier. Le plafond compte. Un workflow qui se réauthentifie en silence en boucle peut masquer un mauvais mot de passe, un verrouillage de compte ou une page de connexion qui a changé, et il générera du trafic pendant ce temps.

async function withSession(page, context, task) {
  if (!(await sessionIsAlive(context))) {
    await loginAndSaveState(context, page); // refresh file after login
  }
  try {
    return await task();
  } catch (error) {
    if (!(await sessionIsAlive(context))) {
      await loginAndSaveState(context, page); // one retry, then fail loudly
      return await task();
    }
    throw error;
  }
}

Dans le fixture, le détecteur a vu le 401, la reconnexion et la vérification se sont terminées en 99 millisecondes, et le fichier d'état rafraîchi contenait à nouveau exactement un cookie de session. L'exécution s'est terminée sur le tableau de bord protégé, table visible et trois lignes rendues. Une réauthentification qui se termine sans ces trois confirmations n'est pas terminée.

Si plusieurs workers partagent un fichier d'état, ils peuvent tous remarquer l'expiration au même moment et se précipiter pour se reconnecter. Le remède est une coordination single-flight : le premier worker rafraîchit le fichier et les autres attendent, ou chaque worker rafraîchit sa propre copie. La mécanique est la même que les problèmes de persistance de session qui apparaissent lorsque plusieurs agents IA s'exécutent contre le même profil de navigateur.

Plusieurs comptes et exécutions en parallèle

L'isolation est par contexte, donc la règle est un fichier de stockage par compte ou rôle, un contexte par worker, et aucune session mutable partagée. Dans le fixture, deux connexions parallèles ont produit deux cookies de session différents, les deux sondages ont renvoyé 200 dans la même milliseconde, et aucun contexte n'a pu voir la session de l'autre. C'est la propriété à préserver volontairement, car le mode de défaillance, quand elle casse, est subtil : deux comptes s'écrasent l'état mutuellement et un test passe pour le mauvais utilisateur.

La meme regle apparait dans un vrai navigateur. Deux Spaces sont deux contextes : l un peut rester idle sur un nouvel onglet pendant que l autre garde une session Airbnb connectee, sans partager les cookies comme le ferait une seule fenetre headed. L isolation est le point. L apercu montre seulement les deux a la fois.

Claude Code a cote de l apercu Spaces d ego (lite) montrant un Space Google idle et un Space Airbnb connecte en cours
Un Space Google idle a cote d un Space Airbnb connecte en cours. L isolation est visible ; l onglet idle n est pas une seconde recherche connectee.

Vérifier que l'authentification a bien pris effet

La vérification, ce sont trois contrôles, et en sauter un, c'est laisser un workflow cassé en silence. Le premier est positif : un élément qui n'existe qu'après la connexion est présent. Le second est négatif : le formulaire de connexion est absent, donc une page qui contient les deux par hasard n'est pas acceptée. Le troisième est un contrôle de données qui échouerait sur une page périmée ou en cache, par exemple une valeur qui doit changer d'une exécution à l'autre.

await page.waitForSelector("#revenue-table");      // positive
await expect(page.locator("#login-form")).toHaveCount(0); // negative
const probe = await context.request.get("/api/summary"); // data
assert.equal(probe.status(), 200);
assert.ok((await probe.json()).q3Revenue);

Les trois ont réussi dans l'exécution de réutilisation du fixture : la table était visible, le nombre de formulaires de connexion était zéro, et la charge JSON contenait le champ attendu. Les trois ensemble prennent quelques millisecondes et transforment la différence entre une session qui fonctionne et une session cassée d'une inférence en un fait. Plus de technique de débogage pour les exécutions qui se comportent encore mal est documentée dans le guide de débogage Playwright, et la page des bonnes pratiques couvre les habitudes autour, y compris quels waits valent la peine d'être écrits.

Quand la session appartient à votre vrai navigateur

Un fichier storageState Playwright est quelque chose que vous possédez. C'est le bon outil lorsque le compte est un fixture, que le mot de passe est dans les secrets CI, et que personne n'est assis devant la machine. C'est le mauvais outil lorsque la connexion est la vôtre : SSO, une clé matérielle, une invite push, une session que vous tenez déjà au chaud dans Chrome au quotidien. Dans ce cas, le travail n'est pas de reconstruire la connexion. C'est d'emprunter le navigateur qui l'a déjà.

ego (lite) est cet emprunt. Un agent IA peut ouvrir un onglet déjà connecté que vous utilisez, continuer à travailler dans un Space qui ne vous vole pas la souris, et s'arrêter lorsqu'un second facteur ou une confirmation de paiement a besoin de vous. La session ne devient jamais un fichier JSON sur le disque. C'est le point : une connexion personnelle doit rester dans le navigateur, pas voyager via un chemin storageState que CI commiterait plus tard par accident.

Claude Code montrant une comparaison d'annonces Airbnb terminée à côté d'un Space ego (lite) en direct sur airbnb.com.sg, avec Agent is in control et Take over visibles
La session Airbnb connectee est restee dans le navigateur. L agent a travaille dans un Space ou Agent is in control et Take over etaient visibles.

Gardez les deux instruments séparés. Les comptes de test sans surveillance restent sur storageState, comme le décrit le guide d'authentification Playwright. Les tableaux de bord personnels restent dans un navigateur visible. ego (lite) ne clique pas sur le MFA, ne confirme pas les paiements et ne collecte pas d'identifiants ; ces arrêts sont documentés dans la documentation Space et le démarrage rapide. La version actuelle du produit est 0.5.0.32 (changelog, 2026-09-12). Revérifiez le changelog et le dépôt GitHub avant de citer un build plus récent.

FAQ

Pourquoi Playwright retombe-t-il sur la page de connexion après l'enregistrement de storageState ?

Playwright retombe sur la page de connexion après storageState lorsque le fichier a été enregistré trop tôt, que le contexte ne l'a jamais chargé, ou que le serveur a révoqué le cookie. Vérifiez d'abord les cookies du fichier, puis les options du contexte. Si les deux ont l'air corrects, traitez-le comme une session morte, pas comme un bug de navigation.

Les cookies de session survivent-ils à storageState ?

Oui. Le fichier enregistre les cookies qu'ils aient une expiration ou non, ainsi que localStorage par origine. Ce qui ne survit pas, c'est tout ce que le navigateur garde hors de cette structure : IndexedDB, session storage, cache et service workers.

Combien de temps dure une connexion Playwright enregistrée ?

Aussi longtemps que le serveur continue d'accepter la session, une décision que le serveur prend et peut inverser à tout moment. La date d'expiration du cookie est un plafond, pas une promesse. Traitez tout fichier d'état de longue durée comme périmé jusqu'à ce qu'un sondage dise le contraire.

Des workers parallèles peuvent-ils partager un fichier storageState ?

Ils peuvent le lire, et le contexte de chaque worker sera isolé. Le problème apparaît lorsqu'un worker rafraîchit le fichier après une reconnexion pendant qu'un autre est en cours d'exécution. Donnez à chaque worker sa propre copie, ou sérialisez le rafraîchissement pour qu'une seule connexion ait lieu à la fois.

Comment s'authentifier avec OAuth ou SSO dans Playwright ?

Parcourez une fois toute la chaîne de redirection, dans un contexte qui conservera les cookies du fournisseur d'identité, et n'enregistrez l'état qu'une fois l'URL finale de l'application atteinte. Si le fournisseur exige une étape interactive, faites-la une fois à la main dans la même exécution, puis réutilisez l'état résultant ensuite.

Comment traiter le MFA et les codes à usage unique ?

Pas en automatisant le second facteur avec un code stocké. Le schéma honnête est une étape humaine unique dont le résultat est la session enregistrée, ou une session déjà authentifiée dans un navigateur que vous possédez. Stocker des secrets OTP ou des codes SMS à côté d'un fichier de stockage transforme un contrôle de sécurité en passif.

Est-il sûr de commiter storageState dans le contrôle de version ?

Non. Le fichier contient des cookies de session vivants. Gardez-le dans le répertoire de travail, ignorez-le dans git, et en CI injectez-le depuis un magasin de secrets. S'il a jamais été commité, traitez la session comme divulguée et révoquez-la.

Comment tester délibérément que mon automatisation gère une session expirée ?

Ajoutez un hook de test qui invalide la session côté serveur, comme le fait le point d'accès revoke du fixture, et exécutez le chemin de réutilisation contre lui. Assertez la redirection, le 401 du sondage et la récupération. Ce seul test couvre le chemin de code qui, autrement, ne se déclenche qu'en production.

Et si l'application stocke son jeton dans IndexedDB ?

storageState ne le transportera pas, donc utilisez l'un des deux chemins : capturer le jeton via un script de page après la connexion et le réinjecter à la réutilisation, ou vous authentifier dans chaque exécution via l'API qui émet le jeton. Le premier est plus rapide, le second est moins couplé aux internes de l'application.

Chaque exécution devrait-elle se réauthentifier plutôt que de réutiliser l'état ?

Pour du CI avec un compte de test jetable, se réauthentifier à chaque exécution est le défaut le plus sûr. Pour un job qui s'exécute souvent contre une session stable, la réutilisation plus un sondage et une reconnexion plafonnée est moins chère et plus facile à raisonner. Les deux sont légitimes ; la réutilisation non vérifiée ne l'est pas.

Pourquoi la connexion fonctionne-t-elle en local mais échoue-t-elle en CI ?

Le plus souvent parce que le fichier d'état manque ou est plus ancien en CI, que l'horloge diffère, ou que la page de connexion sert une variante différente à une IP neuve. Reproduisez avec le même réglage headless, le même viewport et le même fichier de stockage, et sondez la session avant que la suite ne s'exécute. L'échec est souvent environnemental, pas une différence de code.

Un fichier storageState peut-il servir deux comptes ?

Non, et les tentatives de les fusionner tendent à garder le cookie écrit en dernier. Utilisez un fichier par compte ou rôle, nommez-le d'après l'identité à laquelle il appartient, et passez le bon à chaque contexte.