Playwright é o framework de automação de navegador da Microsoft: uma biblioteca Apache-2.0, pensada primeiro para TypeScript, que abre um navegador real, o controla por uma única API e devolve a página depois que o JavaScript termina de rodar. Ele é vendido como uma ferramenta de testes end-to-end, mas o motor por trás dele é justamente o que muita gente usa em silêncio quando uma requisição HTTP retorna uma casca vazia onde os dados deveriam estar. Em termos de proposta, ele compete com Puppeteer e Selenium — navegadores reais que você automatiza, e não clientes HTTP que você precisa interpretar.
Executei microsoft/playwright 1.56.0 em um conjunto fixo de testes de scraping — um catálogo estático com paginação, um artigo, um catálogo renderizado por JavaScript, uma API JSON, um erro 500, um pequeno grafo de crawl e dois sites públicos de prática — em Node v22.22.3, macOS arm64, apenas Chromium. A parte de renderização funcionou bem. A parte de crawling simplesmente não existe, e essa é a lacuna mais importante para entender a ferramenta antes de adotá-la.
O que mais chamou atenção
Dois resultados ficaram no topo e apontam para direções um pouco diferentes.
O primeiro é o comportamento esperado do navegador, limitado pelas esperas que usei. Depois de page.goto(..., { waitUntil: 'domcontentloaded' }), o teste dinâmico aguardou por #dynamic-products article.product-card, e o teste público Quotes to Scrape aguardou por .quote. Esses seletores específicos apareceram, e então os testes retornaram 8/8 itens do fixture e 10 quotes do site público; no fixture, o resultado foi recall 1.0 contra sua verdade-terreno de oito itens. Não precisei de um loop de polling personalizado, mas o Playwright também não eliminou o problema de prontidão — o teste é que forneceu a condição de conclusão. Um screenshot de página inteira também foi salvo na primeira tentativa.
O segundo resultado usou browserContext.request: mais especificamente, ctx.request.get(...) depois que o Chromium já havia sido iniciado e um browser context já existia. Ele chamou diretamente o endpoint JSON do fixture e retornou 8/8 produtos sem criar nem renderizar uma página. Isso elimina o trabalho de DOM, mas não o custo do processo do navegador neste harness. O cliente de requisição associado ao contexto pode compartilhar cookies com as páginas do navegador; um playwright.request.newContext() isolado evita a necessidade de um browser context, mas não compartilha a sessão automaticamente. Neste teste, só percorri o primeiro caminho.
O Playwright também não oferece fila de crawl, gravador de dataset nem throttling automático. Meu teste de grafo de crawl — percorrer links internos, controlar profundidade e evitar revisitas — chegou a 12 páginas nas profundidades {0:1, 1:4, 2:7}, mas a travessia em largura foi código de teste que eu mesmo escrevi. O Playwright abre e inspeciona páginas; persistência da fronteira, política de URLs, retries e agendamento ficam para outra camada.
O que o Playwright realmente é
A ferramenta — microsoft/playwright no GitHub — é escrita em TypeScript, licenciada sob Apache-2.0 e mantida pela Microsoft. A versão testada aqui foi 1.56.0, de 9 de julho de 2026. Os resultados são reportados para essa build, e não como uma declaração de compatibilidade com versões posteriores.
O posicionamento oficial é preciso: um framework para testes e automação web que controla Chromium, Firefox e WebKit por meio de uma única API. O caminho principal do Playwright é como test runner, com fixtures, assertions e trace viewer. Usá-lo como base de scraping significa seguir o modo documentado Library mode: chromium.launch(), depois um context, depois uma page, fora do harness de testes. Tudo nesta análise usou essa API pública. Não rodei um teste de compatibilidade entre versões, então isto não é uma afirmação de estabilidade de todos os comportamentos em versões futuras.
A amplitude documentada é a grande promessa em qualquer outra comparação, então vou colocá-la com cuidado. O Playwright controla três engines de navegador — Chromium, Firefox e WebKit — por meio de uma única API, e oferece clientes de primeira classe em Python, Java e .NET, além de JavaScript. Isso está documentado e, de fato, é o maior diferencial da ferramenta em termos de formato. O que este teste realmente exerceu é mais restrito:
| Capacidade | Status nesta análise |
|---|---|
| Engine Chromium | Exercitada — todos os testes aqui rodaram no Chromium |
| Engine Firefox | Documentada, não verificada aqui |
| Engine WebKit | Documentada, não verificada aqui |
| Uma API para as três engines | Documentada, não verificada aqui |
| Clientes Python, Java e .NET | Documentados, não verificados aqui |
| Proxy | Não testado |
| Escala com múltiplos contexts em paralelo | Não testada |
| Interceptação de rede para scraping com foco em API | Não testada |
Se um alvo renderiza de forma diferente no WebKit do Safari, ou se sua equipe escreve em Python, essa abrangência é o argumento do Playwright — só não use meus resultados como prova de paridade com Firefox ou WebKit, porque eu não os testei.
Como funciona por baixo do capô
O modelo mental é o de um motor de navegador que você automatiza. chromium.launch() inicia um processo de navegador. Um context é uma sessão isolada, com cookies, storage e cache próprios; uma page é uma aba dentro desse contexto. Você chama page.goto(url), espera pela condição que representa a prontidão da aplicação e lê o DOM resultante com helpers como page.$$eval. Isso se aproxima mais de um navegador usado por uma pessoa do que da leitura de uma resposta HTTP, mas não é paridade ambiental: sinais de headless, viewport, idioma, fontes, estado do perfil, caminho TLS/rede e mecanismos de proteção do site ainda podem mudar o que é servido. Esta análise não testou comportamento anti-bot nem paridade com navegador em produção.
page.screenshot() captura a página renderizada, inteira ou recortada, e no meu teste funcionou de primeira. E a API de request que mencionei — context.request.get — usa os cookies do mesmo context, mas pula a renderização. Assim, você pode misturar “carregar a página e ler o DOM” com “chamar apenas o endpoint JSON” no mesmo script, sem trocar de ferramenta.
O que não existe por baixo do capô é uma infraestrutura de crawling. Não há agendador de requests, conjunto de URLs já visitadas persistido, política de cordialidade, nem pipeline de exportação. Uma travessia com limite é fácil de esboçar, mas um frontier confiável também precisa de normalização de URLs, tratamento de redirects, retries, regras de escopo, throttling e recuperação. Essa camada você constrói ou usa um framework que já envolva o motor do navegador.
Realidade de instalação e setup
A instalação acontece em duas etapas, e a segunda carrega a maior parte do peso operacional. npm install playwright instala a biblioteca; um npx playwright install separado baixa os builds de navegador (Chromium, no meu caso). Planeje espaço em disco, tempo de download, cache de navegadores em CI e limpeza de processos, em vez de tratar o pacote npm como se fosse o sistema executável completo.
Se você instalar o Playwright esperando um scraper e seguir o tutorial de testes, vai começar com arquivos de teste e assertions expect(). Já o código de scraping usa diretamente a API da biblioteca. Ambos estão documentados, mas a distinção importa quando você procura exemplos e escolhe comandos de implantação.
Neste teste, a experiência prática foi bem clara: browser contexts isolaram o estado de sessão, chamadas assíncronas se combinaram de forma limpa, um screenshot levou uma única chamada e um HTTP 500 continuou inspecionável pelo objeto de response. O ponto fraco foi operacional, não sintático: o build do navegador precisou ser instalado e seu ciclo de vida precisou ser gerenciado separadamente da biblioteca.
Resultados práticos

