Por Que los Testers Necesitan Habilidades de Programacion

La automatizacion de testing significa escribir codigo. No necesitas convertirte en desarrollador de software, pero necesitas suficiente conocimiento de programacion para escribir, leer y mantener scripts de test. Esta leccion cubre los conceptos esenciales que todo QA automation engineer necesita.

Usamos JavaScript para los ejemplos porque es el lenguaje mas utilizado en automatizacion moderna (Playwright, Cypress, WebdriverIO). Los conceptos aplican a cualquier lenguaje.

Variables y Tipos de Datos

Las variables almacenan datos que tus tests usan — credenciales, URLs, valores esperados, selectores de elementos.

Declarando Variables

// Usa const para valores que no cambiaran
const BASE_URL = 'https://app.example.com';
const TIMEOUT = 30000;

// Usa let para valores que cambiaran
let loginAttempts = 0;
let currentUser = null;

Tipos de Datos

TipoEjemploUso en Testing
String'hello', "world"URLs, selectores, texto esperado
Number42, 3.14Timeouts, contadores, precios
Booleantrue, falseFeature flags, condiciones
NullnullValores vacios/faltantes
UndefinedundefinedVariables sin inicializar
Array[1, 2, 3]Listas de datos de prueba
Object{name: 'John'}Datos estructurados

Operaciones de String para Testing

// Operaciones comunes en tests
const pageTitle = 'Welcome to Dashboard - MyApp';

// Verificar si contiene texto esperado
pageTitle.includes('Dashboard'); // true

// Extraer partes de strings
pageTitle.toLowerCase(); // 'welcome to dashboard - myapp'
pageTitle.trim(); // Eliminar espacios

// Template literals para valores dinamicos
const url = `${BASE_URL}/users/${userId}/profile`;
const errorMsg = `Esperado ${expected} pero se obtuvo ${actual}`;

Condicionales

Los condicionales permiten que tus tests tomen decisiones basadas en condiciones.

// If-else basico
if (userRole === 'admin') {
  await page.click('#admin-panel');
} else if (userRole === 'editor') {
  await page.click('#editor-tools');
} else {
  await page.click('#user-dashboard');
}

// Operador ternario para condiciones simples
const timeout = isCI ? 60000 : 30000;

// Verificando multiples condiciones
if (statusCode >= 200 && statusCode < 300) {
  console.log('Request exitoso');
} else if (statusCode === 404) {
  console.log('Recurso no encontrado');
} else {
  console.log(`Status inesperado: ${statusCode}`);
}

Operadores de Comparacion

OperadorSignificadoEjemplo
===Igualdad estrictastatus === 200
!==No igualrole !== 'guest'
>, <Mayor/menor quecount > 0
>=, <=Mayor/menor o igualprice >= 9.99
&&ANDisVisible && isEnabled
||ORisAdmin || isSuperuser

Loops

Los loops repiten acciones — esenciales para testing data-driven y operaciones batch.

For Loop

// Probar login con multiples usuarios
const users = ['admin', 'editor', 'viewer'];

for (let i = 0; i < users.length; i++) {
  console.log(`Probando usuario: ${users[i]}`);
}

// Loop moderno for...of (preferido)
for (const user of users) {
  console.log(`Probando usuario: ${user}`);
}

forEach y map

// Procesar cada resultado de test
const results = [
  { test: 'login', status: 'pass' },
  { test: 'checkout', status: 'fail' },
  { test: 'search', status: 'pass' }
];

// forEach - hacer algo con cada item
results.forEach(result => {
  console.log(`${result.test}: ${result.status}`);
});

// map - transformar cada item
const testNames = results.map(r => r.test);
// ['login', 'checkout', 'search']

// filter - mantener items que cumplen condicion
const failures = results.filter(r => r.status === 'fail');
// [{ test: 'checkout', status: 'fail' }]

Funciones

Las funciones son los bloques de construccion del codigo de test limpio. Encapsulan logica reutilizable.

Funciones Basicas

// Declaracion de funcion
function calculateTotal(price, quantity, taxRate) {
  const subtotal = price * quantity;
  const tax = subtotal * taxRate;
  return subtotal + tax;
}

// Arrow function (sintaxis moderna)
const calculateTotal = (price, quantity, taxRate) => {
  const subtotal = price * quantity;
  const tax = subtotal * taxRate;
  return subtotal + tax;
};

// Usando la funcion
const total = calculateTotal(29.99, 3, 0.08);

Funciones en Automatizacion

// Funcion reutilizable de login
async function login(page, username, password) {
  await page.goto('/login');
  await page.fill('#username', username);
  await page.fill('#password', password);
  await page.click('#submit');
  await page.waitForURL('/dashboard');
}

// Helper de asercion reutilizable
function assertInRange(actual, min, max) {
  if (actual < min || actual > max) {
    throw new Error(`${actual} no esta en rango [${min}, ${max}]`);
  }
}

// Usando estos en tests
await login(page, 'admin@test.com', 'password123');
assertInRange(responseTime, 0, 3000);

