이번에는 페이지 내 요소를 조작해 버튼을 클릭하고, 폼 양식을 완성시키고 제출해보는 등 E2E 테스트 스크립트를 본격적으로 작성해보려고 합니다.

Document Object Model과 XPath

Selenium 홈페이지에 방문하면 아래 스크린샷 같이 보이실 텐데요.

selenium hompage

만약 해야할 테스트 시나리오에서 Selenium WebDriver 부분의 빨간 READ MORE 버튼을 클릭해 보는게 있다고 가정해 봅시다.

Manual testing을 한다면 어떻게 할까요?

  1. 크롬 브라우저를 켠다
  2. URL 검색창에 https://www.selenium.dev/ 를 입력해 페이지에 접속한다
  3. 페이지에 보이는 Selenium WebDriver 섹션에서 READ MORE 텍스트가 적힌 버튼을 클릭한다

매우 간단합니다😀 그럼 자동화를 할 땐 어떻게 스크립트를 작성하게 될까요? 브라우저를 키고 url로 이동하는 것 까지는 저번 글에서 다루어서 이제 할 수 있습니다.

from selenium import webdriver

# 드라이버 세션을 시작합니다.
driver = webdriver.Chrome()
# URL로 이동합니다.
driver.get("https://www.selenium.dev/")
# 버튼 클릭
# ???

이제 여러분들은 컴퓨터에게 페이지에 보이는 Selenium WebDriver 섹션에서 READ MORE 텍스트가 적힌 버튼 을 어떻게 찾아서 클릭하게 할지 고민해야 합니다. 클릭은 나중에 다룰 예정이니 일단 저 버튼이 어디에 있는지 컴퓨터에게 어떻게 알려줄지 중점적으로 고민해 봅시다.

Document Object Model

Document Object Model(이하 DOM)은 HTML 문서의 인터페이스입니다. 웹은 일종의 문서(document) 인데요, 이 문서를 프로그래밍 언어로 조작할 수 있게 구조화 시켜 프로그래밍 언어로 DOM에 접근하고 DOM 내부 요소의 스타일, 내용 등을 바꾸는게 가능합니다.

다시 https://www.selenium.dev/ 에 접속 후 개발자 도구를 열어봅니다. 크롬 브라우저라면 마우스 우클릭 후 '검사' 메뉴를 클릭하거나 Windows에서는 ctrl + shift + i , MacOS에서는 cmd + option + i 단축키를 통해 열 수 있습니다.

selenium hompage with devtools

여기서 "Console" 탭을 클릭하고 아래와 같이 입력해 봅니다.

let paragraphs = document.getElementsByTagName("div");
console.log(paragraphs);

DOM은 위와 같이 프로그래밍 언어가 문서에 접근해 요소를 찾거나 조작할 수 있도록 인터페이스(ex. getElementsByTagName)를 제공합니다.

XPath

이제 컴퓨터한테 DOM 내부에 있는 Selenium WebDriver 섹션에서 READ MORE 텍스트가 적힌 버튼 을 알려주어야 합니다. 이 때 XPath라는 주소 체계를 통해 컴퓨터에게 해당 버튼이 어디에 있는지 알려줄 수 있습니다.

XPath를 가져오는 방법을 알아보도록 하겠습니다. 개발자 도구에서 가장 위에서 가장 왼쪽에 있는 Inspect 버튼을 활성화한 후, 저희가 클릭할 READ MORE 버튼에 마우스를 올려봅니다.

devtools pick element

그러면 개발자 도구에서 현재 보고있는 요소를 알려줍니다. 이제 개발자 도구에서 해당 요소에 마우스 우클릭을 한 후 Copy -> Copy Full XPath 메류를 클릭합니다.

copy full XPath menu

아무데나 붙여넣기를 하면 /html/body/div/main/section[2]/div/div/div[1]/div/div[2]/div/a 와 동일하거나 비슷한 형태가 나오게 됩니다. 이게 바로 저희가 찾던 버튼의 주소입니다.

