common.skipToContent

Calidad sin secretos

Este sitio se prueba a sí mismo.

Cada push ejecuta la suite unitaria, una suite de flujos en navegador real y un umbral de cobertura antes de que el build llegue a producción. Lo que ves aquí es el resultado real de la última ejecución, más una suite que puedes ejecutar en tu propio navegador ahora mismo.

Ejecútalo tú mismo

Catorce comprobaciones se ejecutan en esta misma página: DOM, accesibilidad, i18n, red y tiempos. Nada se simula y nada viene de la caché.

Test Runner en el Navegador

Ejecuta pruebas de humo, estructura DOM, accesibilidad y rendimiento directamente en tu navegador.

14Total de Pruebas
0Aprobadas
0Fallidas
Duración
Listo para ejecutar

Lo que reportó el CI

Números directos de GitHub Actions. Los resultados de Jest salen con el build; el resumen de Puppeteer se obtiene en vivo de un Gist que el pipeline actualiza en cada ejecución.

Pruebas Jest

Cargando resultados Jest...

Pruebas E2E

Cargando resultados E2E...

Cobertura de Código

Cargando cobertura...

Cómo se prueba

Cuatro capas, cuatro herramientas. Cada fragmento viene directo de este repositorio.

  1. 01Unitario & componente

    El menú se cierra cuando eliges un destino

    Jest + Testing Library

    Los componentes se renderizan en jsdom y se manejan como lo haría un visitante: por etiqueta, rol y clic. Sin detalles de implementación, sin snapshots.

    Qué demuestra

    • La navegación móvil abre y cierra mediante su toggle accesible
    • aria-expanded nunca se desvía del estado visible
    • Elegir una ruta cierra el menú en vez de dejarlo abierto
    src/components/Navbar.test.jsxjsx · fragmento
    test('closes mobile menu when a nav link is clicked', async () => {
      const { container } = render(
        <MemoryRouter>
          <Navbar />
        </MemoryRouter>
      );
    
      const toggle = screen.getByLabelText('Toggle navigation');
      const menu = container.querySelector('#mobileMenu');
    
      fireEvent.click(toggle);
      expect(toggle).toHaveAttribute('aria-expanded', 'true');
      expect(menu).toHaveClass('active');
    
      fireEvent.click(screen.getByText(/Personal Projects/i));
    
      await waitFor(() => {
        expect(toggle).toHaveAttribute('aria-expanded', 'false');
        expect(menu).not.toHaveClass('active');
      });
    });
  2. 02Artefacto de build & seguridad

    La CSP publicada es la que el build prometió

    Jest sobre el build de producción

    Algunas pruebas nunca tocan un componente. Estas leen la carpeta dist generada y verifican las cabeceras de seguridad que Cloudflare servirá.

    Qué demuestra

    • script-src no lleva unsafe-inline
    • El hash del JSON-LD en la cabecera coincide byte a byte con el script
    • Una regresión en el pipeline de build falla antes de publicarse
    tests/jest/csp-validation.test.jsjavascript · fragmento
    it('should NOT contain unsafe-inline in script-src', () => {
      const headers = fs.readFileSync(headersPath, 'utf-8');
      const cspLine = headers.match(/Content-Security-Policy:.*$/m);
      expect(cspLine).toBeTruthy();
    
      const scriptSrc = cspLine[0].match(/script-src ([^;]+)/)[1];
      expect(scriptSrc).not.toContain("'unsafe-inline'");
    });
    
    it('should generate correct SHA-256 hash for JSON-LD script', () => {
      const html = fs.readFileSync(htmlPath, 'utf-8');
      const [, scriptContent] = html.match(
        /<script type="application\/ld\+json">([\s\S]*?)<\/script>/
      );
    
      const calculatedHash = crypto
        .createHash('sha256')
        .update(scriptContent)
        .digest('base64');
      const hashes = JSON.parse(fs.readFileSync(hashesPath, 'utf-8'));
    
      expect(hashes.jsonLd).toBe(`'sha256-${calculatedHash}'`);
    });
  3. 03Extremo a extremo

    Aceptar cookies oculta el banner y persiste

    Puppeteer + Chromium headless

    Cada flujo recibe un contexto de navegador nuevo y una lista de pasos con nombre, así que un fallo señala el paso exacto y el reporte se lee como un guion.

    Qué demuestra

    • El bundle real de producción funciona en un navegador real
    • El estado que debe sobrevivir a una recarga realmente sobrevive
    • Cada paso se cronometra y aparece en el reporte E2E de arriba
    tests/e2e/run-e2e.jsjavascript · fragmento
    {
      name: 'Cookie consent accept hides banner and stores consent',
      run: async (page, step) => {
        await step('Navigate to home page', () =>
          page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: NAV_TIMEOUT })
        );
        await step('Cookie banner is shown', () =>
          page.waitForSelector('#cookieConsentBanner', { timeout: NAV_TIMEOUT })
        );
        await step('Click "Accept all cookies"', () => page.click('#acceptCookies'));
        await step('Banner is dismissed', () =>
          page.waitForSelector('#cookieConsentBanner', { hidden: true, timeout: NAV_TIMEOUT })
        );
        await step('Consent cookie is stored', async () => {
          const cookie = await page.evaluate(() => document.cookie);
          assert(/cookie_consent=accepted/.test(cookie), `Consent cookie not set: "${cookie}"`);
        });
      },
    },
  4. 04En tu navegador

    Todos los enlaces externos se abren de forma segura

    Runner hecho a mano, cero dependencias

    El runner al inicio de esta página es un pequeño motor de comprobaciones asíncronas contra el DOM en vivo. Esta es la razón por la que puedes confiar en los enlaces de este sitio.

    Qué demuestra

    • target="_blank" siempre viaja con rel="noopener noreferrer"
    • La comprobación corre en tu dispositivo, contra la página que estás leyendo
    • Los fallos nombran los hrefs problemáticos, no solo un conteo
    src/utils/inBrowserTestRunner.jsjavascript · fragmento
    {
      id: 'dom-safe-links',
      nameKey: 'testDashboard.runner.tests.safeLinks.name',
      run: async () => {
        const externalLinks = Array.from(document.querySelectorAll('a[target="_blank"]'));
        const insecureLinks = externalLinks.filter((link) => {
          const rel = (link.getAttribute('rel') || '').toLowerCase();
          return !rel.includes('noopener') && !rel.includes('noreferrer');
        });
    
        if (insecureLinks.length > 0) {
          const hrefs = insecureLinks
            .slice(0, 3)
            .map((l) => l.getAttribute('href'))
            .join(', ');
          throw new Error(
            `Found ${insecureLinks.length} external link(s) without rel="noopener noreferrer": ${hrefs}`
          );
        }
        return {
          message: `Verified ${externalLinks.length} external link(s) have secure rel attributes.`,
        };
      },
    },

Reportes

Reportes HTML estáticos, regenerados en cada deploy.