Colecciones: Arrays y Objetos

Arrays

// Datos de prueba como arrays
const validEmails = ['user@test.com', 'admin@company.org', 'test+tag@gmail.com'];
const invalidEmails = ['', 'not-an-email', '@missing.com', 'no-domain@'];

// Metodos utiles de array
validEmails.length;              // 3
validEmails.includes('user@test.com'); // true
validEmails.push('new@test.com'); // Agregar item
validEmails.indexOf('admin@company.org'); // 1

Objetos

// Usuario de prueba como objeto
const testUser = {
  name: 'Jane Smith',
  email: 'jane@test.com',
  role: 'admin',
  permissions: ['read', 'write', 'delete']
};

// Accediendo propiedades
console.log(testUser.name);       // 'Jane Smith'
console.log(testUser['email']);   // 'jane@test.com'

// Selectores como objeto
const selectors = {
  loginForm: '#login-form',
  emailInput: '[data-testid="email"]',
  passwordInput: '[data-testid="password"]',
  submitButton: 'button[type="submit"]'
};

Async/Await

La automatizacion es altamente asincrona — esperar que paginas carguen, elementos aparezcan, respuestas de API retornen. Entender async/await es esencial.

El Problema

// Sin async/await - los callbacks se anidan y se vuelven ilegibles
page.goto('/login', function() {
  page.fill('#email', 'test@test.com', function() {
    page.click('#submit', function() {
      // Esto es "callback hell"
    });
  });
});

La Solucion: Async/Await

// Con async/await - limpio y legible
async function testLogin(page) {
  await page.goto('/login');
  await page.fill('#email', 'test@test.com');
  await page.fill('#password', 'secret123');
  await page.click('#submit');
  await page.waitForSelector('.dashboard');
}

La keyword await pausa la ejecucion hasta que la operacion asincrona se complete. La keyword async marca una funcion que contiene llamadas await.

Patrones Async Comunes en Testing

// Esperar multiples cosas
const [response] = await Promise.all([
  page.waitForResponse('/api/users'),
  page.click('#load-users')
]);

// Esperar con timeout
await page.waitForSelector('.modal', { timeout: 5000 });

// Patron de reintento
async function waitForElement(page, selector, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      await page.waitForSelector(selector, { timeout: 2000 });
      return true;
    } catch {
      console.log(`Reintento ${i + 1}/${retries}`);
    }
  }
  return false;
}

Manejo de Errores

Los tests fallan. Un buen manejo de errores asegura que los fallos se reporten claramente y la limpieza siempre ocurra.

Try-Catch

async function testCheckout(page) {
  try {
    await page.goto('/checkout');
    await page.fill('#card', '4242424242424242');
    await page.click('#pay');
    await expect(page.locator('.success')).toBeVisible();
  } catch (error) {
    console.error(`Test de checkout fallo: ${error.message}`);
    await page.screenshot({ path: 'checkout-failure.png' });
    throw error; // Re-lanzar para marcar test como fallido
  }
}

Bloque Finally

async function testWithCleanup(page) {
  let createdUserId;
  try {
    createdUserId = await createTestUser();
    await page.goto(`/users/${createdUserId}`);
    await expect(page.locator('.user-name')).toHaveText('Test User');
  } finally {
    // Cleanup siempre se ejecuta, incluso si el test falla
    if (createdUserId) {
      await deleteTestUser(createdUserId);
    }
  }
}

Patrones Practicos para Testing

Test Data Builder

function createUser(overrides = {}) {
  return {
    name: 'Default User',
    email: `user_${Date.now()}@test.com`,
    role: 'viewer',
    active: true,
    ...overrides
  };
}

// Uso
const admin = createUser({ role: 'admin', name: 'Admin User' });
const inactive = createUser({ active: false });

Gestion de Configuracion

const config = {
  baseUrl: process.env.BASE_URL || 'http://localhost:3000',
  apiUrl: process.env.API_URL || 'http://localhost:8080',
  timeout: parseInt(process.env.TIMEOUT) || 30000,
  headless: process.env.CI === 'true'
};

Ejercicio: Escribe tus Primeras Funciones Helper

Escribe las siguientes funciones en JavaScript:

  1. generateEmail() — retorna un email unico para datos de prueba
  2. formatPrice(amount, currency) — formatea un precio como “$29.99” o “EUR 29.99”
  3. retryAction(action, maxRetries) — reintenta una accion async hasta maxRetries veces
  4. compareArrays(arr1, arr2) — retorna true si los arrays contienen los mismos elementos

Estos patrones aparecen constantemente en proyectos reales de automatizacion.

Puntos Clave

  • Domina variables, funciones, condicionales y loops — son el 80% del codigo de test
  • Aprende async/await a fondo — todos los frameworks modernos lo usan
  • Usa try-catch-finally para manejo confiable de errores y cleanup
  • Objetos y arrays son tus estructuras de datos principales
  • Las funciones deben ser pequenas, enfocadas y reutilizables entre tests