J'ai une application Web à tester avec Selenium. Il y a beaucoup de JavaScript en cours de chargement de la page.
Ce code JavaScript n’est pas si bien écrit, mais je ne peux rien changer . Donc, attendre qu’un élément apparaisse dans le DOM avec la méthode findElement()
n’est pas une option.
Je veux créer une fonction générique en Java pour attendre le chargement d’une page, une solution possible serait:
document.body.innerHTML
dans une variable chaîne body
.body
à la version précédente de body
. si elles sont identiques, définir incrémenter un compteur notChangedCount
sinon définir notChangedCount
à zéro.notChangedCount >= 10
puis quittez la boucle sinon passez en boucle à la première étape.Pensez-vous que c'est une solution valable?
Si quelqu'un connaissait réellement une réponse générale et toujours applicable, elle aurait été appliquée partout il y a très longtemps et faciliterait grandement nos vies SO.
Vous pouvez faire beaucoup de choses, mais chacune d’elles a un problème:
Comme Ashwin Prabhu l'a dit, si vous connaissez bien le script, vous pouvez en observer le comportement et en suivre certaines variables sur window
ou document
etc. Cette solution ne convient toutefois pas à tout le monde et ne peut être utilisée que par vous et uniquement sur une base limitée. ensemble de pages.
Votre solution en observant le code HTML et en indiquant si elle a été modifiée ou non pendant un certain temps n’est pas mauvaise (il existe également une une méthode pour obtenir le code HTML original et non édité directement par WebDriver
), mais:
setTimeout()
) et travaillent encore et encore et peuvent éventuellement changer le code HTML à chaque exécution. Sérieusement, chaque page "Web 2.0" le fait. Même débordement de pile. Vous pouvez écraser les méthodes les plus couramment utilisées et considérer que les scripts qui les utilisent sont terminés, mais ... vous ne pouvez pas en être sûr.innerHTML
.Il existe des outils pour vous aider à ce sujet. À savoir Progress Listeners avec nsIWebProgressListener et quelques autres. Le support du navigateur pour cela, cependant, est horrible. Firefox a commencé à essayer de le prendre en charge à partir de FF4 (en évolution constante), IE dispose d’un support de base dans IE9.
Et je suppose que je pourrais bientôt proposer une autre solution défectueuse. Le fait est - il n'y a pas de réponse précise sur le moment de dire "maintenant la page est complète" à cause des scripts éternels faisant leur travail. Choisissez celui qui vous sert le mieux, mais méfiez-vous de ses inconvénients.
Merci Ashwin!
Dans mon cas, je devrais avoir besoin d'attendre l'exécution d'un plugin jquery dans un élément .. spécifiquement "qtip"
basé sur votre indice, cela a parfaitement fonctionné pour moi:
wait.until( new Predicate<WebDriver>() {
public boolean apply(WebDriver driver) {
return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
}
}
);
Note: J'utilise Webdriver 2
Vous devez attendre que Javascript et jQuery aient fini de charger . Exécutez Javascript pour vérifier si jQuery.active
est 0
et document.readyState
est complete
, ce qui signifie que la charge de JS et jQuery est terminée.
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);
}
La bibliothèque JS définit-elle/initialise-t-elle une variable connue de la fenêtre?
Si tel est le cas, vous pouvez attendre que la variable apparaisse. Vous pouvez utiliser
((JavascriptExecutor)driver).executeScript(String script, Object... args)
pour tester cette condition (quelque chose comme: window.SomeClass && window.SomeClass.variable != null
) et renvoyer un booléen true
/false
.
Emballez ceci dans un WebDriverWait
, et attendez que le script retourne true
.
Si tout ce que vous avez à faire est d’attendre que le code HTML de la page devienne stable avant d’essayer d’interagir avec des éléments, vous pouvez interroger le DOM périodiquement et comparer les résultats. d'or. Quelque chose comme ceci, où vous passez le temps d'attente maximum et le temps entre les interrogations de page avant la comparaison. Simple et efficace.
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;
}
}
}
Le code ci-dessous fonctionne parfaitement dans mon cas - ma page contient des scripts Java complexes
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;
}
}
}
Source - Comment attendre que la page soit chargée/prête dans Selenium WebDriver
J'ai eu le même problème. Cette solution fonctionne pour moi de WebDriverDoku:
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));
Pour la bibliothèque nodejs
Selenium, j'ai utilisé l'extrait de code suivant. Dans mon cas, je recherchais deux objets ajoutés à la fenêtre, qui sont dans cet exemple <SOME PROPERTY>
, 10000
est le délai d'expiration millisecondes, <NEXT STEP HERE>
est ce qui se produit une fois que les propriétés sont trouvées dans la fenêtre.
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);
});
J'ai demandé à mes développeurs de créer une variable JavaScript "isProcessing" à laquelle je peux accéder (dans l'objet "ae") qu'ils ont définie quand les choses commencent à s'exécuter et qui est claire quand les choses sont faites. Je le lance ensuite dans un accumulateur qui le vérifie toutes les 100 ms jusqu'à ce qu'il en obtienne cinq de suite pour un total de 500 ms sans aucune modification. Si 30 secondes s'écoulent, je jette une exception car quelque chose aurait dû arriver d'ici là. Ceci 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.");
}
Voici mon propre code:
Window.setTimeout ne s’exécute que lorsque le navigateur est inactif.
Si vous appelez la fonction récursivement (42 fois), cela prendra 100 ms s'il n'y a pas d'activité dans le navigateur et beaucoup plus si le navigateur est occupé à faire autre chose.
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;
En bonus, le compteur peut être réinitialisé sur document.readyState ou sur les appels jQuery Ajax ou si des animations jQuery sont en cours d'exécution (uniquement si votre application utilise jQuery pour les appels 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); " +
" } "+
...
EDIT: Je remarque que executeAsyncScript ne fonctionne pas bien si une nouvelle page est chargée et que le test peut cesser de répondre indéfiniment. Il est préférable de l’utiliser à la place.
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");
}
};
}
Deux conditions peuvent être utilisées pour vérifier si la page est chargée avant de rechercher un élément sur la page:
WebDriverWait wait = new WebDriverWait(driver, 50);
Utiliser ci-dessous redayState attendra le chargement de la page
wait.until((ExpectedCondition<Boolean>) wd ->
((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
En dessous, JQuery attendra que les données n'aient pas été chargées.
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++;
}
}
Après ces JavaScriptCode, essayez de trouverOut webElement.
WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));
Vous pouvez écrire une logique pour gérer cela. J'ai écrit une méthode qui retournera le WebElement
et cette méthode sera appelée trois fois ou vous pouvez augmenter le temps et ajouter un contrôle nul pour 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;
}
}
Pour le faire correctement, vous devez gérer les exceptions.
Voici comment je fais une attente pour un iFrame. Cela nécessite que votre classe de test JUnit transmette l'instance de RemoteWebDriver à l'objet page:
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") ) );
}
....
NOTE: Vous pouvez voir tout mon exemple de travail ici .
J'aime votre idée d'interroger le code HTML jusqu'à ce qu'il soit stable. Je peux ajouter cela à ma propre solution. L'approche suivante est en C # et nécessite jQuery.
Je suis le développeur d'un projet de test SuccessFactors (SaaS) dans lequel nous n'avons aucune influence sur les développeurs ou les caractéristiques du DOM derrière la page Web. Le produit SaaS peut potentiellement changer sa conception sous-jacente du DOM 4 fois par an. La recherche de méthodes de test robustes et performantes avec Selenium est donc toujours active (y compris les tests NOT avec Selenium lorsque cela est possible!)
Voici ce que j'utilise pour "page prête". Cela fonctionne dans tous mes propres tests actuellement. La même approche a également fonctionné pour une grande application web Java interne il y a quelques années et était robuste depuis plus d'un an au moment où j'ai quitté le projet.
Driver
est l'instance WebDriver qui communique avec le navigateur.DefaultPageLoadTimeout
est une valeur de délai d'attente en ticks (100ns par tick)public IWebDriver Driver { get; private set; }
// ...
const int GlobalPageLoadTimeOutSecs = 10;
static readonly TimeSpan DefaultPageLoadTimeout =
new TimeSpan((long) (10_000_000 * GlobalPageLoadTimeOutSecs));
Driver = new FirefoxDriver();
Dans ce qui suit, notez l'ordre des attentes dans la méthode PageReady
(document Selenium ready, Ajax, animations), ce qui est logique si vous y réfléchissez:
Quelque chose comme votre approche de comparaison DOM pourrait être utilisé entre 1 et 2 pour ajouter une autre couche de robustesse.
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");
}