web-dev-qa-db-fra.com

Comment faire en sorte que Selenium attend la réponse ajax?

Comment faire en sorte que Selenium attende le chargement d’un widget de calendrier, par exemple? Pour le moment, je ne fais que Thread.sleep(2500) après avoir exporté le testcase dans un programme Junit.

56
Zombies

J'utiliserais

waitForElementPresent(locator)

Cela attendra que l'élément soit présent dans le DOM.

Si vous devez vérifier que l’élément est visible, vous ferez peut-être mieux

waitForElementHeight(locator)
41
Neil Aitken

Une solution plus générale que d'attendre un élément serait d'attendre la fermeture de toutes les connexions au serveur. Cela vous permettra d’attendre la fin de tous les appels ajax, même s’ils n’ont pas de rappel et n’affectent donc pas la page. Plus de détails peuvent être trouvés ici .

En utilisant C # et jQuery, j'ai créé la méthode suivante pour attendre la fin de tous les appels AJax (si quelqu'un dispose de moyens plus directs d'accéder aux variables JS à partir de C #, veuillez commenter):

internal void WaitForAjax(int timeOut = 15)
{
    var value = "";
    RepeatUntil(
        () => value = GetJavascriptValue("jQuery.active"), 
        () => value == "0", 
        "Ajax calls did not complete before timeout"
    );
}

internal void RepeatUntil(Action repeat, Func<bool> until, string errorMessage, int timeout = 15)
{
    var end = DateTime.Now + TimeSpan.FromSeconds(timeout);
    var complete = false;

    while (DateTime.Now < end)
    {
        repeat();
        try
        {
            if (until())
            {
                complete = true;
                break;
            }
        }
        catch (Exception)
        { }
        Thread.Sleep(500);
    }
    if (!complete)
        throw new TimeoutException(errorMessage);
}

internal string GetJavascriptValue(string variableName)
{
    var id = Guid.NewGuid().ToString();
    _Selenium.RunScript(String.Format(@"window.$('body').append(""<input type='text' value='""+{0}+""' id='{1}'/>"");", variableName, id));
    return _Selenium.GetValue(id);
}
23
Morten Christiansen

Si vous utilisez python, vous pouvez utiliser cette fonction, qui clique sur le bouton et attend le changement de DOM:

def click_n_wait(driver, button, timeout=5):
    source = driver.page_source
    button.click()
    def compare_source(driver):
        try:
            return source != driver.page_source
        except WebDriverException:
            pass
    WebDriverWait(driver, timeout).until(compare_source)

(CREDIT: basé sur cette réponse de débordement de pile )

11
Nimo

Avec webdriver aka Selenium2, vous pouvez utiliser la configuration d'attente implicite comme indiqué sur http://docs.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits

Utilisation de Java:

WebDriver driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));

Ou en utilisant python:

from Selenium import webdriver

ff = webdriver.Firefox()
ff.implicitly_wait(10) # seconds
ff.get("http://somedomain/url_that_delays_loading")
myDynamicElement = ff.find_element_by_id("myDynamicElement")
6
toutpt

Ce travail pour moi 

public  void waitForAjax(WebDriver driver) {
    new WebDriverWait(driver, 180).until(new ExpectedCondition<Boolean>(){
        public Boolean apply(WebDriver driver) {
            JavascriptExecutor js = (JavascriptExecutor) driver;
            return (Boolean) js.executeScript("return jQuery.active == 0");
        }
    });
}
2
Artem Kislicyn

J'ai écrit la méthode suivante comme solution (je n'avais pas d'indicateur de charge):

public static void waitForAjax(WebDriver driver, String action) {
       driver.manage().timeouts().setScriptTimeout(5, TimeUnit.SECONDS);
       ((JavascriptExecutor) driver).executeAsyncScript(
               "var callback = arguments[arguments.length - 1];" +
                       "var xhr = new XMLHttpRequest();" +
                       "xhr.open('POST', '/" + action + "', true);" +
                       "xhr.onreadystatechange = function() {" +
                       "  if (xhr.readyState == 4) {" +
                       "    callback(xhr.responseText);" +
                       "  }" +
                       "};" +
                       "xhr.send();");
}

