Selenium WebDriver : JavaScript가있는 복잡한 페이지가로드 될 때까지 기다립니다.


103

Selenium으로 테스트 할 웹 애플리케이션이 있습니다. 페이지로드시 많은 JavaScript가 실행됩니다.
이 JavaScript 코드는 잘 작성되지 않았지만 아무것도 변경할 수 없습니다. 따라서 findElement()메서드 를 사용하여 요소가 DOM에 나타날 때까지 기다리는 것은 옵션이 아닙니다.
페이지가로드 될 때까지 기다리기 위해 Java에서 일반 함수를 만들고 싶습니다. 가능한 해결책은 다음과 같습니다.

  • WebDriver에서 JavaScript 스크립트를 실행하고 그 결과를 document.body.innerHTML문자열 변수에 저장합니다 body.
  • 비교] body의 이전 버전으로 변수를 body. 같으면 카운터 증가를 설정하고 notChangedCount그렇지 않으면 notChangedCount0으로 설정 합니다.
  • litte 시간을 기다립니다 (예 : 50ms).
  • 페이지가 일정 시간 동안 (예 : 500ms) 변경되지 notChangedCount >= 10않은 경우 루프를 종료하고 그렇지 않으면 첫 번째 단계로 루프합니다.

이것이 유효한 해결책이라고 생각하십니까?


findElement ()는 기다리지 않습니다-그게 무슨 뜻입니까?
Rostislav Matl

1
findElement요소를 사용할 수있을 때까지 기다립니다.하지만 때때로 자바 스크립트 코드가 완전히 초기화되기 전에 요소를 사용할 수 있기 때문에 옵션이 아닙니다.
psadac

나는 그것을 잊었다-나는 WebDriverWait 및 ExpectedCondition을 사용하는 데 익숙해 져 더 유연합니다.
Rostislav Matl

답변:


73

누군가가 일반적이고 항상 적용 가능한 대답을 실제로 알고 있다면 그것은 오래 전에 모든 곳 에서 구현되었을 것이며 우리의 삶을 훨씬 더 쉽게 만들 것입니다.

할 수있는 일이 많지만 모든 일에는 문제가 있습니다.

  1. 애쉬 윈 프라 부 말했듯이 당신이 잘 스크립트를 알고있는 경우에, 당신의 행동을 관찰하고에 그 변수의 일부를 추적 할 수 있습니다 window또는 document등이 솔루션을하지만, 모든 사람이 아니므로 오직 제한된에서만 사용할 수 있습니다 페이지.

  2. HTML 코드를 관찰하고 일정 기간 동안 변경되었는지 여부를 확인하는 솔루션은 나쁘지 않습니다 (또한 직접 편집하지 않은 원본 HTML을 가져 오는 방법도 있습니다 WebDriver).

    • 실제로 페이지를 주장하는 데 오랜 시간이 걸리며 테스트 시간이 크게 연장 될 수 있습니다.
    • 올바른 간격이 무엇인지 결코 알 수 없습니다. 스크립트 500ms 이상 걸리는 큰 파일을 다운로드 할 있습니다. IE에서 몇 초가 걸리는 회사의 내부 페이지에는 여러 스크립트가 있습니다. 컴퓨터의 리소스가 일시적으로 부족할 수 있습니다. 즉, 바이러스 백신이 CPU를 완전히 작동시킬 수 있다고 말하면 복잡하지 않은 스크립트의 경우에도 500ms가 너무 짧을 수 있습니다.
    • 일부 스크립트는 수행되지 않습니다. 그들은 약간의 지연 ( setTimeout())을 가지고 자신을 호출하고 반복해서 작동하며 실행될 때마다 HTML을 변경할 수 있습니다. 진지하게, 모든 "웹 2.0"페이지가 그렇게합니다. 심지어 스택 오버플로. 사용되는 가장 일반적인 방법을 덮어 쓰고이를 사용하는 스크립트를 완료된 것으로 간주 할 수 있지만 ... 확실하지 않습니다.
    • 스크립트가 HTML 변경 이외의 작업을 수행하면 어떻게됩니까? innerHTML재미 뿐만 아니라 수천 가지 일을 할 수 있습니다.
  3. 이에 도움이되는 도구가 있습니다. 즉 , nsIWebProgressListener 및 다른 일부 와 함께 진행 리스너 입니다. 그러나 이에 대한 브라우저 지원은 끔찍합니다. Firefox는 FF4부터 지원하기 시작했으며 (아직 발전하고 있음) IE는 IE9에서 기본 지원을 제공합니다.

