Docs Autonomy
Intégrations

GitLab CI

Lancez des exécutions Autonomy depuis GitLab CI pour une URL de prévisualisation ou un APK Android, et faites échouer le pipeline si une exécution ne réussit pas.

Lancez Autonomy après un déploiement ou un build mobile, puis gardez le job GitLab ouvert jusqu'à ce que chaque exécution ait un résultat. Le job ne réussit que si toutes les exécutions sont Passed. Copiez le script partagé et la configuration de la plateforme que vous distribuez.

Placement du workflow

Placez Autonomy après le job de déploiement qui obtient l'URL de prévisualisation finale, ou après le job qui construit l'application mobile. L'exemple web ci-dessous prend une URL déjà déployée et vérifie qu'elle répond. Il ne déploie pas votre application.

Chaque exemple YAML est un .gitlab-ci.yml complet et alternatif. Pour l'ajouter à un pipeline existant, conservez vos jobs de build et de déploiement, fusionnez ses stages et faites pointer needs vers le job qui produit l'URL ou le build. N'exécutez que du code de confiance, sur une branche par défaut protégée.

Secrets requis

Dans Settings → CI/CD → Variables, ajoutez-les comme valeurs Variable, et non File, avec la portée d'environnement *. Protégez les variables et désactivez l'expansion des références de variables. Marquez la clé API Masked and hidden. Protégez la branche par défaut avant d'exécuter les exemples.

  • AUTONOMY_API_KEY : une clé API d'organisation commençant par aut_. Les propriétaires et les administrateurs créent les clés dans Organization → API keys. Une clé donne accès à tous les projets de l'organisation.
  • AUTONOMY_TEST_PLAN_ID : le plan de test à exécuter. Ouvrez-le dans Test plans du projet : l'identifiant est la dernière partie de l'URL de la page, /p/<project>/plans/<id>. Le plan doit contenir au moins un cas de test pour la plateforme. Pour exécuter plutôt un seul cas de test, définissez AUTONOMY_TEST_CASE_ID (issu de /p/<project>/tests/<id>) et ne définissez pas l'identifiant du plan. Définissez exactement l'un des deux.
  • PREVIEW_URL : l'URL de prévisualisation HTTPS prête pour l'exemple web. Autonomy doit pouvoir l'atteindre depuis Internet : pas localhost, ni un service qui n'existe qu'à l'intérieur du job de CI.
  • Pour iOS, ajoutez AUTONOMY_IOS_TEST_PLAN_ID et AUTONOMY_IOS_BUNDLE_ID. Pour Android, ajoutez AUTONOMY_ANDROID_TEST_PLAN_ID et AUTONOMY_ANDROID_PACKAGE_NAME. Chaque plan doit contenir des cas de test pour cette plateforme, et les identifiants doivent correspondre à l'application construite.
  • Facultatif : AUTONOMY_ENVIRONMENT_SLUG exécute sur un environnement enregistré du même projet, avec ses cibles et ses valeurs d'environnement, au lieu de l'URL ou du build de ce job. Le script saute alors le téléversement. AUTONOMY_WAIT_SECONDS modifie le temps d'attente total.

Sans environnement, une exécution n'a pas de valeurs d'environnement : une étape qui utilise {{env.NAME}} se termine en Couldn't start. Définissez AUTONOMY_ENVIRONMENT_SLUG, ou utilisez des cas de test qui n'ont pas besoin de valeurs d'environnement.

Les exemples définissent AUTONOMY_REQUEST_ID à partir de CI_JOB_ID : un job GitLab relancé envoie donc une nouvelle requête. La branche et le commit proviennent des variables prédéfinies de GitLab. Voir Plans de test pour regrouper des cas de test.

URL de prévisualisation web

Créez .ci/ à la racine du dépôt, enregistrez ce script sous .ci/autonomy.sh et committez-le avec votre fichier de pipeline. Toutes les recettes de cette page utilisent ce même script. Il nécessite Bash, jq et curl 7.76 ou ultérieur ; les jobs en conteneur les installent.

.ci/autonomy.sh
#!/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.'

Enregistrez ceci sous .gitlab-ci.yml. Activez les runners Linux hébergés par GitLab pour votre projet ; le job web et le job de téléversement iOS sélectionnent saas-linux-small-amd64. Sur GitLab Self-Managed, remplacez ce tag dans chaque job Linux en conteneur par celui de votre propre runner Linux à exécuteur Docker. Lancez le pipeline avec Build → Pipelines → New pipeline sur la branche par défaut protégée. Le log affiche Started Autonomy runs: et les identifiants des exécutions dès qu'Autonomy accepte la requête.

.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

Pour une URL créée dans votre job de déploiement existant, déplacez les entrées printf et artifacts:reports:dotenv dans ce job, puis remplacez le nom dans needs par le sien. Supprimez la variable de projet fixe PREVIEW_URL pour qu'elle n'écrase pas la valeur générée. Ne placez aucun identifiant dans preview.env ; GitLab le stocke comme artefact téléchargeable.

Transmission d'un build mobile

