Screenshots and Videos
Capture screenshots and record videos during test execution for debugging and reporting.
Screenshots and videos are invaluable for debugging test failures, documenting issues, and generating test reports.
Basic Screenshot
import org.openqa.selenium.OutputType;import org.openqa.selenium.TakesScreenshot;import java.io.File;import java.nio.file.Files;
// Take screenshotFile screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// Save to fileFiles.copy(screenshot.toPath(), new File("screenshot.png").toPath());
// Or get as bytes for embedding in reportsbyte[] screenshotBytes = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
// Or get as Base64 stringString screenshotBase64 = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BASE64);# Take screenshot and save to filedriver.save_screenshot("screenshot.png")
# Or get as Base64 stringscreenshot_base64 = driver.get_screenshot_as_base64()
# Or get as bytesscreenshot_bytes = driver.get_screenshot_as_png()const fs = require('fs');
// Take screenshot as Base64const screenshot = await driver.takeScreenshot();
// Save to filefs.writeFileSync('screenshot.png', screenshot, 'base64');
// Or use directly as Base64 string for reportsusing OpenQA.Selenium;using System.IO;
// Take screenshotScreenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot();
// Save to filescreenshot.SaveAsFile("screenshot.png");
// Or get as Base64 stringstring screenshotBase64 = screenshot.AsBase64EncodedString;
// Or get as bytesbyte[] screenshotBytes = screenshot.AsByteArray;Screenshot of Specific Element
Capture just a specific element instead of the full page:
import org.openqa.selenium.WebElement;
// Find the elementWebElement element = driver.findElement(By.id("target-element"));
// Take screenshot of just this elementFile elementScreenshot = element.getScreenshotAs(OutputType.FILE);Files.copy(elementScreenshot.toPath(), new File("element.png").toPath());# Find the elementelement = driver.find_element(By.ID, "target-element")
# Take screenshot of just this elementelement.screenshot("element.png")// Find the elementconst element = await driver.findElement(By.id('target-element'));
// Take screenshot of just this element (Selenium 4)const elementScreenshot = await element.takeScreenshot();fs.writeFileSync('element.png', elementScreenshot, 'base64');// Find the elementIWebElement element = driver.FindElement(By.Id("target-element"));
// Take screenshot of just this elementScreenshot elementScreenshot = ((ITakesScreenshot)element).GetScreenshot();elementScreenshot.SaveAsFile("element.png");Full Page Screenshot
Capture the entire scrollable page, not just the viewport:
import org.openqa.selenium.firefox.FirefoxDriver;
// Firefox supports full page screenshots nativelyFirefoxDriver firefoxDriver = (FirefoxDriver) driver;File fullPageScreenshot = firefoxDriver.getFullPageScreenshotAs(OutputType.FILE);Files.copy(fullPageScreenshot.toPath(), new File("fullpage.png").toPath());# Firefox supports full page screenshots natively# driver must be Firefox
# Using Firefox's full page screenshotfull_screenshot = driver.get_full_page_screenshot_as_png()with open("fullpage.png", "wb") as f: f.write(full_screenshot)// For Chrome, use CDP (Chrome DevTools Protocol)// Firefox has native support
// Chrome full page via CDPconst cdp = await driver.createCDPConnection('page');const screenshot = await driver.executeScript(` return await new Promise((resolve) => { chrome.runtime.sendMessage({action: 'captureFullPage'}, resolve); });`);// Firefox supports full page screenshotsvar firefoxDriver = (FirefoxDriver)driver;Screenshot fullPage = firefoxDriver.GetFullPageScreenshot();fullPage.SaveAsFile("fullpage.png");Screenshot on Test Failure
Automatically capture screenshots when tests fail:
// TestNG listener for screenshot on failureimport org.testng.ITestListener;import org.testng.ITestResult;
public class ScreenshotListener implements ITestListener {
@Override public void onTestFailure(ITestResult result) { WebDriver driver = ((BaseTest) result.getInstance()).getDriver();
if (driver != null) { String testName = result.getName(); String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String filename = "screenshots/" + testName + "_" + timestamp + ".png";
try { File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); Files.createDirectories(Paths.get("screenshots")); Files.copy(screenshot.toPath(), Paths.get(filename)); System.out.println("Screenshot saved: " + filename); } catch (IOException e) { e.printStackTrace(); } } }}# conftest.py - pytest fixture for screenshot on failureimport pytestfrom datetime import datetimeimport os
@pytest.hookimpl(tryfirst=True, hookwrapper=True)def pytest_runtest_makereport(item, call): outcome = yield report = outcome.get_result() setattr(item, f"rep_{report.when}", report)
@pytest.fixture(autouse=True)def screenshot_on_failure(request, driver): yield # Check if test failed if hasattr(request.node, "rep_call") and request.node.rep_call.failed: os.makedirs("screenshots", exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"screenshots/{request.node.name}_{timestamp}.png" driver.save_screenshot(filename) print(f"Screenshot saved: {filename}")// Mocha afterEach hook for screenshot on failureconst fs = require('fs');const path = require('path');
afterEach(async function() { if (this.currentTest.state === 'failed') { const screenshotDir = 'screenshots'; if (!fs.existsSync(screenshotDir)) { fs.mkdirSync(screenshotDir); }
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = path.join( screenshotDir, `${this.currentTest.title}_${timestamp}.png` );
const screenshot = await driver.takeScreenshot(); fs.writeFileSync(filename, screenshot, 'base64'); console.log(`Screenshot saved: ${filename}`); }});// NUnit screenshot on failure using TearDown[TearDown]public void TakeScreenshotOnFailure(){ if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) { string screenshotDir = "screenshots"; Directory.CreateDirectory(screenshotDir);
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string testName = TestContext.CurrentContext.Test.Name; string filename = Path.Combine(screenshotDir, $"{testName}_{timestamp}.png");
Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); screenshot.SaveAsFile(filename); TestContext.WriteLine($"Screenshot saved: {filename}"); }}Video Recording
Using Docker Selenium with Video
version: '3.8'
services: chrome: image: selenium/standalone-chrome:latest shm_size: 2gb ports: - "4444:4444" - "7900:7900" # VNC viewer
video: image: selenium/video:latest depends_on: - chrome environment: - DISPLAY_CONTAINER_NAME=chrome - FILE_NAME=test_recording.mp4 volumes: - ./videos:/videosUsing FFmpeg for Recording
import java.io.IOException;
public class VideoRecorder { private Process ffmpegProcess;
public void startRecording(String filename) throws IOException { // Linux/Mac - record X11 display String[] command = { "ffmpeg", "-y", "-video_size", "1920x1080", "-framerate", "25", "-f", "x11grab", "-i", ":0.0", filename };
ffmpegProcess = Runtime.getRuntime().exec(command); }
public void stopRecording() { if (ffmpegProcess != null) { ffmpegProcess.destroy(); } }}import subprocessimport signal
class VideoRecorder: def __init__(self): self.process = None
def start_recording(self, filename): # Linux - record X11 display command = [ "ffmpeg", "-y", "-video_size", "1920x1080", "-framerate", "25", "-f", "x11grab", "-i", ":0.0", filename ] self.process = subprocess.Popen(command)
def stop_recording(self): if self.process: self.process.send_signal(signal.SIGINT) self.process.wait()
# Usagerecorder = VideoRecorder()recorder.start_recording("test_video.mp4")# ... run tests ...recorder.stop_recording()const { spawn } = require('child_process');
class VideoRecorder { constructor() { this.process = null; }
startRecording(filename) { // Linux - record X11 display this.process = spawn('ffmpeg', [ '-y', '-video_size', '1920x1080', '-framerate', '25', '-f', 'x11grab', '-i', ':0.0', filename ]); }
stopRecording() { if (this.process) { this.process.stdin.write('q'); this.process.kill('SIGINT'); } }}
// Usageconst recorder = new VideoRecorder();recorder.startRecording('test_video.mp4');// ... run tests ...recorder.stopRecording();using System.Diagnostics;
public class VideoRecorder{ private Process ffmpegProcess;
public void StartRecording(string filename) { // Windows - use GDI grab var startInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-y -f gdigrab -framerate 25 -i desktop {filename}", UseShellExecute = false, CreateNoWindow = true };
ffmpegProcess = Process.Start(startInfo); }
public void StopRecording() { ffmpegProcess?.Kill(); }}Screenshot Comparison
Compare screenshots to detect visual changes:
import javax.imageio.ImageIO;import java.awt.image.BufferedImage;
public class ScreenshotComparison {
public static double compareImages(File baseline, File current) throws IOException { BufferedImage img1 = ImageIO.read(baseline); BufferedImage img2 = ImageIO.read(current);
if (img1.getWidth() != img2.getWidth() || img1.getHeight() != img2.getHeight()) { return 0.0; // Different dimensions }
long diff = 0; for (int y = 0; y < img1.getHeight(); y++) { for (int x = 0; x < img1.getWidth(); x++) { diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y)); } }
long maxDiff = 3L * 255 * img1.getWidth() * img1.getHeight(); return 100.0 * (1.0 - (double) diff / maxDiff); }
private static int pixelDiff(int rgb1, int rgb2) { int r1 = (rgb1 >> 16) & 0xff; int g1 = (rgb1 >> 8) & 0xff; int b1 = rgb1 & 0xff; int r2 = (rgb2 >> 16) & 0xff; int g2 = (rgb2 >> 8) & 0xff; int b2 = rgb2 & 0xff; return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2); }}from PIL import Imageimport math
def compare_images(baseline_path, current_path): """Compare two images and return similarity percentage.""" img1 = Image.open(baseline_path) img2 = Image.open(current_path)
if img1.size != img2.size: return 0.0 # Different dimensions
pixels1 = list(img1.getdata()) pixels2 = list(img2.getdata())
diff = sum( abs(p1[i] - p2[i]) for p1, p2 in zip(pixels1, pixels2) for i in range(3) # RGB channels )
max_diff = 3 * 255 * len(pixels1) similarity = 100.0 * (1.0 - diff / max_diff) return similarity
# Usagesimilarity = compare_images("baseline.png", "current.png")print(f"Similarity: {similarity:.2f}%")assert similarity > 99.0, "Visual regression detected!"const { PNG } = require('pngjs');const fs = require('fs');
function compareImages(baselinePath, currentPath) { const img1 = PNG.sync.read(fs.readFileSync(baselinePath)); const img2 = PNG.sync.read(fs.readFileSync(currentPath));
if (img1.width !== img2.width || img1.height !== img2.height) { return 0.0; // Different dimensions }
let diff = 0; for (let i = 0; i < img1.data.length; i += 4) { diff += Math.abs(img1.data[i] - img2.data[i]); // R diff += Math.abs(img1.data[i+1] - img2.data[i+1]); // G diff += Math.abs(img1.data[i+2] - img2.data[i+2]); // B }
const maxDiff = 3 * 255 * (img1.data.length / 4); return 100.0 * (1.0 - diff / maxDiff);}
// Usageconst similarity = compareImages('baseline.png', 'current.png');console.log(`Similarity: ${similarity.toFixed(2)}%`);using System.Drawing;
public static class ScreenshotComparison{ public static double CompareImages(string baselinePath, string currentPath) { using var img1 = new Bitmap(baselinePath); using var img2 = new Bitmap(currentPath);
if (img1.Width != img2.Width || img1.Height != img2.Height) return 0.0;
long diff = 0; for (int y = 0; y < img1.Height; y++) { for (int x = 0; x < img1.Width; x++) { Color c1 = img1.GetPixel(x, y); Color c2 = img2.GetPixel(x, y); diff += Math.Abs(c1.R - c2.R); diff += Math.Abs(c1.G - c2.G); diff += Math.Abs(c1.B - c2.B); } }
long maxDiff = 3L * 255 * img1.Width * img1.Height; return 100.0 * (1.0 - (double)diff / maxDiff); }}Organized Screenshot Storage
import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;
public class ScreenshotManager { private static final String BASE_DIR = "screenshots";
public static String captureScreenshot(WebDriver driver, String testName, String step) { LocalDateTime now = LocalDateTime.now(); String date = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); String time = now.format(DateTimeFormatter.ofPattern("HH-mm-ss"));
// Create organized directory structure String dir = String.format("%s/%s/%s", BASE_DIR, date, testName); Files.createDirectories(Paths.get(dir));
// Descriptive filename String filename = String.format("%s/%s_%s.png", dir, time, step);
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); Files.copy(screenshot.toPath(), Paths.get(filename));
return filename; }}
// UsageScreenshotManager.captureScreenshot(driver, "LoginTest", "01_homepage_loaded");ScreenshotManager.captureScreenshot(driver, "LoginTest", "02_credentials_entered");ScreenshotManager.captureScreenshot(driver, "LoginTest", "03_login_successful");from datetime import datetimeimport os
class ScreenshotManager: BASE_DIR = "screenshots"
@classmethod def capture(cls, driver, test_name, step): now = datetime.now() date = now.strftime("%Y-%m-%d") time = now.strftime("%H-%M-%S")
# Create organized directory structure dir_path = f"{cls.BASE_DIR}/{date}/{test_name}" os.makedirs(dir_path, exist_ok=True)
# Descriptive filename filename = f"{dir_path}/{time}_{step}.png" driver.save_screenshot(filename)
return filename
# UsageScreenshotManager.capture(driver, "LoginTest", "01_homepage_loaded")ScreenshotManager.capture(driver, "LoginTest", "02_credentials_entered")ScreenshotManager.capture(driver, "LoginTest", "03_login_successful")const fs = require('fs');const path = require('path');
class ScreenshotManager { static BASE_DIR = 'screenshots';
static async capture(driver, testName, step) { const now = new Date(); const date = now.toISOString().split('T')[0]; const time = now.toTimeString().split(' ')[0].replace(/:/g, '-');
// Create organized directory structure const dirPath = path.join(this.BASE_DIR, date, testName); fs.mkdirSync(dirPath, { recursive: true });
// Descriptive filename const filename = path.join(dirPath, `${time}_${step}.png`);
const screenshot = await driver.takeScreenshot(); fs.writeFileSync(filename, screenshot, 'base64');
return filename; }}
// Usageawait ScreenshotManager.capture(driver, 'LoginTest', '01_homepage_loaded');await ScreenshotManager.capture(driver, 'LoginTest', '02_credentials_entered');await ScreenshotManager.capture(driver, 'LoginTest', '03_login_successful');using System;using System.IO;
public class ScreenshotManager{ private const string BaseDir = "screenshots";
public static string Capture(IWebDriver driver, string testName, string step) { DateTime now = DateTime.Now; string date = now.ToString("yyyy-MM-dd"); string time = now.ToString("HH-mm-ss");
// Create organized directory structure string dirPath = Path.Combine(BaseDir, date, testName); Directory.CreateDirectory(dirPath);
// Descriptive filename string filename = Path.Combine(dirPath, $"{time}_{step}.png");
Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); screenshot.SaveAsFile(filename);
return filename; }}
// UsageScreenshotManager.Capture(driver, "LoginTest", "01_homepage_loaded");ScreenshotManager.Capture(driver, "LoginTest", "02_credentials_entered");ScreenshotManager.Capture(driver, "LoginTest", "03_login_successful");Best Practices
| Practice | Description |
|---|---|
| Capture on failure | Automatic screenshots when tests fail |
| Organized naming | Date/test/step structure for easy navigation |
| Element screenshots | Focus on relevant elements |
| Video for complex flows | Record full sessions for debugging |
| Clean up old files | Remove screenshots older than X days |
| Attach to reports | Embed in HTML test reports |
Next Steps
- Debugging Tips - General debugging strategies
- Explicit Waits - Wait before capturing
- pytest Integration - Integrate with reports
Copy-paste recipes for this topic
- Highlight an Element for Debugging and Screenshots
Draw a temporary outline around the element a step is about to use so screenshots and videos show what the test targeted.
- Screenshot a Single Element to a File
Capture just one component, named by test and timestamp, ready for a visual comparison tool or a bug report.
- Take a Screenshot on Test Failure Automatically
A framework hook for JUnit 5, pytest, Mocha and NUnit that saves a screenshot and page source only when a test fails.