Configura Maven con Java 11 en VS Code para QA

¿Alguna vez abriste un proyecto de automatización y te encontraste con un pom.xml lleno de dependencias que no entendías, errores de compilación crípticos y sin saber por dónde empezar? Si estás iniciando en el mundo del QA con Java, esa sensación es más común de lo que crees.
La buena noticia es que configurar tu entorno de trabajo no tiene que ser un dolor de cabeza. Con Maven, Java 11 y Visual Studio Code puedes tener un proyecto de automatización limpio, estructurado y listo para crecer, en menos de 20 minutos.
En esta guía vas a aprender exactamente eso: crear un proyecto Maven desde cero, configurar las dependencias correctas para el stack SDTE (Selenium + TestNG), y escribir tu primer DriverFactory.java aplicando el patrón Factory Method y el principio SOLID de Inversión de Dependencias. No como algo abstracto, sino como código real que podrás usar desde el primer día.
Al final encontrarás el código completo listo para copiar y un video donde lo construimos en vivo desde una carpeta vacía hasta el primer test completado (PASS).

¿Por qué Maven y no Gradle?

Antes de escribir una sola línea, vale la pena responder la pregunta que seguramente tienes: ¿por qué Maven?

En el ecosistema QA con Java, Maven sigue siendo el estándar de facto por tres razones concretas:

  • Convención sobre configuración: la estructura de carpetas (src/main/java, src/test/java) es predecible en cualquier proyecto del mundo. Entras a un repo nuevo y ya sabes dónde están los tests.
  • Integración con CI/CD: Jenkins, GitHub Actions y Azure DevOps tienen soporte nativo para mvn test. Cero configuración extra.
  • Compatibilidad total con el stack SDTE: Selenium, TestNG, Karate, Appium y Allure tienen sus artefactos publicados en Maven Central con mantenimiento activo.

Gradle es una excelente opción también, pero para un QA Junior que está aprendiendo el stack, Maven reduce la curva de aprendizaje porque su sintaxis XML es explícita: cada dependencia se ve, se entiende y se versiona sin magia detrás.


Requisitos previos en 5 minutos

Antes de crear el proyecto necesitas tener instalado:

HerramientaVersión mínimaVerificar con
Java JDK11 (LTS)java -version
Maven3.8+mvn -version
Visual Studio CodeCualquier reciente
Extension Pack for JavaÚltimaDesde el marketplace de VSCode

Tip para instalar Java 11 rápido: usa SDKMAN en Mac/Linux o Adoptium en Windows. Ambos te permiten tener múltiples versiones de Java sin conflictos.

Para instalar la extensión de Java en VSCode, abre el marketplace (Ctrl+Shift+X), busca Extension Pack for Java de Microsoft e instálala. Esta sola extensión trae todo lo que necesitas: soporte de Maven, autocompletado, debugger y test runner.


Estructura del proyecto SDTE

Antes de crear archivos, entendamos qué vamos a construir. La estructura de paquetes del stack SDTE para un proyecto inicial es:

sdte-proyecto/
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── com/sdte/
    │           └── core/
    │               └── DriverFactory.java   ← hoy lo creamos aquí
    └── test/
        └── java/
            └── com/sdte/
                └── tests/
                    └── web/
                        └── SmokeTest.java   ← y este también

Esta separación no es capricho: Maven usa src/main para código de producción y src/test para código de pruebas. Cuando ejecutas mvn test, solo compila y corre lo de src/test, pero tiene acceso a todo lo de src/main. Así, tu DriverFactory.java vive en main (es infraestructura reutilizable) y tus tests viven en test.


Paso a paso: crear el proyecto

1. Crear el proyecto Maven desde VSCode

Abre la paleta de comandos en VSCode (Ctrl+Shift+P), escribe Maven: Create Maven Project y selecciona el arquetipo maven-archetype-quickstart. Cuando te pida los datos, usa:

groupId:    com.sdte
artifactId: sdte-proyecto
version:    1.0.0
package:    com.sdte

VSCode generará la estructura base automáticamente y abrirá el pom.xml.

2. Configurar el pom.xml

Reemplaza el contenido del pom.xml generado con esta configuración lista para el stack SDTE:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sdte</groupId>
    <artifactId>sdte-proyecto</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <!-- Java 11 LTS — estable y ampliamente soportado -->
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- Versiones del stack SDTE -->
        <selenium.version>4.20.0</selenium.version>
        <testng.version>7.10.2</testng.version>
    </properties>

    <dependencies>
        <!-- Selenium WebDriver — automatización web -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <!-- TestNG — framework de testing -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Plugin para ejecutar tests TestNG con mvn test -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
            </plugin>
        </plugins>
    </build>

