Jenkins
Déclenchez Autonomy depuis Jenkins avec une URL de prévisualisation ou un artefact mobile et faites échouer le build si l'exécution ne réussit pas.
Ajoutez Autonomy à un Pipeline Jenkins après le déploiement de prévisualisation ou le build mobile. Cette page comprend le script d'API et trois Jenkinsfiles complets ; choisissez celui de votre plateforme.
Placement du workflow
Utilisez un job Pipeline avec Pipeline script from SCM, pointant vers une branche de confiance et le Jenkinsfile de votre dépôt. Commitez .ci/autonomy.sh avec ce fichier, créez les identifiants ci-dessous et sélectionnez Build Now. Aucun webhook ni Multibranch Pipeline n'est nécessaire.
Dans un pipeline existant, placez l'étape Autonomy après la fin du déploiement, une fois la prévisualisation accessible. L'exemple web autonome utilise une URL déjà déployée. Pour une prévisualisation dynamique, supprimez la liaison autonomy-preview-url et affectez la sortie de votre étape de déploiement à env.PREVIEW_URL avant cette étape.
Installez les plugins Pipeline, Pipeline: Declarative, Git et Credentials Binding, ainsi que leurs dépendances. Préparez un agent Linux portant le label linux, avec Bash, Git, jq et curl 7.76 ou ultérieur. Les labels sélectionnent vos agents ; Jenkins ne fournit pas de runners Linux ou macOS hébergés.
Secrets requis
Dans Manage Jenkins → Credentials, ajoutez des identifiants de type Secret text accessibles à ce job. Conservez exactement les ID indiqués. Créez uniquement les entrées nécessaires à la plateforme choisie.
| ID de l'identifiant | Valeur |
|---|---|
autonomy-api-key | Une clé API d'organisation aut_ du déploiement que vous appellerez. |
autonomy-api-url | URL de base de l'API, par exemple https://YOUR_DEPLOYMENT.convex.site, sans /api. |
autonomy-web-plan-id | ID de votre plan web dans Test Plans. |
autonomy-preview-url | URL HTTPS finale et accessible de la prévisualisation, pour l'exemple web. |
autonomy-ios-plan-id | ID de votre plan iOS dans Test Plans. |
autonomy-ios-bundle-id | Bundle ID de l'application construite, par exemple com.example.app. |
autonomy-android-plan-id | ID de votre plan Android dans Test Plans. |
autonomy-android-package-name | Application ID de l'APK construit, par exemple com.example.app. |
- Créez un plan de test contenant au moins un cas de test pour la plateforme choisie. Utilisez son ID Test Plans, pas l'ID d'un cas individuel de Test Cases.
- Prévoyez un runner Autonomy compatible. Une exécution en file d'attente ne valide pas le pipeline avant sa fin.
- Utilisez des cas qui n'ont pas besoin de valeurs d'exécution provenant d'un Environment enregistré. Ces exemples envoient des cibles explicites et ne sélectionnent pas d'Environment.
- Rendez la prévisualisation accessible au runner Autonomy. Une URL accessible uniquement à Jenkins ne fonctionnera pas.
withCredentials injecte les valeurs uniquement autour de l'appel API. Les blocs shell Groovy entre apostrophes laissent Bash développer les variables ; conservez set +x et n'affichez jamais la clé.
URL de prévisualisation web
Enregistrez ce script partagé dans .ci/autonomy.sh. Les trois Jenkinsfiles ci-dessous utilisent exactement ce fichier. Il transmet la cible, affiche tous les ID des exécutions mises en file d'attente et attend tous les verdicts.
#!/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.'Enregistrez ceci dans Jenkinsfile pour un job web. Les quatre identifiants web ci-dessus permettent de lancer le premier build manuel sans paramètres de job.
pipeline {
agent { label 'linux' }
options {
skipDefaultCheckout(true)
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Autonomy web') {
steps {
withCredentials([
string(credentialsId: 'autonomy-api-key', variable: 'AUTONOMY_API_KEY'),
string(credentialsId: 'autonomy-api-url', variable: 'AUTONOMY_API_URL'),
string(credentialsId: 'autonomy-web-plan-id', variable: 'AUTONOMY_TEST_PLAN_ID'),
string(credentialsId: 'autonomy-preview-url', variable: 'PREVIEW_URL')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
set +x
export AUTONOMY_REQUEST_ID="jenkins:${JOB_NAME}:${BUILD_NUMBER}"
export AUTONOMY_BRANCH="${BRANCH_NAME:-${GIT_BRANCH:-manual}}"
export AUTONOMY_COMMIT_SHA="$(git rev-parse HEAD)"
bash .ci/autonomy.sh web
'''
}
}
}
}
}Transfert d'artefact mobile
Utilisez un plan de test distinct pour chaque plateforme. Conservez .ci/autonomy.sh de la section web et remplacez Jenkinsfile par l'exemple correspondant. Le script demande une URL d'envoi, téléverse l'archive, exige une analyse d'artefact saine, puis déclenche le plan.
iOS
Préparez un véritable agent macOS portant le label macos, avec Xcode, son SDK Simulator, Git et zip. Jenkins n'a pas d'offre gratuite de macOS hébergé. Utilisez votre Mac existant ; l'achat de capacité relève d'une décision distincte. Résolvez les dépendances du projet avant xcodebuild et remplacez MyApp, le chemin du workspace et le nom de sortie par ceux de votre projet.
Construisez un .app de simulateur, pas un .ipa pour appareil physique. zip -y conserve les liens symboliques et l'archive préserve les permissions d'exécution. stash transmet cette archive à l'étape Linux ; Linux la téléverse sans la décompresser. Pour les applications volumineuses, configurez un gestionnaire d'artefacts distant Jenkins afin que le transfert ne surcharge pas le contrôleur.
pipeline {
agent none
options { skipDefaultCheckout(true) }
stages {
stage('Build iOS Simulator app') {
agent { label 'macos' }
options { timeout(time: 60, unit: 'MINUTES') }
steps {
checkout scm
sh '''#!/usr/bin/env bash
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
rm -f .build/MyApp.app.zip
(cd ios/build/Build/Products/Release-iphonesimulator && \
zip -qry "$WORKSPACE/.build/MyApp.app.zip" MyApp.app)
'''
stash name: 'ios-simulator', includes: '.build/MyApp.app.zip'
}
}
stage('Autonomy iOS') {
agent { label 'linux' }
options { timeout(time: 35, unit: 'MINUTES') }
steps {
checkout scm
unstash 'ios-simulator'
withCredentials([
string(credentialsId: 'autonomy-api-key', variable: 'AUTONOMY_API_KEY'),
string(credentialsId: 'autonomy-api-url', variable: 'AUTONOMY_API_URL'),
string(credentialsId: 'autonomy-ios-plan-id', variable: 'AUTONOMY_TEST_PLAN_ID'),
string(credentialsId: 'autonomy-ios-bundle-id', variable: 'APP_IDENTIFIER')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
set +x
export ARTIFACT_PATH='.build/MyApp.app.zip'
export AUTONOMY_REQUEST_ID="jenkins:${JOB_NAME}:${BUILD_NUMBER}"
export AUTONOMY_BRANCH="${BRANCH_NAME:-${GIT_BRANCH:-manual}}"
export AUTONOMY_COMMIT_SHA="$(git rev-parse HEAD)"
bash .ci/autonomy.sh ios
'''
}
}
}
}
}Android
Préparez un agent portant le label linux-android, avec Bash, Git, jq, curl 7.76 ou ultérieur, un JDK compatible avec votre version de Gradle et les paquets du SDK Android requis par votre projet. Définissez JAVA_HOME et ANDROID_HOME sur l'agent et acceptez les licences du SDK. L'exemple suppose un wrapper Gradle exécutable dans android/gradlew ; adaptez le répertoire et le chemin de l'APK à votre projet. Le build et l'envoi utilisent le même workspace.
pipeline {
agent { label 'linux-android' }
options {
skipDefaultCheckout(true)
timeout(time: 60, unit: 'MINUTES')
}
stages {
stage('Build Android APK') {
steps {
checkout scm
sh '''#!/usr/bin/env bash
set -euo pipefail
cd android
./gradlew --no-daemon assembleDebug
'''
}
}
stage('Autonomy Android') {
steps {
withCredentials([
string(credentialsId: 'autonomy-api-key', variable: 'AUTONOMY_API_KEY'),
string(credentialsId: 'autonomy-api-url', variable: 'AUTONOMY_API_URL'),
string(credentialsId: 'autonomy-android-plan-id', variable: 'AUTONOMY_TEST_PLAN_ID'),
string(credentialsId: 'autonomy-android-package-name', variable: 'APP_IDENTIFIER')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
set +x
export ARTIFACT_PATH='android/app/build/outputs/apk/debug/app-debug.apk'
export AUTONOMY_REQUEST_ID="jenkins:${JOB_NAME}:${BUILD_NUMBER}"
export AUTONOMY_BRANCH="${BRANCH_NAME:-${GIT_BRANCH:-manual}}"
export AUTONOMY_COMMIT_SHA="$(git rev-parse HEAD)"
bash .ci/autonomy.sh android
'''
}
}
}
}
}Conditionner le pipeline au verdict
Le script appelle POST /api/v1/run.trigger, puis interroge POST /api/v1/run.get pour chaque entrée renvoyée dans runIds. Une réponse en file d'attente signifie seulement que la demande est acceptée. La réussite exige que chaque résultat final contienne verdict: "passed" et validity: "valid".
Une erreur API, une analyse rejetée, un statut inattendu, un résultat en échec ou non vérifié, une annulation ou un délai dépassé produit un code de sortie non nul, ce qui fait échouer l'étape Jenkins. Le délai d'interrogation par défaut est de 1 200 secondes pour l'ensemble des exécutions renvoyées ; augmentez AUTONOMY_WAIT_SECONDS pour les plans plus longs et adaptez le timeout Jenkins. Un délai de pipeline dépassé n'annule pas une exécution Autonomy déjà en file d'attente.
AUTONOMY_REQUEST_ID combine le nom du job Jenkins et le numéro de build, auxquels le script ajoute la plateforme. Relancer le déclenchement dans le même build réutilise sa clé d'idempotence ; un nouveau build Jenkins obtient une nouvelle clé.
Stratégie de branche et d'événement
Commencez par des builds manuels depuis une branche par défaut de confiance. Une fois le transfert du déploiement opérationnel, ajoutez au job votre déclencheur SCM ou votre planification existante. Utilisez de courts plans de smoke test pour les builds de modifications de confiance et des plans plus étendus pour les builds de version ou planifiés.
Les scripts extraits du dépôt ont accès aux identifiants. N'exécutez pas le Jenkinsfile ou le script d'une pull request non fiable avec ces identifiants, même si sa branche correspond à un filtre. Utilisez un job de confiance pour tester une URL de prévisualisation approuvée. Séparez les agents disposant de ces identifiants de ceux qui exécutent des jobs non fiables.
Références du fournisseur
Les exemples utilisent la syntaxe documentée de Declarative Pipeline, les liaisons Secret text, les étapes stash et unstash et les agents Jenkins. Consultez Using a Jenkinsfile pour configurer Pipeline from SCM.