Xray & Jira — Le Test Management Industrialisé

Ce que personne ne vous dit. Les vrais pièges, les vraies solutions, par un architecte QA terrain.

👤 Julien Mer — QA Architect, 20+ ans | 📅 Février 2026 | ⏱️ ~3h de lecture approfondie

Pourquoi centraliser le test management dans Jira ?

Il y a une question que personne ne pose jamais en début de projet. Une question tellement évidente qu'elle passe sous le radar de tous les comités d'architecture, de tous les appels d'offre, de tous les "kick-off meetings" saupoudrés de slides PowerPoint : où sont vos tests ?

Pas "avez-vous des tests" — ça, tout le monde dit oui. Non. sont-ils ? Dans quel outil ? Dans quel format ? Et surtout : qui peut les retrouver à 3h du matin quand la prod est en feu ?

En 20 ans de missions — de la Défense nationale aux biotechnologies, de l'aérospatiale à la grande distribution — j'ai vu la même scène se répéter des dizaines de fois. L'équipe dev utilise Jira. L'équipe QA utilise TestRail. Ou Zephyr. Ou HP ALM. Ou, dans les cas les plus désespérants, un fichier Excel partagé sur un drive quelque part, avec 47 onglets et des cellules colorées en rouge-orange-vert selon un code que seul le créateur original (parti depuis 2 ans) comprenait.

Le résultat est toujours le même : deux mondes parallèles. Les développeurs vivent dans Jira. Les testeurs vivent ailleurs. Et entre les deux ? Un gouffre. Un gouffre qu'on essaie de combler avec des "synchros hebdo", des exports CSV, des réunions de status où quelqu'un dit "oui oui, c'est testé" sans que personne ne puisse le vérifier.

Histoire vraie — La banque et ses 58 000 tests

Une grande banque européenne gérait 58 000 scénarios de test dans HP Quality Center. 200 000 steps. Des équipes de 100+ testeurs réparties sur 3 pays. Le jour où ils ont voulu comprendre quels tests couvraient une exigence réglementaire spécifique — une question de conformité bancaire, pas un caprice — il a fallu 3 semaines pour reconstituer la traçabilité.

Trois semaines. Pour une question qui, dans un système bien conçu, prend 30 secondes avec un filtre JQL.

Leur migration vers Xray/Jira a pris 3-4 mois. Mais après : traçabilité complète, audit en temps réel, et surtout — les devs et les testeurs travaillaient enfin dans le même outil. Le premier "quick win" inattendu ? Les développeurs ont commencé spontanément à créer des tests, parce que c'était devenu aussi simple que créer un ticket Jira.

Le paysage en 2025 : un marché en consolidation

Le marché du test management est en pleine transformation. Atlassian a racheté, restructuré, imposé la migration Cloud. Les outils historiques (HP ALM, TestLink) sont en déclin. Et dans l'écosystème Jira, trois acteurs dominent :

Critère Xray Zephyr Scale TestRail
Intégration Jira Native (issue types Jira) Plugin (panels custom) Externe (connecteur)
Issue Types propres ✅ Test, Precondition, Test Set, Test Plan, Test Execution ❌ Panels séparés dans les issues ❌ Outil externe
JQL sur les tests ✅ Recherche native + fonctions custom ⚠️ Limité ❌ Pas applicable
Workflows Jira ✅ Workflows standard Jira sur les tests ⚠️ Workflows séparés ❌ Workflows propres à TestRail
REST API v1 + v2 (DC), REST + GraphQL (Cloud) REST API REST API
CI/CD natif Jenkins, GitLab, Bamboo, GitHub Actions Jenkins, Bamboo Via API uniquement
Pricing (Cloud, 50 users) ~$250-300/mois (Standard) ~$250/mois ~$600/mois (séparé de Jira)
Marketplace rank #1 test management (fastest growing) #2 (ex-TM4J, racheté SmartBear) Hors Marketplace
L'avis honnête — pas le discours commercial

Xray n'est pas parfait. Le reporting natif est son talon d'Achille — vous aurez besoin d'eazyBI ou Custom Charts pour des dashboards dignes de ce nom. L'interface peut être déroutante au début (trop de concepts). Et si vous n'êtes pas déjà sur Jira, n'adoptez pas Jira juste pour Xray — c'est mettre la charrue avant les bœufs.

Mais si votre équipe vit déjà dans Jira au quotidien ? L'argument "natif" d'Xray est imbattable. Un test Xray est un ticket Jira. Pas un panel greffé, pas un lien vers un outil externe. Un ticket. Avec son workflow, ses custom fields, son JQL, ses notifications, ses boards. C'est cette simplicité conceptuelle qui fait la vraie différence sur le terrain.

Les 5 niveaux de maturité du test management

Avant de foncer sur un outil, il faut savoir où vous en êtes. Voici les 5 niveaux de maturité que j'observe sur le terrain :

Niveau 1
Niveau 2
Niveau 3
Niveau 4
Niveau 5
🔥
Niveau 1 — Le Chaos
Tests dans Excel, Word, ou la tête des gens. Aucune traçabilité. "C'est testé" = "quelqu'un l'a regardé". Pas de couverture mesurable. Quand ça casse en prod, on cherche qui a fait quoi pendant des heures.
📋
Niveau 2 — L'Outillage Basique
Un outil existe (TestLink, Zephyr, Xray) mais mal configuré. Tests créés sans structure, exécutions non tracées, résultats pas exploités. L'outil est là, mais personne ne l'utilise vraiment. Les rapports sont vides ou faux.
⚙️
Niveau 3 — Le Process
Structure en place : Test Plans, Test Executions, traçabilité requirements→tests. Les résultats sont utilisés en sprint review. Mais tout est manuel. Les tests auto existent à côté, déconnectés. Le reporting est basique.
🔄
Niveau 4 — L'Intégration
CI/CD injecte automatiquement les résultats dans Xray. JUnit, Cucumber, Robot Framework — tout remonte. Dashboards temps réel. Traçabilité bidirectionnelle exigence↔test↔défaut. La QA est dans la pipeline, pas à côté.
🚀
Niveau 5 — L'Orchestration
Test as Code. Les tests sont versionnés dans Git, exécutés par la CI, résultats poussés automatiquement. Test Plans dynamiques. Risk-based testing calculé. AI-assisted test generation. L'outil s'efface derrière le process.
Où cibler ? La plupart des équipes que je rencontre sont au niveau 2 ou 3. L'objectif réaliste avec Xray est d'atteindre le niveau 4 en 3-6 mois. Le niveau 5 est un objectif à 12-18 mois qui nécessite une vraie stratégie DevOps en place.

Quand NE PAS utiliser Xray

C'est la section que vous ne trouverez dans aucune documentation officielle. Mais elle est cruciale pour éviter des erreurs coûteuses :

