Les tests d'automatisation irréguliers peuvent être un fléau pour les ingénieurs d'assurance qualité, introduisant de l'incertitude et compromettant la fiabilité des suites de tests. S'appuyant sur mon expérience en tant que mentor SDET et QA Automation, cet article propose des conseils pratiques pour vaincre les flocons. Bien que je fournisse des exemples JavaScript et Playwright, ces conseils universels sont applicables dans n'importe quel langage et framework, vous aidant à rédiger des tests d'automatisation robustes et fiables. Allons-y et assurons-nous que vos tests tiennent bon.
1. Évitez les méthodes d'attente implicites
Parfois, vous rencontrerez des situations dans lesquelles vous devrez attendre que des éléments apparaissent dans le DOM ou que votre application atteigne un état spécifique pour poursuivre votre scénario de test. Même avec des cadres d'automatisation modernes et intelligents tels que les fonctionnalités d'attente automatique de Playwright, il y aura des cas où vous devrez implémenter des méthodes d'attente personnalisées. Par exemple, considérons un scénario avec des champs de texte et un bouton de confirmation. En raison du comportement spécifique du serveur, le bouton de confirmation n'est visible qu'après environ cinq secondes après avoir rempli le formulaire. Dans de tels cas, il est essentiel de résister à la tentation d’insérer une attente implicite de cinq secondes entre deux étapes de test.
textField.fill('Some text') waitForTime(5000) confirmationButton.click()
Utilisez plutôt une approche intelligente et attendez l’état exact. Il peut s'agir de l'élément de texte apparaissant après un certain chargement ou d'un élément tournant du chargeur disparaissant une fois l'étape en cours terminée avec succès. Vous pouvez vérifier si vous êtes prêt pour la prochaine étape de test.
button.click() waitFor(textField.isVisible()) textField.fill()
Bien sûr, il existe des cas où vous devez attendre quelque chose que vous ne pouvez pas vérifier intelligemment. Je suggère d'ajouter des commentaires à de tels endroits ou même d'ajouter l'explication de la raison en tant que paramètre dans vos méthodes d'attente ou quelque chose comme ça :
waitForTime(5000, {reason: "For unstable server processed something..."}
Un avantage supplémentaire de son utilisation réside dans les rapports de test dans lesquels vous pouvez stocker de telles explications afin qu'il soit évident pour quelqu'un d'autre ou même pour vous à l'avenir quoi et pourquoi une telle attente de cinq secondes ici.
waitForTime(5000, {reason: "For unstable server processed something..."} button.click() waitFor(textField.isVisible()) textField.fill()
2. Utilisez des localisateurs robustes et fiables pour sélectionner les éléments
Les localisateurs constituent un élément crucial des tests d’automatisation, et tout le monde le sait. Cependant, malgré ce simple fait, de nombreux ingénieurs en automatisation misent sur la stabilité et utilisent quelque chose comme ça dans leurs tests.
//*[@id="editor_7"]/section/div[2]/div/h3[2]
C'est une approche idiote car la structure du DOM n'est pas si statique ; certaines équipes différentes peuvent parfois le modifier, et ce après l'échec de vos tests. Alors, utilisez les localisateurs robustes. Au fait, vous êtes les bienvenus dans ma lecture d'histoire liée à XPath.
3. Rendre les tests indépendants les uns des autres
Lors de l'exécution de la suite de tests avec plusieurs tests dans un seul thread, il est crucial de garantir que chaque test est indépendant. Cela signifie que le premier test doit ramener le système à son état d'origine afin que le test suivant n'échoue pas en raison de conditions inattendues. Par exemple, si l'un de vos tests modifie les paramètres utilisateur stockés dans LocalStorage, effacer l'entrée LocalStorage pour les paramètres utilisateur après les premières exécutions de tests est une bonne approche. Cela garantit que le deuxième test sera exécuté avec les paramètres utilisateur par défaut. Vous pouvez également envisager des actions telles que la réinitialisation de la page à la page d'entrée par défaut et la suppression des cookies et des entrées de base de données afin que chaque nouveau test commence avec des conditions initiales identiques.
Par exemple, dans Playwright, vous pouvez utiliser les annotations beforeAll(), beforeEach(), afterAll() et afterEach() pour y parvenir.
Vérifiez la prochaine suite de tests avec quelques tests. La première consiste à modifier les paramètres d'apparence de l'utilisateur et la seconde à vérifier l'apparence par défaut.
test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goTo(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
Si une telle suite de tests s’exécute en parallèle, tout se passera bien. Cependant, si ces tests sont exécutés un par un, vous pourriez être confronté à un test échoué car l’un d’eux interfère avec un autre. Le premier test a changé l’apparence du clair au foncé. Le deuxième test vérifie si l'apparence actuelle est claire. Mais cela échouera puisque le premier test a déjà modifié l’apparence par défaut. Pour résoudre un tel problème, j'ai ajouté le hook afterEach() exécuté après chaque test. Dans ce cas, il accédera à la vue par défaut et effacera l'apparence de l'utilisateur en le supprimant du stockage local.
test.afterEach(async ({ page }) => { await user.localStorage(appearanceSettings).clear() await user.goTo(views.home) }); test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goto(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
En utilisant cette approche, chaque test de cette suite sera indépendant.
4. Utilisez judicieusement les tentatives automatiques
Personne n’est à l’abri de tests irréguliers, et un remède populaire à ce problème consiste à effectuer de nouvelles tentatives. Vous pouvez configurer les tentatives pour qu'elles se produisent automatiquement, même au niveau de la configuration CI/CD. Par exemple, vous pouvez configurer des tentatives automatiques pour chaque test ayant échoué, en spécifiant un nombre maximum de tentatives de trois fois. Tout test qui échoue initialement sera réessayé jusqu'à trois fois jusqu'à ce que le cycle d'exécution du test soit terminé. L’avantage est que si votre test n’est que légèrement irrégulier, il peut réussir après quelques tentatives.
Cependant, l’inconvénient est qu’il pourrait échouer, ce qui entraînerait une consommation d’exécution supplémentaire. De plus, cette pratique peut conduire par inadvertance à une accumulation de tests instables, car de nombreux tests peuvent sembler « réussis » à la deuxième ou à la troisième tentative, et vous pourriez les étiqueter à tort comme stables. Par conséquent, je ne recommande pas de définir des tentatives automatiques globalement pour l’ensemble de votre projet de test. Au lieu de cela, utilisez les nouvelles tentatives de manière sélective dans les cas où vous ne pouvez pas résoudre rapidement les problèmes sous-jacents du test.
Par exemple, imaginez que vous avez un scénario de test dans lequel vous devez télécharger certains fichiers à l'aide de l'événement « filechooser ». Vous obtenez l'erreur <Timeout dépassé en attendant l'événement 'filechooser'>, qui indique que le test Playwright attend qu'un événement 'filechooser' se produise mais prend plus de temps.
async function uploadFile(page: Page, file) { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); }
Cela peut être dû à diverses raisons, telles qu'un comportement inattendu de l'application ou un environnement lent. Comme il s’agit d’un comportement aléatoire, la première chose à laquelle vous pourriez penser est d’utiliser une nouvelle tentative automatique pour ce test. Cependant, si vous réessayez l'intégralité du test, vous risquez de perdre du temps supplémentaire et d'obtenir le même comportement que celui obtenu avec la première erreur dans la méthode uploadFile() elle-même, donc si l'erreur se produit, vous n'avez pas besoin de réessayer l'intégralité du test.
Pour ce faire, vous pouvez utiliser l’instruction standard try…catch.
async function uploadFile(page: Page, file) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); break; // Success, exit the loop } catch (error) { console.error(`Attempt ${retryCount + 1} failed: ${error}`); retryCount++; } } }
Une telle approche vous fera gagner du temps supplémentaire et rendra votre test moins flou.
En conclusion, le chemin vers des tests d’automatisation fiables est simple. Vous pouvez minimiser ou relever activement les défis courants et mettre en œuvre des stratégies intelligentes. N'oubliez pas que ce voyage est en cours et que vos tests deviendront plus fiables avec de la persévérance. Dites adieu aux flocons et bienvenue à la stabilité. Votre engagement envers la qualité garantit la réussite du projet. Bons tests !
Également publié .