Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

With PHP’s php-webdriver/php-webdriver client, save the current browser screenshot with $driver->takeScreenshot('screenshot.png'). To keep one automatically when a PHPUnit test fails, capture it while the WebDriver session is still open—usually in a failure-handling path inside the test, or through a project-specific PHPUnit extension. PHPUnit does not provide a built-in Selenium screenshot-on-failure switch.

Save a screenshot with PHP WebDriver

Install and configure the php-webdriver/php-webdriver package and connect its RemoteWebDriver instance to your Selenium session as usual. Once the browser is on the page you want to inspect, call takeScreenshot().

<?php

// Save the current browser screenshot as a PNG file.
$driver->takeScreenshot(__DIR__ . '/artifacts/screenshot.png');

// Or receive the PNG data and decide where to store it yourself.
$screenshotData = $driver->takeScreenshot();
file_put_contents(__DIR__ . '/artifacts/screenshot-from-data.png', $screenshotData);

The package documents both forms: supplying a path saves the screenshot, while omitting it returns screenshot data. Use a writable directory and a .png filename. Create the directory before the test runs if it does not already exist.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

These calls capture the current browser view by default. Do not assume a full-page image: screenshot behavior can vary by browser and driver, and the Selenium Java API cautions that behavior outside W3C-conformant implementations is best effort. Verify what your specific browser, driver, Selenium Server and PHP binding combination captures before relying on it.

#1 Best Overall
Sale
The Web Application Hacker's Handbook: Finding and Exploiting Security Flaws
  • Comes with secure packaging
  • It can be a gift item
  • Easy to read text

Capture a specific element

For a single DOM element, locate it and call takeElementScreenshot():

<?php

use FacebookWebDriverWebDriverBy;

$element = $driver->findElement(WebDriverBy::id('some_id'));
$element->takeElementScreenshot(__DIR__ . '/artifacts/element-screenshot.png');

The selector must identify an element present on the current page. If it is missing, not yet rendered, or obscured in a way that prevents interaction, resolve that condition before capture. Element screenshots are distinct from a screenshot of the current browser view.

Keep a screenshot when a PHPUnit test fails

The key requirement is session lifetime: capture before the browser is quit or released. PHPUnit’s setUp() and tearDown() run for each test method. A typical test creates the driver in setUp() and releases it in tearDown(), so a failure screenshot must be taken before teardown closes the session.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Local failure handling in one test

A simple approach is to catch the throwable around the browser work, save the screenshot, and rethrow it so PHPUnit still reports the original failure. The following illustrates the pattern; adapt driver construction and cleanup to your project.

<?php

use FacebookWebDriverRemoteRemoteWebDriver;
use PHPUnitFrameworkTestCase;
use Throwable;

final class CheckoutTest extends TestCase
{
    private RemoteWebDriver $driver;

    protected function setUp(): void
    {
        // Create/connect to the browser session used by this test.
        $this->driver = createDriverForThisProject();
    }

    public function testCheckoutPageLoads(): void
    {
        try {
            $this->driver->get('https://example.com/checkout');
            self::assertSame('Checkout', $this->driver->getTitle());
        } catch (Throwable $failure) {
            $directory = __DIR__ . '/artifacts';
            if (!is_dir($directory)) {
                mkdir($directory, 0775, true);
            }

            $path = $directory . '/checkout-test.png';
            try {
                $this->driver->takeScreenshot($path);
            } catch (Throwable $captureFailure) {
                // Preserve the test failure; record capture failure separately if useful.
                error_log('Could not capture failure screenshot: ' . $captureFailure->getMessage());
            }

            throw $failure;
        }
    }

    protected function tearDown(): void
    {
        if (isset($this->driver)) {
            $this->driver->quit();
        }
    }
}

createDriverForThisProject() is deliberately a stand-in for your existing driver setup, not a php-webdriver function. This local pattern is easy to add to a test, but it only covers failures that pass through its try/catch. If the test has several operations or the suite needs consistent coverage, put the pattern in a shared helper or pursue a runner-level integration.

Reusable capture through a PHPUnit extension

For suite-wide handling, a project can implement and register a PHPUnit test-runner extension that subscribes to failure or error outcome events. The subscriber needs access to the relevant WebDriver instance and must capture before the browser session is closed. PHPUnit documents extension interfaces and outcome subscribers, but that is an integration route—not a ready-made Selenium screenshot extension. Implement and verify it against the PHPUnit version used by your project.