N'utilisez PAS Xray si...
🚫
Vous n'êtes pas sur Jira
Xray est un add-on Jira. Adopter Jira + Xray ensemble quand l'équipe utilise Azure DevOps ou GitLab ? C'est un changement trop brutal. Privilégiez un outil standalone comme TestRail ou qTest.
🚫
Votre équipe fait < 5 personnes
Pour une petite équipe avec peu de tests, un simple dossier Jira avec des tâches "Test" suffit. Le coût cognitif d'Xray (5 issue types, workflows, configuration) n'est pas justifié. Commencez simple.
🚫
Vous faites 100% test auto sans test management
Si vos tests sont 100% automatisés, dans Git, avec des rapports Allure/HTML natifs, et que personne ne fait de test manuel — Xray apporte peu. Votre "test management" c'est Git + CI. Et c'est très bien.
🚫
Votre budget est serré et vous êtes > 100 users
Au-delà de 100 utilisateurs, le pricing Xray Cloud devient significatif. Si le budget est contraint, Zephyr Squad (gratuit jusqu'à 10 users) ou TestLink (open source) peuvent être des alternatives viables.

Les éditions Xray en 2025-2026 : comprendre l'offre

Atlassian a introduit un modèle "App Editions" que Xray a adopté. Voici la cartographie réelle :

Édition Cible Features clés Pricing indicatif
Standard (Cloud) PME, équipes en croissance Toutes les features core : issue types, traçabilité, CI/CD, Test Case Importer, reporting de base ~$10/mois (10 users), ~$5-6/user ensuite
Advanced (Cloud) Mid-market, teams matures Standard + insights d'adoption, AI étendue, storage/API augmentés, test design avancé Premium sur Standard (TBD)
Enterprise (app séparée) Grandes organisations Versioning/approvals des tests, Dynamic Test Plans, Remote Job Triggers, AI Test Model Generation (Sembi IQ) ~$15/mois (10 users), ~$6.50/user ensuite
Data Center On-premise, régulé Équivalent Enterprise, hébergement propre, REST API v1+v2, PAT auth Licence annuelle (négociable)
Le piège du "Standard suffit" : Pour 80% des équipes, l'édition Standard couvre tous les besoins. L'Enterprise se justifie principalement pour les organisations avec des exigences d'audit formel (versioning/approval de tests) ou qui ont besoin de plans de test dynamiques. Ne surpayez pas "au cas où".

Le vrai ROI : au-delà du discours marketing

Parlons chiffres concrets, pas de slides commercial. Quand une équipe passe d'Excel/TestLink à Xray bien configuré, voici ce qu'on observe réellement sur le terrain :

Métrique Avant (Excel/outil séparé) Après (Xray intégré) Impact
Temps pour trouver un test lié à une exigence 15-45 minutes (recherche manuelle) 10 secondes (JQL / lien direct) 🔥 -98%
Temps de création de rapport de couverture 2-4 heures (copier-coller) 30 secondes (gadget Jira) 🔥 -99%
Résultats auto dans le test management Import manuel J+1 Temps réel (CI/CD → Xray) ✅ Temps réel
Visibilité dev sur les tests Quasi nulle Même board, mêmes filtres ✅ Collaboration
Préparation audit conformité 2-3 semaines Quelques heures (exports automatisés) 🔥 -90%
SBB — Le réseau ferroviaire suisse

Les Chemins de fer fédéraux suisses (SBB) sont le cœur du transport public helvétique. Plus de 600 applications, 100 000+ cas de test, 1 000+ employés formés. En 2018, lors de leur transformation agile, ils ont choisi Xray pour centraliser tout leur test management. Le point clé ? Ils testent à la fois du logiciel et du matériel — signalisation, billetterie, affichage en gare. Xray leur a permis de tout unifier dans un seul référentiel, avec une traçabilité complète du requirement hardware au test d'intégration.

L'écosystème Xray : ce qui se branche dessus

🔌
CI/CD
Jenkins (plugin dédié), GitLab CI, GitHub Actions, Bamboo, Azure DevOps. Import automatique des résultats JUnit/TestNG/Cucumber/Robot Framework/NUnit/pytest.
📊
Reporting
eazyBI (analytics avancé), Custom Charts for Jira, Document Generator (Xporter) pour exports DOCX/PDF/XLSX, gadgets Jira natifs.
🤖
Automation
Selenium, Playwright, Cypress, Appium, Robot Framework, Katalon Studio. Tout framework qui produit du JUnit/xUnit XML peut s'intégrer.
🧠
AI (2025)
AI Test Case Generation (toutes éditions) — génère des cas de test depuis les requirements. AI Test Model Generation (Enterprise) — modèles visuels du système via Sembi IQ.
• • •

Voilà pour le contexte. Vous avez maintenant la carte du territoire. Vous savez pourquoi centraliser, quand le faire (et quand ne pas le faire), combien ça coûte, et ce que ça rapporte concrètement.

Dans l'onglet suivant, on va plonger dans le sujet que personne n'ose aborder : les 10 anti-patterns qui font échouer 80% des implémentations Xray. Ce sont des erreurs que j'ai vues et corrigées sur le terrain — pas de la théorie de consultant.

Les 10 Anti-Patterns qui détruisent votre implémentation Xray

Installer Xray prend 5 minutes. Le configurer correctement prend 5 jours. Le massacrer prend environ 2 sprints.

Ce qui suit n'est pas un catalogue théorique. Ce sont 10 patterns destructeurs que j'ai observés — et souvent corrigés — sur des projets réels. Chacun est accompagné de ses symptômes, de son histoire, et surtout de sa solution. Parce que repérer un anti-pattern sans donner le fix, c'est comme diagnostiquer une maladie sans prescrire le traitement.

#1
💀 Le Test Orphelin
"On a 3 000 tests mais on ne sait pas ce qu'ils testent"

C'est l'anti-pattern le plus répandu et le plus sournois. Des milliers de tests existent dans Xray, mais aucun n'est relié à une exigence, une user story, ou un epic. Ils flottent dans le vide, comme des satellites sans orbite.

🔴 Symptômes : Le Traceability Report est vide. Le Coverage Report montre 0%. Quand un PO demande "est-ce que cette feature est testée ?", personne ne peut répondre en moins de 30 minutes. Les tests sont regroupés par module technique au lieu d'être liés aux requirements.
Vu sur le terrain

Une équipe retail avait migré 2 500 tests depuis TestLink. Import CSV parfait, tous les steps préservés. Mais personne n'avait recréé les liens requirement↔test. Résultat : 6 mois après la migration, le management a demandé un rapport de couverture pour un audit. Réponse de l'équipe : "on ne peut pas le générer". Le management a conclu que Xray ne servait à rien et a failli annuler les licences. Le problème n'était pas l'outil — c'était l'absence de traçabilité.

✅ Le Fix : Activez "Requirement Coverage" dans chaque projet Xray. Définissez quels issue types sont des requirements (Story, Epic, Bug...) via le mapping Xray. Puis imposez une règle simple : aucun test ne passe en review sans au moins un lien "tests" vers une issue Jira. Utilisez le JQL issueFunction in requirements() AND NOT issueFunction in tested() pour trouver les exigences non couvertes.
#2
💀 L'Exécution Fantôme
"Les tests passent... mais pas dans Xray"

Celui-là est vicieux. L'équipe exécute des tests — manuels ou automatisés — mais ne crée jamais de Test Execution dans Xray. Les testeurs exécutent "de tête", notent les résultats dans Confluence, dans Slack, ou nulle part. Les tests automatisés tournent dans Jenkins mais les résultats ne remontent pas.

🔴 Symptômes : Le Test Execution coverage est à 0% malgré des semaines de test. Les statuts des Test Runs sont tous "TODO". Le burndown de test est plat. L'historique d'exécution est vide — impossible de savoir si un test a déjà été exécuté, quand, par qui, et avec quel résultat.
✅ Le Fix : Pour les tests manuels : systématisez la création de Test Execution issues en début de sprint. Liez-les au Test Plan du sprint et à la Fix Version. Pour les tests auto : configurez le pipeline CI/CD pour pousser les résultats via l'API Xray (POST /api/v2/import/execution/junit). Un résultat de test qui n'est pas dans Xray n'existe pas.
Jenkinsfile
// Exemple Jenkins : push résultats JUnit vers Xray Cloud
stage('Report to Xray') {
    steps {
        step([$class: 'XrayImportBuilder',
            endpointName: '/junit',
            importFilePath: 'target/surefire-reports/*.xml',
            serverInstance: 'xray-cloud-instance',
            projectKey: 'PROJ',
            testPlanKey: 'PROJ-1234'
        ])
    }
}
#3
💀 Le Cimetière de Tests
"On a 15 000 tests. 12 000 sont obsolètes."

Personne n'ose supprimer un test. "Et si on en a besoin plus tard ?" Alors les tests s'accumulent. Des tests pour des features supprimées il y a 3 ans. Des tests dupliqués (version manuelle + version auto). Des tests qui échouent systématiquement et que tout le monde ignore.

🔴 Symptômes : Le Test Repository est un marécage. Les recherches retournent des dizaines de résultats non pertinents. Les Test Plans incluent des tests obsolètes qui faussent les métriques. Le temps de revue d'un Test Plan explose parce qu'il faut trier le bon grain de l'ivraie.
✅ Le Fix : Instaurez une "Test Hygiene Review" trimestrielle. Utilisez le JQL pour identifier les tests jamais exécutés : issuetype = Test AND created < -180d AND NOT issueFunction in executedTests(). Créez un label DEPRECATED et un workflow status "Archived". Les tests obsolètes ne sont pas supprimés (traçabilité) mais exclus des Test Plans actifs. Objectif : < 20% de tests non exécutés dans les 6 derniers mois.
#4
💀 Le Custom Field Carnival
"On a créé 47 custom fields. Personne ne remplit les 45 derniers."

L'admin Jira enthousiaste découvre qu'on peut ajouter des custom fields aux issues Xray. Alors il en crée. Pour le navigateur. Pour l'environnement. Pour la priorité de test (en plus de la priorité Jira). Pour la complexité. Pour le "test type" (en plus du type Xray). Pour la date prévue d'exécution. Pour le nom du testeur assigné (en plus de l'assignee Jira)...

🔴 Symptômes : L'écran de création d'un test ressemble à un formulaire d'impôts. Les testeurs remplissent le minimum et laissent le reste vide. Les dashboards basés sur ces fields montrent "No data" partout. Le temps de création d'un test passe de 2 à 15 minutes.
✅ Le Fix : Règle d'or : utilisez les champs natifs avant de créer des custom fields. Xray fournit déjà : Test Type, Test Environments (pour navigateur/OS), Components, Labels, Priority, Assignee. Avant de créer un custom field, posez-vous 3 questions : (1) Est-ce que Jira/Xray n'a pas déjà ce champ ? (2) Est-ce qu'un label ou un component ne suffirait pas ? (3) Est-ce que quelqu'un va réellement filtrer/reporter sur ce champ ? Si vous ne répondez pas "oui" aux 3, ne le créez pas.
La doc Xray elle-même le dit : "Don't reinvent the wheel. Use standard fields, such as labels and components, and avoid unnecessary creation of custom fields." Ce n'est pas moi qui l'invente — c'est le vendor qui prévient ses propres utilisateurs.
#5
💀 Le Copier-Coller Massif
"On duplique les tests pour chaque environnement/version"

Au lieu d'utiliser les Test Environments et les Test Plans versionnés, l'équipe crée des copies de chaque test : "Login - Chrome", "Login - Firefox", "Login - Safari", "Login - v2.1", "Login - v2.2"... Le Test Repository explose en taille, et quand il faut modifier un step, il faut le faire dans 12 copies.

🔴 Symptômes : Le nombre de tests croît exponentiellement sans que la couverture augmente. Les modifications d'un test ne sont pas propagées aux copies. Des incohérences apparaissent entre versions "identiques". Le ratio tests/exigences est absurdement élevé (50 tests pour 1 exigence).
✅ Le Fix : Un test = un scénario logique. La variabilité est gérée par :
Test Environments (Chrome, Firefox, iOS) : configurez-les dans Xray, assignez-les aux Test Executions
Fix Versions : liez les Test Executions aux versions Jira
Test Data : utilisez les champs "Test Data" des steps pour paramétrer
Preconditions : partagez les prérequis entre tests au lieu de les dupliquer dans chaque test
#6
💀 Le Test Plan Fourre-Tout
"Un seul Test Plan pour tout le projet, depuis le début"

Au lieu de créer un Test Plan par sprint, par release, ou par campagne, l'équipe maintient UN Test Plan géant qui grossit indéfiniment. 500 tests, 800 tests, 1 200 tests... Le Test Plan devient un monstre ingérable où les résultats du sprint 1 se mélangent avec ceux du sprint 47.

🔴 Symptômes : Le Test Plan met 30 secondes à charger. Le Test Execution Board est illisible. Impossible de connaître le taux de réussite d'un sprint spécifique. Les tests en échec d'il y a 6 mois polluent les métriques actuelles.
✅ Le Fix : Adoptez une stratégie de Test Plans structurée :
1 Test Plan par Sprint (ex: "TP - Sprint 24 - v3.2.0") regroupant les tests de la release
1 Test Plan "Régression" permanent, exécuté à chaque release
1 Test Plan par campagne spéciale (UAT, Performance, Sécurité)
Liez chaque Test Plan à une Fix Version Jira. Archivez les anciens (ne pas supprimer). Utilisez les filtres pour ne voir que les plans actifs.
#7
💀 Le Manuel-Auto Schizophrène
"Les mêmes tests existent en version manuelle ET automatisée"

L'équipe a commencé par des tests manuels. Puis l'automatisation est arrivée. Au lieu de convertir les tests manuels en tests automatisés (en changeant le Test Type), on a créé des tests auto en parallèle. Résultat : chaque scénario a deux entrées dans Xray. La couverture est comptée en double. Les résultats se contredisent.

🔴 Symptômes : Un requirement montre 2 tests liés pour le même scénario. Le Coverage Report affiche des chiffres gonflés. Un test manuel montre "PASS" et son doublon auto montre "FAIL" — sans que personne ne sache lequel croire. Le Test Repository contient des paires "TC-001 Manual" / "TC-001 Auto".
✅ Le Fix : Dans Xray, un Test a un champ Test Type : Manual, Cucumber, Generic. Quand vous automatisez un test manuel, ne créez pas un nouveau test — changez le type du test existant de "Manual" à "Generic" ou "Cucumber". Les liens vers les requirements sont préservés, l'historique d'exécution est conservé, et la couverture reste exacte. Un scénario = un test = un type.
#8
💀 Le Reporting Aveugle
"On utilise Xray depuis 1 an mais on n'a aucun dashboard"

L'outil est en place, les tests sont exécutés, les résultats sont enregistrés... mais personne ne les regarde. Pas de dashboard. Pas de gadgets. Pas de rapports automatisés. Les données sont là, mais invisibles. Le management demande des "KPIs qualité" et l'équipe QA ouvre Excel pour les calculer manuellement.

🔴 Symptômes : Le dashboard Jira du projet n'a aucun gadget Xray. Les sprint reviews montrent des captures d'écran faites à la main. Personne ne connaît le taux de tests en échec, le coverage rate, ou le test execution progress. Le management perçoit la QA comme une "boîte noire".
✅ Le Fix : Créez un dashboard Jira dédié QA avec les gadgets essentiels : Overall Test Coverage (% requirements couvertes), Test Execution Progress (par Test Plan), Test Runs by Status (pie chart), Flaky Tests (tests qui alternent PASS/FAIL). Ajoutez eazyBI pour les tendances historiques. Et surtout : affichez ce dashboard en sprint review. Les métriques qu'on ne montre pas n'existent pas dans l'esprit du management.
Soyons honnêtes sur le reporting Xray

Le reporting natif d'Xray est son point faible reconnu. Les gadgets de base sont limités. Pour des dashboards réellement exploitables, vous aurez besoin d'eazyBI (~$10-50/mois selon la taille) ou de Custom Charts for Jira. C'est un coût additionnel que personne ne mentionne au moment de la vente. Prévoyez-le dans votre budget dès le départ.

#9
💀 Le Workflow Ignoré
"Tous les tests sont en status 'Draft' depuis la création du projet"

Xray permet de définir des workflows pour les Tests (Draft → In Review → Approved → Deprecated). Mais personne ne les configure, ou personne ne les utilise. Tous les tests sont "Draft" à vie. Il n'y a aucun process de review, aucune approbation, aucun contrôle qualité sur les tests eux-mêmes.

🔴 Symptômes : Des tests incomplets ou incorrects sont exécutés et donnent des résultats trompeurs. Il n'y a pas de "Definition of Done" pour un cas de test. Quand un auditeur demande "qui a approuvé ce test ?", personne ne peut répondre.
✅ Le Fix : Configurez un workflow simple pour le Test issue type : Draft → In Review → Approved → Deprecated. Ajoutez une transition condition : seul un "QA Lead" ou "Test Manager" peut déplacer un test vers "Approved". Créez un filtre JQL : issuetype = Test AND status = Draft AND created < -14d pour repérer les tests oubliés en Draft. Pour les organisations réglementées, l'édition Enterprise offre le versioning et les approbations formelles.
#10
💀 Le Vendor Lock-In Inconscient
"On a mis toute notre QA dans Xray sans plan de sortie"

C'est le plus stratégique de tous les anti-patterns. L'équipe a investi 2 ans à construire un patrimoine de tests dans Xray. 10 000 tests, des centaines de Test Plans, des dashboards, des intégrations CI/CD... Et un jour, Atlassian change les pricing, ou l'entreprise décide de migrer vers Azure DevOps, ou Xray discontinue une feature critique. Et là, la question tombe : "Comment on sort ?"

🔴 Symptômes : Aucun export régulier des données. Pas de documentation du modèle de données Xray. Pas de scripts de backup automatisé. Le jour où il faut migrer, c'est la panique.
✅ Le Fix : Préparez votre sortie dès le premier jour :
— Documentez votre modèle de données (quels issue types, quels custom fields, quels liens)
— Scriptez un export mensuel via l'API REST (tests, executions, résultats, liens)
— Utilisez le Document Generator pour des exports réguliers en XLSX
— Évitez la sur-customisation Xray — utilisez les champs Jira standard quand possible
— Gardez vos tests Cucumber/Gherkin dans Git (pas seulement dans Xray) — c'est votre assurance-vie
Python
# Script de backup mensuel - export des tests via API Xray Cloud
import requests
import json
from datetime import datetime

XRAY_CLIENT_ID = "your_client_id"
XRAY_CLIENT_SECRET = "your_client_secret"

# 1. Authentification
auth = requests.post(
    "https://xray.cloud.getxray.app/api/v2/authenticate",
    json={"client_id": XRAY_CLIENT_ID, "client_secret": XRAY_CLIENT_SECRET}
)
token = auth.json()

# 2. Export des tests via GraphQL
headers = {"Authorization": f"Bearer {token}"}
query = """
{
    getTests(jql: "project = PROJ AND issuetype = Test", limit: 100) {
        total
        results {
            issueId
            testType { name }
            steps { action data result }
            preconditions { issueId }
        }
    }
}
"""
response = requests.post(
    "https://xray.cloud.getxray.app/api/v2/graphql",
    json={"query": query},
    headers=headers
)

# 3. Sauvegarde locale
backup = {
    "date": datetime.now().isoformat(),
    "data": response.json()
}
with open(f"xray_backup_{datetime.now().strftime('%Y%m%d')}.json", "w") as f:
    json.dump(backup, f, indent=2)

print(f"✅ Backup: {response.json()['data']['getTests']['total']} tests exportés")
• • •
10 / 10
anti-patterns couverts — vérifiez votre projet maintenant

Si vous reconnaissez 3+ de ces patterns dans votre projet, une revue d'architecture Xray est urgente.

Architecture Xray : le modèle de données qu'il faut comprendre

Avant de configurer quoi que ce soit, il faut comprendre comment Xray fonctionne sous le capot. Parce que la majorité des erreurs de configuration viennent d'une incompréhension du modèle de données. Et ce modèle est à la fois la force et la complexité d'Xray.

Les 6 issue types Xray : le socle

Quand vous installez Xray dans un projet Jira, 5 nouveaux issue types apparaissent (plus un concept virtuel) :

📋 Requirement ← covers → 🧪 Test ← grouped in → 📦 Test Set

🧪 Test ← planned in → 📅 Test Plan ← contains → ▶️ Test Execution

▶️ Test Execution ← generates → 🏃 Test Run ← produces → 🐛 Defect
Issue Type Rôle Analogue réel Jira natif ?
Test Le cas de test lui-même — steps, expected results, test data. C'est le template, pas l'exécution. La recette de cuisine ✅ Issue type Jira
Pre-Condition Prérequis partageable entre tests : config, données, état initial. Lié à N tests. Les ingrédients à préparer avant ✅ Issue type Jira
Test Set Regroupement logique de tests (par fonctionnalité, par module). Organisationnel uniquement. Un classeur de recettes ✅ Issue type Jira
Test Plan Campagne de test planifiée. Regroupe des tests pour une version/sprint/release. Calcule les métriques. Le menu du restaurant pour la semaine ✅ Issue type Jira
Test Execution L'exécution effective d'un ensemble de tests dans un contexte donné (version, environnement). La session en cuisine ce soir ✅ Issue type Jira
Test Run Le résultat d'UN test dans UNE exécution. PASS/FAIL/TODO + evidence + defects. Le plat terminé (réussi ou raté) ❌ Entité Xray interne
L'erreur classique : Confondre Test et Test Run. Le Test est le template (les steps, les expected results). Le Test Run est l'instance (l'exécution concrète avec résultat, date, testeur, evidence). Un même Test peut avoir 100 Test Runs si vous l'avez exécuté 100 fois.

Le Test Repository : organiser sans se noyer

Le Test Repository est la vue arborescente qui organise vos tests en dossiers. C'est l'équivalent d'un explorateur de fichiers pour vos cas de test. Et c'est là que beaucoup d'équipes se perdent — parce qu'il n'y a pas UNE bonne organisation, il y a VOTRE organisation.

Voici les 3 patterns d'organisation les plus courants :

📁 Pattern 1 : Par Fonctionnalité (recommandé pour 80% des projets)
📂 Test Repository
├── 📁 Authentication
│   ├── 📁 Login
│   │   ├── 🧪 TC-001: Login with valid credentials
│   │   ├── 🧪 TC-002: Login with invalid password
│   │   └── 🧪 TC-003: Login with 2FA
│   ├── 📁 Registration
│   │   ├── 🧪 TC-010: Register new user
│   │   └── 🧪 TC-011: Register with existing email
│   └── 📁 Password Reset
│       └── 🧪 TC-020: Reset via email link
├── 📁 Cart & Checkout
│   ├── 📁 Cart Management
│   └── 📁 Payment Processing
├── 📁 Admin Panel
│   ├── 📁 User Management
│   └── 📁 Product Management
└── 📁 API Tests
    ├── 📁 REST Endpoints
    └── 📁 GraphQL Queries

Avantage : Aligné avec la structure fonctionnelle de l'application. Facile à naviguer pour les POs et les devs. Mappe naturellement sur les requirements.

📁 Pattern 2 : Par Type de Test (pour les équipes avec des rôles séparés)
📂 Test Repository
├── 📁 Functional Tests
│   ├── 📁 Smoke Tests
│   ├── 📁 Regression Tests
│   └── 📁 Integration Tests
├── 📁 Non-Functional Tests
│   ├── 📁 Performance Tests
│   ├── 📁 Security Tests
│   └── 📁 Accessibility Tests
├── 📁 E2E Tests
│   ├── 📁 Critical Paths
│   └── 📁 Edge Cases
└── 📁 Exploratory
    └── 📁 Session-based Charters

Avantage : Clair pour les équipes avec des testeurs spécialisés (perf, sécu, auto). Facilite le reporting par type de test.

Inconvénient : Un même scénario peut apparaître dans plusieurs catégories (un test de login est à la fois "fonctionnel" et "smoke"). Risque de duplication.

📁 Pattern 3 : Hybride (grands projets multi-modules)
📂 Test Repository
├── 📁 Module A - Backend
│   ├── 📁 Unit Tests (auto - Generic)
│   ├── 📁 API Tests (auto - Generic)
│   └── 📁 Integration Tests (auto/manual)
├── 📁 Module B - Frontend
│   ├── 📁 Component Tests (auto - Cucumber)
│   ├── 📁 E2E Tests (auto - Generic)
│   └── 📁 Visual Regression (auto - Generic)
├── 📁 Cross-Module
│   ├── 📁 Smoke Suite
│   ├── 📁 Regression Suite
│   └── 📁 UAT Scenarios (manual)
└── 📁 Non-Functional
    ├── 📁 Performance
    └── 📁 Security

Avantage : Reflète l'architecture technique réelle. Chaque module owner gère ses tests. Scalable.

Custom Fields Xray : les champs calculés qui changent tout

Xray ajoute des custom fields spéciaux aux issues Jira. Ce sont des champs calculés — ils se mettent à jour automatiquement en fonction des exécutions et de la couverture :

Custom Field Visible sur Ce qu'il calcule Usage JQL
Requirement Status Requirements (Story, Epic...) Couverture de l'exigence basée sur les tests liés et leurs résultats pour une version donnée "Requirement Status" = OK
Test Run Status Tests Status agrégé du test basé sur les dernières exécutions pour une version "TestRunStatus" = FAIL
Test Environments Test Executions Environnements de test (Chrome, Firefox, iOS...) "Test Environments" = Chrome
Tests count Test Set, Test Plan Nombre de tests dans le set/plan
Pro tip : Le champ "Requirement Status" est particulièrement puissant. Ajoutez-le à votre board Agile sur les cards Story/Epic. Vous verrez en temps réel, directement sur le board, quelles stories sont couvertes (vert), partiellement testées (jaune), ou non couvertes (gris). C'est le genre de visibilité qui change la dynamique d'un sprint review.

JQL étendu : les super-pouvoirs Xray

Xray enrichit JQL avec des fonctions custom qui transforment la recherche de tests :

JQL
# 1. Exigences non couvertes par des tests
issueFunction in requirements() AND NOT issueFunction in tested()

# 2. Tests qui n'ont jamais été exécutés
issuetype = Test AND NOT issueFunction in executedTests()

# 3. Tests en échec dans le dernier sprint
issuetype = Test AND "TestRunStatus" = FAIL AND fixVersion = "Sprint-24"

# 4. Exigences dont la couverture est KO
issuetype = Story AND "Requirement Status" = NOK

# 5. Tests liés à un epic spécifique
issueFunction in testingRequirements("epic = PROJ-100")

# 6. Test Executions d'un environnement spécifique
issuetype = "Test Execution" AND "Test Environments" = "Chrome 120"

# 7. Tests Cucumber (BDD) du projet
issuetype = Test AND "Test Type" = Cucumber AND project = PROJ

# 8. Preconditions orphelines (non liées à des tests)
issuetype = "Pre-Condition" AND NOT issueFunction in preconditionTests()
Important : Les fonctions issueFunction in requirements(), tested(), executedTests(), etc. sont spécifiques à Xray. Elles ne fonctionnent que si Xray est installé et activé dans le projet. Elles ne sont pas disponibles dans les "filtres rapides" des boards — uniquement dans les filtres JQL classiques et les gadgets.

Configuration des Workflows : le piège de la sur-ingénierie

Xray utilise les workflows Jira standard pour ses issue types. Ça veut dire que vous pouvez créer un workflow à 15 statuts avec 47 transitions, des validators, des conditions, des post-functions... Mais ne le faites pas.

Voici les workflows recommandés :

🧪 Workflow pour Test (cas de test)
Draft In Review Approved Deprecated

Draft : Test en cours de rédaction. Pas encore exécutable.

In Review : Soumis à un peer ou QA Lead pour validation des steps et expected results.

Approved : Prêt à être inclus dans un Test Plan et exécuté.

Deprecated : Obsolète mais conservé pour traçabilité historique.

Transition "Approved" → condition : seuls les rôles "QA Lead" ou "Test Manager" (organisations réglementées)

▶️ Workflow pour Test Execution
Open In Progress Done

Simple et efficace. Le statut se met à jour automatiquement quand les Test Runs sont complétés. Pas besoin de complexifier.

📅 Workflow pour Test Plan
Draft Active Completed Archived

Draft : Plan en préparation (sélection des tests, assignation).

Active : Sprint/release en cours, exécutions en cours.

Completed : Toutes les exécutions terminées, métriques finales.

Archived : Conservé pour l'historique, exclu des dashboards actifs.

Intégration CI/CD : le nerf de la guerre

C'est là que Xray prend tout son sens. Un test management déconnecté de la CI/CD, c'est comme un GPS déconnecté du satellite — il montre une carte, mais pas votre position réelle.

Pipeline typique avec Xray :

Git Push CI Build Run Tests JUnit XML Xray API Import Test Exec created

Test Exec Test Runs updated Coverage recalculated Dashboard updated

Jenkins — Plugin Xray dédié

Jenkinsfile
pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh 'mvn test -Dsurefire.reportFormat=xml'
            }
        }
        stage('Import to Xray') {
            steps {
                step([$class: 'XrayImportBuilder',
                    endpointName: '/junit',
                    importFilePath: 'target/surefire-reports/*.xml',
                    importToSameExecution: 'true',
                    serverInstance: 'your-xray-instance',
                    projectKey: 'MYPROJ',
                    fixVersion: '3.2.0',
                    testPlanKey: 'MYPROJ-456',
                    testEnvironments: 'Chrome 120'
                ])
            }
        }
    }
}

