Les tests mobiles couvrent les interactions tactiles, la fragmentation des appareils, la simulation des conditions réseau, les contraintes WebKit sur iOS et les compromis entre appareils réels et émulateurs. Chacun de ces aspects demande des techniques différentes du test web desktop.
Défis spécifiques au mobile
Fragmentation des appareils : Android tourne sur des milliers d'appareils différents, avec des tailles d'écran, versions d'OS et configurations matérielles variées. iOS est plus homogène, mais reste fragmenté sur les tailles et versions. Interactions tactiles : Sur mobile, on tape, glisse, pince et pivote. Les tests basés sur le clic souris ne couvrent pas ces interactions. Conditions réseau : Les utilisateurs mobiles fonctionnent souvent avec des connexions lentes ou instables (3G, 4G, Wi-Fi capricieux). L'application doit gérer ces conditions sans planter. Contraintes de performance : Moins de RAM, processeurs plus lents, batteries limitées. Ce qui est rapide sur desktop peut être lent sur mobile. Différences de plateforme : iOS et Android gèrent différemment les notifications, les deep links, le partage de fichiers, l'accès caméra et les notifications push. Le problème WebKit sur iOS : Tous les navigateurs iOS utilisent WebKit en interne. Chrome sur iPhone n'est pas Chrome. C'est le moteur de Safari enveloppé dans une interface Chrome. Un bug WebKit affecte tous les navigateurs iOS sans exception.Types de tests mobiles
Tests web responsive
Tester l'application web dans des viewports mobiles, sans installation d'application.
// Playwright — test sur viewport mobile
test('la page de connexion fonctionne sur mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE
await page.goto('/login');
// L'interface mobile doit apparaître
await expect(page.getByTestId('mobile-header')).toBeVisible();
await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
// Les fonctionnalités de base fonctionnent
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'ValidPass1');
await page.tap('[data-testid="submit"]'); // tap plutôt que click
await expect(page).toHaveURL('/dashboard');
});Tests d'applications natives
Applications installées depuis l'App Store ou Google Play. Nécessite Appium, Detox (React Native) ou XCUITest/Espresso directement.
Tests Progressive Web App (PWA)
Applications web qui se comportent comme des applications natives : installables, fonctionnelles hors ligne, capables d'envoyer des notifications push.
Playwright pour les tests web mobiles
L'émulation de périphériques de Playwright couvre les scénarios de test mobile les plus courants pour les applications web :
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
// Desktop
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
// Mobile
{ name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
{ name: 'tablet', use: { ...devices['iPad Pro'] } },
],
});Appareils disponibles (voir npx playwright show-devices) : iPhone 14, iPhone 14 Pro, iPhone SE, Pixel 7, Galaxy S21, iPad Pro et iPad Mini.
L'émulation configure automatiquement la taille du viewport, le user agent, le ratio pixel de l'appareil et la prise en charge des événements tactiles.
Événements tactiles vs événements souris
Sur mobile, on tape. Playwright's click() fonctionne dans les deux cas, mais certains scénarios demandent un toucher explicite :
test('le carousel swipe fonctionne', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/products');
const carousel = page.getByTestId('product-carousel');
const box = await carousel.boundingBox();
if (box) {
// Simuler un swipe vers la gauche (le doigt se déplace de droite à gauche)
await page.touchscreen.tap(box.x + box.width - 50, box.y + box.height / 2);
await page.mouse.move(box.x + box.width - 50, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + 50, box.y + box.height / 2, { steps: 20 });
await page.mouse.up();
}
// Vérifier que la diapositive suivante est visible
await expect(page.getByTestId('slide-2')).toBeVisible();
});Tester les mises en page responsive
Vérifier que l'interface s'adapte correctement aux différents points de rupture :
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'mobile-large', width: 414, height: 896 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1280, height: 720 },
];
for (const viewport of viewports) {
test(`la navigation s'affiche correctement sur ${viewport.name}`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/');
if (viewport.width < 768) {
// Mobile : menu hamburger
await expect(page.getByTestId('hamburger')).toBeVisible();
await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
} else {
// Desktop/tablette : navigation complète
await expect(page.getByTestId('hamburger')).not.toBeVisible();
await expect(page.getByTestId('desktop-nav')).toBeVisible();
}
});
}Limitation de la bande passante
Tester le comportement de l'application sur des réseaux mobiles lents :
test('l\'application charge sur une 3G lente', async ({ page, context }) => {
// Limiter à la vitesse 3G
const cdpSession = await context.newCDPSession(page);
await cdpSession.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: 780 * 1024 / 8, // 780 kbps 3G download
uploadThroughput: 330 * 1024 / 8, // 330 kbps 3G upload
latency: 100, // 100ms de latence
});
const startTime = Date.now();
await page.goto('/');
const loadTime = Date.now() - startTime;
// La page doit charger en moins de 10 secondes sur 3G
expect(loadTime).toBeLessThan(10000);
await expect(page.getByTestId('main-content')).toBeVisible();
});
test('l\'application fonctionne hors ligne (PWA)', async ({ page, context }) => {
await page.goto('/');
// Passer hors ligne
await context.setOffline(true);
await page.reload();
// La PWA doit afficher du contenu en cache ou une page hors ligne
await expect(page.getByTestId('offline-message')).toBeVisible();
// OU
await expect(page.getByTestId('main-content')).toBeVisible(); // Contenu en cache
// Repasser en ligne
await context.setOffline(false);
});Tests sur appareils réels avec BrowserStack
L'émulation couvre la plupart des scénarios. Pour les appareils réels, les services cloud principaux sont BrowserStack (iOS et Android dans le cloud), Sauce Labs (offre similaire) et Firebase Test Lab (parc de Google, orienté Android).
// Configuration Playwright pour les appareils réels BrowserStack
export default defineConfig({
projects: [
{
name: 'real-iphone-14',
use: {
connectOptions: {
wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify({
'browserstack.user': process.env.BS_USER,
'browserstack.key': process.env.BS_KEY,
'browserName': 'safari',
'bstack:options': {
deviceName: 'iPhone 14',
osVersion: '16',
}
}))}`,
},
},
},
],
});Bugs mobiles courants à tester
Taille des zones de tap : Des boutons trop petits pour être touchés avec fiabilité (moins de 44x44px sur iOS, 48x48dp sur Android).test('tous les éléments interactifs sont assez grands pour être touchés', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
const buttons = await page.locator('button, a, [role="button"]').all();
for (const button of buttons) {
const box = await button.boundingBox();
if (box) {
expect(box.width, `Bouton trop étroit : ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
expect(box.height, `Bouton trop court : ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
}
}
});test('pas de défilement horizontal sur mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
expect(scrollWidth).toBe(clientWidth); // Pas de dépassement horizontal
});Checklist de tests mobiles
Avant chaque release :
- [ ] Parcours principaux testés sur iPhone (Safari) et Android (Chrome)
- [ ] Mise en page responsive vérifiée à 375px, 768px, 1280px
- [ ] Zones de tap d'au moins 44x44px
- [ ] Pas de défilement horizontal sur les viewports mobiles
- [ ] Formulaires utilisables avec les claviers mobiles
- [ ] États de chargement visibles sur les connexions lentes
- [ ] Images correctement dimensionnées pour le mobile
- [ ] Texte lisible sans zoom (minimum 16px)
Récapitulatif
| Type de test | Outil | Quand l'utiliser |
|---|---|---|
| Web responsive | Viewport Playwright | Chaque sprint |
| Cross-browser mobile | Presets devices Playwright | Avant chaque release |
| Limitation réseau | Chrome DevTools Protocol via Playwright | Fonctionnalités à usage mobile intensif |
| Appareils réels | BrowserStack, Sauce Labs | Avant les releases majeures |
| Applications natives | Appium, Detox | Projet natif séparé |
Pour la plupart des applications web, les tests responsive couvrent 80 % de la valeur des tests mobiles. Les vérifications sur appareils réels sont à réserver à la validation pré-release et aux bugs connus liés à des plateformes spécifiques, notamment le comportement de Safari iOS.
→ See also: Émulation Mobile dans Playwright: Tests Responsifs et Tactiles | Stratégies de Tests Cross-Browser: Quand et Comment Tester sur Plusieurs Navigateurs | Tests d'Accessibilité avec Playwright: Vérifications a11y Automatisées