Temos o prazer de anunciar que, a partir da versão 23, a biblioteca de automação do navegador Puppeteer agora tem suporte de primeira classe para Firefox. Isso significa que agora é fácil escrever automação e realizar testes ponta a ponta usando o Puppeteer e executá-lo no Chrome e no Firefox.
Como usar o Puppeteer com Firefox
Para começar, basta definir o produto como “firefox”Ao iniciar o Puppeteer:
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
browser: "firefox"
});
const page = await browser.newPage();
// ...
await browser.close();Tal como acontece com o Chrome, o Puppeteer é capaz de baixar e iniciar a versão estável mais recente do Firefox, portanto, a execução em qualquer um dos navegadores deve oferecer a mesma experiência de desenvolvedor que os usuários do Puppeteer esperam.
Embora os recursos oferecidos pelo Puppeteer não sejam uma surpresa, trazer suporte para vários navegadores tem sido uma tarefa significativa. O suporte do Firefox não é baseado em um protocolo de automação específico do Firefox, mas no WebDriver BiDi, um protocolo cross-browser que está sendo padronizado no W3C e atualmente tem implementação no Gecko e no Chromium. Esse uso de um protocolo entre navegadores deve tornar muito mais fácil o suporte a muitos navegadores diferentes daqui para frente.
Posteriormente nesta postagem, mergulharemos em alguns dos conhecimentos mais técnicos por trás do WebDriver BiDi. Mas primeiro gostaríamos de salientar que o anúncio de hoje é uma grande demonstração de como a colaboração produtiva pode promover o estado da arte na web. Desenvolver um novo protocolo de automação de navegador dá muito trabalho, e muito obrigado à equipe do Puppeteer e aos outros membros do Grupo de Trabalho de Testes e Ferramentas de Navegador do W3C, por todos os seus esforços para nos levar até este ponto.
Você também pode conferir a postagem da equipe do Puppeteer sobre como preparar a produção do WebDriver BiDi.
Principais recursos
Para usuários antigos do Puppeteer, os recursos disponíveis são familiares. No entanto, para pessoas em outros ecossistemas de automação e teste – particularmente aqueles que até recentemente dependiam inteiramente do WebDriver baseado em HTTP – esta seção descreve algumas das novas funcionalidades que o WebDriver BiDi torna possível implementar em vários navegadores.
Captura de mensagens de log
Um requisito comum ao testar aplicativos web é garantir que não haja erros inesperados relatados ao console. Este também é um caso em que um protocolo baseado em eventos se destaca, pois evita a necessidade de pesquisar o navegador em busca de novas mensagens de log.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
browser: "firefox"
});
const page = await browser.newPage();
page.on('console', msg => {
console.log(`[console] ${msg.type()}: ${msg.text()}`);
});
await page.evaluate(() => console.debug('Some Info'));
await browser.close();Saída:
[console] debug: Some Info
Emulação de dispositivo
Muitas vezes, ao testar um layout reativo, é útil garantir que o layout funcione bem em diversas dimensões de tela e proporções de pixels do dispositivo. Isso pode ser feito usando um navegador móvel real, em um dispositivo ou em um emulador. No entanto, para simplificar, pode ser útil realizar o teste em um desktop configurado para imitar a janela de visualização de um dispositivo móvel. O exemplo abaixo mostra o carregamento de uma página com o Firefox configurado para emular o tamanho da janela de visualização e a proporção de pixels do dispositivo de um telefone Pixel 5.
import puppeteer from "puppeteer";
const device = puppeteer.KnownDevices["Pixel 5"];
const browser = await puppeteer.launch({
browser: "firefox"
});
const page = await browser.newPage();
await page.emulate(device);
const viewport = page.viewport();
console.log(
`[emulate] Pixel 5: ${viewport.width}x${viewport.height}` +
` (dpr=${viewport.deviceScaleFactor}, mobile=${viewport.isMobile})`
);
await page.goto("https://www.mozilla.org");
await browser.close();
Saída:
[emulate] Pixel 5: 393x851 (dpr=3, mobile=true)
Interceptação de rede
Um requisito comum para testes é ser capaz de rastrear e interceptar solicitações de rede. A interceptação é especialmente útil para evitar solicitações a serviços de terceiros durante os testes e fornecer dados de resposta simulados. Também pode ser usado para lidar com caixas de diálogo de autenticação HTTP e substituir partes da solicitação e resposta, por exemplo, adicionar ou remover cabeçalhos. No exemplo abaixo, usamos a interceptação de solicitação de rede para bloquear todas as solicitações de fontes da web em uma página, o que pode ser útil para garantir que a falha no carregamento dessas fontes não interrompa o layout do site.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
browser: 'firefox'
});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
if (request.url().includes(".woff2")) {
// Block requests to custom user fonts.
console.log(`[intercept] Request aborted: ${request.url()}`);
request.abort();
} else {
request.continue();
}
});
const response = await page.goto("https://support.mozilla.org");
console.log(
`[navigate] status=${response.status()} url=${response.url()}`
);
await browser.close();Saída:
[intercept] Request aborted: https://assets-prod.sumo.prod.webservices.mozgcp.net/static/Inter-Bold.3717db0be15085ac.woff2 [navigate] status=200 url=https://support.mozilla.org/en-US/
Pré-carregar scripts
Freqüentemente, as ferramentas de automação desejam fornecer funcionalidades personalizadas que podem ser implementadas em JavaScript. Embora o WebDriver sempre tenha permitido a injeção de scripts, não foi possível garantir que um script injetado sempre fosse executado antes do início do carregamento da página, impossibilitando evitar corridas entre os scripts da página e o script injetado.
WebDriver BiDi fornece scripts de “pré-carregamento” que podem ser executados antes de uma página ser carregada. Ele também fornece um meio de emitir eventos personalizados de scripts. Isso pode ser usado, por exemplo, para evitar a pesquisa de elementos esperados, mas em vez disso, usar um observador de mutação que é acionado assim que o elemento estiver disponível. No exemplo abaixo esperamos pelo
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
browser: 'firefox',
});
const page = await browser.newPage();
const gotMessage = new Promise(resolve =>
page.exposeFunction("sendMessage", async message => {
console.log(`[script] Message from pre-load script: ${message}`);
resolve();
})
);
await page.evaluateOnNewDocument(() => {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
for (const node of mutation.addedNodes) {
if (node.tagName === "TITLE") {
sendMessage(node.textContent);
}
}
}
};
});
observer.observe(document.documentElement, {
subtree: true,
childList: true,
});
});
await page.goto("https://support.mozilla.org");
await gotMessage;
await browser.close();Saída:
[script] Message from pre-load script: Mozilla Support
Antecedentes Técnicos
Até recentemente, as pessoas que desejavam automatizar os navegadores tinham duas opções principais:
Infelizmente, ambas as opções apresentam compensações significativas. A API “clássica” do WebDriver é baseada em HTTP e seu modelo envolve automação enviando um comando ao navegador e aguardando uma resposta. Isso funciona bem para cenários de automação onde você carrega uma página e depois verifica, por exemplo, se algum elemento é exibido, mas a incapacidade de obter eventos – por exemplo, logs do console – de volta do navegador ou executar vários comandos simultaneamente, torna a API uma opção inadequada para casos de uso mais avançados.
Por outro lado, as APIs específicas do navegador geralmente foram projetadas para oferecer suporte aos casos de uso complexos de ferramentas de desenvolvimento no navegador. Isso deu a eles um conjunto de recursos muito mais avançado do que é possível usar o WebDriver, pois eles precisam oferecer suporte a casos de uso como gravação de logs do console ou solicitações de rede.
Portanto, os clientes de automação de navegador foram forçados a escolher entre oferecer suporte a muitos navegadores usando um único protocolo e fornecer um conjunto limitado de recursos ou fornecer um conjunto de recursos mais rico, mas tendo que implementar vários protocolos para fornecer funcionalidade separadamente para cada navegador suportado. Obviamente, isso aumentou o custo e a complexidade da criação de uma excelente automação entre navegadores, o que não é uma boa situação, especialmente quando os desenvolvedores costumam citar os testes entre navegadores como um dos principais pontos problemáticos no desenvolvimento para a web.
Os desenvolvedores de longa data podem notar a analogia aqui com a situação dos editores antes do desenvolvimento do Language Server Protocol (LSP). Naquela época, cada editor de texto ou IDE tinha que implementar suporte personalizado para cada linguagem de programação diferente. Isso dificultou a obtenção de suporte para uma nova linguagem em todas as ferramentas que os desenvolvedores usavam. O advento do LSP mudou isso, fornecendo um protocolo comum que poderia ser suportado por qualquer combinação de editor e linguagem de programação. Para que uma nova linguagem de programação como TypeScript seja suportada por todos os editores, não é mais necessário que eles adicionem suporte um por um; ele só precisa fornecer um servidor LSP e será automaticamente suportado em qualquer editor que suporte LSP. O advento deste protocolo comum também possibilitou coisas que antes eram difíceis de imaginar. Por exemplo, bibliotecas específicas como Tailwind obtendo sua própria implementação LSP para habilitar funcionalidades de editor personalizadas.
Portanto, para melhorar a automação entre navegadores, adotamos uma abordagem semelhante: desenvolver o WebDriver BiDi, que traz o conjunto de recursos de automação anteriormente limitado a protocolos específicos do navegador para um protocolo padronizado que pode ser implementado por qualquer navegador e usado por qualquer ferramenta de automação em qualquer linguagem de programação.
Na Mozilla, vemos esta estratégia de padronização de protocolos para remover barreiras à entrada, permitir o florescimento de um ecossistema diversificado de implementações interoperáveis e permitir que os usuários escolham aqueles que melhor se adaptam às suas necessidades como parte fundamental do nosso manifesto e visão web.
Para obter mais detalhes sobre o design do WebDriver BiDi e como ele se relaciona com o WebDriver clássico, consulte nossas postagens anteriores.
Removendo suporte experimental a CDP no Firefox
Como parte de nosso trabalho inicial para melhorar os testes entre navegadores, lançamos uma implementação parcial do CDP, limitada a alguns comandos e eventos necessários para dar suporte aos casos de uso de testes. Anteriormente, esta era a base do suporte experimental para Firefox no Puppeteer. No entanto, quando ficou claro que esse não era o caminho a seguir para a automação entre navegadores, os esforços nesse sentido foram interrompidos. Como resultado, ele não tem manutenção e não funciona com os recursos modernos do Firefox, como o isolamento de sites. Portanto, o suporte está programado para ser removido no final de 2024.
Se você está usando CDP com Firefox e não sabe como fazer a transição para WebDriver BiDi, entre em contato usando um dos canais listados no final desta postagem e discutiremos suas necessidades.
O que vem a seguir?
Embora o Firefox agora seja oficialmente suportado no Puppeteer e tenha funcionalidade suficiente para cobrir muitos cenários de automação e teste, ainda existem algumas APIs que permanecem sem suporte. Eles se enquadram amplamente em três categorias (consulte a documentação do Puppeteer para obter uma lista completa):
- APIs altamente específicas de CDP, principalmente aquelas do módulo CDPSession. É improvável que sejam suportadas diretamente, mas casos de uso específicos que atualmente exigem essas APIs podem ser candidatos à padronização.
- APIs que exigem padrões adicionais funcionam. Por exemplo, page.accessibility.snapshot retorna um dump da árvore de acessibilidade do Chromium. No entanto, como atualmente não há uma descrição padronizada de como essa árvore deve ser, é difícil fazer isso funcionar em vários navegadores. Há também casos que são muito mais simples, pois requerem apenas trabalho na própria especificação WebDriver BiDi; por exemplo page.setGeolocation.
- APIs que possuem um padrão, mas ainda não foram implementadas, por exemplo, a capacidade de executar scripts em trabalhadores necessários para comandos como WebWorker.evaluate.
Esperamos preencher essas lacunas daqui para frente. Para ajudar a priorizar, estamos interessados em seus comentários: Tente executar seus testes do Puppeteer no Firefox! Se você não conseguir obtê-los no Firefox devido a um bug ou a um recurso ausente, informe-nos usando um dos métodos abaixo para que possamos levar isso em consideração ao planejar nossos padrões futuros e trabalho de implementação:
Engenheiro de software focado em manter uma web aberta e saudável. Membro da equipe principal de testes de plataforma web.
Mais artigos de James Graham…
Mais artigos de Henrik Skupin…
Mais artigos de Julian Descottes…
Mais artigos de Alexandra Borovova…