GitLab CI — Via API REST

gitlab-ci.yml
test:
  stage: test
  script:
    - pytest --junitxml=report.xml tests/
  after_script:
    - |
      # Authenticate with Xray Cloud
      TOKEN=$(curl -s -X POST \
        "https://xray.cloud.getxray.app/api/v2/authenticate" \
        -H "Content-Type: application/json" \
        -d "{\"client_id\":\"$XRAY_CLIENT_ID\",\"client_secret\":\"$XRAY_CLIENT_SECRET\"}")
      
      # Import JUnit results
      curl -X POST \
        "https://xray.cloud.getxray.app/api/v2/import/execution/junit?projectKey=PROJ&testPlanKey=PROJ-456" \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/xml" \
        -d @report.xml
  artifacts:
    reports:
      junit: report.xml

GitHub Actions

github-actions.yml
- name: Import results to Xray
  env:
    XRAY_CLIENT_ID: ${{ secrets.XRAY_CLIENT_ID }}
    XRAY_CLIENT_SECRET: ${{ secrets.XRAY_CLIENT_SECRET }}
  run: |
    TOKEN=$(curl -s -X POST \
      "https://xray.cloud.getxray.app/api/v2/authenticate" \
      -H "Content-Type: application/json" \
      -d "{\"client_id\":\"$XRAY_CLIENT_ID\",\"client_secret\":\"$XRAY_CLIENT_SECRET\"}")

    curl -X POST \
      "https://xray.cloud.getxray.app/api/v2/import/execution/junit?projectKey=PROJ" \
      -H "Authorization: Bearer $(echo $TOKEN | tr -d '\"')" \
      -H "Content-Type: application/xml" \
      -d @test-results/junit.xml

