Docs de Autonomy
Integraciones

GitLab CI

Ejecuta Autonomy desde GitLab CI con una URL de previsualización, una app de iOS Simulator o un APK de Android y condiciona el pipeline al veredicto.

Dispara Autonomy después de un despliegue o un build móvil y mantén abierto el job de GitLab hasta que cada caso de prueba seleccionado tenga un veredicto válido y aprobado. Copia el script compartido y la configuración de la plataforma que distribuyes.

Ubicación en el flujo de trabajo

Coloca Autonomy después del job de despliegue que obtiene la URL de previsualización definitiva, o después del job que construye el artefacto móvil. El ejemplo web siguiente recibe una URL ya desplegada y comprueba que responde. No despliega tu aplicación.

Cada ejemplo YAML es una alternativa completa de .gitlab-ci.yml. Al añadirlo a un pipeline existente, conserva los jobs de build y despliegue, combina las etapas y dirige needs al job que produce la URL o el artefacto. Ejecuta únicamente código de confianza en una rama predeterminada protegida.

Secretos necesarios

En Settings → CI/CD → Variables, añade estos valores de tipo Variable, no File, con ámbito de entorno *. Protege las variables y desactiva la expansión de referencias a variables. Marca la clave API como Masked and hidden. Protege la rama predeterminada antes de ejecutar los ejemplos.

  • AUTONOMY_API_KEY: una clave API de organización que empiece por aut_, creada en Settings → API Keys de Autonomy. Debe pertenecer a la organización que contiene el plan. Actualmente, las claves de organización incluyen todos los ámbitos de la API.
  • AUTONOMY_API_URL: copia CONVEX_SITE_URL o NEXT_PUBLIC_CONVEX_SITE_URL del despliegue correspondiente a tu clave API. Usa su URL HTTP de Convex exacta, incluida cualquier región en el nombre del host, sin /api ni una ruta final. No uses la URL del panel ni .convex.cloud. Una clave de desarrollo necesita la URL de su despliegue de desarrollo.
  • AUTONOMY_TEST_PLAN_ID: el ID de un plan real de Test Plans, con al menos un caso de prueba web. Copia el ID de la URL del plan. Un ID de Test Cases no es intercambiable con un ID de plan.
  • PREVIEW_URL: la URL HTTPS de previsualización lista para el ejemplo web. Debe ser accesible desde el runner de Autonomy, no localhost ni un servicio disponible solo dentro del job de CI.
  • Para iOS, añade AUTONOMY_IOS_TEST_PLAN_ID y AUTONOMY_IOS_BUNDLE_ID. Para Android, añade AUTONOMY_ANDROID_TEST_PLAN_ID y AUTONOMY_ANDROID_PACKAGE_NAME. Cada plan debe contener casos de esa plataforma; los identificadores deben coincidir con la app construida.
  • Ten un runner de Autonomy disponible para la plataforma solicitada y suficientes créditos de ejecución. Para estos objetivos independientes explícitos, usa casos que no hagan referencia a valores de ejecución de un Environment guardado.

Los ejemplos establecen AUTONOMY_REQUEST_ID a partir de CI_JOB_ID, de modo que un job reintentado de GitLab recibe una solicitud nueva. Los metadatos de rama y commit proceden de las variables predefinidas de GitLab. Consulta Test Plans para organizar los casos seleccionados.

URL de previsualización web

Crea .ci/ en la raíz del repositorio, guarda el siguiente script como .ci/autonomy.sh y haz commit junto con el archivo del pipeline. Todas las recetas de esta página usan exactamente este script. Necesita Bash, jq y curl 7.76 o posterior; los jobs con contenedor los instalan.

.ci/autonomy.sh
#!/usr/bin/env bash
set -euo pipefail
set +x
: "${AUTONOMY_API_URL:?Set the API base URL, without /api}"
: "${AUTONOMY_API_KEY:?Set an aut_ organization API key}"
: "${AUTONOMY_TEST_PLAN_ID:?Set the Test Plans ID for this platform}"
: "${AUTONOMY_REQUEST_ID:?Set a unique CI job/attempt ID}"
platform="${1:-web}"
api="${AUTONOMY_API_URL%/}"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

post() {
  curl --fail-with-body --silent --show-error \
    --connect-timeout 15 --max-time 90 \
    -X POST "$api$1" \
    -H "Authorization: Bearer $AUTONOMY_API_KEY" \
    -H 'Content-Type: application/json' --data-binary "$2"
}

