Saltar al contenido principal
Version: Next (5.0)

Pruebas Selenium

Este documento ofrece una visión básica sobre cómo empezar a desarrollar pruebas Selenium para aplicaciones desarrolladas con AWE. Antes de empezar a desarrollar pruebas, asegúrate de leer la Guía de desarrollo de pruebas Selenium, especialmente la sección Consejos de optimización/ayuda. Los aspectos básicos que debes conocer antes de empezar a desarrollar pruebas Selenium, como las configuraciones generales y la integración con Jenkins, no se tratan en este documento.

Todo el contenido de este documento está explicado asumiendo que el lector ya sabe cómo usar las herramientas y los comandos relativos a Selenium.

Dónde encontrar cada cosa
tip

Es muy importante comentar todas las pruebas. Cada bloque de comandos relacionado con la interacción de un componente DEBE empezar con un comentario que indique qué intenta hacer la prueba.

Instrucciones básicas​

Actualmente nuestras definiciones de pruebas selenium se basan en Java WebDrivers. Estos drivers permiten al desarrollador lanzar un navegador definido y acciones sobre él para probar la interfaz de usuario.

Para desarrollar pruebas de integración con WebDrivers en AWE, sigue los siguientes pasos:

  • Importa el paquete awe-testing en las dependencias de tu pom:

    <!-- Test dependencies -->
    <dependency>
    <groupId>com.almis.awe</groupId>
    <artifactId>awe-testing</artifactId>
    <scope>test</scope>
    </dependency>
  • Define las pruebas de integración en el pom:

    <!-- Spring boot -->
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
    ...
    <execution>
    <id>pre-integration-test</id>
    <configuration>
    <wait>1000</wait>
    <maxAttempts>180</maxAttempts>
    </configuration>
    <goals>
    <goal>start</goal>
    </goals>
    </execution>
    <execution>
    <id>post-integration-test</id>
    <goals>
    <goal>stop</goal>
    </goals>
    </execution>
    ...
    </executions>
    </plugin>
  • Define el plugin de maven failsafe para lanzar las pruebas IT (de integración):

    <!-- Failsafe -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <executions>
    <execution>
    <id>integration-test</id>
    <goals>
    <goal>integration-test</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
  • ¡Y listo! Simplemente lánzalo con maven:

    mvn verify

Definición de una prueba​

Para generar una prueba selenium basta con extender la clase SeleniumUtilities en tus clases de pruebas de integración (XxxxxXxxIT.class):

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class WebsocketTestsIT extends SeleniumUtilities {

/**
* Log into the application
* @throws Exception
*/
@Test
public void t000_loginTest() throws Exception {
checkLogin("test", "test", "span.avatar-text", "Manager (test)");
}

/**
* Log out from the application
* @throws Exception
*/
@Test
public void t999_logoutTest() throws Exception {
checkLogout(".slogan", "Almis Web Engine");
}

/**
* Tests something
* @throws Exception Error on test
*/
@Test
public void t001_myFirstTest() throws Exception {
// Title
setTestTitle("My first test");

// Test things
// ...
}
}

Establecer el título de la prueba​

Toda prueba debería empezar con un título, para encontrar dónde empieza la prueba en el archivo de log:

// Test title
setTestTitle("My first test");

Iniciar y cerrar sesión​

Hemos creado dos métodos para simplificar las acciones de inicio y cierre de sesión:

Para iniciar sesión en la aplicación simplemente llama al método checkLogin con los siguientes parámetros:

  • user - Nombre de usuario
  • password - Contraseña
  • userName - Nombre que la aplicación muestra para el usuario autenticado
// Log in and check the name of the logged user
checkLogin("test", "test", "Manager (test)");

El paso no depende del cliente (AngularJS o React): sabe dónde muestra cada uno al usuario. También existe una forma con un selector CSS y un texto, checkLogin("test", "test", "span.avatar-text", "Manager (test)"), que ata la prueba a un único renderizado: prefiere la anterior.

Para cerrar sesión en la aplicación simplemente llama al método checkLogout, que comprueba que se muestra la pantalla de inicio de sesión:

// Log out
checkLogout();

Si la aplicación pide una confirmación antes de cerrar sesión, usa checkLogoutWithConfirmation(). Para comprobar que la aplicación rechaza unas credenciales, usa checkLoginRejected(user, password, messageType, title, message).

Clases de prueba independientes​

Las clases de prueba de las aplicaciones de test de AWE no dependen unas de otras, y cada prueba abre su propia pantalla y sesión, de modo que una prueba que falla no deja a las siguientes clases sin un inicio de sesión o un módulo. Dentro de una clase, las pruebas de una secuencia de crear, actualizar y eliminar sobre el mismo registro siguen siendo una cadena ordenada por los nombres tNNN_: la actualización necesita el registro que creó la creación. Tres reglas mantienen las clases independientes:

  • Prepara la sesión antes de cada prueba, de forma idempotente. ensureLoggedIn("test", "test", "Manager (test)") no hace nada cuando la aplicación ya muestra a ese usuario (el texto debe ser exactamente el que muestra el frontend, por ejemplo Manager (test), y espera brevemente a un nombre que aún no está rellenado), cierra la sesión del otro usuario cuando hay otro autenticado, e inicia sesión cuando no hay nadie. ensureModule("Test", "test") selecciona el módulo solo cuando su opción de menú no se muestra, y ensureLoggedOut() cierra sesión solo cuando se muestra un usuario. Es seguro ejecutarlos antes de cada prueba. En las aplicaciones de test (awe-tests/awe-boot y awe-tests/awe-boot-react) una pequeña clase base, AbstractSessionTests, los ejecuta en un @BeforeEach; la extensión que abre el navegador se ejecuta antes, de modo que la primera prueba de una clase encuentra un navegador nuevo e inicia sesión. La clase elige el estado desde el que empiezan sus pruebas con un valor Session en su constructor (LOGGED_IN, o TEST_MODULE para las pantallas de prueba), y una prueba que comprueba ella misma el inicio de sesión o la selección de módulo declara un estado anterior con @StartsFrom:

    class MatrixTestsIT extends AbstractSessionTests {

    MatrixTestsIT() {
    super(Session.TEST_MODULE);
    }

    // Starts from the login screen: ensureLoggedOut() leaves it there whatever the browser showed
    @Test
    @StartsFrom(Session.BLANK)
    void t000_loginTest() {
    checkLogin("test", "test", "Manager (test)");
    }
    }
  • Mantén una prueba real de inicio de sesión y una prueba real de cierre de sesión por clase (t000_... y t999_...), porque comprueban esas funcionalidades. Las demás pruebas nunca dependen de ellas: la preparación inicia sesión de nuevo cuando la prueba de inicio de sesión no se ejecutó o falló.

  • Abre la pantalla en cada prueba (gotoScreen) en lugar de continuar en la pantalla que dejó la prueba anterior, y usa datos propios: una clase crea los registros que actualiza y elimina, con nombres que ninguna otra clase usa (las pruebas del servidor de correo usan auth server y plain server, no el mismo nombre). Los grids se buscan por el texto que contiene una fila, de modo que ningún nombre puede estar contenido en el nombre de un registro de otra clase (Manual task y Prerequisite task, no Task y Task 2). Un registro que una clase solo necesita como requisito previo (las pruebas de tareas programadas usan un calendario y una tarea manual para su lanzamiento personalizado y sus dependencias) lo crea esa clase al principio y lo elimina al final, después de los registros que lo usan. Las clases CRUD (AbstractCrudTests) hacen lo mismo: las clases de módulo y de base de datos crean su propio sitio, y la clase de usuario su propio perfil.

  • gotoScreen no recarga la pantalla que ya está abierta. El cliente conserva sus criterios, sus recargas pendientes y la fila que seleccionó la prueba anterior, y un grid que se recarga después pierde esa selección, de modo que una prueba que selecciona una fila justo después de otra en la misma pantalla puede fallar en un botón que permanece deshabilitado. Donde importe, pasa por otra pantalla primero (las pruebas del planificador lo hacen en AbstractSchedulerTests.openScreen). De la misma manera, una prueba no debe dejar trabajo en segundo plano ejecutándose hacia la siguiente: una tarea programada con dependencias las lanza cuando termina, por lo que las pruebas de tareas programadas la ejecutan antes de añadir las dependencias.

  • Una prueba que cambia algo global lo restaura incluso cuando falla. Hazlo en un @AfterEach que se ejecute solo cuando la prueba no terminó su propia limpieza, con una bandera que la prueba activa antes del paso que puede cambiar el estado y desactiva tras el paso que lo restaura. SchedulerManagementTestsIT detiene y reinicia el único planificador de la aplicación, que las demás clases del planificador necesitan en ejecución: es una clase propia, cada prueba termina arrancando el planificador de nuevo, y su limpieza pulsa el botón de reinicio (funciona sea cual sea el estado del planificador) cuando la prueba falla en medio. IntegrationTestsIT elimina de la misma manera la configuración de pantalla que almacena.

Las clases que resultan de dividir una larga conservan su @Tag, de modo que se ejecutan en el mismo job de CI y no hace falta un nuevo job. Un job de React lista sus clases en TEST_CLASSES: reemplaza el nombre antiguo por los nuevos (una prueba de la aplicación React comprueba que cada clase listada existe).

Ir a una nueva página​

Para ir a una nueva página, tienes que llamar al método gotoScreen con una lista de nombres de opciones correspondientes a las opciones de menú en las que necesitas hacer clic para ir a la pantalla seleccionada. Por ejemplo, para ir a la pantalla sites necesitas hacer clic primero en la opción de menú tools:

// Go to sites screen
gotoScreen("tools", "sites");

Hacer clic en un botón​

Para hacer clic en un botón simplemente llama al método clickButton con el nombre del botón como parámetro:

// Click "ButSnd" button
clickButton("ButSnd");

Si el botón hace que se cargue una nueva pantalla, puedes indicar al método que espere después de la llamada al botón:

// Click "ButNew" button and wait the screen to load
clickButton("ButNew", true);

Si el botón es un botón de búsqueda, puedes llamar al método searchAndWait para esperar a que el grid se cargue (si el botón se llama ButSch):

// Search and wait
searchAndWait();

Si el nombre del botón es otro, simplemente indícalo como primer argumento:

// Search and wait
searchAndWait("mySearchButton");

Esperar a entradas y botones accionables​

Cuando un flujo personalizado necesita esperar hasta que un criterio pueda recibir entrada de forma segura o hasta que un botón se pueda pulsar, SeleniumUtilities expone dos ayudantes protegidos para las subclases:

  • waitForInputActionability(String criterionName)
  • waitForButtonClickability(String buttonName)

Estos ayudantes son genéricos y pueden reutilizarse en cualquier flujo de UI que necesite entradas accionables o botones pulsables.

// Wait until a criterion can receive input
waitForInputActionability("cod_usr");

// Wait until a button is clickable
waitForButtonClickability("ButLogIn");

Úsalos cuando la UI renderiza el elemento pronto pero una actualización posterior del frontend aún está habilitando el control.

Esperar a que un grid se cargue​

Para esperar a que un grid se cargue, llama al método waitForLoadingGrid:

// Wait a grid to load
waitForLoadingGrid();

Aceptar un diálogo de confirmación​

Para confirmar un diálogo simplemente llama al método acceptConfirm:

// Confirm a dialog
acceptConfirm();

Comprobar y cerrar un mensaje​

Cuando la aplicación devuelve un mensaje, puedes verificarlo y cerrarlo con el método checkAndCloseMessage:

// Accept message
checkAndCloseMessage(messageType);

Donde messageType depende del mensaje que quieras verificar. Tienes los siguientes tipos de mensaje:

  • success - mensaje verde, cuando todo va bien.
  • warning - mensaje amarillo, hay una advertencia.
  • info - mensaje azul, una notificación al usuario.
  • danger - mensaje rojo, la aplicación ha fallado de algún modo.

Combo: hacer clic en un botón, aceptar el diálogo de confirmación y cerrar el mensaje​

Hay operaciones que normalmente se lanzan juntas. La combinación de hacer clic en un botón (botón de confirmación), aceptar un diálogo y cerrar un mensaje de éxito es una de ellas. Hemos creado una funcionalidad para hacer todas estas acciones en un único método sencillo:

// Store and confirm
clickButtonAndConfirm("ButCnf");
  • Hace clic en el botón "ButCnf" (o el definido)
  • Hace clic en aceptar el diálogo de confirmación
  • Espera un mensaje success y lo cierra

Catálogo de pasos​

Cada paso que puede llamar una prueba es un método protected de SeleniumUtilities, de modo que una clase de prueba que lo extienda los llama directamente. Este catálogo los lista todos, agrupados por área, para que puedas revisarlo antes de escribir una prueba. Los nombres de los parámetros siguen la firma Java: criterionName es el identificador del criterio, gridId, rowId y columnId son los identificadores del grid, la fila y la columna, y messageType es success, info, warning o danger. Un paso se comporta igual en el cliente AngularJS y en el React, salvo que su descripción indique qué difiere (consulte Clientes AngularJS y React).

Un paso que no está listado aquí no existe: añádelo (consulta Añadir un paso que falta) en lugar de escribir un selector en tu prueba. Esta lista la comprueba una prueba unitaria de awe-testing (SeleniumTestGuideSyncTest): la compilación falla si un método público o protegido, no obsoleto, de SeleniumUtilities no se menciona en esta guía.

Pasos obsoletos

Un paso que se reemplaza se marca como @Deprecated en el código fuente y se mantiene durante toda la línea 5.x, sin eliminación antes de la 6.0 (consulta Compatibilidad de la API). Los pasos que reciben o devuelven un By de Selenium, y getDriver(), están obsoletos en favor del mismo paso con un Locator (consulta Pasos personalizados sin tipos de Selenium):

ObsoletoUsar en su lugar
waitForText(By selector, String contains)waitForText(Locator selector, String contains)
waitForValue(By selector, String contains)waitForValue(Locator selector, String contains)
waitForEmptyText(By selector, String text)waitForEmptyText(Locator selector, String text)
waitForCssSelector(String cssSelector) (returns a By)waitForCssLocator(String cssSelector) (returns a Locator)
writeText(By selector, CharSequence text)writeText(Locator selector, CharSequence text)
writeTextOnDriver(By selector, CharSequence... text)writeTextOnDriver(Locator selector, CharSequence... text)
checkVisible(By selector)checkVisible(Locator selector)
checkNotVisible(By selector)checkNotVisible(Locator selector)
getDriver()Los pasos de este catálogo, o getBrowser() con un Locator
clickGridColumnsShowAll(String gridId) (las columnas ya no tienen Mostrar todas)showGridColumn(String gridId, String columnId) para cada columna oculta

Los pasos CSS en bruto que reciben un String (click(String cssSelector), checkText(String, String)...) no están obsoletos.

Sesión y navegación​

PasoQué hace
goToUrl(String url)Abre una URL: el punto de partida de una suite
setTestTitle(String title)Escribe el título de la prueba en el log. Empieza toda prueba con él
checkLogin(String username, String password, String userName)Inicia sesión y comprueba el nombre que la aplicación muestra para el usuario autenticado. Forma preferida
checkLogin(String username, String password, String cssSelector, String checkText)Inicia sesión y comprueba un texto dentro de un selector CSS. Forma con selector en bruto: prefiere la de tres argumentos
ensureLoggedIn(String username, String password, String userName)No hace nada cuando la aplicación ya muestra a ese usuario (el texto exacto que muestra el frontend, como Manager (test)); cierra la sesión del otro usuario cuando hay otro autenticado, e inicia sesión (checkLogin) cuando no hay nadie. Úsalo en la preparación de una clase de prueba, no en la prueba que comprueba el inicio de sesión
ensureLoggedOut()No hace nada cuando no se muestra ningún usuario; cierra sesión cuando hay uno, aceptando la confirmación cuando la aplicación la pide. Úsalo en la preparación de la prueba que comprueba el inicio de sesión
checkLoginRejected(String username, String password, String messageType, String title, String message)Intenta iniciar sesión con credenciales que la aplicación rechaza y comprueba el mensaje que muestra
checkLogout()Cierra sesión y comprueba que se muestra la pantalla de inicio de sesión. Forma preferida
checkLogout(String cssSelector, String checkText)Cierra sesión y comprueba un texto dentro de un selector CSS. Forma con selector en bruto: prefiere checkLogout()
checkLogoutWithConfirmation()Cierra sesión de una aplicación que pide primero una confirmación, la acepta y comprueba la pantalla de inicio de sesión
gotoScreen(String... menuOptions)Hace clic en las opciones de menú en orden para abrir una pantalla, y espera a que el menú surta efecto
waitForMenuOption(String option)Espera hasta que el clic en una opción de menú haya surtido efecto. gotoScreen ya lo llama
checkMenuOption(String option, String text)Comprueba que una opción de menú es visible y contiene un texto
checkLauncherCardDescription(String option, String text)Comprueba que una tarjeta de una página lanzadora muestra una descripción (su línea secundaria)
checkLauncherCardWithoutDescription(String option)Comprueba que una tarjeta de una página lanzadora no tiene descripción
checkBreadcrumbContains(String text)Comprueba que la ruta de navegación de la página es visible y contiene un texto
selectModule(String moduleName)Abre el menú de ajustes (ButSetTog) y selecciona un módulo en el selector module
ensureModule(String moduleName, String menuOption)No hace nada cuando la opción de menú que solo muestra ese módulo ya es visible; selecciona el módulo (selectModule) cuando no lo es
broadcastMessageToUser(String user, String text)Va a la pantalla tools > broadcast-messages de las aplicaciones de test de AWE, envía un texto a un usuario y cierra los mensajes success e info
invalidateSession()Invalida la sesión del usuario desde otra ventana, como si se hubiera cerrado en el servidor

