Prueba AngularJS con selenio
selenium c#-4.0 (11)
Tengo la aplicación SPA en la pila ASP MVC + AngularJS y me gustaría probar la interfaz de usuario. Por ahora, estoy probando Selenium con los controladores PhantomJS y WebKit.
Página de prueba - ver con un solo elemento - la lista de <li>
que se carga dinámicamente desde el servidor y se une por Angular.
<div id="items">
<li>text</li>
<li>text2</li>
</div>
Estoy tratando de pasar la prueba
_driver.FindElements(By.TagName(''li''))
El problema es que en este momento no hay elementos cargados, y _driver.PageSource no contiene elementos también.
¿Cómo puedo esperar para cargar los artículos? Por favor, no sugieras Thread.Sleep ()
Al lado de la sugerencia de Eddiec. Si prueba una aplicación AngularJS, le sugiero que piense en un transportador
El transportador le ayudará a resolver el asunto de espera (sincronización, asincrónico). Sin embargo, hay algunas notas
1 - Necesita desarrollar su prueba en javascript
2 - Hay algunos mecanismos diferentes para manejar el flujo
Aquí hay un ejemplo de cómo esperar en Angular si está usando WebDriverJS . Originalmente pensé que tenías que crear una condición personalizada, pero wait
acepta cualquier función.
// Wait for Angular to Finish
function angularReady(): any {
return $browser.executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get(''$http'').pendingRequests.length === 0)")
.then(function(angularIsReady) {
return angularIsReady === true;
});
}
$browser.wait(angularReady, 5000).then(...);
Lamentablemente, esto no funciona con PhantomJS debido a CSP (content-security-policy) y unsafe-eval
. No puedo esperar al Chrome 59 sin cabeza en Windows .
Cree una nueva clase que le permita averiguar si su sitio web usando AngularJS ha terminado de hacer llamadas AJAX, de la siguiente manera:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
public class AdditionalConditions {
public static ExpectedCondition<Boolean> angularHasFinishedProcessing() {
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver driver) {
return Boolean.valueOf(((JavascriptExecutor) driver).executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get(''$http'').pendingRequests.length === 0)").toString());
}
};
}
}
Puede usarlo en cualquier parte de su código utilizando el siguiente código:
WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);
wait.until(AdditionalConditions.angularHasFinishedProcessing()));
Esto esperará cargas de página / jquery.ajax (si está presente) y llamadas de $ http, y cualquier ciclo de resumen / renderización que lo acompañe, tírelo en una función de utilidad y espere.
/* C# Example
var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
pageLoadWait.Until<bool>(
(driver) =>
{
return (bool)JS.ExecuteScript(
@"*/
try {
if (document.readyState !== ''complete'') {
return false; // Page not loaded yet
}
if (window.jQuery) {
if (window.jQuery.active) {
return false;
} else if (window.jQuery.ajax && window.jQuery.ajax.active) {
return false;
}
}
if (window.angular) {
if (!window.qa) {
// Used to track the render cycle finish after loading is complete
window.qa = {
doneRendering: false
};
}
// Get the angular injector for this app (change element if necessary)
var injector = window.angular.element(''body'').injector();
// Store providers to use for these checks
var $rootScope = injector.get(''$rootScope'');
var $http = injector.get(''$http'');
var $timeout = injector.get(''$timeout'');
// Check if digest
if ($rootScope.$$phase === ''$apply'' || $rootScope.$$phase === ''$digest'' || $http.pendingRequests.length !== 0) {
window.qa.doneRendering = false;
return false; // Angular digesting or loading data
}
if (!window.qa.doneRendering) {
// Set timeout to mark angular rendering as finished
$timeout(function() {
window.qa.doneRendering = true;
}, 0);
return false;
}
}
return true;
} catch (ex) {
return false;
}
/*");
});*/
Hemos tenido un problema similar en el que nuestro framework interno se usa para probar varios sitios, algunos de ellos usan JQuery y otros usan AngularJS (y 1 incluso tiene una combinación). Nuestro marco está escrito en C # por lo que era importante que cualquier JScript que se ejecutara se realizara en fragmentos mínimos (para fines de depuración). En realidad, tomó muchas de las respuestas anteriores y las mezcló juntas (así que acrediten dónde se debe el crédito @npjohns). A continuación hay una explicación de lo que hicimos:
A continuación, se devuelve un verdadero / falso si el HTML DOM se ha cargado:
public bool DomHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
var hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
while (hasThePageLoaded == null || ((string)hasThePageLoaded != "complete" && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
if (timeout != 0) continue;
Console.WriteLine("The page has not loaded successfully in the time provided.");
return false;
}
return true;
}
Luego verificamos si se está utilizando JQuery:
public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor)
{
var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined");
return (bool)isTheSiteUsingJQuery;
}
Si se usa JQuery, verificamos que esté cargado:
public bool JqueryHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
var hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
while (hasTheJQueryLoaded == null || (!(bool) hasTheJQueryLoaded && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
if (timeout != 0) continue;
Console.WriteLine(
"JQuery is being used by the site but has failed to successfully load.");
return false;
}
return (bool) hasTheJQueryLoaded;
}
Luego hacemos lo mismo con AngularJS:
public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor)
{
string UsingAngular = @"if (window.angular){
return true;
}";
var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular);
return (bool) isTheSiteUsingAngular;
}
Si se está utilizando, verificamos que se haya cargado:
public bool AngularHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
string HasAngularLoaded =
@"return (window.angular !== undefined) && (angular.element(document.body).injector() !== undefined) && (angular.element(document.body).injector().get(''$http'').pendingRequests.length === 0)";
var hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
while (hasTheAngularLoaded == null || (!(bool)hasTheAngularLoaded && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
if (timeout != 0) continue;
Console.WriteLine(
"Angular is being used by the site but has failed to successfully load.");
return false;
}
return (bool)hasTheAngularLoaded;
}
Después de verificar que el DOM se haya cargado correctamente, puede usar estos valores de bool para realizar esperas personalizadas:
var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript));
var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));
Hice el siguiente código y me ayudó para las fallas de condición de carrera asíncrona.
$window._docReady = function () {
var phase = $scope.$root.$$phase;
return $http.pendingRequests.length === 0 && phase !== ''$apply'' && phase !== ''$digest'';
}
Ahora en el modelo Selenium PageObject, puedes esperar
Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();");
return result == null ? false : (Boolean) result;
Para mi problema particular con la página HTML que contiene iframes y desarrollado con AnglularJS, el siguiente truco me ahorró mucho tiempo: en el DOM vi claramente que hay un iframe que envuelve todo el contenido. Entonces, se supone que el siguiente código funciona:
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), ''Creative chooser'')]");
Pero no funcionaba y me dijo algo como "No se puede cambiar al encuadre. La ventana estaba cerrada". Luego modifiqué el código a:
driver.switchTo().defaultContent();
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), ''Creative chooser'')]");
Después de esto todo fue sin problemas. Así que, evidentemente, Angular estaba destruyendo algo con iframes y justo después de cargar la página cuando espera que el controlador se centre en el contenido predeterminado, se centró en algunos ya eliminados por el marco angular. Espero que esto pueda ayudar a algunos de ustedes.
Puedes simplemente extraer el transportador para fragmentos de código útiles. Esta función se bloquea hasta que Angular termine de renderizar la página. Es una variante de la respuesta de Shahzaib Salim, excepto que está sondeando y estoy programando una devolución de llamada.
def wait_for_angular(self, selenium):
self.selenium.set_script_timeout(10)
self.selenium.execute_async_script("""
callback = arguments[arguments.length - 1];
angular.element(''html'').injector().get(''$browser'').notifyWhenNoOutstandingRequests(callback);""")
Reemplaza ''html'' por cualquier elemento que sea tu ng-app
.
Si está usando AngularJS, entonces usar Protractor es una buena idea.
Si usa transportador puede usar su método waitForAngular () que esperará a que se completen las solicitudes http. Sigue siendo una buena práctica esperar a que se muestren los elementos antes de actuar sobre ellos. Dependiendo de su lenguaje y de su implementación, esto podría verse en un lenguaje sincrónico.
WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));
O en JS puede usar el método de espera que ejecuta una función hasta que devuelve verdadero
browser.wait(function () {
return browser.driver.isElementPresent(elementToFind);
});
Si no desea hacer el cambio completo a Transportador pero desea esperar a Angular, le recomiendo usar Paul Hammants ngWebDriver (Java). Está basado en transportador pero no tienes que hacer el cambio.
Resolví el problema escribiendo una clase de acciones en la que esperé Angular (utilizando ngWebDriver''s waitForAnularRequestsToFinish ()) antes de llevar a cabo las acciones (hacer clic, completar, verificar, etc.).
Para ver un fragmento de código, vea mi respuesta a esta pregunta
Si su aplicación web está realmente creada con Angular como usted dice, la mejor manera de realizar pruebas de extremo a extremo es con Protractor .
Internamente, el transportador utiliza su propio método waitForAngular
para garantizar que el transportador de waitForAngular
espere automáticamente hasta que Angular haya terminado de modificar el DOM.
Por lo tanto, en el caso normal, nunca necesitaría escribir una wait
explícita en sus casos de prueba: Transportador lo hace por usted.
Puede consultar el tutorial de Angular Phonecat para aprender a configurar Transportador.
Si quiere usar Protractor en serio, querrá adoptar los pageobjects . Si desea un ejemplo de eso, eche un vistazo a mi paquete de prueba de objetos de página para Angular Phonecat.
Con Protractor escribes tus pruebas en Javascript (el transportador se basa en realidad en el nodo), y no en C #, pero a cambio, Protractor maneja todo lo que te espera.