case "$platform" in
  web)
    : "${PREVIEW_URL:?Set the reachable preview URL after deployment}"
    targets=$(jq -n --arg url "$PREVIEW_URL" '{web:{baseUrl:$url}}')
    ;;
  ios|android)
    : "${ARTIFACT_PATH:?Set the path to the app archive or APK}"
    : "${APP_IDENTIFIER:?Set the bundle ID or Android package name}"
    test -s "$ARTIFACT_PATH"
    file=$(basename "$ARTIFACT_PATH")
    bytes=$(wc -c < "$ARTIFACT_PATH" | tr -d '[:space:]')
    content_type=application/zip
    if [ "$platform" = android ]; then
      content_type=application/vnd.android.package-archive
    fi
    upload=$(post /api/artifacts/upload-url "$(jq -n \
      --arg platform "$platform" --arg fileName "$file" \
      --arg contentType "$content_type" --argjson declaredBytes "$bytes" \
      '{platform:$platform,fileName:$fileName,contentType:$contentType,declaredBytes:$declaredBytes}')")
    url=$(jq -er '.uploadUrl | strings | select(length > 0)' <<< "$upload")
    storage=$(jq -er '.storageId | strings | select(length > 0)' <<< "$upload")
    intent=$(jq -er '.intentId | strings | select(length > 0)' <<< "$upload")
    jq -e '.method == "PUT"' <<< "$upload" > /dev/null
    curl --fail-with-body --silent --show-error \
      --connect-timeout 15 --max-time 600 -X PUT "$url" \
      -H "Content-Type: $content_type" --data-binary "@$ARTIFACT_PATH"
    scan=$(post /api/artifacts/scan "$(jq -n \
      --arg storageId "$storage" --arg intentId "$intent" \
      '{storageId:$storageId,intentId:$intentId}')")
    if ! jq -e '.status == "clean"' <<< "$scan" > /dev/null; then
      echo 'Artifact scan did not pass; refusing to trigger.' >&2
      exit 1
    fi
    targets=$(jq -n --arg platform "$platform" --arg storageId "$storage" \
      --arg fileName "$file" --arg identifier "$APP_IDENTIFIER" \
      '{($platform):({storageId:$storageId,sourceMode:"upload",fileName:$fileName} +
        (if $platform == "ios" then {bundleId:$identifier} else {packageName:$identifier} end))}')
    ;;
  *) echo 'Usage: bash .ci/autonomy.sh web|ios|android' >&2; exit 2 ;;
esac

body=$(jq -n --arg testPlanId "$AUTONOMY_TEST_PLAN_ID" \
  --arg platform "$platform" --argjson targets "$targets" \
  --arg branch "${AUTONOMY_BRANCH:-manual}" \
  --arg commitSha "${AUTONOMY_COMMIT_SHA:-}" \
  --arg key "$AUTONOMY_REQUEST_ID:$platform" \
  '{testPlanId:$testPlanId,platforms:[$platform],targets:$targets,
    branch:$branch,deployment:{branch:$branch,commitSha:$commitSha},idempotencyKey:$key}')
post /api/v1/run.trigger "$body" > "$work/trigger.json"
jq -e '.runIds | type == "array" and length > 0' "$work/trigger.json" > /dev/null
jq -er '.runIds[] | strings | select(length > 0)' "$work/trigger.json" > "$work/run-ids"
printf 'Queued Autonomy runs:\n'
cat "$work/run-ids"

deadline=$((SECONDS + ${AUTONOMY_WAIT_SECONDS:-1200}))
while IFS= read -r run_id; do
  while :; do
    if (( SECONDS >= deadline )); then
      echo "Timed out waiting for $run_id; failing the CI job." >&2
      exit 1
    fi
    result=$(post /api/v1/run.get "$(jq -n --arg runId "$run_id" '{runId:$runId}')")
    status=$(jq -er '.status' <<< "$result")
    case "$status" in
      queued|running|retrying) sleep 10 ;;
      passing|failed|unverified|invalid|canceled)
        printf '%s: %s\n' "$run_id" "$status"
        if ! jq -e '.verdict == "passed" and .validity == "valid"' <<< "$result" > /dev/null; then
          echo "Autonomy gate failed for $run_id." >&2
          exit 1
        fi
        break ;;
      *) echo "Unexpected run status: $status" >&2; exit 1 ;;
    esac
  done
