HOME > etc > tips

Selenium - 페이지 로딩이 완료될 때까지 기다리기 (python)

JSFollow03 Oct 2019

Selenium으로 테스트를 하거나 매크로를 만들다보면 간혹 ElementNotVisibleException 에러가 발생합니다. 잘 동작하는 코드였는데 어쩔때는 이런 에러를 발생시키며 동작하지 않을 때가 있습니다. 이유는 Ajax 등으로 웹페이지가 로딩되는 시간 차이가 있기 때문입니다. 그래서 페이지 로딩이 완료되지 않은 상태에서 find_element_by_id 등으로 존재하지 않은 아이템을 찾으려고 하니 이런 에러가 발생할 수 밖에 없습니다.

해결 방법은 페이지가 로딩될 때까지 기다리는 것입니다.

Selenium은 다음 두가지 방법의 wait을 제공합니다.

  • Implicitly wait: 정해진 시간만큼 충분히 기다리기
  • Explicitly wait: 어떤 조건이 만족할 때까지 기다리기

Implicitly wait

Implicitly wait을 10초로 설정하면 페이지가 로딩되는데 10초까지 기다립니다. 만약 페이지 로딩이 2초에 완료되었다면 더 기다리지 않고 다음 코드를 수행합니다. 기본 설정은 0초로 되어있고, 한번만 설정하면 driver를 사용하는 모든 코드에 적용이 됩니다.

아래 코드처럼 get을 호출하기 전에 wait 시간을 10초로 설정하면, get이 페이지가 모두 로딩될 때까지 기다린 후 find_element_by_id으로 element를 찾습니다.

from selenium import webdriver

driver = webdriver.Chrome("chrome path")
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

Explicitly wait

Explicitly wait은 명시적으로 어떤 조건이 성립했을 때까지 기다립니다. 조건이 성립하지 않으면 timeout으로 설정된 시간만큼 최대한 기다립니다.

다음 코드는 ID가 someid인 element가 clickable이 될 때까지 기다리고, 그 element를 리턴하는 코드입니다. get() 코드 다음으로 wait.until() 코드를 추가하시면 됩니다. wait.until()은 특정 조건이 만족될 때까지 기다리고 그 element를 리턴합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome("chrome path")
driver.get("http://somedomain/url_that_delays_loading")

# wait until someid is clickable
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))

먼저 WebDriverWait 객체를 생성합니다. 인자로 driver와 timeout이 들어갑니다. 이 객체는 재사용할 수 있기 때문에 전역에 객체를 만들고 그 이후에 재사용하면 좋습니다.

조건을 설정하는 방법은 wait.until(조건)처럼 인자에 기다릴 조건을 넣으시면 됩니다.

위의 예제에서 조건에 해당하는 것은 아래 코드인데요. ID가 someid인 element가 clickable이 될 때까지 기다린다는 의미입니다.

EC.element_to_be_clickable((By.ID, 'someid')

아래 코드 처럼 By를 변경하여 class name이나 name으로 element를 찾게 할 수도 있습니다.

EC.element_to_be_clickable((By.CLASS_NAME, 'some_classname')
EC.element_to_be_clickable((By.NAME, 'some_name')

clickable이 싫다면 아래 처럼 element_to_be_clickable를 다른 조건으로 변경할 수도 있습니다.

EC.title_is(...)
EC.title_contains(...)
EC.presence_of_element_located(...)
EC.visibility_of_element_located(...)
EC.visibility_of(...)
EC.presence_of_all_elements_located(...)
EC.text_to_be_present_in_element(...)
EC.text_to_be_present_in_element_value(...)
EC.frame_to_be_available_and_switch_to_it(...)
EC.invisibility_of_element_located(...)
EC.element_to_be_clickable(...)
EC.staleness_of(...)
EC.element_to_be_selected(...)
EC.element_located_to_be_selected(...)
EC.element_selection_state_to_be(...)
EC.element_located_selection_state_to_be(...)
EC.alert_is_present(...)

모든 코드에 적용되는 implicitly wait과 다르게, explicitly wait은 코드를 넣은 위치에서만 기다립니다.

참고