Formats d'import supportés

Format Endpoint API Frameworks
JUnit XML /import/execution/junit JUnit, TestNG, pytest, NUnit, Mocha, Jest
Cucumber JSON /import/execution/cucumber Cucumber-JVM, CucumberJS, SpecFlow, Behave
Robot Framework /import/execution/robot Robot Framework (output.xml)
xUnit XML /import/execution/xunit xUnit.net, MSTest
Xray JSON /import/execution Format custom Xray (le plus flexible)
Le format Xray JSON est le plus puissant mais le moins documenté. Il permet d'envoyer des résultats avec des informations enrichies : defects liés, evidence (captures d'écran en base64), commentaires, timestamps précis. Utilisez-le quand JUnit XML ne suffit pas.
Xray JSON Format
{
  "testExecutionKey": "PROJ-789",
  "info": {
    "summary": "Regression Suite - Build #1234",
    "startDate": "2025-12-01T10:00:00+01:00",
    "finishDate": "2025-12-01T10:45:00+01:00",
    "testPlanKey": "PROJ-456",
    "testEnvironments": ["Chrome 120", "Ubuntu 24"]
  },
  "tests": [
    {
      "testKey": "PROJ-100",
      "status": "PASSED",
      "start": "2025-12-01T10:00:00+01:00",
      "finish": "2025-12-01T10:02:30+01:00",
      "comment": "All assertions passed"
    },
    {
      "testKey": "PROJ-101",
      "status": "FAILED",
      "start": "2025-12-01T10:02:31+01:00",
      "finish": "2025-12-01T10:05:00+01:00",
      "comment": "Expected 200, got 500",
      "defects": ["PROJ-999"],
      "evidences": [
        {
          "data": "iVBORw0KGgo...(base64)...",
          "filename": "screenshot_error.png",
          "contentType": "image/png"
        }
      ]
    }
  ]
}

BDD avec Xray : Cucumber natif

Xray supporte nativement le format Cucumber/Gherkin. Les tests de type "Cucumber" dans Xray contiennent directement le scénario Gherkin. Et voilà ce qui est puissant : vous pouvez exporter les feature files depuis Xray vers votre repo, et importer les résultats d'exécution après le run CI.

Cycle BDD avec Xray :

PO écrit le Gherkin dans Xray Export .feature files Dev écrit les step defs

CI exécute Cucumber JSON results Import vers Xray Coverage mise à jour
Gherkin dans Xray
# Ce scénario est stocké dans un Test Xray de type "Cucumber"
# Issue key: PROJ-200, lié à Story PROJ-50

@PROJ-200
Feature: User Authentication
  As a registered user
  I want to log in to the application
  So that I can access my dashboard

  @PROJ-201
  Scenario: Successful login with valid credentials
    Given I am on the login page
    When I enter "user@test.com" as email
    And I enter "ValidPass123!" as password
    And I click the login button
    Then I should be redirected to the dashboard
    And I should see "Welcome back" message

  @PROJ-202
  Scenario Outline: Failed login with invalid credentials
    Given I am on the login page
    When I enter "<email>" as email
    And I enter "<password>" as password
    And I click the login button
    Then I should see "<error_message>"

    Examples:
      | email          | password  | error_message          |
      | bad@test.com   | pass123   | Invalid credentials    |
      | user@test.com  |           | Password is required   |
      |                | pass123   | Email is required      |
Le tag @PROJ-200 est la clé : c'est lui qui fait le lien entre le feature file dans Git et le Test dans Xray. Quand Cucumber exécute ce scénario et que les résultats sont importés, Xray sait automatiquement que c'est le Test PROJ-200 qui a été exécuté. Sans ce tag, l'import crée un nouveau test au lieu de mettre à jour l'existant.
• • •

Vous avez maintenant la vision complète de l'architecture Xray : le modèle de données, l'organisation du repository, les workflows, l'intégration CI/CD, et le BDD natif. L'onglet suivant va couvrir la traçabilité et le reporting — la partie qui justifie tout l'investissement aux yeux du management.

Traçabilité : la chaîne qui lie tout

La traçabilité, c'est la capacité de répondre à cette question à tout moment : "Pour cette exigence, quels tests existent, quand ont-ils été exécutés, quel est leur résultat, et quels défauts ont été trouvés ?"

C'est simple à énoncer. C'est terriblement difficile à maintenir dans la durée. Et c'est ce que les auditeurs vérifient en premier.

La matrice de traçabilité Xray

Chaîne de traçabilité complète :

📋 Epic → décomposé en → 📋 Story → testée par → 🧪 Test

🧪 Test → exécuté dans → ▶️ Test Exec → produit → 🏃 Test Run

🏃 Test Run → peut créer → 🐛 Bug → lié à → 📋 Story (originale)

La beauté de ce modèle : la boucle est fermée. D'une exigence, vous remontez au bug. D'un bug, vous retrouvez l'exigence. D'un test, vous voyez tout son historique d'exécution. C'est bidirectionnel et automatique — à condition que les liens soient créés.

Requirement Coverage : le KPI n°1

Le Coverage Report est probablement la fonctionnalité la plus importante d'Xray. Il vous montre, en un coup d'œil, quelles exigences sont couvertes par des tests, lesquelles ne le sont pas, et quel est le résultat des dernières exécutions.

Pour l'activer :

1️⃣
Activer Coverage sur le projet
Project Settings → Xray → Requirement Coverage → Enable. Définissez quels issue types sont des "requirements" (Story, Epic, Task, Bug...).
2️⃣
Lier Tests aux Requirements
Sur chaque Test, ajoutez un lien "tests" vers la Story/Epic correspondante. Ou depuis la Story, créez un test via le panneau Xray.
3️⃣
Configurer le calcul
Xray Settings → Custom Fields → Requirement Status : choisissez "Latest test run" ou "Earliest unreleased version" selon votre stratégie.
4️⃣
Visualiser
Le champ "Requirement Status" apparaît sur chaque Story/Epic. Ajoutez-le aux boards, aux filtres, aux dashboards. Vert = couvert et PASS. Rouge = couvert mais FAIL. Gris = non couvert.
Le piège de la couverture à 100% : Avoir 100% de couverture ne veut PAS dire que votre application est bien testée. Ça veut dire que chaque requirement a au moins un test lié. Si ce test est superficiel, ou s'il ne couvre qu'un seul scénario alors que l'exigence en nécessite 10, votre couverture est un mirage. La qualité des tests compte autant que leur existence.

Les KPIs QA essentiels dans Xray

KPI Source Cible Alerte si...
Requirement Coverage % Traceability Report > 90% < 70% → exigences non testées
Test Execution Pass Rate Test Plan Board > 85% < 70% → qualité en danger
Test Execution Progress Test Plan % executed 100% avant release < 80% à J-2 → risque de release
Flaky Test Rate Historique Test Runs < 5% > 10% → tests instables à fixer
Defect Leakage Bugs trouvés en prod vs pre-prod < 10% > 20% → couverture insuffisante
Test Automation Ratio Tests par Test Type > 60% < 30% → automatisation en retard
Average Time to Test Test Execution dates Variable Tendance croissante → tests trop complexes

Dashboards : construire la visibilité

Un dashboard QA efficace raconte une histoire en 10 secondes. Le manager qui l'ouvre doit comprendre immédiatement : "est-ce qu'on peut releaser ou pas ?" Pas besoin de 15 gadgets — 5 suffisent.

Le dashboard QA minimum viable

🥧
Gadget 1 — Coverage Pie
Pie chart des requirements par statut de couverture (Covered OK / Covered NOK / Not Covered). Filtre : project = PROJ AND issuetype = Story AND fixVersion = currentRelease()
📊
Gadget 2 — Execution Progress
Barre de progression du Test Plan actif. % PASS / FAIL / TODO / EXECUTING. Visible d'un coup d'œil.
📈
Gadget 3 — Trend Line
Courbe d'évolution des tests PASS vs FAIL au fil des sprints (via eazyBI). Montre la tendance qualité.
🐛
Gadget 4 — Open Defects
Liste des bugs ouverts liés aux tests en échec. Priorité + assignee + âge. Le "top des problèmes" du moment.
Gadget 5 — Flaky Tests
Top 10 des tests qui alternent PASS/FAIL (via eazyBI). Ces tests parasitent vos métriques et doivent être fixés en priorité.

Compliance & Audit : la traçabilité qui sauve

Pour les organisations réglementées (banque, pharma, aérospatiale, défense), la traçabilité n'est pas un "nice-to-have" — c'est une obligation légale. L'auditeur veut voir :

Qui a approuvé ce test ?
Workflow avec status "Approved" + Jira audit log. Enterprise : versioning + e-signature.
Quand a-t-il été exécuté ?
Test Run avec timestamp précis, environnement, testeur assigné.
Quelle exigence couvre-t-il ?
Lien "tests" vers le requirement. Traceability Report bidirectionnel.
Quelles preuves existent ?
Evidence (screenshots, logs) attachées au Test Run. Document Generator pour export PDF formel.
Pour les audits : Le Document Generator (Xporter) permet de générer des rapports de test formels en DOCX/PDF avec un template personnalisé : logo entreprise, tableaux de résultats, signatures, conformité aux normes (DO-178C, IEC 62304, ISO 26262...). Préparez le template une fois, générez à la demande.

eazyBI : le reporting qui manque à Xray

Soyons clairs : sans eazyBI (ou Custom Charts), le reporting Xray est basique. Les gadgets natifs Jira couvrent le minimum mais ne permettent pas les analyses croisées, les tendances historiques, ou les drill-downs. eazyBI est le complément quasi-obligatoire pour un reporting digne de ce nom.

Ce qu'eazyBI permet que les gadgets Jira ne font pas :

Besoin Gadgets Jira natifs eazyBI
Couverture par sprint/release ⚠️ Limité (un filtre par gadget) ✅ Drill-down dynamique
Tendance PASS/FAIL sur 6 mois ❌ Pas d'historique ✅ Time series natif
Heatmap par module/composant ✅ Pivot table + heatmap
Top flaky tests ✅ Calcul de variance sur les runs
Export automatisé PDF ✅ Scheduled reports
Corrélation défauts ↔ couverture ✅ Cross-dimension analysis
Le coût caché du reporting

eazyBI coûte ~$10/mois pour 10 utilisateurs, ~$50/mois pour 100. Ajoutez-le au budget Xray dès le départ. Un test management sans reporting exploitable, c'est comme avoir une voiture sans tableau de bord — vous roulez, mais vous ne savez pas à quelle vitesse ni combien de carburant il reste.

• • •