다시 개발자 도구에서 "Console" 탭을 클릭하고 아래와 같이 입력해 봅니다.

// 복사한 XPath를 붙여넣기해서 진행해보세요.
$x('/html/body/div/main/section[2]/div/div/div[1]/div/div[2]/div/a');

요소가 잘 나오나요?

XPath를 표현하는 방법은 2가지가 있는데요, 한번 몇가지 케이스들을 알아보도록 하겠습니다.

Full XPath

저희가 방금 붙여넣기 했을 때 문서의 최상단(html)부터 해당 요소까지를 나타낸 표현입니다. 예를들면 "대한민국 경기도 OO시 OO구 OO로 OOO호"와 같은 특정 요소를 찾아낼 수 있는 가장 정확한 표현입니다. 다만 이 표현방식은 중간에 div 요소가 더 들어가서 사람이 봤을 때 UI는 변경되지 않았지만, 컴퓨터는 못찾는 경우가 생기게 되어서 바꾸어줘야 하는 경우가 생깁니다.

Shorten XPath

이 방식은 좀 더 유연하고, 찾는 요소가 완전 없어지거나 크게 바뀌지 않는 한 유지할 수 있는 장점이 있습니다. 어떻게 다른지 한번 알아봅시다!

다시 https://www.selenium.dev/ 페이지에서 한번 헤더 영역에 있는 "Download" 버튼을 한번 보도록 합니다.

여기서 아까와 같은 방법으로 "Copy full XPath" 메뉴를 클릭해 확인해 봅니다. 아까와 비슷한 형태로 /html/body/header/nav/div/ul/li[3]/a 라고 나옵니다.

이번에는 "Copy XPath" 메뉴를 클릭해 확인해 봅니다.

//*[@id="main_navbar"]/ul/li[3]/a 라고 나옵니다. 유추해보자면 main_navbar 라는 요소의 하위 어딘가 있는 a 라고 알 수 있을 것 같습니다.

이런식으로 요소의 attribute들을 통해 XPath를 좀 더 고유하고 간결하게 표현할 수 있습니다. 예를 들어 아까의 "대한민국 경기도 OO시 OO구 OO로 OOO호" 라는 주소가 아닌 좀 더 고유한 "스타벅스 OO점" 과 같은 주소같은 것이죠. 만약 제가 찾는 집이 "스타벅스 OO점 바로 왼쪽 건물 2층"이라고 할 수도 있습니다. 몰론 이런 방식도 스타벅스와 같은 기준이 바뀌면 바꾸어주어야 하는 단점이 있지만, 내가 찾는 버튼에 고유한 attribute를 달아주면 잘 바꾸지 않을 것입니다.(웹 개발자에게 id나 다른 attribute를 달아달라고 요청하시면 됩니다!😎)

Shorten XPath에서 //*[@id="abcd"] 는 모든 요소 중 id attribute가 abcd 인 요소전체라는 의미를 가집니다. 그리고 항상 여러개가 나올 수 있습니다.

좀 더 자세히 써보겠습니다. //a[@id="abcd"]a 요소 중 id attribute가 abcd 인 요소 전체라는 의미입니다.

//a[contains(@class, "abcd")] 이렇게도 쓸 수 있습니다. 이는 a 요소 중 class attribute가 abcd 문자를 포함하는 모든 요소라는 의미입니다.

요소의 attribute는 @ 로 표기하고, // 뒤에는 * 또는 a 와 같이 특정 태그를 넣을 수 있습니다.

특별하게 텍스트 값은 다르게 표기합니다. 바로 //p[text()="abcd"] 가 되는데요, 텍스트는 @ 대신 text() 로 사용합니다. 몰론 앞서 말한 contains 도 사용할 수 있습니다(ex. //p[contains(text(), "abcd")]

💡
Shorten XPath는 Full XPath와 달리 하나의 요소임에도 여러개 나올 수 있습니다.

