📜 ⬆️ ⬇️

Selenium: waiting for all AJAX requests to complete

Recently, a lot of different AJAX applications have been divorced. In fact, automating the testing of such an application does not differ from automating the testing of a regular WEB application, but there are several subtleties. One of the subtleties is just waiting for the completion of all AJAX requests. For example, if marking a certain checkbox on a page causes an update to be selected by an AJAX request, then the test, which immediately after marking selects a specific option, will fall off, because This option'a will not be there. And all because the test itself is executed much faster than the AJAX request for updating the list.

In this case, the automator has several outputs.

Put sleep after checkbox mark.
This is the worst and, unfortunately, the most commonly used solution.
We do not know in advance how long it will take to execute an AJAX request, respectively, we will have to set the waiting time based on the minimum sufficient for most cases. For example, 5 seconds. When there are a lot of such expectations for 5 seconds, our tests will start to run for a very long time, even when all AJAX requests are executed quickly.
In addition, sometimes for various reasons, the execution time of an AJAX request can be 5.2 seconds, in such cases we will get false test drops, which is also bad.

Use the Wait class and wait until Selenium # isElementPresent returns true for the desired option.
This method is already better, but still should not be applied, in the future I will write in detail why. Instead of the Wait class, it is better to use the Selenium # waitForCondition method, in which wait for the required element to appear.
')
Somehow, after checking the checkbox, wait for all AJAX requests to complete, and only then choose an option.
This method will be considered in more detail, because It is quite versatile and simple from the point of view of an automator.

Most WEB applications for working with AJAX use specialized libraries (jQuery, Prototype, Dojo, etc.), which provide the developer with a higher level of abstraction than the standard API, and therefore greater flexibility.

In order to wait for the completion of all AJAX requests in the Selenium test, you need to learn how to follow these requests globally. In the standard API, there is no possibility of installing global interceptors, but in almost all of the third-party libraries there is such an opportunity, although everywhere this is done in its own way. Here's an example of how you can wait for all AJAX requests to complete when using the jQuery library:
/** * Waits for all active jQuery AJAX requests to finish. * * @param timeout Timeout in milliseconds. * @throws SeleniumError If timeout is reached. */ Selenium.prototype.doWaitForJqueryAjaxRequests = function(timeout) { return Selenium.decorateFunctionWithTimeout(function() { return selenium.browserbot.getUserWindow().jQuery.active == 0; }, timeout); }; 
Here we simply wrap the condition we need (the number of active AJAX requests is zero) in the Selenium # decorateFunctionWithTimeout method, which will wait for this condition to be met within the timeout specified in the timeout, and if it waits, the method will be successfully completed, otherwise the SeleniumError exception will be thrown .

If we describe in a meta-language what we need to create a universal method of waiting, we get something like this:
  1. Determine which libraries are used to work with AJAX.
  2. Wait until all AJAX requests have been completed for each of the libraries used.

Everything is simple, it remains to implement it in JavaScript and connect as an extension to Selenium RC or to Selenium IDE, as you like. When using Selenium RC for greater versatility, you can load extension code using the DefaultSelenium # setExtensionJs method.

Here is the finished implementation (supported by jQuery, Prototype and Dojo):
 /** * Waits for all active AJAX requests to finish during specified timeout. Works only for AJAX requests which are * instantiated using one of the following frameworks: jQuery, Prototype, Dojo. Don't work (immediately returns without * any errors) if standard AJAX API or one of other frameworks is used to send XML HTTP request. * * @param timeout Timeout in milliseconds. * @throws SeleniumError If timeout is reached. */ Selenium.prototype.doWaitForAjaxRequests = function(timeout) { return Selenium.decorateFunctionWithTimeout(function() { var userWindow = selenium.browserbot.getUserWindow(); var isJqueryComplete = typeof(userWindow.jQuery) != 'function' || userWindow.jQuery.active == 0; var isPrototypeComplete = typeof(userWindow.Ajax) != 'function' || userWindow.Ajax.activeRequestCount == 0; var isDojoComplete = typeof(userWindow.dojo) != 'function' || userWindow.dojo.io.XMLHTTPTransport.inFlight.length == 0; return isJqueryComplete && isPrototypeComplete && isDojoComplete; }, timeout); }; 

If for writing tests not Selenese is used , but a normal programming language, then in order to be able to use the new method, it is necessary to expand the driver used by adding this method to it like this:
 import com.thoughtworks.selenium.CommandProcessor; import com.thoughtworks.selenium.DefaultSelenium; public class CustomSelenium extends DefaultSelenium { public CustomSelenium(String serverHost, int serverPort, String browserStartCommand, String browserURL) { super(serverHost, serverPort, browserStartCommand, browserURL); } public CustomSelenium(CommandProcessor processor) { super(processor); } /** * Waits for all active AJAX requests to finish during specified timeout. Works only for AJAX requests which are * instantiated using one of the following frameworks: jQuery, Prototype, Dojo. Don't work (immediately returns * without any errors) if standard AJAX API is used to send request. * * @param timeout Timeout in milliseconds. */ public void waitForAjaxRequests(final int timeout) { commandProcessor.doCommand("waitForAjaxRequests", new String[]{String.valueOf(timeout)}); } } 


Now we can easily replace such a test code:
 ... selenium.check("name=enableBender"); sleep(5000); selenium.select("name=mode", "label=Kill all humans"); ... 

On this:
 ... selenium.check("name=enableBender"); selenium.waitForAjaxRequests(60000); selenium.select("name=mode", "label=Kill all humans"); ... 

And the tests will be performed at a speed equal to the speed of the server response, i.e. without undue delay.

For some projects where AJAX requests begin to run immediately after the page loads (yes, there are such), I recommend overloading the waitForPageToLoad, waitForFrameToLoad and waitForPopUp methods by adding the last waitForAjaxRequests to them not to jerk it constantly in tests.

Finally, I repeat once again that in the standard API there is no possibility of installing global interceptors for AJAX requests, so this method will not work if developers use the standard API directly. Fortunately, in more or less serious projects this is not done. But it is quite possible that in some project it uses its own wrapper around the standard API, in which case you just need to support this wrapper in user-extensions.js.

Source: https://habr.com/ru/post/111649/


All Articles