Esperas y pausas​

PasoQué hace
waitForInputActionability(String criterionName)Espera hasta que un criterio pueda recibir entrada
waitForButtonClickability(String buttonName)Espera hasta que un botón se pueda pulsar
waitForButton(String buttonName)Espera hasta que un botón es pulsable
waitForTab(String tabCriterionName)Espera hasta que un criterio de pestañas es pulsable
waitForContextButton(String buttonName)Espera hasta que un botón del menú contextual es pulsable
waitForLoadingBar()Espera a que desaparezca la barra de carga de la página
waitForLoadingGrid()Espera a que termine la carga de un grid
waitForText(String clazz, String contains)Espera un texto dentro de la etiqueta con una clase CSS. Forma con selector en bruto
waitForText(Locator selector, String contains)Espera un texto dentro de un locator. Para ayudantes de tu producto
waitForValue(Locator selector, String contains)Espera hasta que el valor de un input contiene un texto. Para ayudantes de tu producto
waitForEmptyText(Locator selector, String text)Espera hasta que el valor de un input ya no contiene un texto. Para ayudantes de tu producto
waitForCssLocator(String cssSelector)Espera un selector CSS y lo devuelve como un Locator. Forma con selector en bruto
waitForText(By selector, String contains)Obsoleto: usa waitForText(Locator, String)
waitForValue(By selector, String contains)Obsoleto: usa waitForValue(Locator, String)
waitForEmptyText(By selector, String text)Obsoleto: usa waitForEmptyText(Locator, String)
waitForCssSelector(String cssSelector)Obsoleto: devuelve un By. Usa waitForCssLocator(String)
pause(Integer time)Duerme un número de milisegundos. Úsalo solo cuando no hay un estado que esperar
showMouse()Muestra un puntero que sigue al ratón real, una ayuda visual para vídeos y capturas de pantalla

Botones, menús e info​

PasoQué hace
clickButton(String buttonName)Hace clic en un botón
clickButton(String buttonName, boolean waitForLoadingBar)Hace clic en un botón y, cuando es true, espera a que la pantalla se cargue
clickButtonAndConfirm(String button)Hace clic en un botón, acepta el diálogo de confirmación y cierra el mensaje success
clickButtonAndConfirm(String button, String messageType)Igual, cerrando un mensaje de otro tipo
checkButtonVisible(String buttonId)Comprueba que un botón se muestra, sea cual sea su estado
checkButtonNotVisible(String buttonId)Comprueba que un botón no se muestra
checkButtonDisabled(String buttonId)Comprueba que un botón se muestra y está deshabilitado
clickInfoButton(String infoButtonName)Hace clic en un botón de info (el menú de usuario, la lista de módulos...)
openUserMenu()Abre o cierra el menú de usuario (el avatar del usuario conectado), donde están el tema, el modo de color y el idioma
clickUserMenuOption(String optionId)Hace clic en una opción del menú de usuario abierto (dark-mode, light-mode). Solo AngularJS
switchToDarkMode(), switchToLightMode()Abre el menú de usuario (salvo que ya esté abierto) y elige el modo oscuro o el claro. Solo AngularJS
switchToLightModeIfDark()Abre el menú de usuario (salvo que ya esté abierto) y, si ofrece el modo claro (el usuario está en el modo oscuro), lo elige y espera a que la página sea clara; si no, mantiene el modo. Un test empieza en el modo claro sea cual sea el modo con el que empieza la sesión. Solo AngularJS
selectTheme(String themeName)Abre el menú de usuario (salvo que ya esté abierto), elige un tema y espera a que la pantalla cargue. Solo AngularJS
checkThemeApplied(String themeName)Comprueba que un tema está aplicado a la página. Solo AngularJS
checkColorMode(String mode)Comprueba que el usuario eligió el modo dark o light (gana al sistema operativo, así que también prueba que la elección llegó a la página). Solo AngularJS
checkDesignToken(String token, String value)Comprueba el valor de un design token (--awe-color-primary) en la página, sin distinguir mayúsculas ni espacios. Es el que le dan el tema y el modo aplicados. Solo AngularJS
checkThemeAppearance(String mode, String primary)Comprueba que <html> lleva la clase del modo dark o light (y no la del otro) y la paleta primaria del tema en data-awe-primary (red, blue...), como pintan los dos motores el tema de AWE 5
checkComputedStyle(String cssSelector, String property, String value)Comprueba el valor calculado de una propiedad CSS del primer elemento que encaja con el selector (body, background-color, rgb(248, 250, 252)), sin distinguir mayúsculas ni espacios. Los colores llegan como rgb(...)
clickContextButton(String... contextButtonOptionList)Hace clic en las opciones de un menú contextual en orden
closeContextMenu()Cierra el menú contextual abierto sin elegir una opción
checkContextMenuNotVisible()Comprueba que no se muestra ningún menú contextual
searchAndWait()Hace clic en ButSch y espera a que el grid se cargue
searchAndWait(String buttonName)Igual con otro botón de búsqueda

Mensajes y diálogos​

PasoQué hace
acceptConfirm()Acepta el diálogo de confirmación y espera a que desaparezca
checkAndCloseMessage(String messageType)Comprueba que se muestra un mensaje del tipo y lo cierra
closeMessages(String messageType, int count)Cierra los mensajes de una pila uno a uno. Una pantalla con varios mensajes (uno por consulta fallida) mantiene sus controles bloqueados hasta que se cierran todos
checkMessageTitle(String messageType, String text)Comprueba el título de un mensaje
checkMessageText(String messageType, String text)Comprueba el texto de un mensaje
checkMessagePopover(String component, String title, String text)Comprueba el título y el texto del popover de un target message mostrado sobre un componente
waitForMessagePopoverToHide(String component)Espera a que se oculte el popover de un target message (tras el timeout de su tipo, o al descartarlo)
dismissMessagePopover(String component)Descarta el popover de un target message antes de su timeout (pulsa su máscara en AngularJS y escape en React) y espera a que se oculte
checkMessageMissing(String messageType)Comprueba que no se muestra ningún mensaje del tipo
closeDialog(String dialogId)Cierra un diálogo modal con el icono de cierre de su cabecera, como un usuario que lo descarta sin usar sus botones
checkDialogClosed(String dialogId)Comprueba que se ha cerrado un diálogo modal
checkDialogWidth(String dialogId, int width)Comprueba el ancho en píxeles de un diálogo abierto, con dos de margen. Úsalo para comprobar las clases de tamaño modal-sm, modal-md y modal-lg
dragResizable(String elementId, String direction, int offset)Arrastra el tirador de un lado (top, right, bottom, left) del redimensionable más cercano que rodea a un elemento, offset píxeles en su eje (positivo hacia la derecha o hacia abajo)
getResizableBounds(String elementId, String direction)Devuelve la caja del redimensionable más cercano que rodea a un elemento, con tirador en ese lado, como {left, top, width, height}
getResizableNeighbourBounds(String elementId, String direction)Devuelve la caja del elemento junto a ese redimensionable en el lado de su tirador: el que cede o toma el tamaño de un arrastre
getResizableOppositeNeighbourBounds(String elementId, String direction)Devuelve la caja del elemento junto a ese redimensionable en el otro lado: un arrastre no lo mueve
closeSidePanel(String panelId)Cierra un panel lateral con el botón de cierre de su cabecera
closeSidePanelWithEscape(String panelId)Cierra un panel lateral abierto pulsando Esc y comprueba que se ha cerrado
checkSidePanelClosed(String panelId)Comprueba que un panel lateral se ha cerrado
checkSidePanelOpen(String panelId, String position, String maxWidth)Comprueba que un panel lateral está abierto contra su borde (left o right) y tan ancho como su ancho máximo (una longitud css que resuelve el navegador, limitada por el ancho de la pantalla), con dos píxeles de margen
checkSidePanelKeepsTheFocus(String panelId, int presses)Pulsa Tab las veces indicadas y comprueba que el foco del teclado nunca sale del panel lateral
closePopover(String popoverId)Cierra un popover anclado a un control con el botón de cierre de su cabecera
closePopoverWithEscape(String popoverId)Cierra un popover abierto pulsando Esc y comprueba que se ha cerrado
checkPopoverClosed(String popoverId)Comprueba que un popover anclado a un control se ha cerrado
checkPopoverOpen(String popoverId, String anchorId, String position)Comprueba que un popover está abierto junto a su ancla: en su lado (top, right, bottom o left), a 16 píxeles como mucho y alineado con ella
checkPopoverKeepsTheFocus(String popoverId, int presses)Pulsa Tab las veces indicadas y comprueba que el foco del teclado nunca sale del popover
checkButtonFocused(String buttonId)Comprueba que un botón tiene el foco del teclado (por ejemplo el que abrió un diálogo, un panel lateral o un popover, una vez cerrado)
checkValidationErrorVisible()Comprueba que la pantalla muestra errores de validación

Criterios: escribir y leer​

PasoQué hace
writeText(String criterionName, CharSequence text)Escribe un texto en un criterio, borrándolo antes
writeText(String criterionName, CharSequence text, boolean clearText)Igual, eligiendo si se borra el criterio antes
writeText(Locator selector, CharSequence text)Escribe un texto en un locator. Para ayudantes de tu producto
writeTextOnDriver(Locator selector, CharSequence... text)Envía teclas a un locator como lo haría el navegador, sin desplazarse ni hacer pausas. Para ayudantes de tu producto
writeText(By selector, CharSequence text)Obsoleto: usa writeText(Locator, CharSequence)
writeTextOnDriver(By selector, CharSequence... text)Obsoleto: usa writeTextOnDriver(Locator, CharSequence...)
clearText(String cssSelector)Borra el texto del input encontrado por un selector CSS. Forma con selector en bruto
getText(String criterionName)Devuelve el texto de un criterio
checkCriterionContents(String criterionName, String search)Comprueba el valor de un criterio de texto
checkCriterionLabel(String criterionName, String text)Comprueba la etiqueta de un criterio
checkCriterionUnit(String criterionName, String text)Comprueba el complemento de unidad de un criterio (el texto tras el input)
checkCheckboxRadio(boolean isChecked, String... criteriaNames)Comprueba que unos criterios están marcados (o no)
clickCheckbox(String criterionName)Hace clic en un checkbox o botón de radio
clickCheckboxOption(String criterionName, String optionId)Hace clic en una opción de un grupo de botones (un checkbox o radio de botón dibujado como un solo grupo)

Criterios: fechas​

PasoQué hace
clickDate(String criterionName)Abre el selector de fechas de un criterio
selectDate(String dateName, CharSequence dateValue)Elige una fecha completa
selectDay(String dateName, Integer day)Elige un día del mes actual
selectMonth(String dateName, String month)Elige un mes
selectYear(String dateName, Integer year)Elige un año
clickEnabledDatepickerDay()Hace clic en un día del selector de fechas abierto que se puede elegir
getTodayDay()Devuelve el día del mes de hoy
getTomorrowDay()Devuelve el día del mes de mañana

Criterios: rango de fechas y cargador​

PasoQué hace
checkCriterionFocused(String criterionName)Comprueba que el foco del teclado está en un criterio (por ejemplo, después de que una validación fallida lo haya mostrado)
checkDateRange(String criterionName, String from, String to)Comprueba las dos fechas de un criterio de rango de fechas (dd/MM/yyyy, vacío cuando no hay)
checkDateRangeDisabled(String criterionName)Comprueba que las dos fechas de un criterio de rango de fechas no se pueden editar
checkDateRangePresetCount(String criterionName, int count)Comprueba cuántos preajustes rápidos ofrece un criterio de rango de fechas
clickDateRangePreset(String criterionName, String presetCode)Hace clic en un preajuste rápido (1M, YTD, ALL...)
checkDateRangePresetActive(String criterionName, String presetCode)Comprueba que un preajuste es el activo (las fechas son las que ha establecido)
clearDateRangeFrom(String criterionName)Borra la fecha de inicio de un criterio de rango de fechas
submitDateRangeFrom(String criterionName, String date)Escribe la fecha de inicio de un criterio de rango de fechas (dd/MM/yyyy) y pulsa Intro, que envía la pantalla
submitDateRangeTo(String criterionName, String date)Escribe la fecha de fin de un criterio de rango de fechas (dd/MM/yyyy) y pulsa Intro, que envía la pantalla
dropFile(String criterionName, String fileName, String mimeType, String base64Content)Suelta un archivo en la zona de arrastre de un criterio cargador, como un usuario que lo arrastra desde el escritorio
checkUploadCaptureButton(String criterionName, boolean shown)Comprueba que un criterio cargador ofrece el botón que abre la cámara (true), o que no lo ofrece (false)
chooseCapturedFile(String criterionName, String filePath)Elige un archivo con el campo de cámara (el que captura una imagen) de un criterio cargador. Solo React: AngularJS crea su campo de archivo al hacer clic en el botón de la cámara, así que no hay un campo al que dar el archivo
checkUploadedFile(String criterionName, String fileName)Comprueba el nombre del archivo que contiene un criterio cargador
checkNoUploadedFile(String criterionName)Comprueba que un criterio cargador no contiene ningún archivo
checkUploadPreview(String criterionName)Comprueba que un criterio cargador muestra la vista previa de la imagen que contiene (una dirección blob:)
checkNoUploadPreview(String criterionName)Comprueba que un criterio cargador no muestra ninguna vista previa
clearUpload(String criterionName)Hace clic en el botón que borra el archivo de un criterio cargador

Criterios: select y suggest​

PasoQué hace
selectFirst(String criterionName)Elige el primer valor de un select
selectLast(String criterionName)Elige el último valor de un select
selectContain(String criterionName, String label)Elige el valor de un select cuya etiqueta contiene un texto
selectResult(String match)Elige un resultado de la lista de select abierta
suggest(String criterionName, String search, String label)Escribe una búsqueda en un suggest y elige el resultado cuya etiqueta contiene un texto
suggestLast(String criterionName, String search)Escribe una búsqueda en un suggest y elige el último resultado
suggestResult(String match)Elige un resultado de la lista de suggest abierta
suggestMultiple(String criterionName, String search, String label)Añade un valor a un select o suggest múltiple
suggestMultiple(String criterionName, boolean clear, String search, String label)Igual, borrando antes los valores elegidos
suggestMultipleList(String criterionName, String... items)Añade varios valores a un select o suggest múltiple
openSuggest(String criterionName)Abre la lista de un select o suggest
writeSuggestSearch(String criterionName, CharSequence text)Escribe en el cuadro de búsqueda de la lista abierta
suggestReplacingSearch(String criterionName, String search1, String search2, String match, Integer pause)Escribe una búsqueda, la reemplaza por otra mientras la primera aún se está cargando y elige un resultado
suggestMultipleReplacingSearch(String criterionName, String search1, String search2, String match, Integer pause)Igual, para un suggest múltiple
checkSelectContents(String criterionName, String search)Comprueba el valor elegido de un select
checkSuggestContents(String criterionName, String search)Comprueba el valor elegido de un suggest. El cliente lo muestra como texto (AngularJS) o en un input (React): el paso sabe cuál
checkMultipleSelectorContents(String criterionName, String search)Comprueba un valor elegido de un select o suggest múltiple
checkSelectNumberOfResults(String criterionName, Integer number)Abre un select y comprueba cuántos resultados lista
checkSuggestResultCount(int expected)Comprueba cuántos resultados muestra la lista de select o suggest abierta

Usa checkSelectContents para un select y checkSuggestContents para un suggest: un suggest no se dibuja como un select.

Grids: filas, celdas y columnas​

Los pasos sobre una celda tienen dos formas: con un rowId (la celda de una fila dada) y sin él (la celda de la fila que se está editando). La tabla lista una forma y dice "(también con rowId)" cuando existe la otra.

