
Playwright storageState 파일은 다 있는 것처럼 보여도, 앱이 실제로 의존하는 상태를 복원하지 못할 수 있습니다. cookies도 있고 localStorage도 있고 JSON도 멀쩡해 보여도, sessionStorage는 기본으로 빠집니다. 앱이 세션 일부를 거기에 두고 있다면 새 context에 파일을 불러도 그 상태는 돌아오지 않습니다.
이 구분이 중요한 이유는 storageState가 스냅샷이지 완전한 브라우저 프로필이 아니기 때문입니다. Playwright는 그 스냅샷을 내보내고, 불러오고, 계정별로 격리하고, 검증하기 쉽게 만듭니다. 하지만 작업이 sessionStorage, 확장 프로그램, 기존 로그인 프로필, 사람이 MFA를 끝내는 일처럼 파일에 깔끔히 들어가지 않는 브라우저 상태에 의존한다면, ego (lite)는 다른 길을 갑니다. JSON으로 다시 만들지 않고 실제 Chromium 환경을 그대로 둡니다.
이 가이드가 다루는 것은 스냅샷 자체입니다. storageState에 무엇이 들어 있는지, 어떻게 만들고 불러오는지, 계정과 환경을 어떻게 나누는지, 복원이 실제로 먹혔는지를 어떻게 증명하는지입니다. 로그인 루프와 자동 재인증은 별도 문제입니다. 여기서의 질문은 더 단순합니다. 파일이 실제로 무엇을 저장했고, 무엇을 남겼는가.
Playwright storageState란
Playwright storageState는 브라우저 context 하나의 cookie와 localStorage를 저장한 스냅샷입니다. 원하는 상태가 이미 들어 있는 context에서 내보낸 뒤, 이후 context에 넣어 빈 상태가 아니라 그 스냅샷으로 시작하게 합니다.
공식 Playwright 인증 문서는 이 파일 위에 테스트 준비를 올립니다. 그건 스냅샷의 사용자이지 정의가 아닙니다. 이 페이지가 다루는 것은 파일입니다.
X와 LinkedIn의 로그인 장벽은 다음 글에서 다룹니다: 로그인 장벽 너머의 AI 스크래핑.
JavaScript 스크래핑 경로 선택은 다음 글에서 다룹니다: JavaScript 웹 스크래핑.
storageState 파일에 실제로 들어 있는 것
파일은 cookies와 origins를 가진 JSON입니다. cookies는 cookie 객체 배열입니다. origins는 origin 레코드 배열이고, 각각 localStorage name/value 쌍을 가집니다. Playwright의 storageState API는 경로를 넘기면 그 형태를 쓰고, 넘기지 않으면 같은 객체를 반환합니다.
| 스토어 | 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에서 확인했습니다. quotes.toscrape.com의 headed Chromium은 최상위 키 cookies와 origins가 있는 storageState를 내보냈습니다. d05-demo는 origins localStorage에 있었습니다. JSON 문자열에 sessionStorage도 d05-session도 없었습니다.

Playwright 인증 가이드는 일부 구성의 재사용 상태에 IndexedDB와 passkeys가 나온다고도 합니다. 블로그가 나열했다고 그 키가 있다고 가정하지 마세요. 만든 파일을 열고 최상위 키를 읽으세요.
Expires나 Max-Age가 없는 세션 cookie는 프로필 디렉터리의 별도 함정입니다. 그 디스크 동작은 지속 브라우저 세션에서 다룹니다. storageState JSON에서 cookie 만료는 각 cookie의 명시 필드입니다. 0이거나 지난 타임스탬프면 다음 날까지 살지 못하는 cookie입니다.
storageState를 만들고 불러오는 방법
원하는 상태가 이미 들어 있는 context에서 만듭니다. 그 상태가 필요한 페이지를 열기 전에 새 context로 불러옵니다. 한 origin에서 내보내고 다른 origin의 localStorage가 나오길 기대하지 마세요.
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 authentication docs는 setup 프로젝트로 감싸 테스트가 로그인 UI를 건너뛰게 합니다. 감싸기는 선택입니다. 두 호출은 선택이 아닙니다.
앱이 세션 토큰을 sessionStorage에만 두면 이 파일은 그것을 나르지 않습니다. page.evaluate로 sessionStorage를 복사하거나, 프로세스를 살려 두거나, 실제 프로필을 쓰세요.
복원도 같은 OpenCode 세션에서 확인했습니다. 그 파일에서 불러온 새 context는 local = local-only, session = null을 출력했습니다.

