Taking Screenshots

An assertion tells you a test failed. A screenshot tells you what the browser actually looked like at the moment it failed — which locator broke, which page it landed on, whether an unexpected popup was in the way. Selenium captures screenshots through the TakesScreenshot interface, which every real WebDriver implementation (Chrome, Firefox, Edge) supports.

The TakesScreenshot Interface

WebDriver itself doesn't declare a screenshot method — you cast the driver to TakesScreenshot and call getScreenshotAs(), passing the output format you want.

WebDriver driver = new ChromeDriver();
driver.get("https://recipebox.example.com/recipes/add");

// Cast the driver to TakesScreenshot, then capture as a File
File screenshotFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);

// Selenium writes it to a temp file; you must copy it somewhere permanent
File destination = new File("screenshots/recipe-saved.png");
FileUtils.copyFile(screenshotFile, destination); // e.g. org.apache.commons.io.FileUtils
Note: getScreenshotAs(OutputType.FILE) returns a temporary file that Selenium deletes at JVM shutdown. Always copy it to a real path immediately if you want to keep it. OutputType.BYTES and OutputType.BASE64 are also available — handy for attaching a screenshot directly into an HTML test report instead of writing a separate file.

Saving with a Timestamped Filename

Overwriting the same screenshot.png on every run makes it useless for debugging a specific failure. Timestamp the filename so every capture is kept.

public static String takeScreenshot(WebDriver driver, String label) throws IOException {
    String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
    String fileName = label + "_" + timestamp + ".png";

    File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
    File dest = new File("screenshots/" + fileName);
    FileUtils.copyFile(src, dest);

    return dest.getAbsolutePath();
}

Capturing the "Recipe Saved!" Confirmation

Some moments are worth capturing even on a passing test — proof that the feature actually worked, not just that an assertion string matched. After RecipeBox saves a new recipe, it shows a confirmation banner reading "Recipe saved!". Here's a test that waits for it and screenshots it as evidence.

@Test
public void savingRecipeShowsConfirmation() throws IOException {
    driver.findElement(By.id("recipeTitle")).sendKeys("Paneer Butter Masala");
    driver.findElement(By.id("categoryDropdown")).sendKeys("Main Course");
    driver.findElement(By.id("saveRecipeBtn")).click();

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    WebElement confirmation = wait.until(
        ExpectedConditions.visibilityOfElementLocated(By.className("confirmation-banner"))
    );
    Assert.assertEquals(confirmation.getText(), "Recipe saved!");

    // Capture proof of the confirmation screen
    takeScreenshot(driver, "recipe_saved_confirmation");
}

Auto-Capturing a Screenshot on Failure

The pattern testers reach for the most: wrap the test body (or assertion) in a try/catch, and on any thrown exception or failed assertion, take a screenshot before re-throwing — so you never lose the failure evidence, and the test still reports as failed.

@Test
public void addRecipeAndCaptureFailures() {
    try {
        driver.findElement(By.id("recipeTitle")).sendKeys("Chicken Tikka Masala");
        driver.findElement(By.id("saveRecipeBtn")).click();

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        WebElement confirmation = wait.until(
            ExpectedConditions.visibilityOfElementLocated(By.className("confirmation-banner"))
        );
        Assert.assertEquals(confirmation.getText(), "Recipe saved!");

    } catch (Throwable t) {
        try {
            takeScreenshot(driver, "FAILURE_addRecipe");
        } catch (IOException ioEx) {
            System.err.println("Could not save failure screenshot: " + ioEx.getMessage());
        }
        throw t; // re-throw so TestNG still reports the test as failed
    }
}
Tip: In a real framework you'd usually hook this into a TestNG ITestListener's onTestFailure() method instead of wrapping every single test in try/catch — that way every test in the suite gets automatic failure screenshots for free. We'll cover TestNG listeners in more depth once you've seen the basics.
Next up: Time to formalize how these tests are structured and run — head to TestNG Basics to learn @Test, @BeforeMethod/@AfterMethod, and assertions.