</project>

Guarda el archivo. VSCode detectará los cambios y descargará las dependencias automáticamente (verás el progreso en la barra inferior). Si no arranca solo, haz clic derecho en el pom.xmlUpdate Project.

3. Verificar que Maven descargó todo

Abre la terminal integrada de VSCode (Ctrl+Ñ) y ejecuta:

mvn dependency:resolve

Deberías ver BUILD SUCCESS con la lista de dependencias resueltas. Si ves errores de red, verifica tu conexión o configura el proxy en ~/.m2/settings.xml.


🔧 Gancho Práctico — Código + Video

El código

Aquí están los tres archivos que construimos hoy. Están ordenados para que los crees en secuencia y el proyecto compile en el primer intento.

Archivo 1 — DriverFactory.java
Ruta: src/main/java/com/sdte/core/DriverFactory.java

// ============================================
// DriverFactory.java
// Capa     : DSL Java / Core
// Patrón   : Factory Method
// Principio: D — Dependency Inversion (DIP)
// Nivel    : Principiante
// ============================================
// Por qué DIP aquí:
//   Los tests deben depender de WebDriver (abstracción),
//   NUNCA de new ChromeDriver() (implementación concreta).
//   DriverFactory es el único lugar donde vive esa decisión.
// ============================================

package com.sdte.core;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.edge.EdgeDriver;

public class DriverFactory {

    // ThreadLocal: cada hilo de ejecución tiene su propio driver.
    // Esto permite correr tests en paralelo sin conflictos.
    private static final ThreadLocal<WebDriver> driverHolder =
        new ThreadLocal<>();

    /**
     * Factory Method — crea y almacena el driver según el browser.
     * Llama a este método una sola vez por hilo (en @BeforeClass).
     *
     * @param browser "chrome" | "firefox" | "edge"
     * @return WebDriver listo para usar
     */
    public static WebDriver getDriver(String browser) {
        if (driverHolder.get() == null) {

            // Paso 1: decidir qué implementación concreta crear
            WebDriver driver;
            switch (browser.toLowerCase()) {
                case "firefox":
                    driver = new FirefoxDriver();
                    break;
                case "edge":
                    driver = new EdgeDriver();
                    break;
                case "chrome":
                default:
                    // Chrome es el default — cambia con -Dbrowser=firefox
                    driver = new ChromeDriver();
                    break;
            }

            // Paso 2: almacenar en ThreadLocal para este hilo
            driverHolder.set(driver);
        }
        // Paso 3: retornar la instancia (siempre WebDriver, nunca ChromeDriver)
        return driverHolder.get();
    }

    /**
     * Cierra el browser y libera el ThreadLocal.
     * Llama a este método en @AfterClass, siempre.
     */
    public static void quitDriver() {
        if (driverHolder.get() != null) {
            driverHolder.get().quit();
            // Evitar memory leak en ejecuciones paralelas con grid
            driverHolder.remove();
        }
    }
}

Archivo 2 — SmokeTest.java
Ruta: src/test/java/com/sdte/tests/web/SmokeTest.java

// ============================================
// SmokeTest.java
// Capa     : TestNG Framework
// Principio: D — Dependency Inversion aplicado
// Nivel    : Principiante
// ============================================
// Nota clave:
//   Este test NUNCA instancia ChromeDriver directamente.
//   Depende de WebDriver (abstracción) y DriverFactory se encarga
//   de decidir qué browser usar. Eso es DIP en la práctica.
// ============================================

package com.sdte.tests.web;

import com.sdte.core.DriverFactory;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class SmokeTest {

    // Abstracción — no ChromeDriver, no FirefoxDriver: WebDriver
    private WebDriver driver;

    @BeforeClass
    public void setUp() {
        // Paso 1: obtener el browser desde property del sistema
        // Ejecutar con: mvn test -Dbrowser=firefox
        // Si no se pasa la property, usa chrome por defecto
        String browser = System.getProperty("browser", "chrome");
        driver = DriverFactory.getDriver(browser);
        driver.manage().window().maximize();
    }

    @Test
    public void verificarTituloGoogle() {
        // Paso 1: navegar
        driver.get("https://www.google.com");

        // Paso 2: obtener título real de la página
        String titulo = driver.getTitle();

        // Paso 3: verificar con mensaje descriptivo
        Assert.assertTrue(
            titulo.contains("Google"),
            "El título debería contener 'Google' pero fue: " + titulo
        );
    }

    @AfterClass
    public void tearDown() {
        // Siempre limpiar — libera el browser y el ThreadLocal
        DriverFactory.quitDriver();
    }
}

