Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Capture the screenshot before you quit WebDriver, save it with a deterministic test name, then upload that file to Drive or Dropbox. Selenium’s Java TakesScreenshot interface captures the current browsing context and can return a temporary File, bytes, or Base64. The example below uses a temporary file, copies it to a permanent path, and uploads that copy so the evidence remains available even when a cloud request fails.
The same lifecycle works in JUnit, TestNG, or a custom runner: detect a failed test, capture while the driver is alive, persist the image, upload it, log the remote identifier, and only then run teardown.
1. Capture before WebDriver teardown
A screenshot is tied to the live browsing context. Call getScreenshotAs in the failure callback before driver.quit() or driver.close(). Calling it after teardown commonly produces a WebDriverException; some drivers can also throw UnsupportedOperationException when screenshot capture is not supported. The API contract is documented by Selenium’s TakesScreenshot Java API.
Reusable Java capture utility
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
public final class FailureScreenshot {
private FailureScreenshot() {}
public static Path capture(WebDriver driver, Path directory, String testId)
throws IOException {
if (!(driver instanceof TakesScreenshot)) {
throw new UnsupportedOperationException(
"This WebDriver does not implement TakesScreenshot");
}
Files.createDirectories(directory);
String safeId = testId.replaceAll("[^A-Za-z0-9._-]", "_");
String fileName = safeId + "-" + Instant.now().toEpochMilli() + ".png";
File temporary = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
Path destination = directory.resolve(fileName);
Files.copy(temporary.toPath(), destination,
StandardCopyOption.REPLACE_EXISTING);
return destination;
}
}
OutputType.FILE gives you a temporary file that must be copied before the test process or operating system removes it. Use OutputType.BYTES when a provider client accepts an input stream, or OutputType.BASE64 when your reporting system requires a Base64 value.
#1 Best Overall
Invoke it from a failure hook
Your JUnit extension, TestNG listener, or runner should call a method like this only for a failed test:
public void onFailure(String testId, WebDriver driver) {
Path local;
try {
local = FailureScreenshot.capture(
driver, Path.of("build", "failure-screenshots"), testId);
} catch (WebDriverException | UnsupportedOperationException e) {
// Record the capture failure, but still execute normal teardown.
logger.error("Could not capture screenshot for {}", testId, e);
return;
} catch (IOException e) {
logger.error("Could not persist screenshot for {}", testId, e);
return;
}
try {
uploadToDrive(local, testId);
// Or call uploadToDropbox(local, testId).
} catch (Exception e) {
logger.error("Cloud upload failed for {}; local file is {}",
testId, local, e);
}
}
Keep the local file until the upload has returned successfully. Include the test identifier, browser, page URL, and failure timestamp in your log record, but never write OAuth access tokens or refresh tokens to logs.
2. Prepare Java and Google Drive authentication
Google’s Java quickstart lists Java 11 or later, Gradle 7.0 or later, a Google Cloud project, an enabled Drive API, and OAuth client credentials as prerequisites. Follow the current Google Drive Java quickstart for project creation and credential download. Its simplified authorization flow is intended for testing; production applications should choose an authorization and consent design appropriate for their users and deployment.
- Create or select a Google Cloud project and enable the Google Drive API.
- Create an OAuth client for a desktop application and download the credentials JSON.
- Put the JSON outside source control, for example in a CI secret or a protected configuration directory.
- Add the current Google Drive Java client, OAuth client, and HTTP transport dependencies shown by the quickstart. Do not hard-code dependency versions; use the versions currently documented there.
- Choose a least-privilege scope.
DriveScopes.DRIVE_FILElimits access to files created or opened by the application; broader scopes require more user consent.
Build a Drive service
The following helper follows Google’s installed-application OAuth pattern. The first interactive run opens a browser; subsequent runs reuse the token data stored in the tokens directory.
Rank #2
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
public final class DriveFactory {
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
public static Drive create() throws Exception {
NetHttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
InputStream credentialsStream = DriveFactory.class.getResourceAsStream(
"/credentials.json");
if (credentialsStream == null) {
throw new IllegalStateException("credentials.json was not found");
}
GoogleClientSecrets secrets = GoogleClientSecrets.load(
JSON_FACTORY, new InputStreamReader(credentialsStream));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
transport, JSON_FACTORY, secrets,
Collections.singleton(DriveScopes.DRIVE_FILE))
.setDataStoreFactory(new FileDataStoreFactory(new File("tokens")))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder()
.setPort(8888).build();
Credential credential = new AuthorizationCodeInstalledApp(flow, receiver)
.authorize("selenium-runner");
return new Drive.Builder(transport, JSON_FACTORY, credential)
.setApplicationName("Selenium failure artifacts")
.build();
}
}
For headless CI, complete the authorization once in a controlled environment and store the resulting token data in your CI secret store, or implement the service-account or delegated-access design approved by your organization. Do not copy a developer’s personal token into a repository.
3. Upload the PNG to Google Drive
Google documents three upload types: simple media, multipart (metadata plus media), and resumable. Simple and multipart guidance covers files up to 5 MB; use resumable uploads for files over 5 MB or for transfers where interruption recovery matters. See the Drive upload guide.
Multipart upload with metadata
This is a practical default for a normal Selenium PNG because it sends the file and its metadata in one request. Replace parentFolderId with a real folder ID, or omit the parents property to use the authenticated user’s default location.
import com.google.api.client.http.FileContent;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import java.nio.file.Path;
public static String uploadToDrive(Drive service, Path screenshot,
String testId, String parentFolderId)
throws Exception {
File metadata = new File()
.setName(screenshot.getFileName().toString())
.setDescription("Selenium failure: " + testId);
if (parentFolderId != null && !parentFolderId.isBlank()) {
metadata.setParents(java.util.List.of(parentFolderId));
}
FileContent media = new FileContent("image/png", screenshot.toFile());
File created = service.files().create(metadata, media)
.setFields("id,name,webViewLink,size")
.execute();
return created.getId();
}
Persist the returned file ID with the test result. A Drive file ID is more reliable for later automation than scraping a human sharing URL. If people must open the image, apply your organization’s sharing policy explicitly; the upload itself does not define who can access it.
Choosing the upload mode
| Drive mode | Use it when | Important detail |
|---|---|---|
| Simple media | Small media without file metadata | Uses uploadType=media; Google’s guidance places this in the up-to-5 MB category. |
| Multipart | Small media that needs a name, folder, or description | The Java FileContent example above sends metadata and PNG content together. |
| Resumable | Files over 5 MB or interruption-prone connections | Creates an upload session so a broken transfer can resume instead of restarting from byte zero. |
Selenium screenshots are often below 5 MB, but full-page captures and unusually large pages can exceed that threshold. Check the file size before selecting a mode rather than assuming every PNG is small.
4. Upload to Dropbox instead
Dropbox provides an official Java SDK for API v2. Install the current SDK release and follow its Java SDK documentation for authorization and dependency coordinates. Keep the access token in an environment variable or CI secret.
SDK upload example
The following uses the SDK’s upload-builder pattern. Method names can change between SDK releases, so verify the signatures against the release you install.
Recommended Free Tools
import com.dropbox.core.DbxRequestConfig;
import com.dropbox.core.v2.DbxClientV2;
import com.dropbox.core.v2.files.FileMetadata;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public static String uploadToDropbox(Path screenshot, String testId)
throws Exception {
String token = System.getenv("DROPBOX_ACCESS_TOKEN");
if (token == null || token.isBlank()) {
throw new IllegalStateException("DROPBOX_ACCESS_TOKEN is not set");
}
DbxRequestConfig config = DbxRequestConfig.newBuilder(
"selenium-failure-uploader").build();
DbxClientV2 client = new DbxClientV2(config, token);
String remotePath = "/selenium-failures/"
+ testId.replaceAll("[^A-Za-z0-9._-]", "_") + "-"
+ System.currentTimeMillis() + ".png";
try (InputStream in = Files.newInputStream(screenshot)) {
FileMetadata uploaded = client.files().uploadBuilder(remotePath)
.uploadAndFinish(in);
return uploaded.getId();
}
}
Use the provider’s current authorization example to obtain the token and grant only the folder or app permissions your organization allows. The remote path is Dropbox’s namespace, so decide whether each pipeline, branch, or project gets its own prefix. The returned file metadata gives you an identifier for your test report.
5. Drive or Dropbox: choosing for a test pipeline
| Decision axis | Google Drive | Dropbox |
|---|---|---|
| Java integration | Google publishes a Java client, quickstart, and FileContent upload examples. |
Dropbox publishes an official Java SDK for API v2; use its current examples for authorization and uploads. |
| Authorization | OAuth client credentials and a Drive scope; the quickstart’s simplified flow is for testing. | OAuth and app permissions configured through Dropbox; keep tokens outside source control. |
| Organization model | Files and optional parent folder IDs. | Remote paths in the Dropbox namespace. |
| Interrupted uploads | Documented resumable mode is available and is appropriate for large or unreliable transfers. | Follow the SDK release documentation for the upload behavior and session methods you need. |
| Sharing and discovery | Record the file ID and apply Drive sharing policy. | Record the returned metadata and apply Dropbox link or team policy. |
Choose the provider your team already governs unless a specific requirement—such as Drive’s documented resumable mode—outweighs that operational consistency.
6. Reliability, parallel runs, and security
- Use collision-resistant names. Include a sanitized test ID, epoch milliseconds or an ISO timestamp, and optionally a run ID. Parallel workers should write to separate local directories or include the worker ID.
- Upload after copying. The Selenium temporary file is an implementation detail; the copied path is your durable retry source.
- Retry cloud requests, not browser capture. A transient HTTP failure can often be retried with backoff. Re-capturing later may show a different page state.
- Keep credentials secret. Store OAuth client secrets, refresh tokens, and Dropbox access tokens in the CI secret manager. Redact authorization headers from HTTP logs.
- Record context. Log provider, remote file ID or path, test name, browser, URL, timestamp, and the local path. This makes a failed upload diagnosable without exposing credentials.
- Define retention. Screenshots can contain personal data, tokens rendered in a page, or customer information. Set folder retention and access rules before enabling uploads on shared CI.
- Preserve local artifacts on failure. If the provider is unavailable, publish the local PNG as the CI job artifact so the failure is not lost.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
WebDriverException during capture |
The driver has already closed, the page crashed, or the session became invalid. | Move capture into the failure callback before teardown; verify the session is still alive and retain the local log. |
UnsupportedOperationException |
The selected driver does not support TakesScreenshot. |
Use a screenshot-capable driver or mark the capture as unavailable instead of blocking teardown. |
| Zero-byte or missing local file | The temporary file was moved or deleted before it was copied, or the destination directory is not writable. | Copy immediately, create the directory first, and check file size before uploading. |
| Drive authorization prompt repeats | Token storage is not writable, the token directory was discarded between CI jobs, or the scope changed. | Persist token data securely, authorize again after scope changes, and verify the OAuth client configuration. |
| Drive upload rejected for a large image | The request used simple or multipart mode for a file over the documented 5 MB guidance. | Switch to a resumable upload and retain the local file for retry. |
| Drive file appears in the wrong folder | The parent folder ID was omitted or is not accessible to the authorized identity. | Set the correct parent ID and confirm that the OAuth identity can write to that folder. |
| Dropbox returns an authorization or permission error | The token is missing, expired, or lacks the app-folder/team permission for the target path. | Re-authorize using the current SDK flow and choose a permitted remote path. |
| Cloud upload times out | CI egress, proxy, DNS, or a transient provider problem. | Retry with bounded backoff, inspect CI network policy, and keep the local artifact as a fallback. |
| Screenshot shows only part of the page | TakesScreenshot captures the current browsing context and viewport behavior depends on the driver. |
Scroll or resize as part of your test when you need a particular state, and document the driver’s behavior; do not assume a full-page image. |
Or skip the browser setup
If your goal is a clean image of a URL rather than the exact in-session state, ScreenshotNeo is a website screenshot API and MCP server. One request returns PNG, JPEG, WebP, or PDF, without installing Selenium or managing a browser in your test worker.
Its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the request was billed. AI agents can use the MCP server’s take_screenshot, get_page_info, and capture_pdf tools.
Relevant controls include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper size/margins/orientation/page ranges, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for a selector/delay/network idle, ad/tracker/request/resource blocking, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, image resizing, a chosen cache TTL, signed links for public images, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and familiar parameter names for easier migration.
Call it from a shell with the ScreenshotNeo API documentation nearby:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
And Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account to try it.
Frequently Asked Questions
Can one captured file be sent to both Drive and Dropbox?
Yes. Persist the PNG once, then pass the same path to each provider method. Uploading the saved file avoids taking a second browser capture and preserves one consistent failure image.
Should a CI job fail when the screenshot upload fails?
Usually keep the test failure as the primary status, record the upload error, and publish the local PNG as a CI artifact. Make upload failure fatal only when cloud retention is a contractual requirement.
How should screenshots from parallel workers be organized?
Give each worker a run-specific directory or filename prefix and include the test identifier plus a timestamp. This prevents collisions while keeping the provider’s folder or path structure searchable.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