PasoQué hace
clickRowContents(String search)Selecciona la fila que contiene un texto
clickRowContents(String gridId, String search)Igual en un grid dado. Una fila que ya está seleccionada sigue seleccionada
toggleRowContents(String gridId, String search)Hace clic en una fila aunque ya esté seleccionada, para alternar su selección
editRow(String search)Inicia la edición de la fila que contiene un texto
editRow(String gridId, String search)Igual en un grid dado
editRow(String gridId, String rowId, String columnId)Inicia la edición de una fila haciendo clic en una de sus celdas
clickCell(String gridId, String columnId)Hace clic en una celda de la fila seleccionada
clickCell(String gridId, String rowId, String columnId)Hace clic en una celda
contextMenuRowContents(String search)Abre el menú contextual de la fila que contiene un texto
contextMenuRowContents(String gridId, String search)Igual en un grid dado
contextMenu(String gridId, String rowId, String columnId)Abre el menú contextual de una celda
contextMenuOnButton(String buttonId)Abre el menú contextual de un botón con un clic derecho
contextMenuOnCriterion(String criterionId)Abre el menú contextual de un criterio con un clic derecho
contextMenuOnTab(String tabId)Abre el menú contextual de un criterio de pestañas con un clic derecho sobre su lista de pestañas
contextMenuOnWizard(String wizardId)Abre el menú contextual de un wizard con un clic derecho sobre su paso activo
clickGridCellButton(String gridId, String rowId, String columnId)Pulsa el botón de una celda de un grid
selectAllRowsOfGrid(String gridId)Selecciona todas las filas con el checkbox de la cabecera
checkAllRowsSelected(String gridId)Comprueba que todas las filas de un grid están seleccionadas
clickGridViewport(String gridId)Hace clic en la zona del grid que muestra las filas, fuera de cualquier fila
sortGrid(String gridId, String columnId)Hace clic en la cabecera de una columna para ordenar
scrollGrid(String gridId, int horizontal, int vertical)Desplaza el grid en píxeles
saveRow() / saveRow(String gridId)Hace clic en el botón de guardar de un grid editable y espera
getText(String gridId, String columnId)Devuelve el texto del editor de una celda de la fila que se está editando
getText(String gridId, String rowId, String columnId)Devuelve el texto de una celda
writeText(String gridId, String columnId, CharSequence text)Escribe en el editor de una celda de la fila que se está editando
writeText(String gridId, String rowId, String columnId, CharSequence text)Escribe en el editor de una celda
writeText(String gridId, String rowId, String columnId, CharSequence text, boolean clearText)Igual, eligiendo si se borra antes
clickCheckbox(String gridId, String columnId)Hace clic en el checkbox o radio de una celda de la fila que se está editando
clickCheckbox(String gridId, String rowId, String columnId)Hace clic en el checkbox o radio de una celda
clickDate(String gridId, String columnId)Abre el selector de fechas de una celda (también con rowId)
selectDate(String gridId, String columnId, CharSequence dateValue)Elige una fecha en una celda (también con rowId)
selectDay(String gridId, String columnId, Integer day)Elige un día del mes actual en una celda (también con rowId)
selectMonth(String gridId, String columnId, String month)Elige un mes en una celda (también con rowId)
selectYear(String gridId, String columnId, Integer year)Elige un año en una celda (también con rowId)
selectFirst(String gridId, String columnId)Elige el primer valor de una celda select (también con rowId)
selectLast(String gridId, String columnId)Elige el último valor de una celda select (también con rowId)
selectContain(String gridId, String columnId, String label)Elige un valor en una celda select (también con rowId)
suggest(String gridId, String columnId, String search, String label)Sugiere en una celda (también con rowId)
suggestLast(String gridId, String columnId, String search)Sugiere el último resultado en una celda (también con rowId)
suggestMultiple(String gridId, String columnId, String search, String label)Añade un valor a una celda múltiple (también con rowId, y con clear)

Grids: comprobaciones​

PasoQué hace
checkRowContents(String... searchList)Comprueba que el grid contiene todos los textos
checkRowContentsGrid(String gridId, String... searchList)Igual en un grid dado
checkRowNotContains(String search)Comprueba que el grid no contiene un texto
hasRowContents(String search)Indica, sin fallar, si el grid contiene un texto una vez cargada: un paso que crea o elimina un registro lo usa para averiguar si el registro ya está, de modo que pueda ejecutarse de nuevo tras un intento fallido
hasRowContentsGrid(String gridId, String search)Igual en un grid dado
checkCellContents(String gridId, String rowId, String columnId, String search)Comprueba el contenido de una celda
checkGridCellsHaveNoActiveContent(String gridId)Comprueba que las celdas de un grid muestran texto y nada activo: ningún script, frame, imagen, formulario, manejador de eventos ni enlace a una url de script. Úsalo con un valor que lleve marcado para demostrar que el grid no lo interpreta
checkGridColumnOrder(String gridId, String columnId, String... expected)Comprueba que las filas de un grid están en un orden dado, leyendo los valores de una columna
checkGridRowGripShown(String gridId, int rowNumber)Comprueba que una fila de un grid con move-rows muestra su asa de arrastre (las filas se numeran desde 1)
dragGridRowBefore(String gridId, int rowNumber, int targetRowNumber)Arrastra una fila de un grid con move-rows por su asa y la suelta en la mitad superior de otra fila, de modo que queda justo encima
dragGridRowAfter(String gridId, int rowNumber, int targetRowNumber)Arrastra una fila de un grid con move-rows por su asa y la suelta en la mitad inferior de otra fila, de modo que queda justo debajo
checkGridPresent(String gridId)Comprueba que un grid existe en la pantalla, aunque esté oculta
checkGridNotVisible(String gridId)Comprueba que un grid no se muestra
checkGridNotPresent(String gridId)Comprueba que un grid no está en la página, ni visible ni oculto, como el grid anidado de un detalle de fila cuando la fila se cierra o su grid se recarga
checkGridPageSize(String size)Comprueba el número de filas que un grid muestra en cada página
checkGridPaginationInfo(String text)Comprueba el texto del paginador que indica el rango de filas mostrado y el total (View 1 - 10 of 25)
clickGridNextPage()Va a la página siguiente de un grid con su paginador y espera a la carga. En AngularJS solo funciona con el paginador compacto: el paginador ancho no tiene hook para sus enlaces de página
checkGridRadioChoice(String gridId, String rowId, String columnId, String optionId)Comprueba que una opción del radio o del button-radio de una celda es la elegida. La opción se identifica por su valor
checkGridRadioNoChoice(String gridId, String rowId, String columnId)Comprueba que ninguna opción del radio o del button-radio de una celda está elegida
selectGridRadioOption(String gridId, String rowId, String columnId, String optionId)Elige una opción del radio o del button-radio de una celda con un clic. La opción se identifica por su valor
moveGridRadioChoiceNext(String gridId, String rowId, String columnId)Mueve la elección de una celda del grid a la siguiente opción con la flecha derecha, desde la opción elegida (o la primera si no hay ninguna)
moveGridRadioChoicePrevious(String gridId, String rowId, String columnId)Mueve la elección de una celda del grid a la opción anterior con la flecha izquierda, desde la opción elegida (o la primera si no hay ninguna)
checkGridEmptyState(String title, String text)Comprueba que un grid sin filas muestra su estado vacío, con un título y un texto que contienen los indicados
checkGridEmptyStateNotVisible()Comprueba que ningún grid muestra su estado vacío
clickGridEmptyAction()Pulsa el botón que ofrece el estado vacío
clickGridRowDetailToggle(String gridId, String rowId)Pulsa el botón del detalle de una fila, que abre el detalle cuando está cerrado y lo cierra cuando está abierto
pressEnterOnGridRowDetailToggle(String gridId, String rowId)Da el foco al botón del detalle de una fila, como cuando el usuario llega a él con el tabulador, y pulsa Intro
pressSpaceOnGridRowDetailToggle(String gridId, String rowId)Da el foco al botón del detalle de una fila, como cuando el usuario llega a él con el tabulador, y pulsa la barra espaciadora
checkGridRowDetailOpen(String gridId, String rowId, String text)Comprueba que el detalle de una fila está abierto y contiene un texto (con sus saltos de línea), y que su botón lo indica (aria-expanded) y apunta a él (aria-controls)
checkGridRowDetailToggleLabel(String gridId, String rowId, String label)Comprueba la etiqueta (aria-label) del botón del detalle de una fila, que indica si muestra u oculta el detalle
checkGridRowDetailClosed(String gridId, String rowId)Comprueba que el detalle de una fila no se muestra y que su botón lo indica (aria-expanded)
checkNoGridRowDetailOpen(String gridId)Comprueba que ninguna fila de un grid tiene su detalle abierto: no se muestra ningún detalle y ningún botón indica que su detalle está abierto
checkGridRowDetailHasClass(String gridId, String rowId, String cssClass)Comprueba que el detalle de una fila tiene unas clases de estilo (separadas por espacios), como las del style del detalle de fila
checkGridRowDetailComputedStyle(String gridId, String rowId, String property, String value)Comprueba el valor que el navegador calcula para una propiedad de estilo del detalle de una fila (border-left-width, 4px), lo que demuestra que las reglas de estilo llegan a la página
checkGridRowDetailUnderItsRow(String gridId, String rowId)Comprueba que el detalle de una fila empieza justo debajo de su fila y ocupa al menos el 90% del ancho de la fila
checkGridRowDetailAcrossGrid(String gridId, String rowId)Comprueba que el detalle de una fila ocupa al menos el 90% del ancho visible del grid, aunque las columnas sean más estrechas
checkGridRowDetailHasNestedGrid(String gridId, String rowId, String nestedGridId)Comprueba que el detalle de una fila muestra un grid anidado (detalle de fila con componentes). El id del grid anidado es el que el cliente genera para la fila (GrdOrders-7)
checkGridRowDetailHoldsNestedGrid(String gridId, String rowId, String nestedGridId)Comprueba que el detalle de una fila contiene el grid anidado entero, hasta su última fila y en todo su ancho, sin recortarlo
checkGridRowNotSelected(String gridId, String rowId)Comprueba que una fila se muestra y no está seleccionada
checkGridHeaderVisible(String gridId, String columnId)Comprueba que se muestra la cabecera de una columna
checkGridHeaderNotVisible(String gridId, String columnId)Comprueba que no se muestra la cabecera de una columna (grid con la clase grid-no-header)
checkGridFirstRowAtTop(String gridId, String rowId, String columnId)Comprueba que la primera fila empieza justo bajo el borde superior del grid, con dos píxeles de margen: un grid sin cabecera (clase grid-no-header) no deja hueco donde estaba la cabecera
checkCellHasClass(String gridId, String rowId, String columnId, String cssClass)Comprueba que una celda tiene una clase de estilo, como la scale-seq-3 que asigna una consulta para colorearla
checkGridSparkline(String gridId, String rowId, String columnId, String type, String values)Comprueba el sparkline que dibuja una columna sparkline en una celda: su tipo (line, bar o area) y el valor de cada punto, que es su tooltip, separados por comas (3,5,-1,8)
checkGridSparklineEmpty(String gridId, String rowId, String columnId)Comprueba que una celda no tiene sparkline porque su valor no es una serie válida de números, mientras la celda sigue visible
checkGridSparklineMarkers(String gridId, String rowId, String columnId, int markers)Comprueba cuántos marcadores (puntos del valor más bajo, el más alto y el último) tiene el sparkline de una celda
checkGridSparklineNotFocusable(String gridId, String rowId, String columnId)Comprueba que el sparkline de una celda no toma el foco del teclado: no es enfocable y nada dentro de él es una parada del tabulador
checkGridCellLines(String gridId, String rowId, String columnId, int lines)Comprueba el número de líneas que muestra el texto de una celda (una columna con varias líneas, con o sin max-lines)
checkGridCellLinesAtLeast(String gridId, String rowId, String columnId, int lines)Comprueba que el texto de una celda se divide al menos en un número de líneas
checkGridCellTextCut(String gridId, String rowId, String columnId, boolean cut)Comprueba que el texto de una celda se muestra completo, o que lo corta su número máximo de líneas
checkGridRowTaller(String gridId, String rowId, String otherId, String columnId)Comprueba que una fila de un grid es más alta que otra
checkGridRowFitsItsCells(String gridId, String rowId, String columnId)Comprueba que ningún texto en varias líneas de una fila se sale de la fila
checkGridRowNotCut(String gridId, String rowId, String columnId)Comprueba que una fila no está cortada: queda entera dentro de cada elemento de la grid que la rodea y recorta su contenido (desbordamiento vertical distinto de visible)
checkGridCellTitle(String gridId, String rowId, String columnId, String text)Comprueba que el valor completo de una celda se expone como el título (tooltip) de su texto
checkGridCellFullTextTooltip(String gridId, String rowId, String columnId, String text)Comprueba que el texto cortado de una celda muestra su valor completo en un tooltip al pasar el ratón y con el foco del teclado, y que desaparece al salir
checkGridCellSecondaryLine(String gridId, String rowId, String columnId, String main, String secondary)Comprueba el valor principal y la segunda línea de una celda de una columna con secondary-field (secondary null: la celda muestra solo la línea principal)
checkGridCellStatus(String gridId, String rowId, String columnId, String text, String tone)Comprueba el estado de una celda de una columna con status-field: su tono (data-tone y clase del tono) y su texto
checkGridCellChips(String gridId, String rowId, String columnId, List<String> chips, List<String> hidden)Comprueba las etiquetas que muestra una celda de una columna chips, en orden, y la etiqueta "+n" cuyo tooltip y etiqueta accesible listan las ocultas (hidden vacía: sin etiqueta "+n")
checkRowSelected(String gridId, String search)Comprueba que la fila de un grid con un texto está seleccionada
measureGridSortRender(String gridId, String columnId)Ordena un grid del cliente con un clic en una cabecera y devuelve los milisegundos que tardan en dibujarse sus filas
openGridColumns(String gridId)Abre el diálogo de columnas de la barra de herramientas de un grid
closeGridColumns(String gridId)Cierra el diálogo de columnas de un grid con su botón de cierre (x), conservando los cambios
checkGridColumnsPanel(String gridId, String panel)Comprueba que las columnas de un grid están abiertas como popover anclado al botón de columnas (popover, el valor por defecto), como diálogo modal (dialog) o como panel lateral (side-panel), según el atributo columns-panel del grid
checkGridColumnsCounter(String gridId, int visible, int total)Comprueba el contador «N de M visibles» de la cabecera del diálogo de columnas
checkGridColumnsListed(String gridId, String columnId)Comprueba que el diálogo de columnas lista una columna
checkGridColumnsNotListed(String gridId, String columnId)Comprueba que el diálogo de columnas no lista una columna. Las columnas que la pantalla oculta nunca aparecen en él
hideGridColumn(String gridId, String columnId)Oculta una columna desmarcándola en el diálogo de columnas (que debe estar abierto)
showGridColumn(String gridId, String columnId)Muestra una columna marcándola en el diálogo de columnas (que debe estar abierto)
moveGridColumnUp(String gridId, String columnId)Sube una columna una posición con su flecha en el diálogo de columnas (antes pasa el ratón por la fila: las flechas solo se ven en la fila bajo el puntero)
moveGridColumnDown(String gridId, String columnId)Baja una columna una posición con su flecha en el diálogo de columnas (antes pasa el ratón por la fila: las flechas solo se ven en la fila bajo el puntero)
checkGridColumnCannotMoveUp(String gridId, String columnId)Comprueba que una columna no puede subir: es la primera de su cabecera de grupo, o la primera sin grupo
checkGridColumnCannotMoveDown(String gridId, String columnId)Comprueba que una columna no puede bajar: es la última de su cabecera de grupo, o la última sin grupo
clickGridColumnsShowAll(String gridId)Obsoleto: las columnas ya no tienen Mostrar todas. Usa showGridColumn(String, String) para cada columna oculta
clickGridColumnsReset(String gridId)Pulsa Restaurar predeterminado en el diálogo de columnas: vuelve a la definición de la pantalla y se elimina la disposición guardada. Es silencioso (sin mensaje cuando funciona): el paso espera a que responda el servidor, cuando las columnas dejan de estar aria-busy
clickGridColumnsCancel(String gridId)Pulsa Cancelar en el diálogo de columnas: deshace los cambios hechos desde que se abrió (o desde el último Restaurar predeterminado) y espera a que se cierre
clickGridColumnsSave(String gridId)Pulsa Guardar en el diálogo de columnas: guarda la disposición como la predeterminada del usuario. Es silencioso (sin mensaje cuando funciona) y cierra las columnas cuando la disposición se guarda: el paso espera a que se cierren, y falla si siguen abiertas porque la disposición no se guardó
checkGridColumnsLeftToRight(String gridId, String... columnIds)Comprueba que las columnas se muestran en ese orden, de izquierda a derecha
checkGridColumnNotShown(String gridId, String columnId)Comprueba que una columna no se muestra en el grid
checkGridGroupHeaderOver(String gridId, String title, String firstColumnId, String lastColumnId)Comprueba que una cabecera de grupo abarca exactamente desde su primera hasta su última columna visible, con tres píxeles de margen
checkGridGroupHeaderNotShown(String gridId, String title)Comprueba que una cabecera de grupo no se muestra (ninguna de sus columnas está visible)
maximizeGrid(String gridId)Maximiza un grid con el botón de su barra de herramientas
restoreGrid(String gridId)Restaura un grid maximizado con el botón de su barra de herramientas
checkGridMaximized(String gridId)Comprueba que un grid ocupa el área de contenido de la pantalla (su maximize target) y la cabecera sigue visible
checkGridNotMaximized(String gridId)Comprueba que un grid está restaurado
maximizeWindow(String windowId)Maximiza una ventana (maximize="true" y con etiqueta) con su botón de maximizar
restoreWindow(String windowId)Restaura una ventana maximizada con su botón de restaurar
checkWindowMaximized(String windowId)Comprueba que una ventana ocupa el área de contenido de la pantalla (su maximize target) y la cabecera sigue visible
checkWindowNotMaximized(String windowId)Comprueba que una ventana está restaurada (ya no está fija sobre el área de contenido)
maximizeTab(String tabId)Maximiza un componente de pestañas (lista y contenido, maximize="true") con su botón de maximizar
restoreTab(String tabId)Restaura un componente de pestañas maximizado con su botón de restaurar
checkTabMaximized(String tabId)Comprueba que un componente de pestañas ocupa el área de contenido de la pantalla (su maximize target) y la cabecera sigue visible
checkTabNotMaximized(String tabId)Comprueba que un componente de pestañas está restaurado (ya no está fijo sobre el área de contenido)
checkGridIconVisible(String gridId, String columnId, String icon)Comprueba que el icono de una columna de iconos se muestra (plus, sin el prefijo de la librería)
checkColumnSuccessIcon(String columnId)Comprueba que una columna muestra un icono de éxito

