How to Automatically Save Playwright Screenshots to Google Drive
Capture a Playwright screenshot and upload it to Google Drive with Node.js. Choose an OAuth setup that fits your runtime and file ownership needs.
Use Playwright to capture the page as PNG bytes, then pass those bytes to the Google Drive API v3 files.create method as file media. This avoids temporary files and works in a Node.js process once you configure Google authentication. If you need a file on disk for debugging, save the screenshot to a path and upload it as a readable stream instead.
This guide uses Node.js, Playwright, and Google’s googleapis client. See the Playwright screenshot guide, the Drive API file creation guide, and the Drive API upload guide.
1. Enable Drive API and choose authentication
- Enable the Google Drive API in the Google Cloud project associated with your credentials.
- Choose the identity that should own or access the uploaded screenshot. OAuth uploads create files as the signed-in user. Service-account uploads belong to the service account; they do not automatically appear in an employee’s My Drive.
- Choose an OAuth scope that permits the upload you need. Do not copy the Drive quickstart’s metadata-readonly scope for a write operation. Use the narrowest write scope that fits your application and folder requirements.
- Make sure the authenticated identity has access to the destination folder, if you plan to upload into one.
Google’s Node.js quickstart demonstrates a local browser sign-in flow and notes that its browser-based setup does not run from a remote terminal. It is a starting point for local development, not a ready-made authentication design for every unattended server or CI runner. For unattended use, select server-side credentials or an approved delegated user flow based on your organization’s policy and required file ownership.
2. Install dependencies and prepare an authenticated Drive client
Install Playwright and Google’s Node.js API client in your project:
npm install playwright googleapis
The upload example below expects an authenticated Google client named auth. How you obtain it depends on your runtime and credential choice. For local development, follow the Google Drive Node.js quickstart and adapt its authentication setup to include an upload-capable scope. For a server or CI runner, configure credentials appropriate to that environment; do not assume an interactive browser prompt will be available.
Set the destination folder ID if needed. The folder must be accessible to the identity represented by auth. Omit parents to create the file in the authenticated identity’s default Drive location.
3. Capture PNG bytes and upload them directly
This complete capture-and-upload function takes an existing authenticated client and a page URL. It returns the created Drive file’s ID, name, and view link. The screenshot buffer is passed directly as media, so no local screenshot path is required.
const { google } = require('googleapis');
const { chromium } = require('playwright');
async function captureAndUpload({ auth, url, folderId }) {
const drive = google.drive({ version: 'v3', auth });
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 });
const png = await page.screenshot({ fullPage: true, type: 'png' });
const timestamp = new Date().toISOString().replaceAll(':', '-');
const fileName = `page-screenshot-${timestamp}.png`;
const requestBody = {
name: fileName,
mimeType: 'image/png',
...(folderId ? { parents: [folderId] } : {}),
};
const response = await drive.files.create({
requestBody,
media: {
mimeType: 'image/png',
body: Buffer.from(png),
},
fields: 'id, name, webViewLink',
});
return response.data;
} finally {
await browser.close();
}
}
// Supply `auth` from your chosen Google OAuth or server-side credential flow.
// const file = await captureAndUpload({ auth, url: 'https://example.com', folderId: process.env.DRIVE_FOLDER_ID });
// console.log(file);
Keep the screenshot format and MIME type aligned. This example explicitly requests PNG and uploads it as image/png. The ISO timestamp in the name makes each run distinct; it does not rely on a same-name upload overwriting an earlier Drive file.
4. Use a temporary file when you need one
A disk-backed screenshot can be easier to inspect after a failed run. Playwright resolves relative screenshot paths from the process’s current working directory, which may differ in CI from your local project directory.
const fs = require('node:fs');
const path = require('node:path');
const { google } = require('googleapis');
const { chromium } = require('playwright');
async function captureToDiskAndUpload({ auth, url, folderId }) {
const drive = google.drive({ version: 'v3', auth });
const browser = await chromium.launch({ headless: true });
const filePath = path.resolve(process.cwd(), 'page-screenshot.png');
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 });
await page.screenshot({ path: filePath, fullPage: true, type: 'png' });
const response = await drive.files.create({
requestBody: {
name: path.basename(filePath),
mimeType: 'image/png',
...(folderId ? { parents: [folderId] } : {}),
},
media: {
mimeType: 'image/png',
body: fs.createReadStream(filePath),
},
fields: 'id, name, webViewLink',
});
return response.data;
} finally {
await browser.close();
}
}
The Drive client’s Node.js examples use a readable stream as media.body. With this approach, check that the process can write to the resolved path and that the file exists before the upload begins.
5. Tune capture behavior for your pages
| Need | Playwright option or approach | Consideration |
|---|---|---|
| Capture the visible viewport | page.screenshot() |
This is the default; it does not capture the entire scrollable page. |
| Capture the full scrollable page | page.screenshot({ fullPage: true }) |
Long pages produce larger images and can take longer to capture and upload. |
| Use a stable viewport | browser.newPage({ viewport: { width: 1440, height: 900 } }) |
Choose dimensions that match the layout you need to document. |
| Wait for a particular page state | await page.locator('main').waitFor() |
Prefer a meaningful selector when a page’s network activity never becomes idle. |
| Use a different image type | Set the screenshot type and matching Drive MIME type |
For example, JPEG should be uploaded as image/jpeg; keep the filename extension consistent too. |
The example uses waitUntil: 'networkidle', but pages with analytics, polling, or other persistent network activity may not reach that state promptly. In that case, wait for a meaningful selector or use an appropriate navigation wait condition, then capture. Playwright’s Page API documents screenshot options and page methods.
6. Handle naming, folders, and repeated runs
- Unique run files: Include a timestamp, test name, build number, or run ID in the file name. Drive file creation should not be treated as an implicit overwrite operation.
- Organized storage: Set
parents: [folderId]inrequestBodyto place the new file in a folder. Confirm the authenticated identity can write there. - Useful metadata: Set the file name and MIME type in the metadata. Request only response fields you need, such as
id,name, andwebViewLink. - Correct media: Await
page.screenshot()and send its returned bytes, or use a readable stream from the completed file. A name alone does not upload image content.
Drive supports creating a file with metadata and media in one request. For larger uploads or upload-specific behavior, consult the official upload guide.
7. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 or authorization error | The Drive API is disabled, the token scope is read-only, or the identity lacks access to the target folder. | Enable Drive API, obtain a token with an appropriate write scope, and check folder access for the authenticated identity. |
| Upload succeeds but the file is not visible in your account | The file was created by a different identity, commonly a service account. | Inspect the credentials used and the returned file ID. Arrange access or sharing for the intended account or use an identity suited to the desired ownership. |
| Image is empty or cannot be opened | The upload body is not the awaited screenshot bytes or completed file stream, or its MIME type does not match the content. | Await the screenshot call, pass its returned buffer (or a readable stream), and match MIME type and extension to the image format. |
| CI reports that the screenshot file is missing | A relative path resolved from a different working directory, or the runner cannot write there. | Resolve an absolute path, check directory permissions, or use the in-memory buffer method. |
| Navigation times out before capture | The page did not meet the selected navigation condition in time, possibly because it keeps network connections active. | Choose a suitable navigation wait condition and wait for a page-specific selector before taking the screenshot. |
| Duplicate files accumulate | Each files.create call creates a file; a repeated name is not a dependable replacement strategy. |
Use distinct names intentionally, or implement explicit lookup and update logic if your workflow should replace a prior file. |
8. Reliability, performance, and cost
Capture and upload are two separate operations: page rendering can fail before there are screenshot bytes to upload, and Drive can reject an upload after capture succeeds. Handle and log those stages separately, including the URL, run identifier, screenshot name, and Drive response ID where available. Avoid logging credentials or sensitive page content.
Full-page images use more memory and take longer to transfer than viewport captures, especially for tall pages. The buffer method avoids filesystem setup but keeps the image in process memory; the stream method is useful when you need a local artifact and lets the Drive client read from disk. For repeated or bulk runs, bound concurrency so browser processes and upload requests do not overwhelm the runner or exhaust memory.
Playwright itself is open source, but running browsers and storing screenshots consume compute and Drive storage. The research sources do not establish a Drive price for this workflow; check the applicable Google plan and API terms for your account. Set a retention or cleanup policy if each scheduled run creates a new file.
Or skip the browser setup
If you need a screenshot without installing or running Playwright in your own service, ScreenshotNeo is a website screenshot API and MCP server for developers. Make one GET request with a URL and receive an image or PDF. The response can be PNG, JPEG, or WebP; see the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
- Cookie banners are accepted and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before capture; each cleanup step can be switched off.
- Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server offers
take_screenshot,get_page_info, andcapture_pdftools for AI agents, including Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots a month with no card.
FAQ
Which Google account will own the screenshot?
The account or identity represented by the credentials used for the Drive API call creates the file. OAuth user credentials create it as that user; service-account credentials create it under the service account.
Can I save the screenshot to a shared folder?
Yes, specify the folder ID in parents and ensure the authenticated identity has permission to create files there. Workspace sharing and ownership policies may vary by organization.
Does the screenshot path need to be on disk?
No. Playwright returns screenshot bytes when you omit path; upload those bytes directly. Use a path and readable stream when you also want a local copy.


