테스트 프레임워크
7e72ea2 기준.e2e는 웹과 모바일 앱의 사용자 흐름을 검사하는 TypeScript 테스트 프레임워크입니다. agent.act()로 목표를 주면 에이전트가 화면을 읽고 조작합니다. 같은 테스트 안에서 expect(screen.getByRole(...))로 결과를 확인할 수 있습니다.
행동을 유연하게 만드는 일과, 행동의 결과가 맞는지 증명하는 일을 분리한 것이 이 프로젝트의 핵심입니다. AI에게 모든 판정을 맡기지 않아도 되고, 반대로 모든 클릭을 사람이 미리 지정할 필요도 없습니다.
아래는 가입 폼을 가진 학습용 앱을 가정한 예시입니다. docs/examples/quickstart/tests/agent.e2e.ts의 API 형태를 바탕으로, 목표와 확인 문구를 한국어로 바꿨습니다. 앱에 맞는 역할·이름·완료 문구가 있어야 합니다.
import { test } from '@e2e-dev/web';
import { expect } from 'e2e';
test('방문자가 체험 계정을 만든다', async ({ app, agent, screen }) => {
await app.open('/');
await agent.act('이름 {name}, 이메일 {email}로 무료 체험을 신청해 줘', {
params: { name: '김배움', email: 'learner@example.test' },
});
await expect(screen.getByRole('status')).toContainText('체험 시작');
});
app은 앱의 입구, agent는 목표를 수행하는 조작자, screen은 플랫폼 공통 화면 탐색 창구입니다. 웹 엔진의 test를 가져오면 웹 전용 browser fixture도 사용할 수 있습니다. 공통 API만 쓰는 테스트는 e2e의 test로도 작성합니다.
“도서관까지 가 주세요”라고 하면 기사는 상황에 맞춰 길을 고릅니다. 하지만 기사가 “도착했다”고 말한 것만으로 목적지가 맞다고 확정하지 않습니다. 건물의 간판과 주소를 다시 확인해야 합니다.
e2e의 재생 캐시는 확인된 운행 경로를 저장합니다. 다음에는 그 경로를 따라가되, 현재 길과 도착 상태가 맞지 않으면 일반 모드에서 다시 기사에게 판단을 맡깁니다.
README는 “에이전트 단계가 없는 테스트에는 모델이 필요 없다”고 설명합니다. packages/e2e/package.json에서도 ai와 모델 제공자 패키지를 선택적 peer dependency로 선언합니다. 먼저 locator 기반 테스트를 익히고, 변화가 잦은 조작 부분에만 에이전트를 추가하는 학습 순서가 가능합니다.
엔진 계약, 지연된 캐시 확정, 프로세스 격리, 시간 예산, 입력 검증을 함께 읽습니다. 이 개념들은 테스트 도구뿐 아니라 작업 큐, 플러그인 시스템, AI 자동화 서비스에도 그대로 연결됩니다.
2026-10-23 GitHub 기준 스타 11,746, 포크 610이며, 2026-07-22 생성되어 약 3개월 만에 TrendShift Monthly #20에 선정됐습니다. 이 숫자는 관심의 규모를 보여 줍니다. 실제 사용자 수, 성공률, 유지보수 비용까지 설명하는 수치는 아닙니다.
공개된 순위만으로 인기가 오른 원인을 확정할 수는 없습니다. 다만 README의 사용 방식과 소스 구조를 보면, 선택자 유지보수 부담을 줄이려는 시도, 반복 실행의 모델 비용을 줄이는 재생, 웹·모바일 공통 작성 방식이 개발자의 관심을 끌 만한 요소입니다. 다음 비교는 이 설계에 근거한 해석입니다.
| 비교 축 | 기존 방식의 출발점 | e2e의 선택과 대가 |
|---|---|---|
| Playwright Test | 웹의 page·locator로 동작을 명시 | Playwright 위의 별도 실행기. 자연어 목표를 섞지만 설정·API를 다시 배워야 함 |
| Maestro | 모바일 흐름을 YAML 명령으로 기술 | TypeScript와 공통 screen API 사용. 모델을 쓸 때는 호출 비용·판정 변동을 관리해야 함 |
| 매번 모델로 조작 | 실행할 때마다 화면을 해석하고 행동 결정 | 검증된 act 동작은 재생 가능. 화면 변화나 동적 작업에서는 모델에 다시 넘길 수 있음 |
| 플랫폼별 테스트 | 웹·모바일 도구를 따로 구성 | 공통 runner와 assertion을 공유. 각 플랫폼의 지원 기능까지 같아지는 것은 아님 |
비교 근거는 저장소의 docs/migrate/playwright.mdx, docs/migrate/maestro.mdx, packages/web/src/engine.ts입니다. 다른 제품의 전체 기능이나 성능 우열을 평가한 표는 아닙니다.
e2e는 Playwright를 버리는 도구가 아니라, Playwright 기반 웹 엔진과 별도 모바일 엔진을 하나의 테스트 실행 방식으로 묶는 도구입니다.
README의 재생 설명은 조건을 충족한 에이전트 행동 단계에 해당합니다. packages/e2e/src/cache/identity.ts의 TraceCacheKind는 'act'만 허용합니다. agent.assert(), agent.waitFor(), agent.extract()는 현재 화면을 모델로 판단하므로 재생하지 않습니다.
| 상황 | 소스에서 확인되는 동작 |
|---|---|
| 검증된 act 재생 성공 | 그 act의 모델 호출을 건너뛸 수 있음 |
| 그 뒤 agent.assert 실행 | 판정용 모델 호출은 별도로 발생 |
| 일반 모드에서 재생 불일치 | 현재 상태에서 executor로 넘겨 이어서 수행 |
| strict 모드에서 낡은 기록 | REPLAY_STALE로 실패. 기록 없는 새 단계는 여전히 실시간 실행 가능 |
| 새 모델 성능 비교 | 모델 변경만으로 캐시가 비워지지 않으므로 --no-cache 필요 |
재생 캐시는 시험의 정답지를 복사하는 장치가 아니라, 답을 구하는 작업 순서를 다시 사용하는 장치입니다. 현재 결과의 검사는 계속 필요합니다. 판정까지 과거 기록으로 대신하면 틀린 결과를 영원히 통과시킬 수 있기 때문입니다.
packages/*/src의 TypeScript 계열 구현은 85,397줄, packages/*/tests는 95,892줄입니다. 테스트에는 단위·통합·타입 검사가 포함됩니다. 테스트 줄 수가 많다고 정확성이 증명되는 것은 아니지만, 성공 경로 외에 실패·중단·낡은 캐시를 어떻게 명세하는지 읽을 재료가 풍부합니다.
한편 Playwright 마이그레이션 문서는 fullyParallel의 동등 기능과 HTML 리포터가 없다고 표시합니다. 실제 packages/e2e/src/report/builtin.ts의 기본 리포터도 list, json, junit, markdown 네 가지입니다. 익숙한 도구의 기능을 전부 대체한다고 생각하면 학습과 도입 범위를 잘못 잡게 됩니다.
이 저장소의 중심은 상시 운영하는 API 서버나 데이터베이스가 아니라 로컬 또는 CI에서 실행되는 Node.js 프로세스입니다. CLI가 설정과 테스트를 읽고, 워커가 앱·브라우저·기기를 다룹니다. MCP 서버도 이 실행 자원을 연결하는 창구이며, 일반 웹 서비스의 백엔드와 역할이 다릅니다.
| 부품 | 실제 선언 | 역할 · 근거 파일 |
|---|---|---|
| 언어·실행 환경 | TypeScript 7.0.2Node ^22.22.3 || >=24.8.0 | ESM 기반 SDK·CLI. packages/e2e/package.json |
| CLI | commander 15.0.0@clack/prompts 1.8.1 | 명령·옵션 파싱과 초기 설정 질문. 같은 파일 |
| 설정·코드 해석 | oxc-parser 0.154.0oxc-transform 0.154.0 | 테스트·설정 코드를 다루는 도구. 같은 파일 |
| 형식·단언 | zod 4.6.1@vitest/expect 5.0.5 | 값의 구조와 assertion 기반. 같은 파일 |
| 모델 연결 | ai ^7.0.0 선택적 peer개발용 고정값 7.0.107 | AI SDK를 이용한 모델·도구 호출. 같은 파일 |
| MCP | @modelcontextprotocol/server 2.3.1 | 외부 코딩 에이전트가 도구를 호출하는 서버. 같은 파일 |
| 이미지 비교 | fast-png 8.0.0pixelmatch 8.0.0 | 스크린샷 처리·픽셀 차이 비교. 같은 파일 |
e2e 패키지는 0.21.2, 웹 엔진은 0.15.2, 모바일 엔진은 0.12.3입니다. 최신 GitHub 릴리스 표기인 @e2e-dev/testmu@0.0.1을 runner의 버전으로 읽으면 안 됩니다.
| 영역 | 실제 버전 | 무엇을 위한 코드인가 |
|---|---|---|
| 웹 엔진 | playwright-core 1.64.1 | packages/web/package.json. 브라우저 제어용이며 React UI 라이브러리가 아님 |
| 모바일 엔진 | agent-device 0.21.29 | packages/mobile/package.json. 기기 조작·접근성 화면 읽기 |
| 웹 벤치마크 | next 16.4.1react 19.3.1 | apps/web-benchmark/package.json. 어려운 UI를 테스트하는 대상 앱 |
| 모바일 벤치마크 | expo 57.0.22react-native 0.86.2react 19.2.3 | apps/mobile-benchmark/package.json. 네이티브 화면·권한 창 등의 테스트 대상 |
| 문서 사이트 | mint 4.2.909 | docs/package.json. Mintlify 문서 개발·검증 도구 |
따라서 “Next.js와 Expo가 e2e를 구동한다”는 해석은 틀립니다. 이들은 테스트받는 앱의 스택입니다. 프레임워크 자체를 쓸 때 대상 앱이 React나 Expo일 필요는 없습니다. examples/에는 SwiftUI·Compose·Flutter·.NET MAUI 예제도 있습니다.
| 항목 | 선언·설정 | 배울 점 |
|---|---|---|
| 모노레포 | pnpm@12.3.4 | 루트 package.json. workspace는 packages/*, apps/*, docs |
| 정적 검사 | oxlint 1.83.0fallow 3.27.0 | 문법·오류 가능성과 사용하지 않는 코드 검사. 루트 package.json |
| 테스트·릴리스 | vitest 5.0.1@changesets/cli 3.0.3 | 패키지 테스트와 여러 npm 패키지의 변경·배포 관리 |
| 컴파일 | target: ES2023module: NodeNext | tsconfig.base.json. 엄격한 타입 검사와 ESM 경로 처리 |
| CI 호환성 | Node 22, 24, 26, 22.22.3, 24.8.0 | .github/workflows/spec.yml. 최신 계열과 지원 최저점을 함께 검사 |
| 실행 환경 | Linux 4 vCPU · macOS 6 vCPU | Blacksmith runner 라벨 기준. 개발자 PC의 최소 요구량은 아님 |
pnpm-workspace.yaml에는 allowBuilds로 의존성 빌드 허용 여부를 적고, undici 등의 하위 버전을 overrides로 조정합니다. ignoreWorkspaceCycles: true도 있습니다. 웹 엔진은 runner를 peer로 쓰고 runner 개발 테스트는 웹 엔진을 사용하므로, 루트 빌드는 패키지 순서를 명시합니다.
의존성의 이름을 외우기보다, 실행 제품·테스트 대상·개발 도구 중 어디에 쓰이는지 구분하는 것이 기술 스택을 읽는 첫 단계입니다.
사용자가 작성한 e2e.config.ts + tests/*.e2e.ts
|
v
e2e CLI / runner 공개 API
|
설정 해석 · 수집 · 대상 선택
|
buildWorkPlans / runUnits
|
+-----------+------------+
| |
워커 프로세스 A 워커 프로세스 B
한 target에 연결 한 target에 연결
| |
+---- 테스트 attempt ----+
|
app / screen / expect / agent
|
+-------------+----------------+
| |
locator assertion agent.act
| |
| StepTraceSession.begin
| / \
| 재생 가능 기록 없음·일반 모드 불일치
| | |
| replayTrace StepExecutor
| | 기본: AI SDK 도구 루프
+--------------------+--------------------+
|
e2e/engine 공통 계약
/ \
@e2e-dev/web @e2e-dev/mobile
PlaywrightSurface AgentDeviceSurface
| |
브라우저 / BrowserProvider 기기 / DeviceProvider
뒤따른 검증 성공 --> 쓰기 허용 시 attempt 종료 때 캐시 확정
결과·실패 근거 --> JSON / JUnit / Markdown / 터미널
외부 코딩 에이전트 --> MCP stdio --> 세션·동작 도구
위 도식의 중심은 화면 자체가 아니라 화면을 관찰하고 조작하는 공통 계약입니다. 모델과 재생기, locator가 서로 다른 브라우저를 직접 만지는 대신 같은 실행 계층을 통과합니다. 이 덕분에 예산·정책·기록 규칙을 여러 구현에 반복해서 쓰는 부담을 줄입니다.
packages/e2e/src/engine/manifest.ts의 defineEngine()은 이름·버전·spiVersion을 확인하고, 제공된 훅으로 capability 집합을 만듭니다. 일반 객체의 알 수 없는 키는 오류입니다. perform이 있으면 observe와 지원 actions도 있어야 합니다.
// defineEngine()의 실제 검증 중 일부
if (spec.perform !== undefined) {
if (!capabilities.has('observation')) {
throw invalid(name, 'declares perform without observe: action targets are observation refs');
}
if (spec.actions === undefined) {
throw invalid(name, 'declares perform without actions: list the action kinds the surface honors');
}
capabilities.add('actions');
}
즉 “버튼을 누를 수 있다”는 선언은 “지금 관찰한 버튼을 가리킬 수 있다”는 조건에 기대고 있습니다. TypeScript 타입만으로는 JavaScript 설정이나 외부 패키지의 실제 값을 보장하지 못하므로, 설정을 불러올 때 실제 값도 검증합니다.
| 구현 파일·함수 | 공통 계약에 맞추는 작업 |
|---|---|
packages/web/src/engine.tsweb() | PlaywrightSurface를 만들고 observe·locate·perform에 연결. browser fixture 기여 |
packages/mobile/src/engine.tsbuildEngine() | AgentDeviceSurface를 같은 계약에 연결. 기기 풀 크기를 workers 제한에 반영하고 device fixture 기여 |
packages/mobile/src/nodes.tsprojectSnapshot() | 기기의 평평한 접근성 목록을 트리로 복원하고 플랫폼 역할을 공통 역할로 투영 |
packages/web/src/provider.tsBrowserProviderScope | worker 단위로 빌릴지 attempt마다 새 브라우저를 빌릴지 수명 선택 |
e2e/engine이라는 계약을 알고 엔진이 여기에 맞춥니다. capability는 “이 엔진이 실제로 제공하는 기능 목록”입니다. 같은 콘센트에 연결된 기계라고 모든 기능까지 같다고 가정하지 않습니다.packages/e2e/src/agent/step-cache.ts의 StepTraceSession은 캐시 조회·재생·실시간 기록을 맡습니다. stage()는 시작·종료 화면의 차이와 경로를 담은 기록을 준비합니다. 화면·경로 변화가 없거나 안전하게 기록할 수 없으면 저장 후보를 만들지 않습니다.
실시간 act 수행
|
+-- TraceRecorder가 확정된 동작 기록
|
+-- stage(): 종료 경로·등장/사라진 화면 단서 수집
|
+-- 뒤의 검증 단계 통과 여부 기록
|
v
settleStagedTraces(): attempt 종료 시, read-write·strict 해제 기준
|
+-- 뒤에서 검증됨 --> saved / kept
+-- 검증 안 됨 --> unconfirmed / 조건에 따라 기존 항목 삭제
+-- 중단됨 --> 저장·삭제하지 않음
핵심 비교식은 packages/e2e/src/cache/context.ts의 settleStagedTraces()에 있습니다. 현재 기록보다 뒤에 있는 검증 단계가 통과해야 승인됩니다. 테스트가 오류 없이 끝났다는 사실만으로는 충분하지 않습니다.
// 실제 승인 조건. 단계 번호는 시간 순서다.
const confirmed = entry.stepIndex < settlement.lastVerifiedStepIndex;
// 개념 예: act(2) → locator assertion(3)이 통과했다.
// 2 < 3 이므로 act 기록은 확인된 후보가 된다.
packages/e2e/src/cache/identity.ts는 프로젝트·테스트·대상·엔진 이름과 SPI·명령·매개변수·에이전트 문맥 등을 키에 담습니다. 같은 명령을 반복해도 호출 순번으로 구분합니다. 모델 변경과 엔진 패키지 버전 변경은 직접적인 키 변경 사유가 아닙니다. 엔진 이름과 SPI는 키에 들어가며, 실제 화면 적합성은 재생 중 다시 확인합니다.
packages/e2e/src/agent/step-cache.ts의 StepTraceSession은 replayTrace()로 새 관측에서 대상을 다시 찾고, endStateMatches()에서 목표 상태가 재생 전부터 있었는지와 최종 상태가 맞는지를 확인합니다. 일반 모드에서 중간 재생이 어긋나면 진행한 앞부분을 전달하고 현재 화면에서 executor가 이어받습니다.
식당에서 주문서를 적는 일과 손님에게 음식이 맞게 나갔다고 확인하는 일은 다릅니다. 기록은 주문서이고, 뒤따른 검증은 배식 확인입니다. 이 코드는 주문서를 썼다는 이유만으로 다음 주문의 모범 답안으로 채택하지 않습니다.
캐시의 가치는 저장 속도보다, 무엇을 다시 믿어도 되는지 결정하는 승인 규칙에 있습니다. 이 구조는 데이터베이스의 완전한 트랜잭션은 아니지만, 실행 중 준비와 종료 시 확정을 분리하는 사고법을 보여 줍니다.
저장 계층도 학습할 만합니다. packages/e2e/src/cache/store.ts의 FileCacheStore는 항목을 1,048,576바이트로 제한하고 SHA-256 형태의 키만 파일명으로 씁니다. packages/e2e/src/internal/atomic-write.ts의 writeFileAtomic()은 옆 임시 파일에 쓴 뒤 이름을 바꿉니다. 동시 기록은 잠금 없이 마지막 쓰기가 남는 구조이며, 캐시는 복구 가능한 보조 데이터로 취급합니다.
캐시의 기본 모드는 로컬에서 read-write, CI에서는 read-only입니다. 다만 설정으로 명시한 read-write는 CI에서도 유지하며, 사용자 정의 store는 CI의 기본 읽기 전용 전환에서 제외됩니다. strict 모드에서는 설정한 쓰기 모드와 관계없이 항목을 쓰거나 삭제하지 않습니다. packages/e2e/src/config/resolve.ts의 기본값 처리에서, 기본 쓰기 정책과 명시적인 설정을 구분하는 방식을 볼 수 있습니다.
packages/e2e/src/run/units.ts의 buildWorkPlans()는 setup 작업과 일반 파일 작업을 나눕니다. packages/e2e/src/run/scheduler.ts의 runUnits()가 이를 배분하며, 워커 하나는 한 target에 묶여 한 번에 한 작업 단위를 실행합니다. 같은 파일의 테스트는 선언 순서를 유지하고 파일 사이에서 병렬 실행합니다.
워커 상태: starting --> idle --> busy --> idle
|
실패·인프라 문제 등
v
retired --> 프로세스 종료
target별 setup 완료 --> 그 target의 일반 파일 배정
전체 workers 제한 + 엔진의 workers 제한 + 남은 작업 수
packages/e2e/src/run/worker/handle.ts의 ChildProcessRunner는 fork()와 IPC로 워커를 만들고 통신합니다. 종료 판정에는 exit보다 IPC가 비워진 뒤의 close 이벤트를 사용합니다. 워커가 사라지기 직전에 보낸 결과를 놓치지 않으려는 선택입니다.
plannedSlots()는 실행기 제한·엔진 제한·작업 수의 최솟값을 계산합니다. longestFirst()는 이전 실행에서 오래 걸린 파일을 먼저 배정하고 실행 시간 기록이 없는 파일은 그보다 우선 실행합니다. 워커를 늘리는 것 외에 마지막에 긴 파일 하나가 남는 시간을 줄이는 방법도 있음을 보여 줍니다.
재시도는 packages/e2e/src/run/retry.ts의 isRetryEligible(), runWithRetries(), retryVerdict()가 공유합니다. 시간 초과나 test 범주 오류가 대상이며, 다시 성공한 결과는 단순 passed가 아니라 flaky입니다. 오류가 설정 문제인지, 테스트 실패인지에 따라 대응이 달라집니다.
packages/e2e/src/agent/tool-loop.ts의 createToolLoopExecutor()는 도구 실행 루프·종료 판정·반복 감지·호출 수 집계를 묶습니다. 모델의 텍스트를 셸이나 임의 코드로 평가하는 방식이 아니라, 허용된 도구 인자를 해석해 실행 계층에 전달합니다.
packages/e2e/src/agent/secrets.ts의 authorizeSecretFill()은 등록된 비밀인지, 대상이 활성화된 편집 필드인지, password 용도의 비밀이 password 필드로 가는지 확인한 다음에야 값을 꺼냅니다. packages/e2e/src/cache/recorder.ts의 TraceRecorder는 typeSecret의 평문 대신 비밀 이름을 기록합니다.
다만 정책을 만능 격리로 읽으면 안 됩니다. packages/e2e/src/internal/urls.ts의 resolveNavigationUrl()은 http:·https:·정확한 about:blank를 허용하지만 호스트 허용 목록은 없습니다. 비밀 입력 승인에도 출처 제한이 없습니다. 테스트·설정·엔진 코드는 실행한 사용자의 OS 권한으로 작동합니다.
모델이 어떤 도구를 쓰자고 제안해도, 실제 허용 여부는 실행기가 결정합니다. 이 구조를 따라 만들 때는 허용 도구뿐 아니라 네트워크 목적지·기기 권한·테스트 코드의 신뢰 범위까지 별도로 설계해야 합니다.
e2e/ ├── package.json / pnpm-workspace.yaml / pnpm-lock.yaml ├── tsconfig.base.json ├── packages/ │ ├── e2e/ │ │ ├── src/ │ │ │ ├── cli/ · config/ · collect/ │ │ │ ├── run/worker/ │ │ │ ├── engine/ · locator/ · expect/ │ │ │ ├── agent/ · cache/ │ │ │ └── report/ · mcp/ · oauth/ · telemetry/ │ │ ├── schema/ │ │ └── tests/ (unit/ · integration/ · types/ · live/) │ ├── web/ · mobile/ │ ├── kernel/ · lightpanda/ · smol/ │ ├── eas/ · limrun/ · testmu/ │ └── decision/ · github/ ├── apps/ │ ├── testbed/ │ ├── web-benchmark/ │ └── mobile-benchmark/ ├── docs/ (reference/ · migrate/ · integrations/ · examples/) ├── examples/ (with-vite/ · with-next/ · with-expo/ 등) ├── scripts/ ├── skills/ · plugins/ └── .github/workflows/
| 디렉토리 | 역할과 읽는 목적 |
|---|---|
packages/e2e/src | SDK·runner·CLI 본체. 호출을 입력 검증에서 결과 기록까지 추적 |
packages/webpackages/mobile | 공통 계약을 실제 브라우저·기기에 연결. 플랫폼 차이를 흡수하는 위치 |
| 서비스 연동 패키지 | Kernel·TestMu·EAS 등의 공급자를 연결. 핵심 실행기가 공급자 SDK를 직접 알 필요를 줄임 |
packages/decision | 선택 확률 기반 모델용 executor. 도구 루프 구현을 바꿀 수 있는 확장 지점 |
packages/e2e/schema | 리포트·세션·에이전트 판정의 JSON wire 형식. 저장·전달되는 데이터 계약 |
apps/testbed | 빌드된 패키지를 실제 사용자처럼 소비하는 검증 앱. runner 동작 재현에 적합 |
| 두 benchmark 앱 | iframe·canvas·네이티브 알림 등 어려운 화면의 회귀 검사. 일반 사용법 예제와 구분 |
examples | 기술별 독립 사용자 프로젝트. pnpm workspace에는 포함되지 않음 |
docs · skills | 사람과 코딩 에이전트가 읽는 사용 지침. 문서 예제의 타입 검사도 수행 |
scripts · .github | 배포·버전·문서 정합성·설치 검증과 CI 자동화 |
처음부터 모든 디렉토리를 읽기보다 docs/examples/quickstart/ → packages/web/src/engine.ts → packages/e2e/src/engine/manifest.ts → packages/e2e/src/agent/step-cache.ts 순서로 내려가면 사용법에서 계약과 구현으로 자연스럽게 연결됩니다.
처음 보는 모노레포에서는 “배포되는 코드”와 “그 코드를 사용하는 검증 앱”부터 가르면 읽어야 할 범위가 크게 줄어듭니다.
tsconfig.base.json은 strict 외에도 두 가지 중요한 옵션을 켭니다. exactOptionalPropertyTypes는 “키가 없음”과 “키는 있고 값이 undefined임”을 구분합니다. noUncheckedIndexedAccess는 배열·사전 조회 결과가 없을 수 있음을 드러냅니다.
// 학습용 축약 예시
type Options = { timeout?: number };
const absent: Options = {};
const present: Options = { timeout: 5000 };
// const wrong: Options = { timeout: undefined }; // 엄격 옵션에서는 오류
const table: Record<string, number> = { web: 1 };
const slots = table['mobile'];
if (slots !== undefined) console.log(slots + 1);
작은 실습: 선택적 설정 키를 세 개 만들고, 누락·undefined·잘못된 문자열을 각각 넣어 보세요. 타입 오류와 defineEngine() 같은 실행 중 오류가 서로 다른 입력 경로를 지킨다는 점을 설명해 봅니다.
| 기술 | 실제 파일·식별자 | 배울 것과 작은 실습 |
|---|---|---|
| 접근성 모델 | packages/mobile/src/nodes.tsROLE_MAP, projectSnapshot() | text-field를 textbox로 통일하는 변환. 가짜 iOS·Android 입력 세 개를 공통 역할로 매핑 |
| 선언형 API | packages/e2e/src/locator/expression.tspackages/e2e/src/locator/engine.ts | 요소를 찾는 요청을 데이터로 표현. 버튼 이름을 담은 객체를 두 종류의 가짜 화면에 적용 |
| 비동기 취소 | packages/e2e/src/expect/poll.tspollMatcher() | 개별 assertion과 attempt 마감의 최솟값 사용. 느린 읽기를 중단했을 때 다음 단계가 오염되지 않는지 설계 |
| 프로세스 통신 | packages/e2e/src/run/worker/handle.tsChildProcessRunner | 메시지와 수명 관리. 요청·시작·결과·종료 네 메시지의 순서를 종이에 그리기 |
| 캐시 식별 기준 | packages/e2e/src/cache/identity.tsnormalizeInstruction() | 줄바꿈·유니코드 정규화와 문맥 키. 공백·명령·매개변수를 바꾸며 어떤 항목이 달라져야 할지 예측 |
| 파일 일관성 | packages/e2e/src/internal/atomic-write.tswriteFileAtomic() | 임시 파일과 rename. 쓰기 도중 중단되어도 기존 파일 또는 새 파일 중 하나가 남아야 하는 이유 설명 |
| 스키마 검사 | packages/e2e/tests/unit/schema-fixtures.test.tspackages/e2e/schema/ | 올바른 예와 틀린 예를 같이 유지. 필수 필드 하나를 빠뜨린 리포트가 왜 거절돼야 하는지 정리 |
| MCP 입출력 | packages/e2e/src/mcp/server.tsserveMcp() | 표준 출력은 프로토콜, 진단은 별도 경로. 로그 한 줄이 JSON 통신에 섞이면 생기는 문제 재현 구상 |
코드 흐름을 읽을 때는 함수 하나의 구현과 그 함수를 검증하는 테스트 한 개를 짝지으면 이해 부담이 줄어듭니다.
packages/e2e/src/expect/poll.ts의 pollMatcher()는 읽기 함수를 다시 호출하고 조건을 검사합니다. 개별 마감과 attempt 마감 중 빠른 쪽에서 멈추며, 취소 신호도 함께 받습니다. 읽기 자체가 끝나지 않는 경우에도 시간 제한을 적용합니다.
주의할 세부사항도 있습니다. docs/cache.mdx는 expect.poll()과 일반 값 assertion을 캐시 승인용 검증으로 세지 않는다고 설명합니다. 화면 locator assertion은 packages/e2e/src/expect/async.ts에서 verifies: true로 기록됩니다. “assertion이라는 이름이면 모두 같은 부수 효과가 있다”는 가정을 버리는 좋은 사례입니다.
같은 성공·실패를 표현하는 API라도, 실행 기록에서 맡는 역할은 다를 수 있으므로 호출부와 기록부를 함께 읽어야 합니다.
packages/e2e/tests/unit/retry.test.ts에서 재시도 뒤 성공을 무엇이라 부르는지 찾습니다.packages/e2e/tests/unit/step-cache.test.ts에서 저장되는 경우와 저장되지 않는 경우를 나눕니다.packages/e2e/tests/integration/trace-cache-replay.test.ts에서 실제 화면과 재생을 연결하는 검사를 읽습니다.packages/e2e/tests/types/sdk-types.ts에서 @ts-expect-error로 금지하는 API 조합을 봅니다.이 네 파일은 각각 상태 정책, 캐시 정책, 통합 동작, 공개 타입 계약을 보여 줍니다. 하나의 큰 테스트로 전부 확인하려 하기보다 실패 원인을 어느 층에서 빨리 발견할 수 있는지가 좋은 테스트 분할의 기준입니다.
| 사용 범위 | 필요한 환경 | 근거·주의점 |
|---|---|---|
| SDK·CLI | Node ^22.22.3 || >=24.8.0 | 각 핵심 패키지의 engines.node. 단순히 “Node 22 이상”이라고 줄이면 범위가 달라짐 |
| 저장소 개발 | pnpm 12.3.4 | 루트 packageManager. 일반 사용 프로젝트의 패키지 관리자와 구분 |
| 로컬 웹 테스트 | 브라우저 바이너리와 OS 의존 라이브러리 | playwright-core 패키지와 브라우저 실행 파일은 별개. CI도 Chromium 설치 단계를 둠 |
| 로컬 iOS | macOS·Xcode·시뮬레이터·대상 앱 | .github/workflows/mobile.yml은 macOS에서 xcrun simctl 등으로 환경 확인 |
| 로컬 Android | Android SDK·에뮬레이터·가상화 환경 | CI는 KVM, API 36, x86_64, google_apis, pixel_7 사용 |
| 에이전트 단계 | 지원 모델 연결과 필요한 인증·네트워크 | API·구독·로컬 모델 등의 선택지는 README 기준. 기본 모델은 자동으로 정해지지 않음 |
| 원격 기기·브라우저 | 해당 서비스 계정·토큰·접속 환경 | 로컬 기기 부담을 옮길 수 있으나 공급자 비용과 지원 기능을 따로 확인 |
Node 범위의 ^22.22.3은 22 계열의 해당 패치 이상을 뜻하며, 뒤의 >=24.8.0과 합쳐집니다. 범위에 들어간다는 것과 CI에서 모든 버전을 시험했다는 것은 다릅니다. CI에서 명시적으로 검사하는 조합은 3절 표의 다섯 항목입니다.
핵심 설정 파일과 CI에는 PC용 공식 최소 RAM·디스크 용량이 명시되어 있지 않습니다. 아래 값은 단일 워커로 배우기 위한 보수적인 계획치이며, 프로젝트가 보증하는 최소 사양이나 실측 결과가 아닙니다.
| 학습 시나리오 | 시작 계획치 | 자원이 늘어나는 이유 |
|---|---|---|
| 소스 읽기·웹 1개 | 현대적 4코어급 CPU·RAM 8GB부터 검토 | 편집기·앱 서버·브라우저를 함께 띄움. 큰 앱이면 여유 메모리 추가 |
| 모바일 1개 | RAM 16GB 이상을 우선 검토 | IDE·빌드 도구·시뮬레이터가 동시에 메모리를 사용 |
| 웹·모바일 병렬 | RAM 32GB급 또는 원격 자원 검토 | 워커마다 브라우저·기기와 앱 자원이 늘어남. 먼저 1개로 안정성 확인 |
| GPU | 원격 모델 사용 시 별도 학습용 GPU 불필요 | 로컬 모델을 선택하면 그 모델의 메모리·GPU 요구량이 추가됨 |
| 디스크 | 브라우저·SDK·앱 빌드·영상 보관 공간 확보 | 고정 GB 수보다 보관 정책과 설치할 플랫폼 수가 중요 |
packages/e2e/vitest.config.ts는 실제 브라우저·앱·워커 프로세스를 쓰는 통합 테스트를 maxWorkers: 3으로 제한합니다. CPU가 밀리면 올바른 기능도 시간 초과처럼 보일 수 있기 때문입니다. 내 학습 환경에서는 workers: 1부터 시작하는 편이 원인을 구분하기 쉽습니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
timeout / launchTimeout | 120,000 / 60,000ms | 테스트 한 시도와 엔진 초기화·시도 준비에 쓰는 서로 다른 예산 |
actionTimeout / assertionTimeout | 30,000 / 5,000ms | 동작과 조건 확인을 같은 길이로 기다리지 않음 |
cleanupTimeout | 30,000ms | 각 afterEach 훅·fixture 해제·엔진 정리에 주는 시간 예산 |
retries | 로컬 0 · CI 1 | 기본 재시도 횟수. 명시 설정·CLI가 우선 |
workers | 로컬 가용 병렬도의 절반 내림, 최소 1 · CI 1 | 엔진·작업 수에 의해 실제 배정이 더 줄 수 있음 |
근거는 packages/e2e/src/config/resolve.ts입니다. 느린 환경에서 모든 timeout을 크게 늘리기 전에 어떤 단계가 밀리는지 확인해야 합니다. 앱 준비 실패, assertion 불일치, 모델 응답 지연은 서로 다른 문제입니다.
아래 과제는 본인 소유의 학습용 앱과 별도 테스트 프로젝트를 전제로 합니다. 예시의 http://localhost:3000은 앱의 실제 주소에 맞추고, 외부 서비스에 가입·결제하는 시나리오는 로컬 모형으로 대체합니다. 코드는 커밋 7e72ea2의 API 형태를 기준으로 합니다.
준비: 이름 입력란, 제출 버튼, role="status" 결과 영역을 가진 작은 웹 앱을 준비합니다. 테스트용 의존성은 e2e@0.21.2와 @e2e-dev/web@0.15.2 조합을 기준으로 맞춥니다.
구현: 아래 설정에서 시작하고, locator로 입력과 제출을 수행합니다. 모델 설정 없이 실행되는지 확인합니다.
// e2e.config.ts
import type { E2EConfig } from 'e2e';
import { web } from '@e2e-dev/web';
export default {
targets: [{ name: 'web', engine: web(), app: { url: 'http://localhost:3000' } }],
workers: 1,
} satisfies E2EConfig;
// tests/greeting.e2e.ts
import { test } from '@e2e-dev/web';
import { expect } from 'e2e';
test('이름을 제출하면 인사한다', async ({ app, screen }) => {
await app.open('/');
await screen.getByRole('textbox', { name: '이름' }).fill('김배움');
await screen.getByRole('button', { name: '제출' }).click();
await expect(screen.getByRole('status')).toContainText('김배움');
});
# 의존성과 Chromium이 준비된 학습 프로젝트에서
npx e2e run tests/greeting.e2e.ts --workers 1 --reporter list,json
완료 기준: 올바른 결과에서는 통과하고, 앱이 다른 이름을 표시하도록 바꾸면 실패해야 합니다. 버튼 이름을 바꿨을 때와 결과 문구를 바꿨을 때의 오류를 비교해 보세요.
준비: 공식 quickstart를 따라 모델과 인증을 설정합니다. 과제 1의 입력·클릭 부분을 agent.act()로 바꾸되 마지막 locator assertion은 남깁니다. 캐시는 read-write 모드로 설정합니다.
실험: 첫 실행과 두 번째 실행의 step.cache.mode, step.cache.write, 모델 호출 수를 비교합니다. 그 뒤 locator assertion을 지우고 캐시를 비운 상태에서 다시 기록합니다. 저장이 승인되지 않는 이유를 4절의 비교식으로 설명합니다.
// 모델·캐시 설정을 마친 테스트 본문의 일부
await agent.act('이름 {name}을 입력하고 제출해 줘', {
params: { name: '김배움' },
});
await expect(screen.getByRole('status')).toContainText('김배움');
완료 기준: “모델이 성공이라고 말함”, “뒤의 확인이 통과함”, “기록이 저장됨”을 세 칸으로 나눈 관찰표를 만듭니다. 이어서 agent.assert()도 추가해 재생된 act와 실시간 판정의 호출을 구분합니다.
읽을 곳: packages/e2e/src/collect/select.ts의 shardItems()·applyShard(), packages/e2e/src/run/runner.ts의 validateRunOptions()입니다.
실험: 단일 target·agent 조합에서 일반 테스트 다섯 개와 serial 그룹 하나를 세 shard로 나눈다고 가정합니다. serial 그룹을 하나의 항목으로 센 뒤, 각 shard의 구간을 계산합니다. 그룹 안 테스트 수가 많으면 실행 시간까지 균등해지는지도 생각해 보세요.
// applyShard()의 실제 구간 계산
const size = Math.floor(items.length / shard.total);
const remainder = items.length % shard.total;
const from = (shard.index - 1) * size + Math.min(shard.index - 1, remainder);
const to = from + size + (shard.index - 1 < remainder ? 1 : 0);
완료 기준: 항목 여섯 개라면 세 구간이 각각 두 항목이고, 한 그룹이 갈라지지 않음을 표시합니다. index: 0, index: 4, total: 3, 소수·NaN 입력을 왜 거절해야 하는지 적습니다. 최신 커밋의 주제가 바로 SDK의 숫자 shard 검증입니다.
구현: 독립 실험 프로젝트에서 defineEngine()을 이용해 버튼 하나의 observe 결과를 돌려주는 모형을 설계합니다. 처음에는 관찰만 제공하고, 다음에는 perform과 actions를 함께 추가합니다. 구체적인 노드 형식은 packages/e2e/src/engine/contract.ts와 공식 엔진 작성 문서를 따릅니다.
실패 사례: observe 없는 perform, 오타 키, 지원하지 않는 SPI 버전, 예약 fixture 이름을 각각 넣었을 때 어느 단계에서 거절돼야 하는지 표로 만듭니다.
완료 기준: 브라우저 코드 없이도 계약 검증의 의미를 설명하고, 잘못된 선언이 테스트 실행 전 INVALID_CONFIG로 막혀야 함을 확인합니다. 구현을 바꾸는 것과 계약을 바꾸는 것의 차이도 적습니다.
읽을 곳: packages/e2e/src/run/retry.ts와 packages/e2e/tests/unit/retry.test.ts. 실제 앱 없이 가짜 attempt 결과의 배열만 사용해 같은 판정 규칙을 구현합니다.
입력 사례: 첫 시도 성공, test 오류 후 성공, 시간 초과 후 성공, 설정 오류, 첫 실패 뒤 재시도 도중 취소, 자체 skip을 비교합니다. 종료 상태와 시도 횟수를 함께 기록합니다.
완료 기준: 재시도로 성공한 결과가 flaky이고, 설정·인프라 오류를 무조건 반복하면 안 되는 이유를 설명합니다. 이후 scheduler가 실패한 워커를 교체하는 문제와 테스트를 재시도하는 문제를 별개로 그려 보세요.
실습의 완료 기준은 초록색 통과 표시 하나가 아니라, 의도적으로 틀리게 만들었을 때 예상한 층에서 실패하는지까지 확인하는 것입니다.
주당 3~5시간을 가정한 학습 계획입니다. TypeScript·Promise가 처음이면 1주차를 늘리고, 모바일 환경 준비가 어렵다면 웹과 가짜 화면만으로 5주차까지 진행할 수 있습니다.
| 주차 | 읽을 개념·소스 | 손에 남길 결과물 |
|---|---|---|
| 1주차 | TypeScript의 객체·union·async/await. tsconfig.base.json, quickstart 예제 | 모델 없는 테스트 한 개와 “어떤 조건에서 실패하는가” 설명 |
| 2주차 | 접근성·role·locator·대기. packages/e2e/src/locator/expression.ts, packages/e2e/src/expect/async.ts, packages/e2e/src/expect/poll.ts | 고정 sleep을 조건 대기로 바꾼 예제와 시간 제한 표 |
| 3주차 | Adapter·capability·수명. packages/e2e/src/engine/manifest.ts, 두 엔진의 engine.ts | 웹·모바일 훅 대응표와 최소 가짜 엔진 설계 |
| 4주차 | 캐시 키·후조건·지연 확정. packages/e2e/src/cache/identity.ts, packages/e2e/src/agent/step-cache.ts, packages/e2e/src/cache/context.ts | 첫 기록·재생·불일치·검증 없음의 네 실행 흐름도 |
| 5주차 | 프로세스·IPC·재시도·샤딩. packages/e2e/src/run/units.ts, packages/e2e/src/run/scheduler.ts, packages/e2e/src/run/retry.ts | 워커 상태 그림과 shard 구간 계산표 |
| 6주차 | 도구 정책·MCP·CI 계약. packages/e2e/src/agent/secrets.ts, packages/e2e/src/mcp/server.ts, .github/workflows/spec.yml | 입력→권한 확인→실행→증거의 경계 지도와 작은 설계 발표 자료 |
주마다 소스 한 곳과 테스트 한 곳만 골라도 됩니다. 중요한 것은 읽는 파일 수보다 입력·출력·실패 조건을 본인 말로 다시 설명하는가입니다.
projectSnapshot()과 golden snapshot 테스트를 읽고, OS별 접근성 차이를 같은 역할로 변환하는 연습을 합니다.“왜 assert를 재생하지 않는가?”, “왜 워커 종료를 close로 기다리는가?”, “왜 설정도 실행 중 검증하는가?”를 자료 없이 설명해 보세요. 막히는 질문이 다음에 읽을 소스를 정해 줍니다.
좋은 딥다이브의 끝은 저장소의 모든 파일을 아는 상태가 아니라, 작은 설계 선택의 이유를 다른 문제에 적용할 수 있는 상태입니다.
| 용어 | 쉬운 설명 |
|---|---|
| E2E | 사용자가 앱에 들어와 조작하고 결과를 얻는 흐름을 이어서 검사하는 테스트 |
| assertion | 실행 결과가 반드시 만족해야 하는 합격 조건 |
| locator | 역할·이름·텍스트 등으로 현재 화면의 요소를 찾는 요청 |
| fixture | 테스트 실행에 준비해 전달하는 도구·자원. 여기서는 app·screen·browser·device 등 |
| target | 엔진과 앱 정보를 묶은 테스트 실행 대상. 웹·iOS·Android 등을 별도로 선언 |
| attempt | 테스트를 한 번 시도한 실행. 재시도가 생기면 attempt도 추가됨 |
| worker | 테스트 작업을 받아 실행하는 일꾼. 기본 실행 경로는 별도 자식 프로세스 |
| shard | 전체 선택 항목 중 일부를 다른 기계·CI 작업에 나누어 맡기는 조각 |
| serial group | 순서와 상태 의존성을 지키며 실행하는 그룹. 샤딩 때 target·agent 조합별로 한 항목으로 취급 |
| flaky | 처음에는 실패했지만 재시도에서 통과한 결과. 안정적인 통과와 구분하는 신호 |
| engine · SPI | 실제 화면을 다루는 구현과, 그 구현이 runner에 맞춰야 하는 연결 계약 |
| capability | 엔진이 실제 지원한다고 선언한 기능. 지원하지 않는 기능을 미리 구분하는 목록 |
| semantic tree | 화면을 픽셀 대신 역할·이름·값·상태·부모 자식 관계로 표현한 구조 |
| observation · revision | 현재 화면을 읽은 결과와 그 관측을 구분하는 표식. 오래된 요소 참조를 구분하는 데 사용 |
| StepExecutor | 목표를 받아 실행 단계를 수행하는 교체 가능한 구현. 기본 AI 루프나 다른 실행기를 연결 |
| tool calling | 모델이 허용된 함수 이름과 구조화된 인자를 제안하면 프로그램이 검증 후 수행하는 방식 |
| replay · hand-off | 과거 동작을 현재 화면에서 재현하는 것과, 맞지 않는 지점에서 실시간 실행기로 넘기는 것 |
| postcondition | 동작이 끝난 뒤 보여야 하는 상태. 이 캐시는 종료 경로와 화면 변화 단서를 함께 확인 |
| cache key | 어느 문맥에서 만든 기록인지 구분하는 식별자. 명령만 같다고 기록을 공유하지 않게 함 |
| unique() | 실행마다 달라지는 입력을 재생 시 현재 값으로 바꿀 수 있게 표시하는 API |
| strict cache | 존재하던 기록이 낡았으면 실시간 보정으로 숨기지 않고 실패시키는 실행 모드 |
| atomic write | 임시 파일을 완성한 뒤 교체하여 반쯤 쓴 결과가 정식 파일로 보이지 않게 하는 쓰기 방식 |
| redaction | 모델 입력·기록 등에 민감한 문자열이 드러나지 않도록 가리거나 치환하는 처리 |
| deadline · cancellation | 언제까지 실행할지 정하는 마감과, 그 전에라도 멈추라는 신호 |
| MCP · stdio | AI 도구 연결 규약과 표준 입출력을 통한 통신. 프로토콜 출력과 일반 로그를 섞지 않아야 함 |
| monorepo · workspace | 여러 패키지를 한 저장소에 두고 의존성·빌드·검사를 함께 관리하는 구성 |
| golden snapshot | 기대하는 변환 결과를 파일로 고정하고, 코드 변경 후 차이를 검토하는 테스트 기준 |
| telemetry | 명령·엔진·오류 분류 같은 사용 정보를 수집하는 기능. 비활성화 설정 제공 |
용어를 다시 읽을 때는 “그 용어가 어떤 실패를 막는가”를 붙여 보세요. revision은 낡은 관측, timeout은 끝나지 않는 대기, postcondition은 동작만 했을 뿐 결과는 틀린 재생, 스키마는 잘못된 데이터 형식을 다루는 장치입니다.
| 항목 | 확인 내용 |
|---|---|
| 프로젝트·설명 | tester-army/e2e · 웹·모바일 앱을 위한 차세대 E2E 테스트 프레임워크 |
| 트렌드 | TrendShift Monthly #20 선정 |
| 스타 / 포크 | 11,746 / 610 · 2026-10-23 GitHub 기준 |
| 주 언어 / 생성일 | TypeScript / 2026-07-22 |
| 최근 push | 2026-10-22 |
| 최신 릴리스 | @e2e-dev/testmu@0.0.1 · 2026-10-16 · GitHub 릴리스 기준 |
| 핵심 패키지 버전 | e2e 0.21.2 · @e2e-dev/web 0.15.2 · @e2e-dev/mobile 0.12.3 · 커밋의 package.json 기준 |
| 최근 커밋 | 7e72ea2 · 2026-10-22 · SDK list의 숫자 shard 검증 수정 (#996) |
| 전체 추적 파일 | 2,025개 · 커밋에 포함된 파일 목록 기준 |
| TypeScript 계열 | .ts 193,378줄 / 1,069개 · .tsx 13,370줄 / 78개 · 합계 206,748줄 |
| 패키지 구현 / 테스트 | packages/*/src 85,397줄 / 396개 · packages/*/tests 95,892줄 / 452개 · .ts·.tsx 기준 |
| JavaScript 계열 | .js·.jsx·.mjs·.cjs 합계 4,603줄 / 21개 |
| 기타 코드 | Swift 407줄 · Kotlin·Kotlin 설정 756줄 · Dart 261줄 · C# 105줄 · CSS 1,605줄 |
| 문서 규모 | .md·.mdx 합계 39,455줄 / 192개 |
| 라이선스 / 개발 단계 | Apache-2.0 / README는 1.0 이전의 활발한 개발 단계로 설명 |
줄 수는 커밋 7e72ea2의 Git 추적 파일을 확장자별로 세었으며 빈 줄·주석·테스트·예제를 포함합니다. 마지막 개행이 없는 줄도 한 줄로 셉니다. JSON·잠금 파일·이미지·바이너리는 위 코드 합계에서 제외했습니다. 테스트 비중은 코드 커버리지를 뜻하지 않습니다.
① API가 아직 바뀔 수 있습니다. README는 1.0 이전이며 minor 릴리스 사이에도 API·설정이 바뀔 수 있다고 밝힙니다. 본문의 버전과 현재 문서의 버전을 맞추고, 패키지별 peer 범위를 확인해야 합니다.
② 재생이 모든 판단과 비용을 없애지는 않습니다. act만 캐시 대상이며 에이전트 판정은 실시간입니다. 플랫폼·화면 변화·동적 값·기록 불가 작업에서는 재생이 멈추거나 기록되지 않을 수 있습니다. 이 글의 구조 설명은 특정 성공률이나 실행 속도를 보증하지 않습니다.
③ 지원 범위가 기존 도구와 같지 않습니다. 기본 HTML 리포터는 없고, 플랫폼별 engine capability도 다릅니다. 브라우저 엔진의 지원 기능을 모바일에도 그대로 가정하거나 Playwright 설정을 그대로 옮길 수 없습니다.
④ 정책은 OS 샌드박스가 아닙니다. 테스트·설정·엔진은 사용자 권한으로 실행됩니다. 탐색과 비밀 입력에는 호스트 허용 목록이 없으며, 일반 텍스트·화면 정보는 선택한 모델 제공자에게 전달될 수 있습니다. 신뢰할 수 없는 코드는 외부 격리 환경에서 다뤄야 합니다.
⑤ 환경과 기록 보관을 따로 관리해야 합니다. 기기·브라우저 설치, 모델 인증·요금, 영상·추적 파일 보관은 운영 조건에 포함됩니다. CLI의 사용 정보 수집은 E2E_TELEMETRY_DISABLED=1 또는 npx e2e telemetry disable로 끌 수 있습니다. 이를 꺼도 모델 호출 자체가 로컬 처리로 바뀌지는 않습니다.