Ensuite, j’ai appelé cette méthode avec le pilote réel . Plus de description dans ce post .

2
Hrabosch

Voici une version groovy basée sur la réponse de Morten Christiansen.

void waitForAjaxCallsToComplete() {
    repeatUntil(
            { return getJavaScriptFunction(driver, "return (window.jQuery || {active : false}).active") },
            "Ajax calls did not complete before timeout."
    )
}

static void repeatUntil(Closure runUntilTrue, String errorMessage, int pollFrequencyMS = 250, int timeOutSeconds = 10) {
    def today = new Date()
    def end = today.time + timeOutSeconds
    def complete = false;

    while (today.time < end) {
        if (runUntilTrue()) {
            complete = true;
            break;
        }

        sleep(pollFrequencyMS);
    }
    if (!complete)
        throw new TimeoutException(errorMessage);
}

static String getJavaScriptFunction(WebDriver driver, String jsFunction) {
    def jsDriver = driver as JavascriptExecutor
    jsDriver.executeScript(jsFunction)
}
1
Cirem

J'ai eu une situation similaire, je voulais attendre pour les demandes ajax afin que le panneau de chargement aurait disparu, j'ai inspecté le html avant et après les demandes, a constaté qu'il existe un div pour le panneau de chargement ajax, le dix est affiché pendant la demande ajax, et masquée après la fin de la demande. J'ai créé une fonction pour attendre que le panneau soit affiché, puis attendre qu'il soit caché

public void WaitForModalPanel()
{
    string element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer'  and not(contains(@style,'display: none'))]";
    WebDriverWait wait = new WebDriverWait(driver, new TimeSpan(0, 2, 0));
    wait.Until(ExpectedConditions.ElementIsVisible(By.XPath(element_xpath)));
    element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer' and contains(@style,'DISPLAY: none')]";
    wait.Until(ExpectedConditions.ElementExists(By.XPath(element_xpath)));
}

Vérifiez this pour plus de détails

1
Amir Shenouda

Le code (C #) ci-dessous garantit l'affichage de l'élément cible:

        internal static bool ElementIsDisplayed()
        {
          IWebDriver driver = new ChromeDriver();
          driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
          WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
          By locator = By.CssSelector("input[value='csharp']:first-child");
          IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
          {
            return d.FindElement(locator);
          });
          return myDynamicElement.Displayed;
        }