복원이 먹혔는지 증명하는 방법
복원 증명은 직접 쓴 키를 읽거나, 저장한 cookies만 열 수 있는 인증 URL을 여는 일입니다. '로그인 폼이 없다'를 증거로 삼지 마세요. 리다이렉트 버그로도 폼은 숨습니다.
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");
}실제 계정이면 cookie가 유효할 때만 200을 주는 URL을 치고, 보이는 로그인 라벨을 어서트하세요. /login에 떨어지면 스냅샷은 낡은 것입니다. 재인증은 다른 곳의 일입니다. 여기서 필요한 것은 실패입니다. 이 JSON은 세션을 복원하지 않았습니다.
| 확인 | 통과 | 실패 |
|---|---|---|
| localStorage 키 | 저장한 값을 읽음 | 불러온 뒤 null |
| sessionStorage 키 | 직접 복사하지 않으면 null | localStorage가 남았으니 이것도 남았다고 가정 |
| Cookie 만료 | 필요한 cookies의 expires가 미래임 | expires가 0이거나 이미 지남 |
계정과 환경을 격리하는 방법
재사용할 계정, 환경, 브라우저 context마다 파일 하나. user.json에 스테이징과 프로덕션 cookies를 섞으면 잘못된 테넌트를 테스트하게 됩니다.
JSON의 origins는 origin 범위입니다. https://quotes.toscrape.com에 저장한 localStorage 키는 http://quotes.toscrape.com에 나타나지 않습니다. 스킴, 호스트, 포트가 모두 셉니다.
playwright/.auth/staging-admin.json
playwright/.auth/staging-viewer.json
playwright/.auth/prod-readonly.json병렬 워커는 각자 파일이나 context가 필요합니다. 다시 쓰는 두 context가 JSON 하나를 공유하면 경합입니다. 준비 뒤에 내보내고, 실행 중에는 읽기 전용으로 불러오고, 새 파일은 전용 갱신 잡에서만 쓰세요.
저장된 상태가 만료됐는지 알아보는 방법
파일은 유효해 보여도 사이트가 이미 세션을 취소했을 수 있습니다. JSON의 cookie expires를 본 뒤, 그 cookie가 필요한 라이브 URL을 확인하세요.
expires: -1 또는 0인 cookie는 스냅샷 안의 세션 cookie입니다. 같은 실행에서는 되고, 브라우저 처리에 따라 나중에 사라질 수 있습니다. 지난 타임스탬프는 이미 죽은 것입니다. 미래 타임스탬프도 서버에서 취소될 수 있습니다.
중요한 것은 라이브 확인입니다. 불러온 뒤 인증 경로를 여세요. 로그인 페이지, 401, 익명 껍데기가 오면 스냅샷은 다 쓴 것입니다. 파일을 갱신하세요. 이 페이지에서 한 번 로그인하는 기계를 만들지 마세요.
storageState를 안전하게 보관하는 방법
JSON은 비밀번호로 취급하세요. Playwright 자체 인증 가이드도 해당 계정을 사칭할 cookies와 헤더가 들어 있을 수 있다고 합니다. git, 로그, CI 산출물, 모델 컨텍스트에 넣지 마세요.
# .gitignore
playwright/.auth/CI는 잡 시작에 시크릿 스토어에서 파일을 주입하고 잡 끝에 지울 수 있습니다. 출력하지 마세요. 실패한 테스트 zip에 붙이지 마세요. '세션 디버그'를 위해 에이전트 프롬프트에 붙여 넣지 마세요.
실제 브라우저 프로필을 재사용해야 하는 경우
앱이 cookies와 localStorage 이상을 필요로 하면 실제 Chromium 프로필을 재사용하세요. 확장 프로그램, sessionStorage, 기기 신호, 사람이 MFA를 버티는 일입니다. JSON 파일은 그것을 나르지 못합니다.
여기에 맞는 것이 ego (lite) 0.5.0.32입니다. 에이전트는 머신에 이미 있는 일상 브라우저 프로필을 상대로 격리된 Space에서 돌아갑니다. 탭을 지켜볼 수 있고, 프롬프트를 넘겨받을 수 있고, 작업을 멈출 수 있습니다. 이 버전의 Changelog 날짜는 2026-09-12입니다. 자세한 내용은 ego (lite) 변경 로그에 있습니다. storageState 내보내기 도구가 아닙니다. 파일 형태가 맞지 않을 때 파일을 건너뛰는 경로입니다.
그 격리는 OpenCode에서 ego (lite)로 확인했습니다. Spaces 개요는 quotes 작업을 단독 실행 Space에 두고, 다른 작업은 별도 Space로 나눴습니다. 세션을 JSON에서 다시 만들지 않았습니다.

