how - Selenium WebDriver: espere a que se cargue la página compleja con JavaScript(JS)
selenium webdriver execute javascript (13)
Tengo una aplicación web para probar con selenio. Hay una gran cantidad de JavaScript ejecutándose en la carga de la página.
Este código JavaScript no está tan bien escrito pero no puedo cambiar nada. Entonces, esperar que un elemento aparezca en el DOM con el método findElement()
no es una opción.
Quiero crear una función genérica en Java para esperar a que se cargue una página, una posible solución sería:
- ejecute un script JavaScript desde WebDriver y almacene el resultado de
document.body.innerHTML
en unbody
variable de cadena. - compare la variable del
body
con la versión anterior delbody
. si son iguales, configure incrementar un contadornotChangedCount
contrario, establezcanotChangedCount
en cero. - espere un momento (50 ms por ejemplo).
- si la página no ha cambiado durante un tiempo (500 ms, por ejemplo) entonces
notChangedCount
> = 10, salga del bucle, de lo contrario, vaya al primer paso.
¿Crees que es una solución válida?
¿La biblioteca JS define / inicializa cualquier variable conocida en la ventana?
De ser así, podría esperar a que aparezca la variable. Puedes usar
((JavascriptExecutor)driver).executeScript(String script, Object... args)
para probar esta condición (algo así como: window.SomeClass && window.SomeClass.variable != null
) y devolver un booleano true
/ false
.
Envuelva esto en WebDriverWait
, y espere hasta que el script devuelva true
.
Aquí está mi propio código:
Window.setTimeout se ejecuta solo cuando el navegador está inactivo.
Por lo tanto, llamar a la función recursivamente (42 veces) tomará 100 ms si no hay actividad en el navegador y mucho más si el navegador está ocupado haciendo otra cosa.
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;
Como beneficio adicional, el contador se puede restablecer en document.readyState o en llamadas jQuery Ajax o si se están ejecutando algunas animaciones jQuery (solo si su aplicación usa jQuery para llamadas ajax ...)
...
" 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); " +
" } "+
...
EDITAR: Noto que executeAsyncScript no funciona bien si se carga una página nueva y la prueba puede dejar de responder de forma indefinida, mejor usar esto en su lugar.
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");
}
};
}
Debes esperar a que Javascript y jQuery terminen de cargarse. Ejecute Javascript para verificar si jQuery.active
es 0
y document.readyState
está complete
, lo que significa que la carga de JS y jQuery está completa.
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);
}
El siguiente código funciona perfectamente en mi caso: mi página contiene scripts complejos de Java
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;
}
}
}
Fuente: cómo esperar la página para cargar / listo en Selenium WebDriver
Gracias Ashwin!
En mi caso, debería necesitar esperar la ejecución de un plugin jquery en algún elemento ... específicamente "qtip"
basado en su pista, funcionó perfectamente para mí:
wait.until( new Predicate<WebDriver>() {
public boolean apply(WebDriver driver) {
return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
}
}
);
Nota: estoy usando Webdriver 2
Les pedí a mis desarrolladores que crearan una variable de JavaScript "isProcessing" a la que yo pueda acceder (en el objeto "ae") que ellos configuran cuando las cosas comienzan a ejecutarse y se borran cuando las cosas están hechas. A continuación, lo ejecuto en un acumulador que lo comprueba cada 100 ms hasta que obtiene cinco en una fila durante un total de 500 ms sin ningún cambio. Si pasan 30 segundos, tiro una excepción porque algo debería haber sucedido para entonces. Esto está en 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.");
}
Para hacerlo correctamente, debe manejar las excepciones.
Así es como espero un iFrame. Esto requiere que su clase de prueba JUnit pase la instancia de RemoteWebDriver al objeto de la página:
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") ) );
}
....
NOTA: Aquí puede ver mi ejemplo completo de trabajo .
Para la biblioteca de Selenium nodejs
, utilicé el siguiente fragmento. En mi caso, estaba buscando dos objetos que se agregan a la ventana, que en este ejemplo son <SOME PROPERTY>
, 10000
es el tiempo de espera milisegundos, <NEXT STEP HERE>
es lo que sucede después de que las propiedades se encuentran en la ventana.
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);
});
Puedes escribir algo de lógica para manejar esto. He escrito un método que devolverá WebElement
y este método se llamará tres veces o puede aumentar el tiempo y agregar una verificación nula para WebElement
Aquí hay un ejemplo
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;
}
}
Se pueden usar dos condiciones para verificar si la página se carga antes de encontrar algún elemento en la página:
WebDriverWait wait = new WebDriverWait(driver, 50);
El uso de redayState a continuación esperará hasta que se cargue la página
wait.until((ExpectedCondition<Boolean>) wd ->
((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
A continuación JQuery esperará hasta que los datos no se hayan cargado
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++;
}
}
Después de estos JavaScriptCode intente encontrar Out webElement.
WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));
Si alguien realmente supiera una respuesta general y siempre aplicable, se habría implementado en todas partes hace siglos y haría que nuestras vidas sean mucho más fáciles.
Hay muchas cosas que puede hacer, pero cada una de ellas tiene un problema:
Como dijo Ashwin Prabhu, si conoces bien el guión, puedes observar su comportamiento y rastrear algunas de sus variables en la
window
odocument
etc. Esta solución, sin embargo, no es para todos y solo puede ser utilizada por ti y solo en un sitio limitado. conjunto de páginas.Su solución al observar el código HTML y si se ha modificado o no durante algún tiempo no es malo (también hay un método para obtener el HTML original y no editado directamente por
WebDriver
), pero:- Lleva mucho tiempo afirmar una página y podría prolongar significativamente la prueba.
- Nunca se sabe cuál es el intervalo correcto. El script podría estar descargando algo grande que lleva más de 500 ms. Hay varios guiones en la página interna de nuestra empresa que demoran varios segundos en IE. Es posible que su computadora no cuente con los recursos necesarios, por ejemplo, que un antivirus hará que su CPU funcione a pleno rendimiento, y que 500 ms pueden ser demasiado cortos incluso para un script no complejo.
- Algunos guiones nunca terminan. Se llaman a sí mismos con un poco de retraso (
setTimeout()
) y funcionan una y otra vez y posiblemente podrían cambiar el HTML cada vez que se ejecutan. En serio, cada página "Web 2.0" lo hace. Incluso . Podría sobrescribir los métodos más comunes utilizados y considerar los scripts que los usan como completos, pero ... no puede estar seguro. - ¿Qué pasa si el script hace algo más que cambiar el HTML? Podría hacer miles de cosas, no solo alguna diversión
innerHTML
.
Hay herramientas para ayudarte en esto. Es decir, los oyentes de progreso junto con nsIWebProgressListener y algunos otros. El soporte del navegador para esto, sin embargo, es horrible. Firefox comenzó a tratar de soportarlo desde FF4 en adelante (aún en evolución), IE tiene soporte básico en IE9.
Y creo que podría encontrar otra solución defectuosa pronto. El hecho es que no hay una respuesta definitiva sobre cuándo decir "ahora la página está completa" debido a los guiones eternos que hacen su trabajo. Elija el que le sirva mejor, pero tenga cuidado con sus deficiencias.
Si todo lo que necesita hacer es esperar a que el html en la página se estabilice antes de tratar de interactuar con los elementos, puede sondear el DOM periódicamente y comparar los resultados, si los DOM son los mismos dentro del tiempo de encuesta dado, usted está dorado. Algo así como que pasas el tiempo máximo de espera y el tiempo entre las encuestas de la página antes de comparar. Simple y efectivo.
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;
}
}
}
Tuve un problema. Esta solución funciona para mí desde WebDriverDoku:
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));