
Файл Playwright storageState может выглядеть полным и всё равно не восстановить состояние, от которого реально зависит приложение. Cookies могут быть на месте, localStorage тоже, JSON может быть совершенно корректным, но sessionStorage по умолчанию отсутствует. Если приложение держит часть сессии там, загрузка файла в новый контекст это состояние не вернёт.
Это различие важно, потому что storageState представляет собой снимок, а не полный профиль браузера. Playwright позволяет легко экспортировать этот снимок, загружать его, изолировать по аккаунту и проверять. Но когда задача зависит от состояния браузера, которое в файл не укладывается, например sessionStorage, расширения, уже открытый профиль со входом или человек, который проходит MFA, ego (lite) идёт другим путём: оставляет настоящее окружение Chromium на месте, вместо того чтобы собирать его заново из JSON.
Это руководство держится на самом снимке: что содержит storageState, как его сгенерировать и загрузить, как разделить аккаунты и окружения и как доказать, что восстановленное состояние действительно сработало. Циклы входа и автоматическая повторная аутентификация: отдельная задача. Здесь вопрос проще: что файл реально сохранил и что оставил за бортом?
Что такое Playwright storageState?
Playwright storageState это сохранённый снимок cookies и localStorage одного контекста браузера. Вы экспортируете его после того, как в контексте уже есть нужное состояние, затем передаёте этот снимок в следующий контекст, чтобы он стартовал с ним, а не с пустой jar.
Официальная документация Playwright по аутентификации строит на этом файле настройку тестов. Это потребитель снимка, а не определение снимка. Эта страница остаётся на файле.
Стены входа на X и LinkedIn разобраны в материале AI-скрейпинг за стенами входа.
Маршрутизация скрейпинга на JavaScript разобрана в материале веб-скрейпинг на JavaScript.
Что на самом деле содержит файл storageState?
Файл представляет собой JSON с cookies и origins. cookies это массив объектов cookie. origins это массив записей origin, у каждой пары name/value из localStorage. storageState API записывает эту форму, когда вы передаёте path, и возвращает тот же объект, когда не передаёте.
| Хранилище | Есть в storageState по умолчанию? | Что это значит |
|---|---|---|
| cookies | Да | Каждая запись может нести name, value, domain, path, expires, httpOnly, secure и sameSite. |
| localStorage | Да, внутри origins | Ключ привязан к origin. Ключ, сохранённый на https://quotes.toscrape.com, не появится на другом origin. |
| sessionStorage | Нет | В JSON по умолчанию нет ключа sessionStorage. Восстановленная страница читает sessionStorage как null. |
Мы проверили форму этого файла 2026-09-18 из OpenCode. Headed Chromium на quotes.toscrape.com экспортировал storageState с ключами верхнего уровня cookies и origins. d05-demo был в origins localStorage. В строке JSON не было sessionStorage или d05-session.

Руководство Playwright по аутентификации также упоминает IndexedDB и passkeys в повторно используемом состоянии для некоторых схем. Не считайте, что эти ключи есть, только потому что их перечислил блог. Откройте сгенерированный файл и прочитайте ключи верхнего уровня.
Сессионные cookies без Expires или Max-Age это отдельная ловушка в каталогах профиля. Это поведение диска разобрано в материале постоянные сессии браузера. В JSON storageState срок действия cookie это явное поле у каждой cookie. Ноль или прошедшая метка времени показывают cookie, которая не переживёт следующий календарный день.
Как сгенерировать storageState и загрузить его?
Генерируйте из контекста, в котором уже есть нужное состояние. Загружайте в новый контекст до того, как откроете страницу, которой оно нужно. Не экспортируйте с одного origin и не ждите localStorage другого origin.
import { chromium } from "playwright";
const browser = await chromium.launch();
const setup = await browser.newContext();
const page = await setup.newPage();
await page.goto("https://quotes.toscrape.com/");
await page.evaluate(() => {
localStorage.setItem("d05-demo", "local-only");
sessionStorage.setItem("d05-session", "session-only");
});
await setup.storageState({ path: "playwright/.auth/user.json" });
await setup.close();
const reused = await browser.newContext({
storageState: "playwright/.auth/user.json",
});
const next = await reused.newPage();
await next.goto("https://quotes.toscrape.com/");
const restored = await next.evaluate(() => ({
local: localStorage.getItem("d05-demo"),
session: sessionStorage.getItem("d05-session"),
}));
console.log(restored);
await browser.close();Этот фрагмент и есть весь механизм. Официальная документация Playwright по аутентификации оборачивает его в setup-проект, чтобы тесты пропускали UI входа. Обёртка необязательна. Два вызова обязательны.
Если приложение хранит токен сессии только в sessionStorage, этот файл его не унесёт. Скопируйте sessionStorage через page.evaluate, держите процесс живым или используйте настоящий профиль.
Мы проверили восстановление в той же сессии OpenCode. Новый контекст, загруженный из этого файла, напечатал local = local-only и session = null.

