php elephant sticker
Photo by RealToughCandy.com on Pexels.com
目次

[Guía completa lista para el campo] Automatización de tests en Laravel y verificación de accesibilidad — Pest/PHPUnit, Feature/E2E, Dusk, axe/Pa11y y configuración de CI

Qué aprenderás (destacados)

  • Cómo elegir entre Pest y PHPUnit; diseñar tests para Feature / Unit / Browser (Dusk)
  • Patrones prácticos para factories, seeding, ejecuciones en paralelo y mocks/fakes (Mail/Queue/Notification/Event)
  • Cómo integrar checks de accesibilidad automatizados (axe-core/Pa11y) con Dusk/CI
  • Validación E2E de navegación, foco, regiones en vivo y operaciones con teclado
  • Cobertura, capturas ante fallos, estrategia de datos de prueba y consejos de rendimiento
  • Una tubería CI/CD de ejemplo usando GitHub Actions

Lectores previstos (¿quién se beneficia?)

  • Ingenieros Laravel de nivel principiante a intermedio: comprende la panorámica del testing y blinda tu proyecto frente a fallos
  • Tech leads / QA: ejecuta E2E y checks de accesibilidad en cada pull request
  • Diseñadores / redactores: incorpora garantías para lectores de pantalla en textos de UI y mensajes de error

Nivel de accesibilidad: ★★★★★

Usa Dusk junto con axe/Pa11y y automatiza la verificación hasta role="status" / aria-live / aria-describedby / traslados de foco / operaciones de teclado. Ante fallos, captura pantallas y logs y guárdalos en CI.


1. Introducción: los tests garantizan “robustez” y “responsabilidad”

Los objetivos del testing son congelar el comportamiento y proporcionar una red de seguridad para cambiar con confianza. Laravel trae testing HTTP, rollbacks de base de datos, diversos fakes y automatización de navegador real (Dusk). Superponer checks de accesibilidad te permite garantizar continuamente que sea visible, legible y operable. Dibuja primero el mapa y luego mejora paso a paso.


2. Diseño general: roles por capa de test

  • Unit: Lógica pura de funciones/clases pequeñas. Evita I/O; corre en milisegundos.
  • Feature (HTTP/DB): Rutas, validación, autorización, escrituras en DB, notificaciones—casos de uso.
  • Browser (Dusk): Interacciones reales de navegador para verificar pantallas, foco, operaciones de teclado y anuncios.
  • Checks de accesibilidad: Integra axe/Pa11y con Dusk/CI para atrapar defectos básicos.
  • Contrato/snapshot: Detecta cambios rompientes en esquemas API (OpenAPI) o textos de UI.

En la práctica, empezar con Feature 70% / Unit 20% / E2E 10% equilibra bien coste e impacto.


3. Configuración: Pest, paralelismo, DB, factories

3.1 Instalar Pest (opcional)

composer require pestphp/pest --dev
php artisan pest:install
  • Escribe tests estilo función bajo tests/Feature y tests/Unit. Puede coexistir con PHPUnit.

3.2 Base de datos de test y ejecuciones en paralelo

php artisan test --parallel
# o
php artisan test --parallel --processes=4
  • Usa el trait RefreshDatabase para rollbacks transaccionales o migraciones.
  • Para paralelo, usa SQLite en memoria o aprovisiona MySQL de prueba por proceso.

3.3 Factories y transiciones de estado

// database/factories/PostFactory.php
$factory->define(Post::class, fn() => [
  'title' => fake()->sentence(),
  'body'  => fake()->paragraph(),
  'status'=> 'draft',
  'published_at' => null,
]);

// Estado
public function published(): static {
  return $this->state(fn() => ['status'=>'published','published_at'=>now()]);
}
  • Usa estados para hacer explícitos borrador/publicado; los tests se leen mejor.

4. Tests Feature: bloquea casos de uso

4.1 Validación y autorización

use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('can create an article', function () {
  $user = User::factory()->create();
  $this->actingAs($user);

  $res = $this->post('/posts', ['title'=>'First Post','body'=>'Body']);
  $res->assertRedirect('/posts');

  $this->assertDatabaseHas('posts', ['title'=>'First Post','user_id'=>$user->id]);
});

test('guest cannot create', function () {
  $this->post('/posts', ['title'=>'x','body'=>'y'])->assertRedirect('/login');
});

4.2 JSON API (Sanctum)