Grids en árbol​

PasoQué hace
clickTreeButton(String gridId, String rowId)Expande o contrae una fila
checkTreeRowVisible(String gridId, String rowId)Comprueba que una fila se muestra
checkTreeRowNotVisible(String gridId, String rowId)Comprueba que una fila no se muestra
checkTreeRowDeleted(String gridId, String rowId)Comprueba que una fila se muestra como eliminada (marcada para eliminar al guardar)
checkTreeIconVisible(String gridId, String rowId)Comprueba que el icono de expandir/contraer de una fila se muestra
checkTreeIconNotVisible(String gridId, String rowId)Comprueba que el icono de expandir/contraer de una fila no se muestra

Pestañas, asistentes, gráficos, listas de etiquetas y frames​

PasoQué hace
clickTab(String tabName, String tabLabel)Hace clic en una pestaña de un criterio de pestañas. La etiqueta es la clave de locale (AngularJS) o el texto traducido (React)
checkActiveWizardStep(String number)Comprueba el número del paso activo de un asistente
checkTagListContains(String tagListId, String text)Comprueba que una lista de etiquetas (vista de texto) contiene un texto
checkTabInvalid(String tabId, boolean invalid)Comprueba que un criterio de pestañas marca una pestaña como que contiene campos que han fallado la validación (true), o que no marca ninguna (false)
checkWizardStepInvalid(String wizardId, boolean invalid)Lo mismo para los pasos de un asistente
checkChartVisible(String chartId)Comprueba que un gráfico se muestra (el gráfico se ha dibujado)
checkPivotTableVisible(String pivotTableId)Comprueba que una tabla dinámica se muestra (su tabla se ha dibujado)
zoomChart(String chartId)Hace zoom en un gráfico con la rueda del ratón sobre su centro
checkChartZoomed(String chartId)Comprueba que un gráfico tiene zoom (muestra una parte de su rango)
checkCardValue(String cardId, String expectedText)Comprueba el valor de una tarjeta, su número y su unidad (por ejemplo 412,600 €); los espacios entre ellos no cuentan
checkCardDelta(String cardId, String expectedText, String trend, String tone)Comprueba la variación de una tarjeta: su texto (por ejemplo +12.5% o −0.18 pt, con el signo menos U+2212), su dirección (up, down o flat) y su tono (success, warning, danger, info o neutral)
checkCardChip(String cardId, String expectedText, String tone)Comprueba el chip de estado de una tarjeta: su texto y su tono (success, warning, danger, info o neutral)
checkCardChipChangesOnClick(String cardId, String buttonId)Pulsa un botón y comprueba que el chip de estado de una tarjeta cambia de texto, con un tono válido (por ejemplo un botón de recarga)
checkCardProgress(String cardId, int percent)Comprueba el progreso de una tarjeta hacia su objetivo, el porcentaje que anuncia su barra de progreso
checkCardBodyComponent(String cardId, String componentId)Comprueba que un componente (un criterio, una rejilla o un gráfico) se muestra en el cuerpo de una tarjeta
checkMapVisible(String mapId)Comprueba que un mapa se muestra con sus marcadores dibujados (solo cliente React)
checkMapMarkerCount(String mapId, int count)Comprueba cuántos marcadores muestra un mapa (solo cliente React)
clickMapMarker(String mapId, String rowId)Hace clic en el marcador de una fila de un mapa (solo cliente React)
checkMapMarkerSelected(String mapId, String rowId)Comprueba que el marcador de una fila de un mapa está seleccionado (solo cliente React)
checkMapRouteVisible(String mapId)Comprueba que un mapa dibuja una ruta (solo cliente React)
checkLogViewerContains(String text)Espera hasta que el visor de logs muestra un texto
checkFileViewerContains(String text)Espera hasta que el visor de ficheros muestra un texto
checkTextInEmbeddedFrame(String contentCssSelector, String text)Comprueba un texto dentro del frame que incrusta una aplicación externa. El selector pertenece a esa aplicación, por lo que es el único literal que puedes pasar

Descargas​

PasoQué hace
startRecordingDownloads()Empieza a contar los ficheros que descarga la pantalla a partir de ahora
checkFilesDownloaded(int count)Comprueba que la pantalla ha descargado exactamente count ficheros desde startRecordingDownloads()

Comprobaciones en bruto​

Estos pasos reciben un selector CSS. Existen para los ayudantes de tu producto y para componentes que AWE no conoce, no para las clases de prueba: consulta Mantener las pruebas de navegador libres de selectores.

PasoQué hace
click(String cssSelector)Hace clic en un elemento
checkText(String cssSelector, String text)Comprueba el texto completo de un elemento
checkTextContains(String cssSelector, String text)Comprueba que el texto de un elemento contiene un texto
checkTextNotContains(String cssSelector, String text)Comprueba que el texto de un elemento no contiene un texto
checkPresence(String cssSelector)Comprueba que un elemento está presente
checkVisible(String cssSelector)Comprueba que un elemento es visible (también con un Locator; la forma By está obsoleta)
checkNotVisible(String cssSelector)Comprueba que un elemento no es visible (también con un Locator; la forma By está obsoleta)
checkVisibleAndContains(String cssSelector, String search)Comprueba que un elemento es visible y contiene un texto

Compatibilidad de la API de awe-testing​

Los productos extienden SeleniumUtilities y compilan contra awe-testing, por lo que su API pública y protegida es un contrato de compatibilidad: una nueva versión de awe-testing no debe romper una suite de pruebas que compilaba contra la anterior. Añadir miembros o marcarlos como obsoletos (@Deprecated) está permitido; eliminar o cambiar la firma de un miembro público o protegido no lo está.

El contrato lo hace cumplir la compilación. El módulo awe-testing ejecuta japicmp en la fase verify y compara el jar recién compilado con la última versión publicada, establecida por la propiedad awe-testing.api-baseline.version en awe-framework/awe-testing/pom.xml. La compilación falla ante cualquier cambio incompatible binario o de código fuente de un miembro público o protegido de una clase de awe-testing. El informe se escribe en awe-framework/awe-testing/target/japicmp/.

La comprobación solo ve las clases de awe-testing: sus supertipos de terceros (Selenium, JUnit, Spring) no están en el classpath de comparación. Un cambio que procede de esas librerías (por ejemplo una actualización de Selenium que cambia un tipo usado en una firma) no lo detecta esta comprobación; aparece cuando se compila la suite del producto.

Si una ruptura es intencionada, documéntala en el changelog y añade una exclusión estrecha y comentada en ese pom (nunca una exclusión general). Tras cada versión de develop (5.x), actualiza la propiedad de línea base a la versión recién publicada, de modo que la siguiente versión se compruebe contra ella (consulta los pasos de publicación en CONTRIBUTING.md). Para ejecutar la comprobación por separado:

mvn verify -pl awe-framework/awe-testing -DskipTests

Usa -Djapicmp.skip=true para omitirla en local (por ejemplo cuando estás sin conexión y la línea base no está en tu repositorio local).

Hooks de prueba estables (data-testid)​

Los componentes de AWE exponen un pequeño conjunto fijo de atributos data-testid. Nombran la parte de un componente, nunca una instancia, de modo que una prueba sigue funcionando cuando AWE reemplaza la librería que renderiza el componente (select2, datepicker, ui-grid, Bootstrap...). Prefiere estos hooks a las clases de librería (.select2-*, .datepicker-*, .ui-grid-*, .modal, .nav-tabs, .fa-*...) en tus propios selectores.

La instancia se identifica siempre con el atributo que AWE ya renderiza: criterion-id en el contenedor de un criterio, grid-id / tree-grid-id, row-id y column-id en los grids, option-id en un menú contextual, name en el menú de la aplicación, info-dropdown-id, dialog-id y el id de botones y paneles. Algunas librerías añaden su DOM al final del <body> (el desplegable del select, el popup del selector de fechas, los popovers). Esos elementos llevan data-testid-owner="<component id>" para que una prueba pueda saber qué componente los abrió.

Criterios​

data-testidElementoDónde encontrarlo
criterion-inputEl control real del criterio: el input, textarea, checkbox, radio o input oculto, el valor de una vista de texto, o el selector de archivos de un uploader. En un select o suggest es el input oculto que contiene el valor: interactúa con select. Los editores de las celdas de un grid editable también lo llevanDentro de [criterion-id='X'], o dentro de la celda del grid
selectContenedor select2 visible de un select o suggest (el elemento en el que hacer clic)Dentro de [criterion-id='X']
select-valueValor elegido de un select simpleDentro de [criterion-id='X']
select-choice, select-choice-closeUn elemento elegido de un select o suggest múltiple, y el enlace que lo eliminaDentro de [criterion-id='X']
select-searchInput de búsqueda (el del desplegable para un select simple, el del contenedor para un select múltiple)Desplegable, o dentro de [criterion-id='X']
select-dropdownEl desplegable abierto. Solo el desplegable abierto lleva el hookFinal de <body>, con data-testid-owner
select-optionUna opción del desplegable abiertoDentro del desplegable, con data-testid-owner
datepickerEl popup de fecha abiertoFinal de <body>, con data-testid-owner
datepicker-day, datepicker-month, datepicker-yearCeldas de día, mes y año del popupDentro del popup, con data-testid-owner
upload-filename, upload-clearNombre del archivo subido y botón que lo borraDentro de [criterion-id='X'], o dentro de la celda del grid
upload-drop-zone, upload-preview, upload-captureÁrea de arrastre (solo con drop-zone="true"), vista previa de la imagen del archivo elegido y botón de la cámara (solo con capture)Dentro de [criterion-id='X']
loaderCargador de un criterio o de cualquier otro componenteDentro del componente
select-triggerLa flecha que abre el panel de un select. Haz clic en ella en lugar de en el medio de un select corto, donde puede estar el icono de borrar. Solo ReactDentro de [criterion-id='X']
criterion-unitEl complemento de unidad de un criterio (el texto tras el input, como EUR). Solo ReactDentro de [criterion-id='X']
criterion-errorEl error de validación de un criterio (AngularJS lo dibuja en el contenedor de errores compartido). Solo ReactDentro de [criterion-id='X']

En un checkbox o radio de botones, el cliente React renderiza un criterio para todo el grupo, y cada opción es un criterion-input que lleva option-id con su valor: [criterion-id='X'] [data-testid='criterion-input'][option-id='Y']. AngularJS renderiza cada opción como un criterio propio. Usa clickCheckboxOption(criterionName, optionId).

Grids y árboles​

data-testidElementoDónde encontrarlo
gridRaíz de un grid o grid en árbol[grid-id='X'], [tree-grid-id='X']
grid-header-cellCelda de cabecera de una columnaDentro del grid, con column-id
grid-header-checkboxEtiqueta del checkbox "seleccionar todo". data-selectedDentro del grid
grid-viewportZona desplazable de las filas. data-container es body, left o right (columnas fijas)Dentro del grid
grid-rowUna fila, con row-id. data-selectedDentro del viewport
grid-cellUna celda: el elemento que lleva column-id, sea lo que sea lo que renderice (valor, editor, checkbox, icono de árbol)Dentro de una fila, con column-id
grid-row-checkboxEtiqueta del checkbox de selección de una fila. data-selectedDentro de la celda
grid-row-save, grid-row-cancelBotones de guardar y cancelar de un grid editableDentro del grid
grid-pagination, grid-page-previous, grid-page-next, grid-goto-page, grid-page-size, grid-pagination-infoPaginación del pie, sus flechas anterior/siguiente (data-disabled) del paginador compacto, el input "ir a la página" (solo AngularJS) y el select del tamaño de página. En React el tamaño de página lleva además data-value, porque el desplegable repite el textoDentro del grid
grid-loaderCargador del grid (también de la tabla dinámica)Dentro del grid
grid-empty, grid-empty-title, grid-empty-text, grid-empty-actionEl estado vacío de un grid sin filas, su título, su texto y el botón que ofreceDentro del grid
grid-toolbar, grid-columns-button, grid-maximize-buttonLa barra de herramientas de un grid (con data-testid-owner = id del grid, y data-maximized) y sus botones de columnas y maximizarDentro de la página
window, window-maximize-buttonUna ventana (con data-testid-owner = id de la ventana, y data-maximized) y su botón de maximizarDentro de la página
tabs, tab-maximize-buttonUn componente de pestañas completo, lista y contenido (con data-testid-owner = id de la pestaña, y data-maximized), y su botón de maximizarDentro de la página
grid-columns-dialog, grid-columns-counter, grid-columns-show-all, grid-columns-reset, grid-columns-save, grid-columns-closeEl diálogo de columnas de un grid (con data-testid-owner = id del grid), su contador «N de M visibles» (data-visible, data-total) y sus accionesDentro de la página
grid-column-group, grid-column-item, grid-column-checkbox, grid-column-handle, grid-column-up, grid-column-downEn el diálogo de columnas: un grupo de columnas (su título es su texto), una columna (column-id) con su casilla, su asa de arrastre y sus flechasDentro del diálogo
grid-group-headerUna cabecera de grupo de un grid, sobre sus columnasDentro del grid
tree-iconIcono de expandir/contraer de una fila de árbol. data-expanded y data-loadingDentro de la celda
tree-header-iconIcono de expandir/contraer todo de la cabecera. Solo AngularJSDentro del grid
column-iconEl icono de una columna de iconos. data-icon lleva el icono del valor de la celda (por ejemplo fa-plus en un grid de multioperación)Dentro de la celda
grid-row-editEl botón que inicia la edición de una fila. Solo ReactDentro de la fila

Pestañas y asistentes​

data-testidElementoDónde encontrarlo
tab-listLas cabeceras de pestañas de un criterio de pestañas. data-disabledDentro de [criterion-id='X']
tab, tab-link, tab-labelUna cabecera de pestaña (li con el id tab-<value>, data-active y data-invalid), el enlace en el que hacer clic y la etiquetaDentro de la lista de pestañas, o dentro del tabdrop-menu cuando la pestaña no cabe
tab-paneContenido de una pestaña, con el id del panel. data-activeDentro de [criterion-id='X']
tabdrop, tabdrop-toggle, tabdrop-menuEl desplegable "más" que contiene las pestañas que no caben, el botón que lo abre y su menú. Solo AngularJS: la lista de pestañas de React no tiene menú "más"Dentro de la lista de pestañas
wizard-step, wizard-paneUna cabecera de paso (data-active, data-completed y data-invalid) y un panel de contenido (data-active)Dentro de [criterion-id='X']
wizard-step-numberEl número que muestra un paso. Solo React: el paso muestra un icono en lugar del número cuando tiene uno, y entonces data-step-number en el wizard-step lleva el númeroDentro del paso

Botones, menús e info​

data-testidElementoDónde encontrarlo
buttonEl <button> de un botón, con el id del botón (también dentro de celdas de grid)Cualquier lugar
context-menu, context-menu-option, context-menu-link, context-submenuUn menú contextual, una opción (con option-id), su enlace (data-disabled) y un menú anidadoDentro del componente que posee el menú
menu, menu-option, menu-link, menu-dropdown, menu-submenuEl menú de la aplicación, una opción (data-active, data-open), su enlace (con name), el desplegable de primer nivel y los submenús anidados (data-open)Dentro del menú
info-dropdown, info-dropdown-toggle, info-dropdown-menuUn desplegable de info (con info-dropdown-id), el enlace que lo abre y su menúCualquier lugar
info-button, info-button-linkUn botón de info y su enlace (info-button-link: solo AngularJS)Cualquier lugar
avatar, avatar-nameEl avatar del usuario autenticado en la cabecera (lleva el id y el nombre de usuario como título) y el nombre junto a él. Solo ReactCabecera