Как доказать, что восстановленное состояние сработало?
Доказывайте восстановление ключом, который вы записали, или аутентифицированным URL, который откроют только сохранённые cookies. Не считайте доказательством, что «формы входа нет». Ошибка редиректа тоже может спрятать форму.
await page.goto("https://quotes.toscrape.com/");
const local = await page.evaluate(() => localStorage.getItem("d05-demo"));
if (local !== "local-only") {
throw new Error("storageState did not restore localStorage");
}Для настоящего аккаунта откройте URL, который возвращает 200 только при валидной cookie, затем проверьте видимую метку входа. Если вы попали на /login, снимок устарел. Повторная аутентификация здесь не разбирается. Здесь нужен только отказ: этот JSON сессию не восстановил.
| Проверка | Успех | Отказ |
|---|---|---|
| ключ localStorage | Читает сохранённое вами значение | null после загрузки |
| ключ sessionStorage | null, если вы сами его не скопировали | Предположение, что он выжил, потому что выжил localStorage |
| Срок cookie | expires в будущем у нужных cookies | expires равен 0 или уже в прошлом |
Как изолировать аккаунты и окружения?
Один файл на аккаунт, на окружение, на каждый контекст браузера, который вы собираетесь переиспользовать. Смешивать cookies стейджинга и продакшена в user.json это способ тестировать не того тенанта.
Origin в JSON ограничены origin. Ключ localStorage, сохранённый на https://quotes.toscrape.com, не появится на http://quotes.toscrape.com. Схема, хост и порт все имеют значение.
playwright/.auth/staging-admin.json
playwright/.auth/staging-viewer.json
playwright/.auth/prod-readonly.jsonПараллельным воркерам нужен свой файл или свой контекст. Общий JSON на два контекста, которые потом пишут обратно, это гонка. Экспортируйте после настройки, загружайте только для чтения во время запуска и пишите новый файл только из отдельной задачи обновления.
Как понять, что сохранённое состояние истекло?
Файл может выглядеть валидным, хотя сайт уже отозвал сессию. Проверьте expires cookie в JSON, затем живой URL, которому нужна эта cookie.
Cookie с expires: -1 или 0 это сессионная cookie в снимке. Она может сработать в том же запуске и исчезнуть позже, в зависимости от того, как браузер с ней обращается. Метка времени в прошлом уже мертва. Будущая метка всё равно может быть отозвана на стороне сервера.
Живая проверка и есть та, что имеет значение. После загрузки откройте аутентифицированный маршрут. Если вы получаете страницу входа, 401 или анонимную оболочку, снимок израсходован. Обновите файл. На этой странице не собирайте схему «войти один раз».
Как безопасно хранить storageState?
Обращайтесь с JSON как с паролем. Собственное руководство Playwright по auth говорит, что в нём могут быть cookies и заголовки, которые выдают себя за вас. Держите его вне git, логов, артефактов CI и контекста модели.
# .gitignore
playwright/.auth/CI может подставить файл из хранилища секретов в начале задания и удалить его в конце. Не печатайте его. Не прикладывайте к zip упавшего теста. Не вставляйте в промпт агента, чтобы «отладить сессию».
Когда лучше переиспользовать настоящий профиль браузера?
Переиспользуйте настоящий профиль Chromium, когда приложению нужно больше, чем cookies и localStorage: расширения, sessionStorage, сигналы устройства или человек, который сидит через MFA. JSON-файл это не унесёт.
Именно здесь ego (lite) 0.5.0.32 оказывается уместен. Агент работает в изолированном Space против повседневного профиля браузера, который уже есть на машине. Вкладку можно наблюдать, промпт можно перехватить, задачу можно остановить. Changelog этой версии датирован 2026-09-12 на странице changelog ego (lite). Это не экспортёр storageState. Это путь, который пропускает файл, когда форма файла неверна.
Мы проверили эту изоляцию в ego (lite) из OpenCode. Обзор Spaces держал задачу с цитатами в своём запущенном Space, а другую работу в отдельном Space, вместо того чтобы собирать сессию заново из JSON.

