AI 에이전트가 버튼을 인식하는 방식에 대한 연구
요약
본 글은 AI 에이전트가 웹 페이지의 버튼을 인식하는 두 가지 방식을 비교 분석합니다. 이미지 기반 방식과 텍스트 설명(DOM 구조) 기반 방식으로 나뉘며, 후자가 더 정확하고 효율적임을 보여줍니다. 특히, 단순한 'generic' 요소와 명시적인 'button' 태그 간의 정보 차이가 에이전트의 추론 능력에 큰 영향을 미친다는 점을 발견했습니다.
핵심 포인트
- 텍스트 설명 기반 방식은 이미지 인식보다 비용 효율적이고 정확합니다.
- 에이전트는 단순히 시각적 요소(generic)만으로는 버튼임을 '추측'해야 합니다.
- 명시적인 태그(button)는 에이전트에게 확실한 사실(fact)을 제공하여 성능을 높입니다.
AI 에이전트가 내 버튼들을 어떻게 볼까 알아내다
앱의 내용을 전화를 통해 누군가에게 설명한다고 상상해 보세요. 스크린샷을 보낼 수 없습니다. 단지 페이지에 있는 것을 읽어줄 뿐입니다. “체크아웃이라고 적힌 제목이 있어요. 이메일 입력란이 있습니다. ‘주문하기’라고 쓰인 버튼이 있어요.”
AI 에이전트가 여러분의 앱을 사용할 때 받는 정보가 대략 이런 식입니다.
정확히 어떤 설명이 나오는지 알고 싶어서, 저는 아홉 개의 다른 버튼을 가진 페이지를 만들고 각 버튼에 대해 에이전트가 무엇을 받는지 출력해 보았습니다. 그중 일부는 저를 놀라게 했습니다.