Archivo 3 — ejecutar el test

# Ejecutar con Chrome (default)
mvn test

# Ejecutar con Firefox
mvn test -Dbrowser=firefox

# Ejecutar con Edge
mvn test -Dbrowser=edge

Output esperado en consola:

[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Desglose del código

ClaseResponsabilidadPatrón / Principio
DriverFactoryCrear y almacenar el WebDriver correctoFactory Method + ThreadLocal
SmokeTestVerificar comportamiento de la appUsa abstracción (DIP)

¿Por qué ThreadLocal? Imagínalo como un casillero: cada hilo de ejecución tiene su propio casillero con su propio driver. Cuando corres tests en paralelo con TestNG, el hilo 1 no puede agarrar el driver del hilo 2. Sin ThreadLocal, dos tests corriendo al mismo tiempo usarían el mismo browser y se pisarían entre sí.

¿Por qué WebDriver y no ChromeDriver en el test? Porque mañana quizás el cliente pide correr los tests en Firefox, o el CI los corre en Edge. Si escribiste ChromeDriver driver, tienes que cambiar cada test. Si escribiste WebDriver driver, solo cambias DriverFactory y todo funciona. Eso es exactamente lo que significa Dependency Inversion: depender de abstracciones, no de implementaciones.


El video — Implementación en vivo

¿Prefieres ver cómo se construye desde una carpeta vacía? En este video lo hacemos en tiempo real:

URL_DEL_VIDEO
Maven + Java 11 + VSCode para QA — Setup completo desde cero

Timestamps:

  • 0:00 — Intro: qué vamos a construir y por qué
  • 0:45 — Instalación de JDK 11 y verificación
  • 1:30 — Crear el proyecto Maven desde VSCode
  • 3:00 — Configurar el pom.xml con Selenium + TestNG
  • 5:00 — Escribir DriverFactory.java — patrón Factory Method explicado
  • 8:30 — Escribir SmokeTest.java — aplicando DIP
  • 11:00mvn test en vivo — primer test verde
  • 13:00 — Cambiar browser con -Dbrowser=firefox sin tocar el test
  • 14:30 — Conclusión y próximos pasos del stack SDTE

Recursos del video:
📎 Código completo en GitHub: [link]
📎 Adoptium JDK 11: https://adoptium.net
📎 Extension Pack for Java: marketplace VSCode


🚀 Tu Turno — 3 Niveles de Práctica

Nivel 1 — Ejecuta (10 min)
Copia los tres archivos tal como están, ejecuta mvn test y observa cómo Chrome se abre, navega a Google y el test pasa. Luego ejecuta mvn test -Dbrowser=firefox y mira cómo el mismo test corre en Firefox sin cambiar una sola línea del test.
Objetivo: confirmar que el entorno funciona y ver DIP en acción

Nivel 2 — Extiende el DriverFactory (20 min)
Agrega soporte para un cuarto browser: Safari (solo Mac). El método getDriver() debe aceptar "safari" y crear un SafariDriver. Verifica que los tests existentes no necesitan ningún cambio.
Objetivo: experimentar que Open/Closed y DIP van de la mano

Nivel 3 — Integra configuración externa (45 min)
En lugar de pasar el browser por -Dbrowser=, lee el valor desde un archivo config/config.properties en src/test/resources. Crea una clase ConfigReader.java en com/sdte/core/ que cargue esa propiedad. DriverFactory debe usar ConfigReader en lugar de System.getProperty().
Objetivo: construir la base real de configuración del stack SDTE

¿Llegaste al nivel 3? Comparte tu ConfigReader.java en los comentarios 👇


Conclusión

Hoy configuraste la base del stack SDTE desde cero. Antes de cerrar, los tres puntos que no se te deben olvidar:

  • Maven es estructura: la convención de carpetas y el pom.xml hacen que cualquier persona pueda entrar a tu proyecto y orientarse en segundos.
  • DriverFactory es tu primer patrón de diseño: Factory Method + ThreadLocal no es complejidad innecesaria, es la diferencia entre un test suite que escala y uno que explota en paralelo.
  • Nunca escribas new ChromeDriver() en un test: cada vez que lo veas en código ajeno, ya sabes que hay una violación de DIP ahí. Ya tienes el criterio para mejorarlo.

La próxima semana vamos a construir encima de este setup: agregaremos BasePage.java con el patrón Template Method y crearemos el primer Page Object real. Si no quieres perderte ese post, suscríbete al newsletter abajo.

¿Qué parte implementarías primero en tu proyecto actual: el DriverFactory o el ConfigReader del nivel 3? Cuéntame en los comentarios.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *