AGENTIC QA · MINI-CURSO · CI PARA TESTING

Parte II · Arquitectura

ci-pipelines

Por qué cuatro suites y no una. Qué gatea cada una y cuándo dispara. regression.yml job por job. Dónde aparecen los resultados y cómo vuelven al TMS. Cómo se conectará todo esto al pipeline de la app. Y cómo escribís el tuyo.

Capítulos 6-10 estrategia · regression · resultados · dev pipeline · tu workflow Cap 9 marcado no implementado aún Requiere la Parte I

→ avanzar · O overview · N notas · F pantalla completa · clic en los bloques interactivos

Parte II. Asume la Parte I (vocabulario, triggers, if, artifacts). Cap 9 es prospectivo y está etiquetado como no implementado en cada slide.

mapa: haz clic en un capítulo para saltar

Cinco capítulos más, del archivo al sistema

La Parte I te enseñó a leer un workflow. Esta parte explica por qué están escritos así, qué producen y cómo encajan con el resto del equipo.

CAP 06

La estrategia: cuatro suites, cuatro preguntas

Build · Smoke · Sanity · Regression. Qué gatea cada una, cuándo dispara, por qué no se mezclan.

→ slide 3
CAP 07

regression.yml a fondo

Cuatro jobs, merge de resultados, publicación Allure con historia, una noche completa en línea de tiempo.

→ slide 10
CAP 08

Resultados y write-back al TMS

Dónde miro qué. El RTR/STR: execution_key, AUTO_SYNC, TMS_PROVIDER. La matriz de decisión real.

→ slide 16
CAP 09 · no implementado

Conectar con el pipeline de desarrollo

repository_dispatch · workflow_call · deployment_status. Que producción espere a las suites.

→ slide 21
CAP 10

Escribe tu propio workflow

Constructor interactivo sobre nuestra plantilla + checklist de calidad antes del PR.

→ slide 26

El veredicto GO / CAUTION / NO-GO y la clasificación de fallos (REGRESSION vs FLAKY vs ENVIRONMENT) no se repiten aquí: viven en regression-testing/how-it-works.es.html, que arranca justo donde este deck termina.

Índice clicable de la Parte II. Cap 9 marcado distinto (dashed) porque es prospectivo.

Capítulo 06

La estrategia: cuatro suites, cuatro preguntas

Un solo workflow que corra «todos los tests» contesta todo tarde y mal. Cada suite existe porque hay una pregunta distinta, con una urgencia distinta y un costo distinto de respuesta.

Build ≠ Smoke ≠ Sanity ≠ Regressiontrigger, alcance, duración y qué gateacuándo correr cuál
Cap 6. Fuente de la doctrina smoke/sanity/regression: docs + investigación (drizz.dev, getautonoma, harness) + la tabla de /regression-testing §When to run each suite.

una tabla que vale por el capítulo

Cada workflow contesta una pregunta y gatea una decisión

SuitePreguntaTriggerAlcanceDura (orden de magnitud)Gatea
build.ymlEl framework compila, pasa lint y su documentación sigue consistente?pull_request → maintypes · lint · docs gates · contratos de documentación · evals · playwright test --list. Cero tests ejecutados.~3 minel merge del PR
unit-tests.ymlLas herramientas del propio repo (cli/, scripts/) siguen andando?push (cualquier rama) · PR de fork · manualbun test (sin las suites de Playwright) + los gates de repo que build.yml no corre. Inerte fuera del repo del boilerplate.minutoscada push, también el directo a main
smoke.ymlLa app está viva tras el deploy?cron 02:00 · manualsolo @critical (proyectos smoke-ui + smoke-api)2-5 min«seguimos o avisamos?» post-deploy
sanity.ymlFunciona ESTE cambio / feature?solo manual, con inputsun archivo, un --grep, o e2e/integration1-10 minvalidar un test nuevo en CI antes del PR
regression.ymlSe rompió algo más?cron 00:00 · manualintegration completo → e2e completo20-60 minel release: GO / CAUTION / NO-GO
smokepocos tests, siempre, rápido
sanityun recorte, a demanda
regressiontodo, cada noche
buildel framework mismo, en cada PR

La regla que nace de la tabla

Smoke habla del build de la app; sanity habla del cambio; regression habla del producto entero. Tres pass-rates distintos con tres SLOs distintos: nunca los sumes en un número (regla R6 de /regression-testing). Y build no habla de la app en absoluto: habla de nosotros.

La secuencia clásica de la industria es smoke → sanity → regression como gates sucesivos: si smoke falla, no gastás la regresión. En este repo los gates son temporales (cron) y manuales; conectarlos al deploy de la app es el Capítulo 9.

Tabla maestra. Pirámide de suites. Regla R6: no mezclar pass-rates. Referencia a la secuencia smoke→sanity→regression de la industria.

suite 0: no prueba la app, prueba el repo de pruebas

build.yml: el gate del Pull Request