test('can fetch list via API', function () {
  Sanctum::actingAs(User::factory()->create(), ['posts:read']);
  Post::factory()->count(3)->published()->create();

  $this->getJson('/api/v1/posts')
      ->assertOk()
      ->assertJsonCount(3, 'data')
      ->assertJsonStructure(['data'=>[['id','title','author'=>['id','name']]]]);
});

4.3 Verificar efectos colaterales con fakes

test('notification is sent on create', function () {
  Notification::fake();

  $user = User::factory()->create();
  $this->actingAs($user)->post('/posts', ['title'=>'x','body'=>'y']);

  Notification::assertSentTo($user, \App\Notifications\PostPublished::class);
});

4.4 URLs firmadas y rate limiting

test('expired URL is rejected', function () {
  $url = URL::temporarySignedRoute('files.show', now()->subMinute(), ['id'=>1]);
  $this->get($url)->assertForbidden();
});

test('comment posting is throttled', function () {
  RateLimiter::for('comment', fn()=>[Limit::perMinute(5)->by('t')]);
  $this->actingAs(User::factory()->create());

  for($i=0;$i<5;$i++){ $this->post('/comments', ['body'=>'hi']); }
  $this->post('/comments', ['body'=>'hi'])->assertStatus(429);
});

5. Tests de navegador (Dusk): protege la experiencia—operaciones y anuncios

5.1 Configuración

php artisan dusk:install
php artisan dusk
  • Chromedriver incluido. Ejecuta headless en CI.

5.2 Flujo básico

// tests/Browser/RegisterTest.php
public function test_register_flow()
{
    $this->browse(function (Browser $b) {
        $b->visit('/register')
          ->type('#name','Hanako Yamada')
          ->type('#email','hanako@example.com')
          ->type('#password','StrongPassw0rd!')
          ->type('#password_confirmation','StrongPassw0rd!')
          ->check('agree')
          ->press('Sign up')
          ->waitForLocation('/dashboard')
          ->assertSee('Welcome');
    });
}

5.3 Teclado, foco, regiones en vivo

public function test_error_summary_focus_and_readable()
{
    $this->browse(function (Browser $b) {
        $b->visit('/register')
          ->press('Sign up') // enviar vacío
          ->waitFor('#error-title')
          ->assertFocused('#error-title') // el foco va al resumen
          ->assertSeeIn('#error-title','Please review your input.')
          ->assertPresent('[role="alert"]')
          ->assertAttribute('#email','aria-invalid','true');
    });
}
  • En errores, asegúrate de que el foco se mueve al resumen de errores, que existe role="alert" y que los campos tienen aria-invalid.
  • Anuncia progreso/resultados vía role="status" / aria-live. En Dusk, verifica su presencia en el DOM.

5.4 Capturas y logs

public function test_capture_on_failure()
{
    $this->browse(function (Browser $b) {
        $b->visit('/')->screenshot('home'); // storage/screenshots/home.png
    });
}
  • La captura automática ante fallo es el camino de depuración más corto. Guárdala siempre como artefacto en CI.

6. Checks automatizados de accesibilidad: conecta axe/Pa11y con Dusk/CI

6.1 Dusk × axe-core (integración mínima)

  1. npm i -D axe-core
  2. Inyecta axe.min.js en la página desde Dusk y evalúa.
public function test_accessibility_smoke()
{
    $this->browse(function (Browser $b) {
        $b->visit('/register');

        // Inyectar axe
        $axe = file_get_contents(base_path('node_modules/axe-core/axe.min.js'));
        $b->script($axe.'; void(0);');

        // Ejecutar y recuperar resultados
        $results = $b->script('return axe.run(document, { resultTypes: ["violations"] });')[0];

        // Filtro simple para serious/critical
        $violations = collect($results['violations'] ?? [])->filter(function($v){
            return in_array('serious', $v['impact'] ?? []) || in_array('critical', $v['impact'] ?? []);
        });

        if ($violations->isNotEmpty()) {
            logger()->error('axe.violations', ['violations'=>$violations]);
        }

        $this->assertTrue($violations->isEmpty(), 'Accessibility violations detected');
    });
}
  • Empieza con un smoke que detecta problemas severos.
  • Ajusta reglas detalladas en CI con Pa11y para una operación más sencilla.

