
내 에이전트가 동일한 LinkedIn 클릭을 30번 실패했습니다. Camoufox는 첫 시도에 성공했어요.
요약
AI 에이전트가 브라우저 자동화 과정에서 겪는 기술적 한계와 실패 원인을 분석합니다. 프롬프트의 문제가 아닌, 레이턴시, 합성 클릭의 신뢰성 문제, UI 요소 가림 등 브라우저 환경의 구조적 요인을 다룹니다.
핵심 포인트
- AI 에이전트의 브라우저 자동화 실패는 프롬프트보다 환경적 요인이 큼
- CSS 트랜지션과 액션 간의 지연 시간(Latency) 문제
- isTrusted 플래그가 없는 합성 클릭(Synthetic clicks)의 무시 현상
- 스티키 헤더 등 UI 요소에 의한 클릭 가로채기 문제
때로는 AI가 그냥 바보 같은 행동을 합니다. 아무리 프롬프트를 주어도 마찬가지입니다.
동일한 클릭, 30번의 반복
제 에이전트는 동일한 LinkedIn 클릭을 30번 실패했습니다. 무한 재시도 루프에서 동일한 버튼을 동일한 페이지에서 계속 누르는 식이었죠. LinkedIn 프로필은
프론트엔드 디버깅 (Frontend debugging)은 과소평가된 사례입니다. 에이전트는 콘솔 에러 (console errors), 네트워크 워터폴 (network waterfall), 그리고 실시간 페이지 상태 (live page state)를 읽을 수 있습니다. 무언가를 구축하면서 별도의 테스트 하네스 (test harness)를 실행하지 않고도 에이전트가 브라우저에서 자신의 출력을 직접 검증하기를 원할 때 유용합니다. 한 파트너가 오직 스크린샷을 통해서만 소통하는 페어 프로그래밍 (pair programming)이라고 생각하면 됩니다. 대부분의 대화에서는 비효율적이지만, 놀랍게도 코드 리뷰 (code review)에는 괜찮습니다.
이 4가지 사례는 일상적인 브라우저 자동화 워크플로우 (browser automation workflow)에 필요한 대부분을 다룹니다. 동작에 정밀한 타이밍이나 네이티브 이벤트 핸들링 (native event handling)이 요구될 때 예외적인 사례들이 나타납니다.
실패하는 4가지 방식
LinkedIn 사례는 동일한 동작에서 4가지의 뚜렷한 실패 모드 (failure modes)를 생성했습니다. 각각은 다르게 전개됩니다.
클릭이 도달하기 전에 메뉴가 닫힙니다. 스크린샷을 찍는 것과 클릭을 발송하는 것 사이의 오버헤드 (overhead)는 압축할 수 없습니다. 150ms 만에 사라지는 CSS 트랜지션 (CSS transition)의 경우, 그 오버헤드만으로도 충분합니다. 클릭이 도착하기 전에 메뉴가 사라져 버립니다.
합성 클릭 (Synthetic clicks)이 조용히 무시됩니다. LinkedIn의 artdeco 컴포넌트 라이브러리는 실제 하드웨어 입력을 통해 생성되는 종류인 신뢰할 수 있는 포인터 이벤트 (trusted pointer events)를 수신합니다. 프로그램 방식으로 발송된 클릭은 isTrusted 플래그를 가지고 있지 않습니다. 컴포넌트는 응답하지 않습니다. 에러도, 피드백도, 아무것도 없습니다.
요소가 스티키 헤더 (sticky header)에 의해 가려집니다. LinkedIn의 내비게이션 바 (navbar)는 뷰포트 (viewport) 상단에 고정된 상태로 유지됩니다. 만약 오버플로 메뉴 (overflow menu)가 페이지 상단 근처에서 열리면, 내비게이션 바가 클릭을 가로챕니다. Chrome MCP는 이를 감지하기 위한 히트 테스트 (hit-test)를 수행하지 않습니다.
이 각각은 개별적으로 해결 가능합니다. 하지만 동일한 동작에서 이 4가지가 동시에 발생하는 것은 구조적인 벽입니다.
왜 실패하는가 (프롬프트 문제가 아닙니다)
Chrome MCP는 브라우저 상단에서 작동합니다. 이는 확장 프로그램(extension) 형태로 자리 잡으며, 스크린샷을 통해 관찰하고, 사이클당 1개의 액션(action)을 전달한 뒤 대기합니다. 이 모델은 실질적인 장점을 가지고 있습니다. 렌더링 엔진(rendering engine) 내부에 아무것도 설치할 필요가 없고, 기존의 Chrome 세션을 수정 없이 재사용할 수 있으며, 별도의 설정 없이 실행 중인 어떤 Chrome 인스턴스라도 지정할 수 있습니다. 트레이드오프(tradeoff)는 모든 액션이 라운드 트립(round-trip)을 포함하여 시간 민감적인 UI 상태를 놓칠 정도의 지연 시간(latency)을 발생시킨다는 점이며, 전달되는 클릭이 신뢰할 수 있는 이벤트 파이프라인(trusted event pipeline) 내부에서 발생하지 않는다는 점입니다.
최신 JavaScript 프레임워크들은 들어오는 포인터 이벤트(pointer events)의 isTrusted 속성을 확인하며, React, Vue, 그리고 LinkedIn의 artdeco와 같은 컴포넌트 라이브러리들은 모두 이 확인 과정을 거칩니다. Chrome MCP의 클릭은 DOM에 도달하지만, 컴포넌트의 내부 검증(validation)에서 실패합니다. 이 실패는 소리 없이 일어나며, 아무런 에러도 표면화되지 않기 때문에, 해당 검증이 존재한다는 사실조차 알기 위해서는 컴포넌트 소스 코드를 읽어야만 합니다.
저는 특히 타이밍 측면을 해결하기 위해 3주를 보냈습니다: wait_for_element를 사용하고, 재시도 간격(retry gaps)을 늘리고, 스크린샷과 클릭 사이의 지연 값(delay values)을 다르게 설정해 보았습니다. 하지만 메뉴는 계속해서 닫혀버렸습니다. isTrusted 문제는 이와 완전히 별개의 문제였으며, artdeco 소스를 읽고 나서야 비로소 그 사실을 깨달았습니다. 참으로 즐거운 목요일이었죠.
Why CLIs often beat MCP for agent work에서는 이러한 트레이드오프의 토큰 비용(token cost) 측면을 다루고 있지만, 실제로 운영 환경(production)에서 발목을 잡는 것은 신뢰할 수 있는 이벤트(trusted event) 측면입니다.
Chrome MCP의 클릭은 기술적으로는 클릭입니다. 하지만 컴포넌트는 이를 아무도 요청하지 않은 정중한 이메일처럼 취급합니다.
전환 신호: 2~3번의 실패
규칙은 단 한 줄입니다: 동일한 특정 액션에서 2~3번의 실패가 발생하면, 중단하고 도구를 전환하십시오. 실패의 원인이 구조적인 문제라면, 재시도 루프(retry loop)를 추가하는 것은 결과에 아무런 영향을 주지 못합니다.
동일한 제스처(gesture)에 대해 3번째 시도가 실패한 후라면, 당신은 접근 방식을 디버깅하고 있는 것이 아닙니다. 그저 로그 파일의 양만 늘리고 있는 것입니다.
전환을 유발하는 상황들:
- 클릭이 도달하기 전에 메뉴나 오버레이가 닫힘
- 좁은 시간 범위 내에서 2개의 제스처를 체인(chain)으로 연결해야 함 (메뉴 열기, 그 다음 항목 클릭)
- 요소가 클릭에 반응하지 않으며 아무런 에러도 표시하지 않음
- React 또는 Vue 버튼이 폼(form)이 유효해 보임에도 불구하고 계속 비활성화(grayed out) 상태로 유지됨
- 동일한 동작을 헤드리스(headless) 모드에서 수십 번 실행해야 함
저는 이 2~3회의 임계값이 대부분의 표준 웹 UI에 적용된다고 생각합니다. 실패 모드(failure modes)가 다르게 나타나는 완전히 커스텀된 프레임워크(custom frameworks)에도 동일하게 적용되는지는 확신할 수 없습니다.
Camoufox가 바꾸는 것 (그리고 그 대가)
_Camoufox_는 _Playwright_에 의해 구동되며, 봇 탐지(bot detection)에 대비해 강화된 Firefox 브라우저입니다. Chrome MCP가 브라우저 외부에서 작동하는 반면, Playwright는 실제 사용자 입력이 거치는 것과 동일한 경로를 통해 렌더링 엔진(rendering engine) 내부에서 이벤트를 주입합니다.
LinkedIn의 오버플로(overflow) 사례의 경우:
실제 신뢰할 수 있는 이벤트 (Real trusted events). Playwright는 isTrusted: true와 함께 전체 포인터 이벤트 체인(pointer event chain)을 디스패치(dispatch)합니다. 오버플로 메뉴가 열립니다. "Connect" 항목이 클릭을 등록합니다. 첫 번째 프로필 완료.
ARIA 기반 로케이터 (ARIA-based locators). 스크린샷의 픽셀 좌표 대신 page.getByRole('menuitem', { name: 'Connect' })를 사용합니다. 결정론적(deterministic)이고 가독성이 좋으며, LinkedIn이 월요일 유지보수 시간에 패딩(padding)을 2픽셀 조정하더라도 깨지지 않습니다. 모든 팀이 그런 배포를 경험해 봤을 겁니다 😅
동작 간의 왕복(round-trip) 없음. 메뉴를 열고 동일한 스크립트 실행 내에서 항목을 클릭합니다. 두 단계 사이에 스크린샷 사이클이 없습니다. 레이스 컨디션(race condition)도 없습니다.
React 상태 검증 (React state validation). 입력값에 값이 생길 때까지 비활성화 상태로 유지되는 제출(submit) 버튼의 경우: Playwright는 컴포넌트의 상태를 깨우기 위해 input과 change 이벤트를 모두 방출한 다음, 잠금이 해제된 버튼을 클릭합니다. Chrome MCP는 비활성화된 버튼을 클릭합니다. 아무 일도 일어나지 않습니다.
먼저 주의사항을 말씀드립니다. 세션이 전송되지 않습니다. Camoufox는 Chrome의 쿠키를 상속받지 않기 때문에 첫 실행 시 수동 로그인이 필요합니다. 새로운 브라우저 핑거프린트 (Browser Fingerprint)는 LinkedIn의 "새 기기" 인증을 트리거합니다. Playwright 스크립트는 유지보수가 필요한 프로덕션 코드 (Production code)입니다. 그리고 LinkedIn의 속도 제한 (Rate limiting)은 작업의 리듬 (Cadence)이 자동화된 것처럼 보인다면 당신이 어떤 브라우저를 사용하는지는 신경 쓰지 않습니다.
전환에는 실제 설정 비용이 발생합니다. Chrome MCP가 특정 동작에서 이미 2~3번의 실패를 보였을 때 비로소 가치가 있습니다. 그 전에는 그렇지 않습니다. Playwright는 Chrome DevTools Protocol을 기반으로 구축되었으며, 이것이 이벤트 모델 (Event model)이 브라우저 상단에서 작동하는 그 어떤 것보다 실제 사용자 입력에 훨씬 더 가깝게 구현되는 이유 중 하나입니다.
언제 무엇을 사용할 것인가
Chrome MCP vs Camoufox 결정 가이드
Chrome MCP 용도:
- 이미 로그인되어 있는 사이트에서의 빠르고 일회성인 작업
- 안정적인 페이지로부터 콘텐츠 읽기
- 표준 HTML 입력란에서의 양식 채우기 (Form filling)
- 기존 세션에서의 프론트엔드 (Frontend) 문제 디버깅
Camoufox + Playwright 용도:
- 신뢰성이 필요한 반복적인 동작
- 신뢰할 수 있는 이벤트 (Trusted events)가 필요한 상호작용 요소
- 정밀한 타이밍 시퀀스 (메뉴, 드롭다운, 모달)
- 재시도 루프 (Retry loops)를 감당할 수 없는 프로덕션 자동화
전환 시점: 동일한 특정 동작에서 2~3번의 실패가 발생했을 때. 30번이 아닙니다.
30번의 시도 세션은 40분의 시간과 Playwright 스크립트 재작성을 소모했습니다. Camoufox는 첫 번째 프로필을 12초 만에 확보했습니다. 그 후 나머지 29번도 동일한 속도로 실행되었습니다.
신호는 2번째 시도 때 이미 있었습니다. 저는 그것을 읽어내는 데 28번의 시도가 더 필요했을 뿐입니다. 🤦♂️
Sources
- Camoufox: Playwright를 기반으로 구축된, 자동화에 최적화된 (hardened) Firefox 브라우저
- Playwright documentation: 브라우저 자동화 라이브러리
- Chrome DevTools Protocol: 공식 CDP 명세
이 포스트에는 제휴 링크가 포함되어 있을 수 있습니다. 해당 링크를 클릭하시면 저에게 소정의 수수료가 지급될 수 있습니다. 독자님께 추가 비용이 발생하지 않으며, 제가 매일 양질의 글을 계속해서 발행할 수 있도록 도와줍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기