
Un pantallazo sobre qué vamos a cubrir en este curso y el por qué de cada herramienta.
Esta clase introduce a los estudiantes al mundo de Selenium WebDriver, una herramienta esencial en el campo de la automatización de pruebas para aplicaciones web. Vamos a ver por qué es diferente de Selenium IDE y qué otras cosas necesita para funcionar en lo que vamos a llamar Framework.
En esta clase, vamos a dar una primera mirada a Cucumber, una herramienta clave para el desarrollo de pruebas de software basadas en el comportamiento (BDD).
Abordaremos cómo Cucumber permite a los equipos de desarrollo traducir requisitos de negocio en un lenguaje claro y legible, que luego se transforma en pruebas automatizadas.
Vamos a poner la base sobre la que los estudiantes comprenderán cómo Cucumber facilita la colaboración entre desarrolladores, QA y otros stakeholders, mejorando la calidad y eficiencia del proceso de desarrollo de software.
En próximas clases vamos a ahondar en todos estos conceptos y utilizarlos para crear un robusto framework de automatización de pruebas
Esta clase está dedicada a mirar por primera vez a Gradle, una potente herramienta de automatización de construcción (build) utilizada ampliamente en proyectos de desarrollo de software.
La instalación del Java Development Kit (JDK) varía ligeramente dependiendo del sistema operativo que estés utilizando. A continuación, te proporciono una guía general para los sistemas operativos más comunes:
Windows
Descargar JDK:
Ve al sitio web oficial de Oracle (oracle.com) y descarga el instalador del JDK adecuado para tu versión de Windows (32 o 64 bits).
Ejecutar el Instalador:
Abre el archivo descargado y sigue las instrucciones del instalador. Durante la instalación, puedes cambiar la ruta de instalación o dejarla por defecto.
Configurar el PATH (Opcional):
Para poder ejecutar Java desde cualquier lugar en la línea de comandos, debes agregar la ruta del JDK al PATH de tu sistema.
Busca "Variables de entorno" en la configuración del sistema y agrega la ruta del directorio bin del JDK (por ejemplo, C:\Program Files\Java\jdk-11\bin) a la variable PATH.
macOS
Descargar JDK:
Visita el sitio web de Oracle y descarga el instalador para macOS.
Instalar el JDK:
Abre el archivo .dmg descargado y sigue las instrucciones del instalador.
Verificar la Instalación:
Abre el Terminal y escribe java -version para asegurarte de que la instalación se haya completado correctamente.
Linux
Descargar JDK:
Descarga el JDK desde el sitio web de Oracle o usa un gestor de paquetes. Por ejemplo, en sistemas basados en Debian (como Ubuntu), puedes usar el comando sudo apt install default-jdk.
Instalar JDK:
Si descargaste un archivo tar.gz, extráelo en una carpeta deseada (por ejemplo, /usr/lib/jvm) y configura las variables de entorno.
Configurar Variables de Entorno:
Establece las variables JAVA_HOME y PATH. Puedes hacerlo editando los archivos de perfil del usuario, como .bashrc o .bash_profile, agregando líneas como:
export JAVA_HOME=/ruta/al/jdk
export PATH=$JAVA_HOME/bin:$PATH
Actualizar Alternativas (Linux):
En algunos sistemas Linux, puedes necesitar actualizar las alternativas de Java usando update-alternatives.
Verificar la Instalación:
Ejecuta java -version en el terminal.
Notas Adicionales
Las versiones específicas del JDK (como JDK 8, 11, etc.) pueden tener instrucciones de instalación ligeramente diferentes.
Si preferís una versión gratuita y de código abierto de JDK, podés optar por OpenJDK, que también tiene distribuciones para diferentes sistemas operativos (yo por ejemplo uso OpenJDK 17 en este curso).
Recordá revisar regularmente las actualizaciones del JDK para mantener tu sistema seguro y al día.
Vamos a crear un proyecto con Gradle y Java!
Abrir la Terminal en VSCode:
Podés abrir la terminal en VSCode con Ctrl + `` o desde el menú Ver -> Terminal`.
Crear un Nuevo Directorio para el Proyecto:
Ejecutá mkdir nombre_del_proyecto para crear un nuevo directorio.
Navegá al directorio con cd nombre_del_proyecto.
Construir y Ejecutar el Proyecto:
En la terminal de VSCode, ejecuta gradle build para construir el proyecto.
Ejecuta tu aplicación con gradle run o utilizando las tareas de Gradle en VSCode.
Consideraciones Adicionales
Si es la primera vez que usas Gradle, el proceso de inicialización puede tardar un poco mientras descarga los componentes necesarios.
Es importante familiarizarse con el archivo build.gradle, ya que es allí donde se define la configuración de compilación, las dependencias del proyecto y otras configuraciones importantes. No se preocupen que vamos a verlo a fondo en este curso ?
La extensión "Java Extension Pack" en VSCode proporciona soporte adicional para proyectos Java, incluyendo características como depuración y autocompletado de código
Vamos a necesitar interactuar con una página web, hacer validaciones y poder escribir nuestros escenarios en formato Gherkin. ¿Están listos para instalar todo lo necesario?
Configuración del build.gradle
Dependencias:
Selenium WebDriver:
testImplementation 'org.seleniumhq.selenium:selenium-java:última_versión'
Cucumber:
testImplementation 'io.cucumber:cucumber-java:última_versión' testImplementation 'io.cucumber:cucumber-junit:última_versión'
TestNG:
testImplementation 'org.testng:testng:última_versión'
En esta clase vamos a estar armando la estructura del proyecto y tomando una importante decisión de diseño sobre su arquitectura.
Al final, deberían quedar con una estructura como la siguiente:
SELENIUMFREERANGE
├── .gradle
├── .vscode
├── build
├── gradle
├── src
│ └── test
│ └── java
│ ├── pages
│ ├── runner
│ └── steps
└── resources
└── features
├── .gitattributes
├── .gitignore
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle
Una extensión útil para navegar rápido de Feature file a Step Definition y que nos marque si algún paso de nuestros scenarios no está implementado aún.
La extensión en VSCode es: alexkrechik.cucumberautocomplete
Archivos que hemos modificado: .vscode/settings.json
settings.json
{
"cucumberautocomplete.steps": ["src/test/java/steps/*.java"],
"cucumberautocomplete.syncfeatures": "test/features/*.feature",
"cucumberautocomplete.strictGherkinCompletion": true,
"cucumberautocomplete.strictGherkinValidation": true,
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "automatic"
}
Programación Orientada a Objectos...automatización de páginas web y páginas siendo objetos...todo se une en este concepto que va a hacer que el mantenimiento de tu framework sea una brisa.
Veamos de qué va el Page Object Model, cómo es su teoría y por qué deberías usarlo.
Veamos en esta clase a la otra gran opción al Page Object Model: Screenplay. ¿Qué es? ¿Es popular? ¿Qué herramientas se usan? ¿Cómo se ve?
¡Todo eso y más en esta clase!
Llegó el momento de crear nuestro primer Feature File y, para poder correrlo (sin éxito) por primera vez para validar que tenemos todo instalado y configurado correctamente, vamos a tener que completar una serie de pasos.
Acá les dejo las clases que creamos y modificamos y su código como referencia.
settings.gradle: Le sacamos que incluya "app" y dejamos solo
rootProject.name = 'SeleniumFreeRange'
TestRunner.java
package runner;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources",
glue = "src/test/java/steps",
plugin = { "pretty", "html:target/cucumber-reports" })
public class TestRunner {}
FreeRangeNavigation.feature
Feature: Navigation bar
To see the subpages
Without logging in
I can click the navigation bar links
Scenario: I can access the subpages through the navigation bar
Given I navigate to www.freerangetesters.com
When I try to access the free sections through the navigation bar
Then I am redirected to the right page
Al final de build.gradle, cambiamos la tarea "test" de la siguiente manera.
tasks.named('test') {
systemProperty "cucumber.options", System.getProperty("cucumber.options")}
En esta clase estamos haciendo una de las partes fundamentales de nuestro framework y, también, de las más complejas. Atentos a los comentarios que agregué al código y a la clase!
Archivos en los que trabajamos:
A FreeRangeNavegacion.feature, de momento, le vamos a dejar solo un paso, el Given.
Feature: Navigation bar
To see the subpages
Without logging in
I can click the navigation bar links
Scenario: I can access the subpages through the navigation bar
Given I navigate to www.freerangetesters.com
Ahora, en la BasePage.java es donde va a ocurrir la magia:
package pages;
// Importaciones necesarias
import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
//Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
//Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
}
Llegó el momento de crear las próximas partes que van a dar vida a nuestro Framework BDD con Selenium y Cucumber.
Acá las clases y el código que hemos hecho:
PaginaPrincipal.java dentro de la carpeta Pages.
package pages;
public class PaginaPrincipal extends BasePage {
public PaginaPrincipal() {
super(driver);
}
// Método para navegar a www.freerangetesters.com
public void navigateToFreeRangeTesters() {
navigateTo("https://www.freerangetesters.com");
}
}
FreeRangeSteps.java dentro de la carpeta steps.
package steps;
import io.cucumber.java.en.Given;
import pages.PaginaPrincipal;
public class FreeRangeSteps {
PaginaPrincipal landingPage = new PaginaPrincipal();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
}
El TestRunner.java en la carpeta runner tuvo un pequeño cambio en el path a los steps, ahora queda así:
package runner;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources", // Directorio de nuestros archivos .feature
glue = "steps", // Paquete donde tenemos nuestras clases definiendo los steps
plugin = { "pretty", "html:target/cucumber-reports" })
public class TestRunner {
}
3 líneas de código super importantes. Les voy a enseñar mi implementación para manejar elementos web con Selenium y Java!
Archivos sobre los que trabajamos: pages/BasePage.java y pages/PaginaPrincipal.java
BasePage.java
package pages;
// Importaciones necesarias
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
//Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
//Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
// Encuentra y devuelve un WebElement en la página utilizando un locator XPath, esperando a que esté presentente en el DOM
private WebElement Find(String locator){
return wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(locator)));
}
public void clickElement(String locator){
Find(locator).click();
}
}
El ejemplo del final lo hice en pages/PaginaPrincipal.java
package pages;
public class PaginaPrincipal extends BasePage {
private String searchButton = "//*[@id=\"page_section_48252437\"]/div/section/div[2]";
public PaginaPrincipal() {
super(driver);
}
// Método para navegar a www.freerangetesters.com
public void navigateToFreeRangeTesters() {
navigateTo("https://www.freerangetesters.com");
clickElement(searchButton);
}
}
Nuestro framework está casi listo para empezar a entregar valor al equipo. Pero...al terminar de ejecutar el browser queda abierto y si vuelvo a correr abre otra ventana. Sumamente molesto, no? Por eso, en esta clase, vamos a hacer tareas de limpieza y diseñar una forma de cerrar la instancia del WebDriver una vez termina la ejecución.
Archivos que modificamos: BasePage.java y TestRunner.java
BasePage
package pages;
// Importaciones necesarias
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus
* subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando
* el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el
* driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
// Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como
* argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
// Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
//Método estático para cerrar la instancia del driver.
public static void closeBrowser() {
driver.quit();
}
// Encuentra y devuelve un WebElement en la página utilizando un locator XPath,
// esperando a que esté presentente en el DOM
private WebElement Find(String locator) {
return wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(locator)));
}
public void clickElement(String locator) {
Find(locator).click();
}
}
TestRunner:
package runner;
import org.junit.AfterClass;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import pages.BasePage;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources", // Directorio de nuestros archivos .feature
glue = "steps", // Paquete donde tenemos nuestras clases definiendo los steps
plugin = { "pretty", "html:target/cucumber-reports" })
public class TestRunner {
@AfterClass
public static void cleanDriver() {
BasePage.closeBrowser();
}
}
Calentemos motores con una función sencilla: Necesitamos llenar campos de texto.
Archivos que usamos: BasePage.java
package pages;
// Importaciones necesarias
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus
* subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando
* el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el
* driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
// Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como
* argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
// Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
//Método estático para cerrar la instancia del driver.
public static void closeBrowser() {
driver.quit();
}
// Encuentra y devuelve un WebElement en la página utilizando un locator XPath,
// esperando a que esté presentente en el DOM
private WebElement Find(String locator) {
return wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(locator)));
}
public void clickElement(String locator) {
Find(locator).click();
}
public void write(String locator, String keysToSend){
Find(locator).clear();
Find(locator).sendKeys(keysToSend);
}
}
Momento de encargarnos de un par de funciones que nos van a ayudar con una de las primeras cosas con las que se bloquean los que inician automation con Selenium: Elegir elementos de un dropdown.
Archivos que usamos: BasePage.java
package pages;
// Importaciones necesarias
import java.time.Duration;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus
* subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando
* el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el
* driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
// Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como
* argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
// Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
//Método estático para cerrar la instancia del driver.
public static void closeBrowser() {
driver.quit();
}
// Encuentra y devuelve un WebElement en la página utilizando un locator XPath,
// esperando a que esté presentente en el DOM
private WebElement Find(String locator) {
return wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(locator)));
}
public void clickElement(String locator) {
Find(locator).click();
}
public void write(String locator, String keysToSend){
Find(locator).clear();
Find(locator).sendKeys(keysToSend);
}
public void selectFromDropdownByValue(String locator, String value){
Select dropdown = new Select(Find(locator));
dropdown.selectByValue(value);
}
public void selectFromDropdownByIndex(String locator, Integer index){
Select dropdown = new Select(Find(locator));
dropdown.selectByIndex(index);
}
public int dropdownSize(String locator){
Select dropdown = new Select(Find(locator));
List<WebElement> dropdownOptions = dropdown.getOptions();
return dropdownOptions.size();
}
}
Llegó el momento de subir, una vez más, la dificultad. En este caso vamos a estar haciendo una navegación dinámica, pasando por primera vez un parámetro a un step en Cucumber, usando marcadores de posición en un locator e implementando una tabla de ejemplos en el Feature file ?
Archivos en los que trabajamos en esta clase: FreeRangeNavegación.feature, PaginaPrincipal.java, FreeRangeSteps.java
FreeRangeNavegación.feature
Feature: Navigation bar
To see the subpages
Without logging in
I can click the navigation bar links
Scenario Outline: I can access the subpages through the navigation bar
Given I navigate to www.freerangetesters.com
When I go to <section> using the navigation bar
Examples:
| section |
| Cursos |
| Recursos |
| Udemy |
| Mentorías |
| Newsletter |
PaginaPrincipal.java
package pages;
public class PaginaPrincipal extends BasePage {
private String sectionLink = "//a[normalize-space()='%s' and @href]";
public PaginaPrincipal() {
super(driver);
}
// Método para navegar a www.freerangetesters.com
public void navigateToFreeRangeTesters() {
navigateTo("https://www.freerangetesters.com");
}
public void clickOnSectionNavigationBar(String section) {
// Reemplaza el marcador de posición en sectionLink con el nombre
String xpathSection = String.format(sectionLink, section);
clickElement(xpathSection);
}
}
FreeRangeSteps.java
package steps;
import io.cucumber.java.en.*;
import pages.PaginaPrincipal;
public class FreeRangeSteps {
PaginaPrincipal landingPage = new PaginaPrincipal();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
}
Apliquemos lo aprendido para crear un nuevo scenario, clases para páginas y reforzar conceptos.
Archivos en los que trabajamos: resources/FreeRangeNavegacion.feature, pages/PaginaCursos.java, pages/PaginaFundamentosTesting.java, steps/FreeRangeSteps.java
FreeRangeNavegacion
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
# Scenario Outline: I can access the subpages through the navigation bar
# Given I navigate to www.freerangetesters.com
# When I go to <section> using the navigation bar
# Examples:
# | section |
# | Cursos |
# | Recursos |
# | Udemy |
# | Mentorías |
# | Newsletter |
Scenario: Courses are presented correctly to potential customers
Given I navigate to www.freerangetesters.com
When I go to Cursos using the navigation bar
And select Introducción al Testing
FreeRangeSteps
package steps;
import io.cucumber.java.en.*;
import pages.PaginaCursos;
import pages.PaginaFundamentosTesting;
import pages.PaginaPrincipal;
public class FreeRangeSteps {
PaginaPrincipal landingPage = new PaginaPrincipal();
PaginaCursos cursosPage = new PaginaCursos();
PaginaFundamentosTesting fundamentosPage = new PaginaFundamentosTesting();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
@And("select Introducción al Testing")
public void navigateToIntro() {
cursosPage.clickFundamentosTestingLink();
fundamentosPage.clickIntroduccionTestingLink();
}
}
PaginaCursos
package pages;
public class PaginaCursos extends BasePage {
private String fundamentosTestingLink = "//a[normalize-space()='Fundamentos del Testing' and @href]";
public PaginaCursos() {
super(driver);
}
public void clickFundamentosTestingLink() {
clickElement(fundamentosTestingLink);
}
}
PaginaFundamentosTesting
package pages;
public class PaginaCursos extends BasePage {
private String fundamentosTestingLink = "//a[normalize-space()='Fundamentos del Testing' and @href]";
public PaginaCursos() {
super(driver);
}
public void clickFundamentosTestingLink() {
clickElement(fundamentosTestingLink);
}
}
Otro scenario más para validar lo que es importante para el negocio Free Range.
Archivos usados: FreeRangeNavegacion.feature, PaginaPrincipal.java, FreeRangeSteps.java
FreeRangeNavegacion
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
# Scenario Outline: I can access the subpages through the navigation bar
# Given I navigate to www.freerangetesters.com
# When I go to <section> using the navigation bar
# Examples:
# | section |
# | Cursos |
# | Recursos |
# | Udemy |
# | Mentorías |
# | Newsletter |
# Scenario: Courses are presented correctly to potential customers
# Given I navigate to www.freerangetesters.com
# When I go to Cursos using the navigation bar
# And select Introducción al Testing
Scenario: Users can select a plan when signing up
Given I navigate to www.freerangetesters.com
When I select Elegir Plan
PaginaPrincipal
package pages;
public class PaginaPrincipal extends BasePage {
private String sectionLink = "//a[normalize-space()='%s' and @href]";
private String elegirUnPlanButton = "//a[normalize-space()='Elegir Plan' and @href]";
public PaginaPrincipal() {
super(driver);
}
// Método para navegar a www.freerangetesters.com
public void navigateToFreeRangeTesters() {
navigateTo("https://www.freerangetesters.com");
}
public void clickOnSectionNavigationBar(String section) {
// Reemplaza el marcador de posición en sectionLink con el nombre
String xpathSection = String.format(sectionLink, section);
clickElement(xpathSection);
}
public void clickOnElegirPlanButton() {
clickElement(elegirUnPlanButton);
}
}
FreeRangeSteps
package steps;
import io.cucumber.java.en.*;
import pages.PaginaCursos;
import pages.PaginaFundamentosTesting;
import pages.PaginaPrincipal;
public class FreeRangeSteps {
PaginaPrincipal landingPage = new PaginaPrincipal();
PaginaCursos cursosPage = new PaginaCursos();
PaginaFundamentosTesting fundamentosPage = new PaginaFundamentosTesting();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
@When("I select Elegir Plan")
public void selectElegirPlan() {
landingPage.clickOnElegirPlanButton();
}
@And("select Introducción al Testing")
public void navigateToIntro() {
cursosPage.clickFundamentosTestingLink();
fundamentosPage.clickIntroduccionTestingLink();
}
}
A veces hay que empezar por lo más complejo, pero viéndolo paso a paso, para después respirar hondo y hacer lo más sencillo de taquito como decimos en Argentina. En este caso, vamos a terminar la clase con una función nueva en la BasePage, heredada en una nueva clase Page Object para la página de Registro y un Step definido nuevo que va a comparar listas de valores usando TestNG.
Archivos en los que trabajamos: BasePage.java, PaginaRegistro.java, FreeRangeNavegacion.feature y FreeRangeSteps.java
FreeRangeNavegacion:
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
# Scenario Outline: I can access the subpages through the navigation bar
# Given I navigate to www.freerangetesters.com
# When I go to <section> using the navigation bar
# Examples:
# | section |
# | Cursos |
# | Recursos |
# | Udemy |
# | Mentorías |
# | Newsletter |
# Scenario: Courses are presented correctly to potential customers
# Given I navigate to www.freerangetesters.com
# When I go to Cursos using the navigation bar
# And select Introducción al Testing
Scenario: Users can select a plan when signing up
Given I navigate to www.freerangetesters.com
When I select Elegir Plan
Then I can validate the options in the checkout page
FreeRangeSteps:
package steps;
import java.util.List;
import org.testng.Assert;
import java.util.Arrays;
import io.cucumber.java.en.*;
import pages.PaginaCursos;
import pages.PaginaFundamentosTesting;
import pages.PaginaPrincipal;
import pages.PaginaRegistro;
public class FreeRangeSteps {
PaginaPrincipal landingPage = new PaginaPrincipal();
PaginaCursos cursosPage = new PaginaCursos();
PaginaFundamentosTesting fundamentosPage = new PaginaFundamentosTesting();
PaginaRegistro registro = new PaginaRegistro();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
@When("I select Elegir Plan")
public void selectElegirPlan() {
landingPage.clickOnElegirPlanButton();
}
@And("select Introducción al Testing")
public void navigateToIntro() {
cursosPage.clickFundamentosTestingLink();
fundamentosPage.clickIntroduccionTestingLink();
}
@Then("I can validate the options in the checkout page")
public void validateCheckoutPlans() {
List<String> lista = registro.returnPlanDropdownValues();
List<String> listaEsperada = Arrays.asList("Academia: $16.99 / mes • 11 productos",
"Academia: $176 / año • 11 productos", "Free: Gratis • 1 producto");
Assert.assertEquals(listaEsperada, lista);
}
}
BasePage:
package pages;
// Importaciones necesarias
import java.time.Duration;
import java.util.List;
import java.util.ArrayList;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import io.github.bonigarcia.wdm.WebDriverManager;
public class BasePage {
/*
* Declaración de una variable estática 'driver' de tipo WebDriver
* Esta variable va a ser compartida por todas las instancias de BasePage y sus
* subclases
*/
protected static WebDriver driver;
/*
* Declaración de una variable de instancia 'wait' de tipo WebDriverWait.
* Se inicializa inmediatamente con una instancia dew WebDriverWait utilizando
* el 'driver' estático
* WebDriverWait se usa para poner esperas explícitas en los elementos web
*/
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
/*
* Configura el WebDriver para Chrome usando WebDriverManager.
* WebDriverManager va a estar descargando y configurando automáticamente el
* driver del navegador
*/
static {
WebDriverManager.chromedriver().setup();
// Inicializa la variable estática 'driver' con una instancia de ChromeDriver
driver = new ChromeDriver();
}
/*
* Este es el constructor de BasePage que acepta un objeto WebDriver como
* argumento.
*/
public BasePage(WebDriver driver) {
BasePage.driver = driver;
}
// Método estático para navegar a una URL.
public static void navigateTo(String url) {
driver.get(url);
}
// Método estático para cerrar la instancia del driver.
public static void closeBrowser() {
driver.quit();
}
// Encuentra y devuelve un WebElement en la página utilizando un locator XPath,
// esperando a que esté presentente en el DOM
private WebElement Find(String locator) {
return wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(locator)));
}
public void clickElement(String locator) {
Find(locator).click();
}
public void write(String locator, String keysToSend) {
Find(locator).clear();
Find(locator).sendKeys(keysToSend);
}
public void selectFromDropdownByValue(String locator, String value) {
Select dropdown = new Select(Find(locator));
dropdown.selectByValue(value);
}
public void selectFromDropdownByIndex(String locator, Integer index) {
Select dropdown = new Select(Find(locator));
dropdown.selectByIndex(index);
}
public int dropdownSize(String locator) {
Select dropdown = new Select(Find(locator));
List<WebElement> dropdownOptions = dropdown.getOptions();
return dropdownOptions.size();
}
public List<String> getDropdownValues(String locator) {
Select dropdown = new Select(Find(locator));
List<WebElement> dropdownOptions = dropdown.getOptions();
List<String> values = new ArrayList<>();
for (WebElement option : dropdownOptions) {
values.add(option.getText());
}
return values;
}
}
PaginaRegistro:
package pages;
import java.util.List;
public class PaginaRegistro extends BasePage {
private String planDropdown = "//select[@id='cart_cart_item_attributes_plan_with_interval']";
public PaginaRegistro() {
super(driver);
}
public List<String> returnPlanDropdownValues() {
return getDropdownValues(planDropdown);
}
}
Las verificaciones son una parte fundamental a la hora de automatizar. Vamos a necesitar verificar que el estado de un sistema y sus elementos es el esperado y para eso vamos a usar la librería Assert de TestNG.
Acá les dejo los tipos más usados de Assertions y las que vimos en el video:
TestNG proporciona una serie de métodos de aserción a través de la clase Assert para verificar que las condiciones de las pruebas sean cumplidas. Estas aserciones son esenciales para validar el comportamiento esperado de la aplicación bajo prueba. A continuación, te detallo algunas de las aserciones más comunes y te proporciono ejemplos utilizando Selenium y Java:
1. assertEquals
Verifica que dos valores sean iguales.
Assert.assertEquals(actualTitle, expectedTitle, "El título de la página no es el esperado.");
2. assertNotEquals
Verifica que dos valores no sean iguales.
Assert.assertNotEquals(actualTitle, incorrectTitle, "El título de la página no debería ser este.");
3. assertTrue
Verifica que una condición sea verdadera.
Assert.assertTrue(isElementPresent, "El elemento debería estar presente.");
4. assertFalse
Verifica que una condición sea falsa.
Assert.assertFalse(isElementPresent, "El elemento no debería estar presente.");
Estos son solo ejemplos básicos pero de los más usados. Tengan en cuenta lo que ya vimos en las clases, especialmente lo relacionado a cómo Selenium interactúa con elementos web y cómo nos "traemos" información de ellos para poder validar.
Van a haber momentos en los que van a tener que validar muchas pequeñas cosas que no son como para hacer un scenario por cada una. Digamos, por ejemplo, un formulario con muchos campos.
Queremos tener un test que valide que los campos están presentes y en el estado que los requerimientos dicen, pero no vamos a hacer 25 tests distintos para cada campo. Ahí es donde entran las Soft Assertions. A continuación, dejo cómo implementarlas y un ejemplo:
Primero el import:
import org.testng.asserts.SoftAssert;
Segundo crear la instancia del objeto SoftAssert
SoftAssert soft = new SoftAssert();
Y así se ven (exactamente como las assertions comunes, pero con el potente assertAll(); al final!
public void Ejemplulis() {
String palabraEsperada = "Pepe";
String palabraEncontrada = "Papa";
// Soft Assertions: No detienen la ejecución al fallar. Ideal para verificar
// muchas cosas pequeñas a la vez.
soft.assertEquals(palabraEsperada, palabraEncontrada);
soft.assertTrue(palabraEncontrada.contains(palabraEsperada));
soft.assertNotEquals(palabraEncontrada,palabraEsperada);
soft.assertAll();
}
Seguimos añadiendo funcionalidad a nuestro framework y en esta clase vamos a sumar algo especialmente útil: Tags!
Archivos en los que trabajamos: build.gradle, TestRunner.java, FreeRangeNavegacion.feature
FreeRangeNavegacion:
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
Scenario Outline: I can access the subpages through the navigation bar
Given I navigate to www.freerangetesters.com
When I go to <section> using the navigation bar
Examples:
| section |
| Cursos |
| Recursos |
| Udemy |
| Mentorías |
| Newsletter |
@Courses
Scenario: Courses are presented correctly to potential customers
Given I navigate to www.freerangetesters.com
When I go to Cursos using the navigation bar
And select Introducción al Testing
@Plans @Courses
Scenario: Users can select a plan when signing up
Given I navigate to www.freerangetesters.com
When I select Elegir Plan
Then I can validate the options in the checkout page
TestRunner:
package runner;
import org.junit.AfterClass;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import pages.BasePage;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources", // Directorio de nuestros archivos .feature
glue = "steps", // Paquete donde tenemos nuestras clases definiendo los steps
plugin = { "pretty", "html:target/cucumber-reports" }, tags = "@Papa")
public class TestRunner {
@AfterClass
public static void cleanDriver() {
BasePage.closeBrowser();
}
}
build.gradle:
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java application project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html
*/
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
// https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java
implementation 'org.seleniumhq.selenium:selenium-java:4.15.0'
// https://mvnrepository.com/artifact/io.github.bonigarcia/webdrivermanager
implementation 'io.github.bonigarcia:webdrivermanager:5.6.2'
// https://mvnrepository.com/artifact/org.testng/testng
testImplementation 'org.testng:testng:7.8.0'
// https://mvnrepository.com/artifact/io.cucumber/cucumber-java
implementation 'io.cucumber:cucumber-java:7.14.1'
// https://mvnrepository.com/artifact/io.cucumber/cucumber-junit
testImplementation 'io.cucumber:cucumber-junit:7.14.1'
}
tasks.named('test') {
systemProperty "cucumber.options", System.getProperty("cucumber.options")
systemProperties System.getProperties();
}
Opciones a la hora de correr tags
gradle test -Dcucumber.filter.tags="@Plans" -> Va a correr Scenarios taggeados con @Plan
gradle test -Dcucumber.filter.tags="Not @Plans" -> Va a correr todos los Scenarios que no tengan el tag @Plan
gradle test -Dcucumber.filter.tags="@Plans" and "@Courses" -> Va a correr todos los scenarios que tengan ambos, @Plan y @Courses tags al mismo tiempo.
gradle test -Dcucumber.filter.tags="@Plans" or "@Courses" -> Va a correr los scenarios que tengan los tags @Plans o @Courses (osea...todos los scenarios que tengan uno u otro).
gradle test -Dcucumber.filter.tags="@Plans" and not "@Courses" -> Va a correr todos los scenarios que tengan el tag "@Plans" y no tengan "@Courses".
Los Feature files van a ser tan buenos como organizados y prolijos estén. En esta clase, vamos a sumar otra funcionalidad para ir hacia ahí: Los Backgrounds.
Archivos en los que trabajamos en la clase: FreeRangeNavegación.feature, TestRunner.java
FreeRangeNavegación
@Navigation
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
Background: I am on the Free Range Testers web without logging in.
Given I navigate to www.freerangetesters.com
Scenario Outline: I can access the subpages through the navigation bar
When I go to <section> using the navigation bar
Examples:
| section |
| Cursos |
| Recursos |
| Udemy |
| Mentorías |
| Newsletter |
@Courses
Scenario: Courses are presented correctly to potential customers
When I go to Cursos using the navigation bar
And select Introducción al Testing
@Plans @Courses
Scenario: Users can select a plan when signing up
When I select Elegir Plan
Then I can validate the options in the checkout page
TestRunner:
package runner;
import org.junit.AfterClass;
import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import pages.BasePage;
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources", // Directorio de nuestros archivos .feature
glue = "steps", // Paquete donde tenemos nuestras clases definiendo los steps
plugin = { "pretty", "html:target/cucumber-reports" }, tags = "@Navigation")
public class TestRunner {
@AfterClass
public static void cleanDriver() {
BasePage.closeBrowser();
}
}
Los Feature files son mucho más que simples abstracciones de lo que Selenium está haciendo detrás de escenas, son lo que el Manager, el Business Analyst o el Product Owner van a ver como resultado de nuestro trabajo como Ingenieros QA en los reportes que generemos.
Por este motivo, es vital mantenerlos ordenados, prolijos y profesionales. En esta clase vamos a ver cómo usar las expresiones que nos permiten escribir los steps con distintas palabras, sin alterar el sentido de las acciones realizadas.
Archivos en los que trabajamos: FreeRangeNavegacion.feature y FreeRangeSteps.java
FreeRangeSteps:
package steps;
import java.util.List;
import org.testng.Assert;
import org.testng.asserts.SoftAssert;
import java.util.Arrays;
import io.cucumber.java.en.*;
import pages.PaginaCursos;
import pages.PaginaFundamentosTesting;
import pages.PaginaPrincipal;
import pages.PaginaRegistro;
public class FreeRangeSteps {
SoftAssert soft = new SoftAssert();
PaginaPrincipal landingPage = new PaginaPrincipal();
PaginaCursos cursosPage = new PaginaCursos();
PaginaFundamentosTesting fundamentosPage = new PaginaFundamentosTesting();
PaginaRegistro registro = new PaginaRegistro();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
@When("(I|The user|The client) (select|selects) Elegir Plan")
public void selectElegirPlan() {
landingPage.clickOnElegirPlanButton();
}
@And("(I|The user|The client) (select|selects) Introducción al Testing")
public void navigateToIntro() {
cursosPage.clickFundamentosTestingLink();
fundamentosPage.clickIntroduccionTestingLink();
}
@Then("(I|The user|The client) can validate the options in the checkout page")
public void validateCheckoutPlans() {
List<String> lista = registro.returnPlanDropdownValues();
List<String> listaEsperada = Arrays.asList("Academia: $16.99 / mes • 11 productos",
"Academia: $176 / año • 11 productos", "Free: Gratis • 1 producto");
Assert.assertEquals(listaEsperada, lista);
}
}
FreeRangeNavegacion:
@Navigation
Feature: Navigation
To see the subpages
Without logging in
I can click the navigation bar links
Background: I am on the Free Range Testers web without logging in.
Given I navigate to www.freerangetesters.com
Scenario Outline: I can access the subpages through the navigation bar
When I go to <section> using the navigation bar
Examples:
| section |
| Cursos |
| Recursos |
| Udemy |
| Mentorías |
| Newsletter |
@Courses
Scenario: Courses are presented correctly to potential customers
When I go to Cursos using the navigation bar
And I select Introducción al Testing
@Plans @Courses
Scenario: Users can select a plan when signing up
When The client selects Elegir Plan
Then The client can validate the options in the checkout page
Pensabas que estábamos listos con las expresiones regulares? Cucumber no está de acuerdo con eso. Veamos cómo solucionar el actual problema de steps no definidos (aunque si estén definidos) cuando tenemos expresiones regulares.
Archivo sobre el que trabajamos: FreeRangeSteps.java
FreeRangeSteps
package steps;
import java.util.List;
import org.testng.Assert;
import org.testng.asserts.SoftAssert;
import java.util.Arrays;
import io.cucumber.java.en.*;
import pages.PaginaCursos;
import pages.PaginaFundamentosTesting;
import pages.PaginaPrincipal;
import pages.PaginaRegistro;
public class FreeRangeSteps {
SoftAssert soft = new SoftAssert();
PaginaPrincipal landingPage = new PaginaPrincipal();
PaginaCursos cursosPage = new PaginaCursos();
PaginaFundamentosTesting fundamentosPage = new PaginaFundamentosTesting();
PaginaRegistro registro = new PaginaRegistro();
@Given("I navigate to www.freerangetesters.com")
public void iNavigateToFRT() {
landingPage.navigateToFreeRangeTesters();
}
@When("I go to {word} using the navigation bar")
public void navigationBarUse(String section) {
landingPage.clickOnSectionNavigationBar(section);
}
@When("^(?:I|The user|The client) selects? Elegir Plan$")
public void selectElegirPlan() {
landingPage.clickOnElegirPlanButton();
}
@And("^(?:I|The user|The client) selects? Introducción al Testing$")
public void navigateToIntro() {
cursosPage.clickFundamentosTestingLink();
fundamentosPage.clickIntroduccionTestingLink();
}
@Then("^(?:I|The user|The client) can validate the options in the checkout page$")
public void validateCheckoutPlans() {
List<String> lista = registro.returnPlanDropdownValues();
List<String> listaEsperada = Arrays.asList("Academia: $16.99 / mes • 11 productos",
"Academia: $176 / año • 11 productos", "Free: Gratis • 1 producto");
Assert.assertEquals(listaEsperada, lista);
}
}
Es momento de ver qué podemos hacer con los reportes de Cucumber y qué otras opciones hay.
Archivos sobre los que vamos a trabajar: resources/cucumber.properties
cucumber.properties
cucumber.publish.enabled=true
Además, en la terminal zsh de VSCode van a necesitar configurar el token que les da Cucumber Reports al loguear.
La web de Cucumber Reports es: https://reports.cucumber.io
Ahí mismo loguean y se crean una colección. Esta colección va a estar asociada a un token.
El token, como dijimos antes, lo van a definir en la terminal con
export CUCUMBER_PUBLISH_TOKEN=some-secret-token (reemplacen el some-secret-token por el token que les dio Cucumber)
Llegó el momento de sumar screenshots a los reportes de Cucumber! ¿Cómo lo hacemos? Sencillo, con una nueva clase que vamos a crear en esta clase.
Archivo que creamos y trabajamos en esta clase: steps/Hooks.java
package steps;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import io.cucumber.java.After;
import io.cucumber.java.Scenario;
import pages.BasePage;
public class Hooks extends BasePage {
public Hooks() {
super(driver);
}
@After
public void tearDown(Scenario scenario) {
if (scenario.isFailed()) {
scenario.log("Scenario failing, please refer to the image attached to this report");
final byte[] screenshot = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
scenario.attach(screenshot, "image/png", "Screenshot of the error");
}
}
}
El ejercicio que mandó Solstice: Buscar en Amazon, navegar a la segunda página y elegir el segundo item para agregar al carrito de compras.
En este curso vamos a aprender, desde cero, como crear un framework con Java, Gradle y Selenium Webdriver con Cucumber.
Esto será hecho sobre los conocimientos de Programación Orientada a Objetos en el anterior curso, Programación Para Testers, o los conocimientos que ya poseas sobre POO.
Vamos a aprender cómo instalar el software y librerías necesarios, dejar el workspace listo para empezar a trabajar, entender la lógica detrás de cada paso que realizamos y, lo más importante, cómo hacer las cosas siguiendo las mejores prácticas que yo he empleado con más de 10 años de experiencia míos trabajando en el rubro tanto en América como luego de migrar con trabajo como especialista en esta disciplina!
Si...estos conocimientos te van a servir para cuando decidas buscar trabajo en otros países!
Lo más importante que te vas a llevar de este curso es que, además de aprender a crear un framework desde cero, vas a entender por qué y cómo se hizo cada paso. Esto va a darte la flexibilidad de poder explicar y decidir por vos mismo qué técnicas usar, cómo usarlas y por qué usarlas.
Vas a ser capaz de explicar en una entrevista el por qué y cómo del armado de tu framework con Selenium, mejorar otros ya hechos y mucho más!