먼저, 간단한 분류부터
브라우저 에이전트는 두 가지 종류가 있습니다.
어떤 것은 이미지를 봅니다. 스크린샷을 찍고 버튼이 나타난 곳을 클릭합니다. Claude 컴퓨터 사용(computer use)과 OpenAI의 컴퓨터 사용 에이전트가 이 방식을 따릅니다.
어떤 것은 설명을 읽습니다. 픽셀을 전혀 보지 못합니다. 페이지에 있는 것들에 대한 텍스트 목록, 즉 각 항목의 유형(type), 레이블(label), 현재 상태(state)를 받습니다. 그런 다음 그 목록의 항목들로 행동합니다. browser-use, Stagehand, Microsoft의 Playwright MCP 및 Vercel의 Agent Browser가 모두 이 방식을 따릅니다.
두 번째 그룹이 빠르게 성장하고 있는데, 이유는 지루할 정도로 명확합니다. 텍스트 설명은 스크린샷보다 훨씬 작기 때문입니다. 클릭당 비용이 저렴하고, 무엇을 클릭했는지에 대해 더 정확하기 때문입니다. 50단계의 작업에서는 이 차이가 누적됩니다.
이 글은 그 설명이 무엇으로 구성되어 있는지에 관한 것입니다. 저는 Playwright 1.56과 Chromium 141을 사용하여 이를 생성했습니다.
발견 1: 여러분의 div는 보이지 않는 것이 아닙니다. 추측일 뿐입니다.
저는 <div onClick>가 목록에서 완전히 누락될 것이라고 예상했습니다. 하지만 그렇지 않았습니다. 결과는 다음과 같았습니다:
generic [ref=e2] [cursor=pointer]: Place order
에이전트는 그것을 볼 수 있고, 클릭할 수도 있습니다. 심지어 마우스 커서가 포인터로 변한다는 것까지 알고 있어서 CSS가 약간 새어나오기도 합니다.
그것이 모르는 것은 '무엇인지(what the thing is)'입니다. 'generic'은 '어떤 상자(some box)'를 의미합니다. 따라서 에이전트는 안에 'Place order'라는 글자가 있는 상자와 포인터 커서만 보고, 이것이 아마도 버튼일 것이라고 추론해야 합니다.
실제 버튼과 비교해 봅시다:
button "Place order" [ref=e3] [cursor=pointer]
이것은 명확하게 말해줍니다. 이것은 버튼이며, 'Place order'라고 불립니다. 추론할 필요가 없습니다.
이것이 전체적인 차이입니다: 하나는 사실(fact)이고, 다른 하나는 추측(guess)입니다. 추측은 보통 틀리게 됩니다. 바로 이 점 때문에 에이전트가 실패했을 때 디버깅하기가 매우 고통스럽습니다.
스타일링상의 이유로 실제 <button> 태그를 사용할 수 없다면 좋은 소식이 있습니다: div에 role="button"과 tabindex="0"을 추가하면 동일한 결과를 얻을 수 있습니다. 이 우회책(escape hatch)은 작동합니다. 단지 실제로 적용해야 합니다.
발견 2: 아이콘 버튼이 아무것도 알 수 없는 기능을 하는 버튼일 수 있다
저는 이것을 믿을 수 없어서 테스트를 되돌아가 재실행했습니다.
button [ref=e5]:
img
에이전트는 버튼이 있다는 것은 알고 있습니다. 하지만 그 버튼이 무엇을 하는지는 전혀 모릅니다. '대략적인 아이디어'가 아닙니다—이름(name)이 비어 있습니다.
이것은 div보다 더 나쁩니다. 적어도 div는 텍스트를 가지고 있었습니다. 쓰레기통 아이콘은 사용자에게는 명확하지만, 설명(description)에는 문자 그대로 아무것도 없습니다.
하나의 속성으로 해결할 수 있습니다:
<button aria-label="Delete item" onClick={remove}>
<TrashIcon />
</button>
이제 이것은 button "Delete item"을 읽습니다.
제가 걸린 작은 함정: 아이콘이 SVG가 아니라 이모지인 경우, 그 이모지가 이름이 됩니다. 테스트는 괜찮아 보이고 실제 사용자들도 여전히 막힙니다.
발견 3: 일반 div에 적용된 ARIA 속성은 사라진다
저는 div에 aria-disabled="true"를 적용하여 에이전트가 비활성화된 것을 볼 것이라고 예상했습니다. 실제로 받은 것은 다음과 같습니다:
generic [ref=e13] [cursor=pointer]: Submit
어디에도 '비활성화(disabled)'라는 언급이 없습니다. 비교를 위한 실제 버튼은 다음과 같습니다:
button "Submit" [disabled] [ref=e14]
이유는 이렇습니다. ARIA 상태는 무언가에 부착되어야 합니다. 일반 div에는 역할(role)이 없기 때문에 'disabled'를 담을 것이 없고, 사라져 버립니다.
즉, 보이는 버튼이 비활성화 상태이고, 스타일링된 버튼이 비활성화 상태이며, aria-disabled 속성이 붙어 있더라도, 에이전트에게는 그저 정상적으로 클릭 가능한 상자일 뿐입니다. 에이전트는 그것을 클릭할 것입니다.
해결책은 더 많은 ARIA를 추가하는 것이 아닙니다. 애초에 역할(role)을 가진 요소를 사용하는 것입니다.
아홉 가지 모두 나란히 배치하기
이것이 지금 왜 중요한가
접근성(Accessibility)에 대한 논쟁은 보통 두 가지 방식으로 이루어집니다. '해야 할 올바른 일'이라는 관점과, 많은 곳에서 법적으로 요구된다는 관점입니다. 둘 다 사실입니다. 하지만 어느 쪽도 마감 기한을 맞춘 적이 없습니다. 'A11y 통과'라는 것이 보드에 올라갔다가 잘리는 경우가 많습니다.
여기에 세 번째 이유가 있습니다. 그리고 이 이유가 스프린트 계획(sprint planning)에서 살아남는 핵심입니다: 시맨틱 HTML(semantic HTML)이 당신의 제품이 자동화에게 제공하는 인터페이스로 변하고 있기 때문입니다.
자동화된 테스트. 사용자 대신 무언가를 수행하는 에이전트들. API를 호출하는 대신 UI를 구동하는 모든 도구들이 여기에 해당합니다. 점점 더 많은 것이 바로 이 설명대로 실행됩니다. 만약 당신의 컴포넌트가 스타일링된 div라면, 그러한 흐름은 충돌하지 않습니다—그것들은 추측하고, 가끔 틀리며, 아무도 버그를 재현할 수 없습니다.
자신의 앱에서 직접 시도해 보기 (2분)
이 내용을 하나의 파일로 패키징했기 때문에 자신만의 컴포넌트가 어떻게 보이는지 확인할 수 있습니다.
1단계 — 폴더를 만들고 Playwright 설치하기
mkdir agent-snapshot && cd agent-snapshot
npm init -y
npm i playwright
...
마지막 줄은 브라우저(약 150MB)를 다운로드합니다. 이것은 단 한 번만 발생합니다. (Node 18+ 필요.)
2단계 — 스크립트 가져오기
curl -O https://gist.githubusercontent.com/kakumanu-gayatri/eefa6cc43e83460b612ae678bcaeacd1/raw/agent-snapshot.js
Windows PowerShell의 경우:
curl.exe -O https://gist.githubusercontent.com/kakumanu-gayatri/eefa6cc43e83460b612ae678bcaeacd1/raw/agent-snapshot.js
3단계 — 데모 실행하기
node agent-snapshot.js
이것은 이 기사의 동일한 아홉 개의 버튼을 실행하므로, 실제 어디에 연결하기 전에 직접 출력을 확인할 수 있습니다.
4단계 — 앱에 포인팅하기
개발 서버를 시작한 다음 다음 명령어를 입력합니다:
node agent-snapshot.js http://localhost:3000
결과 읽기
출력에서 세 가지 사항을 확인하세요:
generic (원래 "버튼"이라고 의도했던 부분). 해당 요소에 역할(role)이 없습니다. 에이전트가 추측하고 있는 것입니다.
button [ref=e5] (따옴표 안에 아무것도 없는 경우). 이름이 없는 버튼입니다. 에이전트는 클릭할 수 있다는 것은 알지만, 무슨 일이 일어날지는 전혀 모릅니다.
누락된 [expanded] 또는 [disabled]. 해당 상태가 CSS 클래스에 존재하며, 아무것도 읽을 수 없습니다.
만약 ERR_CONNECTION_REFUSED 오류를 받았다면, 개발 서버가 아직 실행되고 있지 않은 것입니다.
솔직한 참고 사항: _snapshotForAI()는 내부 Playwright API이므로 버전별로 변경될 수 있습니다. 이 스크립트는 해당 기능이 없을 경우 공개된 ariaSnapshot()으로 대체됩니다. 저는 여기에서 모든 것을 Playwright 1.56.0 및 Chromium 141을 기준으로 측정했습니다.
제 테스트를 통해 얻은 또 다른 주의사항: 공개된 ariaSnapshot()과 Playwright MCP가 실제로 에이전트에게 보내는 내부 형식은 정확히 일치하지 않습니다. 공개 형식에서는 제 div가 일반 텍스트로 표시되었지만, 에이전트용 형식에서는 커서와 함께 generic으로 표시되었습니다. 특정 에이전트를 디버깅하는 경우, 해당 에이전트가 실제로 사용하는 형식을 확인하고 — 내부 API는 버전별로 변경될 수 있다는 점을 유념하세요.
요약
<button> 태그는 에이전트에게 자신이 무엇인지 알려줍니다. 스타일링된 div는 추측하게 만듭니다.
대부분의 경우 이 추측은 맞습니다. 나머지 경우에는 "에이전트가 버튼을 찾을 수 없었다"라는 버그 보고서를 받게 되는데, 이는 눈앞에 분명히 있는 버튼에 대한 내용일 때가 많습니다.
테스트 페이지, 스크립트 및 원시 출력은 여기에 있습니다: Agent Snapshot Gist. 직접 자신의 앱에서 실행하여 어떤 결과가 나오는지 확인해 보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기