Gráficos, listas de etiquetas y logs​

data-testidElementoDónde encontrarlo
chartUn gráfico. Se identifica por chart-id, y data-rendered es true una vez dibujado el gráfico: espera a ello. Solo React (AngularJS: el svg de [chart-id='X'])Dentro de la pantalla
tag-listUna lista de etiquetas (vista de texto), identificada por tag-list-id. Solo React (AngularJS: [awe-tag-list='X'])Dentro de la pantalla
log-viewerEl elemento que contiene el texto del visor de logsDentro de la pantalla

Usa checkChartVisible(chartId), checkTagListContains(tagListId, text) y checkLogViewerContains(text) en lugar de estos selectores.

Mensajes, diálogos y cargadores​

data-testidElementoDónde encontrarlo
alert, alert-title, alert-message, alert-closeUna alerta de la zona de alertas (data-type es success, info, warning o danger), su título, su texto y su botón de cerrarZona de alertas
popover, popover-title, popover-contentEl mensaje mostrado sobre un componente (un popover de Bootstrap en AngularJS, un overlay panel de PrimeReact en React) (data-type, y data-testid-owner con el componente al que apunta)Final de <body>
help-popoverSolo AngularJS. La ayuda de un componente, renderizada una vez para toda la aplicación. Se muestra solo mientras data-open es trueZona de alertas
dialog, dialog-closeUn diálogo modal (con data-testid-owner = id del diálogo, y data-open) y el botón de su cabecera que lo cierraDentro de [dialog-id='X']
confirm-dialog, confirm-accept, confirm-cancelEl diálogo de confirmación y sus botonesFinal de la zona de alertas
loaderUn cargador de componente (criterios, columnas, selects). Los grids usan grid-loaderDentro del componente
error-boundary, error-boundary-title, error-boundary-message, error-boundary-details, error-boundary-retry, error-boundary-reloadSolo React. El panel que muestra un error boundary cuando una parte de la aplicación falla al renderizar (data-scope es app o view), su título, el mensaje del error, su pila de componentes y sus botones. Lee el mensaje y la pila del código fuente de la página de la evidencia del fallo para diagnosticar una pantalla en blancoEn lugar de la parte que falló (view) o de toda la aplicación (app)
loading-bar, loading-spinnerLa barra de carga global (solo AngularJS) y su spinner. React no tiene barra de carga: muestra el spinner mientras se carga una vista. Existen solo mientras la aplicación se está cargandoFinal de <body>
note

Los hooks de carga (loader, grid-loader) están presentes desde el inicio de la carga; solo los esqueletos visuales aparecen tras 150 ms. Sigue esperando con estos hooks. Un grid que se recarga con el cargador skeleton mantiene sus filas, atenuadas, con aria-busy="true".

Estado​

El estado que necesita una prueba se expone como atributos de datos, de modo que no dependa de clases de librería. Son siempre "true" o "false", excepto data-type, data-container, data-icon, data-value y data-step-number:

AtributoSignificado
data-selectedFila o checkbox seleccionado, celda del selector de fechas seleccionada
data-activePestaña, paso de asistente, asistente o panel de pestaña y opción de menú activos; la celda del selector de fechas que tiene el foco del teclado
data-disabledLista de pestañas, enlace de menú contextual, flecha de página anterior/siguiente o celda del selector de fechas deshabilitados
data-openOpción de menú o submenú que está abierto; popover de ayuda que se muestra; diálogo que está abierto. Un diálogo vuelve a ser false solo cuando se ha eliminado su fondo (backdrop), de modo que la pantalla es interactiva
data-maximizedBarra de herramientas de un grid, ventana o componente de pestañas que ocupa el área de contenido
data-expanded, data-loadingFila de árbol que está expandida, o que está cargando sus hijos
data-completedPaso de asistente que ya está hecho
data-invalidPestaña, paso de asistente o elemento de acordeón (AngularJS: el panel del elemento) que contiene campos que han fallado la validación
data-outside-monthDía del selector de fechas que pertenece al mes anterior o siguiente
data-typeTipo de un mensaje
data-containerContenedor del viewport de un grid: body, left o right
data-iconClases de icono que muestra una columna de iconos. Haz coincidir una con [data-icon~='fa-plus']
data-editingFila de grid que se está editando. Solo React: el cliente React puede editar una fila que no está seleccionada (AngularJS edita la fila seleccionada)
data-deletedFila de grid marcada para eliminar cuando se guarda el grid (grids de multioperación y grids en árbol). Solo React. Consulta checkTreeRowDeleted(gridId, rowId)
data-renderedUn gráfico que se ha dibujado. Solo React
data-zoomedUn gráfico que muestra una parte de su rango porque tiene zoom. Solo React
data-step-numberNúmero de un paso de asistente (un texto, no un booleano). Solo React
data-valueValor de un control cuyo texto lo repite, como el tamaño de página de un grid (un texto, no un booleano). Solo React
// Value of the input of a criterion
Locator input = Locator.css("[criterion-id='Txt'] [data-testid='criterion-input']");

// Option "Yes" of the open dropdown of the criterion "Sta"
Locator option = Locator.css("[data-testid='select-option'][data-testid-owner='Sta']");

// Enabled days of the current month in the popup of the criterion "Cal"
Locator days = Locator.css("[data-testid='datepicker'][data-testid-owner='Cal'] "
+ "[data-testid='datepicker-day'][data-outside-month='false'][data-disabled='false']");

// Cell "name" of the selected rows of the grid "Grd"
Locator cell = Locator.css("[grid-id='Grd'] [data-testid='grid-row'][data-selected='true'] "
+ "[data-testid='grid-cell'][column-id='name']");

// Active tab of the criterion "Tab" and the danger alerts
Locator tab = Locator.css("[criterion-id='Tab'] [data-testid='tab'][data-active='true']");
Locator danger = Locator.css("[data-testid='alert'][data-type='danger']");

El vocabulario vive en una constante JavaScript para cada cliente: TestIds y TestAttributes en awe-client-angular (js/awe/data/testIds.js, disponible también como constante de AngularJS) y en awe-client-react (src/utilities/testIds.js). El mismo concepto usa el mismo valor en ambos, y las constantes Java de awe-testing (TestIds, TestAttributes) los reflejan: TestIdsVocabularyTest falla si se desvían. Una parte que solo renderiza un cliente se marca como Solo React o Solo AngularJS en las tablas anteriores. Los hooks son aditivos: no se elimina ninguna clase, id o atributo existente.

Rara vez necesitas estos hooks en una prueba: un paso ya los usa. Son para los ayudantes de tu producto y para un componente para el que AWE aún no tiene paso (entonces plantéate añadir el paso).

Clientes AngularJS y React​

AWE 5 tiene dos clientes web, y la aplicación de test de cada uno ejecuta su propia suite (awe-tests/awe-boot para AngularJS, awe-tests/awe-boot-react para React). El cliente bajo prueba se establece con awe.test.frontend (angular por defecto, react). Los pasos de SeleniumUtilities ocultan las diferencias, de modo que el mismo paso funciona en ambos. Estas son las diferencias con las que un autor de pruebas todavía se encuentra, porque son visibles en las pantallas y no en el renderizado:

TemaAngularJSReactQué hacer en una prueba
Cierre de sesiónEl botón de cerrar sesión es visible en el shellEl botón de cerrar sesión está dentro del menú del avatar, que checkLogout() abreUsa checkLogout(). Si la aplicación pide una confirmación (la aplicación de referencia de React lo hace), usa checkLogoutWithConfirmation() Los pasos ensureLoggedIn y ensureLoggedOut eligen la variante por sí mismos
Etiqueta de pestañaclickTab coincide con la clave de locale de la etiqueta (ENUM_MATRIX_EDITABLE)clickTab coincide con el texto traducido que ve el usuario (Editable)Pasa el valor del cliente que pruebas. Si una suite apunta a ambos, mantén la etiqueta en una constante por suite
Valor de suggestEl valor elegido se muestra como textoEl valor elegido es el valor de un inputUsa checkSuggestContents para un suggest y checkSelectContents para un select; nunca leas el valor con un selector
Grupos de botonesCada opción de un checkbox o radio de botón es un criterio propioUn grupo de botones es un criterio y cada opción lleva su option-idUsa clickCheckboxOption(criterionName, optionId), y checkCheckboxRadio(...) para el estado. Un grupo puede no tener ninguna opción marcada por defecto
Fila editada y fila seleccionadaLa fila que editas es la fila seleccionadaUna fila puede editarse sin estar seleccionada (data-editing), y un grid multiselección alterna la selección cuando haces doble clic para editarUsa editRow(...) y los pasos de celda; usa toggleRowContents(gridId, search) cuando necesites que la selección cambie aunque la fila estuviera seleccionada
Gesto de ediciónUn solo clic en la filaDoble clic, reintentado hasta que el editor está abiertoUsa editRow(...). No hagas clic tú mismo en la celda
NúmerosNada especial: escribe el valorEl input numérico (InputNumber de PrimeReact) toma un . o , escrito como su separador decimal y añade él mismo el separador de milesEscribe solo los dígitos y el separador decimal, no el separador de miles: writeText("Unt", "325274,50"), y después comprueba 325.274,50 con checkCriterionContents
Errores de criterioSe muestran en el contenedor de errores compartidoSe muestran bajo el criterio (criterion-error)Usa checkValidationErrorVisible()
Popoverspopover (target messages) y help-popover (ayuda)popover (target messages, un overlay panel de PrimeReact que no se oculta al pasar el ratón); la ayuda es un tooltipUsa checkMessagePopover, dismissMessagePopover y waitForMessagePopoverToHide para los target messages, checkMessageTitle y checkMessageText para los mensajes
Pestañas que no cabenUn menú "más" (tabdrop) las contieneSin menú "más"clickTab abre el menú cuando el cliente tiene uno
Barra de cargaUna barra de carga en la parte superior de la páginaSolo un spinner mientras se carga una vistaUsa waitForLoadingBar(), que sabe qué esperar
Tamaño de página de impresiónUn selector nativoUn desplegable con un selector oculto: el texto repite el valor, por lo que el tamaño de página se expone en data-valueUsa checkGridPageSize(size)

Cuando un comportamiento difiere y ninguna fila anterior ayuda, no bifurques según el cliente en tu prueba: añade un paso (consulta Añadir un paso que falta) y deja que decida el perfil de motor de cada cliente.

Locators neutrales respecto a la herramienta (vista previa)​

Los perfiles de motor (IAweFrontEndInstructions) siguen devolviendo By de Selenium en AWE 5.0, por lo que nada cambia para tus pruebas. Los pasos que reciben un By tienen un gemelo que recibe un Locator: consulta Pasos personalizados sin tipos de Selenium. Para que las pruebas de navegador puedan ejecutarse más adelante en otra herramienta de automatización sin reescribirse, com.almis.awe.testing.driver.Locator es un locator inmutable y neutral respecto a la herramienta (Locator.css(...) o Locator.xpath(...), con kind() y expression()). Se obtiene uno a partir de un By con Locator.from(by) (o Locator.from(listOfBy)): CSS y XPath se mantienen tal cual y By.id se convierte en el selector CSS de id equivalente, con escapes. Otros tipos de By (name, class name, link text, tag name) se rechazan con una IllegalArgumentException, porque los perfiles no los usan. toBy() vuelve a Selenium. Una prueba unitaria comprueba que todos los locators de los perfiles de AngularJS y React se convierten. Locator es una vista previa: sus métodos de conversión a Selenium pueden pasar al adaptador de Selenium en una versión posterior.

Detrás de él, com.almis.awe.testing.driver.BrowserDriver es el puerto interno y neutral respecto a la herramienta sobre el que se ejecutará SeleniumUtilities, con SeleniumBrowserDriver como su adaptador de Selenium (disponible también desde SeleniumModel.getBrowser()). Es una vista previa sin promesa de compatibilidad todavía: escribe tus pruebas con los pasos de SeleniumUtilities, no contra el puerto. Además de consultas y acciones cubre la apertura de páginas, scripts, desplazamiento, frames, tamaño de ventana, evidencia de fallos (captura de pantalla, código fuente de la página, consola del navegador) y el cierre del navegador.

Herramienta de automatización (awe.test.tool)​

La herramienta que controla el navegador se elige con awe.test.tool. Sus valores son selenium (el valor por defecto, por lo que nada cambia para las suites existentes) y playwright, que es un piloto para proyectos propios (ver más abajo) aunque es la herramienta de navegador bloqueante del pipeline de AWE. El nombre no distingue mayúsculas de minúsculas, y un valor que no es una herramienta soportada detiene las pruebas al arrancar con un mensaje que lista las soportadas. getDriver() es la única parte de la API que pertenece a Selenium: devuelve el driver de Selenium con selenium y lanza una UnsupportedOperationException con cualquier otra herramienta, así que escribe tus propios pasos con los pasos neutrales y getBrowser().

mvn -f awe-tests/awe-boot-react/pom.xml verify -Dskip.junit=true -Dskip.selenium=false \
-Dawe.test.tool=selenium -Dawe.test.browser=headless-chrome -Dit.test=SchedulerCalendarTestsIT

Playwright​

awe.test.tool=playwright ejecuta las mismas pruebas con Playwright for Java, que forma parte de awe-testing (no hay nada más que añadir a tu proyecto). Los pasos se comportan como con Selenium: las mismas pausas, los mismos desplazamientos antes de un clic, consultas que responden de inmediato y son sondeadas por los pasos. Para tus propios proyectos es nuevo: prueba tus suites con él e informa de lo que difiere. El pipeline de AWE ejecuta sus suites de navegador con él: los jobs de Playwright bloquean las merge requests, develop y master (ver más abajo).

El navegador se elige con el habitual awe.test.browser; Playwright ejecuta los navegadores que instala él mismo, no los de tu máquina:

awe.test.browserNavegador de Playwright
chromeChromium, con ventana
headless-chromeChromium, sin ventana
firefoxFirefox, con ventana
headless-firefoxFirefox, sin ventana

Cualquier otro valor (edge, opera, ie, remote-* y todo navegador service-*) detiene las pruebas al arrancar con un mensaje: los navegadores remotos y de servicio aún no están soportados por el piloto de Playwright, ya que son grids de Selenium e imágenes Docker que Playwright no usa. Usa awe.test.tool=selenium para ellos. awe.test.browser-width y awe.test.browser-height establecen el viewport y awe.test.timeout el timeout por defecto de Playwright.

Playwright descarga sus navegadores (Chromium, Firefox y WebKit, aproximadamente 1 GB) en ~/.cache/ms-playwright (en macOS ~/Library/Caches/ms-playwright) la primera vez que una ejecución de pruebas que lo elige inicia Playwright. Para instalarlos de antemano, por ejemplo en la imagen de un job de CI, o para instalar solo el que necesitas, ejecuta su línea de comandos desde tu proyecto, que no necesita ninguna instalación propia de Playwright (--with-deps instala también las librerías del sistema en Linux; --only-shell instala solo el headless shell de Chromium, aproximadamente 200 MB, que es todo lo que necesita headless-chrome):

mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps chromium"

Establece PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 para evitar que descargue nada por su cuenta, y PLAYWRIGHT_BROWSERS_PATH para mantener los navegadores en otro lugar, por ejemplo en una carpeta que cachea la CI.

mvn -f awe-tests/awe-boot-react/pom.xml verify -Dskip.junit=true -Dskip.selenium=false \
-Dawe.test.tool=playwright -Dawe.test.browser=headless-chrome -Dit.test=SchedulerCalendarTestsIT

Las pruebas unitarias de awe-testing que ejecutan un Chromium real mediante Playwright nunca descargan un navegador: usan el que está instalado y se omiten donde no hay ninguno, de modo que una máquina sin navegadores sigue obteniendo una compilación en verde. El job de CI de las pruebas unitarias (Unit · Java) instala solo el headless shell de Chromium con sus librerías, lo cachea entre pipelines y pasa -Dawe.test.playwright.required=true, que convierte un navegador ausente en un fallo en lugar de una omisión; usa el mismo interruptor para asegurarte de que las pruebas se ejecutan en tu máquina. La CI demuestra Chromium en las pruebas del adaptador de Unit · Java; las suites de Playwright siguientes ejecutan Chromium y Firefox. Para comprobar el Firefox del adaptador en tu máquina, ejecuta las mismas pruebas unitarias con -Dawe.test.playwright.engine=firefox (un Firefox que está instalado y falla al arrancar hace fallar la prueba; uno que no está instalado la omite).