La traçabilité et le reporting sont ce qui transforme Xray d'un "outil de plus" en un avantage stratégique. Quand le management voit un dashboard clair qui montre la couverture, les tendances, et les risques en temps réel, la QA passe de centre de coût à centre de confiance.

L'onglet final va couvrir le sujet le plus pratique : comment migrer vers Xray depuis votre outil actuel, et comment industrialiser votre setup pour le long terme.

Migration vers Xray : le guide de survie

Migrer un test management, c'est comme déménager une bibliothèque. Vous pouvez mettre tous les livres dans des cartons et les balancer dans le camion — mais à l'arrivée, bonne chance pour retrouver quoi que ce soit. Ou vous pouvez planifier, trier, étiqueter, et arriver avec une bibliothèque mieux organisée qu'avant le déménagement.

Ce chapitre couvre les migrations réelles que j'ai observées, avec les outils, les pièges, et les stratégies concrètes.

La règle d'or : ne jamais faire de Big Bang

JAMAIS de migration Big Bang. Migrer 10 000 tests d'un coup un vendredi soir "pour que ce soit prêt lundi" est la recette du désastre. Toujours : pilot d'abord (1 projet, ~100 tests), valider, ajuster le mapping, puis étendre progressivement.

Migration depuis HP ALM / Quality Center

HP ALM est le mastodonte que beaucoup d'organisations veulent quitter — licences coûteuses, interface vieillissante, impossibilité de travailler sur plusieurs projets simultanément. Mais c'est aussi la migration la plus complexe, parce que le modèle de données ALM est fondamentalement différent de Jira.

🔧 Outils disponibles

1. Xray Test Case Importer (intégré) : Import CSV/Excel avec mapping wizard. Supporte HP ALM/QC v12.5x directement. Pour les versions plus anciennes : export CSV depuis ALM → import via l'importer.

2. ALM Xray Jumper (BRIGHTKNOCK) : Outil tiers spécialisé. Extraction des données via l'UI HP ALM, mapping pré-défini ALM→Xray, import via REST API Jira/Xray. Accès aux données ALM via OTA + tables DB + file repository. C'est l'option "industrielle" pour les gros volumes.

3. Scripts custom : Pour les cas complexes, un script Python/Java qui extrait via l'API ALM OTA et injecte via l'API REST Xray. Plus de contrôle, mais plus de travail.

📊 Mapping des entités ALM → Xray
HP ALM Xray / Jira Notes
Requirements Story / Epic Créer les requirements comme issues Jira d'abord
Test (dans Test Plan) Test (issue type) Mapping direct des steps
Test Set Test Set ou Test Plan Selon l'usage (regroupement logique vs campagne)
Test Instance (dans Test Lab) Test Execution ⚠️ L'historique des runs est le plus dur à migrer
Run Test Run Statuts à remapper (Passed→PASS, Failed→FAIL...)
Defect Bug (Jira) Recréer les liens Test Run → Bug
Test Folders Test Repository (dossiers) Structure arborescente à recréer
Attachments Jira Attachments ⚠️ Migration séparée via API REST
Case Study — La banque aux 58 000 tests

Migration HP QC → Xray/Jira. 58 000 scénarios, 200 000 steps, équipes réparties sur 3 pays. Timeline : 3-4 mois incluant la phase pilot, le déploiement, la formation interne, et la migration elle-même.

Ce qui a bien marché : pas d'impact sur les performances Jira, unification des équipes dev/test, création de défauts pendant l'exécution avec auto-linking, travail multi-projets simultané (impossible dans HP QC), audits facilités.

Ce qui était dur : l'historique d'exécution (partiellement migré), les custom fields ALM (nécessitant un remapping complet), les pièces jointes volumineuses (migration séparée en batch).

Migration depuis TestLink

TestLink est open source et populaire dans les PME. La migration est plus simple car le modèle de données est plus léger.

🔧 Processus de migration TestLink → Xray

Étape 1 : Export depuis TestLink au format XML

Étape 2 : Utiliser les scripts de conversion disponibles sur le GitHub Xray pour convertir XML → CSV

Étape 3 : Import CSV via le Test Case Importer Xray avec les fichiers de configuration JSON

Étape 4 : Validation post-import (vérifier steps, preconditions, liens)

TestLink Xray Notes
Test SuiteTest Set / Dossier RepositoryAu choix selon la structure souhaitée
Test CaseTestMapping direct
PreconditionsPre-Condition (issue type)Créées séparément et liées
Test StepsTest StepsMapping direct (action, expected result)
Custom FieldsCustom Fields Jira⚠️ Mapping manuel requis

Migration depuis Zephyr (Squad / Scale)

Ironie du sort : beaucoup d'équipes migrent de Zephyr vers Xray tout en restant dans Jira. La raison ? L'intégration "native" d'Xray (issue types) vs les panels custom de Zephyr.

🔧 Processus Zephyr → Xray

1. Export des tests Zephyr en Excel/XML depuis l'interface Zephyr

2. Conversion du format via script (les custom step fields Zephyr ne sont pas directement supportés dans Xray — mapping vers "Additional step information")

3. Import via Test Case Importer avec le wizard de field mapping

4. Recréation manuelle des liens Test↔Requirement (les plus critiques)

5. Validation : comparer les comptages (nombre de tests, steps, liens) entre ancien et nouveau

Point d'attention : Les custom step fields de Zephyr n'ont pas d'équivalent direct dans Xray. Ils doivent être mappés vers "Additional step information" ou des custom fields Jira. Prévoyez du temps pour ce remapping.

Migration depuis Excel / CSV

Le cas le plus fréquent. Et paradoxalement, le plus risqué — parce que les fichiers Excel sont rarement structurés de manière cohérente.

🔧 Préparer un Excel pour l'import Xray

Le Test Case Importer attend un CSV avec des colonnes spécifiques. Voici la structure recommandée :

CSV
"Test ID","Summary","Description","Priority","Labels","Step #","Action","Data","Expected Result"
"TC-001","Login avec identifiants valides","Vérifier le login standard","High","smoke,regression","1","Ouvrir la page de login","URL: /login","Page de login affichée"
"TC-001","","","","","2","Entrer email et mot de passe","user@test.com / Pass123","Champs remplis"
"TC-001","","","","","3","Cliquer sur 'Connexion'","","Redirection vers dashboard"
"TC-002","Login avec mot de passe invalide","Vérifier le rejet","High","smoke","1","Ouvrir la page de login","URL: /login","Page de login affichée"
"TC-002","","","","","2","Entrer email et mauvais mdp","user@test.com / bad","Message d'erreur affiché"

Règles critiques :

— Un test sur plusieurs lignes : la première ligne contient le Summary/Priority/Labels, les suivantes seulement les steps
— Séparez les CSV par type de test (Manual, Cucumber, Generic)
— Nettoyez les données AVANT l'import : supprimez les tests obsolètes, uniformisez les labels
— Utilisez un fichier de configuration JSON pour rendre l'import reproductible

Migration depuis TestRail

TestRail est un outil solide, mais externe à Jira. La migration vers Xray vise à tout centraliser.

🔧 Processus TestRail → Xray

1. Export depuis TestRail : Sections + Test Cases en CSV via l'interface d'admin

2. Attention aux templates TestRail : Step-by-Step, Plain Text, BDD-Gherkin — chacun a un format CSV différent

3. Import CSV via Test Case Importer Xray

4. Les custom fields TestRail doivent être recréés dans Jira puis mappés

5. Les test results historiques : export via API TestRail → import via API Xray (script custom nécessaire)

Dans l'autre sens (Xray → TestRail) : Si vous devez exporter d'Xray vers TestRail, utilisez le Document Generator avec un template XLSX custom (nommé ex: TestRailMigrationTemplate), puis importez le XLSX via le CSV importer de TestRail.

Les 7 pièges de migration que personne ne mentionne

💣
1. Les liens perdus
La traçabilité requirement↔test↔defect est le cauchemar n°1 de toute migration. Les liens CSV ne se recréent pas automatiquement. Prévoyez un script de relinkage post-import ou faites-le à la main pour les liens critiques.
💣
2. L'historique d'exécution
Les résultats historiques (qui a exécuté quoi, quand, avec quel résultat) ne migrent quasiment jamais proprement. Les dates originales sont perdues, remplacées par la date d'import. Si l'historique est critique pour l'audit, conservez une archive de l'ancien outil.
💣
3. Les pièces jointes
Les attachments (screenshots, logs, documents) nécessitent des scripts séparés via REST API. L'import CSV ne les prend pas en charge. C'est souvent la partie la plus longue et la plus fastidieuse.
💣
4. Les custom fields perdus
Les custom fields de l'outil source n'ont pas toujours d'équivalent dans Xray. Ils doivent être recréés dans Jira, mappés dans l'importer, et validés post-import. Prévoyez un document de mapping exhaustif.
💣
5. Le cimetière importé
Migrer 10 000 tests dont 7 000 sont obsolètes, c'est importer un cimetière. Nettoyez AVANT de migrer. Archivez les tests inutilisés depuis 12+ mois dans l'ancien outil, puis migrez uniquement le vivant.
💣
6. Le mapping amnésique
Vous avez passé 3 jours à configurer le mapping des champs... et personne ne l'a documenté. 6 mois plus tard, quand il faut migrer un deuxième projet, on recommence à zéro. Documentez TOUT le mapping dans un doc partagé.
💣
7. Le Big Bang du vendredi
"On migre tout ce week-end et lundi tout le monde est sur Xray." Non. Un pilot d'abord (1 projet, 50-100 tests). Validez avec les utilisateurs. Ajustez. Puis étendez projet par projet. Timeline réaliste pour un gros projet : 3-4 mois.

Industrialisation : Test as Code

Le niveau ultime d'intégration, c'est le "Test as Code" : vos tests vivent dans Git, sont exécutés par la CI, et les résultats remontent automatiquement dans Xray. Le test management devient une extension naturelle du cycle de développement, pas un process parallèle.

Test as Code avec Xray :

📝 .feature files dans Git CI exécute Cucumber JSON results → Xray API

Xray crée/met à jour Test Execution Test Runs updated Coverage recalculated

PO modifie scenario dans Xray Export .feature via API PR auto dans Git
Python
# Script d'export des feature files depuis Xray Cloud
# À intégrer dans votre pipeline CI ou un cron job

import requests
import zipfile
import io
import os

XRAY_BASE = "https://xray.cloud.getxray.app/api/v2"

def authenticate():
    resp = requests.post(f"{XRAY_BASE}/authenticate", json={
        "client_id": os.environ["XRAY_CLIENT_ID"],
        "client_secret": os.environ["XRAY_CLIENT_SECRET"]
    })
    return resp.json()  # Returns the token string

def export_features(token, test_keys):
    """Export feature files from Xray for given test keys"""
    headers = {"Authorization": f"Bearer {token}"}
    # Export Cucumber features for specific tests
    resp = requests.get(
        f"{XRAY_BASE}/export/cucumber",
        params={"keys": ";".join(test_keys)},
        headers=headers
    )
    
    if resp.status_code == 200:
        # Response is a zip file containing .feature files
        z = zipfile.ZipFile(io.BytesIO(resp.content))
        z.extractall("src/test/resources/features/")
        print(f"✅ Exported {len(z.namelist())} feature files")
        for name in z.namelist():
            print(f"   → {name}")
    else:
        print(f"❌ Export failed: {resp.status_code}")

if __name__ == "__main__":
    token = authenticate()
    # Export all Cucumber tests from project
    export_features(token, ["PROJ-200", "PROJ-201", "PROJ-202"])

Stratégie de sortie : préparer l'exit dès le jour 1

C'est le conseil le plus contre-intuitif de cette formation : le jour où vous adoptez Xray, préparez votre plan de sortie. Pas parce que vous allez partir — mais parce que savoir que vous pouvez partir vous rend libre dans vos choix technologiques.

📋
Documentez votre modèle
Quels issue types, quels custom fields, quels workflows, quels liens. Un document vivant mis à jour à chaque changement de configuration.
💾
Backups automatisés
Script mensuel d'export via API REST : tests, executions, résultats, liens. Stockez en JSON ou CSV sur un storage indépendant de Jira.
📁
Gardez le code dans Git
Les feature files Cucumber, les scripts de test, les configurations — tout doit vivre dans votre repository Git, pas seulement dans Xray. Git est votre assurance-vie.
⚖️
Évitez la sur-customisation
Plus vous customisez Xray (custom fields, workflows complexes, post-functions), plus la migration sera douloureuse. Utilisez les champs Jira standard quand possible.
• • •

