Free tools Windows power users keep installed

One-click scans. No signup required.

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

Use php-webdriver’s screenshot methods while the browser session is still open: call $driver->takeScreenshot('path.png') for the current browser view, or call $element->takeElementScreenshot('path.png') for one element. To capture failures, keep the driver alive until your failure-handling code runs—normally before tearDown() closes the session.

This guide shows one-off captures, PHPUnit failure handling, reusable extension architecture, CI artifact practices, and common causes of missing screenshots. Examples use PHP, PHPUnit, Selenium WebDriver, and the php-webdriver/php-webdriver package. Pin versions that you have verified together; the documentation reviewed for this topic does not establish one universal compatibility matrix.

What the PHP WebDriver screenshot API does

The PHP binding exposes a current-page screenshot helper on RemoteWebDriver. Supplying a filename writes a PNG; omitting it returns the image data so your code can save or process it itself.

<?php
use FacebookWebDriverRemoteRemoteWebDriver;

// $driver is an existing RemoteWebDriver session.
$driver->get('https://example.test/login');

// Save directly to a writable PNG path.
$driver->takeScreenshot(__DIR__ . '/artifacts/login.png');

// Or keep the PNG bytes in memory.
$screenshotData = $driver->takeScreenshot();
file_put_contents(__DIR__ . '/artifacts/login-memory.png', $screenshotData);

Use a .png destination and create the directory before the test runs. The default should be understood as the browser’s current view. Exact behavior can vary by browser and driver implementation, particularly where an implementation does not fully conform to the WebDriver standard; do not assume that every setup produces a full, stitched, page-length image.

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

Capture a particular element

Element capture is useful for a component, error message, or checkout panel instead of the entire viewport.

<?php
use FacebookWebDriverWebDriverBy;

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

The element must exist and be available when the method is called. Wait for it using the synchronization strategy already used by your test; an immediate lookup can fail when the page is still rendering.

Basic PHPUnit test with a screenshot

PHPUnit creates a fresh test-case instance for each test method and invokes setUp() before the test and tearDown() afterward. Create the WebDriver in setUp(), perform captures while it is live, and release it in tearDown().

<?php
use FacebookWebDriverRemoteDesiredCapabilities;
use FacebookWebDriverRemoteRemoteWebDriver;
use FacebookWebDriverWebDriverBy;
use PHPUnitFrameworkTestCase;

final class LoginTest extends TestCase
{
    private RemoteWebDriver $driver;
    private string $artifactDirectory;

    protected function setUp(): void
    {
        parent::setUp();
        $this->artifactDirectory = __DIR__ . '/artifacts';
        if (!is_dir($this->artifactDirectory) && !mkdir($this->artifactDirectory, 0775, true) && !is_dir($this->artifactDirectory)) {
            throw new RuntimeException('Cannot create screenshot directory');
        }

        // Match these capabilities and the Selenium endpoint to your environment.
        $this->driver = RemoteWebDriver::create(
            'http://127.0.0.1:4444',
            DesiredCapabilities::chrome()
        );
    }

    public function testInvalidPasswordShowsAnError(): void
    {
        $this->driver->get('https://example.test/login');
        $this->driver->findElement(WebDriverBy::id('email'))->sendKeys('user@example.test');
        $this->driver->findElement(WebDriverBy::id('password'))->sendKeys('wrong-password');
        $this->driver->findElement(WebDriverBy::cssSelector('button[type="submit"]'))->click();

        $this->driver->takeScreenshot($this->artifactDirectory . '/invalid-password.png');
        $this->assertSame('Invalid password', $this->driver->findElement(WebDriverBy::id('error'))->getText());
    }

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

Replace the example URL, selectors, Selenium endpoint, and capabilities with values for your application. Keep the browser session available until all diagnostics are written.

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

Take a screenshot when a test fails

A small, dependable pattern is to wrap the test body in try/catch, capture the current view, then rethrow the failure so PHPUnit still reports it. Catching Throwable covers assertion failures and other errors that occur inside the test body.

public function testCheckout(): void
{
    try {
        $this->driver->get('https://example.test/checkout');
        // Browser actions and assertions...
        $this->assertSame('Complete order', $this->driver->findElement(WebDriverBy::tagName('h1'))->getText());
    } catch (Throwable $failure) {
        $name = preg_replace('/[^A-Za-z0-9_.-]+/', '_', $this->name());
        $file = $this->artifactDirectory . '/' . $name . '-' . date('Ymd-His') . '.png';
        try {
            $this->driver->takeScreenshot($file);
        } catch (Throwable $captureFailure) {
            // Preserve the original test failure; report captureFailure separately in CI logs.
            error_log('Screenshot capture failed: ' . $captureFailure->getMessage());
        }
        throw $failure;
    }
}

This local approach is easy to understand but must be repeated or abstracted for every test that needs it. It also captures only when execution reaches the wrapper and the session is still usable. A browser crash, lost Selenium connection, or failure in setUp() may leave no session from which to capture.

Reusable suite-wide integration with PHPUnit events

For a large suite, implement and register a PHPUnit test-runner extension that subscribes to failure and error outcome events. The subscriber needs access to the WebDriver instance associated with the failing test, then calls takeScreenshot() before the session is released. PHPUnit’s extension interfaces and event APIs are versioned, so adapt the subscriber to the exact PHPUnit release in your project rather than copying a version-specific class signature blindly.

Design checklist