그리고 곧 또 다른 결함이있는 해결책을 찾을 수있을 것 같습니다. 사실은-작업을 수행하는 영원한 스크립트 때문에 "이제 페이지가 완성되었습니다"라고 말할시기에 대한 명확한 대답이 없습니다. 당신에게 가장 잘 봉사하는 것을 선택하되 단점을 조심하십시오.


31

감사합니다 Ashwin!

제 경우에는 일부 요소에서 jquery 플러그인 실행을 기다려야합니다. 특히 "qtip"

귀하의 힌트를 바탕으로 완벽하게 작동했습니다.

wait.until( new Predicate<WebDriver>() {
            public boolean apply(WebDriver driver) {
                return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
            }
        }
    );

참고 : Webdriver 2를 사용하고 있습니다.


2
그것은 나에게도 매우 잘 작동합니다. 제 경우에는 페이지로드가 완료된 직후에 요소가 Javascript에 의해 수정되었으며 Selenium은이를 암시 적으로 기다리지 않았습니다.
Noxxys

2
Predicate는 어디에서 왔습니까? 어떤 수입 ??
Amado Saladino

@AmadoSaladino "import com.google.common.base.Predicate;" Google lib guava-15.0 링크에서 : mvnrepository.com/artifact/com.google.guava/guava/15.0 최신 버전 27.0이 있습니다.
Eduardo Fabricio

26

Javascript 및 jQuery가로드를 완료 할 때까지 기다려야합니다. 자바 스크립트를 실행하여 jQuery.activeis 0document.readyStateis 인지 확인합니다 complete. 이는 JS 및 jQuery로드가 완료되었음을 의미합니다.

public boolean waitForJStoLoad() {

    WebDriverWait wait = new WebDriverWait(driver, 30);

    // wait for jQuery to load
    ExpectedCondition<Boolean> jQueryLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        try {
          return ((Long)executeJavaScript("return jQuery.active") == 0);
        }
        catch (Exception e) {
          return true;
        }
      }
    };

    // wait for Javascript to load
    ExpectedCondition<Boolean> jsLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        return executeJavaScript("return document.readyState")
            .toString().equals("complete");
      }
    };

  return wait.until(jQueryLoad) && wait.until(jsLoad);
}

2
이것이 모든 상황 에서 작동하는지 확실하지 않지만 내 사용 사례에는 효과가 있습니다
DaveH

1
저에게도 작동하지만 까다로운 부분이 있습니다. 페이지가 이미로드되었다고 가정 해 보겠습니다. 요소 (예 : .click () 보내기 또는 .send some keys to it]와 상호 작용 한 다음 페이지 처리가 완료되었는지 확인하려면 지연을 추가해야합니다. 예 : click (), sleep (0.5 초 ), (readyState = 'complete'& jQuery.active == 0)까지 기다립니다. 절전 모드를 추가하지 않으면 iQuery가 테스트 시간에 활성화되지 않습니다! (이 알아 나에게 약간의 시간이 걸렸다, 그래서 그것을 공유 할 생각)
Fivos Vilanakis

document.readyState == 'complete' && jQuery.active == 0훌륭하게 작동하지만 React와 비슷한 것이 있습니까? 이 검사는 여전히 반응 데이터 / 구성 요소를로드하지 못하기 때문에?
Rain9333

10

