Como automatizar capturas de tela de sites com Playwright
Use o Playwright para automatizar capturas de tela de sites com JavaScript. Capture uma página, páginas inteiras, visualizações mobile e lotes de URLs com novas tentativas.
13 min de leituraCaptura de sitesPor Temposhot Team
Playwright é uma biblioteca de automação de navegador da Microsoft. Ela controla Chromium, Firefox e WebKit a partir de Node.js, Python, Java ou .NET. Este post usa o pacote do Node.js para criar um script de captura de tela, começando por uma página e terminando com um lote de URLs que repete as falhas.
Quando o Playwright é a ferramenta certa
Um script compensa a configuração quando as capturas fazem parte de um processo técnico maior:
- as capturas são executadas no CI após cada deploy
- a lista de URLs vem de um sitemap, de um banco de dados ou de uma API
- os arquivos precisam ir para uma estrutura de pastas fixa ou para um bucket de armazenamento
- a etapa de captura fica junto de testes, scraping ou geração de PDF
Se quem precisa das imagens não escreve código, ou se a lista muda toda semana, uma ferramenta sem código costuma ser mais rápida. Essa troca é tratada no final.
O exemplo trabalhado neste post
Os exemplos levam a um único trabalho: capturar as 200 páginas listadas no sitemap de um site após cada deploy, em tamanho de desktop e de celular, e informar as páginas que falharam. Esse trabalho reúne todas as partes que tornam scripts de captura difíceis: uma lista longa, páginas lentas, imagens lazy, banners de cookies e um timeout ocasional.
Os números desse trabalho são estimativas. Os tempos reais dependem do site, da rede e da máquina.
O custo de manter o script
O script não custa nada, mas exige manutenção. Um redesenho do site pode mudar o banner de cookies, o elemento usado como sinal de página pronta ou o contêiner de rolagem, e cada mudança quebra parte do lote.
Node.js ou outra linguagem
A API de captura de tela é quase idêntica nos quatro bindings de linguagem. page.screenshot(path=..., full_page=True) em Python faz o mesmo que fullPage: true no Node.js. Escolha a linguagem que o restante do pipeline já usa, para que a etapa de captura fique no mesmo repositório e no mesmo job de CI do código ao redor.
Considere esse tempo ao planejar. Um lote que roda após cada deploy compensa rapidamente a manutenção. Um lote que roda duas vezes por ano costuma falhar na primeira tentativa, porque algo mudou no intervalo.
Instale o Playwright para um script de captura
mkdir site-shots && cd site-shots
npm init -y
npm install -D playwright
npx playwright install chromium
O último comando baixa uma versão do Chromium compatível com a versão do Playwright instalada. Os scripts abaixo usam a extensão .mjs, para que o await no nível superior funcione sem bundler nem configuração extra.
Fixe a versão do pacote e a do navegador juntas
Cada versão do Playwright espera builds específicos do navegador. Depois de atualizar o pacote playwright, execute npx playwright install chromium novamente. Caso contrário, o script falha com uma mensagem informando que o executável não existe.
Faça commit do package-lock.json para que todas as máquinas instalem a mesma versão. Em um runner Linux de CI, npx playwright install --with-deps chromium também instala as bibliotecas de sistema de que o navegador precisa.
O comando screenshot é útil para verificar uma única URL antes de ela entrar no lote. Ele também aceita --device e --wait-for-selector, então uma página lenta pode ser testada pelo terminal.
Faça uma captura de tela de um site
// one.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
Execute com node one.mjs. O navegador inicia em modo headless, ou seja, renderiza a página sem abrir uma janela. A entrada navegador headless explica esse modelo.
Sem outras opções, o arquivo é uma captura da viewport com exatamente 1920×1080 pixels.
Opções que tornam as capturas repetíveis
Duas execuções da mesma página devem gerar a mesma imagem quando nada mudou. Algumas opções de captura removem as fontes comuns de ruído: animações capturadas no meio, o cursor de texto piscando e a densidade de pixels que varia entre máquinas.
await page.screenshot({
path: 'example.jpg',
type: 'jpeg',
quality: 80,
animations: 'disabled',
caret: 'hide',
scale: 'css',
mask: [page.locator('.ad-slot'), page.locator('.live-chat')],
});
mask pinta uma caixa sobre cada elemento encontrado. É a forma mais simples de impedir que anúncios rotativos, horários ao vivo e widgets de chat façam cada execução parecer diferente.
Depure uma captura em branco ou incorreta
Uma captura em branco, uma página de erro ou uma verificação de bot exige olhar o que o navegador viu. Inicie com headless: false e a opção slowMo para acompanhar cada etapa, ou grave um trace e abra-o no visualizador de traces.
O trace mostra uma sequência de quadros da página e as requisições de rede de cada ação. Uma página que renderizou por completo na sequência, mas foi capturada em branco, normalmente indica que a captura rodou antes de terminar uma transição de opacidade. animations: 'disabled' resolve esse caso.
Faça uma captura de página inteira
Adicione fullPage: true para capturar todo o documento rolável.
await page.screenshot({ path: 'example-full.png', fullPage: true });
A largura permanece igual à da viewport, e a altura cresce para acomodar a página. Páginas muito longas geram imagens muito altas, que alguns visualizadores de imagem reduzem bastante. Páginas com cabeçalho fixo podem exibir o cabeçalho uma única vez, no topo, porque o Playwright captura o documento em uma passagem e não junta várias visualizações roladas.
Para capturar apenas um elemento, chame screenshot em um locator: page.locator('main').screenshot({ path: 'main.png' }).
Capture uma região fixa
clip salva um retângulo da página em pixels CSS, informado como x, y, width e height. Ele serve para páginas em que só os primeiros 1.200 pixels importam, como uma landing page acima da dobra mais a primeira seção.
await page.screenshot({ path: 'hero.png', clip: { x: 0, y: 0, width: 1920, height: 1200 } });
Um clip que ultrapassa o fim de uma página curta é cortado na borda da página. Verifique a altura da imagem quando o clip precisar ser idêntico em todas as páginas.
Coloque a URL de destino em um script de lote
Mantenha as URLs em um arquivo de texto, assim a lista pode ser alterada sem editar código.
// batch.mjs
import { readFile, mkdir } from 'node:fs/promises';
import { chromium } from 'playwright';
const urls = (await readFile('urls.txt', 'utf8'))
.split('\n').map((l) => l.trim()).filter(Boolean);
await mkdir('shots', { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
for (const [i, url] of urls.entries()) {
const name = `shots/${String(i + 1).padStart(3, '0')}.png`;
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.screenshot({ path: name, fullPage: true });
console.log('saved', url, name);
}
await browser.close();
Nomes com zeros à esquerda, como 001.png, mantêm os arquivos na ordem da lista no Finder.
Monte a lista a partir de um sitemap
No trabalho de exemplo, a lista vem de sitemap.xml. Busque o arquivo uma vez no início da execução e extraia cada entrada loc, para que páginas novas sejam capturadas sem editar urls.txt.
const xml = await (await fetch('https://example.com/sitemap.xml')).text();
const urls = [...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map((m) => m[1].trim());
Nomeie os arquivos pela página, não só pela posição
Nomes com índice, como 001.png, quebram quando o sitemap ganha uma página, porque cada arquivo seguinte muda uma posição. Nomes montados a partir do caminho da URL permanecem estáveis entre execuções: /pricing/teams vira pricing-teams.png. Mantenha o índice como prefixo somente quando a ordem da lista importar para a revisão.
Duas URLs podem gerar o mesmo nome, por exemplo um caminho com e sem barra final. Verifique nomes duplicados antes da execução e adicione o índice para resolvê-los.
Sites grandes dividem o sitemap em um índice que aponta para outros sitemaps. Siga esses links um nível abaixo e filtre URLs de arquivos, como PDFs, que não são renderizados como páginas.
Adicione concorrência para lotes maiores
Uma página por vez é lento para centenas de URLs. Execute algumas páginas em paralelo, cada uma em seu próprio objeto de página, e limite o número para manter o uso de memória sob controle.
const LIMIT = 4;
const queue = [...urls.entries()];
async function worker() {
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
while (queue.length) {
const [i, url] = queue.shift();
await page.goto(url);
await page.screenshot({ path: `shots/${i + 1}.png`, fullPage: true });
}
await page.close();
}
await Promise.all(Array.from({ length: LIMIT }, worker));
Quatro workers é um ponto de partida razoável em um notebook. Aumente o limite somente depois de verificar o uso de memória, e mantenha-o baixo em sites que você não controla, para que a taxa de requisições seja moderada.
Como o número de workers muda o tempo de execução
O tempo de execução cai quase na mesma proporção do número de workers, até que a máquina ou o site se torne o gargalo. Com uma estimativa de 4 segundos por página, 200 páginas levam cerca de 13 minutos com 1 worker, cerca de 3,5 minutos com 4 e um pouco menos com 8.
Além desse ponto, workers extras aumentam sobretudo a pressão sobre a memória e a carga no servidor. Se as capturas começarem a expirar quando o número aumenta, o site ou a máquina está saturado, e o número deve voltar a cair.
Mantenha a taxa de requisições moderada
Cada carregamento de página também carrega scripts, imagens e fontes, então 4 workers podem significar dezenas de requisições por segundo para um único servidor. Em sites que você não controla, use 1 ou 2 workers e adicione uma pausa de 1 a 2 segundos entre as páginas. Execute lotes grandes fora do horário de pico do site e pare quando as respostas passarem a ser 429 Too Many Requests.
Páginas ou contextos
O worker acima compartilha um único contexto de navegador, então todas as páginas compartilham cookies. Se uma página definir um cookie de consentimento, as páginas seguintes pulam o banner, e as capturas variam conforme a ordem dos workers. Dê a cada worker o próprio contexto com browser.newContext() quando cada captura precisar começar em estado limpo.
Capture telas mobile ou de tablet
O Playwright inclui descritores de dispositivo que definem, em conjunto, viewport, user agent, suporte a toque e proporção de pixels.
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 13'] });
const page = await context.newPage();
Use um contexto por dispositivo para que cookies e armazenamento fiquem separados. Para um tablet, escolha uma entrada de iPad na mesma lista devices, ou defina uma viewport simples, como 768×1024.
Proporção de pixels e tamanho do arquivo
Descritores de celular definem deviceScaleFactor como 2 ou 3. Uma viewport de celular com 390 pixels de largura e fator 3 salva uma imagem com 1.170 pixels de largura, e uma página longa pode chegar a dezenas de milhares de pixels de altura. Isso representa nove vezes os pixels da mesma página com fator 1.
Para revisão de layout, scale: 'css' nas opções de captura mantém um pixel de imagem por pixel CSS. Mantenha a escala do dispositivo somente quando as imagens precisarem ficar nítidas em uma tela de celular.
Confirme que o layout mobile foi carregado
Alguns sites escolhem o layout pelo user agent no servidor, outros pela largura da viewport no CSS. Um descritor define os dois, mas a alteração simples de viewport define apenas a largura. Se as capturas de celular mostrarem um layout de desktop comprimido, troque a viewport simples por um descritor de dispositivo completo.
WebKit para renderização semelhante à do Safari
O Chromium é o padrão na maioria dos scripts, mas os layouts do iPhone são renderizados pelo WebKit nos dispositivos reais. Instale com npx playwright install webkit e inicie com webkit.launch() quando a renderização específica do Safari for importante.
Trate conteúdo com carregamento lazy
Muitas páginas carregam imagens somente quando elas entram na área visível da rolagem. Uma captura de página inteira feita logo após o carregamento pode mostrar caixas vazias mais abaixo. Role a página inteira uma vez antes da captura.
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 200));
}
window.scrollTo(0, 0);
});
A entrada lazy loading explica por que isso acontece.
Aguarde a decodificação das imagens
A rolagem inicia as requisições de imagem, mas a captura ainda pode ocorrer antes de as imagens chegarem. Depois da rolagem, aguarde até que todas as imagens da página informem que estão completas.
await page.waitForFunction(() =>
[...document.images].every((img) => img.complete && img.naturalWidth > 0)
);
URLs de imagens quebradas nunca são concluídas, então essa espera pode atingir o timeout em uma página com um arquivo ausente. Capture o timeout, faça a captura mesmo assim e marque a página no relatório.
Escolha a estratégia de espera adequada
A opção waitUntil define quando goto retorna.
| Valor | Retorna quando |
|---|---|
load | O evento load é disparado. Este é o padrão. |
domcontentloaded | O HTML é analisado, antes de as imagens terminarem. |
networkidle | Não há requisições de rede por pelo menos 500 ms. |
A documentação do Playwright desaconselha networkidle em testes, porque páginas com polling ou analytics podem nunca ficar em silêncio. Um padrão mais confiável é esperar por um elemento que marque a página como pronta, por exemplo await page.locator('main img').first().waitFor().
Uma espera fixa de 5 segundos em 200 páginas adiciona cerca de 17 minutos de tempo ocioso a um único worker e ainda falha na página que precisa de 6 segundos. Uma espera por locator retorna assim que a página está pronta. await page.evaluate(() => document.fonts.ready) cobre o caso das fontes.
Adicione novas tentativas
Redes falham, e algumas páginas expiram uma vez e depois carregam normalmente. Envolva a captura em uma pequena função auxiliar de nova tentativa.
async function capture(page, url, path, tries = 3) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
await page.goto(url, { timeout: 30000 });
await page.screenshot({ path, fullPage: true });
return true;
} catch (error) {
console.warn(`attempt ${attempt} failed for ${url}: ${error.message}`);
}
}
return false;
}
Reúna as URLs que retornam false e grave-as em failed.txt, para que possam ser executadas novamente sozinhas.
Não repita o que não vai mudar
Novas tentativas ajudam com timeouts e conexões interrompidas. Elas não ajudam com uma página que retorna 404 ou 500, e três tentativas em 20 páginas ausentes desperdiçam minutos. page.goto retorna um objeto de resposta, então verifique response.status() e pule a nova tentativa quando o status for 400 ou superior.
Registre o status no relatório, ao lado da URL. Uma lista de 404s vinda de um sitemap já é útil por si só, porque aponta entradas desatualizadas no sitemap.
Recupere uma página que travou
Uma página pode travar em um site pesado, e todo goto seguinte nesse objeto de página passa a lançar erro. Dentro da função de nova tentativa, feche a página após um erro e abra uma nova para a tentativa seguinte. O worker continua em execução, e uma URL problemática deixa de derrubar o restante da fila.
Uma estrutura de pastas prática
site-shots/
urls.txt
batch.mjs
shots/
2026-03-24/
desktop/001.png
mobile/001.png
failed.txt
Uma pasta com data por execução mantém as capturas antigas intactas. Uma subpasta por dispositivo facilita a revisão lado a lado.
O que uma execução de CI deve enviar
No CI, o runner é apagado após o job, então as capturas precisam ser enviadas como artefatos de build. Envie a pasta com data, a lista de falhas e um pequeno relatório em JSON com URL, status, nome do arquivo e duração de cada página.
Compare uma execução com a anterior
Duas pastas com data só são úteis se alguém analisar as diferenças. Para um punhado de páginas, abra os arquivos correspondentes no Compare Images, que tem um controle deslizante e uma visualização de diferença por pixel. Para centenas de páginas, a asserção toHaveScreenshot do Playwright Test compara cada captura com uma linha de base armazenada e falha em diferenças acima de um limite.
Fontes são a diferença mais comum entre uma execução no notebook e uma no CI. Um runner Linux sem as fontes de fallback do site renderiza o texto com larguras diferentes, então as quebras de linha e as alturas das páginas mudam.
Erros comuns
- Esquecer o
await. O script termina antes de o arquivo ser gravado. - Reutilizar uma página após uma falha. Abra uma nova página quando
gotolançar um erro de target closed. - Depender de
networkidleem tudo. Algumas páginas nunca chegam a esse estado, e todas as linhas expiram. - Capturar banners de cookies. Clique no botão de consentimento com um locator, ou oculte-o com
page.addStyleTag. - Ignorar os termos do site. Verifique os termos e as regras do robots.txt de sites que você não controla, e mantenha a taxa baixa.
Feche um banner de cookies de forma confiável
Banners variam de um site para outro, então um único seletor raramente funciona para uma lista inteira. Teste uma lista curta de botões de consentimento conhecidos, com timeout de 2 segundos para cada um, e recorra a ocultar o contêiner do banner com CSS.
const consent = page.getByRole('button', { name: /accept|agree|reject all/i });
if (await consent.first().isVisible({ timeout: 2000 }).catch(() => false)) {
await consent.first().click();
}
Clicar em um botão de consentimento altera os cookies do restante do contexto. Com um contexto por worker, isso não gera problema. Com um contexto compartilhado, é mais um motivo para as capturas variarem conforme a ordem.
Alternativas a um script de captura com Playwright
- Puppeteer oferece uma API semelhante para Chrome e Firefox. Veja Como automatizar capturas de tela de sites com Puppeteer.
- Playwright Test compara capturas de tela com linhas de base. Veja o guia de testes de captura de tela com Playwright.
- APIs de captura de tela hospedadas executam o navegador para você e devolvem imagens por HTTP.
- Temposhot Website Capture executa uma lista de URLs localmente em um Mac, sem código: Desktop 1920×1080, Tablet 768×1024 ou Mobile 390×844, Viewport ou Full page, até 10 navegadores ao mesmo tempo, e Retry por linha.
Opções do script e seus equivalentes sem código
A maioria das escolhas deste post tem uma configuração direta no Website Capture. A diferença está em quem faz a escolha: um desenvolvedor no código, ou qualquer pessoa que consiga colar uma lista. O Website Capture não tem agendador nem suporte a login, então execuções de CI e páginas atrás de login continuam com o script.
Perguntas frequentes
O Playwright precisa de uma janela de navegador visível?
Não. Os navegadores iniciam em modo headless por padrão. Passe headless: false para chromium.launch quando quiser acompanhar a página durante a depuração.
O Playwright pode salvar JPEG em vez de PNG?
Sim. Defina type: 'jpeg' e um valor de quality de 0 a 100 nas opções de captura, ou use um nome de arquivo .jpg.
Por que minha captura de página inteira está cortada?
Algumas páginas rolam dentro de um elemento interno, em vez de rolar o documento. Capture esse elemento com uma captura por locator, ou altere a altura CSS dele antes da captura.
O Playwright pode capturar páginas que exigem login?
Sim. Faça login uma vez, salve a sessão com context.storageState({ path: 'auth.json' }) e crie contextos posteriores com storageState: 'auth.json'. Mantenha esse arquivo fora do controle de versão, porque ele contém cookies de sessão.
Como impedir que anúncios e horários mudem em cada captura?
Passe os locators deles na opção mask. O Playwright pinta uma caixa sólida sobre cada um, então as execuções podem ser comparadas sem diferenças falsas.
Por que as capturas do CI parecem diferentes das do meu notebook?
Fontes, proporção de pixels e locale normalmente são diferentes. Instale as mesmas fontes no runner, defina scale: 'css' e fixe locale e timezoneId no contexto para que datas e formatos numéricos coincidam.
Referências
- Documentação de capturas de tela do Playwright
- Emulação e dispositivos do Playwright
- Instalação do Playwright
Precisa de automação de capturas de tela de sites sem código?
O Temposhot Website Capture recebe uma lista de URLs colada e captura todas as páginas no seu Mac, sem script para manter. Consulte automação de capturas de tela de sites ou comece pelo guia rápido do Website Capture.
playwrightwebsite-captureautomationjavascript
Termos usados aqui: full page screenshot, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot
Transforme capturas de tela repetitivas em trabalho pronto
O Temposhot faz capturas por temporizador ou a cada clique e exporta o resultado em PDF, imagens, ZIP ou MP4.
Posts relacionados
- Como capturar screenshots de sites em lote no MacCapture screenshots de sites em lote no Mac com o Temposhot. Cole uma lista de URLs, escolha os predefinidos de desktop, tablet e mobile, execute a captura em paralelo e exporte para auditorias ou relatórios.
- Como automatizar capturas de tela de sites com PuppeteerUse o Puppeteer para automatizar capturas de tela de sites com JavaScript. Capture uma página, páginas inteiras, lotes de URLs e viewports móveis com um script reutilizável.
- Como automatizar capturas de tela de sites sem códigoEscolha uma forma sem código de automatizar capturas de tela de sites: captura por lista de URLs, plataforma de automação com API de capturas, ferramentas de monitoramento ou extensões do navegador.
- Melhores alternativas ao Xnapper para Mac em 2026Compare alternativas ao Xnapper para Mac em captura repetida, captura e gravação com rolagem, fundos gratuitos, guias multiplataforma e o app nativo do sistema.