Si la page prend en charge jQuery, vous pouvez utiliser la fonction jQuery.active pour vous assurer que l'élément cible est récupéré une fois tous les appels ajax terminés:

 public static bool ElementIsDisplayed()
    {
        IWebDriver driver = new ChromeDriver();
        driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
        WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
        By locator = By.CssSelector("input[value='csharp']:first-child");
        return wait.Until(d => ElementIsDisplayed(d, locator));
    }

    public static bool ElementIsDisplayed(IWebDriver driver, By by)
    {
        try
        {
            if (driver.FindElement(by).Displayed)
            {
                //jQuery is supported.
                if ((bool)((IJavaScriptExecutor)driver).ExecuteScript("return window.$ != undefined"))
                {
                    return (bool)((IJavaScriptExecutor)driver).ExecuteScript("return $.active == 0");
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return false;
            }
        }
        catch (Exception)
        {
            return false;
        }
    }
1
George Kargakis

Cela fonctionne comme un charme pour moi:

public void waitForAjax() {

    try {
        WebDriverWait driverWait = new WebDriverWait(driver, 10);

        ExpectedCondition<Boolean> expectation;   
        expectation = new ExpectedCondition<Boolean>() {

            public Boolean apply(WebDriver driverjs) {

                JavascriptExecutor js = (JavascriptExecutor) driverjs;
                return js.executeScript("return((window.jQuery != null) && (jQuery.active === 0))").equals("true");
            }
        };
        driverWait.until(expectation);
    }       
    catch (TimeoutException exTimeout) {

       // fail code
    }
    catch (WebDriverException exWebDriverException) {

       // fail code
    }
    return this;
}
1
Keerthi S

Comme mentionné ci-dessus, vous pouvez attendre que les connexions actives soient fermées:

private static void WaitForReady() {
    WebDriverWait wait = new WebDriverWait(webDriver, waitForElement);
    wait.Until(driver => (bool)((IJavaScriptExecutor)driver).ExecuteScript("return jQuery.active == 0"));
}

Mon observation est que ceci n’est pas fiable car le transfert de données se fait très rapidement. Le traitement et le rendu des données sur la page prennent beaucoup plus de temps et même les données jQuery.active == 0 peuvent ne pas être encore sur la page.

Il est beaucoup plus sage d’utiliser un élément explicit-wait pour que l’élément apparaisse sur la page, consultez les réponses correspondantes.

La meilleure situation est si votre application Web a un chargeur personnalisé ou une indication que les données sont en cours de traitement. Dans ce cas, vous pouvez simplement attendre que cette indication soit masquée.

1
llatinov

Ci-dessous, mon code pour fetch. M'a pris lors d'une recherche, car jQuery.active ne fonctionne pas avec fetch. Voici la réponse m'a aidé à récupérer le proxy, mais ce n'est que pour ajax pas chercher mock for Selenium

public static void customPatchXMLHttpRequest(WebDriver driver) {
    try {
        if (driver instanceof JavascriptExecutor) {
            JavascriptExecutor jsDriver = (JavascriptExecutor) driver;
            Object numberOfAjaxConnections = jsDriver.executeScript("return window.openHTTPs");
            if (numberOfAjaxConnections instanceof Long) {
                return;
            }
            String script = "  (function() {" + "var oldFetch = fetch;"
                    + "window.openHTTPs = 0; console.log('starting xhttps');" + "fetch = function(input,init ){ "
                    + "window.openHTTPs++; "

                    + "return oldFetch(input,init).then( function (response) {"
                    + "      if (response.status >= 200 && response.status < 300) {"
                    + "          window.openHTTPs--;  console.log('Call completed. Remaining active calls: '+ window.openHTTPs); return response;"
                    + "      } else {"
                    + "          window.openHTTPs--; console.log('Call fails. Remaining active calls: ' + window.openHTTPs);  return response;"
                    + "      };})" + "};" + "var oldOpen = XMLHttpRequest.prototype.open;"
                    + "XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {"
                    + "window.openHTTPs++; console.log('xml ajax called');"
                    + "this.addEventListener('readystatechange', function() {" + "if(this.readyState == 4) {"
                    + "window.openHTTPs--; console.log('xml ajax complete');" + "}" + "}, false);"
                    + "oldOpen.call(this, method, url, async, user, pass);" + "}" +

                    "})();";
            jsDriver.executeScript(script);
        } else {
            System.out.println("Web driver: " + driver + " cannot execute javascript");
        }
    } catch (Exception e) {
        System.out.println(e);
    }
}
0
BatBold

Si le contrôle que vous attendez est un élément Web "Ajax", le code suivant l'attendra ou tout autre élément Web Ajax pour terminer le chargement ou effectuer toute autre opération nécessaire afin de pouvoir continuer en toute sécurité .

    public static void waitForAjaxToFinish() {

    WebDriverWait wait = new WebDriverWait(driver, 10);

    wait.until(new ExpectedCondition<Boolean>() { 
        public Boolean apply(WebDriver wdriver) { 
            return ((JavascriptExecutor) driver).executeScript(
                    "return jQuery.active == 0").equals(true);
        }
    }); 

}
0
Bill Hileman