Why Capture Visual Evidence?
When a test fails, the most common question is “what did the screen look like when it failed?” A stack trace tells you which assertion failed, but a screenshot shows the actual state of the application at that moment. Perhaps the element was hidden behind a modal. Perhaps a loading spinner was still visible. Perhaps the page showed an unexpected error message. Screenshots answer these questions instantly.
Video recording goes further — it shows the entire sequence of events leading to the failure. You can watch the test navigate, click, type, and see exactly where things went wrong.
Screenshot Capture
Playwright
// Screenshot on failure (built-in)
// playwright.config.ts
export default defineConfig({
use: {
screenshot: 'only-on-failure', // 'on', 'off', or 'only-on-failure'
},
});
// Manual screenshot in test
test('checkout flow', async ({ page }) => {
await page.goto('/cart');
await page.screenshot({ path: 'screenshots/cart.png' });
await page.click('#checkout');
await page.screenshot({ path: 'screenshots/checkout.png', fullPage: true });
});
// Element screenshot
const header = page.locator('header');
await header.screenshot({ path: 'screenshots/header.png' });
Cypress
// Cypress captures screenshots on failure by default
// cypress.config.js
module.exports = defineConfig({
screenshotOnRunFailure: true,
screenshotsFolder: 'cypress/screenshots',
});
// Manual screenshot
cy.screenshot('cart-page');
cy.get('.header').screenshot('header-component');
Selenium
// Manual screenshot
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshot, new File("screenshots/failure.png"));
// In TestNG listener for automatic capture on failure
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = getDriverFromResult(result);
File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
String path = "screenshots/" + result.getName() + "_" + System.currentTimeMillis() + ".png";
FileUtils.copyFile(src, new File(path));
}
Video Recording
Playwright
// playwright.config.ts
export default defineConfig({
use: {
video: 'retain-on-failure', // 'on', 'off', 'on-first-retry', 'retain-on-failure'
},
});
// Videos saved to test-results/ directory
Cypress
// cypress.config.js
module.exports = defineConfig({
video: true, // Record all test runs
videoCompression: 32, // Compression quality (0-51)
videosFolder: 'cypress/videos',
});
// Videos are saved per spec file
Playwright Trace Viewer
Playwright’s trace viewer captures far more than video — it records DOM snapshots, network requests, console logs, and action details:
use: {
trace: 'on-first-retry', // Capture trace when retrying failed tests
}
// View trace after test run
// npx playwright show-trace test-results/trace.zip
CI/CD Integration
GitHub Actions Artifacts
- name: Run Playwright tests
run: npx playwright test
continue-on-error: true
- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: test-evidence
path: |
test-results/
playwright-report/
retention-days: 30
Jenkins Artifacts
pipeline {
stages {
stage('Test') {
steps {
sh 'npx playwright test'
}
post {
always {
archiveArtifacts artifacts: 'test-results/**/*', allowEmptyArchive: true
publishHTML([reportDir: 'playwright-report', reportFiles: 'index.html'])
}
}
}
}
}
Step-Level Evidence
For audit trails and debugging, capture evidence at each significant step:
test('complete purchase flow', async ({ page }) => {
await page.goto('/products');
await page.screenshot({ path: 'evidence/01-products-page.png' });
await page.click('[data-testid="add-to-cart"]');
await page.screenshot({ path: 'evidence/02-added-to-cart.png' });
await page.goto('/cart');
await page.screenshot({ path: 'evidence/03-cart-page.png' });
await page.click('#checkout');
await page.fill('#card-number', '4242424242424242');
await page.screenshot({ path: 'evidence/04-payment-form.png' });
await page.click('#submit-payment');
await expect(page.locator('.confirmation')).toBeVisible();
await page.screenshot({ path: 'evidence/05-confirmation.png' });
});
Integration with Allure Reports
@Attachment(value = "Page Screenshot", type = "image/png")
public byte[] captureScreen() {
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
}
@Attachment(value = "API Response", type = "application/json")
public String attachApiResponse(Response response) {
return response.body().asString();
}
Best Practices
- Always capture on failure — this is the minimum requirement
- Capture full page screenshots — partial screenshots miss context
- Name files descriptively — include test name, step, and timestamp
- Set retention policies — old screenshots consume storage; 30 days is typical
- Compress videos — balance quality with file size for CI storage
- Use Playwright traces — they provide far richer debugging data than video alone
Exercises
Exercise 1: Screenshot Strategy
Configure automatic screenshots on failure and at key steps. Run tests, review evidence, and verify it provides enough context to diagnose failures.
Exercise 2: Video Recording
Enable video recording in Playwright or Cypress. Introduce a deliberate failure and use the video to diagnose the issue without looking at code.
Exercise 3: CI/CD Evidence Pipeline
Configure CI pipeline to capture screenshots, videos, and traces. Upload as artifacts with 30-day retention. Create a process for reviewing evidence after nightly test runs.