JS 라이브러리가 창에서 잘 알려진 변수를 정의 / 초기화합니까?

그렇다면 변수가 나타날 때까지 기다릴 수 있습니다. 당신이 사용할 수있는

((JavascriptExecutor)driver).executeScript(String script, Object... args)

이 조건 (예 :)을 테스트 window.SomeClass && window.SomeClass.variable != null하고 부울 true/을 반환합니다 false.

이것을로 감싸고 WebDriverWait스크립트가 반환 될 때까지 기다립니다 true.



7

요소와 상호 작용을 시도하기 전에 페이지의 html이 안정 될 때까지 기다리기만하면 DOM을 주기적으로 폴링하고 결과를 비교할 수 있습니다. DOM이 주어진 폴링 시간 내에 동일하면 황금. 비교하기 전에 최대 대기 시간과 페이지 폴링 사이의 시간을 전달하는 것과 같은 것입니다. 간단하고 효과적입니다.

public void waitForJavascript(int maxWaitMillis, int pollDelimiter) {
    double startTime = System.currentTimeMillis();
    while (System.currentTimeMillis() < startTime + maxWaitMillis) {
        String prevState = webDriver.getPageSource();
        Thread.sleep(pollDelimiter); // <-- would need to wrap in a try catch
        if (prevState.equals(webDriver.getPageSource())) {
            return;
        }
    }
}

1
중간 크기의 페이지를 두 번 폴링하는 데 걸린 시간을 기록한 다음 Java에서 해당 문자열을 비교하고 20ms가 조금 넘게 걸렸습니다.
TheFreddyKilo

2
처음에는 이것이 더러운 해결책이라는 것을 알았지 만 훌륭하게 작동하는 것처럼 보이며 클라이언트 측 변수 설정을 포함하지 않습니다. 한 가지주의 할 점은 자바 스크립트 (예 : 자바 스크립트 시계)에 의해 지속적으로 변경되는 페이지 일 수 있지만 해당 페이지가 없어서 작동합니다!
피터

4

아래 코드는 내 경우 완벽하게 작동합니다-내 페이지에는 복잡한 자바 스크립트가 포함되어 있습니다.

public void checkPageIsReady() {

  JavascriptExecutor js = (JavascriptExecutor)driver;


  //Initially bellow given if condition will check ready state of page.
  if (js.executeScript("return document.readyState").toString().equals("complete")){ 
   System.out.println("Page Is loaded.");
   return; 
  } 

  //This loop will rotate for 25 times to check If page Is ready after every 1 second.
  //You can replace your value with 25 If you wants to Increase or decrease wait time.
  for (int i=0; i<25; i++){ 
   try {
    Thread.sleep(1000);
    }catch (InterruptedException e) {} 
   //To check page ready state.
   if (js.executeScript("return document.readyState").toString().equals("complete")){ 
    break; 
   }   
  }
 }

출처 -Selenium WebDriver에서 페이지가로드 / 준비되기를 기다리는 방법


2
당신은 그 루프 대신 셀레늄 대기를 사용해야합니다
cocorossello

4

페이지에서 요소를 찾기 전에 페이지가로드되었는지 확인하는 데 두 가지 조건을 사용할 수 있습니다.

WebDriverWait wait = new WebDriverWait(driver, 50);

아래 readyState를 사용하면 페이지가로드 될 때까지 대기합니다.

wait.until((ExpectedCondition<Boolean>) wd ->
   ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));

아래 JQuery는 데이터가로드되지 않을 때까지 기다립니다.

  int count =0;
            if((Boolean) executor.executeScript("return window.jQuery != undefined")){
                while(!(Boolean) executor.executeScript("return jQuery.active == 0")){
                    Thread.sleep(4000);
                    if(count>4)
                        break;
                    count++;
                }
            }

이 JavaScriptCode 후에 webElement를 찾으십시오.

WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));

