Test de extremo a extremo
Introducción
Un test de extremo a extremo es exactamente lo contrario de una prueba unitaria. Esta vez, el objetivo es comprobar toda la aplicación, de principio a fin, y en todas las etapas posibles. Todos los casos de uso se probarán.
Por lo tanto, el desarrollador buscará probar todos los componentes, todos los servicios, todas las dependencias e incluso todos los posibles clics del ratón. Si el usuario puede hacerlo, la prueba de extremo a extremo debe haberlo probado antes.
Ejecutar test de un extremo a otro de un proyecto Angular es muy fácil. Basta con lanzar el siguiente comando en la raíz del proyecto:
ng e2e
Ejecución de una prueba de un extremo a otro sin ningún test
Protractor
Protractor es un framework desarrollado para Angular basado en Selenium. Está escrito en Node.js y es una superposición de WebDriverJS, la implementación oficial de Selenium en el mundo JavaScript. Su configuración se reduce a un solo archivo que Angular CLI creó en la carpeta e2e: protractor.conf.js.
En este archivo están los navegadores que se lanzarán y una plantilla para encontrar los archivos de test de un extremo a otro en el proyecto Angular.
// C:\JavaScriptYAngular\Angular\miProyectoAngular\e2e\
protractor.conf.js
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () { } ...
Probar las acciones del usuario
Probar el contenido de una etiqueta HTML estática sigue siendo bastante sencillo, pero la mayoría de las aplicaciones web funcionan con botones, campos de formulario y datos que se muestran en constante cambio. Los test de un extremo a otro no son completos si no se prueban el rellenado de los campos y los clics en los botones.
La plantilla del componente c18 sufre una modificación para integrar un botón y la visualización de un mensaje tras un clic.
<!-- C:\JavaScriptYAngular\Angular\miProyectoAngular\src\app\
components\c18.component.html -->
<div>
<button name="guerra" class="de" id="botones"
(click)="clique()"> Un boton</button>
<div id="mensaje">{{mensaje}}</div>
</div>
La clase TypeScript se modifica para agregar algo de interacción.
// C:\JavaScriptYAngular\Angular\miProyectoAngular\src\app\
components\c18.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-c18',
templateUrl: './c18.component.html',
styleUrls: ['./c18.component.css']
})
export class C18Component implements OnInit { ...
¿Y aparte de hacer clic?
Todas las acciones disponibles se enumeran en el sitio web de Protractor: http://www.protractortest.org/#/api
Los elementos más utilizados se pueden agrupar en dos familias: los que conciernen al navegador y los que se refieren a acciones sobre los elementos recuperados.
1. A nivel del navegador
-
browser.get() Se usa para navegar a la dirección dada en el parámetro. Este método carga todos los elementos necesarios para renderizar la página Angular. El método brower.getCurrentUrl() devuelve la URL completa actual.
-
browser.refresh() recarga la página web actual.
-
browser.actions() le permite definir una secuencia de acciones que solo se ejecutarán cuando se llame al método perform().
Para encadenar varias acciones, simplemente sepárelas por un punto: browser .actions(.doubleClick().clickAndHold().release() .click().perform().
Todas las acciones disponibles se enumeran en el sitio web de Selenium: https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/interactions/Actions.html
2. A nivel de un elemento
El desarrollador puede querer recuperar uno o más elementos. element.all() permite recuperar un array de elementos, mientras que element() recupera solo uno.
Cada elemento es del tipo ElementFinder y, como tal, tiene una serie de métodos particularmente útiles: getTagName, getCssValue, getAttribute, getTest, getSize, getLocation …...
Caso concreto: una lista y botones
El componente C18 se modifica una vez más. Su plantilla muestra una lista HTML y un campo de entrada seguido de un botón para agregar un elemento a la lista.
<!-- C:\JavaScriptYAngular\Angular\miProyectoAngular\src\app\
components\c18.component.html -->
<div>
<ul *ngFor="let j of jedi" id="lista">
<li>{{j}}</li>
</ul>
<input type="text" name="nombre" id="nombre" #nombre >
<button (click)="agregarJedi(nombre.value)" id="boton">
Añadir un Jedi</button>
</div>
La parte TypeScript del componente inicializa la lista de Jedi con tres elementos e implementa un método que permite, desde un parámetro, agregar un elemento a la lista. Por supuesto, un elemento vacío o un elemento ya presente en la lista no debe poder agregarse.
// C:\JavaScriptYAngular\Angular\miProyectoAngular\src\app\
components\c18.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-c18',
templateUrl: './c18.component.html',
styleUrls: ['./c18.component.css'] ...
Práctica
1. Enunciado
Desde la aplicación PokemonManager desarrollada en los capítulos anteriores, realice las siguientes pruebas de extremo a extremo:
-
En la página principal:
-
Navegación a la página principal.
-
El enlace a /generaciones.
-
El enlace a /pokemons.
-
En la página Generaciones
-
La navegación dentro de la página.
-
El número de generaciones de Pokémon tiene que ser mayor que cero.
-
En la página Pokémons:
-
El número de Pokémon tiene que ser mayor que cero.
-
El huevo de pascua debe de estar oculto al principio.
-
El huevo de pascua aparecerá si el Pokémon buscado es C3PO.
Obviamente, todos los test de extremo a extremo que considere necesarios deben agregarse a esta lista.
2. Corrección
a. Página principal
La página principal app.po.ts tiene solo tres métodos, dos de los cuales son muy similares, que se basan en etiquetas <a>.
// C:\JavaScriptYAngular\Angular\PokemonManager\e2e\src\
app.po.ts
import { browser, by, element } from 'protractor';
export class AppPage {
// Navegación a la página principal
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
// Enlace hacia /generaciones
enlaceGeneraciones() {
return element(by.id('enlaceGeneraciones'));
}
// Enlace hacia /pokemons
enlacePokemons() {
return element(by.id('enlacePokemons'));
}
}
Las pruebas implantadas son relativamente sencillas. Simulan un clic en un botón y luego en el otro, y esperan provocar la navegación a otra página.
// C:\JavaScriptYAngular\Angular\PokemonManager\e2e\src\
app.e2e-spec.ts
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('PokemonManager...