가능하다면, Time.sleep()
대신에WebDriverWait.until()
을 사용해볼까요?
time.sleep(secs)
Suspend execution of the calling thread for the given number of seconds. The argument may be a floating point number to indicate a more precise sleep time.
우리는 종종 자동 테스트 코드를 작성할 때에 time.sleep()
을 걸곤 합니다. 대부분의 경우는 driver.find_element()
로 찾아내지 못하는 webElement를 식별하기 위해서인데요, 웹페이지 로딩이 아직 다 안되어서 찾고자 하는 element가 아직 로딩이 다 되지 않은 경우나, ajax를 이용한 비동기 처리에서 원하는 웹요소를 찾을 수 없을 때 사용합니다.
그러나 time.sleep()
은 위 설명에도 기재되어있듯, 실행되는 스레드 자체의 실행을 잠시 지연시키는 것으로, 과도하게 사용하면 전체 테스트 수행시간이 길어지게 됩니다.
네트워크나 데이터 양에 따라서는 빠르게 로딩되는 경우도 있을때에도 불필요한 정지를 하게되는 것입니다.
Selenium에서는 wait
의 개념이 있습니다.
Selenium Webdriver provides two types of waits - implicit & explicit. An explicit wait makes WebDriver wait for a certain condition to occur before proceeding further with execution. An implicit wait makes WebDriver poll the DOM for a certain amount of time when trying to locate an element.
이것은 최초 드라이버를 생성할 때, 설정해줍니다. 이 값을 설정해주면 전체 웹페이지 DOM에서 특정 element를 찾을 때까지 지정해준 시간동안 대기를 합니다.
driver.implicitly_wait(10)
모든 element를 찾을 때 사용할 수 있으며, DOM이 로딩될때까지 기다리므로 퍼포먼스 이슈가 있다고는 합니다.
이것은 명시적 대기라고 하는데, 특정 element를 찾을 때 조건을 설정해가면서 대기를 걸어줄 수 있습니다.
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))
위 코드는 someid
라는 id값을 가진 element를, 클릭이 가능할 때까지 기다리는 일종의 조건문이라고도 할 수 있습니다. 대기시간은 10초를 설정하고 있으나, 해당 요소를 찾게되면 바로 다음 코드를 실행합니다.
Expected Condition은 아래의 추가적으로 아래의 조건들을 쓸 수 있습니다.
위에서 말씀드렸다시피, time.sleep()
은 전체 스레드를 실행대기시켜버립니다. 어떠한 하나의 버튼만 찾고싶은데 time.sleep()
을 걸어버리는 것은 불필요하고, 전체적인 테스트 실행 시간을 길어지게하는 원인이 됩니다.
WebDriverWait
의 생성자와, until()
메소드 내부를 확인하면 아래와 같습니다.
def __init__(
self,
driver,
timeout: float,
poll_frequency: float = POLL_FREQUENCY,
ignored_exceptions: typing.Optional[WaitExcTypes] = None,
):
"""Constructor, takes a WebDriver instance and timeout in seconds.
:Args:
- driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
- timeout - Number of seconds before timing out
- poll_frequency - sleep interval between calls
By default, it is 0.5 second.
- ignored_exceptions - iterable structure of exception classes ignored during calls.
By default, it contains NoSuchElementException only.
Example::
from selenium.webdriver.support.wait import WebDriverWait \n
element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n
is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n
until_not(lambda x: x.find_element(By.ID, "someId").is_displayed())
"""
self._driver = driver
self._timeout = float(timeout)
self._poll = poll_frequency
# avoid the divide by zero
if self._poll == 0:
self._poll = POLL_FREQUENCY
exceptions = list(IGNORED_EXCEPTIONS)
if ignored_exceptions:
try:
exceptions.extend(iter(ignored_exceptions))
except TypeError: # ignored_exceptions is not iterable
exceptions.append(ignored_exceptions)
self._ignored_exceptions = tuple(exceptions)
def until(self, method, message: str = ""):
"""Calls the method provided with the driver as an argument until the \
return value does not evaluate to ``False``.
:param method: callable(WebDriver)
:param message: optional message for :exc:`TimeoutException`
:returns: the result of the last call to `method`
:raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
"""
screen = None
stacktrace = None
end_time = time.monotonic() + self._timeout
while True:
try:
value = method(self._driver)
if value:
return value
except self._ignored_exceptions as exc:
screen = getattr(exc, "screen", None)
stacktrace = getattr(exc, "stacktrace", None)
time.sleep(self._poll)
if time.monotonic() > end_time:
break
raise TimeoutException(message, screen, stacktrace)
until()
의 while
문을 보시면, 일단 True
로 무한 루프를 돌면서 특정값을 찾을 때까지 실행됩니다.
except
에서 무시할 Exception으로 명시한 값에 대한 처리를 해주고, if
조건에서, 설정한 시간을 초과하는 경우에는 break
로 무한루프를 빠져나오고, TimeoutException
을 발생시킵니다. (언어가 다를 뿐, 다른 언어들의 selenium도 대부분 비슷한 동작입니다.)
즉, 찾고자하는 요소를 찾으면 설정한 시간을 다 채우지않고 바로 종료한다 는 것입니다.
이론적으로는 무조건 좋은데, 간혹가다가 wait
는 동작안하는데, time.sleep()
을 주면 동작한다는 글을 심심치 않게 볼 수 있습니다.
이것은 wait.until()
에 주는 조건에 따라 driver가 판단하는 타이밍이 다를 수도 있고, 웹페이지의 DOM의 구조나 네트워크 이슈 등 다양한 이유에 따라서 못찾는 경우가 있습니다.
그렇다고 한 줄 한 줄 마다 sleep()
을 작성하진 마시고...
이럴때는 일단 time.sleep()
을 쓰되, 무분별한 사용은 지양하고, 점진적으로 대기시간을 조정해가며 실행가능한 최소한의 시간을 찾아내야합니다.
끝!!!!