Shorten XPath를 표현하는 방법 중 가장 기본적인 문법을 설명했는데요, 다른 문법에 대해 더 알아보려면 W3schools에서 튜토리얼을 진행하거나 문서를 보시면 됩니다!

READ MORE 버튼 클릭해보기

아까 마저 쓰지 못했던 테스트 스크립트를 완성해 보도록 합시다. VSCode를 실행하고 이전에 만들었던 selenium-web-testing 폴더를 열어줍니다. 하위에 click-basic.py 파일을 만들고, 아래 코드를 작성해 줍니다.

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://www.selenium.dev/")

페이지 내 요소 찾기

이제 우리는 클릭할 요소를 찾아내는 방법을 배웠습니다. 개발자 도구를 이용해 클릭하려 했던 WebDriver 섹션의 READ MORE 버튼의 XPath를 가져와 줍니다. 그리고 아래 코드를 더해줍니다. XPath를 보니 READ MORE는 버튼이 아니라 링크였네요!

# from selenium import webdriver 다음 라인에 작성합니다
from selenium.webdriver.common.by import By

# driver.get() 다음 라인에 작성합니다
# XPath를 가지고 요소를 찾아낸 결과 중 하나를 가져옵니다
element = driver.find_element(By.XPATH, "/html/body/div/main/section[2]/div/div/div[1]/div/div[2]/div/a")

여기서 find_element 함수는 2개의 인자를 받는데요, 첫번째는 뭘 기준으로 찾을 건지, 2번째는 그 기준에 대한 값입니다. 저희는 XPath를 기준으로 찾고, 그 값은 /html/body/div/main/section[2]/div/div/div[1]/div/div[2]/div/a 인 것이죠. 그리고 찾은 결과 요소들 중에서 하나만 갖고 옵니다. 만약 찾은 결과 요소 전체를 다 갖고 오게 하려면 find_elements 라는 함수를 사용할 수 있습니다.

요소를 찾는 기준은 XPath말고도 태그 이름, class 값, id 등이 있는데요, 해당 내용은 Selenium 문서에서 더 찾아보실 수 있습니다.

블로그에서 다루지는 않지만, Selenium 4.0부터는 위의 전통적인 방법 외에 다른 방법으로 요소를 찾을 수 있게 제공하고 있습니다. Selenium 문서에서 확인해 보세요.

요소 클릭하기

이제 element 변수에 저희가 클릭하려 했던 READ_MORE 링크가 담겨있을 것입니다. 한번 클릭해 봅시다. Selenium 문서에 따르면 클릭은 다음과 같이 작성해 볼 수 있을 것 같습니다. 아래 코드를 추가해 주세요!

# element를 클릭합니다
element.click()

간단합니다!

이제 실행해 보도록 하겠습니다. VSCode에서 터미널을 연 후 아래와 같이 명령어를 입력해 주세요.

만약 가상환경을 활성화 하지 않았다면, 파이썬 가상환경을 활성화 합니다.

# for Windows
.venv\Scripts\activate.bat

# for MacOS & Linux
source .venv/bin/activate

스크립트를 실행해 봅니다!

python3 click-basic.py

크롬 브라우저가 잠깐 나왔다가 사라지는 것을 볼 수 있습니다. VSCode 터미널에서 아무 에러가 나지 않았다면 정상적으로 성공한 것입니다.

Code

# click-basic.py
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.selenium.dev/")
element = driver.find_element(By.XPATH, "/html/body/div/main/section[2]/div/div/div[1]/div/div[2]/div/a")
element.click()
driver.quit()

마치며

이번에는 웹 테스트 스크립트를 작성하기 위해 DOM과 XPath에 관해 살펴보고, 요소를 XPath로 찾아 클릭해보는 예제를 작성해보았습니다. 다음 글에서는 본격적으로 테스트 스크립트를 작성해 보도록 하겠습니다.

예제 코드는 https://github.com/dogu-team/selenium-python-tutorial 에서 확인할 수 있습니다.