Jobs de Playwright en el pipeline de AWE. El pipeline de AWE ejecuta las mismas suites que los jobs de Selenium con awe.test.tool=playwright, como 16 jobs: uno por cada combinación de motor (AngularJS o React), navegador (Chromium o Firefox) y suite (aplicación e integración, CRUD, criterios y grids, regresión, websocket e impresión, y planificador). Los jobs de AngularJS ejecutan la aplicación awe-tests/awe-boot y los de React awe-tests/awe-boot-react. Cada job se llama como lo que ejecuta, E2E · <motor> · <navegador> · <suite> (por ejemplo E2E · React · Chromium · CRUD o E2E · AngularJS · Firefox · Scheduler); los jobs de Selenium se llaman Selenium · <motor> · <navegador> · <suite> (Selenium · React · Chrome · Scheduler, Selenium · React · Firefox · Scheduler), de modo que ningún nombre se repite. Los nombres de las suites son cortos: Application (aplicación e integración), CRUD (CRUD, criterios y grids), Regression (regresión, websocket e impresión) y Scheduler. Son jobs explícitos, no una parallel: matrix: GitLab nombra un job de matriz con los valores de sus variables, y cada motor tiene sus propias rules (en una merge request los jobs de React se ejecutan solo cuando cambia el backend o el cliente React, y los de AngularJS cuando cambia cualquier cosa salvo el cliente React), que una matriz no dejaría fijar a cada entrada. Una plantilla oculta por motor y navegador fija las rules y el TARGET (navegador y motor); el job añade sus pruebas, su puerto y su nombre. Los jobs de Selenium tienen cada uno un servicio de navegador distinto. Dos jobs pequeños, Build · Chromium cache y Build · Firefox cache, descargan y cachean los navegadores para ellos. Cada job instala solo el navegador headless que necesita, lo ejecuta junto a la aplicación que arranca y escribe su evidencia de fallos (captura de pantalla, código fuente de la página, consola, trace y vídeo) en browser-evidence/, que encuentras en los artefactos del job como la evidencia de los jobs de Selenium; el grabador de pantalla de Selenium está desactivado (awe.test.allowed-recording=false), ya que Playwright graba la propia página.

  • Cuándo se ejecutan. Automáticamente en cada merge request con cambios de código, en develop y en master; las merge requests ejecutan solo los jobs de Chromium (los que llevan Chromium en el nombre) del motor cuyo código cambió (un cambio limitado al cliente React omite los jobs de AngularJS, y uno limitado al cliente AngularJS omite los de React). Los de Firefox (E2E · <motor> · Firefox · <suite>) y Build · Firefox cache se ejecutan en master y, en develop, solo en la planificación del pipeline "Nightly Firefox" (CI/CD > Schedules, con la variable FIREFOX_NIGHTLY=true), cuyo pipeline no ejecuta nada más salvo Build · Maven. No se ejecutan en support/*, porque support/4.x no tiene adaptador de Playwright. Los jobs de Selenium (Selenium · <motor> · Chrome · <suite> y Selenium · <motor> · Firefox · <suite>) se ejecutan en master, en support/* y en la planificación semanal del pipeline "Weekly Check" en develop, no en merge requests ni en pushes ordinarios.
  • Bloquean el pipeline. Un job de Playwright que falla hace fallar el pipeline, detiene Analysis · Sonar y los jobs de publicación, e impide que Renovate haga automerge, exactamente igual que un job de Selenium. Tienen la misma reejecución por prueba y el mismo reintento de job (#766), y lo mismo para Firefox que para Chromium. Sus archivos jacoco tienen un nombre distinto por job (jacoco-${TEST_NAME}-it.exec), de modo que la cobertura de ambas herramientas se conserva cuando se ejecutaron ambas.
  • Cómo comparar. En master y en el pipeline planificado semanal se ejecutan ambas herramientas. Abre el job de Playwright y el job de Selenium de la misma suite y lee el resumen de failsafe (Tests run: ...) y la línea Playwright ... que el job de Playwright imprime al final de su log, con su estado y el tiempo desde que empezó el job.

Chromium se inicia con --disable-dev-shm-usage (la memoria compartida de un contenedor es demasiado pequeña para él) y, cuando el sandbox no puede funcionar, sin su sandbox (--no-sandbox): ese es el caso cuando se ejecuta headless, cuando el proceso se ejecuta como root, y dentro de un contenedor (Docker, Podman o Kubernetes, que se detectan por /.dockerenv, /run/.containerenv o KUBERNETES_SERVICE_HOST). Un Chromium que un usuario muestra en un escritorio mantiene su sandbox. Establece awe.test.playwright.no-sandbox=false para mantener el sandbox en cualquier caso, o =true para eliminarlo en cualquier caso; si se deja vacío se aplica la regla anterior.

Diferencias que puedes notar respecto a Selenium:

  • Una ventana no se puede mover (una página de Playwright tiene un viewport, no una ventana en una pantalla): la posición se ignora, con un log de depuración.
  • Un script se abandona cuando la página no responde dentro del timeout de scripts (30 segundos, como en Selenium), de modo que una página cuyo hilo de scripts está bloqueado hace fallar el paso con una ScriptTimeoutException en lugar de colgar la ejecución. La consola de la evidencia de fallos espera dos segundos como máximo a una página así y devuelve lo que tenía. Un script que agotó su tiempo puede seguir ejecutándose en la página (no se puede decir al navegador que lo detenga), y la página permanece bloqueada hasta que termina; un script que no había comenzado no se inicia más tarde. Un script que navega la página (cambia la ubicación, envía un formulario) se ejecuta una vez y devuelve null.
  • Un locator CSS encuentra también los elementos dentro de shadow roots abiertos.
  • Visible significa que el elemento tiene una caja y no es visibility: hidden; habilitado tiene también en cuenta aria-disabled.
  • El grabador de vídeo de la ejecución de pruebas (awe.test.allowed-recording) filma la pantalla de la máquina, no el navegador, de modo que una ejecución headless de Playwright no tiene nada de la prueba que filmar: desactívalo con awe.test.allowed-recording=false. Playwright graba la propia página, consulta su evidencia.

Evidencia de Playwright​

Además de la captura de pantalla, el código fuente de la página y la consola de la evidencia de fallos, Playwright deja dos archivos más en awe.test.screenshot-path (browser-evidence/ en CI, y enlazados desde el log como la captura de pantalla):

ArchivoQué es
<test>.trace.zipEl trace de una prueba fallida: cada acción con sus capturas de pantalla, la consola y la red (y el DOM en cada paso, consulta trace-snapshots más abajo). Un archivo por cada prueba fallida, con el nombre de su captura de pantalla
<qualified.TestClass>.webmEl vídeo de la página durante toda la clase de pruebas (con el nombre de la clase cualificada, p. ej. com.almis.awe.test.selenium.CRUDSiteTestsIT.webm). Uno por clase, y conservado solo cuando una prueba de la clase falló. La repetición de una prueba fallida (it.rerun-count) es una sesión nueva de la clase: su vídeo es <qualified.TestClass>-2.webm (luego -3...), junto al de la primera ejecución, que es el que tiene el contexto del fallo
<qualified.TestClass>.video-times.txtJunto al vídeo: el inicio de cada prueba de la clase desde el comienzo del vídeo (mm:ss.SSS, aproximado), su resultado y su nombre. Busca la línea FAILED y lleva el vídeo hasta ahí

El navegador dura toda la clase de pruebas (sus pruebas ordenadas comparten el inicio de sesión y los datos), de modo que el vídeo es de la clase y no de la prueba; el archivo de tiempos indica dónde empieza cada prueba.

Para abrir un trace, usa el Trace Viewer de Playwright, que muestra la línea de tiempo, la página en cada acción y sus detalles:

npx playwright show-trace path/to/LoginIT-2026-10-06_10-00-00-000-[ERROR]-option-t020.trace.zip

o suelta el archivo en trace.playwright.dev, que se ejecuta en tu navegador: el archivo no se sube a ningún sitio.

Ambos se eligen con un modo, off, on-failure (el valor por defecto) o always:

PropiedadValor por defectoSignificado
awe.test.playwright.traceon-failureon-failure guarda el trace de cada prueba fallida y descarta los demás; always los guarda todos; off ni siquiera los graba
awe.test.playwright.trace-snapshotsfalsetrue toma también las instantáneas del DOM de cada acción, de modo que el Trace Viewer muestra la página que puedes inspeccionar antes y después de cada una. Hicieron que la suite del Scheduler fuera aproximadamente un 40% más lenta en nuestras mediciones (las capturas de pantalla del trace no cuestan nada apreciable), así que actívalas solo para investigar un fallo que las capturas no expliquen
awe.test.playwright.videoon-failureon-failure conserva el vídeo de una clase con una prueba fallida y elimina los demás; always conserva todos; off no graba vídeo. El vídeo se graba a la mitad del tamaño de ventana (awe.test.browser-width × awe.test.browser-height): codificar un vídeo a tamaño completo hizo que las suites de Chromium fueran aproximadamente un 50% más lentas en los runners de CI

Establece ambos modos a off para no grabar nada en absoluto, por ejemplo cuando depuras otra cosa.

Escribir pruebas Selenium para tu producto​

Las pruebas de tu producto no deberían saber qué librerías usa AWE para dibujar sus componentes. Si lo saben, reemplazar una librería (como AWE 5 hace con select2, ui-grid o el datepicker) rompe todas las suites. Localiza los componentes en este orden:

  1. Un método de SeleniumUtilities. clickButton, selectContain, suggest, selectDate, editRow, clickTab, checkAndCloseMessage... ya saben cómo encontrar cada componente y se mantienen funcionando cuando AWE cambia. Las secciones siguientes los describen.
  2. Un hook data-testid más los atributos de AWE, cuando no hay un método para lo que necesitas. El hook nombra la parte del componente y el atributo de AWE nombra la instancia: criterion-id, grid-id, row-id, column-id, option-id, info-dropdown-id o el id de un botón. TestIds y TestAttributes (com.almis.awe.testing.selenium) declaran el vocabulario para que no teclees las cadenas.
  3. Nunca una clase de librería (.select2-*, .datepicker, .ui-grid-*, .modal, .nav-tabs, .alert, .popover, .btn, fa-*...) ni una posición en el marcado de una librería.
import com.almis.awe.testing.selenium.TestAttributes;
import com.almis.awe.testing.selenium.TestIds;

// Wait for the warning alert of the login and check its text
checkText(TestIds.css(TestIds.ALERT) + TestAttributes.css(TestAttributes.TYPE, "warning") + " "
+ TestIds.css(TestIds.ALERT_MESSAGE), "The credentials entered for the user -test- are not valid");

// Click the first day of the open datepicker that is not disabled
click(TestIds.css(TestIds.DATEPICKER_DAY) + TestAttributes.css(TestAttributes.DISABLED, false));

// Type in the search box of the open select dropdown
writeText(Locator.css(TestIds.css(TestIds.SELECT_DROPDOWN) + " " + TestIds.css(TestIds.SELECT_SEARCH)), "tee");

El atributo owner​

El desplegable de un select, el popup del selector de fechas y los popovers de mensajes se añaden al final del <body>, fuera del componente. Llevan data-testid-owner="<component id>". Úsalo cuando varios componentes puedan estar abiertos o cuando necesites estar seguro de cuál abrió el elemento. Solo hay un desplegable de select (y un selector de fechas) abierto a la vez, y solo el abierto lleva su hook, de modo que select-dropdown solo basta en la mayoría de las pruebas:

// Dropdown and options opened by the select "Sta"
Locator dropdown = Locator.css("[data-testid='select-dropdown'][data-testid-owner='Sta']");

Espera por estado, no por clases​

Expón lo que esperas como estado: data-selected, data-active, data-open, data-expanded, data-loading... (consulta Estado más arriba). Espera un estado que permanezca establecido hasta que la pantalla haya terminado de volver a renderizarse, por ejemplo data-loading='false' en un icono de árbol, o el estado seleccionado de una fila tras hacer clic en ella. Las clases de librería como .active o .fa-spin cambian por razones que no tienen nada que ver con AWE.

Coincide con el texto completo de un elemento​

Una librería puede dividir el texto de un elemento en varios nodos: cuando escribes en un select, las letras coincidentes se resaltan (<span class="select2-match">B</span>ase), y ningún nodo de texto contiene Base. Compara el texto del elemento completo, con contains(normalize-space(.), 'Base') en un xpath, o con getText(). Nunca uses //text()[contains(., 'Base')] sobre componentes de AWE.

Locator option = Locator.xpath("//*[@data-testid='select-dropdown']//*[@data-testid='select-option']"
+ "[contains(normalize-space(.),'Base')]");

Pasos personalizados sin tipos de Selenium​

Un producto que necesita un paso que AWE no tiene lo escribe en una clase ayudante que extiende SeleniumUtilities (consulta Añadir un paso que falta). Escríbelo sin tipos de Selenium, para que siga funcionando si las pruebas se ejecutan en otra herramienta de automatización: localiza con un com.almis.awe.testing.driver.Locator (Locator.css(...) o Locator.xpath(...)), compón los pasos del catálogo que reciben un Locator y, cuando ningún paso encaja, usa getBrowser(), que cualquier subclase puede llamar.

import com.almis.awe.testing.driver.Locator;

public class MyProductSteps extends SeleniumUtilities {

// Archive an invoice of the invoices screen and check that the screen confirms it
protected void archiveInvoice(String invoiceId) {
Locator archive = Locator.css("[row-id='" + invoiceId + "'] [data-testid='archive-action']");
// A step of the catalogue, with a locator
checkVisible(archive);
// No step for the click on a product action: ask the browser
getBrowser().click(archive);
waitForText(Locator.css("[data-testid='archive-status']"), "Archived");
}
}

getBrowser() devuelve el puerto BrowserDriver: consultas (exists, isVisible, text, attribute, count), acciones (click, type, hover...) y operaciones de página, todo con un Locator. Es una vista previa sin promesa de compatibilidad todavía, así que usa los pasos del catálogo cuando haya uno y mantén las llamadas al navegador dentro de tus ayudantes. Los perfiles de motor (IAweFrontEndInstructions) siguen devolviendo By en 5.0; convierte uno en un Locator con Locator.from(by).

Obsoleto: By y getDriver()

Los pasos que reciben o devuelven un By de Selenium (consulta la tabla bajo Pasos obsoletos) y getDriver() están @Deprecated. Siguen funcionando con la herramienta Selenium durante toda la línea 5.x y no se eliminan antes de la 6.0, de modo que un ayudante existente compila sin cambios y solo muestra un aviso de obsolescencia. getDriver() solo está disponible cuando las pruebas se ejecutan con la herramienta Selenium (awe.test.tool=selenium, el valor por defecto): con cualquier otra herramienta lanza una UnsupportedOperationException que apunta a los pasos neutrales y a getBrowser(), por lo que un paso que lo use no funcionará con otra herramienta. Migra un ayudante reemplazando By.cssSelector(x) por Locator.css(x), By.xpath(x) por Locator.xpath(x) y By.id(x) por Locator.css("[id='x']") (#x deja de coincidir cuando el id tiene puntos, dos puntos u otros caracteres especiales de CSS, como suelen tener los ids generados), y las llamadas al driver por los pasos neutrales o getBrowser().

Mantener las pruebas de navegador libres de selectores​

Las clases de prueba *IT solo deberían describir pasos de pantalla: haz clic en este botón, elige este valor, comprueba este mensaje. Cómo se encuentra un elemento (un selector, un hook data-testid, un xpath) y qué herramienta controla el navegador pertenecen a las instrucciones de frontend y a SeleniumUtilities, de modo que la misma prueba sigue funcionando cuando se reemplaza el motor web (AngularJS o React) o cuando cambia la herramienta de automatización. Los locators escritos en los ejemplos anteriores (TestIds, Locator) son para las clases ayudantes de tu producto que extienden SeleniumUtilities, no para las clases de prueba.

En una clase de prueba no uses:

  • Imports de Selenium (org.openqa.selenium.*) o tipos: By, WebDriver (getDriver()), WebElement, Select, JavascriptExecutor (executeScript(...)), Actions.
  • Locator y getBrowser(): son la forma neutral respecto a la herramienta de localizar y controlar elementos, y pertenecen a los ayudantes de tu producto como lo hacía By.
  • TestIds y TestAttributes.
  • Literales de selector en los ayudantes que reciben uno: click, clearText, checkText, checkTextContains, checkTextNotContains, checkPresence, checkVisible, checkNotVisible, checkVisibleAndContains, checkTextInEmbeddedFrame, waitForCssSelector, waitForCssLocator, waitForText con una clase, checkLogin con un selector y checkLogout con un selector. Usa en su lugar los pasos semánticos, por ejemplo checkLogin("test", "test", "Manager (test)") y checkLogout().

BrowserTestSourceGuard (com.almis.awe.testing.guard, en awe-testing) comprueba esta regla sobre los fuentes Java, sin necesitar un navegador ni ninguna clase de Selenium. Por defecto solo analiza las clases de prueba (*IT.java), porque las clases ayudantes de tu producto son donde pertenecen los locators; .files("*Page.java") (cualquier glob sobre el nombre de archivo) analiza otros archivos. AWE lo aplica a sus propias aplicaciones de test en una prueba unitaria (Unit · Java), y un producto puede hacer lo mismo con una prueba propia:

@Test
void shouldKeepTheBrowserTestsFreeOfSelectors() throws IOException {
Report report = BrowserTestSourceGuard.create()
.allow("FileManagerIT.java", "checkTextInEmbeddedFrame(\"ol.breadcrumb a\", \"Files\")",
"The file manager is a third-party application inside a frame")
.scan(Path.of("src/test/java/com/mycompany/selenium")); // only the *IT.java files unless .files(glob) is set

assertThat(report.isClean()).as(report.describe()).isTrue();
}

El informe lista cada violación con su archivo, línea, regla y fragmento. La comprobación de selectores es heurística: solo mira el argumento selector de los ayudantes anteriores, y un literal cuenta como selector cuando contiene [ ] # . > + ~ * / : o un espacio. Un nombre de etiqueta simple ("button") no se detecta, ni tampoco los selectores construidos en una variable, de modo que el guard previene los casos comunes pero no sustituye a la revisión.

Permitir una excepción justificada​

Cuando una excepción está justificada, permite ese fragmento exacto en ese archivo e indica por qué. El motivo es obligatorio, y una autorización que ya no coincide con el código se notifica (como obsoleta) para eliminarla junto con el código que disculpaba. El fragmento es el que se imprime en el informe: la línea ofensora, o la llamada completa para un literal de selector. Usa allow(file, snippet, reason, times) para permitir un número exacto de apariciones.

Migrar una suite existente paso a paso​

Si una suite ya tiene violaciones, lístalas en un archivo de línea base y deja que el guard funcione como un trinquete: una nueva violación falla, y también falla una violación listada que ha desaparecido, de modo que la línea base solo se reduce hasta que la suite está migrada y el archivo se elimina.

String baseline = BrowserTestSourceGuard.create().scan(testSources).toBaseline(); // write it to a file once

Report report = BrowserTestSourceGuard.create()
.allowBaseline(Path.of("src/test/resources/browser-test-guard-baseline.txt"), "Pending migration to semantic steps")
.scan(testSources);

Cada línea de la línea base es file:snippet; las líneas en blanco y las que empiezan por # se ignoran, y una violación repetida en un archivo se lista una vez por aparición.

Añadir un paso que falta​

Si el catálogo no tiene un paso para lo que necesita tu prueba, no escribas un selector en la prueba: añade el paso una vez, para todos los clientes. Hazlo en este orden, primero la prueba:

  1. Comprueba lo que existe. Busca en el catálogo de pasos y en los hooks. Muchos pasos "que faltan" son una sobrecarga (el mismo paso con un gridId y un rowId).

  2. Escribe el paso en la fachada. Añade un método protected a SeleniumUtilities (com.almis.awe.testing.utilities) con un javadoc. Declara qué hace o ve el usuario, recibe solo cadenas, números y booleanos (nunca un tipo de Selenium, By, Locator o CSS) y pregunta a frontEndInstructions dónde está el elemento. Espera un estado (data-*) antes de actuar. Escribe primero la prueba unitaria (consulta SeleniumUtilitiesSemanticStepsTest) y observa cómo falla.

  3. Añade el locator al perfil de motor. En IAweFrontEndInstructions añade un método default que devuelva el locator para el renderizado de AngularJS, mediante un hook data-testid siempre que haya uno. Nunca añadas un método abstracto: una implementación escrita antes dejaría de compilar. Cuando el cliente React renderiza algo distinto, sobrescríbelo en ReactAweInstructions (y en AngularAweInstructions si el valor por defecto no basta). Los locators deben usar hooks y atributos de AWE, nunca clases de librería: AngularAweInstructionsSelectorGuardTest, ReactAweInstructionsSelectorGuardTest y SeleniumUtilitiesLiteralsGuardTest fallan en caso contrario.

  4. Añade el hook si el cliente no tiene ninguno. Declara el valor en el vocabulario de cada cliente que lo renderiza (awe-client-angular/src/main/resources/js/awe/data/testIds.js y awe-client-react/src/utilities/testIds.js) y en TestIds / TestAttributes (awe-testing). El mismo concepto usa el mismo valor en ambos clientes, y una parte que solo renderiza un cliente se marca como "Solo React" o "Solo AngularJS" en un comentario. TestIdsVocabularyTest y las pruebas Jest de los clientes fallan cuando los vocabularios se desvían. Los hooks son aditivos: nunca elimines ni renombres una clase, id o atributo existente.

  5. Documéntalo. Añade una fila al catálogo de pasos y, si hay un nuevo hook o atributo, a Hooks de prueba estables. SeleniumTestGuideSyncTest hace fallar la compilación mientras un paso público o protegido no se menciona en esta guía como name(.

  6. Comprueba el contrato de compatibilidad. El cambio debe ser aditivo: no elimines ni cambies un miembro público o protegido. Ejecuta el módulo completo, incluida la comprobación de la API:

    mvn verify -pl awe-framework/awe-testing

    Después ejecuta la suite del cliente que cambiaste (consulta Ejecutar las suites). Reemplaza un paso solo marcando como obsoleto el antiguo (@Deprecated, indicando el reemplazo en su javadoc y en el catálogo).

Ejecutar las suites y resolver problemas​

Ejecutar una suite en local​

Las aplicaciones de test son awe-tests/awe-boot (AngularJS) y awe-tests/awe-boot-react (React). Compila el proyecto una vez (mvn install -DskipTests), y después ejecuta una clase IT con un navegador headless desde la raíz del repositorio:

mvn -f awe-tests/awe-boot-react/pom.xml verify -Dskip.junit=true -Dskip.selenium=false \
-Dawe.test.browser=headless-chrome -Dit.test=SchedulerCalendarTestsIT
  • -Dawe.test.browser admite headless-chrome o headless-firefox (también chrome y firefox para ver el navegador). -Dawe.test.tool es selenium y puede omitirse (consulta Herramienta de automatización).
  • -Dit.test= admite una clase, varias separadas por comas (CRUDSiteTestsIT,CriteriaTestsIT) o un método (SchedulerCalendarTestsIT#t001_...). En awe-boot las suites también se seleccionan por etiqueta (-Dgroups=SchedulerIT).
  • La aplicación arranca en el puerto 8080. Para usar otro, establécelo en el entorno (SERVER_PORT=8090); el navegador lee la dirección de awe.test.start-url, que sigue a server.port.
  • Añade xvfb-run -a delante de mvn cuando la máquina no tiene pantalla (por ejemplo WSL).
  • Las pruebas de una clase se ejecutan en orden de nombre (t000_... a t999_...). Las clases que son independientes (consulta Clases de prueba independientes) pueden ejecutarse una prueba cada vez; en las otras una prueba continúa donde la anterior lo dejó, así que ejecuta la clase, no un método aislado, salvo que el método no dependa de los anteriores.

Evidencia de fallos​

Cuando una prueba falla, awe-testing recopila su evidencia junta en awe.test.screenshot-path (target/tests/selenium/screenshots/ por defecto; la CI establece browser-evidence/) e imprime un enlace a cada archivo en la salida de la prueba:

ArchivoQué es
<test>.pngCaptura de pantalla en el momento del fallo
<test>.htmlCódigo fuente de la página (DOM): mira aquí los hooks y atributos del elemento que la prueba no encontró
<test>.console.logConsola del navegador (errores JavaScript, avisos y logs). Las entradas severas también se imprimen en la salida de la prueba, ya que un error del cliente a menudo explica una pantalla en blanco
vídeoLa grabación de la prueba. Los vídeos se conservan solo para las pruebas fallidas (awe.test.video-save=FAILED); awe.test.allowed-recording=false desactiva la grabación
<test>.trace.zip, <qualified.TestClass>.webmSolo Playwright: el trace de la prueba fallida y el vídeo de la clase, consulta Evidencia de Playwright

Empieza por la consola y el HTML: un paso fallido normalmente significa que el elemento aún no estaba en el DOM, no llevaba el atributo de estado esperado, o el cliente lanzó un error antes de renderizarlo.

Pruebas inestables: reejecución, cadenas y cuarentena​

Los jobs de navegador bloquean el pipeline, de modo que una prueba que falla de vez en cuando sin un defecto (una prueba inestable, "flaky") bloquearía las merge requests y las publicaciones al azar. Tres mecanismos se ocupan de ello, del más barato al más fuerte: una prueba fallida se reejecuta sola una vez, una clase cuyas pruebas no se pueden reejecutar solas se declara una cadena dependiente, y una prueba que sigue fallando se pone en cuarentena.

Una prueba fallida se reejecuta sola​

Las aplicaciones de test ejecutan las pruebas de integración con rerunFailingTestsCount de failsafe establecido a 1 (la propiedad de Maven it.rerun-count). Cuando falla una prueba de una clase independiente, esa única prueba se ejecuta una vez más, tras todas las pruebas de la ejecución, en una nueva instancia de su clase con un nuevo navegador, y la preparación de sesión de la clase inicia sesión de nuevo. Las pruebas que ya pasaron no se ejecutan de nuevo.

  • Si el segundo intento pasa, la prueba pasa y se notifica como flaky: el log del job la lista bajo Flakes: con el fallo del primer intento, el resumen dice Flakes: 1, y el informe XML de la clase (TEST-*.xml en los artefactos del job) conserva el primer fallo en un elemento <flakyFailure> de la prueba. La evidencia de fallos del primer intento (captura de pantalla, código fuente de la página, consola, vídeo o trace) permanece en browser-evidence/. El informe de pruebas de GitLab cuenta la prueba como pasada, así que lee la sección Flakes: del log del job, y abre una incidencia cuando una prueba aparezca ahí.
  • Si el segundo intento también falla, la prueba falla y también el job. Una regresión real falla dos veces.
  • El propio job solo se reintenta cuando falla el runner (runner_system_failure), no porque una prueba falle: un reintento de job ejecutaría de nuevo todas las pruebas del job y ocultaría las inestables. Un timeout tampoco se reintenta. (Un job que ejecuta una cadena dependiente también se reintentaría ante un fallo de prueba: hoy ningún job lo hace.)
  • Ejecuta sin reejecución, como cuando reproduces una prueba inestable, con -Dit.rerun-count=0.

Una reejecución llega después de toda la ejecución. failsafe reejecuta las pruebas fallidas cuando se han ejecutado todas las pruebas de la ejecución, en una nueva instancia de la clase, de modo que una prueba se reejecuta en el estado que dejó el final de la ejecución, no el que dejó su fallo. Para una prueba que abre su pantalla y usa datos propios eso es lo mismo, y la reejecución funciona. Para un paso de una secuencia de crear, actualizar y eliminar no lo es: los pasos posteriores de la clase (que se ejecutaron tras el fallo) ya han actualizado o eliminado el registro, y una reejecución simple del paso lo encontraría desaparecido, o encontraría un registro que no espera. Un paso que debe funcionar cuando se reejecuta se asegura de lo que necesita antes de usarlo, y no hace nada cuando ya está ahí:

  • un paso que necesita un registro (una actualización, un duplicado, una vista) lo crea primero con un ayudante que no hace nada cuando el registro está ahí, y el ayudante crea de la misma manera los registros de los que depende (un módulo crea primero su sitio);
  • un paso que crea un registro no hace nada cuando ya está ahí y después ejecuta sus propias comprobaciones, de modo que un intento que falló tras guardar no se repite como duplicado;
  • un paso que elimina un registro no hace nada cuando ya no está y después comprueba que no está;
  • un paso que edita una fila la encuentra por lo que muestra antes y después del paso (el perfil de un módulo es TST antes de la actualización y ADM después de ella).

hasRowContents (consulta el catálogo de pasos) es la sonda, y las clases CRUD (AbstractCrudTests) son el ejemplo: cada paso ejecuta todas sus comprobaciones en una reejecución, nunca menos. Las clases Application y Scheduler aún no están escritas de esta manera: sus pasos que actualizan o eliminan un registro fallan cuando se reejecutan tras terminar la ejecución, por lo que la reejecución no les ayuda (nunca las hace pasar por error). Hasta que lo estén, un paso inestable de esas clases se corrige o se pone en cuarentena, no se deja a la reejecución. Una clase cuyos pasos no se pueden hacer funcionar solos es una cadena dependiente.

Cadenas dependientes​

Ninguna clase de prueba de navegador es hoy una cadena dependiente: las pruebas CRUD se dividieron en clases independientes (sitios, perfiles, módulos, conexiones de base de datos y usuarios, cada una con el sitio o el perfil que necesita), y ese es el camino a seguir. La facilidad se mantiene para una clase cuyas pruebas realmente son una secuencia ordenada, cada una usando lo que creó la anterior, y que no se puede dividir. Una clase así se declara con @DependentChain y el motivo, y conserva su @TestMethodOrder:

@DependentChain("The import creates the records that the following tests check and delete")
@TestMethodOrder(MethodOrderer.MethodName.class)
@Tag("ImportIT")
class ImportTestsIT extends SeleniumUtilities {

La compilación ejecuta las cadenas en una segunda ejecución de failsafe del mismo job (integration-test-chains), después de las clases independientes y contra la misma aplicación, sin reejecución: una reejecución de un paso encontraría los datos que dejó el intento fallido (un registro duplicado, un registro que ya estaba eliminado). La clase es la unidad a ejecutar de nuevo, y la forma de hacerlo es el reintento del job completo: .chain-retry en .gitlab-ci.yml (un reintento ante script_failure y runner_system_failure, en un contenedor nuevo con una base de datos limpia), que solo extiende un job que ejecuta una cadena, listado después de la plantilla que trae .browser-testing (GitLab fusiona las plantillas en orden y gana la última que establece una clave). GitLab reintenta un job y no una entrada de su matriz, de modo que las clases que comparten el job de la cadena se reintentan con él. CiBrowserClassesGuardTest comprueba la regla desde los fuentes: un job cuyas clases incluyen una clase @DependentChain extiende .chain-retry, y un job sin ella no lo hace. Una cadena solo se puede poner en cuarentena como una clase completa, porque no puede ejecutarse con un paso ausente. No añadas @DependentChain para ocultar una prueba inestable: haz las pruebas independientes (consulta Clases de prueba independientes).

Poner en cuarentena una prueba inestable​

Una prueba inestable que falla dos veces seguidas, o que sigue apareciendo bajo Flakes:, no se elimina ni se ignora: se pone en cuarentena, lo que la saca de los jobs bloqueantes y sigue ejecutándola donde no puede bloquear nada.

@Test
@Quarantine(issue = "#812", reason = "The suggest answers after the test has read the text")
void t002_loadSuggestOnGrid() {
...
}

@Quarantine (com.almis.awe.testing.annotations) va en un método de prueba, o en una clase para poner en cuarentena todas sus pruebas. Etiqueta la prueba como quarantine para JUnit, y entonces:

  • Los jobs bloqueantes la dejan fuera. Las aplicaciones de test excluyen la etiqueta quarantine de su ejecución de integración (la propiedad de Maven it.excluded-groups de awe-tests/awe-boot y awe-tests/awe-boot-react), de modo que ni los jobs de Playwright ni los de Selenium la ejecutan.
  • La ejecuta un job que nunca bloquea. E2E quarantine · AngularJS · Chromium y E2E quarantine · React · Chromium (Playwright) y Selenium quarantine · AngularJS · Chrome y Selenium quarantine · React · Chrome (Selenium) ejecutan las pruebas en cuarentena de ambas aplicaciones (AngularJS y React), con Chromium. Tienen allow_failure: true, ningún reintento de job ni reejecución por prueba (-Dit.rerun-count=0, de modo que una prueba inestable se muestra como el fallo que fue), y no son necesarios para Analysis · Sonar ni para los jobs de publicación, de modo que una prueba en cuarentena que falla muestra una advertencia en el pipeline y nada más. Mantienen el mismo informe y la misma evidencia de fallos que los demás jobs de navegador, en los artefactos del job. Cuando no hay nada en cuarentena, los jobs terminan de inmediato sin arrancar la aplicación.
  • Ejecútala tú mismo con -Dgroups=quarantine -Dit.excluded-groups= -Dit.rerun-count=0 (vacío y cero) añadido al comando de Ejecutar una suite en local.

Las reglas de una cuarentena:

  1. Pon en cuarentena con evidencia, no para conseguir un pipeline en verde. Una prueba que falló en un pipeline por un defecto real no es inestable: corrige el defecto. Pon una prueba en cuarentena cuando la hayas visto fallar y pasar con el mismo código (el enlace del job va en la incidencia).
  2. Toda cuarentena tiene una incidencia y un motivo. issue es el número (#812) o la URL de la incidencia que hace seguimiento de la inestabilidad, y reason dice por qué la prueba es inestable en la medida en que se sabe. BrowserTestDeclarationsGuardTest, una prueba unitaria de ambas aplicaciones, hace fallar la compilación sin ellos, y también rechaza @Tag("quarantine") escrito a mano, que ocultaría una prueba sin incidencia.
  3. Una cuarentena es temporal. Corrige la causa, elimina la anotación y cierra la incidencia en la misma merge request: el pipeline de esa merge request ejecuta la prueba de nuevo como una prueba bloqueante. El job Quarantine ... IT es la evidencia de que la prueba sigue siendo inestable o de que se ha recuperado, así que míralo antes de eliminar la anotación.
  4. Pon en cuarentena lo más pequeño que falla. Un método antes que una clase. Los productos pueden usar la misma anotación y el mismo guard (BrowserTestDeclarationsGuard.scan(...)) en sus propias pruebas de navegador.

Pruebas guard de la CI​

Tres pruebas unitarias evitan que las reglas anteriores se desvíen. Leen fuentes, clases compiladas o .gitlab-ci.yml, no necesitan navegador y se ejecutan con las pruebas unitarias (Unit · Java):

PruebaMóduloFalla ante
BrowserTestDeclarationsGuardTestawe-boot, awe-boot-reactUn @Quarantine sin incidencia o sin motivo, un @Tag("quarantine") escrito a mano, un @DependentChain mal declarado
BrowserTestSourcesGuardTestawe-boot, awe-boot-reactSelectores o tipos de la herramienta de automatización en las fuentes de las pruebas de navegador (consulta Mantener las pruebas de navegador libres de selectores)
CiBrowserClassesGuardTestawe-boot-reactQue .gitlab-ci.yml se desvíe de las fuentes: una clase listada en TEST_CLASSES que no existe, un job de cuarentena que podría bloquear el pipeline, un job reintentado ante un fallo de prueba, un job de cadena sin .chain-retry, un job de navegador que no condiciona Analysis · Sonar

Ejecuta una desde la raíz del repositorio, por ejemplo mvn test -pl awe-tests/awe-boot-react -Dtest=CiBrowserClassesGuardTest -Dskip.frontend=true.

Reproducir un fallo del navegador de la CI​

Una prueba que pasa con headless-chrome puede fallar en CI porque la CI ejecuta el navegador desde una imagen de contenedor Selenoid, en una máquina más lenta y con una compilación distinta de Firefox o Chrome. Para reproducirlo, ejecuta la misma imagen en local:

  1. Toma la imagen fijada del navegador de .gitlab-ci.yml (.chrome-testing y .firefox-testing, los servicios selenoid/chrome y selenoid/firefox, con su digest @sha256:) e inícialo:

    docker run -d --rm --platform linux/amd64 -p 4444:4444 -e SCREEN_RESOLUTION=1440x1080x24 \
    selenoid/chrome:latest@sha256:<digest from .gitlab-ci.yml>
  2. Arranca la aplicación de modo que el contenedor pueda alcanzarla, enlazada a todas las interfaces (SERVER_ADDRESS=0.0.0.0).

  3. Ejecuta la clase contra el navegador del contenedor:

    SERVER_ADDRESS=0.0.0.0 mvn -f awe-tests/awe-boot-react/pom.xml verify -Dskip.junit=true -Dskip.selenium=false \
    -Dawe.test.browser=service-chrome -Dawe.test.browser-host=localhost -Dawe.test.browser-port=4444 \
    -Dawe.test.server-host=host.docker.internal -Dawe.test.server-port=8080 \
    -Dawe.test.allowed-recording=false -Dit.test=SchedulerCalendarTestsIT

    Usa service-firefox con la imagen selenoid/firefox para Firefox. -Dawe.test.allowed-recording=false es necesario porque el grabador de vídeo es otro servicio del job de CI que no estás ejecutando.

El navegador abre server-host:server-port, no start-url

Un navegador remoto (service-* y remote-*) no usa awe.test.start-url: la prueba construye la URL de la aplicación a partir de awe.test.server-host y awe.test.server-port (por defecto 8080) más awe.test.context-path. Si la aplicación escucha en otro puerto, o el host no es alcanzable desde el contenedor, el navegador abre la dirección equivocada y todas las pruebas fallan en el primer paso con una pantalla en blanco. Cuando no se establece server-host, usa la IP de la máquina en Linux y host.docker.internal en el resto.

Haz reproducibles los fallos de tiempo

Muchos fallos exclusivos de la CI son fallos de tiempo: el contenedor de la CI tiene poca CPU. Limita la CPU del contenedor del navegador (docker update --cpus=1 <container>) antes de la ejecución para ralentizar el navegador como lo hace la CI, y súbela de nuevo (--cpus=4) para comprobar que una corrección no depende de una máquina rápida.

Cuando el fallo solo aparece en la CI, descarga los artefactos del job (browser-evidence/ tiene la captura de pantalla, el código fuente de la página, la consola del navegador y el vídeo de cada prueba fallida) antes de cambiar código: la evidencia suele indicar si el paso necesita una mejor espera (un estado que esperar) o si el cliente tiene un defecto.

Criterios​

Los siguientes puntos describen cómo rellenar los distintos tipos de criterios disponibles en las pantallas de AWE:

Input y Textarea​

Simplemente llama al método writeText con los siguientes parámetros:

  • criterionId - Identificador del criterio
  • text - Texto a escribir
// Insert text
writeText("criterionId", "textToWrite");

Fecha​

Elegir una fecha concreta en el selector de fechas​

Llama al método selectDate con los siguientes parámetros:

  • criterionId - Identificador del criterio de fecha
  • date - Fecha a seleccionar
// Select a date
selectDate("Cal", "23/10/1978");

Elegir un día del mes actual​

Llama al método selectDay con los siguientes parámetros:

  • criterionId - Identificador del criterio de fecha
  • day - Día a seleccionar
// Select a day in current month
selectDay("Cal", 23);

Elegir un mes en el selector de meses​

Llama al método selectMonth con los siguientes parámetros:

  • criterionId - Identificador del criterio de fecha
  • month - Mes a seleccionar
// Select a month in the month selector
selectMonth("Cal", 23);

Elegir un año en el selector de años​

Llama al método selectYear con los siguientes parámetros:

  • criterionId - Identificador del criterio de fecha
  • year - Año a seleccionar
// Select a year in the year selector
selectYear("Cal", 2019);

Hora​

Igual que input y textarea:

// Write hour
writeText("Tim", "12:23:41");

Select​

Para elegir un resultado en un criterio select, llama al método selectContain:

  • criterionId - Identificador del criterio
  • text - Texto a buscar en la lista de resultados
// Select on selector
selectContain("Sta", "Yes");

Suggest​

Para usar un criterio suggest, llama al método suggest:

  • criterionId - Identificador del criterio
  • text to suggest - Texto a buscar
  • result label - Texto a buscar en la lista de resultados
// Suggest on selector
suggest("Pro", "TS1", "TS1");

Select y suggest múltiples​

Seleccionar un valor​

Selecciona un único valor con el criterio suggestMultiple:

  • criterionId - Identificador del criterio
  • text to suggest - Texto a buscar
  • result label - Texto a buscar en la lista de resultados
// Suggest
suggestMultiple("CrtOpc", "application-info", "application-info");

Más de un valor​

Selecciona más de un valor con el criterio suggestMultipleList:

  • criterionId - Identificador del criterio
  • text 1 to suggest - Texto 1 a buscar
  • text 2 to suggest - Texto 2 a buscar
  • ... - Más textos a buscar
// Suggest
suggestMultipleList("CrtOpc", "application-info", "application-warning", "application-error");

Pestañas​

Comprobar la pestaña activa​

Para comprobar si una pestaña está activa, llama al método checkText:

  • cssSelector - Selector para encontrar el nodo de texto a comprobar
  • text - Texto a comprobar
// Check if tab is active
checkText("[criterion-id='" + tabId + "'] li.active a", "Tab text");

Hacer clic en una pestaña​

Para hacer clic en una pestaña puedes llamar al método clickTab:

  • tabId - Identificador de la pestaña
  • tabOption - Etiqueta de la opción de pestaña: la clave de locale en el cliente AngularJS, el texto traducido en el cliente React (consulta Clientes AngularJS y React)
// Click on tab
clickTab("TabSelMat", "ENUM_MATRIX_MULTISELECT");

Checkbox y botón de radio​

Haz clic en un checkbox o en un botón de radio de la misma manera con el método clickCheckbox:

  • checkboxRadioId - Identificador del criterio
// Click checkbox or radio button
clickCheckbox("ChkBoxVa1");

Vista de texto​

Para comprobar si un componente de vista de texto contiene un texto depende de la estructura de la vista de texto:

  • cssSelector - Selector para encontrar el nodo de texto a comprobar
  • text - Texto a comprobar
// Check the contents of a tag list (text view)
checkTagListContains(textViewId, textToCheck);

Verificar los valores de los criterios​

Criterios de texto​

Para verificar el contenido de los criterios de texto, usa el método checkCriterionContents:

  • criterionId - Identificador del criterio
  • text - Texto con el que coincidir
// Check criterion
checkCriterionContents("Nam", "Inf Changed");

Criterios select y suggest​

Para verificar el contenido de los criterios select y suggest, usa el método checkSelectContents para un select y el método checkSuggestContents para un suggest (un suggest no se dibuja como un select, y los dos clientes muestran su valor de forma distinta):

  • criterionId - Identificador del criterio
  • text - Texto con el que coincidir
// Check a select
checkSelectContents("Scr", "Usr");

// Check a suggest
checkSuggestContents("Sug", "Test");

Celdas de grid​

Columna de input y textarea​

Simplemente llama al método writeText con los siguientes parámetros:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • text - Texto a escribir
// Write on text
writeText("GrdMuo", "Des2", "asdasda");

Columna de fecha​

Elegir una fecha concreta en el selector de fechas en una columna​

Para elegir una fecha en una fila de grid, llama al método selectDate:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • date - Fecha a elegir
// Click on date
selectDate("GrdEdi", "Dat", "23/10/1978");

Elegir un día del mes actual en una columna​

Llama al método selectDay con los siguientes parámetros:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • day - Día a seleccionar
// Select a day in current month
selectDay("GrdEdi", "Dat", 23);

Columna de hora​

Igual que columna de input y textarea:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • time - Hora a seleccionar
// Write hour
writeText("gridId", "timeColumn", "12:23:41");

Columna select​

Para elegir un resultado en un criterio select, llama al método selectContain:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • text - Texto a buscar en la lista de resultados
// Select text
selectContain("GrdScrCnf", "Act", "Yes");

Columna suggest​

Para usar un criterio suggest, llama al método suggest:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • text to suggest - Texto a buscar
  • result label - Texto a buscar en la lista de resultados
// Search for text
suggest("GrdScrCnf", "Atr", "visible", "Visible");

Select y suggest múltiples en una columna​

Estos dos criterios se pueden probar de la misma manera con el criterio suggestMultiple:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
  • text to suggest - Texto a buscar
  • result label - Texto a buscar en la lista de resultados
// Suggest
suggestMultiple("gridId", "columnId", "application-info", "application-info");

Columna de checkbox​

Haz clic en un checkbox o en un botón de radio de la misma manera con el método clickCheckbox:

  • gridId - Identificador del grid
  • columnId - Identificador de la columna
// Click checkbox on a grid
clickCheckbox("gridId", "columnId");

Botón de guardar​

Para hacer clic en un botón de guardar de un grid, llama al método saveRow.

// Save line
saveRow();

Si hay varios grids en la pantalla, necesitas añadir el identificador del grid al método:

// Save line
saveRow("myGridIdentifier");

Comprobar el valor de una fila​

Para comprobar si hay textos concretos dentro de un grid, llama al método checkRowContents:

// Check row contents
checkRowContents("test", "ADM", "Site changed");

Puedes añadir tantos textos como quieras comprobar.

Si quieres comprobar el contenido de una celda concreta, puedes llamar al método checkCellContents:

  • gridId - Identificador del grid
  • rowId - Identificador de la fila
  • columnId - Identificador de la columna
  • text - Texto con el que coincidir
// Check date on second row
checkCellContents("GrdEdi", "2", "Dat", date);

Hacer clic en una fila​

Para hacer clic en una fila con un texto definido, llama al método clickRowContents:

  • gridId - Identificador del grid
  • text - Texto con el que coincidir
// Click on grid
clickRowContents("GrdEdi", "asphalt");

O si quieres hacer clic en una celda concreta, puedes llamar al método clickCell:

  • gridId - Identificador del grid
  • rowId - Identificador de la fila
  • columnId - Identificador de la columna
// Click on a cell
clickCell("GrdMuo", "1", "Des2");

Expandir o contraer una fila​

Para expandir o contraer una fila de un grid en árbol, hemos definido el método clickTreeButton:

  • gridId - Identificador del grid
  • rowId - Identificador de la fila
// Click on button
clickTreeButton("TreGrdLoaEdi", "Prooperator");

Menú contextual en una fila​

Para abrir un menú contextual en una fila de grid, simplemente llama al método contextMenu:

  • gridId - Identificador del grid
  • rowId - Identificador de la fila
  • columnId - Identificador de la columna
// Context menu
contextMenu("TreGrdLoaEdi", "Progeneral-ModBase", "TreGrdLoaEdi_Nam");

Opción de menú contextual​

Puedes hacer clic en una opción de menú contextual con la opción clickContextButton.

  • menuOptions - Opciones en las que hacer clic (ordenadas) en el menú contextual
// Select context menu option
clickContextButton("CtxTreGrdLoaEdiAddSel", "CtxTreGrdLoaEdiAddChl");

Ejemplos​

Añadir un nuevo sitio​

/**
* Add a new site
* @throws Exception
*/
@Test
public void t001_newSite() throws Exception {
// Title
setTestTitle("Add a new site");

// Go to screen
gotoScreen("tools", "sites");

// Click on new button
clickButton("ButNew", true);

// Wait for button
waitForButton("ButCnf");

// Write on criterion
writeText("Nam", "Site test");

// Select last element
selectLast("Act");

// Write on criterion
writeText("Ord", "3");

// Click on button
clickButton("ButGrdAdd");

// Suggest on column selector
suggest("SitModDbsLst", "IdeMod", "Base", "Base");

// Suggest on column selector
suggest("SitModDbsLst", "IdeDbs", "awedb", "awedb");

// Write on criterion
writeText("SitModDbsLst", "Order", "3");

// Save line
saveRow();

// Check row values
checkRowContents("Base", "awedb", "3");

// Store and confirm
clickButtonAndConfirm("ButCnf");

// Wait for button
clickButton("ButRst");

// Suggest on column selector
suggest("CrtSit", "Site test", "Site test");

// Search on grid
searchAndWait();

// Click row
clickRowContents("Site test");

// Click on button
clickButton("ButViw", true);

// Wait for button
waitForButton("ButBck");

// Check row contents
checkRowContents("Base", "awedb");
}