Récapitulatif : la checklist de l'architecte QA

Phase Action Critère de succès
Avant Évaluer la maturité actuelle (niveau 1-5) Document d'évaluation partagé
Avant Choisir l'édition (Standard vs Enterprise) Décision argumentée, budget validé
Setup Configurer les issue types, workflows, repository Structure validée par l'équipe QA + PO
Setup Activer Requirement Coverage + mapping Champ "Requirement Status" visible sur les boards
Migration Pilot migration (1 projet, ~100 tests) Tests importés, liens vérifiés, exécution validée
Migration Migration progressive des autres projets Comptages validés, mapping documenté
Intégration Connecter CI/CD → Xray (JUnit/Cucumber import) Résultats auto remontent en temps réel
Reporting Créer le dashboard QA (5 gadgets essentiels) Dashboard présenté en sprint review
Long terme Test Hygiene Review trimestrielle < 20% de tests non exécutés en 6 mois
Long terme Backup automatisé + documentation exit Export mensuel vérifié, modèle documenté
✅ Prêt
Vous avez la théorie, les pièges, et les solutions. Maintenant, exécutez.

La différence entre un setup Xray qui marche et un qui échoue n'est pas l'outil — c'est la rigueur de la mise en œuvre.

API Xray : le référentiel complet

L'API est le vrai moteur d'Xray. Sans elle, vous avez un outil de saisie manuelle. Avec elle, vous avez une plateforme d'orchestration QA programmable. Tout ce qui est possible dans l'interface est faisable — et souvent mieux — via l'API.

Ce chapitre est votre cheat sheet : les endpoints, les schémas de données, les exemples prêts à copier dans vos scripts et pipelines.

Cloud vs Data Center : deux API différentes

