쿠키와 프록시 컨텍스트를 잃어버리는 Playwright CDP 세션 디버깅
요약
Playwright를 사용하여 CDP(Chrome DevTools Protocol)로 브라우저에 연결할 때 발생하는 세션 및 프록시 컨텍스트 유실 문제를 다룹니다. 단순한 셀렉터 오류가 아닌 브라우저 정체성(identity) 불일치 관점에서의 디버깅 접근법을 제시합니다.
핵심 포인트
- CDP 연결 시 쿠키 및 세션 상태 유실 여부를 먼저 확인해야 함
- 셀렉터 수정 전 브라우저 정체성(identity) 일치 여부 검증 필요
- AI 에이전트 사용 시 환경 변화로 인한 버그 은폐 가능성 주의
- storageState 등 지속성 모델의 명확한 구분 필요
동일한 실패 패턴이 반복되는 것을 보고 저는 이를 별도의 버그 클래스로 취급하기 시작했습니다:
사용자가 **브라우저 프로필 (browser profile)**을 열고 로그인되어 있습니다.
Playwright 스크립트가 CDP를 통해 연결(attach)됩니다.
페이지가 열립니다.
그런 다음 워크플로우가 마치 **세션 (session)**이 비어 있거나, 계정이 로그아웃되었거나, 요청이 잘못된 **프록시 (proxy)**를 통해 들어오는 것처럼 동작합니다.
유혹적인 해결책은 대기 시간(wait)을 추가하거나 셀렉터(selector)를 다시 작성하는 것입니다.
하지만 그것은 대개 잘못된 첫 번째 조치입니다.
**CDP 연결 세션 (CDP-attached session)**이 **인증 상태 (auth state)**나 **프록시 컨텍스트 (proxy context)**를 잃어버렸을 때, 첫 번째 질문은 다음과 같아야 합니다:
어떤 셀렉터가 변경되었는가? 가 아닙니다.
질문은 다음과 같아야 합니다:
내 스크립트가 내가 수동으로 확인한 것과 동일한 브라우저 정체성(identity)에 연결되었는가?
이 노트는 해당 문제에 대한 실질적인 디버깅 체크리스트입니다.
문제: Playwright가 기존 Chromium 브라우저에 연결되지만, 쿠키 (cookies), 세션 상태 (session state) 또는 **프록시 컨텍스트 (proxy context)**가 예상과 일치하지 않습니다.
범위: Chromium, connectOverCDP, 브라우저 프로필 (browser profiles), 지속적 세션 (persistent sessions), 헤드리스 체크 (headless checks), 그리고 AI 에이전트 디버깅 (AI agent debugging).
포함되지 않는 내용: 플랫폼 보호 우회, CAPTCHA 처리, 또는 관련 없는 계정 간의 인증 데이터 이동.
실패 패턴
실행 상태는 겉보기에 보통 정상적으로 보입니다.
브라우저가 시작됩니다. **CDP 엔드포인트 (CDP endpoint)**가 응답합니다. Playwright가 연결됩니다. 페이지가 로드됩니다. 스크립트는 클릭할 수 있고 DOM을 읽을 수 있습니다.
하지만 환경이 잘못되었습니다.
다음 증상 중 하나 이상이 나타날 수 있습니다:
- **쿠키 (Cookies)**가 비어 있거나 예상보다 적습니다.
- 새로고침 후 앱이 로그인 페이지로 리다이렉트됩니다.
- localStorage는 존재하지만, 앱은 여전히 사용자를 로그아웃된 상태로 취급합니다.
- 계정이 수동으로는 작동하지만 CDP를 통해 연결하면 실패합니다.
- 공인 IP가 예상된 **프록시 경로 (proxy route)**와 일치하지 않습니다.
- 작업이 가시 모드(visible mode)에서는 작동하지만 **헤드리스 모드 (headless mode)**에서는 실패합니다.
- **AI 브라우저 에이전트 (AI browser agent)**가 작업을 재시도하고 다른 컨텍스트에서 성공으로 표시합니다.
마지막 케이스는 특히 놓치기 쉽습니다.
일반적인 스크립트는 환경이 변하면 종종 중단됩니다. 하지만 **AI 에이전트 (AI agent)**는 계속 진행하고, 적응하며, 실제 버그를 숨길 수 있습니다.
따라서 셀렉터 (selectors)를 디버깅하기 전에, 저는 한 가지를 증명하려고 시도합니다:
스크립트가 예상된 브라우저 식별 정보 (browser identity)로부터 실행되고 있는가.
먼저 세 가지 지속성 모델 (persistence models)을 구분하십시오
디버깅을 시작하기 전에, 당신이 실제로 사용하고 있는 모델의 이름을 명명하십시오.
많은 **Playwright 세션 버그 (Playwright session bugs)**는 다음 세 가지 모델이 서로 뒤섞여 있기 때문에 혼란을 야기합니다:
- storageState
- launchPersistentContext
- connectOverCDP
이들은 서로 연관되어 있지만, 서로 교체 가능한 것은 아닙니다.
1. storageState
storageState는 스냅샷 (snapshot)입니다.
인증 (auth)이 쿠키 (cookies), localStorage, 그리고 때로는 IndexedDB로 표현될 때 유용합니다.
이것은 전체 **브라우저 프로필 (browser profile)**과 동일한 것이 아닙니다.
적합한 경우:
- 테스트 계정
- 반복 가능한 로그인 설정
- CI 워크플로우 (CI workflows)
- 깨끗한 브라우저 컨텍스트 (clean browser contexts)
위험한 가정:
내 실제 Chrome 프로필이 로그인되어 있으므로, storageState가 모든 것을 포함하고 있을 것이다.
그렇지 않을 수도 있습니다.
일부 앱은 sessionStorage, 서비스 워커 (service workers), 확장 프로그램 (extensions), 디바이스 바운드 상태 (device-bound state), 또는 앱 전용 저장소 (app-specific storage)를 포함하기도 합니다.
2. launchPersistentContext
launchPersistentContext는 특정 **사용자 데이터 디렉토리 (user data directory)**와 함께 브라우저를 시작합니다.
해당 디렉토리는 쿠키 (cookies) 및 localStorage와 같은 브라우저 세션 데이터를 저장합니다.
적합한 경우:
- 장기 유지되는 자동화 프로필
- 로컬 디버깅
- 프로필 디렉토리가 신뢰할 수 있는 유일한 원천 (source of truth)인 워크플로우
위험한 가정:
여러 브라우저 인스턴스를 동일한 프로필 디렉토리에 지정할 수 있다.
그렇게 하지 마십시오.
**사용자 데이터 디렉토리 (user data directory)**를 잠금 가능한 데이터베이스처럼 취급하십시오. 실행 중에는 하나의 브라우저만이 이를 소유합니다.
3. connectOverCDP
connectOverCDP는 **Chrome DevTools Protocol (CDP)**을 통해 Playwright를 기존의 Chromium 브라우저에 연결합니다.
적합한 경우:
- 다른 도구에 의해 시작된 브라우저에 연결하기
- 이미 열려 있는 프로필 디버깅하기
- 수동으로 검사 중인 세션에 자동화를 연결하기
위험한 가정:
CDP 연결이 Playwright로 실행한 브라우저와 정확히 동일한 동작을 보장한다.
종종 잘 작동하기도 하지만, 연결 모델 자체가 동일한 것은 아닙니다. 버그가 쿠키 (cookies), 프로필 (profiles), 또는 **프록시 컨텍스트 (proxy context)**와 관련되어 있다면, 작업 로직을 건드리기 전에 연결된 브라우저를 먼저 확인하십시오.
1단계: 전용 브라우저 프로필 시작하기
평소 사용하는 Chrome 프로필로 디버깅하지 마십시오.
전용 자동화 디렉토리를 사용하십시오:
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/pw-cdp-profile \
...
그 다음, 테스트에 인증된 상태가 필요하다면 해당 브라우저에서 수동으로 로그인하십시오.
체크리스트:
- userDataDir이 이 테스트를 위해 전용으로 설정되었는가.
- 다른 Chrome 프로세스가 동일한 디렉토리를 사용하고 있지 않은가.
- 브라우저가 예상된 **원격 디버깅 포트 (remote debugging port)**와 함께 시작되었는가.
- 프로필이 개인용 기본 Chrome 프로필이 아닌가.
- 프로필을 수동으로 다시 열었을 때 여전히 예상되는 **로그인 상태 (login state)**를 유지하는가.
검증 표준:
자동화 도구가 프로필을 신뢰하기 전에, 수동으로 먼저 프로필의 신뢰성을 확인해야 합니다.
만약 수동 브라우저에서 이미 로그아웃된 상태라면, 아직 Playwright의 문제는 아닙니다.
2단계: CDP를 통해 연결하고 실제 컨텍스트 검사하기
실제 워크플로우를 실행하기 전에 가능한 가장 작은 규모의 스크립트를 실행하십시오.
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
...
이 단계를 건너뛰지 마십시오.
체크리스트:
- contextCount가 예상한 값인가.
- pageCount가 예상한 값인가.
- 스크립트가 예상된 탭을 사용하거나, 예상된 컨텍스트 내에서 새 탭을 생성하는가.
- cookieCount가 예상치 못하게 0이 아닌가.
- localStorageKeys가 로그인한 앱과 일치하는가.
- sessionStorageKeys가 인증 정보가 존재하는 유일한 장소는 아닌가.
- 페이지를 새로고침했을 때 즉시 로그인 페이지로 리다이렉트되지 않는가.
이 최소한의 스크립트가 실패한다면, 실제 워크플로우는 고려할 가치도 없습니다.
문제는 연결된 **브라우저 컨텍스트 (browser context)**에 있습니다.
Step 3: 쿠키가 실제로 인증 소스인지 확인하기
흔히 하는 디버깅 실수 중 하나는 **쿠키 (cookies)**를 세션의 전부로 취급하는 것입니다.
많은 애플리케이션의 경우, 이는 사실이 아닙니다.
각 레이어를 확인하십시오:
- 쿠키 (Cookies)
- localStorage
- IndexedDB
- sessionStorage
- 서비스 워커 (service worker) 상태
- 확장 프로그램 (extension) 상태
- 앱 전용 디바이스 또는 세션 바인딩 (session binding)
유용한 질문은 다음과 같습니다:
Playwright가 볼 수 있는 데이터만으로 로그인 상태를 설명할 수 있는가?
이 스냅샷을 단순한 복사-붙여넣기 메커니즘이 아닌, 검사를 위한 용도로 사용하십시오:
const state = await context.storageState({ indexedDB: true });
console.log({
...
이 로그는 비밀 값(secret values)이 아닌 메타데이터를 기록한다는 점에 유의하십시오.
체크리스트:
- 필요한 **쿠키 도메인 (cookie domains)**이 현재 URL과 일치하는지 확인합니다.
- 필요한 **쿠키 경로 (cookie paths)**가 너무 좁게 설정되어 있지 않은지 확인합니다.
- 필요한 쿠키가 만료되지 않았는지 확인합니다.
Secure쿠키가 HTTPS를 통해 사용되는지 확인합니다.SameSite동작이 리다이렉트 흐름에 적합한지 확인합니다.- 앱이 인증 정보를 그곳에 저장하는 경우 IndexedDB를 확인합니다.
- 새 탭이나 새로고침 시 로그인이 사라진다면 sessionStorage를 고려합니다.
- 인증 값이 CI 로그에 출력되지 않도록 합니다.
검증 기준:
앱이 인증 정보를 어디에 저장하는지 알기 전까지는 이를 쿠키 버그라고 부르지 마십시오.
Step 4: 연결된 세션 내부에서 프록시 확인하기
페이지가 로드되는 것만으로는 충분하지 않습니다.
페이지는 반드시 예상된 **네트워크 정체성 (network identity)**을 통해 로드되어야 합니다.
연결된 동일한 페이지에서 IP 확인을 실행하십시오:
await page.goto("https://api.ipify.org?format=json", {
waitUntil: "domcontentloaded",
});
...
운영 환경(production) 시스템의 경우, 공개 엔드포인트를 자체 내부 IP 에코(echo) 서비스로 교체하십시오.
체크리스트:
- 관찰된 **공용 IP (public IP)**가 예상되는 프록시 그룹과 일치하는지 확인합니다.
- 관찰된 **IP 지역 (IP region)**이 프로필 플랜과 일치하는지 확인합니다.
- 시간대 (Timezone), 로캘 (locale), 그리고 **언어 (language)**가 해당 지역과 일치하는지 확인합니다.
- 프록시가 의도한 계층(layer)에서 설정되었는지 확인합니다.
- **CDP가 연결된 브라우저 (CDP-attached browser)**와 **헤드리스 워커 (headless worker)**가 서로 다른 프록시 경로를 사용하지 않는지 확인합니다.
- 재시도 로직(Retry logic)이 사용자 모르게 다른 프록시로 전환되지 않는지 확인합니다.
검증 표준:
다음 항목들이 일치할 때만 실행이 유효합니다:
- 프로필 ID (profile ID)
- 브라우저 프로필 디렉토리 (browser profile directory)
- CDP 엔드포인트 (CDP endpoint)
- 쿠키 상태 (cookie state)
- 스토리지 상태 (storage state)
- 프록시 경로 (proxy route)
- IP 지역 (IP region)
- 실행 모드 (execution mode)
IP가 잘못되었다면, 셀렉터(selector) 디버깅을 중단하십시오.
단계 5: 헤디드(headed), CDP 연결, 헤드리스(headless) 동작 비교
워크플로우가 수동으로는 작동하지만 자동화에서는 실패할 경우, 환경을 나란히 놓고 비교하십시오.
다음 매트릭스를 사용하십시오:
| 확인 항목 | 헤디드 수동 프로필 | CDP 연결 Playwright | 헤드리스 실행 |
|---|---|---|---|
| 동일한 프로필 경로 | |||
| ... |
목표는 모든 수치를 동일하게 만드는 것이 아닙니다.
목표는 모든 차이점을 설명하는 것입니다.
위험 신호 (Red flags):
- CDP 연결 Playwright의 쿠키가 수동 모드보다 적습니다.
- 헤드리스 (Headless) 모드가 헤디드 모드와 다른 프록시를 사용합니다.
- 동일한 URL이 각 모드에서 다르게 리다이렉트됩니다.
- 프로필 경로가 다르지만 실행 로그에는 나타나지 않습니다.
- 에이전트 재시도 시 연결된 컨텍스트(attached context) 대신 새로운 컨텍스트(fresh context)를 엽니다.
검증 표준:
헤디드 모드의 성공이 헤드리스 모드의 준비 완료를 증명하지 않습니다. CDP 연결이 프로필의 정확성을 증명하지 않습니다.
단계 6: AI 에이전트가 계속 진행하기 전에 중단 조건 추가
이 지점이 **AI 에이전트 디버깅 (AI agent debugging)**이 스크립트 디버깅과 다른 부분입니다.
결정론적(deterministic)인 스크립트는 종종 명확하게 실패를 알립니다.
하지만 에이전트는 적응하여 계속 진행할 수 있습니다.
이는 UI 변동에는 유용하지만, 신원 불일치 (identity mismatch) 측면에서는 위험합니다.
에이전트에게 로그인된 브라우저를 제공하기 전에, 환경 검사를 요구하십시오.
다음의 경우 에이전트를 중단하십시오:
- **쿠키 개수 (cookie count)**가 예상치 못하게 감소합니다.
- 페이지가 로그인 페이지로 리다이렉트(redirect)됩니다.
- **공용 IP (public IP)**가 예상된 프록시와 일치하지 않습니다.
- 재시도(retry) 중에 **IP 지역 (IP region)**이 변경됩니다.
- **시간대 (timezone)**와 프록시 지역이 일치하지 않습니다.
- localStorage 또는 IndexedDB가 비어 있어야 하지 않음에도 비어 있습니다.
- 에이전트가 예상된 **CDP 연결 컨텍스트 (CDP-attached context)**에서 작동하지 않습니다.
- 페이지가 지갑, 결제, 계정 설정, 삭제, 권한 또는 파괴적인 흐름(destructive flows)으로 진입합니다.
- 에이전트가 결과와 프로필 (profile), 프록시 (proxy), 시간 (time), **모드 (mode)**를 연결하는 실행 로그(run log)를 생성할 수 없습니다.
훌륭한 에이전트 실행은 다음 질문에 답할 수 있어야 합니다:
- 어떤 프로필을 사용했는가?
- 어떤 브라우저 컨텍스트 (browser context)에 연결했는가?
- 어떤 프록시 경로 (proxy route)를 사용했는가?
- 페이지가 관찰한 IP는 무엇인가?
- 행동하기 전에 어떤 세션 상태 (session state)를 확인했는가?
- 왜 계속 진행할 수 있었는가?
- 무엇이 나를 중단하게 만들었을 것인가?
에이전트가 이 질문들에 답할 수 없다면, 로그인된 세션에서 동작해서는 안 됩니다.
7단계: 모든 디버깅 시도에 대해 작은 실행 로그 (run log) 사용하기
로그가 화려할 필요는 없습니다.
그저 실행의 정체성(identity)을 캡처하기만 하면 됩니다.
예시 필드:
run_id: 2026-05-30-cdp-auth-debug-001
mode: cdp-attached
cdp_endpoint: http://127.0.0.1:9222
...
이는 다음과 같이 모호한 디버깅 메모를 방지합니다:
로컬에서는 작동했지만 에이전트에서는 실패했습니다.
이 문장만으로는 충분하지 않습니다.
더 나은 메모는 다음과 같습니다:
CDP 연결 (CDP-attached) 실행에서 예상된 프로필과 쿠키 개수를 사용했으나, 관찰된 IP가 예상된 **프록시 지역 (proxy region)**과 일치하지 않아 계정 작업 전에 에이전트가 중단되었습니다.
이것이 디버깅 가능한 형태입니다.
일반적인 원인 및 첫 번째 점검 사항
원인 1: 잘못된 브라우저가 연결됨
증상:
context.cookies()가 예상보다 적은 쿠키를 반환합니다.- 다른 Chrome 창은 로그인되어 있음에도 페이지가 로그아웃되어 있습니다.
- CDP 포트가 이전 브라우저 프로세스에 속해 있습니다.
첫 번째 점검 사항:
- 모든 테스트용 Chrome 프로세스를 종료합니다.
- 하나의 userDataDir을 사용하여 브라우저를 하나만 실행합니다.
- CDP endpoint를 다시 엽니다.
browser.contexts().length를 출력합니다.- 작업을 실행하기 전에 현재 URL과 쿠키 개수를 출력합니다.
원인 2: 잘못된 프로필 디렉토리 (wrong profile directory)
증상:
- 한 브라우저에서는 로그인이 작동하지만, CDP는 깨끗한 세션(clean session)을 확인합니다.
- 앱이 초기 설정(first-time setup)을 요구합니다.
- 쿠키 저장소(cookie jar)가 비어 있습니다.
첫 번째 점검 사항:
- 실행 시 사용된 정확한 userDataDir을 확인합니다.
chrome://version을 수동으로 확인합니다.- 매일 사용하는 기본 Chrome 프로필을 사용하지 마세요.
- 두 개의 브라우저 인스턴스가 동일한 디렉토리를 공유하게 하지 마세요.
원인 3: 인증 정보가 쿠키에만 저장되지 않음 (auth is not only stored in cookies)
증상:
- 쿠키가 존재하지만 앱에서 여전히 로그아웃됩니다.
- 새로고침 시에는 로그인이 유지되지만, 새 탭에서는 유지되지 않습니다.
- OAuth 콜백(callback)은 성공하지만 대시보드가 다시 로그인 페이지로 리다이렉트(redirect)됩니다.
첫 번째 점검 사항:
- localStorage를 검사합니다.
- IndexedDB를 검사합니다.
- sessionStorage를 확인합니다.
- 서비스 워커(service worker) 및 확장 프로그램(extension) 의존성을 확인합니다.
- 쿠키의 도메인(domain), 경로(path),
Secure, 그리고SameSite설정을 확인합니다.
원인 4: 프록시 컨텍스트 이탈 (proxy context drifted)
증상:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기