Todos os números locais foram obtidos contra um servidor de fixture em 127.0.0.1, com a verdade-terreno registrada antes do crawl. O harness exato está em run_playwright_material_tests.mjs, e os artefatos brutos comitados incluem a verdade-terreno e as saídas de cada teste. Eles continuam sendo observações do autor, em uma máquina e uma execução; os links mostram o caminho de reprodução, mas não transformam isso em um benchmark amplo.
| Teste | Alvo | Resultado |
|---|---|---|
| Catálogo estático + paginação | fixture local | 12/12 produtos, recall 1.0 |
| Extração de artigo | fixture local | título + 3/3 parágrafos, boilerplate separado |
| Página dinâmica em JS (render nativo) | fixture local | 8/8, recall 1.0, screenshot de página inteira salvo |
API JSON dinâmica (page.request) | fixture local | 8/8, recall 1.0, sem renderizar DOM |
| Tratamento de HTTP 500 | fixture local | status 500 inspecionável, navegação não lançou erro |
| Grafo de crawl (BFS escrito à mão) | fixture local | 12 páginas, profundidades {0:1, 1:4, 2:7} |
| Books to Scrape | demo público | 20 produtos |
| Quotes JS (renderizado em JS) | demo público | 10 quotes, renderizado nativamente |
O loop de paginação seguiu explicitamente o link de próxima página; o Playwright não descobriu páginas sozinho. O seletor do artigo manteve navegação e texto de rodapé fora do resultado do corpo. Na rota de falha, a navegação retornou um objeto de response com status 500 em vez de lançar erro, deixando para quem chama decidir se deve registrar, tentar novamente ou continuar. Os dois alvos públicos de prática retornaram as contagens mostradas na tabela.
Vale deixar uma fronteira clara: tudo aqui rodou em Chromium, em uma máquina, uma única vez. A tabela de capacidades separa amplitude documentada de comportamento efetivamente exercitado. Não reexecutei a suíte em outro build do Playwright, então não dá para concluir nada sobre versões diferentes. Os tempos por teste também foram omitidos como benchmark; uma única passada de cronômetro em um notebook não sustenta comparação de velocidade.
Prontidão faz parte do contrato de extração
Os resultados dinâmicos dependeram de esperas que representavam os dados desejados, e não apenas a navegação do navegador. No catálogo local, o harness navegou com waitUntil: 'domcontentloaded' e depois chamou waitForSelector('#dynamic-products article.product-card') com timeout de 15 segundos. A execução pública de Quotes JS usou o mesmo estado de navegação e aguardou .quote com timeout de 20 segundos. A extração só começou depois que esses seletores apareceram.
Essa diferença importa quando você adapta o script. domcontentloaded diz que o documento inicial foi parseado; isso não quer dizer que uma resposta de API atrasada chegou, que a hidratação terminou, que uma lista infinita parou de crescer ou que uma linha virtualizada entrou no viewport. Um seletor é útil quando a presença de um único elemento correspondente já basta. Se a completude depende de uma resposta conhecida, contagem de itens, estado da aplicação ou de uma janela de rede silenciosa, espere essa condição em vez disso. A condição deve estar ligada ao contrato do output: “existe pelo menos um card” e “todas as páginas esperadas carregaram” são afirmações diferentes.