1
Selenium은 또한 기본적으로이 작업을 수행합니다. 스크립트에 추가 코드 실행 시간이 추가됩니다 (추가로드에 충분한 시간이 될 수 있지만 다시로드되지 않을 수 있음)
Ardesco

2

나는 개발자들에게 내가 접근 할 수있는 자바 스크립트 변수 "isProcessing"을 만들어달라고 요청했다. ( "ae"객체에서) 일이 시작될 때 설정하고 일이 완료되면 지워진다. 그런 다음 변경없이 총 500ms 동안 연속 5 개를 얻을 때까지 100ms마다 확인하는 누산기에서 실행합니다. 30 초가 지나면 그때까지 무슨 일이 있었어야했기 때문에 예외를 던진다. 이것은 C #입니다.

public static void WaitForDocumentReady(this IWebDriver driver)
{
    Console.WriteLine("Waiting for five instances of document.readyState returning 'complete' at 100ms intervals.");
    IJavaScriptExecutor jse = (IJavaScriptExecutor)driver;
    int i = 0; // Count of (document.readyState === complete) && (ae.isProcessing === false)
    int j = 0; // Count of iterations in the while() loop.
    int k = 0; // Count of times i was reset to 0.
    bool readyState = false;
    while (i < 5)
    {
        System.Threading.Thread.Sleep(100);
        readyState = (bool)jse.ExecuteScript("return ((document.readyState === 'complete') && (ae.isProcessing === false))");
        if (readyState) { i++; }
        else
        {
            i = 0;
            k++;
        }
        j++;
        if (j > 300) { throw new TimeoutException("Timeout waiting for document.readyState to be complete."); }
    }
    j *= 100;
    Console.WriteLine("Waited " + j.ToString() + " milliseconds. There were " + k + " resets.");
}

1

제대로 수행하려면 예외를 처리해야합니다.

iFrame을 기다리는 방법은 다음과 같습니다. 이를 위해서는 JUnit 테스트 클래스가 RemoteWebDriver의 인스턴스를 페이지 객체로 전달해야합니다.

public class IFrame1 extends LoadableComponent<IFrame1> {

    private RemoteWebDriver driver;

    @FindBy(id = "iFrame1TextFieldTestInputControlID" )
    public WebElement iFrame1TextFieldInput;

    @FindBy(id = "iFrame1TextFieldTestProcessButtonID" )
    public WebElement copyButton;

    public IFrame1( RemoteWebDriver drv ) {
        super();
        this.driver = drv;
        this.driver.switchTo().defaultContent();
        waitTimer(1, 1000);
        this.driver.switchTo().frame("BodyFrame1");
        LOGGER.info("IFrame1 constructor...");
    }

    @Override
    protected void isLoaded() throws Error {        
        LOGGER.info("IFrame1.isLoaded()...");
        PageFactory.initElements( driver, this );
        try {
            assertTrue( "Page visible title is not yet available.", driver
     .findElementByCssSelector("body form#webDriverUnitiFrame1TestFormID h1")
                    .getText().equals("iFrame1 Test") );
        } catch ( NoSuchElementException e) {
            LOGGER.info("No such element." );
            assertTrue("No such element.", false);
        }
    }