Aspect Xray Cloud Xray Data Center / Server
Base URL https://xray.cloud.getxray.app/api/v2 https://your-jira.com/rest/raven/2.0/api
Auth OAuth2 — Client ID + Client Secret → Bearer Token PAT (Personal Access Token) ou Basic Auth
GraphQL ✅ Oui (/api/v2/graphql) ❌ Non
REST ✅ v2 ✅ v1 + v2
Rate Limits Oui (dépend de l'édition — Standard < Advanced < Enterprise) Limité par l'infra serveur
Webhooks ✅ Via Jira Automation + API ✅ Via Jira Webhooks

Authentification

Xray Cloud — OAuth2 Bearer Token

Bash
# 1. Obtenir le token (valide ~1h)
TOKEN=$(curl -s -X POST \
  "https://xray.cloud.getxray.app/api/v2/authenticate" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }')

# Le token est retourné directement comme string (avec les guillemets)
echo "Token: $TOKEN"

# 2. Utiliser le token dans les appels suivants
curl -X GET "https://xray.cloud.getxray.app/api/v2/..." \
  -H "Authorization: Bearer $(echo $TOKEN | tr -d '\"')"
Python
import requests
import os

class XrayCloudClient:
    """Client Xray Cloud réutilisable avec gestion du token."""
    
    BASE_URL = "https://xray.cloud.getxray.app/api/v2"
    
    def __init__(self):
        self.client_id = os.environ["XRAY_CLIENT_ID"]
        self.client_secret = os.environ["XRAY_CLIENT_SECRET"]
        self.token = None
    
    def authenticate(self):
        """Obtenir un Bearer token OAuth2."""
        resp = requests.post(
            f"{self.BASE_URL}/authenticate",
            json={
                "client_id": self.client_id,
                "client_secret": self.client_secret
            }
        )
        resp.raise_for_status()
        self.token = resp.json()  # String directe
        return self.token
    
    @property
    def headers(self):
        if not self.token:
            self.authenticate()
        return {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json"
        }
    
    def get(self, endpoint, **kwargs):
        return requests.get(
            f"{self.BASE_URL}{endpoint}",
            headers=self.headers, **kwargs
        )
    
    def post(self, endpoint, **kwargs):
        return requests.post(
            f"{self.BASE_URL}{endpoint}",
            headers=self.headers, **kwargs
        )

# Usage
xray = XrayCloudClient()
xray.authenticate()
print("✅ Connecté à Xray Cloud")

Xray Data Center — PAT ou Basic Auth

Bash
# Option 1 : Personal Access Token (recommandé, Jira DC >= 8.14)
curl -X GET "https://your-jira.com/rest/raven/2.0/api/test/PROJ-100" \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"

# Option 2 : Basic Auth (legacy)
curl -X GET "https://your-jira.com/rest/raven/2.0/api/test/PROJ-100" \
  -u "username:password"
Sécurité : Ne stockez JAMAIS les credentials dans le code. Utilisez des variables d'environnement (XRAY_CLIENT_ID, XRAY_CLIENT_SECRET), des Vault (HashiCorp Vault, AWS Secrets Manager), ou les secrets de votre CI (Jenkins Credentials, GitLab CI Variables).

Schéma logique de l'API

Architecture des endpoints Xray Cloud REST API v2 :

/api/v2/
├── authenticate .................... POST → Bearer Token
├── graphql ......................... POST → Requêtes GraphQL

├── import/
│ ├── execution .................. POST → Import résultats (Xray JSON)
│ ├── execution/junit ............ POST → Import JUnit XML
│ ├── execution/testng ........... POST → Import TestNG XML
│ ├── execution/nunit ............ POST → Import NUnit XML
│ ├── execution/xunit ............ POST → Import xUnit XML
│ ├── execution/robot ............ POST → Import Robot Framework
│ ├── execution/cucumber ......... POST → Import Cucumber JSON
│ ├── execution/behave ........... POST → Import Behave JSON
│ └── execution/bundle ........... POST → Import multi-format (zip)

├── export/
│ ├── cucumber ................... GET → Export .feature files (zip)
│ └── test ....................... GET → Export test definitions

├── test/
│ ├── {testKey} .................. GET → Détail d'un test
│ ├── {testKey}/steps ............ GET → Steps d'un test
│ ├── {testKey}/preconditions .... GET → Preconditions liées
│ └── {testKey}/testruns ......... GET → Historique des runs

├── testplan/
│ ├── {testPlanKey} .............. GET → Détail du plan
│ └── {testPlanKey}/test ......... GET → Tests du plan

├── testexec/
│ ├── {testExecKey} .............. GET → Détail de l'exécution
│ └── {testExecKey}/test ......... GET → Tests dans l'exécution

├── testrun/
│ ├── {testRunId} ................ GET → Détail d'un run
│ ├── {testRunId}/status ......... PUT → Modifier le statut
│ ├── {testRunId}/comment ........ PUT → Ajouter un commentaire
│ ├── {testRunId}/defect ......... POST → Lier un défaut
│ └── {testRunId}/attachment ..... POST → Ajouter une evidence

└── testset/
└── {testSetKey}/test .......... GET → Tests du set

Les endpoints essentiels — avec exemples

1. Importer des résultats d'exécution

C'est l'endpoint le plus utilisé. Chaque pipeline CI appelle ça.

📥 POST /import/execution/junit — Import JUnit XML
Bash
# Import basique — crée une Test Execution automatiquement
curl -X POST \
  "https://xray.cloud.getxray.app/api/v2/import/execution/junit?projectKey=PROJ" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/xml" \
  -d @target/surefire-reports/TEST-com.example.LoginTest.xml

# Import avec contexte enrichi
curl -X POST \
  "https://xray.cloud.getxray.app/api/v2/import/execution/junit?\
projectKey=PROJ&\
testPlanKey=PROJ-456&\
testEnvironments=Chrome%20120;Ubuntu%2024&\
fixVersion=3.2.0&\
revision=abc123def" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/xml" \
  -d @report.xml

Paramètres query :

ParamTypeDescription
projectKeyString (requis)Clé du projet Jira
testPlanKeyStringLier l'exécution à un Test Plan
testExecKeyStringMettre à jour une Test Execution existante (au lieu d'en créer une nouvelle)
testEnvironmentsString (séparés par ;)Environnements de test
fixVersionStringVersion Jira associée
revisionStringHash du commit / révision

Réponse :

JSON
{
  "testExecIssue": {
    "id": "12345",
    "key": "PROJ-789",
    "self": "https://your-jira.atlassian.net/rest/api/2/issue/12345"
  }
}
Astuce pipeline : Utilisez testExecKey pour regrouper les résultats de plusieurs jobs CI dans la même Test Execution. Sans ce paramètre, chaque appel crée une nouvelle Test Execution — ce qui pollue votre historique.
📥 POST /import/execution/cucumber — Import Cucumber JSON
Bash
curl -X POST \
  "https://xray.cloud.getxray.app/api/v2/import/execution/cucumber" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @target/cucumber-report.json
Lien automatique : Le tag @PROJ-200 dans le feature file fait le mapping automatique avec le Test PROJ-200 dans Xray. Sans tag, Xray crée un nouveau test.
📥 POST /import/execution — Import Xray JSON (le plus flexible)
Python
# Import avec le format Xray JSON — contrôle total
import requests
import json
import base64

def import_xray_results(xray_client, results):
    """
    Import des résultats au format Xray JSON.
    Permet : evidence en base64, defects liés, commentaires,
    timestamps précis, infos enrichies.
    """
    payload = {
        "testExecutionKey": "PROJ-789",  # ou omis pour auto-création
        "info": {
            "project": "PROJ",
            "summary": f"Regression Suite - Build #{os.environ.get('BUILD_NUMBER', 'local')}",
            "description": "Exécution automatisée via pipeline CI",
            "startDate": "2026-02-10T09:00:00+01:00",
            "finishDate": "2026-02-10T09:45:00+01:00",
            "testPlanKey": "PROJ-456",
            "testEnvironments": ["Chrome 121", "Ubuntu 24.04"],
            "version": "3.2.0",
            "revision": "a1b2c3d4e5f6"
        },
        "tests": []
    }
    
    for test in results:
        test_entry = {
            "testKey": test["key"],
            "status": test["status"],  # PASSED, FAILED, TODO, EXECUTING, ABORTED
            "start": test["start_time"],
            "finish": test["end_time"],
            "comment": test.get("comment", ""),
        }
        
        # Ajouter des défauts liés si le test a échoué
        if test["status"] == "FAILED" and test.get("defect_key"):
            test_entry["defects"] = [test["defect_key"]]
        
        # Ajouter des preuves (screenshots) en base64
        if test.get("screenshot_path"):
            with open(test["screenshot_path"], "rb") as img:
                b64 = base64.b64encode(img.read()).decode()
            test_entry["evidences"] = [{
                "data": b64,
                "filename": f"evidence_{test['key']}.png",
                "contentType": "image/png"
            }]
        
        # Ajouter les résultats step par step
        if test.get("steps"):
            test_entry["steps"] = [
                {
                    "status": step["status"],
                    "comment": step.get("comment", ""),
                    "actualResult": step.get("actual_result", "")
                }
                for step in test["steps"]
            ]
        
        payload["tests"].append(test_entry)
    
    resp = xray_client.post("/import/execution", json=payload)
    resp.raise_for_status()
    result = resp.json()
    print(f"✅ Import OK → Test Execution: {result['testExecIssue']['key']}")
    return result

# Exemple d'appel
results = [
    {
        "key": "PROJ-100",
        "status": "PASSED",
        "start_time": "2026-02-10T09:00:00+01:00",
        "end_time": "2026-02-10T09:02:30+01:00",
        "comment": "All 5 assertions passed"
    },
    {
        "key": "PROJ-101",
        "status": "FAILED",
        "start_time": "2026-02-10T09:02:31+01:00",
        "end_time": "2026-02-10T09:05:00+01:00",
        "comment": "Expected HTTP 200, got 500 on /api/users",
        "defect_key": "PROJ-999",
        "screenshot_path": "/tmp/screenshots/error_PROJ-101.png",
        "steps": [
            {"status": "PASSED", "actual_result": "Login OK"},
            {"status": "PASSED", "actual_result": "Navigation OK"},
            {"status": "FAILED", "actual_result": "HTTP 500 Internal Server Error",
             "comment": "API /users returned 500 since build #1233"}
        ]
    }
]

xray = XrayCloudClient()
xray.authenticate()
import_xray_results(xray, results)

2. Exporter des feature files Cucumber

📤 GET /export/cucumber — Export .feature files
Bash
# Export par clés de test spécifiques
curl -X GET \
  "https://xray.cloud.getxray.app/api/v2/export/cucumber?keys=PROJ-200;PROJ-201;PROJ-202" \
  -H "Authorization: Bearer $TOKEN" \
  -o features.zip

unzip features.zip -d src/test/resources/features/

# Export par filtre JQL
curl -X GET \
  "https://xray.cloud.getxray.app/api/v2/export/cucumber?\
filter=12345" \
  -H "Authorization: Bearer $TOKEN" \
  -o features.zip

Paramètres :

ParamDescription
keysClés de tests séparées par ;
filterID d'un filtre JQL sauvegardé

Réponse : Fichier ZIP contenant les .feature avec les tags @PROJ-XXX automatiques.

3. GraphQL — L'arme secrète (Cloud uniquement)

GraphQL est la voie royale pour interroger Xray Cloud. Une seule requête peut récupérer les tests, leurs steps, leurs préconditions, et leur historique d'exécution — là où REST nécessiterait 4 appels séparés.

🔮 Requêtes GraphQL essentielles

Récupérer des tests avec leurs steps et préconditions

GraphQL
# POST https://xray.cloud.getxray.app/api/v2/graphql

{
    getTests(jql: "project = PROJ AND issuetype = Test", limit: 50) {
        total
        start
        limit
        results {
            issueId
            jira(fields: ["key", "summary", "status", "priority", "labels"])
            testType { name kind }
            folder { path name }
            steps {
                id
                action
                data
                result
                attachments { id filename }
            }
            preconditions(limit: 10) {
                results {
                    issueId
                    jira(fields: ["key", "summary"])
                    definition
                    preconditionType { name }
                }
            }
            testRuns(limit: 5) {
                results {
                    id
                    status { name color }
                    startedOn
                    finishedOn
                    executedById
                    testExecution { issueId jira(fields: ["key"]) }
                    evidence { filename }
                    defects { issueId jira(fields: ["key", "summary", "status"]) }
                    comment
                }
            }
        }
    }
}

Récupérer la couverture d'un Test Plan

GraphQL
{
    getTestPlan(issueId: "12345") {
        issueId
        jira(fields: ["key", "summary"])
        tests(limit: 100) {
            total
            results {
                issueId
                jira(fields: ["key", "summary"])
                testType { name }
                lastTestRunStatus
            }
        }
        testExecutions(limit: 20) {
            results {
                issueId
                jira(fields: ["key", "summary", "status"])
                testEnvironments
                testRuns(limit: 100) {
                    results {
                        status { name }
                        startedOn
                        finishedOn
                    }
                }
            }
        }
    }
}

Récupérer la couverture des requirements

GraphQL
{
    getTests(jql: "issuetype = Test AND project = PROJ", limit: 100) {
        results {
            issueId
            jira(fields: ["key", "summary"])
            testType { name }
            # Les requirements couverts par ce test
            requirements(limit: 10) {
                results {
                    issueId
                    jira(fields: ["key", "summary", "status"])
                }
            }
            lastTestRunStatus
        }
    }
}

Implémentation Python complète

Python
def graphql_query(xray_client, query, variables=None):
    """Exécuter une requête GraphQL sur Xray Cloud."""
    payload = {"query": query}
    if variables:
        payload["variables"] = variables
    
    resp = xray_client.post("/graphql", json=payload)
    resp.raise_for_status()
    data = resp.json()
    
    if "errors" in data:
        for err in data["errors"]:
            print(f"⚠️ GraphQL Error: {err['message']}")
    
    return data.get("data", {})

# Exemple : Dashboard de couverture en une requête
COVERAGE_QUERY = """
{
    getTests(jql: "project = PROJ AND issuetype = Test", limit: 200) {
        total
        results {
            issueId
            jira(fields: ["key", "summary"])
            testType { name }
            lastTestRunStatus
            requirements(limit: 5) {
                results {
                    jira(fields: ["key", "summary"])
                }
            }
        }
    }
}
"""

xray = XrayCloudClient()
xray.authenticate()
data = graphql_query(xray, COVERAGE_QUERY)

tests = data["getTests"]["results"]
total = data["getTests"]["total"]

# Calculer les stats
passed = sum(1 for t in tests if t.get("lastTestRunStatus") == "PASSED")
failed = sum(1 for t in tests if t.get("lastTestRunStatus") == "FAILED")
no_run = sum(1 for t in tests if not t.get("lastTestRunStatus"))
orphans = sum(1 for t in tests if not t.get("requirements", {}).get("results"))

print(f"📊 Dashboard Xray — {total} tests")
print(f"   ✅ Passed:  {passed} ({passed/total*100:.0f}%)")
print(f"   ❌ Failed:  {failed} ({failed/total*100:.0f}%)")
print(f"   ⬜ No runs: {no_run} ({no_run/total*100:.0f}%)")
print(f"   🔗 Orphans: {orphans} ({orphans/total*100:.0f}%) ← sans requirement")

4. Manipuler les Test Runs

🏃 Test Runs — Statuts, commentaires, evidence, défauts
Python
class TestRunManager:
    """Gestion avancée des Test Runs via API Xray."""
    
    def __init__(self, xray_client):
        self.xray = xray_client
    
    def update_status(self, test_run_id, status, comment=None):
        """
        Modifier le statut d'un Test Run.
        Statuts possibles : PASSED, FAILED, TODO, EXECUTING, ABORTED
        """
        payload = {"status": status}
        if comment:
            payload["comment"] = comment
        
        resp = self.xray.put(f"/testrun/{test_run_id}/status", json=payload)
        resp.raise_for_status()
        print(f"✅ Test Run {test_run_id} → {status}")
    
    def add_evidence(self, test_run_id, filepath, content_type="image/png"):
        """Attacher une evidence (screenshot, log) au Test Run."""
        with open(filepath, "rb") as f:
            files = {"file": (os.path.basename(filepath), f, content_type)}
            resp = requests.post(
                f"{self.xray.BASE_URL}/testrun/{test_run_id}/attachment",
                headers={"Authorization": f"Bearer {self.xray.token}"},
                files=files
            )
        resp.raise_for_status()
        print(f"📎 Evidence ajoutée au Test Run {test_run_id}")
    
    def link_defect(self, test_run_id, defect_key):
        """Lier un défaut Jira à un Test Run."""
        resp = self.xray.post(
            f"/testrun/{test_run_id}/defect",
            json={"issueKey": defect_key}
        )
        resp.raise_for_status()
        print(f"🐛 Defect {defect_key} lié au Test Run {test_run_id}")
    
    def add_comment(self, test_run_id, comment):
        """Ajouter un commentaire au Test Run."""
        resp = self.xray.put(
            f"/testrun/{test_run_id}/comment",
            json={"comment": comment}
        )
        resp.raise_for_status()

# Usage dans un framework de test custom
runner = TestRunManager(xray)

# Après un test qui échoue
runner.update_status("tr-12345", "FAILED", "Timeout on API call after 30s")
runner.add_evidence("tr-12345", "/tmp/screenshots/api_timeout.png")
runner.link_defect("tr-12345", "PROJ-999")
runner.add_comment("tr-12345", "Reproduced consistently since build #1233")

5. Listeners & Webhooks — Réagir aux événements

Les webhooks permettent de déclencher des actions automatiques quand quelque chose se passe dans Xray. Un test échoue ? Envoyez une notif Slack. Une Test Execution est terminée ? Déclenchez un rapport. Un défaut est créé depuis un Test Run ? Assignez-le automatiquement.

Pattern Listener :

Événement Xray/Jira Webhook POST Votre Listener (API) Action (Slack, Email, CI...)
🔔 Listener Python — Serveur webhook Flask
Python
"""
Listener Xray — Serveur webhook qui réagit aux événements Jira/Xray.
Configure un webhook Jira pointant vers ce serveur.

Jira Admin → System → Webhooks → Create
URL: https://your-server.com/webhook/xray
Events: Issue created, Issue updated
JQL Filter: issuetype in (Test, "Test Execution", Bug)
"""

from flask import Flask, request, jsonify
import requests
import json
from datetime import datetime

app = Flask(__name__)

# Configuration
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
XRAY_EVENTS_LOG = "xray_events.log"

def log_event(event_type, data):
    """Logger tous les événements pour audit."""
    with open(XRAY_EVENTS_LOG, "a") as f:
        f.write(json.dumps({
            "timestamp": datetime.now().isoformat(),
            "type": event_type,
            "data": data
        }) + "\n")

def notify_slack(message, color="#FF5630"):
    """Envoyer une notification Slack."""
    requests.post(SLACK_WEBHOOK, json={
        "attachments": [{
            "color": color,
            "text": message,
            "footer": "Xray Listener",
            "ts": int(datetime.now().timestamp())
        }]
    })

@app.route("/webhook/xray", methods=["POST"])
def xray_webhook():
    """Point d'entrée principal du webhook."""
    payload = request.json
    
    event = payload.get("webhookEvent", "unknown")
    issue = payload.get("issue", {})
    issue_type = issue.get("fields", {}).get("issuetype", {}).get("name", "")
    issue_key = issue.get("key", "N/A")
    summary = issue.get("fields", {}).get("summary", "")
    
    log_event(event, {"key": issue_key, "type": issue_type})
    
    # === ÉVÉNEMENT 1 : Test Execution créée ===
    if event == "jira:issue_created" and issue_type == "Test Execution":
        assignee = issue.get("fields", {}).get("assignee", {}).get("displayName", "Non assigné")
        notify_slack(
            f"▶️ Nouvelle Test Execution : *{issue_key}* — {summary}\n"
            f"Assigné à : {assignee}",
            color="#0052CC"
        )
    
    # === ÉVÉNEMENT 2 : Bug créé depuis un Test Run ===
    elif event == "jira:issue_created" and issue_type == "Bug":
        priority = issue.get("fields", {}).get("priority", {}).get("name", "?")
        # Vérifier si le bug est lié à un test (via issuelinks)
        links = issue.get("fields", {}).get("issuelinks", [])
        test_links = [
            l for l in links 
            if l.get("type", {}).get("name") == "is blocked by"
        ]
        if test_links:
            test_key = test_links[0].get("outwardIssue", {}).get("key", "?")
            notify_slack(
                f"🐛 Bug créé depuis un test !\n"
                f"Bug : *{issue_key}* ({priority}) — {summary}\n"
                f"Test source : {test_key}",
                color="#DE350B"
            )
    
    # === ÉVÉNEMENT 3 : Test Execution terminée ===
    elif event == "jira:issue_updated" and issue_type == "Test Execution":
        new_status = payload.get("changelog", {}).get("items", [{}])
        for change in payload.get("changelog", {}).get("items", []):
            if change.get("field") == "status" and change.get("toString") == "Done":
                notify_slack(
                    f"✅ Test Execution terminée : *{issue_key}* — {summary}\n"
                    f"📊 Résultats disponibles dans Xray",
                    color="#36B37E"
                )
    
    # === ÉVÉNEMENT 4 : Test passé en status Deprecated ===
    elif event == "jira:issue_updated" and issue_type == "Test":
        for change in payload.get("changelog", {}).get("items", []):
            if change.get("field") == "status" and change.get("toString") == "Deprecated":
                notify_slack(
                    f"🗄️ Test archivé : *{issue_key}* — {summary}\n"
                    f"Vérifiez qu'il est retiré des Test Plans actifs.",
                    color="#97A0AF"
                )
    
    return jsonify({"status": "ok"}), 200

@app.route("/webhook/ci-trigger", methods=["POST"])
def ci_trigger():
    """
    Endpoint pour déclencher un pipeline CI
    quand un Test Plan passe en status 'Active'.
    """
    payload = request.json
    issue = payload.get("issue", {})
    issue_type = issue.get("fields", {}).get("issuetype", {}).get("name", "")
    
    if issue_type == "Test Plan":
        for change in payload.get("changelog", {}).get("items", []):
            if change.get("field") == "status" and change.get("toString") == "Active":
                test_plan_key = issue.get("key")
                # Déclencher le pipeline Jenkins
                requests.post(
                    "https://jenkins.example.com/job/regression-suite/build",
                    auth=("jenkins_user", "jenkins_token"),
                    json={"parameter": [
                        {"name": "TEST_PLAN_KEY", "value": test_plan_key}
                    ]}
                )
                notify_slack(
                    f"🚀 Pipeline déclenché pour Test Plan *{test_plan_key}*",
                    color="#0052CC"
                )
    
    return jsonify({"status": "ok"}), 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)
Configuration Jira : Pour créer le webhook :
Administration → System → Webhooks → Create Webhook
URL : https://your-server.com/webhook/xray
Events : Issue created, Issue updated
JQL Filter : issuetype in (Test, "Test Execution", "Test Plan", Bug) AND project = PROJ
Le filtre JQL limite les appels webhook aux seuls événements pertinents — ne mettez pas "all events" sinon votre listener sera noyé.
⚡ Jira Automation — Alternative no-code aux webhooks

Si vous ne voulez pas maintenir un serveur webhook, Jira Automation (natif dans Jira Cloud) peut gérer beaucoup de cas automatiquement :

Jira Automation Rules
# Règle 1 : Notifier Slack quand un test échoue
TRIGGER: Issue updated
CONDITION: Issue type = "Test Execution" AND Status changed to "Done"
ACTION: Send Slack message → "#qa-alerts"
         Message: "Test Execution {{issue.key}} terminée — vérifiez les résultats"

# Règle 2 : Assigner les bugs auto-créés au tech lead
TRIGGER: Issue created
CONDITION: Issue type = Bug AND Issue has link type "is created by"
ACTION: Assign to → "tech.lead@company.com"

# Règle 3 : Ajouter un commentaire quand la couverture est OK
TRIGGER: Field value changed
CONDITION: Field "Requirement Status" = OK
ACTION: Add comment → "✅ Couverture validée — tous les tests passent"

# Règle 4 : Transition auto du Test Plan quand toutes les exécutions sont Done
TRIGGER: Issue updated (Test Execution → Done)
CONDITION: Related Test Plan has all Test Executions in Done
ACTION: Transition Test Plan → "Completed"
Quand utiliser quoi :
Jira Automation : notifications simples, transitions de workflow, assignations, commentaires. Pas besoin de serveur, pas de code.
Webhook custom (Flask/Express) : logique complexe, intégration avec des systèmes externes (CI, Slack avancé, bases de données), traitement de données, orchestration multi-outils.
Xray Enterprise Remote Job Triggers : déclencher des exécutions de test depuis Xray vers votre CI. Nécessite l'édition Enterprise.

6. Recettes prêtes à l'emploi

📋 Script complet : Rapport de couverture quotidien
Python
"""
Script de rapport quotidien de couverture Xray.
À exécuter via cron ou CI schedule.
Envoie un résumé Slack + génère un JSON pour dashboard.
"""

import requests
import json
from datetime import datetime

def generate_coverage_report(xray_client, project_key, version=None):
    """Générer un rapport de couverture complet."""
    
    # 1. Récupérer les tests et leur statut via GraphQL
    jql = f"project = {project_key} AND issuetype = Test"
    if version:
        jql += f" AND fixVersion = '{version}'"
    
    query = """
    query($jql: String!) {
        getTests(jql: $jql, limit: 500) {
            total
            results {
                issueId
                jira(fields: ["key", "summary", "labels"])
                testType { name }
                lastTestRunStatus
                requirements(limit: 5) {
                    total
                    results { jira(fields: ["key"]) }
                }
            }
        }
    }
    """
    
    data = graphql_query(xray_client, query, {"jql": jql})
    tests = data.get("getTests", {}).get("results", [])
    total = data.get("getTests", {}).get("total", 0)
    
    # 2. Calculer les métriques
    stats = {
        "date": datetime.now().isoformat(),
        "project": project_key,
        "version": version,
        "total_tests": total,
        "by_status": {},
        "by_type": {},
        "orphan_tests": 0,  # Tests sans requirement
        "coverage_rate": 0
    }
    
    for test in tests:
        # Par statut
        status = test.get("lastTestRunStatus") or "NO_RUN"
        stats["by_status"][status] = stats["by_status"].get(status, 0) + 1
        
        # Par type
        test_type = test.get("testType", {}).get("name", "Unknown")
        stats["by_type"][test_type] = stats["by_type"].get(test_type, 0) + 1
        
        # Orphelins
        req_count = test.get("requirements", {}).get("total", 0)
        if req_count == 0:
            stats["orphan_tests"] += 1
    
    # Taux de couverture = tests liés à au moins 1 requirement
    linked = total - stats["orphan_tests"]
    stats["coverage_rate"] = round((linked / total * 100), 1) if total > 0 else 0
    
    # Pass rate
    passed = stats["by_status"].get("PASSED", 0)
    executed = total - stats["by_status"].get("NO_RUN", 0)
    stats["pass_rate"] = round((passed / executed * 100), 1) if executed > 0 else 0
    
    # 3. Générer le message Slack
    status_emoji = "✅" if stats["pass_rate"] > 85 else "⚠️" if stats["pass_rate"] > 70 else "🔴"
    
    slack_message = f"""
{status_emoji} *Rapport QA Quotidien — {project_key}*
📅 {datetime.now().strftime('%d/%m/%Y %H:%M')}
{'Version: ' + version if version else ''}

📊 *Métriques :*
• Tests total : {total}
• ✅ Passed : {stats['by_status'].get('PASSED', 0)}
• ❌ Failed : {stats['by_status'].get('FAILED', 0)}
• ⬜ Non exécutés : {stats['by_status'].get('NO_RUN', 0)}
• 🔗 Taux de couverture : {stats['coverage_rate']}%
• 📈 Pass rate : {stats['pass_rate']}%
• 👻 Tests orphelins : {stats['orphan_tests']}

🧪 *Par type :*
{chr(10).join(f"• {k}: {v}" for k, v in stats['by_type'].items())}
"""
    
    return stats, slack_message

# Exécution
xray = XrayCloudClient()
xray.authenticate()

stats, message = generate_coverage_report(xray, "PROJ", "3.2.0")

# Envoyer sur Slack
requests.post(SLACK_WEBHOOK, json={"text": message})

# Sauvegarder le JSON pour historique
with open(f"reports/coverage_{datetime.now().strftime('%Y%m%d')}.json", "w") as f:
    json.dump(stats, f, indent=2)

print(f"✅ Rapport généré — Pass rate: {stats['pass_rate']}%")
📋 Script : Nettoyage des tests orphelins (Test Hygiene)
Python
"""
Script de nettoyage : identifier et labelliser les tests orphelins.
Orphelin = test sans requirement lié ET sans exécution depuis 6 mois.
"""

from datetime import datetime, timedelta

def find_orphan_tests(xray_client, project_key, months_inactive=6):
    """Trouver les tests orphelins et inactifs."""
    
    cutoff = (datetime.now() - timedelta(days=months_inactive * 30)).strftime("%Y-%m-%d")
    
    query = """
    query($jql: String!) {
        getTests(jql: $jql, limit: 200) {
            total
            results {
                issueId
                jira(fields: ["key", "summary", "created", "updated", "labels"])
                testType { name }
                requirements(limit: 1) { total }
                testRuns(limit: 1) {
                    results {
                        startedOn
                        status { name }
                    }
                }
            }
        }
    }
    """
    
    jql = f"project = {project_key} AND issuetype = Test AND updated <= '{cutoff}'"
    data = graphql_query(xray_client, query, {"jql": jql})
    
    orphans = []
    for test in data.get("getTests", {}).get("results", []):
        has_requirements = test.get("requirements", {}).get("total", 0) > 0
        has_recent_runs = len(test.get("testRuns", {}).get("results", [])) > 0
        
        if not has_requirements and not has_recent_runs:
            key = test["jira"]["key"]
            summary = test["jira"]["summary"]
            orphans.append({"key": key, "summary": summary})
            print(f"  👻 {key}: {summary}")
    
    print(f"\n📊 {len(orphans)} tests orphelins sur {data['getTests']['total']} inactifs")
    return orphans

# Pour chaque orphelin, ajouter le label DEPRECATED via API Jira
def label_orphans(orphans, jira_base_url, jira_auth):
    """Ajouter le label DEPRECATED aux tests orphelins."""
    for test in orphans:
        requests.put(
            f"{jira_base_url}/rest/api/2/issue/{test['key']}",
            auth=jira_auth,
            json={"update": {"labels": [{"add": "DEPRECATED"}]}}
        )
        print(f"  🏷️ {test['key']} → DEPRECATED")

orphans = find_orphan_tests(xray, "PROJ", months_inactive=6)
# label_orphans(orphans, "https://your-jira.atlassian.net", ("email", "api_token"))

Schéma logique récapitulatif : qui appelle quoi, quand

Flux complet — Du commit au dashboard :

1. DEV push code
└→ Git trigger → CI Pipeline start

2. CI exécute les tests
├→ Unit tests → JUnit XML
├→ Integration tests → JUnit XML
└→ BDD tests → Cucumber JSON

3. CI push résultats vers Xray
├→ POST /import/execution/junit (unit + integration)
└→ POST /import/execution/cucumber (BDD)

4. Xray traite les résultats
├→ Crée/met à jour Test Execution
├→ Met à jour les Test Runs (PASS/FAIL)
├→ Recalcule Requirement Status
└→ Met à jour le Test Plan coverage

5. Webhooks se déclenchent
├→ Test Execution updated → Listener Flask
├→ Bug créé (si FAIL) → Slack notification
└→ Coverage recalculée → Dashboard mis à jour

6. Cron quotidien
└→ Script rapport couverture → Slack + JSON archive

7. Sprint Review
└→ PO/Manager consulte le dashboard Jira → Décision Go/No-Go

Erreurs API fréquentes & debug

Erreur Cause probable Solution
401 Unauthorized Token expiré (durée ~1h) ou credentials invalides Ré-authentifiez. Vérifiez client_id/secret. En DC : vérifiez le PAT.
400 Bad Request Format XML/JSON invalide, champ manquant Validez votre XML JUnit avec un linter. Vérifiez les champs requis.
404 Not Found Issue key invalide, projet inexistant, endpoint incorrect Vérifiez que le projet existe et que Xray est activé dessus.
403 Forbidden Permissions insuffisantes sur le projet Vérifiez les permissions Jira du compte API (Project Role, Browse, Create Issue).
429 Too Many Requests Rate limit atteint (Standard < Advanced < Enterprise) Ajoutez un retry avec backoff exponentiel. Ou upgradez l'édition.
Test créé en double Tag @PROJ-XXX manquant dans le feature Cucumber Ajoutez le tag Jira dans le .feature. Sans tag = nouveau test à chaque import.
Evidence non attachée Content-Type incorrect ou fichier trop volumineux Vérifiez multipart/form-data. Limite : ~10MB par attachment.
Pattern de retry robuste :
Python
import time

def api_call_with_retry(func, max_retries=3, base_delay=2):
    """Wrapper de retry avec backoff exponentiel pour les appels API Xray."""
    for attempt in range(max_retries):
        try:
            response = func()
            if response.status_code == 429:
                # Rate limited — attendre et réessayer
                delay = base_delay * (2 ** attempt)
                retry_after = response.headers.get("Retry-After", delay)
                print(f"⏳ Rate limited. Retry dans {retry_after}s...")
                time.sleep(float(retry_after))
                continue
            response.raise_for_status()
            return response
        except requests.exceptions.ConnectionError:
            delay = base_delay * (2 ** attempt)
            print(f"🔄 Connexion perdue. Retry {attempt+1}/{max_retries} dans {delay}s...")
            time.sleep(delay)
    
    raise Exception(f"❌ Échec après {max_retries} tentatives")

# Usage
result = api_call_with_retry(
    lambda: xray.post("/import/execution/junit",
        data=open("report.xml", "rb").read(),
        headers={**xray.headers, "Content-Type": "application/xml"})
)
• • •
🔌 API Ready
Vous avez les endpoints, les schémas, les scripts, et les patterns de listener.

Commencez par l'import JUnit dans votre CI. Puis ajoutez GraphQL pour le reporting. Puis les webhooks pour l'orchestration. Étape par étape.