왜 Verdex는 CDP를 직접 사용하나요?
Source: Dev.to
소개
Verdex는 개발 시점 저작 도구로, DOM 검사와 JavaScript 실행 컨텍스트에 대한 깊고 구체적인 제어가 필요합니다. 반면 Playwright는 크로스‑브라우저 신뢰성을 최적화한 실행 시점 테스트 러너입니다. 이처럼 근본적으로 다른 사용 사례가 서로 다른 아키텍처 선택을 이끌어냅니다.
영감에 대한 주석
Verdex는 Playwright 설계에 큰 빚을 지고 있습니다—접근성‑트리 구현, 격리된 월드 처리, 요소‑생명주기 관리 등을 면밀히 연구했습니다. Verdex는 Playwright의 정교함(ARIA‑준수 접근성 트리, 견고한 프레임 처리, 격리된 실행 컨텍스트)과 동등성을 목표로 하지만, 아키텍처에서는 차별화됩니다: 실행 시점 테스트 신뢰성 대신 저작 시점 선택자 구성을 위한 구조‑탐색 기본 연산(get_ancestors, get_siblings, get_descendants)을 추가합니다.
실행 시점 vs 저작 시점: 다른 문제, 다른 해결책
| 실행 시점 (Playwright) | 저작 시점 (Verdex) | |
|---|---|---|
| 선택자 처리 | 매 동작마다 선택자를 다시 해결 | 여러 쿼리에서 안정적인 참조 유지 |
| 스테일 요소 버그 | 재해결을 통해 방지 | 다단계 DOM 탐색을 가능하게 함 |
| 크로스‑브라우저 일관성 | 필요 | 주요 고려 사항이 아님 |
| 요소 핸들 | 일시적 | 세션 동안 지속적인 매핑 |
Playwright의 로케이터 철학
Playwright의 로케이터는 각 동작 시 자동으로 재해결되어 스테일 요소 버그를 방지합니다:
“Every time a locator is used for an action, an up‑to‑date DOM element is located in the page.” – Playwright docs
저작 시점 분석을 위해 Verdex는 동일한 요소에 대해 순차적인 쿼리를 수행해야 합니다:
// Sequential queries on the same element during authoring
get_ancestors("e3"); // Walk up from this specific element
get_siblings("e3", 2); // Examine the SAME element's siblings
get_descendants("e3"); // Explore the SAME element's children
Playwright의 로케이터는 매번 DOM을 다시 조회하므로 페이지가 변하면 다른 요소가 반환될 수 있습니다. Playwright는 지속적인 참조를 위한 ElementHandle도 제공하지만, 프레임워크는 그 사용을 권장하지 않으며 동작 후 자동으로 폐기해 재해결 철학을 강화합니다.
“잠깐—스테일 요소는 어떻게 되나요?”
Selenium의 악명 높은 StaleElementReferenceException은 런타임에 발생합니다:
element = driver.find_element(By.ID, "submit")
# ... page re‑renders during test execution ...
element.click() # ❌ Stale!
Verdex의 지속적인 참조는 저작 단계, 즉 정적 스냅샷을 분석하는 동안에만 존재합니다:
// Authoring session (stable snapshot)
resolve_container("e3"); // Walk up from element
get_siblings("e3", 2); // Check siblings
extract_anchors("e3", 1); // Find unique content
// Output: Pure Playwright code (auto‑resolving)
getByTestId("product-card")
.filter({ hasText: "iPhone 15 Pro" })
.getByRole("button", { name: "Add to Cart" });
주요 차이점
- 생명주기 단계 – Verdex 참조는 정적 분석 중에만 존재하고, 동적 테스트 실행에서는 존재하지 않습니다.
- 페이지 상호작용 없음 – 탐색 과정에서 재렌더링이 발생하지 않습니다.
- 안전한 출력 – 생성된 테스트 코드는 Playwright의 자동 재해결 로케이터를 사용합니다.
따라서 Verdex는 다단계 구조 탐색을 위한 지속적인 참조를 활용하면서, 최종 테스트 코드는 Playwright의 견고한 런타임 동작을 그대로 누릴 수 있습니다.
CDP 레이어: 아키텍처 선택이 중요한 곳
Playwright와 Verdex 모두 Chrome DevTools Protocol(CDP)을 기반으로 합니다. 격리된 월드를 생성하는 코드는 비슷합니다:
// Playwright with CDP
const client = await page.context().newCDPSession(page);
await client.send('Page.createIsolatedWorld', {
frameId: mainFrameId,
worldName: 'verdex-bridge',
grantUniveralAccess: true
});
// Puppeteer with CDP
const client = await page.createCDPSession();
const { executionContextId } = await client.send('Page.createIsolatedWorld', {
frameId: mainFrameId,
worldName: 'verdex-bridge',
grantUniveralAccess: true
});
복잡성은 여러 작업에 걸쳐 요소를 다룰 때 나타납니다.
임피던스 불일치
Verdex는 분석 단계 전반에 걸쳐 DOM 노드를 추적하는 지속적인 Map을 유지합니다. Playwright + CDP를 사용할 경우 두 객체 모델을 연결해야 합니다:
- Playwright의 자동 관리
ElementHandle(유틸리티 월드) - 수동 관리 CDP
objectId(격리된 월드)
이 둘 사이를 변환하려면 추가 평가 호출과 컨텍스트 전환이 필요하며, 이는 Playwright의 추상화를 우회하면서도 크로스‑브라우저 번들의 비용을 그대로 지불하게 됩니다.
Puppeteer가 자연스럽게 맞는 이유
Puppeteer는 CDP 원시 기능에 직접 작동하므로 추상화 레벨이 하나뿐입니다:
const session = await page.target().createCDPSession();
// Create isolated world
const { executionContextId } = await session.send('Page.createIsolatedWorld', {
frameId: mainFrameId,
worldName: 'verdex-bridge',
grantUniveralAccess: true
});
// Inject bridge code
await session.send('Runtime.evaluate', {
expression: bridgeCode,
contextId: executionContextId
});
// Stable objectId for multi‑step operations
const { result } = await session.send('Runtime.evaluate', {
expression: 'document.querySelector("[data-testid=product]")',
contextId: executionContextId
});
// Use the same objectId across calls
const analysis = await session.send('Runtime.callFunctionOn', {
objectId: result.objectId,
functionDeclaration: 'function() { return window.verdexBridge.fullAnalysis(this); }',
executionContextId,
returnByValue: true
});
const { result: ancestors } = await session.send('Runtime.callFunctionOn', {
functionDeclaration: 'function() { return window.verdexBridge.get_ancestors(this); }',
objectId: result.objectId,
executionContextId,
returnByValue: true
});
임피던스 불일치가 없고, 자동 폐기에 싸우지 않으며, 객체 모델 간 브리징이 필요 없습니다. puppeteer 패키지(~2 MB)는 CDP‑네이티브인 반면, playwright-core(~10 MB)는 Verdex가 전혀 사용하지 않는 크로스‑브라우저 추상화를 포함합니다.