Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a Mocha afterEach() hook to check whether the test failed and, if it did, call Selenium’s driver.takeScreenshot() before quitting the browser. Selenium returns a base64-encoded PNG; write it to a file with base64 encoding. The browser must still be alive when the hook runs. [Mocha hooks; Selenium WebDriver API]
Capture the screenshot in afterEach()
Mocha runs a test’s afterEach() hook after that test and before the suite’s after() teardown hook. That makes afterEach() the right place to preserve the browser state for a failed test. Keep the driver available until capture finishes; Selenium’s quit() ends the browser session, so commands sent afterward cannot take a screenshot. [Mocha hooks; Selenium WebDriver API]
Here is a complete Mocha example for a suite that creates and owns one Chrome driver. It saves a screenshot only when the test fails, creates the output directory as needed, and gives each file a timestamp-based name. It uses ES modules and the current Selenium JavaScript API shape; check that your installed Node.js, Mocha, Selenium package, and Chrome/WebDriver setup support the imports and options shown.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →import fs from 'node:fs/promises';
import path from 'node:path';
import { Builder } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome.js';
const screenshotDir = path.resolve('artifacts/screenshots');
let driver;
function safeName(title) {
return title.replace(/[^a-z0-9-_]+/gi, '_').slice(0, 120) || 'unnamed-test';
}
describe('checkout', function () {
before(async function () {
const options = new chrome.Options();
// Add options here only when your CI/browser environment requires them.
driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
});
afterEach(async function () {
const test = this.currentTest;
if (test?.state !== 'failed' || !driver) return;
await fs.mkdir(screenshotDir, { recursive: true });
const image = await driver.takeScreenshot();
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `${safeName(test.fullTitle())}-${stamp}.png`;
await fs.writeFile(path.join(screenshotDir, filename), image, 'base64');
});
after(async function () {
if (driver) await driver.quit();
});
it('shows the order confirmation', async function () {
await driver.get('https://example.com');
// Add assertions and interactions for your application here.
});
});
The sample’s example.com navigation is only a placeholder for your application URL; replace it and add the actual test assertions. The capture hook itself does not need a special assertion or a custom failure callback: it reads the test state from Mocha’s hook context after the test completes. The code is an implementation pattern, not a claim of having been run against your specific versions.
#1 Best Overall
Why the hook must be a regular function
Mocha supplies hook context through this. Declare the hook as async function (), as above, rather than an arrow function; an arrow function does not receive Mocha’s own this context. Read this.currentTest inside the hook and check its state before doing I/O, so passing tests do not generate screenshot files.
Save the returned data as a PNG
takeScreenshot() resolves to a base64-encoded PNG string. Passing 'base64' as the file-writing encoding decodes that string into PNG bytes. Do not write the base64 text as an ordinary UTF-8 file and then give it a .png extension; that produces a text file, not an image. Selenium’s documentation demonstrates writing screenshot data with base64 encoding. [Selenium: Working with windows and tabs]
Keep the captured state useful
Do not quit the driver before capture
Put driver.quit() in the suite’s later after() hook, not in a test-level teardown that runs before the screenshot hook. A quit session is no longer available for browser commands. If your project already has teardown hooks, inspect their order and ownership: there should be one clear owner responsible for keeping the session alive through capture and then closing it.
Recommended Free Tools
Rank #2
Account for retries, workers, and repeated titles
Two tests can share a title, retries can produce several failures for the same test, and parallel workers can write artifacts concurrently. A filename based only on fullTitle() can therefore overwrite a prior image. The example appends a timestamp; for highly parallel or tightly timed runs, include a worker identifier, retry number, or unique run identifier as well. Store screenshots in a directory that CI collects as an artifact, and make the directory path configurable if local and CI environments need different locations.
Understand what Selenium captures
Selenium describes takeScreenshot() as a best-effort screenshot of the current page. Treat it as a browser screenshot, not a guarantee of a full-page image across every browser and driver combination. If the failure concerns content below the viewport, use browser- or driver-specific full-page support only after verifying it for the environment you run; otherwise, consider scrolling to the relevant content before capture or saving additional diagnostics. [Selenium WebDriver API]
Handle screenshot failures without hiding test failures
The original assertion failure is usually the most important result. A second error while writing the screenshot can obscure it, so decide how your test runner and CI should report artifact failures. One option is to catch capture errors, log them, and allow the original test result to remain visible:
Rank #3
afterEach(async function () {
const test = this.currentTest;
if (test?.state !== 'failed' || !driver) return;
try {
await fs.mkdir(screenshotDir, { recursive: true });
const image = await driver.takeScreenshot();
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `${safeName(test.fullTitle())}-${stamp}.png`;
await fs.writeFile(path.join(screenshotDir, filename), image, 'base64');
} catch (error) {
console.error(`Could not save screenshot for ${test.fullTitle()}:`, error);
}
});
Use this version in place of—not in addition to—the earlier afterEach(). Logging and continuing preserves visibility of the test failure but means CI may pass without a screenshot unless the test itself already failed. If missing screenshots should fail the job, let the hook error propagate or have your artifact checks detect the missing file. Choose deliberately rather than silently swallowing capture errors.
Troubleshoot common problems
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No screenshot after a failed test | The hook is not running for that suite, its state check does not match the installed Mocha behavior, or the driver is already closed. | Confirm the hook is registered in the relevant suite, inspect this.currentTest and its state, and verify that driver teardown runs afterward. |
this.currentTest is missing |
The hook is an arrow function or is not using Mocha’s BDD hook context. | Use a regular function () declaration for the hook and confirm the project is using Mocha’s BDD interface. |
| WebDriver reports an invalid or ended session | A teardown path called quit() before the capture attempt. |
Move session shutdown to later suite teardown and check for duplicate teardown handlers. |
| File is missing or cannot be written | The directory does not exist, the process lacks write permission, or the configured path differs in CI. | Create the directory recursively, resolve the path explicitly, and ensure CI preserves that directory as an artifact. |
| PNG cannot be opened | The base64 string was written as normal text, or a non-PNG extension was used. | Write using the base64 encoding and keep the .png extension. |
| One test’s image replaces another | Filenames collide across retries, duplicate titles, or parallel workers. | Add a timestamp and, where needed, retry/run/worker identifiers; verify collisions are impossible for your execution pattern. |
| The screenshot misses content lower on the page | The capture is a viewport/browser screenshot rather than a portable full-page capture. | Do not assume uniform full-page behavior; use a verified browser-specific method or capture the relevant area after scrolling. |
Choose a custom hook or an existing package
A small custom hook is usually the straightforward choice when you want control over output paths, filenames, and exactly when capture occurs. It adds no screenshot-specific package dependency beyond Selenium and Mocha, though you remain responsible for artifact naming, directory management, and any diagnostics you want alongside the image.
If the project already uses mocha-webdriver, its npm listing describes automatic screenshot and log collection after failed cases when debug capture is enabled and MOCHA_WEBDRIVER_LOGDIR is configured. Check the package’s current maintenance, configuration, and compatibility with your installed Mocha/Selenium versions before relying on it or adding it. [mocha-webdriver on npm]
Whichever route you use, check behavior with retries and parallel workers, determine whether logs as well as images are needed, and ensure the output directory is uploaded by CI. The package listing describes the capability; it does not establish that a given version fits a particular project.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If you need a screenshot of a reachable page by URL rather than the exact live browser session that just failed, ScreenshotNeo offers a one-request screenshot API. It is not a substitute for the Selenium hook when the failure depends on that test’s authenticated session, unsaved form state, or transient browser state.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
With a ScreenshotNeo API key, this cURL request saves a WebP screenshot of Stripe; replace the target URL as needed. See the ScreenshotNeo API documentation for request options and response details.
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
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with response headers indicating the page verdict and billing status. Its MCP server provides screenshot and page-information tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. For an API-based capture of a page by URL, sign up free for 1,000 screenshots a month with no card.
FAQ
Can I automatically take a screenshot after every test?
Yes. Remove the failure-state condition in afterEach() so it captures unconditionally, but expect more image files and storage use. Keep the driver alive until the hook completes.
Does a Selenium screenshot include the whole page?
Do not rely on that across all browser and driver implementations. Selenium documents a best-effort screenshot of the current page, not a universal full-page guarantee. [Selenium WebDriver API]
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsQuick 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.