  • Define how the subscriber obtains the driver: a test method, a shared registry, or a project-specific adapter.
  • Subscribe to the outcomes you care about, including assertion failures and errors, not only one event type.
  • Use unique names containing the test class, method, process identifier, and timestamp to prevent parallel workers overwriting files.
  • Never replace the original failure with a screenshot exception. Log capture errors and allow PHPUnit’s original outcome to remain authoritative.
  • Capture before tearDown() or any fixture code that quits the browser.

There is no established built-in switch in current PHPUnit documentation that automatically connects Selenium to screenshots. Old PHPUnit Selenium extension manuals mention properties such as $captureScreenshotOnFailure, $screenshotPath, and $screenshotUrl; those are legacy extension-era settings, not current PHPUnit features.

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

Choosing an approach

Approach Scope Failure coverage Effort Main risk
Inline call One known checkpoint Only where explicitly called Lowest Easy to omit on an unexpected failure
try/catch One test or base test helper Failures and errors inside the wrapper Low to moderate Session may already be unusable
PHPUnit extension/subscriber Reusable across a suite Configured terminal outcomes Highest API and driver-lifecycle version fit

Whichever approach you choose, separately configure CI to collect and retain the artifact directory. A file written on the PHP runner is not automatically copied from a remote Selenium or browser host, and remote deployments can differ in where a binding writes a path. Confirm the behavior of your topology instead of assuming that a path on one machine exists on another.

Path, naming, and CI practices

  • Use a directory writable by the process running PHPUnit; fail early if it cannot be created.
  • Prefer paths based on __DIR__ or the CI workspace rather than host-specific absolute paths.
  • Sanitize test names before using them as filenames.
  • Add a worker identifier when running tests in parallel.
  • Upload PNGs as CI artifacts even when the test job fails, and set retention according to your team’s debugging needs.
  • Keep screenshots near logs, browser console output, and the exception message so a failure can be correlated.

Troubleshooting

“Permission denied” or no file appears

The destination directory is missing or not writable by the PHPUnit process. Create it during setup, check ownership and permissions, and print the resolved path in CI logs. Confirm that the job uploads that directory after failures.

“No such session” or a connection error

The browser was quit, crashed, or disconnected before capture. Move the capture before tearDown(), avoid calling quit() in an earlier failure handler, and inspect Selenium and driver logs. If the browser process is gone, a screenshot cannot be recovered.

Element screenshot fails because the element is missing

The selector may be wrong or the page may not have finished rendering. Wait for the element using your project’s explicit-wait strategy, verify the current URL and DOM state, and capture the page screenshot as a fallback.

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

The image shows only part of a long page

That is consistent with current-viewport behavior. Full-page or stitched output is driver- and browser-dependent; verify the exact combination rather than assuming that takeScreenshot() scrolls and stitches the document.

Capture masks the original test error

Wrap the screenshot call in its own try/catch. Log the capture exception, then rethrow the original PHPUnit failure.

Parallel tests overwrite one another

Include the test identifier, process or worker ID, and a timestamp (or a unique ID) in each filename. Ensure each worker can write to the destination.

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

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

For API options, see the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen TTL caching, signed links, async jobs with signed webhooks, bulk capture for 100 URLs per call, a usage API, an OpenAPI specification, and familiar parameter names for easier migration. Plans include 1,000 free shots each month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan.

Version and reliability checklist

  1. Record PHP, PHPUnit, php-webdriver, Selenium Server, browser, and driver versions in the project.
  2. Verify screenshot method signatures against the installed php-webdriver release.
  3. Run a deliberate failing test to confirm capture timing and artifact upload.
  4. Test both local and remote browser execution if CI uses a grid or container.
  5. Review image dimensions and contents for the browser/driver combination you actually deploy.

Frequently Asked Questions

Does PHPUnit automatically save Selenium screenshots on failure?

Not as a current built-in Selenium switch. Use test-level failure handling or implement a PHPUnit extension and outcome subscriber that can access the live WebDriver.

Where is the screenshot file written?

The binding writes to the path supplied to the PHP process. In remote setups, verify which host runs that process and configure CI artifact transfer explicitly.

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

Can I capture an element instead of the page?

Yes. Find it with WebDriverBy and call $element->takeElementScreenshot('element.png').

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.