Come automatizzare gli screenshot di siti web con Playwright
Usa Playwright per automatizzare gli screenshot di siti web con JavaScript. Cattura una pagina, pagine intere, viste mobile e batch di URL con retry.
13 min di letturaCattura siti webDi Temposhot Team
Playwright è una libreria di automazione del browser di Microsoft. Pilota Chromium, Firefox e WebKit da Node.js, Python, Java o .NET. Questo articolo usa il pacchetto Node.js per costruire uno script di screenshot, partendo da una sola pagina e arrivando a un batch di URL che riprova le operazioni fallite.
Quando Playwright è lo strumento giusto
Uno script vale la configurazione quando gli screenshot fanno parte di un processo tecnico più ampio:
- le catture vengono eseguite in CI dopo ogni deploy
- l'elenco degli URL arriva da una sitemap, un database o un'API
- i file devono finire in una struttura di cartelle fissa o in un bucket di storage
- il passaggio dello screenshot si affianca a test, scraping o generazione di PDF
Se chi ha bisogno delle immagini non scrive codice, oppure se l'elenco cambia ogni settimana, uno strumento no-code è di solito più veloce. Questo compromesso è trattato alla fine.
L'esempio pratico di questo articolo
Gli esempi costruiscono verso un unico lavoro: catturare le 200 pagine elencate nella sitemap di un sito dopo ogni deploy, in formato desktop e telefono, e segnalare le pagine che non sono riuscite. Il lavoro ha tutte le parti che rendono difficili gli script di screenshot: un elenco lungo, pagine lente, immagini lazy, banner dei cookie e i timeout occasionali.
I numeri di questo lavoro sono stime. I tempi reali dipendono dal sito, dalla rete e dalla macchina.
Quanto costa gestire lo script
Lo script è gratuito, ma la manutenzione non è zero. Un redesign del sito può cambiare il banner dei cookie, l'elemento usato come segnale di pagina pronta o il contenitore con lo scroll, e ogni modifica interrompe una parte del batch.
Node.js o un altro linguaggio
L'API per gli screenshot è quasi identica nei quattro binding linguistici. page.screenshot(path=..., full_page=True) in Python fa la stessa cosa di fullPage: true in Node.js. Scegli il linguaggio che la pipeline usa già, così il passaggio di screenshot vive nello stesso repository e nello stesso job CI del codice che lo circonda.
Pianifica il tempo per questo. Un batch eseguito dopo ogni deploy ripaga rapidamente la manutenzione. Un batch eseguito due volte l'anno spesso fallisce al primo tentativo, perché nel frattempo qualcosa è cambiato.
Installa Playwright per uno script di screenshot
mkdir site-shots && cd site-shots
npm init -y
npm install -D playwright
npx playwright install chromium
L'ultimo comando scarica una build di Chromium compatibile con la versione di Playwright installata. Gli script seguenti usano l'estensione .mjs, così il top-level await funziona senza bundler né configurazione aggiuntiva.
Fissa insieme la versione e il browser
Ogni release di Playwright richiede build specifiche dei browser. Dopo un aggiornamento del pacchetto playwright, esegui di nuovo npx playwright install chromium, altrimenti lo script fallisce con un messaggio che indica che l'eseguibile non esiste.
Committa package-lock.json in modo che ogni macchina installi la stessa versione. Su un runner CI Linux, npx playwright install --with-deps chromium installa anche le librerie di sistema necessarie al browser.
Il comando screenshot è utile per controllare un singolo URL prima che entri nel batch. Accetta anche --device e --wait-for-selector, quindi una pagina lenta può essere testata dal terminale.
Cattura lo screenshot di un sito web
// 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();
Eseguilo con node one.mjs. Il browser parte in modalità headless, cioè renderizza la pagina senza aprire una finestra. La voce headless browser spiega questo modello.
Senza altre opzioni, il file è uno screenshot del viewport di esattamente 1920×1080 pixel.
Opzioni che rendono le catture ripetibili
Due esecuzioni della stessa pagina dovrebbero produrre la stessa immagine se non è cambiato nulla. Alcune opzioni dello screenshot eliminano le fonti abituali di rumore: animazioni colte a metà, il cursore di testo lampeggiante e una densità di pixel che cambia tra macchine.
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 disegna un riquadro sopra ogni elemento corrispondente. È il modo più semplice per evitare che annunci a rotazione, orari in tempo reale e widget di chat rendano ogni esecuzione diversa.
Risolvi una cattura vuota o errata
Uno screenshot vuoto, con una pagina di errore o con una verifica anti-bot va esaminato per capire cosa ha visto il browser. Avvia con headless: false e l'opzione slowMo per seguire ogni passaggio, oppure registra una trace e aprila nel trace viewer.
La trace mostra una sequenza di fotogrammi della pagina e le richieste di rete per ogni azione. Una pagina che nella sequenza risulta completamente renderizzata ma nella cattura è vuota di solito indica che lo screenshot è stato eseguito prima che finisse una dissolvenza in entrata. animations: 'disabled' risolve questo caso.
Cattura uno screenshot a pagina intera
Aggiungi fullPage: true per catturare l'intero documento scorrevole.
await page.screenshot({ path: 'example-full.png', fullPage: true });
La larghezza resta quella del viewport e l'altezza cresce fino a contenere la pagina. Le pagine molto lunghe producono immagini molto alte, che alcuni visualizzatori riducono parecchio. Nelle pagine con header fissi, l'header compare una sola volta in alto, perché Playwright cattura il documento in un solo passaggio invece di unire viste scorse in sequenza.
Per catturare un singolo elemento, chiama screenshot su un locator: page.locator('main').screenshot({ path: 'main.png' }).
Cattura una regione fissa
clip salva un rettangolo della pagina in pixel CSS, indicato con x, y, width e height. È adatto alle pagine in cui conta solo la parte iniziale, come una landing page fino alla prima sezione.
await page.screenshot({ path: 'hero.png', clip: { x: 0, y: 0, width: 1920, height: 1200 } });
Un clip che supera il fondo di una pagina corta viene tagliato al bordo della pagina. Controlla l'altezza dell'immagine quando il clip deve essere identico su tutte le pagine.
Inserisci gli URL in uno script batch
Tieni gli URL in un file di testo, così nessuno deve modificare il codice per cambiare l'elenco.
// 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();
I nomi con zeri iniziali come 001.png mantengono i file nell'ordine dell'elenco anche in Finder.
Crea l'elenco dalla sitemap
Per il lavoro di esempio, l'elenco arriva da sitemap.xml. Scaricala una volta all'inizio dell'esecuzione ed estrai ogni voce loc, così le nuove pagine vengono catturate senza modificare 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());
Nomina i file per pagina, non solo per posizione
I nomi con indice, come 001.png, si rompono quando la sitemap aggiunge una pagina, perché ogni file successivo scala di uno. I nomi costruiti dal percorso dell'URL restano stabili tra le esecuzioni: /pricing/teams diventa pricing-teams.png. Mantieni l'indice come prefisso solo quando l'ordine dell'elenco conta per la revisione.
Due URL possono produrre lo stesso nome, per esempio un percorso con e senza barra finale. Controlla i nomi duplicati prima dell'esecuzione e aggiungi l'indice per risolverli.
I siti grandi dividono la sitemap in un indice che punta ad altre sitemap. Segui quei link di un livello in giù e filtra gli URL di file come i PDF, che non vengono renderizzati come pagine.
Aggiungi concorrenza per batch più grandi
Una pagina alla volta è lenta con centinaia di URL. Esegui alcune pagine in parallelo, ciascuna nel proprio oggetto page, e limita il numero per tenere sotto controllo la memoria.
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));
Quattro worker sono un buon punto di partenza su un laptop. Aumenta il limite solo dopo aver verificato l'uso della memoria, e tienilo basso per i siti che non possiedi, così la frequenza delle richieste resta corretta.
Come il numero di worker cambia il tempo di esecuzione
Il tempo di esecuzione scende quasi in proporzione al numero di worker, finché la macchina o il sito non diventano il collo di bottiglia. Con una stima di 4 secondi per pagina, 200 pagine richiedono circa 13 minuti con 1 worker, circa 3,5 minuti con 4 e un po' meno con 8.
Oltre questo punto, altri worker aumentano soprattutto la pressione sulla memoria e il carico sul server. Se gli screenshot iniziano ad andare in timeout quando aumenti il numero, il sito o la macchina sono saturi e il numero va riportato in basso.
Mantieni bassa la frequenza delle richieste
Ogni caricamento di pagina carica anche script, immagini e font, quindi 4 worker possono significare decine di richieste al secondo verso lo stesso server. Per i siti che non possiedi, tieni il numero di worker a 1 o 2 e aggiungi una pausa di 1-2 secondi tra una pagina e l'altra. Esegui i batch grandi fuori dalle ore di punta del sito e fermati quando le risposte diventano 429 Too Many Requests.
Pagine o context
Il worker sopra condivide un solo browser context, quindi tutte le pagine condividono i cookie. Se una pagina imposta un cookie di consenso, le pagine successive saltano il banner e le catture differiscono in base all'ordine dei worker. Assegna a ogni worker un proprio context con browser.newContext() quando ogni cattura deve partire da uno stato pulito.
Cattura screenshot mobile o tablet
Playwright include descrittori di dispositivo che impostano insieme viewport, user agent, supporto touch e rapporto pixel.
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 13'] });
const page = await context.newPage();
Usa un context per dispositivo, così cookie e storage restano separati. Per un tablet, scegli una voce iPad dalla stessa lista devices, oppure imposta un viewport semplice come 768×1024.
Rapporto pixel e dimensione del file
I descrittori per telefono impostano deviceScaleFactor a 2 o 3. Un viewport da telefono largo 390 pixel con fattore 3 salva un'immagine larga 1.170 pixel, e una pagina lunga può arrivare a decine di migliaia di pixel in altezza. Sono nove volte i pixel della stessa pagina con fattore 1.
Per la revisione del layout, scale: 'css' nelle opzioni di screenshot mantiene un pixel immagine per pixel CSS. Mantieni il fattore di scala del dispositivo solo quando le immagini devono risultare nitide su uno schermo di telefono.
Verifica che il layout mobile sia stato caricato
Alcuni siti scelgono il layout dallo user agent lato server, altri dalla larghezza del viewport in CSS. Un descrittore imposta entrambi, mentre una semplice modifica di viewport cambia solo la larghezza. Se le catture da telefono mostrano un layout desktop compresso, passa da un viewport semplice a un descrittore completo del dispositivo.
WebKit per un rendering simile a Safari
Chromium è il default nella maggior parte degli script, ma i layout di iPhone sono renderizzati da WebKit sui dispositivi reali. Installalo con npx playwright install webkit e avvialo con webkit.launch() quando conta il rendering specifico di Safari.
Gestisci i contenuti caricati in modo lazy
Molte pagine caricano le immagini solo quando entrano nella vista di scorrimento. Una cattura a pagina intera eseguita subito dopo il caricamento può mostrare riquadri vuoti più in basso. Scorri la pagina una volta prima dello screenshot.
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);
});
La voce lazy loading spiega perché succede.
Attendi che le immagini siano decodificate
Lo scroll avvia le richieste delle immagini, ma lo screenshot può ancora partire prima che le immagini arrivino. Dopo lo scroll, attendi che ogni immagine della pagina dichiari di essere completa.
await page.waitForFunction(() =>
[...document.images].every((img) => img.complete && img.naturalWidth > 0)
);
Gli URL di immagini rotte non si completano mai, quindi questa attesa può scadere su una pagina con un file mancante. Intercetta il timeout, esegui comunque la cattura e segnala la pagina nel report.
Scegli la strategia di attesa giusta
L'opzione waitUntil decide quando goto restituisce il controllo.
| Valore | Restituisce il controllo quando |
|---|---|
load | L'evento load si attiva. È il default. |
domcontentloaded | L'HTML è analizzato, prima che finiscano le immagini. |
networkidle | Nessuna richiesta di rete per almeno 500 ms. |
La documentazione di Playwright sconsiglia networkidle per i test, perché le pagine con polling o analytics potrebbero non fermarsi mai. Un pattern più affidabile è attendere un elemento che segna la pagina come pronta, per esempio await page.locator('main img').first().waitFor().
Un'attesa fissa di 5 secondi su 200 pagine aggiunge circa 17 minuti di tempo inattivo a un solo worker, e fallisce comunque sulla pagina che ne richiede 6. Un'attesa su un locator restituisce il controllo appena la pagina è pronta. await page.evaluate(() => document.fonts.ready) copre il caso dei font.
Aggiungi i retry
Le reti falliscono e alcune pagine vanno in timeout una volta, per poi caricarsi correttamente. Avvolgi la cattura in un piccolo helper di retry.
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;
}
Raccogli gli URL per cui la funzione restituisce false e scrivili in failed.txt, così possono essere rieseguiti da soli.
Non ritentare ciò che non cambierà
I retry aiutano con i timeout e le connessioni interrotte. Non aiutano con una pagina che restituisce 404 o 500, e tre tentativi su 20 pagine mancanti fanno perdere minuti. page.goto restituisce un oggetto response, quindi controlla response.status() e salta il retry quando lo stato è 400 o superiore.
Registra lo stato nel report accanto all'URL. Un elenco di 404 proveniente dalla sitemap è utile di per sé, perché indica voci di sitemap obsolete.
Recupera da una pagina andata in crash
Una pagina può andare in crash su un sito pesante, e ogni goto successivo su quell'oggetto page lancia un errore. Dentro l'helper di retry, chiudi la pagina dopo un errore e aprine una nuova per il tentativo successivo. Il worker continua a girare e un URL problematico non fa più fallire il resto della sua coda.
Una struttura di cartelle pratica
site-shots/
urls.txt
batch.mjs
shots/
2026-03-24/
desktop/001.png
mobile/001.png
failed.txt
Una cartella con data per ogni esecuzione conserva intatte le catture precedenti. Una sottocartella per dispositivo rende più semplice il confronto affiancato.
Cosa deve caricare un'esecuzione CI
In CI il runner viene eliminato al termine del job, quindi gli screenshot devono essere caricati come artifact di build. Carica la cartella con la data, l'elenco dei fallimenti e un piccolo report JSON con URL, stato, nome del file e durata per ogni pagina.
Confronta un'esecuzione con la precedente
Due cartelle con data servono solo se qualcuno ne esamina le differenze. Per poche pagine, apri i file corrispondenti in Compare Images, che include un cursore e una vista delle differenze pixel per pixel. Per centinaia di pagine, l'asserzione toHaveScreenshot di Playwright Test confronta ogni cattura con una baseline salvata e fallisce quando le differenze superano una soglia.
I font sono la differenza più comune tra un'esecuzione sul laptop e una in CI. Un runner Linux senza i font di fallback del sito renderizza il testo con larghezze diverse, quindi cambiano gli a capo e le altezze delle pagine.
Errori comuni
- Dimenticare
await. Lo script termina prima che il file venga scritto. - Riusare una pagina dopo un crash. Apri una nuova pagina quando
gotolancia un errore di target chiuso. - Affidarsi a
networkidleovunque. Alcune pagine non lo raggiungono mai e ogni riga va in timeout. - Catturare i banner dei cookie. Clicca il pulsante di consenso con un locator, oppure nascondilo con
page.addStyleTag. - Ignorare i termini del sito. Controlla i termini d'uso e le regole robots dei siti che non possiedi, e mantieni bassa la frequenza delle richieste.
Chiudi il banner dei cookie in modo affidabile
I banner cambiano da sito a sito, quindi un solo selettore raramente funziona per un intero elenco. Prova una breve lista di pulsanti di consenso noti con un timeout di 2 secondi per ciascuno, e ricorri a nascondere il contenitore del banner con 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();
}
Cliccare un pulsante di consenso cambia i cookie per il resto del context. Con un context per worker questo non è un problema; con un context condiviso è un altro motivo per cui le catture differiscono in base all'ordine.
Alternative a uno script di screenshot con Playwright
- Puppeteer offre un'API simile per Chrome e Firefox. Vedi Come automatizzare gli screenshot di siti web con Puppeteer.
- Playwright Test confronta gli screenshot con le baseline. Vedi la guida allo screenshot testing con Playwright.
- API di screenshot ospitate eseguono il browser al posto tuo e restituiscono le immagini tramite HTTP.
- Temposhot Website Capture esegue localmente su un Mac un elenco di URL senza codice: Desktop 1920×1080, Tablet 768×1024 o Mobile 390×844, Viewport o Full page, fino a 10 browser contemporaneamente e Retry per ogni riga.
Opzioni dello script e i loro equivalenti senza codice
La maggior parte delle scelte di questo articolo ha un'impostazione diretta in Website Capture. La differenza sta in chi fa la scelta: uno sviluppatore nel codice, oppure chiunque possa incollare un elenco. Website Capture non ha uno scheduler e non supporta l'accesso, quindi le esecuzioni in CI e le pagine dietro un login restano nello script.
FAQ
Playwright richiede una finestra del browser visibile?
No. I browser partono in modalità headless per impostazione predefinita. Passa headless: false a chromium.launch quando vuoi osservare la pagina durante il debug.
Playwright può salvare in JPEG invece che in PNG?
Sì. Imposta type: 'jpeg' e un valore quality da 0 a 100 nelle opzioni di screenshot, oppure usa un nome file .jpg.
Perché il mio screenshot a pagina intera è tagliato?
Alcune pagine scorrono dentro un elemento interno invece che nel documento. Cattura quell'elemento con uno screenshot su locator, oppure cambia la sua altezza CSS prima della cattura.
Playwright può catturare pagine dietro un login?
Sì. Accedi una volta, salva la sessione con context.storageState({ path: 'auth.json' }) e crea i context successivi con storageState: 'auth.json'. Tieni quel file fuori dal controllo di versione, perché contiene i cookie di sessione.
Come evito che annunci e timestamp cambino a ogni cattura?
Passa i loro locator nell'opzione mask. Playwright disegna un riquadro pieno su ciascuno, così le esecuzioni si possono confrontare senza differenze false.
Perché gli screenshot in CI sembrano diversi da quelli sul mio laptop?
Di solito cambiano font, rapporto pixel e locale. Installa gli stessi font sul runner, imposta scale: 'css' e fissa locale e timezoneId sul context, così date e formati numerici corrispondono.
Riferimenti
- Documentazione di Playwright sugli screenshot
- Emulazione e dispositivi di Playwright
- Installazione di Playwright
Ti serve l'automazione di screenshot senza codice?
Temposhot Website Capture accetta un elenco di URL incollato e cattura ogni pagina sul tuo Mac, senza script da mantenere. Vedi automazione di screenshot di siti web oppure inizia dalla guida rapida di Website Capture.
playwrightwebsite-captureautomationjavascript
Termini utilizzati in questa pagina: full page screenshot, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot
Trasforma gli screenshot ripetibili in lavoro completato
Temposhot scatta screenshot a intervalli regolari o a ogni clic ed esporta il risultato come PDF, immagini, ZIP o MP4.
Post correlati
- Come catturare in blocco screenshot di siti web su MacCattura in blocco screenshot di siti web su Mac con Temposhot. Incolla un elenco di URL, scegli i preset desktop, tablet e mobile, esegui la cattura in parallelo ed esporta per audit o report.
- Come automatizzare gli screenshot di siti web con PuppeteerUsa Puppeteer per automatizzare gli screenshot di siti web con JavaScript. Cattura una pagina, pagine intere, batch di URL e viewport mobile con uno script riutilizzabile.
- Come automatizzare gli screenshot di siti web senza codiceScegli un modo no-code per automatizzare gli screenshot di siti web: cattura da una lista di URL, piattaforma di automazione con API per screenshot, strumenti di monitoraggio o helper del browser.
- Migliori alternative a Xnapper per Mac nel 2026Confronta le alternative a Xnapper per Mac: acquisizione ripetuta, acquisizione e registrazione a scorrimento, sfondi gratuiti, guide multipiattaforma e l'app integrata.