process.env.VARIABLE devuelve string | undefined en TypeScript, así que un test que usa una credencial faltante no falla con un error claro al iniciar: falla a mitad del test con un error críptico de locator o de autenticación. La solución es un wrapper requireEnv() que lanza una excepción al arranque con el nombre de la variable: un TEST_USER_EMAIL faltante detiene la suite antes de que corra el primer test y te dice exactamente qué configurar. Este artículo cubre la configuración de dotenv con .env y .env.local, el patrón de objeto env con tipos seguros, la validación en global setup, y cómo difieren vars. y secrets. en GitHub Actions para valores sensibles y no sensibles.

Qué va dónde

En playwright.config.ts (commiteado al control de versiones)

export default defineConfig({
  timeout: 30000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [['html'], ['list']],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
});

En archivos .env o secretos de CI (nunca commiteados si contienen secretos)

BASE_URL=https://staging.miapp.com
API_KEY=sk-test-abc123
TEST_USER_EMAIL=testusuario@ejemplo.com
TEST_USER_PASSWORD=ClaveSegura123!
DATABASE_URL=postgresql://localhost:5432/testdb

Configurar dotenv

npm install -D dotenv

Crea .env con valores por defecto seguros (sin credenciales reales):

# .env
BASE_URL=http://localhost:3000
TEST_USER_EMAIL=test@ejemplo.com
TEST_USER_PASSWORD=
DATABASE_URL=

Crea .env.local con los valores reales para local (ignorado por git):

# .env.local — nunca commiteado
TEST_USER_EMAIL=testreal@ejemplo.com
TEST_USER_PASSWORD=ClaveReal123
DATABASE_URL=postgresql://localhost:5432/miapp_test

Cárgalos en la configuración:

// playwright.config.ts
import dotenv from 'dotenv';
import path from 'path';

// Cargar .env.local primero (sobreescribe .env)
dotenv.config({ path: path.resolve(__dirname, '.env.local') });
dotenv.config({ path: path.resolve(__dirname, '.env') });

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
  },
});

Agrega al .gitignore:

.env.local
playwright/.auth/
test-results/
playwright-report/

Variables de entorno con tipos seguros

process.env.VARIABLE devuelve string | undefined. Detectar variables faltantes en el runtime del test es malo. Detéctalas al arranque:

// utils/env.ts
function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`La variable de entorno ${name} es requerida pero no está configurada`);
  }
  return value;
}

export const env = {
  baseURL: process.env.BASE_URL || 'http://localhost:3000',
  apiKey: requireEnv('API_KEY'),
  testUser: {
    email: requireEnv('TEST_USER_EMAIL'),
    password: requireEnv('TEST_USER_PASSWORD'),
  },
};

Impórtalo en los tests:

import { env } from '../utils/env';

test('login con credenciales de test', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(env.testUser.email);
  await page.getByLabel('Password').fill(env.testUser.password);
});

Si TEST_USER_EMAIL no está configurado, obtienes un error claro al arranque: La variable de entorno TEST_USER_EMAIL es requerida pero no está configurada, no un fallo críptico a mitad del test.

Configuración por proyecto

Para múltiples entornos de test, usa proyectos de Playwright con diferentes ajustes:

// playwright.config.ts
const environments = {
  local: {
    baseURL: 'http://localhost:3000',
    apiURL: 'http://localhost:8000',
  },
  staging: {
    baseURL: 'https://staging.miapp.com',
    apiURL: 'https://api-staging.miapp.com',
  },
  production: {
    baseURL: 'https://miapp.com',
    apiURL: 'https://api.miapp.com',
  },
};

const testEnv = (process.env.TEST_ENV as keyof typeof environments) || 'local';
const config = environments[testEnv];

export default defineConfig({
  use: {
    baseURL: config.baseURL,
    extraHTTPHeaders: {
      'X-API-Base': config.apiURL,
    },
  },
});

Ejecutá con:

TEST_ENV=staging npx playwright test
TEST_ENV=production npx playwright test --grep @smoke

Feature flags en tests

Cuando tu aplicación usa feature flags, los tests pueden necesitar comportarse diferente según lo que esté habilitado:

// Verificar si una función está habilitada vía variable de entorno
const NUEVO_CHECKOUT = process.env.FEATURE_NEW_CHECKOUT === 'true';

test('flujo de checkout', async ({ page }) => {
  await page.goto('/checkout');

  if (NUEVO_CHECKOUT) {
    // Testear la nueva UI de checkout
    await expect(page.getByTestId('new-checkout-form')).toBeVisible();
  } else {
    // Testear la UI de checkout legacy
    await expect(page.getByTestId('legacy-checkout-form')).toBeVisible();
  }
});

Mejor: archivos de test separados por estado del feature flag, controlados por proyectos de Playwright:

projects: [
  {
    name: 'nuevo-checkout',
    testMatch: '**/checkout-new/**',
    use: { extraHTTPHeaders: { 'X-Feature-New-Checkout': 'true' } },
  },
  {
    name: 'checkout-legacy',
    testMatch: '**/checkout-legacy/**',
  },
],

Configuración del entorno en CI/CD

Ejemplo de GitHub Actions con manejo correcto de secretos:

# .github/workflows/tests.yml
env:
  # No sensible: usá vars, visible en logs
  BASE_URL: ${{ vars.STAGING_URL }}
  TEST_ENV: staging

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Ejecutar tests
        env:
          # Sensible: usá secrets, enmascarado en logs
          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
          API_KEY: ${{ secrets.API_KEY }}
        run: npx playwright test

La diferencia clave: vars. es para configuración no sensible, visible en los logs del workflow; secrets. está enmascarado en logs, nunca se imprime y se usa para credenciales y claves.

Validar la configuración al inicio de la suite

Usa global setup para validar el entorno antes de que corran los tests:

// global-setup.ts
export default async function globalSetup() {
  const required = ['TEST_USER_EMAIL', 'TEST_USER_PASSWORD', 'BASE_URL'];
  const missing = required.filter(key => !process.env[key]);

  if (missing.length > 0) {
    throw new Error(
      `Variables de entorno requeridas faltantes:\n${missing.map(k => `  - ${k}`).join('\n')}\n\n` +
      `Copiá .env.example a .env.local y completá los valores.`
    );
  }

  console.log(`Ejecutando tests contra: ${process.env.BASE_URL}`);
}

Esto falla rápido con un mensaje claro en lugar de dejar que el primer test falle con un error confuso de locator.

→ See also: Archivo de Configuración de Playwright Explicado: Todas las Opciones | Configuración de Entornos en Playwright: Local, Staging y Producción | GitHub Actions para Tests de Playwright: La Configuración Completa (2026)