같은 공개 URL을 그런 Space 하나에서 확인했습니다. Space 6는 quotes.toscrape.com에서 에이전트 제어를 유지했고, Take over와 Stop이 보였습니다. 브라우저 환경은 그대로였고 JSON 스냅샷으로 다시 조립하지 않았습니다.

localStorage가 돌아오고 sessionStorage가 null이면 파일은 할 일을 한 것입니다. 사라진 로그인 폼을 증거로 삼지 마세요. 2026-09-18 복원된 context는 더미 키에서 local = local-only, session = null을 출력했고 비밀번호 폼은 없었습니다.
과제와 한계
파일은 완전해 보여도 앱이 실제로 쓰는 스토어를 빠뜨립니다. 그게 기본 실패입니다.
두 번째는 origin 불일치입니다. localhost:3000에 저장하고 127.0.0.1:3000에 불러오면 localStorage는 비고, cookies는 도메인에 따라 붙을 수 있습니다.
세 번째는 이 페이지에 로그인 안무를 너무 많이 싣는 일입니다. 401 감지, /login으로 튕기기, 스냅샷 갱신은 진짜 일입니다. 이 파일의 일이 아닙니다.
FAQ
Playwright storageState에 cookies가 들어가나
들어갑니다. cookies는 JSON의 최상위 배열입니다. 각 cookie는 name, value, domain, path, expires, httpOnly, secure, sameSite를 가질 수 있습니다.
storageState에 localStorage가 들어가나
들어갑니다. origins 아래입니다. 각 origin 레코드는 그 origin만의 localStorage name/value 쌍을 가집니다.
storageState에 sessionStorage가 들어가나
기본으로는 들어가지 않습니다. 페이지가 sessionStorage에 쓰고 storageState를 내보내도 sessionStorage 키가 없는 JSON이 나옵니다. 복원된 context는 그 스토어를 빈 것으로 읽습니다. 2026-09-18 headed 실행은 local = local-only, session = null을 복원했습니다.
storageState 파일 만드는 방법
원하는 cookies와 localStorage가 이미 들어 있는 context 뒤에 await context.storageState({ path: 'playwright/.auth/user.json' })를 호출합니다.
새 context에 storageState를 불러오는 방법
browser.newContext에 storageState: 'playwright/.auth/user.json'을 넘깁니다. 스냅샷이 필요한 페이지로 이동하기 전에 불러오세요.
복원이 먹혔는지 확인하는 방법
설정한 localStorage 키를 읽거나, 저장한 cookies만 도달할 수 있는 URL을 여세요. 로그인 UI가 없는 것은 증거가 아닙니다.
storageState를 git에 커밋해도 되나
아니요. playwright/.auth/를 .gitignore에 넣으세요. 파일은 캡처한 계정을 사칭할 수 있습니다.
테스트 두 개가 storageState 파일 하나를 공유할 수 있나
같은 스냅샷을 읽기 전용으로 불러올 수 있습니다. 병렬 워커가 같은 파일을 쓰게 하지 마세요. 계정이 다르면 역할마다 파일을 나누세요.
실제 브라우저 프로필을 써야 하는 경우
앱이 sessionStorage, 확장 프로그램, MFA를 위한 사람을 필요로 할 때입니다. ego (lite)는 그 작업을 일상 Chromium 프로필을 상대로 Space에서 실행합니다.
storageState는 user data directory와 같은가
아닙니다. storageState는 JSON 스냅샷입니다. user data directory는 디스크상의 프로필입니다.
다음 작업이 JSON 스냅샷이 아니라 실제 로그인 브라우저가 필요하다면, ego (lite)는 무료로 내려받을 수 있습니다.
