Web hors-ligne : Passons en mode test
Disclaimer :
Cet article est le septième de la série d’articles sur le web hors-ligne et les services workers. Sommaire :
Web hors-ligne : l'alliance entre l'optimisation utilisateur et les pratiques éco-responsables
Web hors ligne : Précacher votre application pour un usage hors ligne dégradé avec des fallback
Web hors-ligne : Stratégies de cache du plus green au plus simple, entre offline first et online first
Web hors-ligne : Stale While Revalidate ou le meilleur de l’UX pour le pire de l’écoresponsabilité Web hors-ligne : Des métriques positives pour l’utilisateur comme pour la planète
Web hors-ligne : From scratch vs Workbox
Introduction
Il est nécessaire, dans n’importe quel type d’application, d’avoir un harnais de sécurité concernant les fonctionnalités développées et c’est là le rôle des tests. Cependant, il est important de bien réfléchir avant de définir la stratégie de tests à employer. En effet, est-il nécessaire de poser des tests unitaires sur les méthodes d’une librairie qui sont déjà testées en interne ? Cela semble, à juste titre, excessif. Il en va de même pour les tests unitaires concernant les méthodes du cache du navigateur : cela reviendrait à tester le navigateur en lui-même.
Cependant, il est intéressant de tester la logique avec laquelle on utilise les méthodes du cache (que ce soit en natif ou avec l’aide de Workbox) afin de vérifier que les méthodes sont appelées. On utilisera alors la méthode spyOn de jest ou vitest en fonction de l’outil de test installé dans le projet.
const mockedCache = mock<Cache>()
const spiedMethode = jest.spyOn(mockedCache, ‘nomMethode’)
expect(spiedMethode).toHaveBeenCalled()
En revanche, les tests de bout en bout semblent être appropriés car il est intéressant de tester le parcours de l’utilisateur dans son intégralité afin de voir si la gestion du cache est fonctionnelle.
Le cas Cypress
Cependant, simuler le passage de la connexion du mode online en mode offline dans l’environnement de tests mis en place par Cypress n’est pas aussi aisée que l’on puisse croire. Les différents exemples d’implémentation de cette simulation trouvés sur le net n’ont, malheureusement, pas été concluants.
Intercepter pour mieux régner
La première méthode de simulation du mode offline permettait d’intercepter toutes les requêtes pour les détruire, et donc de forcer le cache à renvoyer ses données stockées, puis de les recevoir de nouveau ce qui fonctionnait dans le cas du test de la stratégie Stale While Revalidate.
const goOffline = () => {
cy.intercept('*', request => {
request.destroy();
});
};
const goOnline = () => {
cy.intercept('*', request => {
request.continue();
});
};
Mais elle n’a pas fonctionné lorsqu'il a fallu tester l’affichage de la page de fallback (qui est cachée au premier chargement de l’application en mode online) après avoir perdu la connexion et rechargé l’application.
Agir en interne
La deuxième méthode permettait de changer l’état du network de offline à online en interne à Cypress. On va activer la commande Network.enable du remote:debugger:protocol pour ensuite lancer la commande Network.emulateNetworkConditions avec le paramètre offline à true. Et pour le mode online, c’est la commande Network.disable qui est activée, puis la commande Network.emulateNetworkConditions est relancée avec le paramètre offline à false cette fois.
const goOffline = () => {
cy.log('**go offline**')
.then(() => {
return Cypress.automation('remote:debugger:protocol',
{
command: 'Network.enable',
})
})
.then(() => {
return Cypress.automation('remote:debugger:protocol',
{
command: 'Network.emulateNetworkConditions',
params: {
offline: true,
latency: -1,
downloadThroughput: -1,
uploadThroughput: -1,
},
})
})
}
Aucun résultat. En creusant, il s’avère que cette méthode marcherait sur les versions de Cypress antérieures à 7.3 (github issues) et malgré nos tentatives avec les alternatives postées dans le thread de cette issue, rien n’a fonctionné dans nos cas.
La révélation Playwright
Cypress nous ayant fait défaut, c’est plein d’espoir que nous avons décidé de tester notre application avec Playwright qui offre une méthode de simulation du mode offline.
Il est nécessaire de récupérer le contexte du navigateur afin d’utiliser la méthode de simulation du mode offline.
import { test, expect } from '@playwright/test';
const { chromium } = require('playwright');
// Récupère le navigateur
const browser = await chromium.launch();
// Créée le contexte du navigateur
const context = await browser.newContext();
// Instancie la page en fonction du contexte
const page = await context.newPage();
Await page.goto(‘https://votre-url.com’);
// Simule le mode hors-ligne
await context.setOffline(true);
Ça marche ! Notre test détecte bien le changement d’état du network et nous pouvons donc vérifier le comportement du parcours utilisateur de A à Z. Voici quelques exemples de tests qui permettent de tester les différentes stratégies de cache que nous avons abordées dans les articles précédents.
Nous avons déjà commencé à tester le comportement au rafraîchissement de la page ainsi qu’au changement de page, sans succès. Nous en déduisons que l’enregistrement des services workers sur une nouvelle page ne fonctionne pas dans l’environnement de tests de Playwright. Ci-dessous, les exemples de tests que nous avons réalisés avec leurs explications.
Gestion du cache en Online/Offline first
test('test de la stratégie du Online/Offline First sur la page d’accueil', async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await context.setOffline(true);
await page.goto('https://votre-url/');
await expect(page.getByRole('heading', { name: 'Titre' })).not.toBeVisible();
await context.setOffline(false);
await page.goto('https://votre-url/');
await expect(page.getByRole('heading', { name: 'Titre' })).toBeVisible();
await context.setOffline(true);
await page.goto('https://votre-url/');
await expect(page.getByRole('heading', { name: 'Titre' })).toBeVisible();
});
Dans cet exemple, on :
- Passe en mode hors-ligne
- Arrive sur notre application
- Vérifie que notre Titre ne s’affiche pas
- Passe en mode online
- Rafraîchit notre page
- Vérifie que notre Titre s’affiche
- Passe en mode hors-ligne
- Rafraîchit notre page
- Vérifie que notre Titre s’affiche toujours
Offline Fallback
test('test de la stratégie de fallback', async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://votre-url/');
await expect(page.getByRole('heading', { name: 'Titre' })).toBeVisible();
await context.setOffline(true);
await page.goto('https://votre-url/page-non-chargée');
await expect(page.getByRole('heading', { name: 'Titre page hors ligne' })).toBeVisible();
});
Dans cet exemple, on :
- Arrive sur notre application en mode online
- Vérifie que notre Titre s’affiche
- Passe en mode hors-ligne
- Navigue sur une nouvelle page
- Vérifie que notre Titre page hors ligne s’affiche
Stale While Revalidate
Nous avons ensuite testé les chargements de données au sein même de la page avec succès. Ci-dessous, un exemple avec le Stale While Revalidate fonctionnel avec son explication.
test('test de la stratégie Stale While Revalidate', async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://votre-url/');
await expect(page.getByRole('heading', { name: 'Titre' })).toBeVisible();
await context.setOffline(true);
await page.getByRole('button', { name: 'chargement des données'}).click();
await expect(page.getByTestId('error-message')).toBeVisible();
await context.setOffline(false);
await page.getByRole('button', { name: 'chargement des données' }).click();
await expect(page.getByText('Element chargé au click sur le bouton')).toBeVisible();
await page.goto('https://votre-url/');
await context.setOffline(true);
await page.getByRole('button', { name: 'chargement des données’ }).click();
await expect(page.getByText('Element chargé au click sur le bouton’)).toBeVisible();
});
Dans cet exemple, on :
- Arrive sur notre application en mode online
- Vérifie que notre Titre s’affiche bien
- Passe en mode hors-ligne
- Clique sur notre bouton ‘Chargement des données’
- Vérifie qu’un message d’erreur s’affiche
- Passe en mode online
- Clique de nouveau sur le bouton ‘Chargement des données’
- Vérifie que notre Élément chargé s’affiche
- Rafraîchit notre page
- Passe en mode hors-ligne
- Clique de nouveau sur le bouton ‘Chargement des données’
- Vérifie que notre Élément chargé s’affiche toujours
David contre Goliath
Playwright, malgré son jeune âge (première release en 2020), possède quelques caractéristiques qui lui donnent un avantage sur son aîné Cypress (première release en 2017). En effet, en plus d’avoir prouvé qu’il était le mieux placé pour réaliser des tests sur la gestion du cache en simulant le mode hors-ligne, on peut retrouver les caractéristiques suivantes :
- Multi-navigateurs : Playwright prend en charge Chromium, Firefox et WebKit (Safari), tandis que Cypress ne prend en charge que Chromium.
- Meilleure gestion des onglets et des fenêtres : Playwright peut tester des scénarios impliquant plusieurs onglets ou fenêtres.
- Meilleure gestion des requêtes réseau : Playwright offre un meilleur contrôle sur les requêtes réseau et leur interception.
- Performance : Playwright est généralement plus rapide que Cypress pour l'exécution des tests.
- Moins de limitations : Playwright a moins de limitations techniques que Cypress.
Conclusion
Les tests sont une étape importante qu’il ne faut pas négliger (ou même esquiver) même si elle peut se révéler capricieuse.
Nos expérimentations désastreuses avec Cypress nous ont fait découvrir Playwright et affirmer que c’est ce deuxième outil de tests qui permet de valider le comportement de notre application de bout en bout. Playwright évite, grâce à ses méthodes liées au browser, d’avoir à créer des méthodes complexes comme dans les exemples cités pour Cypress.