Eliminar un módulo​

/**
* Delete a module
* @throws Exception
*/
@Test
public void t056_deleteModule() throws Exception {
// Title
setTestTitle("Delete a module");

// Go to screen
gotoScreen("tools", "modules");

// Wait for button
clickButton("ButRst");

// Suggest on column selector
suggest("CrtMod", "Inf", "Inf");

// Search on grid
searchAndWait();

// Click row
clickRowContents("Inf");

// Store and confirm
clickButtonAndConfirm("ButDel");

// Wait for button
clickButton("ButRst");

// Search on grid
searchAndWait();

// Click row
checkRowNotContains("Inf");
}

Actualizar una base de datos​

/**
* Update a database connection
* @throws Exception
*/
@Test
public void t033_updateDatabase() throws Exception {
// Title
setTestTitle("Update a database connection");

// Go to screen
gotoScreen("tools", "databases");

// Wait for button
clickButton("ButRst");

// Suggest on column selector
suggest("CrtAls", "DBSTest", "DBSTest");

// Search on grid
searchAndWait();

// Click row
clickRowContents("DBSTest");

// Click on button
clickButton("ButUpd", true);

// Wait for button
waitForButton("ButCnf");

// Insert text
writeText("Als", "DBSTest Changed");

// Select on selector
selectContain("Dct", "Jdbc");

// Insert text
writeText("Dbc", "Test");

// Insert text
writeText("Des", "This is a database connection update test case");

// Click on row
clickRowContents("Site changed");

// Suggest on column selector
suggest("SitModDbsLst", "IdeMod", "Test", "Test");

// Save line
saveRow();

// Check row
checkRowContents("Test");

// Store and confirm
clickButtonAndConfirm("ButCnf");

// Wait for button
clickButton("ButRst");

// Suggest on column selector
suggest("CrtAls", "DBSTest Changed", "DBSTest Changed");

// Search on grid
searchAndWait();

// Click row
clickRowContents("DBSTest Changed");

// Click on button
clickButton("ButViw", true);

// Wait for button
waitForButton("ButBck");

// Check contents
checkCriterionContents("Als", "DBSTest Changed");

// Check row contents
checkRowContents("Test");
}