done < "$work/run-ids"
echo 'Every Autonomy run passed.'

Guarda lo siguiente como .gitlab-ci.yml. Habilita los runners Linux alojados de GitLab para tu proyecto; los jobs web y el job de subida de iOS seleccionan explícitamente saas-linux-small-amd64. En GitLab Self-Managed, sustituye esa etiqueta en cada job con contenedor Linux por la etiqueta de tu propio runner Linux con ejecutor Docker. Empieza en Build → Pipelines → New pipeline sobre la rama predeterminada protegida. El registro muestra Queued Autonomy runs: y los ID de ejecución cuando la API acepta la solicitud.

.gitlab-ci.yml
stages: [prepare, qa]

workflow:
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_REF_PROTECTED == "true"'
    - when: never

default:
  tags: [saas-linux-small-amd64]
  image: debian:stable-slim
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends bash curl jq ca-certificates

preview_ready:
  stage: prepare
  script:
    - |
      : "${PREVIEW_URL:?Set the deployed preview URL in CI/CD Variables}"
      curl --fail --silent --show-error --location \
        --connect-timeout 15 --max-time 60 "$PREVIEW_URL" > /dev/null
      printf 'PREVIEW_URL=%s\n' "$PREVIEW_URL" > preview.env
  artifacts:
    reports:
      dotenv: preview.env
    expire_in: 1 day

autonomy_web:
  stage: qa
  timeout: 30m
  needs:
    - job: preview_ready
      artifacts: true
  script:
    - |
      export AUTONOMY_REQUEST_ID="gitlab:$CI_PROJECT_ID:$CI_JOB_ID"
      export AUTONOMY_BRANCH="$CI_COMMIT_REF_NAME"
      export AUTONOMY_COMMIT_SHA="$CI_COMMIT_SHA"
      bash .ci/autonomy.sh web

Si la URL se crea dentro de tu job de despliegue existente, mueve las entradas printf y artifacts:reports:dotenv a ese job y cambia needs por su nombre. Elimina la variable de proyecto fija PREVIEW_URL para que no sobrescriba el valor generado de dotenv. No incluyas credenciales en preview.env; GitLab lo almacena como artefacto descargable.

Transferencia de artefactos móviles

Usa el mismo .ci/autonomy.sh. El script solicita una URL de subida firmada, sube el archivo, exige un análisis limpio y entrega el ID de almacenamiento resultante a la ejecución. Selecciona solo la plataforma cuyo build existe en tu repositorio.

iOS

Usa un runner macOS propio, dedicado y protegido, con la etiqueta macos, el ejecutor de shell Bash, Xcode y el SDK de iOS Simulator instalados. Desactiva Run untagged jobs para este runner y no le asignes la etiqueta del runner Linux. Los runners macOS alojados de GitLab requieren Premium o Ultimate, salvo los programas de código abierto que cumplan sus requisitos. Un proyecto Free normal necesita su propio Mac. Linux no puede realizar este build de Xcode.

Este ejemplo nativo supone ios/MyApp.xcodeproj, un esquema compartido MyApp y un producto MyApp.app. Sustituye los tres por los nombres de tu proyecto e instala las dependencias específicas antes de xcodebuild. Para un proyecto con workspace, usa -workspace ios/MyApp.xcworkspace en lugar de -project. Genera un .app de Simulator, no un .ipa para dispositivo.

El job de macOS archiva la app conservando permisos de ejecución y enlaces simbólicos antes de que GitLab transporte el ZIP. needs:artifacts descarga ese ZIP en el job de Linux, que se encarga de subirlo y esperar.

.gitlab-ci.yml
stages: [build, qa]

workflow:
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_REF_PROTECTED == "true"'
    - when: never

build_ios:
  stage: build
  tags: [macos]
  script:
    - |
      set -euo pipefail
      xcodebuild \
        -project ios/MyApp.xcodeproj \
        -scheme MyApp \
        -configuration Release \
        -sdk iphonesimulator \
        -destination 'generic/platform=iOS Simulator' \
        -derivedDataPath ios/build \
        CODE_SIGNING_ALLOWED=NO \
        build
      mkdir -p .build
      (cd ios/build/Build/Products/Release-iphonesimulator && \
        zip -qry -y "$CI_PROJECT_DIR/.build/MyApp.app.zip" MyApp.app)
  artifacts:
    paths:
      - .build/MyApp.app.zip
    expire_in: 1 day