Мы проверили тот же публичный URL внутри одного из этих Space. Space 6 оставался под управлением агента на quotes.toscrape.com, с видимыми Take over и Stop. Окружение браузера осталось на месте, а не было собрано заново из JSON-снимка.

Если localStorage вернулся, а sessionStorage равен null, файл сделал свою работу. Не считайте отсутствующую форму входа доказательством. 2026-09-18 восстановленный контекст напечатал local = local-only и session = null по фиктивным ключам, без формы пароля.
Каковы сложности и ограничения?
Файл выглядит полным и всё равно пропускает то хранилище, которым приложение реально пользуется. Это отказ по умолчанию.
Несовпадение origin второе. Сохраните на localhost:3000, загрузите против 127.0.0.1:3000, и localStorage пуст, тогда как cookies всё ещё могут прилипнуть в зависимости от domain.
Третье: перегружать эту страницу хореографией входа. Обнаружение 401, отскок на /login и обновление снимка это настоящие задачи. Это не работа этого файла.
Часто задаваемые вопросы
Включает ли Playwright storageState cookies?
Да. cookies это массив верхнего уровня в JSON. У каждой cookie могут быть name, value, domain, path, expires, httpOnly, secure и sameSite.
Включает ли storageState localStorage?
Да, внутри origins. Каждая запись origin хранит пары name/value localStorage только для этого origin.
Включает ли storageState sessionStorage?
По умолчанию нет. Страница может записать sessionStorage, экспортировать storageState и всё равно получить JSON без ключа sessionStorage. Восстановленный контекст читает это хранилище как пустое. 2026-09-18 headed-запуск восстановил local = local-only и session = null.
Как сгенерировать файл storageState?
Вызовите await context.storageState({ path: 'playwright/.auth/user.json' }) после того, как в контексте уже есть нужные cookies и localStorage.
Как загрузить storageState в новый контекст?
Передайте storageState: 'playwright/.auth/user.json' в browser.newContext. Загрузите до перехода на страницу, которой нужен снимок.
Как понять, что восстановленное состояние сработало?
Прочитайте ключ localStorage, который вы задали, или откройте URL, до которого доберутся только сохранённые cookies. Отсутствующий chrome входа не доказательство.
Нужно ли коммитить storageState в git?
Нет. Добавьте playwright/.auth/ в .gitignore. Файл может выдавать себя за аккаунт, который захватил.
Могут ли два теста делить один файл storageState?
Они могут загружать один и тот же снимок только для чтения. Не давайте параллельным воркерам писать в один файл. Если аккаунты разные, используйте по файлу на роль.
Когда использовать настоящий профиль браузера?
Когда приложению нужны sessionStorage, расширения или человек для MFA. ego (lite) выполняет эту работу в Space против вашего повседневного профиля Chromium.
storageState это то же самое, что user data directory?
Нет. storageState это JSON-снимок. user data directory это профиль на диске.
Если следующей задаче нужен настоящий браузер со входом, а не JSON-снимок, ego (lite) можно скачать бесплатно.