6.2 Pa11y (CLI) para checks programados en múltiples páginas

  1. npm i -D pa11y pa11y-ci
  2. Crea pa11y-ci.json.
{
  "defaults": {
    "timeout": 30000,
    "standard": "WCAG2AA",
    "wait": 1000,
    "chromeLaunchConfig": { "args": ["--no-sandbox","--disable-dev-shm-usage"] }
  },
  "urls": [
    "http://localhost:8000/",
    "http://localhost:8000/register",
    "http://localhost:8000/login",
    "http://localhost:8000/posts"
  ]
}
  1. En CI, arranca el servidor de Laravel y ejecuta npx pa11y-ci.
  • Enumera páginas en JSON. Para falsos positivos, configura excepciones de reglas selectivamente.
  • Usa Dusk para cubrir experiencia (traslados de foco, actualizaciones en vivo) que Pa11y puede no detectar.

7. Verificando textos de UI, mensajes de error y anuncios

  • Mantén los mensajes de error cortos y específicos; vincula entradas vía aria-describedby.
  • Usa role="status" / role="alert" según prioridad para toasts/banners.
  • No dependas “solo del color”; añade texto/iconos—comprobable vía DOM en Dusk.
  • Para i18n, verifica <html lang> y language-of-parts (según tu política).

8. Diseño de datos de prueba: realista, no sobre-fabricado

  • Los valores de factory deben verse plausibles. Respeta formatos reales para dirección/teléfono/precio.
  • Usa patrón builder para componer precondiciones complejas (p. ej., post publicado + 3 tags + 2 comentarios).
  • Para rendimiento con datos grandes, usa Seeder; en E2E, prefiere IDs fijos para referenciarlos fácil.

9. Aceleraciones: elimina cuellos de botella

  • Paralelismo + SQLite en memoria para arranques rápidos.
  • Stub de APIs externas con Http::fake() para evitar red.
  • Enfoca Dusk en lo esencial; comprime escenarios pesados a 1–3 casos.
  • Estabiliza feeds/listas E2E con mocks de API y centra los checks a11y en la UI.

10. CI/CD: plantilla GitHub Actions (MySQL/Redis/Node en paralelo)

name: test

on: [push, pull_request]

jobs:
  laravel:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8
        env:
          MYSQL_DATABASE: testing
          MYSQL_USER: user
          MYSQL_PASSWORD: secret
          MYSQL_ROOT_PASSWORD: root
        ports: ["3306:3306"]
        options: >-
          --health-cmd="mysqladmin ping -h 127.0.0.1 -u root -proot"
          --health-interval=10s --health-timeout=5s --health-retries=3
      redis:
        image: redis:alpine
        ports: ["6379:6379"]

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: mbstring, pdo_mysql, intl
          coverage: none

      - name: Cache composer
        uses: actions/cache@v4
        with:
          path: vendor
          key: composer-${{ hashFiles('composer.lock') }}

      - run: composer install --no-interaction --prefer-dist

      - name: Setup Node
        uses: actions/setup-node@v4
        with: { node-version: 20, cache: 'npm' }

      - run: npm ci
      - run: npm run build --if-present

      - name: App key & env
        run: |
          cp .env.example .env
          php artisan key:generate
          php artisan migrate --force

      - name: Pest / PHPUnit
        run: php artisan test --parallel --processes=4

      - name: Dusk (headless)
        run: php artisan dusk --env=dusk.local
        env:
          APP_URL: http://127.0.0.1:8000
          DB_DATABASE: testing
          DB_USERNAME: user
          DB_PASSWORD: secret

      - name: Start server for Pa11y
        run: php -S 127.0.0.1:8000 -t public & echo $! > server.pid && sleep 3

      - name: Pa11y CI
        run: npx pa11y-ci

      - name: Upload artifacts (screenshots)
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: dusk-screens
          path: storage/screenshots
  • Sube siempre capturas de fallos.
  • Habilita intl para que pasen tests de i18n/número/fecha.

11. Recetas frecuentes de fake/mock

Mail::fake();
Queue::fake();
Event::fake();
Notification::fake();
Http::fake([
  'https://api.example.com/*' => Http::response(['ok'=>true], 200),
]);
Storage::fake('s3'); // verificar subidas
  • Verifica de forma segura que “lo enviaste”. No olvides afirmar destinatario/asunto/contador.
  • Para imágenes/PDFs, usa dummies y verifica solo que hubo procesamiento.

12. Perspectivas E2E que protegen la “legibilidad”

  • El anillo de foco permanece visible (Tab lo hace evidente).
  • Botones/enlaces son alcanzables solo con teclado.
  • Usa role="alert" para avisos críticos; role="status" para regulares; aria-live por defecto polite.
  • Las imágenes tienen alt; las decorativas usan alt="".
  • Elementos muy animados respetan prefers-reduced-motion.
  • No dependas solo del color; usa texto/iconos/bordes como señales redundantes.