O tratamento de timeout também fica com quem chama. O teste usou timeouts finitos para seletores, mas não estudou política de retry nem diferenciou uma página lenta de um seletor que mudou de forma permanente. Um wrapper de produção deve registrar qual condição de prontidão falhou, capturar estado suficiente da página para diagnosticar o problema e decidir se vale fazer outra tentativa de navegação. O Playwright fornece eventos e DOM; ele não consegue inferir o que significa “dados completos” no seu caso.
Essa fronteira deve ser documentada ao lado de cada extractor, e não deixada implícita num timeout.
O caminho via API tem um contrato paralelo. ctx.request.get foi apropriado porque o browser context já existia e o compartilhamento de sessão pode ser útil. Se um job descobre que seu endpoint de dados funciona sem qualquer sessão de navegador, um request context isolado é outra arquitetura, com ciclo de vida e comportamento de cookies diferentes. Neste teste, não comparei os dois. Trate “sem renderização do DOM” como o fato medido e decida separadamente se um processo de navegador é necessário no fluxo maior.
A questão do crawling
O resultado do grafo de crawl é o que define como você deve pensar o Playwright. Doze páginas, três profundidades, resultado correto — e toda a lógica de caminhada foi minha. O Playwright forneceu a metade de “abrir esta URL e ler”; eu forneci a fila, o conjunto de visitados e o controle de profundidade.
Para tarefas pequenas e delimitadas, isso não é problema. Em escala de crawl, isso significa que você está escrevendo um crawler em cima de uma biblioteca de navegador ou combinando o Playwright com algo que já faça isso. O padrão documentado é o Crawlee, que envolve o Playwright (e o Puppeteer) com uma fila real de requests, armazenamento de datasets e auto-throttling — você preserva a renderização do Playwright e herda a orquestração. Se você quer que a fila já venha embutida no framework, em vez de acoplada depois, esse é justamente o design do Scrapy, embora o Scrapy seja centrado em HTTP e não renderize JavaScript sozinho. O ponto não é que o Playwright fica aquém; é que “automação de navegador” e “crawling” são trabalhos diferentes, e o Playwright só reivindica um deles.