Utilisez le même .ci/autonomy.sh. Pour iOS et Android, il demande une URL de téléversement, téléverse le build, attend que le contrôle de sécurité revienne sans anomalie et lance les exécutions avec le build téléversé. N'utilisez que la plateforme dont le build se trouve dans votre dépôt.

iOS

Les tests iOS arrivent bientôt. Conservez cette section pour le moment où ils seront disponibles. D'ici là, Autonomy ne lance pas d'exécutions sur iOS.

Utilisez un runner macOS auto-hébergé, dédié et protégé, avec le tag macos, l'exécuteur shell Bash, Xcode et le SDK iOS Simulator. Désactivez Run untagged jobs pour ce runner et ne lui donnez pas le tag du runner Linux. Les runners macOS hébergés par GitLab nécessitent Premium ou Ultimate, hors programmes open source éligibles : un projet Free a donc besoin de son propre Mac. Linux ne peut pas effectuer ce build Xcode.

Cet exemple suppose ios/MyApp.xcodeproj, un schéma partagé MyApp et un produit MyApp.app. Remplacez ces trois noms par les vôtres, et installez les dépendances de votre projet avant xcodebuild. Pour un workspace, utilisez -workspace ios/MyApp.xcworkspace au lieu de -project. Il construit un .app de simulateur, et non un .ipa pour appareil physique.

Le job macOS compresse l'application en préservant les permissions et les liens symboliques avant que GitLab ne la déplace. needs:artifacts télécharge le zip dans le job Linux, qui le téléverse et attend les résultats.

.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 "$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

Enregistrez un runner Linux dédié et protégé, avec le tag android-linux et l'exécuteur shell Bash. Désactivez Run untagged jobs pour ce runner et ne lui donnez pas le tag du runner Linux en conteneur. Sur cette machine, installez le JDK dont votre projet a besoin, les outils en ligne de commande Android, vos versions du SDK de compilation et des build-tools, Bash, jq, curl 7.76 ou ultérieur et des certificats d'autorité de confiance. Définissez ANDROID_HOME, acceptez les licences du SDK et committez un android/gradlew exécutable avec ses fichiers de wrapper. L'exécuteur shell utilise les outils de la machine hôte ; il n'installe pas de SDK Android pour vous.

Cet exemple construit l'APK de debug du module app et le téléverse dans le même job. Si vous utilisez des flavors ou un autre module, modifiez ensemble la tâche Gradle et ARTIFACT_PATH. Un .aab fonctionne aussi.

.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

Conditionner le pipeline au résultat

Le script lance les exécutions avec POST /api/v1/run.trigger et affiche leurs identifiants. Un plan de test lance une exécution par cas de test. Le script suit ensuite chaque exécution avec POST /api/v1/run.get, en renvoyant le dernier cursor avec waitMs: 25000, si bien que chaque appel attend que l'exécution évolue. Une réponse avec timedOut: true signifie seulement que rien n'a encore changé. Voir Exécutions et preuves.

Résultat de l'exécutionLe jobCe qu'indique le log
PassedRéussit, si toutes les autres exécutions ont aussi réussi. Une réussite flaky compte comme une réussite.Passed
FailedÉchoue.Ouvrez l'exécution pour voir quelle étape a échoué et pourquoi, y compris un Runner problem.
Couldn't startÉchoue.L'environnement n'a pas pu être testé, par exemple parce que le build ne s'est pas installé ou qu'une valeur manquait. Cela ne dit rien de votre application ; corrigez la cause et relancez.
CancelledÉchoue.Cancelled

Les erreurs d'API, un build qui ne passe pas le contrôle de sécurité et l'épuisement du temps d'attente font aussi échouer le job. AUTONOMY_WAIT_SECONDS est le temps d'attente total pour toutes les exécutions, 1 200 secondes par défaut. Donnez au job de CI une limite de temps supérieure à cette valeur, augmentée du temps de build et de téléversement. Arrêter le job de CI n'annule pas les exécutions déjà lancées.

AUTONOMY_REQUEST_ID, complété par la plateforme, devient l'idempotencyKey de run.trigger : si la même tentative de job renvoie la requête, elle récupère les mêmes exécutions au lieu d'en créer de nouvelles. Donnez à chaque tentative de job son propre identifiant. La partie située avant le premier deux-points est envoyée comme fournisseur de déploiement, et la branche et le commit accompagnent les exécutions. Les formats complets de requête et de réponse figurent dans Opérations.

Laissez allow_failure désactivé sur les jobs Autonomy.

Stratégie de branches et d'événements

Ces configurations n'autorisent les pushs, les planifications et les pipelines lancés à la main que sur la branche par défaut protégée. Les pipelines de merge request et le code issu de forks ne reçoivent pas la clé. Gardez de courts plans de smoke test pour les déploiements ordinaires ; utilisez une planification ou un pipeline manuel avec un plan plus large pour les vérifications de release.

Pour vérifier la prévisualisation d'une merge request, exécutez à la main la configuration de confiance de la branche par défaut avec l'URL de prévisualisation relue. Ne donnez pas au code non fiable d'une merge request l'accès aux variables protégées. Si vous élargissez les règles de branche, protégez d'abord ces branches et relisez leurs modifications de pipeline.

Références de l'éditeur

Vous n’avez pas trouvé ?

On this page