En Dusk, apóyate en aserciones del DOM y, cuando sea necesario, verifica la presencia de estilos (evita depender de valores de color exactos).


13. Muestras representativas (extractos)

13.1 a11y para errores de validación

public function test_validation_errors_have_describedby()
{
    $this->browse(function (Browser $b) {
        $b->visit('/register')
          ->press('Sign up')
          ->waitFor('#error-title')
          ->assertPresent('#email-error')
          ->assertAttribute('#email','aria-describedby', fn($v) => str_contains($v,'email-error'));
    });
}

13.2 Anuncio en toast

public function test_toast_announces_status()
{
    $this->browse(function (Browser $b) {
        $b->visit('/profile')
          ->press('Save')
          ->waitFor('[role="status"]')
          ->assertSeeIn('[role="status"]','Saved');
    });
}

13.3 Texto alternativo

test('images have alt text', function () {
    $html = $this->get('/')->getContent();
    expect($html)->toContain('alt=');
});

14. Escollos y soluciones

  • Intentarlo todo solo con Dusk → lento y frágil. Haz Feature el núcleo y cubre experiencias clave con Dusk.
  • Confiar solo en checks a11y automáticos → traslados de foco y actualizaciones en vivo son difíciles de detectar. Complementa con E2E.
  • Datos de prueba aleatorios cada vez → difícil reproducir fallos. Estabiliza con IDs fijos o seeds.
  • Olvidar restaurar fakes → Prefiere setup/teardown compartidos (hooks de Pest o setUp/tearDown de PHPUnit) frente a “un test, un assert”.
  • Desajustes Chrome/DB en CI → Incluye siempre health checks; usa --disable-dev-shm-usage para evitar problemas de memoria.
  • Falta de capturas ante fallos → Coste de debug se dispara. El almacenamiento de artefactos debe ser estándar.

15. Lista de verificación (lista para entregar)

Estrategia

  • [ ] Haz Feature (HTTP/DB) el núcleo; Unit para lógica; E2E solo lo esencial
  • [ ] Ejecuta Pest/Feature + Dusk + a11y (Pa11y/axe) en cada PR

Datos

  • [ ] Diseño de estados en factories (draft / published, etc.)
  • [ ] Escenarios reproducibles con seeds e IDs fijos

Accesibilidad

  • [ ] El foco va al resumen de errores; role="alert" / aria-invalid / aria-describedby
  • [ ] Anuncia progreso/finalización con role="status" / aria-live
  • [ ] alt en imágenes, decorativas con alt="", diseños no dependientes del color
  • [ ] Totalmente operable por teclado

Checks automatizados

  • [ ] Smoke de Dusk + axe
  • [ ] CI ejecuta lista de URLs en Pa11y
  • [ ] Define umbrales de fallo por severidad

Velocidad/estabilidad

  • [ ] Paralelismo; SQLite en memoria (o múltiples DBs)
  • [ ] Fakes para Http/Mail/Queue/Storage
  • [ ] Guarda capturas/logs de fallos como artefactos

CI

  • [ ] Chrome headless
  • [ ] Health checks para servicios (DB/Redis)
  • [ ] Cache de dependencias (composer/npm)

16. Resumen

  • Asigna roles claros a Pest/PHPUnit, Feature y Dusk para hacer seguros los cambios diarios.
  • Introduce axe/Pa11y para prevenir automáticamente defectos a11y comunes.
  • Verifica la esencia de la experiencia—traslados de foco, regiones en vivo, teclado—vía E2E.
  • Mantén capturas y logs en CI para que los fallos te enseñen.
  • Comienza con un smoke; amplía páginas y reglas gradualmente según resultados.

Espero que tu proyecto crezca robusto y usable por todas las personas. Toma esta plantilla como estándar por defecto del equipo si te encaja.


Calificación de accesibilidad de este contenido: ★★★★★ (muy alta)

  • Garantías continuas para anuncios/foco/independencia del color mediante checks a11y automatizados y E2E.
  • Incluye responsabilidad operativa (capturas/logs/IDs de petición) ante fallos.
  • Mejoras futuras: testing exploratorio manual regular con lectores de pantalla principales (NVDA/JAWS/VoiceOver/TalkBack).

Enlaces de referencia

por greeden

Deja una respuesta

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

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)