Dev Browser 스킬
스크립트 실행 간에도 페이지 상태를 유지하는 브라우저 자동화 도구입니다. 작업을 점진적으로 완료하기 위해 작고 집중된 스크립트를 작성하세요. 워크플로의 일부를 검증했고 반복 작업이 있다면, 반복 작업을 단일 실행으로 처리하는 스크립트를 작성할 수 있습니다.
접근 방식 선택
- 로컬/소스 공개 사이트: 셀렉터를 직접 작성하려면 먼저 소스 코드를 읽으세요.
- 알 수 없는 페이지 레이아웃:
getAISnapshot()을 사용하여 요소를 발견하고selectSnapshotRef()로 상호작용하세요. - 시각적 피드백: 스크린샷을 찍어 사용자가 보는 화면을 확인하세요.
설정
두 가지 모드가 있습니다. 어떤 모드를 사용할지 불확실하면 사용자에게 물어보세요.
독립 실행형 모드 (기본값)
새로운 자동화 세션을 위해 Chromium 브라우저를 새로 실행합니다.
./skills/dev-browser/server.sh &
사용자가 요청하면 --headless 플래그를 추가하세요. 스크립트를 실행하기 전에 Ready 메시지가 표시될 때까지 기다리세요.
확장 모드
사용자의 기존 Chrome 브라우저에 연결합니다. 다음과 같은 경우에 사용하세요:
- 사용자가 이미 사이트에 로그인되어 있고, 로컬 개발 환경이 아닌 인증된 환경에서 작업을 수행하길 원할 때.
- 사용자가 확장 프로그램 사용을 요청했을 때.
중요: 핵심 흐름은 동일합니다. 사용자의 브라우저 내에서 이름이 있는 페이지를 생성합니다.
릴레이 서버 시작:
cd skills/dev-browser && npm i && npm run start-extension &
콘솔에 Waiting for extension to connect... 다음에 Extension connected가 표시될 때까지 기다리세요. 클라이언트가 연결되었고 브라우저를 제어할 준비가 되었음을 알 수 있습니다.
워크플로:
- 일반 모드와 마찬가지로 스크립트에서
client.page("이름")을 호출하여 새 페이지를 만들거나 기존 페이지에 연결합니다. - 자동화가 사용자의 실제 브라우저 세션에서 실행됩니다.
확장 프로그램이 아직 연결되지 않았다면, 사용자에게 확장 프로그램을 실행하고 활성화하도록 알리세요. 다운로드 링크: https://github.com/SawyerHood/dev-browser/releases
스크립트 작성
모든 스크립트는
skills/dev-browser/디렉토리에서 실행하세요.@/import 별칭을 사용하려면 이 디렉토리의 설정이 필요합니다.
히어독(heredoc)을 사용하여 스크립트를 인라인으로 실행하세요:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect, waitForPageLoad } from "@/client.js";
const client = await connect();
// 사용자 정의 뷰포트 크기로 페이지 생성 (선택 사항)
const page = await client.page("example", { viewport: { width: 1920, height: 1080 } });
await page.goto("https://example.com");
await waitForPageLoad(page);
console.log({ title: await page.title(), url: page.url() });
await client.disconnect();
EOF
스크립트를 tmp/ 파일에 저장하는 것은 재사용이 필요하거나, 스크립트가 복잡하거나, 사용자가 명시적으로 요청한 경우에만 하세요.
핵심 원칙
- 작은 스크립트: 각 스크립트는 한 가지 작업(이동, 클릭, 입력, 확인)만 수행합니다.
- 상태 평가: 마지막에 상태를 로그/반환하여 다음 단계를 결정합니다.
- 설명적인 페이지 이름:
"main"대신"checkout","login"을 사용하세요. - 연결 해제로 종료:
await client.disconnect()- 페이지는 서버에 유지됩니다. - evaluate에서는 일반 JS:
page.evaluate()는 브라우저에서 실행됩니다. TypeScript 구문을 사용하지 마세요.
워크플로 루프
복잡한 작업의 경우 다음 패턴을 따르세요:
- 스크립트 작성: 한 가지 작업을 수행하는 스크립트를 작성합니다.
- 실행: 스크립트를 실행하고 출력을 관찰합니다.
- 평가: 잘 동작했나요? 현재 상태는 무엇인가요?
- 결정: 작업이 완료되었나요? 아니면 다른 스크립트가 필요한가요?
- 반복: 작업이 끝날 때까지 반복합니다.
브라우저 컨텍스트에서 TypeScript 사용 금지
page.evaluate()에 전달된 코드는 브라우저에서 실행되며, 브라우저는 TypeScript를 이해하지 못합니다:
// ✅ 올바름: 일반 JavaScript
const text = await page.evaluate(() => {
return document.body.innerText;
});
// ❌ 잘못됨: TypeScript 구문은 런타임에 실패합니다
const text = await page.evaluate(() => {
const el: HTMLElement = document.body; // 타입 어노테이션은 브라우저에서 오류 발생!
return el.innerText;
});
데이터 스크래핑
대규모 데이터셋을 스크래핑할 때는 DOM을 스크롤하는 대신 네트워크 요청을 가로채서 재생하세요. 전체 가이드는 references/scraping.md를 참조하세요. 요청 캡처, 스키마 발견, 페이지네이션된 API 재생에 대한 내용을 다룹니다.
클라이언트 API
const client = await connect();
// 이름이 있는 페이지를 가져오거나 생성합니다 (뷰포트는 새 페이지에만 적용됨)
const page = await client.page("name");
const pageWithSize = await client.page("name", { viewport: { width: 1920, height: 1080 } });
const pages = await client.list(); // 모든 페이지 이름 나열
await client.close("name"); // 페이지 닫기
await client.disconnect(); // 연결 해제 (페이지는 유지됨)
// ARIA 스냅샷 메서드
const snapshot = await client.getAISnapshot("name"); // 접근성 트리 가져오기
const element = await client.selectSnapshotRef("name", "e5"); // ref로 요소 가져오기
page 객체는 표준 Playwright Page입니다.
대기
import { waitForPageLoad } from "@/client.js";
await waitForPageLoad(page); // 탐색 후
await page.waitForSelector(".results"); // 특정 요소용
await page.waitForURL("**/success"); // 특정 URL용
페이지 상태 검사
스크린샷
await page.screenshot({ path: "tmp/screenshot.png" });
await page.screenshot({ path: "tmp/full.png", fullPage: true });
ARIA 스냅샷 (요소 발견)
getAISnapshot()을 사용하여 페이지 요소를 발견하세요. YAML 형식의 접근성 트리를 반환합니다:
- banner:
- link "Hacker News" [ref=e1]
- navigation:
- link "new" [ref=e2]
- main:
- list:
- listitem:
- link "Article Title" [ref=e8]
- link "328 comments" [ref=e9]
- contentinfo:
- textbox [ref=e10]
- /placeholder: "Search"
ref 해석:
[ref=eN]- 상호작용을 위한 요소 참조 (보이는, 클릭 가능한 요소만)[checked],[disabled],[expanded]- 요소 상태[level=N]- 제목 수준/url:,/placeholder:- 요소 속성
ref와 상호작용:
const snapshot = await client.getAISnapshot("hackernews");
console.log(snapshot); // 필요한 ref 찾기
const element = await client.selectSnapshotRef("hackernews", "e2");
await element.click();
오류 복구
실패 후에도 페이지 상태는 유지됩니다. 다음과 같이 디버그하세요:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("hackernews");
await page.screenshot({ path: "tmp/debug.png" });
console.log({
url: page.url(),
title: await page.title(),
bodyText: await page.textContent("body").then((t) => t?.slice(0, 200)),
});
await client.disconnect();
EOF