autonomy_ios:
  stage: qa
  tags: [saas-linux-small-amd64]
  image: debian:stable-slim
  timeout: 30m
  needs:
    - job: build_ios
      artifacts: true
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends bash curl jq ca-certificates
  script:
    - |
      export AUTONOMY_TEST_PLAN_ID="$AUTONOMY_IOS_TEST_PLAN_ID"
      export APP_IDENTIFIER="$AUTONOMY_IOS_BUNDLE_ID"
      export ARTIFACT_PATH=".build/MyApp.app.zip"
      export AUTONOMY_REQUEST_ID="gitlab:$CI_PROJECT_ID:$CI_JOB_ID"
      export AUTONOMY_BRANCH="$CI_COMMIT_REF_NAME"
      export AUTONOMY_COMMIT_SHA="$CI_COMMIT_SHA"
      bash .ci/autonomy.sh ios

Android

Registra un runner Linux propio, dedicado y protegido, con la etiqueta android-linux y el ejecutor de shell Bash. Desactiva Run untagged jobs para este runner y no le asignes la etiqueta del runner con contenedor Linux. Instala en esa máquina el JDK requerido por el proyecto, las herramientas de línea de comandos de Android, las versiones de compile SDK y build-tools, Bash, jq, curl 7.76 o posterior y certificados CA de confianza. Configura ANDROID_HOME, acepta las licencias del SDK y añade al repositorio un android/gradlew ejecutable con sus archivos wrapper. El ejecutor de shell usa las herramientas del host; no instala un SDK de Android por ti.

Este ejemplo construye el APK de depuración del módulo app y lo sube en el mismo job de Linux. Ajusta la tarea de Gradle y ARTIFACT_PATH conjuntamente si tu proyecto usa variantes u otro módulo.

.gitlab-ci.yml
stages: [qa]

workflow:
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_REF_PROTECTED == "true"'
    - when: never

autonomy_android:
  stage: qa
  tags: [android-linux]
  timeout: 60m
  script:
    - |
      set -euo pipefail
      (cd android && ./gradlew --no-daemon assembleDebug)
      export AUTONOMY_TEST_PLAN_ID="$AUTONOMY_ANDROID_TEST_PLAN_ID"
      export APP_IDENTIFIER="$AUTONOMY_ANDROID_PACKAGE_NAME"
      export ARTIFACT_PATH="android/app/build/outputs/apk/debug/app-debug.apk"
      export AUTONOMY_REQUEST_ID="gitlab:$CI_PROJECT_ID:$CI_JOB_ID"
      export AUTONOMY_BRANCH="$CI_COMMIT_REF_NAME"
      export AUTONOMY_COMMIT_SHA="$CI_COMMIT_SHA"
      bash .ci/autonomy.sh android

Condicionar el pipeline al veredicto

El script consulta cada ID devuelto por run.trigger mediante run.get. Sigue esperando mientras la ejecución esté en queued, running o retrying, y solo tiene éxito cuando todos los resultados finales tienen verdict: "passed" y validity: "valid". Una ejecución en cola no aprueba el pipeline. Los resultados fallidos, inválidos, no verificados, cancelados, inesperados o agotados por tiempo hacen fallar el job.

El presupuesto de espera predeterminado es de 1.200 segundos para todas las ejecuciones devueltas. Configura AUTONOMY_WAIT_SECONDS para cambiarlo y mantén los límites de tiempo del job y del runner de GitLab por encima de ese presupuesto más el tiempo de build y subida. Mantén allow_failure desactivado. Cancelar el job de GitLab o agotar su tiempo detiene el sondeo; no cancela una ejecución de Autonomy ya en cola.

Estrategia de ramas y eventos

Estas configuraciones permiten pushes, ejecuciones programadas y pipelines iniciados manualmente solo en la rama predeterminada protegida. Los pipelines de merge requests y el código de forks no reciben la clave. Usa planes de smoke cortos en despliegues normales; utiliza una programación o un pipeline manual con un plan más amplio para comprobar versiones.

Para comprobar la previsualización de una merge request, ejecuta manualmente la configuración de confianza de la rama predeterminada con la URL de previsualización revisada. No habilites acceso a variables protegidas para código no confiable de merge requests. Si amplías las reglas de ramas, protege esas ramas y revisa primero sus cambios de pipeline.

Referencias del proveedor

On this page