GitLab CI
Inicia ejecuciones de Autonomy desde GitLab CI para una URL de previsualización o un APK de Android, y haz fallar el pipeline salvo que todas las ejecuciones se superen.
Inicia Autonomy después de un despliegue o de un build móvil y mantén el job de GitLab abierto hasta que todas las ejecuciones tengan resultado. El job solo se supera cuando todas las ejecuciones son Passed. Copia el script compartido y la configuración de la plataforma que publicas.
Dónde va en el pipeline
Pon Autonomy después del job de despliegue que obtiene la URL de previsualización definitiva, o después del job que compila la app móvil. El ejemplo web de abajo toma una URL ya desplegada y comprueba que responde. No despliega tu app.
Cada ejemplo YAML es un .gitlab-ci.yml alternativo completo. Si lo añades a un pipeline existente, conserva tus jobs de build y de despliegue, combina sus stages y apunta needs al job que produce la URL o el build. Ejecuta solo código de confianza en una rama predeterminada protegida.
Secretos necesarios
En Settings → CI/CD → Variables, añade estos valores como Variable, no como File, con el ámbito de entorno *. Protege las variables y desactiva la expansión de referencias a variables. Marca la clave de API como Masked and hidden. Protege la rama predeterminada antes de ejecutar los ejemplos.
-
AUTONOMY_API_KEY: una clave de API de la organización que empieza poraut_. Los propietarios y administradores crean claves en Organization → API keys. Una clave tiene acceso a todos los proyectos de la organización. -
AUTONOMY_TEST_PLAN_ID: el plan de pruebas que ejecutar. Ábrelo en Test plans del proyecto: el ID es la última parte de la URL de la página,/p/<project>/plans/<id>. El plan necesita al menos un caso de prueba para la plataforma. Para ejecutar un solo caso de prueba, defineAUTONOMY_TEST_CASE_ID(de/p/<project>/tests/<id>) y deja sin definir el ID del plan. Define exactamente uno de los dos. -
PREVIEW_URL: la URL HTTPS de previsualización ya lista para el ejemplo web. Autonomy debe llegar a ella desde internet: nilocalhostni un servicio que solo exista dentro del job de CI. - Para iOS, añade
AUTONOMY_IOS_TEST_PLAN_IDyAUTONOMY_IOS_BUNDLE_ID. Para Android, añadeAUTONOMY_ANDROID_TEST_PLAN_IDyAUTONOMY_ANDROID_PACKAGE_NAME. Cada plan necesita casos de prueba para esa plataforma, y los identificadores deben coincidir con la app compilada. - Opcional:
AUTONOMY_ENVIRONMENT_SLUGejecuta contra un entorno guardado del mismo proyecto, con sus objetivos y valores del entorno, en lugar de la URL o el build de este job. En ese caso el script omite la subida.AUTONOMY_WAIT_SECONDScambia el tiempo de espera total.
Sin un entorno, una ejecución no tiene valores del entorno: un paso que usa {{env.NAME}} termina como Couldn't start. Define AUTONOMY_ENVIRONMENT_SLUG o usa casos de prueba que no necesiten valores del entorno.
Los ejemplos definen AUTONOMY_REQUEST_ID a partir de CI_JOB_ID, así que un job de GitLab reintentado envía una petición nueva. La rama y el commit vienen de las variables predefinidas de GitLab. Consulta Planes de pruebas para agrupar casos de prueba.
URL de previsualización web
Crea .ci/ en la raíz del repositorio, guarda este script como .ci/autonomy.sh y haz commit junto con el archivo del pipeline. Todas las recetas de esta página usan este mismo script. Necesita Bash, jq y curl 7.76 o posterior; los jobs de contenedor los instalan.
#!/usr/bin/env bash
# Usage: bash .ci/autonomy.sh web|ios|android
# Starts Autonomy runs for this CI job and exits 0 only if every run passed.
set -euo pipefail
set +x
: "${AUTONOMY_API_KEY:?Set an aut_ organization API key}"
: "${AUTONOMY_REQUEST_ID:?Set an ID that is unique for this CI job attempt}"
platform="${1:?Usage: bash .ci/autonomy.sh web|ios|android}"
api="${AUTONOMY_API_URL:-https://api.autonomyqa.com}"
api="${api%/}"
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"
}
# 1. What to run: a test plan or one test case.
if [ -n "${AUTONOMY_TEST_PLAN_ID:-}" ] && [ -z "${AUTONOMY_TEST_CASE_ID:-}" ]; then
what=$(jq -n --arg id "$AUTONOMY_TEST_PLAN_ID" '{testPlanId:$id}')
elif [ -n "${AUTONOMY_TEST_CASE_ID:-}" ] && [ -z "${AUTONOMY_TEST_PLAN_ID:-}" ]; then
what=$(jq -n --arg id "$AUTONOMY_TEST_CASE_ID" '{testCaseId:$id}')
else
echo 'Set exactly one of AUTONOMY_TEST_PLAN_ID or AUTONOMY_TEST_CASE_ID.' >&2
exit 2
fi
# 2. Where to run: a saved environment, or this job's preview URL or build.
if [ -n "${AUTONOMY_ENVIRONMENT_SLUG:-}" ]; then
where=$(jq -n --arg slug "$AUTONOMY_ENVIRONMENT_SLUG" '{environmentSlug:$slug}')
else
case "$platform" in
web)
: "${PREVIEW_URL:?Set PREVIEW_URL, or AUTONOMY_ENVIRONMENT_SLUG}"
where=$(jq -n --arg url "$PREVIEW_URL" '{targets:{web:{baseUrl:$url}}}')
;;
ios|android)
: "${ARTIFACT_PATH:?Set the path to the zipped .app, .apk or .aab}"
: "${APP_IDENTIFIER:?Set the iOS bundle ID or Android package name}"
file=$(basename "$ARTIFACT_PATH")
bytes=$(wc -c < "$ARTIFACT_PATH" | tr -d '[:space:]')
if [ "$platform" = ios ]; then
type=application/zip
else
type=application/vnd.android.package-archive
fi
upload=$(post /api/artifacts/upload-url "$(jq -n \
--arg platform "$platform" --arg fileName "$file" \
--arg contentType "$type" --argjson declaredBytes "$bytes" \
'{platform:$platform,fileName:$fileName,contentType:$contentType,declaredBytes:$declaredBytes}')")
storage=$(jq -er '.storageId' <<< "$upload")
intent=$(jq -er '.intentId' <<< "$upload")
curl --fail-with-body --silent --show-error \
--connect-timeout 15 --max-time 600 \
-X PUT "$(jq -er '.uploadUrl' <<< "$upload")" \
-H "Content-Type: $type" --data-binary "@$ARTIFACT_PATH"
scan=$(post /api/artifacts/scan "$(jq -n \
--arg storageId "$storage" --arg intentId "$intent" \
'{storageId:$storageId,intentId:$intentId}')")
if [ "$(jq -r '.status' <<< "$scan")" != clean ]; then
echo 'The build did not pass the safety check, so no run was started:' >&2
jq -r '.findings[]?.message' <<< "$scan" >&2
exit 1
fi
where=$(jq -n --arg p "$platform" --arg storageId "$storage" \
--arg fileName "$file" --arg id "$APP_IDENTIFIER" \
'{targets:{($p):({storageId:$storageId,sourceMode:"upload",fileName:$fileName}
+ (if $p == "ios" then {bundleId:$id} else {packageName:$id} end))}}')
;;
*) echo 'Usage: bash .ci/autonomy.sh web|ios|android' >&2; exit 2 ;;
esac
fi
# 3. Start the runs. A plan starts one run per test case.
# The request ID's prefix (github, gitlab, ...) names the CI provider.
body=$(jq -n --argjson what "$what" --argjson where "$where" \
--arg platform "$platform" \
--arg branch "${AUTONOMY_BRANCH:-}" --arg pr "${AUTONOMY_PR:-}" \
--arg commitSha "${AUTONOMY_COMMIT_SHA:-}" \
--arg provider "${AUTONOMY_REQUEST_ID%%:*}" \
--arg key "$AUTONOMY_REQUEST_ID:$platform" \
'$what + $where + ({
platforms: [$platform],
branch: $branch,
pr: $pr,
deployment: ({provider: $provider, branch: $branch, commitSha: $commitSha}
| with_entries(select(.value != ""))),
idempotencyKey: $key
} | with_entries(select(.value != "")))')
run_ids=$(post /api/v1/run.trigger "$body" | jq -er '.runIds[]')
printf 'Started Autonomy runs:\n%s\n' "$run_ids"
# 4. Follow each run until it ends. run.get waits up to 25 s for a change;
# sending the last cursor back makes each call wait for something new.
deadline=$((SECONDS + ${AUTONOMY_WAIT_SECONDS:-1200}))
all_passed=true
for run_id in $run_ids; do
cursor=null
while :; do
left=$((deadline - SECONDS))
if [ "$left" -le 0 ]; then
echo "Stopped waiting for run $run_id after AUTONOMY_WAIT_SECONDS. It keeps going in Autonomy." >&2
exit 1
fi
wait_ms=$(( left < 25 ? left * 1000 : 25000 ))
run=$(post /api/v1/run.get "$(jq -n --arg runId "$run_id" \
--argjson cursor "$cursor" --argjson waitMs "$wait_ms" \
'{runId:$runId,cursor:$cursor,waitMs:$waitMs}')")
cursor=$(jq '.cursor' <<< "$run")
status=$(jq -r '.status' <<< "$run")
[ "$status" = queued ] || [ "$status" = running ] || break
done
name="$(jq -r '.testCaseName' <<< "$run") ($run_id)"
case "$status" in
passed)
echo "Passed: $name" ;;
failed)
echo "Failed: $name. Open the run in Autonomy to see which step failed." >&2
all_passed=false ;;
couldnt_start)
echo "Couldn't start: $name. The environment couldn't be tested, so this says nothing about your app. Open the run for the reason, fix it and run again." >&2
all_passed=false ;;
cancelled)
echo "Cancelled: $name." >&2
all_passed=false ;;
*)
echo "Unexpected status $status: $name." >&2
all_passed=false ;;
esac
done
if [ "$all_passed" != true ]; then
echo 'Not every Autonomy run passed.' >&2
exit 1
fi
echo 'Every Autonomy run passed.'Guarda esto como .gitlab-ci.yml. Activa los runners Linux alojados por GitLab en tu proyecto; el job web y el job de subida de iOS seleccionan saas-linux-small-amd64. En GitLab Self-Managed, sustituye esa etiqueta en todos los jobs de contenedor Linux por la etiqueta de tu propio runner Linux con executor Docker. Empieza con Build → Pipelines → New pipeline en la rama predeterminada protegida. El log imprime Started Autonomy runs: y los IDs de las ejecuciones en cuanto Autonomy acepta la petición.
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 webPara una URL creada dentro de tu job de despliegue existente, mueve las entradas printf y artifacts:reports:dotenv a ese job y cambia needs a su nombre. Elimina la variable de proyecto fija PREVIEW_URL para que no sobrescriba el valor generado. No pongas credenciales en preview.env; GitLab lo guarda como un artifact descargable.
Entrega de builds móviles
Usa el mismo .ci/autonomy.sh. Para iOS y Android pide una URL de subida, sube el build, espera a que el control de seguridad salga limpio e inicia las ejecuciones con el build subido. Usa solo la plataforma cuyo build está en tu repositorio.
iOS
Las pruebas en iOS llegarán pronto. Guarda esto para cuando estén disponibles. Hasta entonces, Autonomy no inicia ejecuciones en iOS.
Usa un runner macOS autoalojado, dedicado y protegido, con la etiqueta macos, el executor shell de Bash, Xcode y el SDK de iOS Simulator. Desactiva Run untagged jobs para este runner y no le pongas la etiqueta del runner Linux. Los runners macOS alojados por GitLab requieren Premium o Ultimate, salvo en los programas de código abierto que cumplan los requisitos, así que un proyecto Free necesita su propio Mac. Linux no puede hacer este build de Xcode.
Este ejemplo da por hecho ios/MyApp.xcodeproj, un scheme compartido MyApp y un producto MyApp.app. Sustituye los tres por tus propios nombres e instala las dependencias de tu proyecto antes de xcodebuild. Para un workspace, usa -workspace ios/MyApp.xcworkspace en lugar de -project. Compila un .app de simulador, no un .ipa de dispositivo.
El job de macOS comprime la app en zip conservando los permisos y los enlaces simbólicos antes de que GitLab la mueva. needs:artifacts descarga el zip en el job de Linux, que lo sube y espera los resultados.
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 "$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 iosAndroid
Registra un runner Linux dedicado y protegido con la etiqueta android-linux y el executor shell de Bash. Desactiva Run untagged jobs para este runner y no le pongas la etiqueta del runner de contenedores Linux. En esa máquina, instala el JDK que necesita tu proyecto, las herramientas de línea de comandos de Android, tus versiones de compile SDK y build-tools, Bash, jq, curl 7.76 o posterior y certificados de CA de confianza. Define ANDROID_HOME, acepta las licencias del SDK y haz commit de un android/gradlew ejecutable con sus archivos del wrapper. El executor shell usa las herramientas del host; no instala un SDK de Android por ti.
Este ejemplo compila el APK de debug del módulo app y lo sube en el mismo job. Cambia a la vez la tarea de Gradle y ARTIFACT_PATH si usas flavors u otro módulo. Un .aab también funciona.
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 androidCondicionar el pipeline al resultado
El script inicia las ejecuciones con POST /api/v1/run.trigger e imprime sus IDs. Un plan de pruebas inicia una ejecución por cada caso de prueba. Después sigue cada ejecución con POST /api/v1/run.get, devolviendo el último cursor con waitMs: 25000, de modo que cada llamada espera hasta que la ejecución cambia. Una respuesta con timedOut: true solo significa que todavía no ha cambiado nada. Consulta Ejecuciones y evidencia.
| Resultado de la ejecución | El job | Qué dice el log |
|---|---|---|
| Passed | Se supera, si todas las demás ejecuciones también se superaron. Una ejecución superada con reintento (flaky) cuenta como superada. | Passed |
| Failed | Falla. | Abre la ejecución para ver qué paso falló y por qué, incluido un Runner problem. |
| Couldn't start | Falla. | No se pudo probar el entorno, por ejemplo porque el build no se instaló o faltaba un valor. Esto no dice nada de tu app; corrige la causa y vuelve a ejecutar. |
| Cancelled | Falla. | Cancelled |
Los errores de la API, un build que no supera el control de seguridad y agotar el tiempo de espera también hacen fallar el job. AUTONOMY_WAIT_SECONDS es el tiempo de espera total para todas las ejecuciones, 1.200 segundos por defecto. Mantén el límite de tiempo del job de CI por encima de ese valor más el tiempo de build y de subida. Detener el job de CI no cancela las ejecuciones que ya empezaron.
AUTONOMY_REQUEST_ID, con la plataforma añadida, se convierte en el idempotencyKey de run.trigger: si el mismo intento del job vuelve a enviar la petición, recibe las mismas ejecuciones en lugar de otras nuevas. Da a cada intento del job su propio ID. La parte anterior a los primeros dos puntos se envía como proveedor del despliegue, y la rama y el commit acompañan a las ejecuciones. Los formatos completos de petición y respuesta están en Operaciones.
Mantén allow_failure desactivado en los jobs de Autonomy.
Estrategia de ramas y eventos
Estas configuraciones solo permiten push, programaciones y pipelines lanzados a mano en la rama predeterminada protegida. Los pipelines de merge requests y el código de forks no reciben la clave. Deja los planes de smoke cortos para los despliegues habituales; usa una programación o un pipeline manual con un plan más amplio para las comprobaciones de release.
Para comprobar la previsualización de una merge request, ejecuta a mano la configuración de confianza de la rama predeterminada con la URL de previsualización revisada. No des acceso a las variables protegidas al código de merge requests que no sea de confianza. Si amplías las reglas de ramas, protege antes esas ramas y revisa sus cambios en el pipeline.
Referencias del proveedor
- Referencia de YAML de GitLab CI/CD: stages, reglas de workflow, etiquetas de runner, timeouts y
needs. - Variables de CI/CD: valores de variables, enmascarado y ramas protegidas.
- Artifacts de jobs e informes dotenv: pasar un zip o una URL entre jobs.
- Variables predefinidas: IDs de jobs, nombres de ramas y SHA de commits.
- Runners Linux alojados: la etiqueta
saas-linux-small-amd64y la ejecución en contenedores. - Runners macOS alojados y executor shell: disponibilidad de los runners y requisitos del host.
¿No lo encontraste?
GitHub Actions
Inicia ejecuciones de Autonomy desde un workflow de GitHub Actions para una previsualización web o un APK de Android, y haz fallar el workflow salvo que todas las ejecuciones se superen.
Jenkins
Inicia ejecuciones de Autonomy desde Jenkins para una URL de previsualización o un build móvil, y haz fallar el build salvo que todas las ejecuciones se superen.