WebDriver의 내부 동작 메커니즘

Selenium에서 WebDriver 인스턴스를 생성할 때, 시스템은 대상 브라우저에 맞는 네이티브 컴포넌트가 존재하고 올바른 버전인지 확인합니다. 이후 해당 브라우저 내부에 웹 서비스(즉, 브라우저 제조사에서 제공하는 드라이버: 예컨대 IEDriver, ChromeDriver)를 시작하며, 이는 WebDriver Wire Protocol를 구현한 프로세스입니다. 이 프로토콜은 브라우저를 열고 닫으며, 최대화/최소화, 요소 탐색, 클릭, 파일 업로드 등 거의 모든 작업을 수행할 수 있도록 설계되어 있습니다.

이 프로토콜은 플랫폼 간 호환성을 위해 표준화된 방식으로 작동하며, FirefoxDriver나 ChromeDriver 모두 동일한 원칙을 따릅니다. 예를 들어, FirefoxDriver는 기본적으로 http://localhost:7055에서, ChromeDriver는 일반적으로 http://localhost:46350 같은 포트에서 웹 서비스를 실행합니다. 사용자가 호출하는 모든 WebDriver API는 CommandExecutor를 통해 이 포트의 웹 서비스에 HTTP 요청을 전송하게 되며, 요청 본문에는 WebDriver Wire Protocol에 따라 정의된 JSON 형식의 명령 데이터가 포함됩니다.

  1. 브라우저가 시작되면서 remote server 역할을 수행합니다.
  2. 클라이언트는 CommandExecutor를 통해 RESTful HTTP 요청을 remote server의 특정 포트로 전달합니다 (프로토콜: WebDriver Wire Protocol).
  3. remote server는 브라우저의 네이티브 컴포넌트(예: chromedriver.exe, IEDriver.dll)를 이용해 명령을 실제 브라우저 동작으로 변환합니다.

이러한 기능의 핵심은 브라우저 자체가 WebDriver의 일관된 인터페이스를 구현하고 있기 때문입니다. 이 덕분에 다양한 언어(자바, 파이썬, 루비 등)로 작성된 테스트 스크립트도 일관된 방법으로 브라우저를 제어할 수 있습니다. 주요 브라우저인 크롬, 파이어폭스, IE, 오페라 등은 모두 이러한 인터페이스를 지원하여 자동화가 가능합니다.

간단히 설명하면, 클라이언트 코드는 직접 브라우저와 통신할 수 없기 때문에, Web Service를 중개자로 삼습니다. 이 중개자는 클라이언트의 명령을 브라우저가 이해할 수 있는 형태(예: 자바스크립트)로 번역합니다. 테스트 스크립트는 세션을 생성하고, 그 세션을 통해 HTTP 요청을 보내며, 브라우저는 결과를 다시 Web Service로 반환합니다. 이 서비스는 결과를 JSON 형식으로 가공하여 클라이언트에게 전달합니다. 이를 통해 작업 성공 여부를 판단할 수 있습니다.

크롬 드라이버 공식 문서에 따르면, ChromeDriver는 세 가지 구성 요소로 나뉩니다:

  • 브라우저 자체 (chrome)
  • Selenium이 제공하는 언어 바인딩 (driver)
  • Chromium 프로젝트에서 다운로드되는 실행 파일 (chromedriver), 이는 브라우저와 드라이버 사이의 연결 다리 역할을 합니다.

이 실행 파일은 단순히 "드라이버"가 아니라 server로서의 기능을 수행함을 의미합니다.

실제 예시로 살펴보면:

WebDriver driver = new FirefoxDriver();
driver.get("http://google.com");

위 코드에서 get 호출 시, 클라이언트는 다음과 같은 요청을 보냅니다:

POST /session/285b12e4-2b8a-4fe6-90e1-c35cba245956/url
{
  "url": "http://google.com"
}

이 요청은 localhost:포트/hub/session/{sessionId}/url 경로로 전달되며, 브라우저에게 지정된 페이지로 이동하도록 지시합니다. 성공 시 응답은 아래와 같습니다:

{
  "name": "get",
  "sessionId": "285b12e4-2b8a-4fe6-90e1-c35cba245956",
  "status": 0,
  "value": ""
}

각 필드 의미는 다음과 같습니다:

  • name: 실행된 메서드 이름 (여기서는 get)
  • sessionId: 현재 세션 식별자
  • status: 상태 코드 (0은 성공)
  • value: 반환 값 (페이지 이동 시 빈 문자열)

요소 찾기 요청의 경우 응답은 다음과 같이 나타납니다:

{
  "name": "findElement",
  "sessionId": "285b12e4-2b8a-4fe6-90e1-c35cba245956",
  "status": 0,
  "value": {
    "ELEMENT": "{2192893e-f260-44c4-bdf6-7aad3c919739}"
  }
}

여기서 ELEMENT 값은 발견된 요소의 고유 ID이며, 이후 click 등의 요청에 사용될 수 있습니다.

아래 그림은 각 브라우저 드라이버가 네이티브 컴포넌트에 의존한다는 점을 보여줍니다. 예를 들어, 파이어폭스는 webdriver.xpi라는 확장 프로그램이 필요하고, IE는 dll 파일을 사용하여 명령을 네이티브 호출로 변환합니다. 또한 전체 아키텍처는 RESTful Web Service 기반임을 강조합니다.

WebDriver Architecture Diagram WebDriver의 아키텍처 구성 요소 및 통신 흐름

WebDriver Wire Protocol의 구체적인 동작을 알고 싶다면, Selenium 공식 문서를 참조하세요. 소스 코드에서는 HttpCommandExecutor 클래스가 명령 문자열을 적절한 URL로 매핑하는 Map<String, CommandInfo>를 관리합니다. 이는 REST 원칙에 따라 각 작업이 고유한 URI로 표현됨을 의미합니다. 예시로 일부 매핑은 다음과 같습니다:

.put(NEW_SESSION, post("/session"))
.put(QUIT, delete("/session/:sessionId"))
.put(GET, post("/session/:sessionId/url"))
.put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))
.put(FIND_ELEMENT, post("/session/:sessionId/element"))

모든 요청 경로는 /session/:sessionId로 시작하며, 각 세션마다 고유한 식별자가 부여되어 병행 처리 시 충돌이 발생하지 않습니다. 예를 들어, findElement는 /session/:sessionId/element로 전송되며, 요청 본문에는 검색 조건(예: ID, CSS 선택자, XPath)과 값이 포함됩니다. 응답은 역시 JSON 형식으로 반환되며, 요소의 텍스트, 태그명, 클래스명, 선택자 정보 등을 포함합니다.

응답을 처리하는 코드는 다음과 같습니다:

try {
  response = new JsonToBeanConverter().convert(Response.class, responseAsText);
} catch (ClassCastException e) {
  if (responseAsText != null && "".equals(responseAsText)) {
    return null;
  }
  throw new WebDriverException("Cannot convert text to response: " + responseAsText, e);
}

이처럼 WebDriver는 클라이언트와 브라우저 사이의 중재자로서, 표준화된 프로토콜을 통해 다양한 환경에서 일관된 자동화를 가능하게 합니다.

더 깊은 이해를 원한다면, Selenium Architecture 문서를 참고하세요.

태그: selenium webdriver ChromeDriver RESTful API JSON

10월 3일 06:00에 게시됨