    @Override
    protected void load() {
        LOGGER.info("IFrame1.load()...");
        Wait<WebDriver> wait = new FluentWait<WebDriver>( driver )
                .withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(5, TimeUnit.SECONDS)
                .ignoring( NoSuchElementException.class ) 
                .ignoring( StaleElementReferenceException.class ) ;
            wait.until( ExpectedConditions.presenceOfElementLocated( 
            By.cssSelector("body form#webDriverUnitiFrame1TestFormID h1") ) );
    }
....

참고 : 여기에서 전체 작업 예제를 볼 수 있습니다 .


1

를 들어 nodejs셀레늄 라이브러리, 나는 다음 코드를 사용했다. 내 경우, 나는이 예제에있는 창에 추가 된 두 개의 객체를 찾고 있었다 <SOME PROPERTY>, 10000시간 초과 밀리 초 <NEXT STEP HERE>속성 창에서 발견 된 후 발생하는 것입니다.

driver.wait( driver => {
    return driver.executeScript( 'if(window.hasOwnProperty(<SOME PROPERTY>) && window.hasOwnProperty(<SOME PROPERTY>)) return true;' ); }, 10000).then( ()=>{
        <NEXT STEP HERE>
}).catch(err => { 
    console.log("looking for window properties", err);
});

0

이를 처리하기위한 로직을 작성할 수 있습니다. 나는를 반환하는 메소드를 작성해야 WebElement하고이 방법은 세 번 호출 할 것이다 또는 당신은 시간을 늘리고 널 체크를 추가 할 수 있습니다 WebElement다음은 예입니다

public static void main(String[] args) {
        WebDriver driver = new FirefoxDriver();
        driver.get("https://www.crowdanalytix.com/#home");
        WebElement webElement = getWebElement(driver, "homekkkkkkkkkkkk");
        int i = 1;
        while (webElement == null && i < 4) {
            webElement = getWebElement(driver, "homessssssssssss");
            System.out.println("calling");
            i++;
        }
        System.out.println(webElement.getTagName());
        System.out.println("End");
        driver.close();
    }

    public static WebElement getWebElement(WebDriver driver, String id) {
        WebElement myDynamicElement = null;
        try {
            myDynamicElement = (new WebDriverWait(driver, 10))
                    .until(ExpectedConditions.presenceOfElementLocated(By
                            .id(id)));
            return myDynamicElement;
        } catch (TimeoutException ex) {
            return null;
        }
    }

0

다음은 내 코드입니다.
Window.setTimeout은 브라우저가 유휴 상태 일 때만 실행됩니다.
따라서 함수를 재귀 적으로 호출 (42 회)하면 브라우저에 활동이 없으면 100ms가 걸리고 브라우저가 다른 작업을 수행 중이면 훨씬 더 많이 걸립니다.

    ExpectedCondition<Boolean> javascriptDone = new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver d) {
            try{//window.setTimeout executes only when browser is idle,
                //introduces needed wait time when javascript is running in browser
                return  ((Boolean) ((JavascriptExecutor) d).executeAsyncScript( 

                        " var callback =arguments[arguments.length - 1]; " +
                        " var count=42; " +
                        " setTimeout( collect, 0);" +
                        " function collect() { " +
                            " if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+
                            " else {callback(" +
                            "    true" +                            
                            " );}"+                             
                        " } "
                    ));
            }catch (Exception e) {
                return Boolean.FALSE;
            }
        }
    };
    WebDriverWait w = new WebDriverWait(driver,timeOut);  
    w.until(javascriptDone);
    w=null;

보너스로 카운터는 document.readyState 또는 jQuery Ajax 호출 또는 jQuery 애니메이션이 실행중인 경우 재설정 할 수 있습니다 (앱이 ajax 호출에 jQuery를 사용하는 경우에만 ...)
...

" function collect() { " +
                            " if(!((typeof jQuery === 'undefined') || ((jQuery.active === 0) && ($(\":animated\").length === 0))) && (document.readyState === 'complete')){" +
                            "    count=42;" +
                            "    setTimeout( collect, 0); " +
                            " }" +
                            " else if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+ 

...

편집 : 새 페이지가로드되고 테스트가 무기한 응답을 중지하면 executeAsyncScript가 제대로 작동하지 않는 것으로 나타났습니다. 대신 이것을 사용하는 것이 좋습니다.

