
AI 에이전트가 교차 출처 iframe(Cross-Origin Iframes) 내부를 클릭하게 하는 방법 (chrome-use의 해결 방식)
요약
AI 에이전트가 교차 출처 iframe(Cross-Origin Iframes) 내부의 요소를 제어할 때 발생하는 기술적 한계와 이를 해결하는 chrome-use의 접근 방식을 다룹니다. 동일 출처 정책으로 인한 셀렉터 접근 불가, 스크롤 문제, 좌표 클릭의 부정확성 문제를 분석합니다.
핵심 포인트
- 교차 출처 iframe은 동일 출처 정책으로 인해 CSS 셀렉터 접근이 불가능함
- iframe 내부의 스크롤 컨테이너 제어 문제로 인해 요소가 화면 밖으로 벗어날 수 있음
- chrome-use는 실제 Chrome 브라우저를 직접 제어하여 이러한 자동화 장애물을 극복함
- 단순 픽셀 좌표 기반 클릭은 정밀도가 낮아 결제 등 민감한 작업 시 위험함
💡 원래 제 블로그 blog.leeguoo.com 에 게시되었습니다 — 리버스 엔지니어링(Reverse Engineering), AI 에이전트, 그리고 실제로 배포되는 제품을 만드는 것에 대한 현장 기록입니다.
AI 에이전트를 브라우저에 연결하는 작업은 처음에는 순조롭게 시작됩니다: 페이지를 열고, 콘텐츠를 읽고, 검색창을 채우는 식이죠. 하지만 당신을 정말로 막아서는 것은 **교차 출처 iframe (Cross-Origin Iframes) 내부에 숨겨진 양식(Forms)**입니다. Google Payments 지급 프로필, 결제 컴포넌트, KYC 위젯 등이 그 예입니다. 에이전트는 그 내부의 텍스트를 읽고 값을 채울 수는 있지만, 그 "저장" 버튼을 클릭할 수는 없습니다. 작업은 인지하지만, 완료할 수는 없는 것이죠.
이 글은 우리가 어떻게 그 장애물을 넘어섰는지에 대한 기록입니다. 주인공은 chrome-use입니다. 이는 에이전트를 위한 Rust 기반의 브라우저 자동화 CLI로, Playwright나 헤드리스 모드(Headless mode)를 사용하지 않고 당신이 실제로 로그인되어 있는 Chrome 브라우저를 직접 제어합니다.
교차 출처 iframe이 왜 그렇게 어려운가
일반적인 페이지는 쉽습니다: 접근성 트리(Accessibility Tree)를 캡처하고, 요소 참조(Element references)를 가져와서 클릭하면 됩니다. 하지만 교차 출처 iframe—예를 들어 adsense.google.com 페이지에 payments.google.com iframe이 임베드된 경우—은 세 가지 문제에 동시에 직면하게 됩니다:
- 셀렉터(Selectors)가 접근할 수 없습니다. 동일 출처 정책 (Same-origin policy)에 따라, 외부 문서에서 실행되는 CSS 셀렉터와
eval은 iframe 내부의 DOM에 접근할 수 없습니다. 여기서document.querySelector는 무용지물입니다. - 스크롤이 대상을 놓칩니다. 페이지를 스크롤하고 있다고 생각하겠지만, 실제로 스크롤할 수 있는 것은 iframe 내부의 스크롤 컨테이너입니다. 휠 이벤트(Wheel events)는 외부 문서로 전달되는 반면, 내부는 그대로 멈춰 있습니다. 즉, 대상 행(row)은 화면에 보이지도 않은 채 영원히 "화면 밖"에 머물게 됩니다.
- 좌표를 맹목적으로 클릭해야 하는 상황에 놓입니다. 앞선 두 가지 문제로 인해 결국 "스크린샷 + 픽셀 좌표 추측" 방식으로 돌아가게 되는데, 이는 가장 정밀도가 낮은 방식이며 인접한 필드를 실수로 클릭하기 가장 쉬운 방법입니다. 전역 결제 프로필 (global payment profile) 정보를 수정하는 양식에서 한 번의 잘못된 클릭은 큰 비용을 초래할 수 있습니다.
chrome-use의 기반: 에이전트는 HTML이 아닌 "참조(references)"를 받습니다
해결 방법을 설명하기 전에, 기본적인 설계 방식을 짚어볼 가치가 있습니다. 왜냐하면 이것이 chrome-use와 모델에 가공되지 않은 HTML을 제공하는 진영 사이의 근본적인 차이점이기 때문입니다.
chrome-use는 에이전트에게 페이지 소스를 전달하지 않습니다. 대신, **접근성 트리 스냅샷 (accessibility tree snapshot)**을 캡처하여 각 상호작용 요소에 간결한 참조(reference)를 할당합니다:
- textbox "Email" [ref=e2]
- listbox "Country/region" [ref=e60]
- button "Save" [ref=e41]
에이전트는 이러한 참조를 통해 직접 동작합니다: fill @e2 "...", click @e41. 페이지 하나당 전체 화면의 DOM 노이즈 대신 약 200~400개의 토큰만 소모됩니다. 이 참조 메커니즘은 나중에 iframe을 통해 작업하는 것을 가능하게 만드는 핵심 요소입니다. 스냅샷이 iframe 내부의 노드를 "볼" 수만 있다면, 해당 노드들에 참조를 할당할 수 있기 때문입니다.
세 가지 장애물, 하나씩 해결하기
첫 번째 장애물: 스냅샷이 iframe 내부를 볼 수 있게 만들기.
접근성 트리 (Accessibility Tree)가 교차 출처 iframe (Cross-origin iframes)을 통과하여 해당 노드들을 참조 (Reference)와 함께 포함해야 합니다. 이 문제를 해결하고 나면, snapshot이 다음과 같이 노드들을 직접 나열할 수 있습니다:
- textbox "Phone number (optional)" [ref=e59]
- listbox "Country/region code: Japan (+81)" [ref=e60]
셀렉터 (Selector)가 진입할 수 없는 곳이라도, 참조 (Reference)는 진입할 수 있습니다.
두 번째 장애물: 스크롤이 iframe의 스크롤 컨테이너에 영향을 주도록 만들기.
모든 휠 이벤트 (Wheel event)를 외부 문서 (Outer document)로 보내는 대신, 실제로 스크롤이 필요한 컨테이너를 스크롤합니다. 그러면 하단의 폼 (Form) 행들이 마침내 화면에 나타나며, 해당 노드들의 참조 (Reference)를 사용할 수 있게 됩니다.
세 번째 장애물, 가장 어려운 것: 교차 출처 iframe 내부의 활성화된 제출 버튼이 클릭해도 아무런 반응이 없는 문제.
이 단계는 모든 것이 정상적으로 보이기 때문에 가장 미치게 만드는 부분입니다:
- 실제 키 입력 (Keystrokes)을 통해 번호가 입력되었고,
get value를 통해 값이 입력되었음을 확인했습니다. - "Save" 버튼은 적절한 시점에 활성화됩니다. 유효한 값이 입력되기 전에는 비활성화(Disabled) 상태였다가, 입력을 마치면 나타납니다.
- 그다음
click @e41을 실행하지만—폼은 아무런 반응이 없습니다.find text "Save"를 시도한다고요? 교차 출처 접근 제한 (Cross-origin access)에 의해 차단됩니다. 포커스 (Focus)를 주고 Enter나 Space를 누른다? 여전히 아무 일도 일어나지 않습니다.
겉보기에는 완벽하지만, 모든 것이 잘못되었습니다. 근본 원인은 다음과 같습니다: 교차 출처 iframe 내부의 Material/프레임워크 버튼들은 **합성 클릭 (Synthetic clicks)**을 받아들이지 않습니다. 또한 fill은 프레임워크가 기대하는 input/change 이벤트를 발생시키지 않은 채 입력 값만 변경했을 뿐입니다. 폼은 여전히 "아무것도 변하지 않았다"고 판단하므로, 저장 버튼이 비활성화 상태로 남아있거나 클릭해도 아무런 동작을 하지 않는 것과 다름없는 상태가 됩니다.
해결책은 두 부분으로 나뉩니다: 값 입력 방식이 **실제 키 입력 (Real keystrokes)**으로 전환되어, 모든 문자가 프레임워크가 인식할 수 있는 실제 이벤트를 트리거하도록 합니다. 클릭은 단순히 click()을 때려 넣는 대신, iframe 내부의 콘텐츠 노드에 대해 전체적인 실제 마우스/키보드 활성화 (Real mouse/keyboard activations) 세트를 발생시킵니다.
마무리: 내부 클릭을 통한 성공적인 저장
세 가지 장애물을 모두 통과하면 전체 체인이 작동합니다: 열기(open) → 대상 행으로 스크롤(scroll to the target row) → 스냅샷에서 참조 캡처(capture references from the snapshot) → 실제 키 입력으로 채우기(fill with real keystrokes) → 저장(Save) 누르기. "읽을 수는 있지만 완료할 수는 없다"는 교착 상태가 여기서 끝납니다.
에이전트 브라우저 자동화(agent browser automation)를 구축하는 다른 분들을 위한 몇 가지 값진 교훈
- 접근성 참조(accessibility references)를 우선시하세요. 스크린샷 좌표를 클릭하는 것을 기본값으로 삼지 마세요. 스냅샷이 iframe을 볼 수 있게 되면, 참조(references)는 픽셀을 추측하는 것보다 항상 더 안정적입니다. 스크린샷은 canvas나 WebGL처럼 구조가 정말 없는 경우를 위해서만 남겨두세요.
- 교차 출처 iframe(Cross-origin iframes)은 명확한 경계입니다. 셀렉터(Selectors)와
eval은 거기서 멈춥니다. 도구가 접근성 트리(a11y tree)를 관통할 수 있거나, 아니면 눈을 감고 클릭하는 상황에 처하게 됩니다. - 단순히 채울 수 있는지뿐만 아니라, 제출(submit)할 수 있는지도 테스트하세요. 값이 입력되었다고 해서 프레임워크가 이를 수신했다는 의미는 아닙니다.
fill이 이벤트를 발생시키지 않는 것과 같은 버그는 실제로 저장을 시도할 때만 나타납니다. - 실제 로그인된 브라우저를 사용할 수 있다면, 헤드리스(headless)를 사용하지 마세요. 로그인 상태, 쿠키, 확장 프로그램이 이미 모두 갖춰져 있으며, 자동화 지문(automation fingerprint)도 남지 않습니다. 이것이 바로 chrome-use가 "당신의 Chrome을 직접 구동하는" 방식을 택한 이유이기도 합니다.
사용해보기
curl -fsSL https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.sh | sh
저장소는 github.com/leeguooooo/chrome-use에 있습니다. 저는 사용자의 구독을 활용하고 에이전트를 실제 브라우저/기기에 연결하는 이러한 도구들을 계속해서 만들고 있으며, X @leeguooooo에 업데이트를 게시합니다.
링크
- 🔧 도구: chrome-use on GitHub — 어떤 AI 에이전트에서든 실제 로그인된 Chrome을 제어하세요.
- 📝 더 많은 글: blog.leeguoo.com — 저는 작은 AI 에이전트 도구와 CLI를 만드는 풀스택 개발자 Guo Li (leeguoo)입니다.
- 💬 도움이 되었나요? 레포지토리에 ⭐를 남겨주시거나 여기서 팔로우해 주시면 큰 힘이 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