Before choosing this route, decide how the extension obtains the per-test driver, how it maps an outcome to the correct session, and how it handles multiple tests or parallel workers. Those are application-specific integration concerns; do not assume PHPUnit can discover a browser session automatically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the right failure-capture approach

Approach Scope and coverage Trade-off
Try/catch near browser actions One test or a shared helper; captures throwables that pass through the handler. Low integration effort, but coverage depends on where the handler is placed.
PHPUnit extension and outcome subscriber Reusable runner-level handling for subscribed outcomes, if the extension can access the live driver. More implementation work; event APIs and driver handoff must fit the project’s PHPUnit version and architecture.

Either way, protect the original test result: if screenshot capture itself fails, log or otherwise report that secondary problem without replacing the assertion or application failure that caused the test to fail.

Paths, remote browsers and CI artifacts

A screenshot path must be writable by the PHP test process. Prefer a project-relative directory such as __DIR__ . '/artifacts' over a hard-coded operating-system-specific absolute path. Ensure the directory exists and that the process has permission to write there.

With a remote Selenium setup, establish where the PHP binding writes the file in your actual deployment. The browser may run on another host, but the destination path is a filesystem concern; do not assume a path on the Selenium host is automatically available to the PHP runner. Confirm the produced file on the runner and test the same arrangement in CI.

Saving the file is separate from retaining it. Configure your CI system to collect and retain the artifact directory, including on failed jobs. Use unique filenames when tests can run concurrently or produce multiple captures; otherwise, later screenshots may overwrite earlier ones. Include a test or worker identifier in the filename if necessary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Version and compatibility checks

There is no single compatible version matrix established here for PHP, PHPUnit, php-webdriver, Selenium Server, browser and driver. Pin versions that your project has verified together, and check the API signatures and PHPUnit event interfaces for those exact releases. The php-webdriver wiki and main-branch source can change over time.

Do not copy legacy PHPUnit Selenium extension settings such as $captureScreenshotOnFailure, $screenshotPath or $screenshotUrl into a current test configuration. Those properties appear in PHPUnit 3.7-era extension documentation; they are not established as current PHPUnit features.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting screenshot capture

  • No file appears: Check that the output directory exists, the PHP process can write there, and the path passed to takeScreenshot() is the one you later inspect. In CI, verify artifact collection separately.
  • The file is empty or capture throws: Confirm the WebDriver session is still active and capture happens before quit() or teardown. Preserve the original test failure while logging the screenshot error.
  • Failure screenshots are inconsistent: Confirm every relevant failure outcome reaches the local handler, or review the extension’s subscriptions. A local catch cannot cover failures outside its scope.
  • The image shows only part of the page: The default should be treated as the current browser view. Check the behavior documented for your exact browser and driver rather than assuming full-page capture.
  • Remote execution cannot find the artifact: Determine which machine’s filesystem receives the file and how CI retrieves it. A browser host path and a PHP runner path are not interchangeable by assumption.
  • One test overwrites another’s image: Use unique per-test or per-worker names and ensure parallel jobs do not write to the same shared filename.
  • An old PHPUnit setting has no effect: Remove assumptions based on the PHPUnit 3.7-era screenshot properties; implement capture in test logic or build a version-appropriate extension.

Or skip the browser setup

If you need a screenshot of a public URL rather than evidence from the exact browser session running your test, ScreenshotNeo offers a one-request API. It is a website screenshot API and MCP server from Yorker Media. This does not replace a test-session screenshot when you need the page as rendered with that test’s own cookies, state or interaction.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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)

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}`);

See the ScreenshotNeo API documentation for request options and response details. Cookie banners are accepted and removed along with 60+ known consent platforms, newsletter popups and chat widgets before the shot; those steps can be turned off. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers indicate the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf for AI agents and MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently asked questions

Can I return screenshot data instead of writing a file?

Yes. Call $driver->takeScreenshot() without a path to receive the PNG data, then handle storage yourself.

Does PHPUnit automatically take a Selenium screenshot on every failure?

No built-in switch is established. Add failure handling while the session is live, or implement a PHPUnit extension suited to your version and driver setup.

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.