public static ExpectedCondition<Boolean> documentNotActive(final int counter){ 
    return new ExpectedCondition<Boolean>() {
        boolean resetCount=true;
        @Override
        public Boolean apply(WebDriver d) {

            if(resetCount){
                ((JavascriptExecutor) d).executeScript(
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();");
                resetCount=false;
            }

            boolean ready=false;
            try{
                ready=-1==((Long) ((JavascriptExecutor) d).executeScript(
                        "if(typeof window.mssJSDelay!=\"function\"){\r\n" + 
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();\r\n" + 
                        "}\r\n" + 
                        "return window.mssCount;"));
            }
            catch (NoSuchWindowException a){
                a.printStackTrace();
                return true;
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return ready;
        }
        @Override
        public String toString() {
            return String.format("Timeout waiting for documentNotActive script");
        }
    };
}

0

방법을 모르지만 제 경우에는 페이지로드 및 렌더링 끝이 Firefox 탭에 표시된 FAVICON과 일치합니다.

따라서 웹 브라우저에서 파비콘 이미지를 얻을 수 있다면 웹 페이지가 완전히로드 된 것입니다.

그러나 이것을 어떻게 수행합니까 ....



-1

방법은 다음과 같습니다.

new WebDriverWait(driver, 20).until(
       ExpectedConditions.jsReturnsValue(
                   "return document.readyState === 'complete' ? true : false"));

Selenium은 또한 기본적으로이 작업을 수행합니다. 이렇게하면 스크립트에 코드 실행 시간이 추가됩니다 (추가로드에 충분한 시간이 될 수 있지만 다시로드되지 않을 수 있음)
Ardesco

-1

안정 될 때까지 HTML을 폴링하는 아이디어가 마음에 듭니다. 내 솔루션에 추가 할 수 있습니다. 다음 접근 방식은 C #이며 jQuery가 필요합니다.

저는 SuccessFactors (SaaS) 테스트 프로젝트의 개발자이며 웹 페이지 뒤의 DOM 특성이나 개발자에게 전혀 영향을 미치지 않습니다. SaaS 제품은 잠재적으로 기본 DOM 디자인을 1 년에 4 번 변경할 수 있으므로 Selenium으로 테스트 할 수있는 강력하고 성능이 좋은 방법 (가능한 경우 Selenium으로 테스트하지 않음 포함!)

다음은 "페이지 준비"에 사용하는 것입니다. 현재 내 모든 테스트에서 작동합니다. 동일한 접근 방식이 몇 년 전 대규모 사내 Java 웹 앱에서도 작동했으며 프로젝트를 떠날 때 1 년 이상 강력했습니다.

  • Driver 브라우저와 통신하는 WebDriver 인스턴스입니다.
  • DefaultPageLoadTimeout 틱 단위의 시간 초과 값입니다 (틱당 100ns).

public IWebDriver Driver { get; private set; }

// ...

const int GlobalPageLoadTimeOutSecs = 10;
static readonly TimeSpan DefaultPageLoadTimeout =
    new TimeSpan((long) (10_000_000 * GlobalPageLoadTimeOutSecs));
Driver = new FirefoxDriver();

다음에서 메서드 PageReady(Selenium 문서 준비, Ajax, 애니메이션) 의 대기 순서를 확인하십시오. 생각해 보면 의미가 있습니다.

  1. 코드가 포함 된 페이지로드
  2. 코드를 사용하여 Ajax를 통해 어딘가에서 데이터를로드합니다.
  3. 애니메이션과 함께 데이터 표시

DOM 비교 접근 방식과 같은 것을 1과 2 사이에 사용하여 또 다른 견고성을 추가 할 수 있습니다.


public void PageReady()
{
    DocumentReady();
    AjaxReady();
    AnimationsReady();
}


private void DocumentReady()
{
    WaitForJavascript(script: "return document.readyState", result: "complete");
}

private void WaitForJavascript(string script, string result)
{
    new WebDriverWait(Driver, DefaultPageLoadTimeout).Until(
        d => ((IJavaScriptExecutor) d).ExecuteScript(script).Equals(result));
}

private void AjaxReady()
{
    WaitForJavascript(script: "return jQuery.active.toString()", result: "0");
}

private void AnimationsReady()
{
    WaitForJavascript(script: "return $(\"animated\").length.toString()", result: "0");
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.