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.
Usa GitHub Actions cuando quieras Autonomy como un paso de tu propio workflow: después de un script de despliegue propio, para un build móvil o antes de un job de release. El workflow inicia las ejecuciones, espera sus resultados y falla salvo que todas las ejecuciones sean Passed.
Para ejecutar un plan de pruebas en cada pull request sin archivo de workflow, usa la GitHub App. Ejecuta el plan de pull requests del proyecto contra el despliegue de previsualización e informa con un check de GitHub. Consulta Pruebas de pull requests.
Dónde va en el workflow
Pon el paso de Autonomy después del job que despliega la previsualización o compila la app. La ejecución debe recibir la URL definitiva que abriría quien revisa, o el propio archivo del build. El ejemplo web de abajo toma una URL ya desplegada; no despliega tu app.
Cada workflow de abajo es un archivo completo. Si lo añades a un workflow existente, conserva tus jobs de build y de despliegue y apunta needs al job que produce la URL o el build.
Secretos necesarios
En Settings → Secrets and variables → Actions del repositorio, añade:
| Nombre | Tipo | Valor |
|---|---|---|
AUTONOMY_API_KEY | Secret | Una clave de API de la organización que empieza por aut_. Los propietarios y administradores crean claves en Organization → API keys. |
AUTONOMY_TEST_PLAN_ID | Variable | 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>. |
PREVIEW_URL | Variable | Solo web: una URL HTTPS desplegada a la que Autonomy pueda llegar desde internet, no localhost. |
AUTONOMY_IOS_BUNDLE_ID | Variable | Solo iOS: el bundle ID de la app compilada. |
AUTONOMY_ANDROID_PACKAGE_NAME | Variable | Solo Android: el application ID del APK compilado. |
Valores opcionales que lee el script:
AUTONOMY_TEST_CASE_ID: ejecuta un caso de prueba en lugar de un plan. Su ID es la última parte de/p/<project>/tests/<id>. Define exactamente uno deAUTONOMY_TEST_CASE_IDyAUTONOMY_TEST_PLAN_ID.AUTONOMY_ENVIRONMENT_SLUG: ejecuta 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_SECONDS: el tiempo de espera total para todas las ejecuciones, 1.200 segundos por defecto.
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. Usa un plan de pruebas distinto para cada plataforma, para que cada resultado apunte a la app correcta.
URL de previsualización web
Crea una carpeta .ci en la raíz del repositorio y guarda este script como .ci/autonomy.sh. Todos los workflows de esta página, y las recetas de GitLab, Jenkins, Bitrise y CircleCI, usan este mismo archivo. Necesita Bash, jq y curl 7.76 o posterior; los runners alojados de GitHub los incluyen.
#!/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 .github/workflows/autonomy-web.yml. Lánzalo desde Actions → Autonomy web → Run workflow, opcionalmente con una URL de previsualización.
name: Autonomy web
on:
push:
branches: [main]
workflow_dispatch:
inputs:
preview_url:
description: Preview URL to test
required: false
jobs:
autonomy:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- name: Run Autonomy
env:
AUTONOMY_API_KEY: ${{ secrets.AUTONOMY_API_KEY }}
AUTONOMY_TEST_PLAN_ID: ${{ vars.AUTONOMY_TEST_PLAN_ID }}
PREVIEW_URL: ${{ inputs.preview_url || vars.PREVIEW_URL }}
AUTONOMY_REQUEST_ID: github-actions:${{ github.run_id }}:${{ github.run_attempt }}
AUTONOMY_BRANCH: ${{ github.head_ref || github.ref_name }}
AUTONOMY_COMMIT_SHA: ${{ github.sha }}
run: bash .ci/autonomy.sh webSi un job anterior despliega la previsualización, expón su URL como output del job, añade ese job a needs y define PREVIEW_URL: ${{ needs.deploy.outputs.url }}.
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 y después inicia las ejecuciones con el build subido.
iOS
Las pruebas en iOS llegarán pronto. Guarda esto para cuando estén disponibles. Hasta entonces, Autonomy no inicia ejecuciones en iOS.
Compila el .app de simulador en macOS, comprímelo allí en zip para que se conserven los permisos y los enlaces simbólicos, y pasa el zip a un job de Linux como artifact de Actions. Así la subida y la espera consumen minutos de Linux. Sustituye MyApp y la ruta del workspace por los tuyos; en un proyecto sin workspace, usa -project ios/MyApp.xcodeproj. Compila para el simulador, no un .ipa de dispositivo.
name: Autonomy iOS
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Build and zip the simulator app
run: |
set -euo pipefail
xcodebuild \
-workspace ios/MyApp.xcworkspace \
-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 "$GITHUB_WORKSPACE/.build/MyApp.app.zip" MyApp.app)
- uses: actions/upload-artifact@v5
with:
name: ios-simulator-app
path: .build/MyApp.app.zip
retention-days: 1
autonomy:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v5
with:
name: ios-simulator-app
path: .build
- name: Run Autonomy
env:
AUTONOMY_API_KEY: ${{ secrets.AUTONOMY_API_KEY }}
AUTONOMY_TEST_PLAN_ID: ${{ vars.AUTONOMY_TEST_PLAN_ID }}
APP_IDENTIFIER: ${{ vars.AUTONOMY_IOS_BUNDLE_ID }}
ARTIFACT_PATH: .build/MyApp.app.zip
AUTONOMY_REQUEST_ID: github-actions:${{ github.run_id }}:${{ github.run_attempt }}
AUTONOMY_BRANCH: ${{ github.ref_name }}
AUTONOMY_COMMIT_SHA: ${{ github.sha }}
run: bash .ci/autonomy.sh iosAndroid
Los builds de Android se hacen en Linux, así que el build y la subida caben en un solo job. Ajusta a la vez la tarea de Gradle y la ruta del APK si usas otro módulo u otra variante de build. Un .aab también funciona.
name: Autonomy Android
on:
push:
branches: [main]
workflow_dispatch:
jobs:
autonomy:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Build the debug APK
run: |
set -euo pipefail
cd android
./gradlew --no-daemon assembleDebug
- name: Run Autonomy
env:
AUTONOMY_API_KEY: ${{ secrets.AUTONOMY_API_KEY }}
AUTONOMY_TEST_PLAN_ID: ${{ vars.AUTONOMY_TEST_PLAN_ID }}
APP_IDENTIFIER: ${{ vars.AUTONOMY_ANDROID_PACKAGE_NAME }}
ARTIFACT_PATH: android/app/build/outputs/apk/debug/app-debug.apk
AUTONOMY_REQUEST_ID: github-actions:${{ github.run_id }}:${{ github.run_attempt }}
AUTONOMY_BRANCH: ${{ github.ref_name }}
AUTONOMY_COMMIT_SHA: ${{ github.sha }}
run: bash .ci/autonomy.sh androidSi el build ya está en un enlace HTTPS de corta duración, puedes omitir la subida: llama a run.trigger con targets.ios.artifactUrl o targets.android.artifactUrl y sourceMode: "url". Consulta Builds móviles.
Condicionar 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.
Estrategia de ramas y eventos
Estos workflows se ejecutan con los push a main y cuando se lanzan a mano. Ejecuta planes de smoke cortos en los push habituales, y planes más amplios en ramas de release, programaciones o ejecuciones manuales. Si un plan toca servicios de pago o datos de prueba escasos, lánzalo a mano.
GitHub no da secretos a los workflows de pull requests de forks, así que estos workflows no pueden ejecutarse para ellas. Para tener una ejecución en cada pull request, contra su despliegue de previsualización, usa la GitHub App: consulta Pruebas de pull requests.
Solución de problemas
La ejecución web empieza antes de que la previsualización esté lista
Separa el despliegue y las pruebas en jobs distintos y pasa la URL del despliegue a través de un output del job. Inicia Autonomy solo cuando la URL se conozca y responda.
Se rechaza la subida
El script imprime los motivos del control de seguridad. En iOS, comprueba que el build usó -sdk iphonesimulator y un destino de simulador: un .ipa de dispositivo no puede ejecutarse en un simulador. En Android, sube un .apk o un .aab.
Los pasos que usan valores del entorno no pudieron empezar
Una ejecución con una URL o un build explícitos no tiene entorno. Define AUTONOMY_ENVIRONMENT_SLUG con un entorno guardado, o quita las referencias {{env.NAME}} de esos casos de prueba.
El build de Android no encuentra el SDK
Los runners Ubuntu alojados de GitHub incluyen el SDK de Android. En un runner autoalojado, instala las herramientas de línea de comandos antes del paso de Gradle.
¿No lo encontraste?
GitHub App
Ejecuta un plan en cada pull request, pide ejecuciones en un comentario de la pull request y recibe el resultado como un check.
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.