extracto de .github/workflows/build.ymlreal
      - name: Validate env schema (varlock)
        uses: dmno-dev/varlock-action@v1.0.5
        with:
          fail-on-error: true
          show-summary: true
        # First gate, before any dependency is installed: loads .env.schema +
        # .env.core.schema against the job's env block above and writes a
        # REDACTED summary to the job page. The only required item is TEST_ENV
        # (it has a default); project credentials are optional typed examples,
        # so a PR from a fork, which has no secrets, passes here and at
        # test:env:check below. …
      # … Setup Bun · Install Dependencies
      - name: Validate Test Environment Variables
        run: bun run test:env:check
      - name: TypeScript Check
        run: bun run types:check
      - name: ESLint Check
        run: bun run lint:check
      - name: Docs Gates
        run: |
          bun run docs:check
          bun run skills:check
      - name: Documentation Contracts
        run: bun scripts/lint-doc-contracts.ts --base "$BASE_SHA" --head "$HEAD_SHA"
      - name: Skill Activation Evals Check
        run: bun run skills:evals
      - name: Playwright Compile Check
        run: bunx playwright test --list
  • Contesta «este PR rompe el framework?». Un test que no compila no falla: no existe. Por eso playwright test --list es el step clave: compila cada spec sin ejecutarlo y sin credenciales de app.
  • Mismo orden que la Rule #7 del repo: tests (compilación) → types → lint. Lo que corrés en local antes de commitear, corre aquí antes de mergear. Sin sorpresas.
  • El step Validate env schema (varlock) va primero, antes de bun install: valida que el bloque env del job cumpla .env.schema y deja un summary redactado en la página del job. Lo único requerido es TEST_ENV (tiene default); el par de test-user es opcional, así que un PR de fork, que no tiene secrets, pasa aquí y en test:env:check.
  • Los gates de documentación también bloquean el PR. Docs Gates corre docs:check y skills:check (links y paths muertos, scripts citados que package.json no declara, Rule #17, router de skills y capa de instrucciones). Documentation Contracts falla el PR si cambia una región marcada con LINT.IfChange sin que cambien las páginas que su LINT.ThenChange nombra (o sin un trailer Docs-Checked: en un commit del PR). Por eso el checkout de build.yml trae el historial completo (fetch-depth: 0).
  • Cancelable (cancel-in-progress: true): un push nuevo invalida al anterior y nada quedó publicado.
  • El timeout más corto de las suites (ver timeout-minutes en build.yml): si tarda más, algo está mal en la máquina, no en el código.

Lo que NO hace, a propósito

No corre ni un test contra staging. Correr la suite en cada PR costaría minutos y credenciales por cada push (incluidos forks, que no las tienen) y mezclaría dos preguntas: «compila?» con «la app funciona?». La segunda se contesta con sanity, a demanda.

build.yml: gate de PR del framework. varlock primero, tests→types→lint, gates de documentación, playwright --list. No corre tests contra la app, con justificación.

suite 1: la pregunta más barata, la más frecuente

smoke.yml: la app está viva?

extracto de .github/workflows/smoke.yml step Run Smoke Tests (@critical) · playwright.config.ts proyectos smoke-*real
# smoke.yml
      - name: Run Smoke Tests (@critical) on ${{ env.TEST_ENV }}
        run: bun run test:smoke

# package.json
"test:smoke": "… playwright test --project=smoke-ui --project=smoke-api"

# playwright.config.ts (comentario del bloque smoke, resumido)
# Smoke Tests - @critical tagged tests, ONE PROJECT PER SURFACE
# smoke-ui  == e2e + @critical
# smoke-api == integration + @critical
{ name: 'smoke-ui',  grep: /@critical/, … }
{ name: 'smoke-api', grep: /@critical/, … }

Por qué dos proyectos y no uno

El comentario del config lo cuenta: un solo proyecto smoke que abarcara e2e + integration tenía que elegir UN bloque use, y eligió el de UI, que le pasaba una sesión de navegador (con cookie) a los tests de API. Un test que esperaba 401 sin token recibía 200. Un proyecto no puede cargar el auth de dos superficies. Se partió en dos que reflejan exactamente a sus suites completas más el grep.

  • El tag decide el alcance. Un test entra en smoke por llevar @critical en su título, no por estar en una carpeta. El mismo tag alimenta el label suite de Allure: una sola taxonomía para CI y reporte.
  • Un job de suite, un job de reporte. Sin integración-primero: los críticos de API y UI corren juntos porque el objetivo es velocidad, no diagnóstico.
  • Cron a las 02:00 UTC: después del deploy nocturno. También a mano tras cualquier deploy: «seguimos?».
  • Nunca escribe en la ejecución de regresión. El secret STP_EXECUTION_KEY no se lee aquí (bloque env del workflow): un smoke que importara al RTR voltearía Test Runs que pertenecen a un veredicto de regresión completo. Solo importa si le pasás una execution_key explícita.
  • Criterio de tamaño (industria): smoke debería ser menos del 5% de la regresión y terminar en minutos. Si tu smoke tarda 20, es una regresión disfrazada.
smoke.yml: @critical, dos proyectos por bug real de auth compartido (comentario del config), cron 02:00, no importa al RTR sin key explícita.

suite 2: la única que siempre necesita que le digas qué

sanity.yml: funciona ESTE cambio?

extracto de .github/workflows/sanity.yml · step Run Sanity Testsreal
      - name: Run Sanity Tests on ${{ env.TEST_ENV }}
        shell: bash
        env:
          GREP_INPUT: ${{ github.event.inputs.grep }}
          TEST_FILE: ${{ github.event.inputs.test_file }}
          TEST_TYPE: ${{ github.event.inputs.test_type }}
        run: |
          grep_args=()
          if [ -n "$GREP_INPUT" ]; then
            grep_args=(--grep "$GREP_INPUT")
          fi
          # Determine which test command to run
          if [ -n "$TEST_FILE" ]; then
            echo "Running specific test file: $TEST_FILE"
            bun run test "$TEST_FILE" "${grep_args[@]}"
          elif [ "$TEST_TYPE" == "e2e" ]; then
            echo "Running E2E tests"
            bun run test:e2e "${grep_args[@]}"
          elif [ "$TEST_TYPE" == "integration" ]; then
            echo "Running Integration tests"
            bun run test:integration "${grep_args[@]}"
          else
            echo "Running all tests"
            bun run test "${grep_args[@]}"
          fi
InputTipoEfecto
test_typechoice all / e2e / integrationelige el script de package.json
grepstring--grep "…": por tag o por texto del título
test_filestringuna ruta: gana sobre test_type
custom_allure_urlstring, default sanitycarpeta del reporte: /{env}/{custom}/
  • Precedencia: archivo > tipo. Y el grep se suma a cualquiera. Pasar grep y test_file a la vez funciona, pero el skill lo desaconseja: elegí uno para que el reporte diga qué probaste.
  • Su uso canónico: validar en CI un test que acabás de escribir, antes del PR. gh workflow run sanity.yml -f environment=staging -f test_file=tests/e2e/auth/login.test.ts.

Nota de seguridad (corregida en el workflow)

Este step interpolaba grep y test_file directamente dentro de run: y los pasaba por eval. Un input es texto controlado por quien dispara, y la guía de seguridad de GitHub pide pasarlo primero a una variable de env: y leerlo entre comillas. Eso es lo que ves arriba: los tres inputs entran por env:, se leen como "$GREP_INPUT" y "$TEST_FILE", y el eval desapareció: el comando se arma como un array de bash. Mismas cuatro ramas, mismos mensajes. Al escribir el tuyo, seguí este patrón (Cap 10).

sanity.yml: inputs y precedencia; uso canónico; nota de hardening ya resuelta: los inputs pasaron a env:, se leen entre comillas y el eval se quitó (array de bash). Mostrar el listado como el ejemplo del patrón seguro.

decisor: elige la situación y mira qué suite corresponde

Qué suite corro? La situación decide

Acabo de escribir un testy quiero verlo pasar en CI antes del PR
Devs desplegaron a staging«podemos seguir probando?»
Mañana es el releasenecesito el veredicto
Un bug de checkout se arreglóquiero verificar solo esa área
Abrí un PR al repo de testsrefactor de una Page
Una regresión falló por redquiero re-correr sin nuevo RTR
build.yml
solo, automático en el PR
No hacés nada: se dispara solo. Si queda rojo, el PR no se mergea.
smoke.yml
@critical · 2-5 min
gh workflow run smoke.yml -f environment=staging. Verde = seguimos. Rojo = avisar antes de que el equipo pierda la tarde.
sanity.yml
un archivo o un grep
-f test_file=… para tu test nuevo; -f grep="@checkout" para el área del fix. Pequeño, dirigido, con reporte propio.
regression.yml
todo · 20-60 min
Antes del release: /regression-testing crea el RTR y dispara con -f execution_key=<RTR>. Un re-run por ENVIRONMENT antes del veredicto reutiliza el MISMO RTR.
Decisor de suite: 6 situaciones → 4 suites, con el comando gh. Escenario 6 refuerza la regla de un RTR por veredicto.

quiz 06: estrategia

Qué suite y por qué?

Q1El PM pide «un solo número de calidad» sumando los pass-rates de smoke (98%) y regression (91%). Qué respondés?
A94.5%, promedio simple
BSon suites con preguntas y SLOs distintos: se reportan por separado; el veredicto de release sale solo de regression
C98%: smoke es la que importa para el release
Por separado. Smoke mide «está viva?» sobre 5% de los tests; regression mide «se rompió algo?» sobre todo. Promediar esconde el 91%, que es el que decide (regla R6: never mix smoke + regression into one pass-rate). El deck how-it-works cuenta cómo el 91% se convierte en CAUTION.
Q2Por qué build.yml corre playwright test --list en vez de playwright test?
APara ahorrar minutos: es un atajo temporal
BPorque su pregunta es «compila la suite?», no «funciona la app?»: lista = compila todo sin ejecutar ni necesitar la app
CPorque los PR no tienen browsers instalados
Pregunta distinta. --list carga y compila cada spec: si un import está roto o un tipo no cierra, falla ahí, sin credenciales de app ni browsers. Ejecutar la suite en cada PR mezclaría el estado del framework con el estado de staging, y haría que un PR de fork (sin secrets) fallara por motivos que no son suyos.
Q1: no mezclar pass-rates (R6). Q2: --list = compilar sin ejecutar; separación de preguntas.

Capítulo 07

regression.yml a fondo

Cuatro jobs en regression.yml, y cada decisión con su comentario. Es el workflow mejor documentado del repo: vamos a leerlo como lo leería quien tiene que mantenerlo.

por qué Integration antes que E2Emerge de resultadospublicación Allure con historiauna noche en línea de tiempo
Cap 7. regression.yml completo.

simulador: la misma animación de la Parte I, ahora con el porqué de cada job

Cuatro jobs: dos que prueban, dos que cuentan

needs needs: [int, e2e]
IntegrationTests
bun run test:integration
queued
E2ETests
bun run test:e2e · needs int
queued
allure-report
fusiona + publica
queued
XrayImport
atc_results → RTR
queued
JobSubeCorre si…
IntegrationTestsintegration-allure-results · integration-test-resultssiempre (raíz del grafo)
E2ETestse2e-allure-results · e2e-test-results · e2e-playwright-report · e2e-failure-evidenceIntegration terminó bien
allure-reportmerged-allure-results-{env}ambos terminaron (como sea) y generate_allure != 'false'
XrayImportnada: escribe en Xrayambos terminaron (como sea) y vars.TMS_PROVIDER es xray

Dos que prueban, dos que cuentan

Los dos primeros ejecutan Playwright y suben evidencia. Los dos últimos no prueban nada: consumen los artifacts para producir el reporte (humanos) y la importación (TMS). Separarlos permite que corran en paralelo, que corran aunque la suite haya fallado, y que cada uno se pueda apagar solo: generate_allure=false apaga uno; TMS_PROVIDER=jira apaga el otro.

Grafo animado reutilizado. Tabla: qué sube cada job y cuándo corre. Dos que prueban, dos que cuentan.

un reporte por run, no uno por job

Merge: dos artifacts entran, un reporte sale

extracto de .github/workflows/regression.yml · steps Download … Allure Results, Merge Allure Resultsreal
      - name: Download Integration Allure Results
        uses: actions/download-artifact@v4
        with:
          name: integration-allure-results
          path: regression-artifacts/integration-allure-results
        continue-on-error: true
      - name: Download E2E Allure Results
…        (idéntico, e2e-allure-results)
      - name: Merge Allure Results
        id: merge_results
        run: |
          mkdir -p merged-allure-results
          if [ -d "regression-artifacts/integration-allure-results" ] && [ -n "$(ls -A …)" ]; then
            cp -r regression-artifacts/integration-allure-results/. merged-allure-results/
…          (lo mismo para e2e)
      - name: Check Allure Results
        id: check_allure
            echo "has_results=true" >> $GITHUB_OUTPUT
      - name: Upload Merged Allure Results
        if: steps.check_allure.outputs.has_results == 'true'
          name: merged-allure-results-${{ env.TEST_ENV }}
  • Allure results son archivos sueltos (un JSON por test, más attachments). Fusionar dos carpetas es copiar todo en una: cp -r. Allure genera después UN reporte con las dos suites como suites distintas dentro.
  • continue-on-error en cada descarga: si E2E se saltó (porque Integration falló), su artifact no existe y la descarga fallaría. El reporte igual debe salir, con lo que haya.
  • El merged se vuelve a subir como artifact propio (step Upload Merged Allure Results). Es el que el agente baja en /regression-testing Fase 2 para analizar: gh run download <RUN_ID> -n merged-allure-results-staging.
  • Una alternativa nativa: download-artifact@v4 con pattern: '*-allure-results' + merge-multiple: true hace el merge en un step. El repo lo hace a mano para poder loguear qué faltó.

Por qué un solo reporte

El veredicto es sobre el producto, no sobre un job. Un PM que abre dos reportes tiene que sumar mentalmente; uno solo con la pestaña Graphs (pirámide de testing, severidades, tendencia) contesta solo. Y la historia de tendencias (history.jsonl) se lleva por {env}/{suite}: dos reportes serían dos historias.

Merge: cp -r de dos carpetas de allure-results; continue-on-error en descargas; merged re-subido para el agente; alternativa pattern+merge-multiple.

del artifact a una URL con historia

Publicar: gh-pages, tendencias y la URL predecible

extracto de .github/workflows/regression.yml · steps Get Allure History, Build & Publish Allure Reportreal
      - name: Get Allure History
        uses: actions/checkout@v4
        with:
          ref: gh-pages                 # la rama publicada, no main
          path: gh-pages
          fetch-depth: 1
        continue-on-error: true        # primer run: la rama no existe
      - name: Build & Publish Allure Report (Allure 3)
        if: steps.check_allure.outputs.has_results == 'true'
        env:
          PORTAL_URL: ${{ secrets.PORTAL_URL }}   # + PORTAL_*, R2_* (modo privado)
        run: |
          if [ -n "$PORTAL_URL" ]; then
            bun scripts/ci/publish-allure-portal.ts …      # portal privado (R2)
          else
            bun scripts/ci/publish-allure-pages.ts \
              --env "${{ env.TEST_ENV }}" --suite regression \
              --run "${{ github.run_number }}" \
              --results merged-allure-results --pages gh-pages --keep 10
            echo "## Allure Report" >> $GITHUB_STEP_SUMMARY
            echo "View the report: https://${{ github.repository_owner }}.github.io/${{ … }}/${{ env.TEST_ENV }}/regression/" >> $GITHUB_STEP_SUMMARY
          fi
lo que publish-allure-pages.ts deja en gh-pagesreal
{env}/{suite}/
  index.html      → redirect relativo al último run
  history.jsonl   → tendencia Allure 3 (restaurada antes de
                    generar, guardada después)
  {runNumber}/    → un reporte completo por run
                    (se conservan los últimos 10, --keep 10)
  • Mismo generador que en local. El job hace checkout completo del repo para usar allurerc.mjs y el devDep allure: lo que ves en bun allure:open es byte a byte lo publicado. Un solo plugin (Awesome) → el index.html ES el reporte, con Report | Graphs | Timeline.
  • La historia vive en gh-pages. Sin el step Get Allure History cada reporte nacería sin pasado y los gráficos de tendencia estarían vacíos. Se restaura antes y se escribe después.
  • URL predecible: https://upex-galaxy.github.io/agentic-qa-boilerplate/staging/regression/. Solo existe si el job allure-report terminó: si falló, analizás desde el artifact.
  • Modo privado opt-in: con el secret PORTAL_URL el reporte va a un portal con login (R2 + Vercel) en vez de a Pages. Runbook: regression-testing/references/private-hosting-setup.md.
  • gh-pages engorda (un commit por deploy, blobs de screenshots para siempre): pages-squash.yml la aplana a un commit el 1° de cada mes.
Publicación: checkout gh-pages para historia, script propio con allurerc.mjs, --keep 10, URL predecible, portal privado opt-in, squash mensual.

línea de tiempo: pulsa ▶

Una noche de regresión, de 00:00 a 00:35

tiempos ilustrativos dentro de los timeout-minutes de cada job
int
IntegrationTests · 6 min
e2e
E2ETests · 25 min
allure
4 min
xray
3 min
00:0000:1000:2000:3000:35

Lo que queda al final

los artifacts de la tabla del Cap 7, con su retención · el reporte en /staging/regression/<run>/ (últimos 10) · el summary del run con el link · los Test Runs actualizados en el RTR de Xray.

Lo que NO pasa solo

Nadie clasifica los fallos ni emite GO / NO-GO: eso lo hace el agente con /regression-testing, leyendo el merged y el --log-failed. El CI produce evidencia; el veredicto es humano-asistido.

Lo que también podría pasar

El job Slack está escrito y comentado al final del archivo: un webhook con resultado por job y botones al run y al reporte. Descomentarlo es una decisión de equipo, no un cambio técnico.

Timeline animado. Cierre del Cap 7: qué queda, qué no pasa solo (veredicto), qué podría (Slack comentado).

quiz 07: regression.yml

Leíste el archivo o solo el deck?

Q3IntegrationTests falla a las 00:06. Qué pasa con el reporte Allure de esa noche?
ANo se genera: allure-report depende de los dos jobs de tests
BSe genera solo con los resultados de Integration: allure-report tiene if: always() y la descarga de E2E lleva continue-on-error
CSe genera con Integration y E2E: E2E corre igual
Parcial pero existe. E2ETests se salta (su needs falló y no tiene always()). allure-report sí arranca (if: always() && …), la descarga de e2e-allure-results falla sin romper el job, el merge copia lo que hay y publica. El agente verá una suite sola y sabrá por qué.
Q4Es el primer run de la historia del repo. El step «Get Allure History» intenta hacer checkout de gh-pages, que no existe. Qué pasa?
AEl job falla: no se puede publicar sin la rama
BEl step falla pero el job sigue (continue-on-error: true); el reporte nace sin historia y el script crea la rama
CGitHub crea la rama automáticamente antes del checkout
Sigue sin historia. El step Get Allure History lleva continue-on-error justamente para el primer run. Los gráficos de tendencia aparecen del segundo reporte en adelante, cuando ya hay un history.jsonl que restaurar. Es el mismo mecanismo que en local con .allure/history.jsonl.
Q3: reporte parcial gracias a always() + continue-on-error. Q4: primer run sin gh-pages, continue-on-error.

Capítulo 08

Del runner al TMS: resultados y write-back

Dónde aparece cada cosa cuando el run termina, y cómo los resultados vuelven a Jira/Xray sin que nadie los copie a mano: el RTR, la execution_key y la matriz de decisión que gobierna cada import.

Actions UI · artifacts · Allure · Playwright report · tracesRTR / STR · execution_key · AUTO_SYNC · TMS_PROVIDER
Cap 8. El write-back tal como quedó tras la RTR ladder (ADR-0002).

el mapa de los resultados

Terminó el run. Dónde miro qué?

Quiero saber…DóndeCómo llegoQué veo
Pasó o falló, y qué job?GitHub → Actions → el rungh run view <RUN_ID> --json status,conclusion,jobsgrafo de jobs con ✓/✗/⊘, duración por job, el summary con el link al reporte
Qué línea falló?Logs del jobgh run view <RUN_ID> --log-failedsolo los steps rojos (el --log completo puede pesar >50 MB)
Qué tests, con qué tendencia?Reporte Allure publicadohttps://…github.io/…/{env}/{suite}/Report (drill-down, tags) · Graphs (status, severidades, pirámide) · Timeline
Qué pasó en pantalla?Playwright HTML report + tracesgh run download <RUN_ID> -n e2e-playwright-reporttrace viewer: DOM, red, consola paso a paso. trace: 'retain-on-failure' en el config
Evidencia para el bugartifact de fallogh run download <RUN_ID> -n e2e-failure-evidence -D ./analysis/evidence/screenshots, traces, videos (si video_record=true)
Los datos crudos para analizarartifact mergedgh run download <RUN_ID> -n merged-allure-results-stagingun JSON por test: status, message, labels (testId = ATC, suite, severity)
Qué quedó en el TMS?Jira / Xrayel RTR (Test Execution) del runun Test Run por ATC con PASSED / FAILED, importado desde reports/atc_results.json con la clave de cada Test

Orden de verdad para el análisis

/regression-testing fija la prioridad: Allure results JSON > Playwright results.json > logs. Los logs son el último recurso, no el primero. Y el label suite de Allure deriva del tag Playwright que también eligió el alcance en CI: una sola taxonomía, cero reconciliación.

Tabla "dónde miro qué" con comandos gh reales. Prioridad Allure > results.json > logs.

un RTR por veredicto

Write-back: el run record nace antes que el run

1 · /regression-testing crea el RTR
Test Execution RTR: staging-2026-09-23: Regression Testing, padre «QA Test Artifacts», testPlan → RTP, environment + assignee al crear.
ANTES del trigger
→
2 · dispara con la key
gh workflow run regression.yml -f environment=staging -f execution_key=UPEX-482
workflow_dispatch input
→
3 · el workflow la recibe
STP_EXECUTION_KEY: ${{ github.event.inputs.execution_key || secrets.STP_EXECUTION_KEY }}
regression.yml, env STP_EXECUTION_KEY · input gana al secret
→
4 · KATA deja atc_results.json
Reporter KataReporter → reports/atc_results.json en cada job; subido como *-atc-results.
tests/KataReporter.ts
→
5 · XrayImport importa
bun run xray:payload (una entrada por ATC, sin testInfo) y bun xray import xray en ${STP_EXECUTION_KEY}. Nunca crea un Test.
job XrayImport
→
6 · el agente cierra
Comenta GO / CAUTION / NO-GO EN el RTR y lo transiciona a close. El RTP no se toca.
Fase 3 del skill
  • Por qué antes. El import de Xray solo escribe dentro de una Execution que ya existe. Si le das solo el proyecto, acuña una Execution nueva y sin padre en cada run (comentario del job XrayImport). Crearla antes garantiza padre, plan y dueño.
  • Un RTR por veredicto. Re-run por fallo de entorno antes del veredicto → misma key. Re-run después de un veredicto → RTR nuevo (#2). Nunca se reabre uno cerrado.
  • El secret es el fallback del cron. Un run programado no tiene inputs; STP_EXECUTION_KEY como secret le da destino. El nombre quedó por compatibilidad; la semántica cambió.
  • Smoke y sanity no leen el secret (bloque env de smoke.yml): importar el subconjunto @critical en el RTR voltearía Test Runs que pertenecen a un veredicto completo. Solo importan con key explícita.
  • Nunca la key del RTP/STP. Un Test Plan deriva su estado de sus Executions; escribir en él es un error de modelo.
  • STR solo al cierre de sprint: entonces la key es la del STR, con doble membresía STP + RTP. Un run regular nunca es el STR.
Flujo de 6 pasos del write-back. Reglas: RTR antes, uno por veredicto, secret = fallback cron, smoke/sanity no leen el secret, nunca RTP/STP, STR solo cierre de sprint.

simulador: mueve los tres interruptores y mira qué steps corren de verdad

La matriz: AUTO_SYNC × TMS_PROVIDER × execution_key

Configuración del run

AUTO_SYNC (secret)
execution_key (input o secret)

Tres interruptores, una idea

Todo es opt-in y no bloqueante: un repo sin TMS ve un aviso, nunca un rojo. La suite es verde o roja por los tests; el write-back nunca cambia ese color (continue-on-error en el import).

Las expresiones son las del archivo. Nota el detalle de diseño: en xray, los steps «Sync … to TMS» de cada job se saltan porque XrayImport ya importa el mismo reports/atc_results.json (comentario del step: «two imports of the same runs is a double-import, not a safety net»). El paso test:sync lee reports/atc_results.json, que KataReporter escribe al salir Playwright: por eso es un step separado y nunca un teardown.

Matriz interactiva con los if reales. Opt-in y no bloqueante. Double-import evitado. atc_results.json post-proceso.

quiz 08: resultados y write-back

Adónde fueron los resultados?

Q5Disparás smoke.yml a mano sin execution_key. AUTO_SYNC=true, TMS_PROVIDER=xray. Qué llega a Xray?
ALos @critical se importan al RTR de la última regresión (secret STP_EXECUTION_KEY)
BNada: smoke no lee el secret, y sin key el step de sync se salta a propósito
CXray crea una Test Execution nueva para el smoke
Nada, por diseño. smoke.yml pone STP_EXECUTION_KEY: ${{ github.event.inputs.execution_key }} sin fallback al secret, y el if del step Sync Results to TMS exige key cuando el proveedor es xray. Importar el @critical dentro del RTR voltearía Test Runs de un veredicto completo (comentario del bloque env). C tampoco: el import sin destino se evita justamente porque acuñaría huérfanas.
Q6La regresión del jueves dio NO-GO, se arregló el bug y ahora re-corrés. Qué execution_key usás?
ALa misma RTR del jueves: es la misma regresión
BUna RTR nueva (…#2): el veredicto del jueves ya se escribió y cerró; nunca se reabre
CLa key del RTP, para que el plan se actualice
Nueva, con #2. Un RTR = un veredicto. El del jueves tiene su comentario NO-GO y está cerrado; escribir encima borraría la evidencia de la decisión. Solo un re-run por ENVIRONMENT antes del veredicto reutiliza la key. Y C es el error de modelo: un Test Plan nunca recibe imports (comentario del bloque env de regression.yml).
Q5: smoke sin key no importa. Q6: RTR nuevo tras veredicto; nunca RTP.
no implementado en este repo · diseño prospectivo

Capítulo 09

Conectar las suites al pipeline de desarrollo

Sin esta integración, nuestras suites corren por reloj o por botón. El objetivo: que el deploy de la app a staging dispare las suites, y que producción espere a que pasen. GitHub ofrece tres mecanismos; ninguno está cableado aún. Todo YAML de este capítulo es ilustrativo.

repository_dispatchworkflow_calldeployment_statusel gate a producción
Cap 9: prospectivo. Cada slide lleva el banner. Fuentes: docs GitHub (events, reuse-workflows, deployments REST) + investigación (oneuptime Flux, qaskills, math-operations-e2e).
no implementado en este repo

el problema y las tres formas de resolverlo

De «corre a las 00:00» a «corre cuando la app se despliega»

Punto de partida

La app despliega a staging a las 22:00 y nuestra regresión corre por cron a las 00:00 (horas ilustrativas; la real es el schedule de regression.yml). Si el deploy fue a las 00:30, probamos la versión vieja. Y producción no espera a nadie: el deploy a prod es una decisión humana sin gate automatizado de QA.

Objetivo

Deploy a staging → smoke (inmediato) y regression o sanity del área → el resultado vuelve al commit de la app → deploy-production solo arranca si el gate está verde, con un revisor humano vía environment: production.

El obstáculo: dos repos

El GITHUB_TOKEN de un workflow no dispara nada en OTRO repo, y los eventos de la app ocurren en la app. Cruzar la frontera exige uno de los tres mecanismos, y un token con alcance (PAT fine-grained o GitHub App) en los dos primeros.

MecanismoQuién disparaQué recibe el repo QAFeedback a la appCoste
repository_dispatchla app, con POST /repos/{qa}/dispatches (token cross-repo)event_type (≤100 chars) + client_payload (≤10 claves: env, sha, url)manual: escribir un commit status / check en la appmedio: token + dos workflows
workflow_callla app, como un job: uses: upex-galaxy/qa/.github/workflows/regression.yml@maininputs y secrets declarados; secrets: inherit si misma orgnativo: el job sale ✓/✗ en el run de la app; needs es el gatebajo si misma org; el repo QA declara on: workflow_call
deployment_statusla plataforma de hosting (Vercel, Render…) al terminar el deploystate (success/failure/pending…), environment, environment_urlindirecto: no bloquea el deploy que ya ocurrióbajo, pero solo llega al repo donde ocurrió el deployment

Regla de las docs que hace posible todo esto: los eventos provocados con GITHUB_TOKEN no disparan otros workflows (anti-bucle) salvo workflow_dispatch y repository_dispatch. Por eso el segundo existe.

Punto de partida vs objetivo. Obstáculo: dos repos. Tabla de 3 mecanismos con límites reales de docs (100 chars, 10 claves, inherit misma org, deployment_status local al repo).
no implementado en este repo · yaml ilustrativo

diseñador: elige mecanismo, suite y dureza del gate

Diseña el gate: los dos lados del YAML

app repo + repo QAilustrativo
Diseñador: 3 mecanismos × 3 suites × 2 durezas → YAML de ambos lados + pros/contras. Todo etiquetado ilustrativo.
no implementado en este repo · diseño recomendado

cómo se vería el pipeline completo, con la recomendación

El pipeline objetivo: deploy → suites → producción con permiso

app: build + unit
CI de la app. Rápido, en cada push.
repo app
→
app: deploy-staging
environment: staging. Al terminar, invoca QA.
repo app
→
QA: smoke (2-5 min)
Está viva? Falla → nada más corre.
repo QA · smoke.yml
→
QA: regression o sanity
Todo, o el área tocada (grep por componente).
repo QA · regression.yml
→
app: deploy-production
needs: [qa] + environment: production con required reviewers.
repo app

Recomendación para este repo (cuando se implemente)

  • workflow_call como mecanismo principal: app y QA viven en la misma organización (upex-galaxy), así que secrets: inherit funciona y el gate es un needs: nativo, visible, sin token extra.
  • Añadir on: workflow_call a smoke.yml y regression.yml sin quitar el dispatch ni el cron: un mismo archivo, tres formas de entrar. inputs.environment sirve para los tres.
  • Smoke como primer gate, regression como segundo. Smoke rojo corta en 3 minutos. Es la secuencia smoke → sanity → regression de la industria, ahora con eventos en vez de crons.
  • Producción con dos llaves: la suite verde (needs) y un humano (protection rule «required reviewers» en el environment production). El veredicto GO sigue siendo humano-asistido; el CI solo impide que se olvide.

Lo que cambia en el write-back

Un run disparado por la app no pasa por /regression-testing, así que nadie creó el RTR antes. Opciones: que la app no pase key (el import se salta con ::warning, el reporte Allure igual se publica), o que un step previo cree el RTR con bun xray y lo pase como input. La segunda es la que preserva «un RTR por veredicto».

Lo que NO cambia

Ni el esqueleto de la suite ni el reporte ni la clasificación de fallos. El trigger es la única línea que se mueve. Por eso valía la pena aprender el esqueleto primero.

Fuentes: docs GitHub reuse-workflows, events-that-trigger-workflows (repository_dispatch, deployment_status), deploy-to-environment; patrones de la industria: E2E post-deploy con dispatch (oneuptime, qaskills) y repos cross-trigger (AST-LW/math-operations-e2e).

Pipeline objetivo con recomendación: workflow_call (misma org), smoke→regression, producción con needs + reviewers. Impacto en el RTR. Fuentes.

quiz 09: integración con el pipeline de desarrollo

Qué mecanismo elegís?

Q7La app y este repo están en la misma organización de GitHub. Querés que deploy-production no arranque si la regresión falla. Mecanismo más simple?
Arepository_dispatch + escribir un commit status de vuelta
Bworkflow_call: la app invoca regression.yml como job y pone needs en deploy-production
Cdeployment_status en el repo QA
workflow_call. Misma org → secrets: inherit vale; el job QA aparece en el run de la app y needs es el gate, sin tokens ni statuses a mano. A funciona pero cuesta más (token cross-repo + feedback manual). C no llega: deployment_status se emite en el repo donde ocurrió el deployment, que es la app, no QA.
Q8Añadís on: workflow_call a regression.yml y leés el entorno con github.event.inputs.environment. Desde la app llega vacío. Por qué?
Agithub.event.inputs es del dispatch; los inputs de workflow_call llegan por el contexto inputs, que sirve para ambos
BLa app olvidó secrets: inherit
Cworkflow_call no admite inputs de tipo choice
Contexto equivocado. El evento de un workflow reutilizable es el del caller, no un dispatch, así que github.event.inputs no trae nada. El contexto inputs unifica ambos casos: es el que las docs recomiendan y el que habría que adoptar en los tres || 'staging' del archivo al hacer el cambio. B afecta a secrets, no a inputs.
Q7: workflow_call para misma org. Q8: inputs vs github.event.inputs en workflow_call.

Capítulo 10

Escribe tu propio workflow

Todo lo anterior cabe en una plantilla. Ahora la armás con interruptores, la leés completa y la pasás por el checklist antes de abrir el PR. Sin copiar a ciegas: cada bloque ya sabés qué hace.

constructor sobre la plantilla del repochecklist de calidadcómo probarlo antes de mergear
Cap 10: constructor + checklist + cómo probar.

constructor: cada interruptor añade o quita un bloque; el YAML sigue el esqueleto del repo

Arma tu suite con la plantilla

identidad
triggers y bloques
cron nocturno schedule
write-back al TMS execution_key + sync
input video_record
job allure-report

Después de generar

Guardalo como .github/workflows/<suite>.yml, corré bun run format:check (prettier valida YAML) y pasá el checklist de la siguiente slide. Los inputs se leen con github.event.inputs para ser coherente con las suites actuales; si añadís workflow_call, migrá a inputs.*.

generado · esqueleto de smoke.ymlplantilla
Constructor: 4 suites × entornos × 4 toggles → YAML completo con el esqueleto del repo. Botón copiar.

checklist: marca cada punto; se guarda en este navegador

Doce controles antes de abrir el PR

  • Propósito en la cabecera. Un comentario que dice qué pregunta contesta y cuándo corre. Si no podés escribirlo, no sabés qué suite es.
  • Comandos de package.json, no inventados. bun run test:smoke, nunca npx playwright test … escrito a mano (Rule #11).
  • Cada input tiene fallback || 'default' donde un cron pueda dispararlo.
  • Ningún secret en el YAML. Solo ${{ secrets.X }}. Los nombres siguen el prefijo de entorno: STAGING_USER_EMAIL.
  • Ningún input interpolado dentro de run:. Pasalo por env: y leelo como "$VAR" (docs: secure-use).
  • timeout-minutes en cada job. Sin él, un cuelgue factura hasta 6 horas.
  • if: always() en cada upload de resultados y failure() en la evidencia extra.
  • Nombres de artifact únicos por run, con prefijo de suite/job. v4 es inmutable.
  • concurrency decidida: publica → cancel-in-progress: false; solo valida → true.
  • permissions mínimos si el workflow escribe en GitHub; nada declarado si solo lee.
  • Acciones con versión fijada (@v4) y, para terceros críticos, SHA completo con comentario de versión.
  • Probado con workflow_dispatch antes de confiar en el cron. Un cron nuevo no corre hasta estar en main; el botón sí (gh workflow run <file> --ref <tu-rama> una vez que el archivo también exista en main).

Cierre del ciclo: el PR dispara build.yml, que valida el repo pero no el YAML nuevo. El YAML lo valida bun run format:check en local (prettier) y, en última instancia, el primer run. Por eso el punto 12 no es opcional.

Checklist de 12 puntos con persistencia local. Punto 5 conecta con la nota de sanity.yml. Punto 12: probar con dispatch.

quiz 10: el tuyo

Último control del mini-curso

Q9Tu workflow nuevo recibe un input grep y lo usa así: run: bun run test --grep="${{ github.event.inputs.grep }}". Un compañero de seguridad lo marca. Qué cambiás?
ANada: solo gente con permiso puede disparar el workflow
Benv: GREP: ${{ github.event.inputs.grep }} y en el run --grep="$GREP": el valor no participa en la generación del script
CPasarlo a un secret
Variable intermedia. Una expresión dentro de run: se sustituye ANTES de que bash lea el script: un input con "; rm -rf … se convierte en código. Con env: el valor llega como variable de entorno y bash lo trata como dato. Es la recomendación literal de la guía de seguridad de GitHub, y el patrón que aplica el step Run Sanity Tests de sanity.yml: inputs por env:, leídos entre comillas, sin eval.
Q10Terminaste el workflow, mergeaste y a la mañana no corrió. Revisás: el cron está bien, el archivo está en main. Qué mirás primero?
ASi el repo tiene actividad reciente (los crons se desactivan a los 60 días en repos públicos) y si el run quedó encolado por carga
BSi falta permissions: write-all
CAmbas cosas de A, y además el formulario de dispatch: si gh workflow run tampoco lo arranca, el problema es el YAML, no el reloj
Separar el reloj del archivo. Primero descartá el cron (inactividad de 60 días, retraso por carga, hora UTC mal convertida). Después dispará a mano: si el dispatch falla, GitHub te muestra el error de sintaxis o el step roto; si funciona, el problema era el trigger. B es ruido: los permisos no impiden que un workflow arranque.
Q9: env intermedio contra script injection. Q10: diagnóstico cron vs YAML.

cierre: el mapa completo y qué sigue

De un archivo YAML a un sistema de calidad que corre solo

Lo que sabés ahora

Leer y escribir un workflow de GitHub Actions. Por qué existen cuatro suites y qué gatea cada una. Cómo regression.yml fusiona, publica e importa. Dónde vive cada resultado. Cómo funciona el write-back RTR/STR. Y qué haría falta para que producción espere a las suites.

Lo que sigue en el pipeline

El run terminó y dejó evidencia. Clasificar cada fallo (REGRESSION · FLAKY · ENVIRONMENT · KNOWN · NEW TEST · KNOWN-BLOCKED), puntuar y emitir el veredicto es /regression-testing: how-it-works.es.html. Escribir los tests que corren aquí es /test-automation.

Referencias

Repo: .github/workflows/*.yml (leelos con este deck al lado) · .agents/skills/regression-testing/references/ci-cd-integration.md (sharding, caché, path filters, troubleshooting) · references/github-pages-setup.md · references/private-hosting-setup.md. GitHub Docs: docs.github.com/en/actions. Playwright: playwright.dev/docs/test-sharding.

Ejercicio final

Tomá el YAML del constructor para una suite visual por tag @visual. Pasá los 12 controles. Abrí un PR de prueba (sin mergear) y mirá cómo build.yml reacciona. Después borrá la rama. Si entendiste cada línea que escribiste, este mini-curso cumplió.

Un pipeline de testing no es una lista de comandos: es la respuesta escrita a «qué queremos saber, cuándo, y qué pasa si la respuesta es no?». Ahora sabés escribirla.

← Parte I · Fundamentos

Cierre del mini-curso. Recap, handoff a how-it-works (veredicto) y test-automation, referencias, ejercicio final.
Quiz 0 / 10
← → navegar · O resumen · S presentador · N notas · F pantalla