Screenshots and Videos
Capture screenshots and record videos during test execution for debugging and reporting.
Selenium 3 & 4 Stable
Screenshots and videos are invaluable for debugging test failures, documenting issues, and generating test reports.
Basic Screenshot
Take a Screenshot
Selenium 3 & 4 Stable
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:
Element Screenshot
Selenium 4 Stable
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:
Full Page Screenshot (Firefox)
Selenium 4 Medium
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:
Screenshot on Failure (pytest)
Selenium 3 & 4 Stable
// 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
Record with FFmpeg
Selenium 3 & 4 Medium
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:
Basic Screenshot Comparison
Selenium 3 & 4 Stable
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
Organized Screenshot Naming
Selenium 3 & 4 Stable
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