Todo navegador no iOS, incluindo o Chrome, roda sobre o WebKit por baixo. Um bug CSS no Safari afeta todos os navegadores iOS, e um teste que passa no Chrome do Android não diz nada sobre o comportamento no iOS. A maior parte do valor dos testes mobile cobre 80% dos cenários com a emulação de dispositivos nativa do Playwright, sem precisar de um laboratório físico. Com uma linha de configuração você define viewport, user agent, pixel ratio e suporte a toque.
Desafios dos testes mobile
Fragmentação de dispositivos: O Android roda em milhares de dispositivos diferentes com tamanhos de tela, versões de SO e hardware variados. O iOS é mais controlado, mas ainda tem múltiplos tamanhos de tela e versões. Interações por toque: Usuários mobile tocam, deslizam, fazem pinch e rotacionam a tela. Testes de clique com mouse no desktop não cobrem nada disso. Condições de rede: Usuários mobile frequentemente têm conexões lentas e instáveis. 3G, 4G, wi-fi com sinal fraco: sua aplicação precisa lidar com tudo isso. Limitações de hardware: Dispositivos mobile têm menos RAM, CPUs mais lentas e baterias menores que desktops. O que é rápido no desktop pode ser lento no mobile. Diferenças de plataforma: iOS e Android tratam notificações, deep links, compartilhamento de arquivos, acesso à câmera e push notifications de formas diferentes. O problema do WebKit no iOS: TODOS os navegadores no iOS usam WebKit por baixo. Chrome no iPhone usa o motor do Safari, não o engine do Chrome. Bugs no WebKit afetam todos os navegadores iOS.Tipos de testes mobile
Testes de web responsiva
Testar sua web app em viewports mobile, sem precisar instalar nada no dispositivo.
// Playwright — testar viewport mobile
test('página de login funciona no mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE
await page.goto('/login');
// UI específica de mobile deve aparecer
await expect(page.getByTestId('mobile-header')).toBeVisible();
await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
// Funcionalidade principal funciona
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'SenhaValida1');
await page.tap('[data-testid="submit"]'); // tap em vez de click
await expect(page).toHaveURL('/dashboard');
});Testes de app nativo
Apps instalados pela App Store ou Google Play. Exige Appium, Detox (React Native) ou XCUITest/Espresso diretamente.
Testes de Progressive Web App (PWA)
Web apps que se comportam como apps nativos: instaláveis, funcionam offline, enviam push notifications.
Playwright para testes de web mobile
A emulação de dispositivos do Playwright cobre os cenários mais importantes de testes mobile para web apps:
// 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'] } },
],
});Dispositivos disponíveis (veja npx playwright show-devices):
iPhone 14,iPhone 14 Pro,iPhone SEPixel 7,Galaxy S21iPad Pro,iPad Mini
A emulação de dispositivos configura:
- Tamanho do viewport
- String de user agent
- Device pixel ratio
- Suporte a eventos de toque
Eventos de toque vs eventos de mouse
Usuários mobile tocam, não clicam. O click() do Playwright funciona nos dois casos, mas às vezes você precisa de toque explícito:
test('swipe no carrossel funciona', 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) {
// Simula swipe para a esquerda (dedo move da direita para a esquerda)
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();
}
// Verifica que o próximo slide está visível
await expect(page.getByTestId('slide-2')).toBeVisible();
});Testando layouts responsivos
Verifique se a UI se adapta corretamente em diferentes breakpoints:
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(`navegação renderiza corretamente em ${viewport.name}`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/');
if (viewport.width < 768) {
// Mobile: menu hamburguer
await expect(page.getByTestId('hamburger')).toBeVisible();
await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
} else {
// Desktop/tablet: nav completa
await expect(page.getByTestId('hamburger')).not.toBeVisible();
await expect(page.getByTestId('desktop-nav')).toBeVisible();
}
});
}Throttling de rede
Teste como sua app se comporta em redes mobile lentas:
test('app carrega em 3G lento', async ({ page, context }) => {
// Throttle para velocidades de 3G
const cdpSession = await context.newCDPSession(page);
await cdpSession.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: 780 * 1024 / 8, // 780 kbps de download 3G
uploadThroughput: 330 * 1024 / 8, // 330 kbps de upload 3G
latency: 100, // 100ms de latência
});
const startTime = Date.now();
await page.goto('/');
const loadTime = Date.now() - startTime;
// Página deve carregar em menos de 10 segundos em 3G
expect(loadTime).toBeLessThan(10000);
await expect(page.getByTestId('main-content')).toBeVisible();
});
test('app funciona offline (PWA)', async ({ page, context }) => {
await page.goto('/');
// Vai offline
await context.setOffline(true);
await page.reload();
// PWA deve mostrar conteúdo em cache ou página offline
await expect(page.getByTestId('offline-message')).toBeVisible();
// OU
await expect(page.getByTestId('main-content')).toBeVisible(); // Conteúdo em cache
// Volta online
await context.setOffline(false);
});Testes em dispositivos reais com BrowserStack
Emulação cobre a maioria dos cenários. Para dispositivos reais:
- BrowserStack — dispositivos iOS e Android reais na nuvem
- Sauce Labs — oferta similar
- Firebase Test Lab — farm de dispositivos do Google (foco em Android)
// Configuração do Playwright para dispositivos reais no 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 mobile comuns para testar
Tamanho de tap target: Botões pequenos demais para tocar com precisão (menos de 44x44px no iOS, 48x48dp no Android).test('todos os elementos interativos têm tamanho suficiente para tocar', 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, `Botão estreito demais: ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
expect(box.height, `Botão baixo demais: ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
}
}
});test('sem scroll horizontal no 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); // Sem overflow horizontal
});font-size: 16px como mínimo.
Checklist de testes mobile
Antes de cada release:
- [ ] Fluxos principais testados no iPhone (Safari) e Android (Chrome)
- [ ] Layout responsivo verificado em 375px, 768px e 1280px
- [ ] Tap targets com pelo menos 44x44px
- [ ] Sem scroll horizontal em viewports mobile
- [ ] Formulários funcionam com teclados mobile
- [ ] Estados de loading visíveis em conexões lentas
- [ ] Imagens com tamanhos adequados para mobile (não imagens dimensionadas para desktop)
- [ ] Texto legível sem zoom (mínimo 16px)
Resumo
| Tipo de teste | Ferramenta | Quando usar |
|--------------|------------|-------------|
| Web responsiva | Viewport do Playwright | A cada sprint |
| Mobile cross-browser | Preset de devices do Playwright | Antes do release |
| Throttling de rede | Chrome DevTools Protocol no Playwright | Features com uso intenso de mobile |
| Dispositivos reais | BrowserStack, Sauce Labs | Antes de releases maiores |
| Apps nativos | Appium, Detox | Projeto de app nativo separado |
A maioria das web apps precisa de testes responsivos (80% do valor dos testes mobile) mais verificações ocasionais em dispositivos reais para o comportamento do Safari no iOS. Reserve farms de dispositivos reais para validação pré-release e bugs conhecidos específicos de plataforma.
→ Veja também: Emulação Móvel no Playwright: Testes Responsivos e Táteis | Estratégias de Testes Cross-Browser: Quando e Como Testar em Múltiplos Navegadores | Testes de Acessibilidade com Playwright: Verificações a11y Automatizadas