A BFS de 12 páginas deixa essa fronteira de responsabilidade bem concreta. Ela forneceu uma fila, um conjunto de visitados e controle de profundidade para um grafo controlado. Um frontier de produção ainda precisa definir canonização de URLs, tratamento de redirects, hosts permitidos, chaves duplicadas, retries, concorrência, atraso por host, persistência e semântica de retomada. Exportar é outra escolha: o fixture gravou JSON e CSV porque o harness fez isso, não porque o Playwright ofereça uma abstração de dataset.
O design da sessão também afeta o wrapper. Um único browser pode conter múltiplos contexts com cookies e storage isolados, mas esta análise não mediu escala com múltiplos contexts nem isolamento de falhas. Reutilizar um context pode preservar login e reduzir trabalho de setup; criar contexts separados pode evitar vazamento de estado entre jobs. Essas são políticas de crawler, ainda que o Playwright forneça o primitivo de context. Vale medir o ciclo de vida escolhido com o build do navegador e o ambiente de implantação que você realmente vai usar.
Prós e contras
Prós:
- Execução de JavaScript em um engine real de Chromium; os dois alvos dinâmicos chegaram aos seletores usados como condição de prontidão.
- Screenshot de página inteira capturado na primeira chamada.
- Seletores extraíram corretamente os campos do catálogo estático e do artigo nos fixtures controlados.
browserContext.requestacessou o endpoint JSON sem renderizar a página, enquanto o processo do navegador já iniciado permaneceu parte do harness.- Robusto diante de resposta ruim: HTTP 500 pôde ser inspecionado e a navegação não lançou erro.
- Suporte documentado a três engines (Chromium, Firefox, WebKit) por meio de uma única API, além de clientes Python, Java e .NET (documentado; apenas Chromium foi exercitado aqui).
- Apache-2.0 e mantido pela Microsoft.
- Boa experiência de desenvolvimento no modo library: uma API para várias engines, async nativo e screenshots triviais.
Contras:
- Sem fila de crawl, dataset ou auto-throttle embutidos — trabalho em escala de crawl fica com você ou com um wrapper como o Crawlee.
- Peso do navegador: o download do binário e o custo por página são o verdadeiro preço em comparação com uma ferramenta apenas HTTP.
- O enquadramento padrão é o test runner; fazer scraping exige saber que o library mode existe e sair do caminho mais divulgado.
- Apenas o Playwright 1.56.0 e o Chromium foram exercitados; paridade entre versões e entre engines não foi testada.
- Não gera saída estruturada em JSON por schema por conta própria; você escreve os seletores e modela os dados.
Para quem serve e quem deve passar longe
Se o seu problema é renderizar páginas cujos dados aparecem depois que o JavaScript roda, ou coletar screenshots junto com dados do DOM, o Playwright é um candidato razoável para reproduzir contra os seus alvos. Equipes que já usam testes com Playwright podem reaproveitar os mesmos conceitos e habilidades com seletores no modo library. Clientes Python, Java e .NET são opções documentadas, mas esta análise exerceu apenas Node e Chromium.
Considere outra camada em três casos. Se os dados já estiverem presentes na resposta HTTP, uma ferramenta centrada em HTTP evita o custo de inicialização e renderização do navegador; Colly é uma biblioteca de crawler nessa categoria, enquanto Trafilatura é voltada para extração de artigos. Se você precisa de fila, persistência e throttling, use um framework de crawling ou um wrapper para Playwright. Se você quer saída já no formato de esquema, sem manter seletores, compare serviços gerenciados de extração. Nenhuma dessas alternativas foi benchmarkada nesta análise.
Se a decisão específica for entre Playwright e Puppeteer, isso merece uma comparação própria; nosso comparativo lado a lado executa os dois nos mesmos fixtures e mostra onde a escolha realmente se define.
Alternativas e onde a extração gerenciada entra
O Playwright é gratuito, Apache-2.0 e self-hosted. Você assume a implantação do navegador, os seletores, as condições de prontidão, o código de crawl, as atualizações e o tratamento de falhas. Esta análise não mediu desempenho contra anti-bot nem comparou custo total de operação com um serviço gerenciado.
Dentro do open source, as comparações úteis são por tarefa. Para trabalho de crawl em escala com navegador, o Crawlee adiciona a fila e o dataset que o Playwright não traz. Se o objetivo é obter Markdown pronto para LLM a partir de um navegador real, em vez de linhas moldadas manualmente, Crawl4AI executa um navegador e produz Markdown para esse fluxo. E, se você estiver avaliando várias opções ao mesmo tempo, nosso resumo de scrapers open source organiza as categorias lado a lado.
Divulgação: Thunderbit é o produto do publicador e não foi executado neste fixture do Playwright. Ele representa a categoria de extração gerenciada: o serviço opera a renderização e devolve texto da página ou registros estruturados, enquanto o Playwright deixa a operação do navegador e a lógica de seletores com o desenvolvedor. A comparação, portanto, é de modelo de hospedagem, formato de saída e modelo de custo — não de desempenho observado nesta análise.
Experimente Thunderbit para extração de dados da web
Veredito
Use o Playwright quando o seu alvo precisar de um motor de navegador e você estiver disposto a assumir condições de prontidão, seletores e orquestração de crawl. A tabela de resultados mostra que o modo library com Chromium lidou, como esperado, com os fixtures controlados estáticos, dinâmicos, de API, screenshot e falha neste teste conduzido pelo autor.
Mantenha intacta a fronteira das evidências: apenas Chromium e Node foram exercitados, a caminhada de 12 páginas dependia de uma BFS escrita à mão, browserContext.request pulava a renderização da página, mas não o processo do navegador já em execução, e toda extração dinâmica usou um seletor de prontidão declarado. Essas restrições fazem do Playwright, nesta análise, um primitivo de navegador — não um sistema de crawling completo e medido.
Experimente Thunderbit para extração de dados da web Get Started Free
Perguntas frequentes
Ainda preciso de waits ao fazer scraping com Playwright?
Sim. A execução no navegador não informa ao script quando os dados da aplicação estão prontos. Nestes testes, a navegação foi feita até domcontentloaded e depois houve espera por um seletor específico do alvo antes da extração. Páginas de produção podem exigir outro sinal, como uma response, estado de um locator ou evento da aplicação.
O Playwright consegue fazer crawl de um site inteiro sozinho? Não nativamente. Não há fila de requests, gravador de dataset nem auto-throttle embutidos — meu teste de grafo de crawl alcançou 12 páginas nas profundidades {0:1, 1:4, 2:7} só porque eu escrevi a busca em largura manualmente. Para trabalho em escala de crawl, combine o Playwright com o Crawlee, que o envolve com uma camada real de crawling, ou use um framework de crawler.
Quando devo usar browserContext.request em vez de um request context isolado?
Use browserContext.request quando as chamadas HTTP precisarem compartilhar cookies com páginas dentro de um browser context já existente. Use playwright.request.newContext() quando quiser um contexto apenas de API, sem abrir navegador, e não precisar de compartilhamento automático de cookies com páginas do navegador. Apenas o primeiro caminho foi testado aqui.
Firefox e WebKit foram testados aqui? Não. Todos os testes rodaram em Chromium, em uma única máquina e em uma única execução. O suporte do Playwright a três engines (Chromium, Firefox, WebKit) e seus clientes para Python, Java e .NET são capacidades documentadas que estou relatando como tais, não verificadas — paridade com Firefox e WebKit, proxy, escala paralela e interceptação de rede estão fora do que estes números cobrem.
Qual ambiente esta análise cobriu? Playwright 1.56.0, Node v22.22.3, macOS arm64 e apenas Chromium. Firefox, WebKit, proxy, escala paralela, comportamento anti-bot e versões posteriores do Playwright ficaram fora da execução. A instalação exigiu a biblioteca mais um download separado do build do navegador.


