셀레니움에서 동적 XPATH 활용하기

Soyean·2023년 12월 1일
0

ETC

목록 보기
10/10

셀레니움의 locator 중 주요 선호되는 locator는 id, class name, xpath가 있습니다.
이 중 오늘은 xpath에 대해 방법에 대해 알아보려고 합니다 🍀

XPATH 유형

xpath의 경우 상대 경로, 절대 경로로 나뉠 수 있습니다.
절대 경로는 동적인 페이지의 경우 변경될 수 있고 수정이 편리하지 않으며 직관적으로 알아볼 수 없다는 단점들이 존재하여 상대 경로가 우선적으로 사용되어야 합니다.

  • 절대 XPath : 가장 상위 노드부터 표현
  • 상대 XPath : 중간 노드부터 표현

velog 홈페이지트렌딩 문구에 대한 xpath를 구해보겠습니다.
개발자 도구 > 네트워크 탭 > 해당 영역 클릭 후 xpath 복사 / 전체 xpath 복사 시 아래처럼 xpath를 구할 수 있습니다.
이때, 전체 xpath 복사한 값이 /로 시작하는 절대 xpath이고 xpath 복사한 값이 //로 시작하는 동적 xpath 값 입니다.

▸ 전체 xpath 복사 시, /html/body/div/div[1]/div[2]/div/div[1]/div[1]/div/a[1]/span
▸ xpath 복사 시, //*[@id="html"]/body/div/div[1]/div[2]/div/div[1]/div[1]/div/a[1]/span

만약 velog 홈페이지에서 프로필 이미지를 xpath를 활용하여 가져오고 싶다면 ?

단순히 개발자 도구에서 프로필 이미지의 xpath를 순차적으로 복사하면 아래처럼 복사됩니다. 차이점 보이시나요 ??

1️⃣ 첫 번째 프로필 xpath : //*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[1]/div[2]/a/img
→ body 하단의 div 하단의 div 첫번째의 ... li 목록의 첫 번째의 div의 a의 이미지
2️⃣ 두 번째 프로필 xpath : //*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img
3️⃣ 세 번째 프로필 xpath : //*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img
....

정답은 li[N]에 N이 변경되고 있습니다~
첫 번째 프로필 xpath를 확인하자면 body 하단의 div 하단의 div 첫 번째의 ... li 목록의 첫 번째의 div의 a의 이미지! 라는 뜻이 됩니다.

위의 방식으로 프로필 이미지를 가져오려고 한다면 하나씩 n 값을 변경하면서 가져오는 등의 방법을 사용해야 합니다. 불가능한 것은 아니지만 가장 좋은 방법은 아닐 것 입니다!

ex :

profile_image_1 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[1]/div[2]/a/img" )
profile_image_2 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img" ) 
profile_image_3 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[3]/div[2]/a/img" ) 

or

for i in range(1,10) : 
	profile_image = driver.find_element(By.XPATH,"//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[" + str(i) + "]/div[2]/a/img") 

그렇다면 하나의 xpath로 모든 프로필 이미지를 가져올 수는 없을까요?
html 코드를 다시 확인해보면 모든 프로필 이미지는 PostCard_userInfo__Cu1X5라는 class 명의 a 노드의 바로 아래에 위치하고 있습니다.

▶︎ 때문에 아래처럼 PostCard_userInfo__Cu1X5라는 클래스 명의 a 노드 하단에 있는 모든 이미지! 를 표현하는 동적 xpath를 사용한다면 현재 페이지에 노출되고 있는 모든 프로필 이미지를 한번에 가져올 수 있습니다.

전체 프로필 xpath : //a[@class="PostCard_userInfo__Cu1X5"]/img
ex ) profile_images = driver.find_elements(By.XPATH,"//a[@class="PostCard_userInfo__Cu1X5"]/img")

동적 XPATH 작성하는 방법

동적 xpath는 중간 노드부터 표현하는 값이기 때문에 어떤 노드부터 어떻게 표현하는지에 따라서 같은 노드에 대해 다양한 방법으로 표현할 수 있습니다.

✿ 경로 표현식

표현식설명
노드명해당 노드명인 노드 선택
/루트 노드부터 탐색
//지정된 노드부터 탐색
.현재 노드 선택
..현재 노드의 부모 노드 선택
@현재 노드의 속성 선택

✿ 필터 표현식

함수설명예제
count()해당 경로를 가진 노드 개수# //table[count(tr)=1]
position()노드의 위치# //ol/li[position()=2]
name()현재 노드의 이름# //[starts-with(name(), 'h')]
text()해당 텍스트를 가진 노드# //button[text()="Submit"]
contains()해당 텍스트를 포함하는 노드# font[contains(@class,"head")] #id # //button[contains(text(),"Go")]
starts-with()해당 텍스트로 시작하는 노드# font[starts-with(@class,"head")]
ends-with()해당 텍스트로 끝나는 노드# font[ends-with(@class,"head")]

✿ 상대 경로 표현

선택자예제
h1//h1
div p//div//p
ul > li//ul/li
ul > li > a//ul/li/a
div > *//div/*
:root/
:root > body/body
ul > li:first-of-type//ul/li[1]
ul > li:nth-of-type(2)//ul/li[2]
ul > li:last-of-type//ul/li[last()]
li#id:first-of-type//li[1][@id="id"]
a:first-child//*[1][name()="a"]
a:last-child//*[last()][name()="a"]

✿ 속성 선택자 사용

선택자예제
#id//*[@id="id"]
.class//*[@class="class"]
input[type="submit"]//input[@type="submit"]
a#abc[for="xyz"]//a[@id="abc"][@for="xyz"]
a[rel]//a[@rel]
a[href^='/']//a[starts-with(@href, '/')]
a[href$='pdf']//a[ends-with(@href, '.pdf')]
a[href*='://']//a[contains(@href, '://')]
a[rel~='help']//a[contains(@rel, 'help')]

XPATH 테스트 페이지

  1. Chrome : 개발자도구
    chrome을 활용한 웹 자동화를 할 경우에는 개발자도구 > CTRL+F에서 노출되는 검색창에 xpath를 입력하면 해당하는 element를 찾을 수 있습니다.
  1. XPATH Tester
    APPIUM 과 같은 앱 자동화를 할 경우에는 XPath Tester 와 같은 외부 테스트 페이지에 XML 소스를 복사하여 확인 가능합니다.

참고 :
https://tcpschool.com/xml/xml_xpath_filterExpression
https://devhints.io/xpath#class-check

profile
주니어 QA 🐥

0개의 댓글