En el último artículo hablé sobre algunas ideas y patrones, como el patrón de Objeto de página, que ayudan a escribir pruebas de IU que se pueden mantener. En este artículo, vamos a discutir algunos temas avanzados que podrían ayudarlo a escribir pruebas más sólidas y solucionarlos cuando fallan:
Voy a utilizar Selenium para los temas de automatización del navegador tratados en este artículo.
Al igual que en el artículo anterior, los conceptos y soluciones discutidos en este artículo son aplicables independientemente del idioma y el marco de la interfaz de usuario que utilice. Antes de continuar, lea el artículo anterior, ya que me referiré a él y a su código de ejemplo varias veces. No te preocupes Esperare aquí.
Añadiendo Hilo de dormir
(o retrasos en general) se siente como un truco inevitable cuando se trata de pruebas de UI. Tiene una prueba que falla intermitentemente y, después de algunas investigaciones, la remonta hasta retrasos ocasionales en la respuesta; Por ejemplo, navega a una página y busca o afirma algo antes de que la página esté completamente cargada y el marco de automatización de su navegador arroje una excepción que indique que el elemento no existe. Muchas cosas podrían contribuir a este retraso. Por ejemplo:
O una mezcla de estos y otros temas..
Digamos que tiene una página que normalmente demora menos de un segundo en cargarse, pero las pruebas que se producen fallan de vez en cuando debido a un retraso ocasional en la respuesta. Tienes pocas opciones:
Usted ve, no hay victorias con retrasos arbitrarios: o bien obtiene un conjunto de pruebas lento o frágil. Aquí voy a mostrarle cómo evitar insertar retrasos fijos en sus pruebas. Vamos a discutir dos tipos de retrasos que deberían cubrir casi todos los casos con los que debe lidiar: agregar un retraso global y esperar a que suceda algo.
Si todas las páginas tardan aproximadamente el mismo tiempo en cargarse, lo que es más largo de lo esperado, la mayoría de las pruebas fallarán debido a una respuesta inoportuna. En casos como este puedes usar Esperas Implícitas:
Una espera implícita es decirle a WebDriver que consulte el DOM durante un período de tiempo determinado al intentar encontrar un elemento o elementos si no están disponibles de inmediato. La configuración predeterminada es 0. Una vez establecida, la espera implícita se establece para la vida de la instancia del objeto WebDriver.
Así es como se establece una espera implícita:
Controlador WebDriver = nuevo FirefoxDriver (); driver.Manage (). Timeouts (). ImplicitlyWait (TimeSpan.FromSeconds (5));
De esta manera le estás diciendo a Selenium que espere hasta 5 segundos cuando intenta encontrar un elemento o interactuar con la página. Así que ahora puedes escribir:
driver.Url = "http: // somedomain / url_that_delays_loading"; IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
en lugar de:
driver.Url = "http: // somedomain / url_that_delays_loading"; Thread.Sleep (5000); IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
El beneficio de este enfoque es que FindElement
volverá tan pronto como encuentre el elemento y no espere los 5 segundos completos cuando el elemento esté disponible antes.
Una vez que la espera implícita se establece en su WebDriver
instancia se aplica a todas las acciones en el controlador; para que puedas deshacerte de muchos Hilo de dormir
s en tu codigo.
5 segundos es la espera que hice para este artículo: debe encontrar la espera implícita óptima para su aplicación y debe hacer que la espera sea lo más corta posible. De las documentaciones API:
El aumento del tiempo de espera implícito de la espera se debe usar con prudencia, ya que tendrá un efecto adverso en el tiempo de ejecución de la prueba, especialmente cuando se usa con estrategias de ubicación más lentas como XPath.
Incluso si no usa XPath, el uso de largas esperas implícitas ralentiza sus pruebas, especialmente cuando algunas pruebas realmente fallan, porque el controlador web va a esperar mucho tiempo antes de que se agote el tiempo y arroje una excepción.
El uso de la espera implícita es una excelente manera de deshacerse de muchos retrasos codificados en su código; pero aún va a encontrarse en una situación en la que necesita agregar algunos retrasos fijos en su código porque está esperando que suceda algo: una página es más lenta que todas las demás páginas y tiene que esperar más, está esperando a que finalice una llamada AJAX o que aparezca un elemento en la página o desaparezca de la página, etc. Aquí es donde necesita esperas explícitas.
Así que ha establecido la espera implícita en 5 segundos y funciona para muchas de sus pruebas; pero todavía hay algunas páginas que a veces tardan más de 5 segundos en cargarse y resultan en pruebas fallidas.
Como nota al margen, debes investigar por qué una página tarda tanto tiempo antes de intentar corregir la prueba rota haciendo que espere más. Puede haber un problema de rendimiento en la página que lleva a la prueba roja, en cuyo caso debería corregir la página, no la prueba.
En el caso de una página lenta, puede reemplazar los retrasos fijos con esperas explícitas:
Una espera explícita es el código que usted define para esperar que ocurra una determinada condición antes de continuar con el código..
Puedes aplicar esperas explícitas usando WebDriverWait
clase. WebDriverWait
vive en WebDriver.Support
montaje y se puede instalar utilizando Selenium.Support nuget:
////// Proporciona la capacidad de esperar una condición arbitraria durante la ejecución de la prueba. /// clase pública WebDriverWait: DefaultWait/// /// Inicializa una nueva instancia de la /// La instancia de WebDriver solía esperar..El valor de tiempo de espera que indica cuánto tiempo debe esperar la condición. Public WebDriverWait (controlador IWebDriver, tiempo de espera de TimeSpan); ///clase. /// /// Inicializa una nueva instancia de la /// Un objeto que implementa elclase. /// interfaz utilizada para determinar cuándo ha pasado el tiempo.La instancia de WebDriver solía esperar..El valor de tiempo de espera que indica cuánto tiempo debe esperar la condición.UNA valor que indica la frecuencia con la que se comprueba si la condición es verdadera. WebDriverWait pública (reloj IClock, controlador IWebDriver, timeSpan timeout, TimeSpan sleepInterval);
Aquí hay un ejemplo de cómo puedes usar WebDriverWait
en tus pruebas:
driver.Url = "http: // somedomain / url_that_takes_a_long_time_to_load"; WebDriverWait wait = new WebDriverWait (controlador, TimeSpan.FromSeconds (10)); var myDynamicElement = wait.Until (d => d.FindElement (By.Id ("someElement")));
Le estamos diciendo a Selenium que queremos que espere esta página / elemento en particular por hasta 10 segundos.
Es probable que tenga algunas páginas que demoren más que su espera implícita predeterminada y no es una buena práctica de codificación repetir este código en todas partes. Después de todo Código de prueba es código. En su lugar, podría extraer esto en un método y usarlo de sus pruebas:
public IWebElement FindElementWithWait (By by, int secondsToWait = 10) var wait = new WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); return wait.Until (d => d.FindElement (by));
Entonces puedes usar este método como:
var slowPage = new SlowPage ("http: // somedomain / url_that_takes_a_long_time_to_load"); var element = slowPage.FindElementWithWait (By.Id ("someElement"));
Este es un ejemplo artificial para mostrar cómo podría verse el método y cómo podría usarse. Lo ideal sería mover todas las interacciones de la página a los objetos de la página..
Veamos otro ejemplo de una espera explícita. A veces, la página está completamente cargada, pero el elemento aún no está allí porque luego se carga como resultado de una solicitud AJAX. Tal vez no sea un elemento lo que está esperando, pero solo desea esperar a que finalice una interacción AJAX antes de poder hacer una afirmación, por ejemplo, en la base de datos. Nuevamente aquí es donde la mayoría de los desarrolladores usan Hilo de dormir
para asegurarse de que, por ejemplo, esa llamada AJAX se haya realizado y el registro esté ahora en la base de datos antes de pasar a la siguiente línea de la prueba. Esto puede ser fácilmente rectificado utilizando la ejecución de JavaScript.!
La mayoría de los marcos de automatización del navegador le permiten ejecutar JavaScript en la sesión activa, y Selenium no es una excepción. En Selenium hay una interfaz llamada IJavaScriptExecutor
con dos métodos:
////// Define la interfaz a través de la cual el usuario puede ejecutar JavaScript. /// interfaz pública IJavaScriptExecutor ////// Ejecuta JavaScript en el contexto del marco o ventana seleccionado actualmente. /// /// El código JavaScript para ejecutar.. ////// El valor devuelto por el script. /// objeto ExecuteScript (secuencia de comandos de cadena, objeto params [] args); ////// Ejecuta JavaScript de forma asíncrona en el contexto del marco o ventana actualmente seleccionado. /// /// El código JavaScript para ejecutar.. ////// El valor devuelto por el script. /// object ExecuteAsyncScript (secuencia de comandos de cadena, objeto params [] args);
Esta interfaz es implementada por RemoteWebDriver
que es la clase base para todas las implementaciones de controladores web. Así que en su instancia de controlador web puede llamar EjecutarScript
para ejecutar un script de JavaScript. Aquí hay un método que puede usar para esperar a que todas las llamadas AJAX terminen (asumiendo que está usando jQuery):
// Se supone que esto reside en una clase que tiene acceso a la instancia 'WebDriver' activa a través del campo / propiedad 'WebDriver'. public void WaitForAjax (int secondsToWait = 10) var wait = new WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); wait.Until (d => (bool) ((IJavaScriptExecutor) d) .ExecuteScript ("return jQuery.active == 0"));
Combinar la EjecutarScript
con WebDriverWait
y usted puede deshacerse de Hilo de dormir
agregado para llamadas AJAX.
jQuery.active
devuelve el número de llamadas AJAX activas iniciado por jQuery; así que cuando es cero no hay llamadas AJAX en progreso. Obviamente, este método solo funciona si todas las solicitudes AJAX son iniciadas por jQuery. Si está utilizando otras bibliotecas de JavaScript para las comunicaciones AJAX, debe consultar la documentación de su API para un método equivalente o realizar un seguimiento de las llamadas AJAX..
Con la espera explícita, puede establecer una condición y esperar hasta que se cumpla o hasta que caduque el tiempo de espera. Vimos cómo podríamos verificar que las llamadas AJAX terminen, otro ejemplo es verificar la visibilidad de un elemento. Al igual que la verificación AJAX, puede escribir una condición que verifique la visibilidad de un elemento; Pero hay una solución más fácil para eso que se llama. Condición esperada
.
De la documentación de Selenium:
Hay algunas condiciones comunes que se presentan con frecuencia al automatizar los navegadores web.
Si estás usando Java estás de suerte porque Condición esperada
La clase en Java es bastante extensa y tiene muchos métodos de conveniencia. Puedes encontrar la documentación aquí..
.Los desarrolladores de redes no son tan afortunados. Todavía hay un Condiciones esperadas
clase en WebDriver.Support
montaje (documentado aquí) pero es muy mínimo:
Condiciones públicas previas de clase sellada ////// Una expectativa para comprobar el título de una página. /// /// El título esperado, que debe ser una coincidencia exacta.. ////// Funciones públicas estáticascuando el titulo coincide; de otra manera, . /// TitleIs (título de la cadena); /// /// La expectativa de comprobar que el título de una página contiene una subcadena que distingue entre mayúsculas y minúsculas. /// /// El fragmento del título esperado.. ////// Funciones públicas estáticascuando el titulo coincide; de otra manera, . /// TitleContains (título de la cadena); /// /// Una expectativa de comprobar que un elemento está presente en el DOM de una página ///. Esto no significa necesariamente que el elemento sea visible. /// /// El localizador usado para encontrar el elemento.. ////// Los Funciones públicas estáticasuna vez que se encuentra. /// ElementExists (por localizador); /// /// Una expectativa de comprobar que un elemento está presente en el DOM de una página /// y visible. Visibilidad significa que el elemento no solo se muestra, sino que /// también tiene un alto y ancho que es mayor que 0. /// /// El localizador usado para encontrar el elemento.. ////// Los Funciones públicas estáticasUna vez localizado y visible. /// ElementIsVisible (por localizador);
Puedes usar esta clase en combinación con WebDriverWait
:
var wait = new WebDriverWait (controlador, TimeSpan.FromSeconds (3)) var element = wait.Until (ExpectedConditions.ElementExists (By.Id ("foo")));
Como puede ver en la firma de la clase anterior, puede verificar el título o partes del mismo y la existencia y visibilidad de los elementos usando Condición esperada
. El soporte listo para usar en .Net puede ser muy mínimo; pero esta clase no es más que una envoltura alrededor de algunas condiciones simples. Puede implementar fácilmente otras condiciones comunes en una clase y usarlas con WebDriverWait
de tus guiones de prueba.
Otra joya solo para desarrolladores de Java es FluentWait
. Desde la página de documentación., FluentWait
es
Una implementación de la interfaz de espera que puede tener su tiempo de espera y su intervalo de sondeo configurados sobre la marcha. Cada instancia de FluentWait define la cantidad máxima de tiempo para esperar una condición, así como la frecuencia con la que se verifica la condición. Además, el usuario puede configurar la espera para ignorar tipos específicos de excepciones mientras espera, como NoSuchElementExceptions al buscar un elemento en la página.
En el siguiente ejemplo estamos tratando de encontrar un elemento con id foo
en la página de sondeo cada cinco segundos hasta 30 segundos:
// Esperando 30 segundos para que un elemento esté presente en la página, comprobando // su presencia una vez cada cinco segundos. Espereespera = nueva FluentWait (driver) .withTimeout (30, SECONDS) .pollingEvery (5, SECONDS) .ignoring (NoSuchElementException.class); WebElement foo = wait.until (nueva función () public WebElement apply (controlador de WebDriver) return driver.findElement (By.id ("foo")); );
Hay dos cosas sobresalientes sobre FluentWait
: en primer lugar, le permite especificar el intervalo de sondeo que podría mejorar el rendimiento de su prueba y, en segundo lugar, le permite ignorar las excepciones en las que no está interesado.
FluentWait
es bastante impresionante y sería genial si existiera un equivalente en .Net también. Dicho esto, no es tan difícil implementarlo usando WebDriverWait
.
Tienes los objetos de página en su lugar, tienes un buen código de prueba que se puede mantener en SECO y también evitas retrasos fijos en tus pruebas; pero tus pruebas siguen fallando!
La interfaz de usuario suele ser la parte cambiada con más frecuencia de una aplicación típica: a veces mueves elementos en una página para cambiar el diseño de la página y, a veces, los cambios en la estructura de la página según los requisitos. Estos cambios en el diseño y el diseño de la página podrían llevar a una gran cantidad de pruebas fallidas si no elige sabiamente sus selectores.
No utilice selectores difusos y no confíe en la estructura de su página.
Muchas veces me han preguntado si está bien agregar una identificación a los elementos de la página solo para las pruebas, y la respuesta es un rotundo sí. Para hacer que nuestro código de unidad sea verificable, le hacemos muchos cambios, como agregar interfaces y usar la inyección de dependencia. Código de prueba es el código. Haz lo que sea necesario para apoyar tus pruebas.
Digamos que tenemos una página con la siguiente lista:
En una de mis pruebas quiero hacer clic en el álbum "Let There Be Rock". Estaría pidiendo problemas si usara el siguiente selector:
Por.XPath ("// ul [@ id = 'lista de álbumes'] / li [3] / a")
Cuando sea posible, debe agregar ID a los elementos y apuntarlos directamente y sin confiar en los elementos que los rodean. Así que voy a hacer un pequeño cambio en la lista:
Yo he añadido carné de identidad
atributos a anclajes basados en la identificación única de los álbumes para que podamos dirigir un enlace directamente sin tener que pasar por ul
y li
elementos. Así que ahora puedo reemplazar el selector frágil con Por.Id ("album-35")
que está garantizado para funcionar siempre que el álbum esté en la página, lo que por cierto también es una buena afirmación. Para crear ese selector, obviamente tendría que tener acceso a la identificación del álbum desde el código de prueba.
Sin embargo, no siempre es posible agregar identificadores únicos a los elementos, como filas en una cuadrícula o elementos en una lista. En casos como este, puede usar clases CSS y atributos de datos HTML para adjuntar propiedades rastreables a sus elementos para una selección más fácil. Por ejemplo, si tenía dos listas de álbumes en su página, una como resultado de la búsqueda del usuario y otra para los álbumes sugeridos según las compras anteriores del usuario, puede diferenciarlos usando una clase de CSS en el ul
elemento, incluso si esa clase no se utiliza para diseñar la lista:
Si prefiere no tener clases de CSS no utilizadas, puede utilizar atributos de datos HTML y cambiar las listas a:
y:
Una de las principales razones por las que las pruebas de UI fallan es que no se encuentra un elemento o texto en la página. A veces esto sucede porque llega a una página incorrecta debido a errores de navegación, a cambios en las navegaciones de páginas de su sitio web oa errores de validación. Otras veces podría ser debido a una página faltante o un error del servidor.
Independientemente de las causas del error y de si obtiene esto en su registro de servidor de CI o en su consola de prueba de escritorio, NoSuchElementException
(o similar) no es del todo útil para averiguar qué salió mal, ¿verdad? Entonces, cuando su prueba falla, la única manera de solucionar el error es ejecutarlo nuevamente y verlo como falla. Existen algunos trucos que podrían evitarle volver a ejecutar las pruebas de UI lentas para la resolución de problemas. Una solución para esto es capturar una captura de pantalla cuando falla una prueba para que podamos consultarla más adelante..
Hay una interfaz en Selenium llamada ITakesScreenshot
:
////// Define la interfaz utilizada para tomar imágenes de captura de pantalla de la pantalla. /// interfaz pública ITakesScreenshot ////// Obtiene un /// ///Objeto que representa la imagen de la página en la pantalla. /// /// UNA Captura de pantalla GetScreenshot ();Objeto que contiene la imagen. ///
Esta interfaz se implementa mediante clases de controlador web y se puede utilizar de esta manera:
var screenshot = driver.GetScreenshot (); screenshot.SaveAsFile ("", ImageFormat.Png);
De esta manera, cuando una prueba falla porque estás en una página incorrecta, puedes resolverlo rápidamente al verificar la captura de pantalla capturada.
Sin embargo, incluso capturar capturas de pantalla no siempre es suficiente. Por ejemplo, es posible que vea el elemento que espera en la página, pero la prueba sigue fallando y dice que no lo encuentra, tal vez debido al selector incorrecto que conduce a una búsqueda de elementos no exitosa. Entonces, en lugar de (o para complementar) la captura de pantalla, también puede capturar la fuente de la página como html. Hay un Fuente de la página
propiedad en IWebDriver
interfaz (que es implementada por todos los controladores web):
////// Obtiene la fuente de la última página cargada por el navegador. /// ////// Si la página se ha modificado después de cargarla (por ejemplo, por JavaScript) /// no hay garantía de que el texto devuelto sea el de la página modificada. /// Consulte la documentación del controlador en particular que se está utilizando para /// determinar si el texto devuelto refleja el estado actual de la página /// o el último texto enviado por el servidor web. El origen de la página devuelto es una representación /// del DOM subyacente: no espere que tenga el formato /// o que se escape de la misma manera que la respuesta enviada desde el servidor web. /// cadena PageSource get;
Al igual que hicimos con ITakesScreenshot
podría implementar un método que capture la fuente de la página y la conserve en un archivo para su posterior inspección:
File.WriteAllText ("", driver.PageSource);
Realmente no desea capturar capturas de pantalla y fuentes de página de todas las páginas que visita y para las pruebas de aprobación; de lo contrario, tendrá que pasar por miles de ellos cuando algo salga mal. En su lugar, solo debe capturarlos cuando una prueba falla o de lo contrario, cuando necesite más información para solucionar problemas. Para evitar contaminar el código con demasiados bloques try-catch y para evitar duplicaciones de código, debe colocar todas las búsquedas y aseveraciones de sus elementos en una clase y envolverlos con try-catch y luego capturar la captura de pantalla y / o la fuente de la página en el bloque catch . Aquí hay un poco de código que podría usar para ejecutar acciones contra un elemento:
public void Execute (By by, Actionaction) try var element = WebDriver.FindElement (by); acción (elemento); catch var capturer = new Capturer (WebDriver); capturer.CaptureScreenshot (); capturer.CapturePageSource (); lanzar;
los Capturador
La clase puede ser implementada como:
clase pública Capturer cadena pública estática OutputFolder = Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "FailedTests"); privado readonly RemoteWebDriver _webDriver; Capturer público (RemoteWebDriver webDriver) _webDriver = webDriver; public void CaptureScreenshot (string fileName = null) var camera = (ITakesScreenshot) _webDriver; var screenshot = camera.GetScreenshot (); var screenShotPath = GetOutputFilePath (fileName, "png"); screenshot.SaveAsFile (screenShotPath, ImageFormat.Png); public void CapturePageSource (string fileName = null) var filePath = GetOutputFilePath (fileName, "html"); File.WriteAllText (filePath, _webDriver.PageSource); cadena privada GetOutputFilePath (cadena fileName, cadena fileExtension) if (! Directory.Exists (OutputFolder)) Directory.CreateDirectory (OutputFolder); var windowTitle = _webDriver.Title; fileName = fileName ?? string.Format ("0 1. 2", windowTitle, DateTime.Now.ToFileTime (), fileExtension) .Replace (':', '.'); var outputPath = Path.Combine (OutputFolder, fileName); var pathChars = Path.GetInvalidPathChars (); var stringBuilder = new StringBuilder (outputPath); foreach (var item en pathChars) stringBuilder.Replace (item, '.'); var screenShotPath = stringBuilder.ToString (); devuelve screenShotPath;
Esta implementación persiste la captura de pantalla y la fuente HTML en una carpeta llamada FailedTests al lado de las pruebas, pero puede modificarla si desea un comportamiento diferente.
Aunque solo mostré métodos específicos para Selenium, existen API similares en todos los marcos de automatización que conozco y se pueden usar fácilmente.
En este artículo hablamos de algunos consejos y trucos de pruebas de interfaz de usuario. Discutimos cómo puede evitar un conjunto de pruebas UI frágil y frágil al evitar retrasos fijos en sus pruebas. Luego discutimos cómo evitar los selectores y las pruebas frágiles al elegirlos con prudencia y también cómo depurar sus pruebas de UI cuando fallan..
La mayoría del código que se muestra en este artículo se puede encontrar en el repositorio de muestra de MvcMusicStore que vimos en el último artículo. También vale la pena señalar que una gran cantidad de código en MvcMusicStore fue tomado de la base de código de Seleno, por lo que si quieres ver muchos trucos geniales es posible que desees revisar Seleno. Descargo de responsabilidad: soy co-fundador de la organización TestStack y colaborador en Seleno.
Espero que lo que hemos discutido en este artículo le ayude en sus esfuerzos de prueba de UI.