🎯 Test Output and Artifacts in Playwright
The Detective’s Evidence Locker 🔍
Imagine you’re a detective solving a mystery. After every investigation, you collect evidence — photos, fingerprints, notes, and recordings. You put them in a special evidence locker so you can look back later and understand exactly what happened.
Playwright works the same way!
When your tests run, Playwright can save artifacts — screenshots, videos, logs, and traces. These go into a special folder called the test output directory. If a test fails, you can open the locker and see exactly what went wrong!
🗂️ Test Output Directory
What Is It?
The test output directory is like a filing cabinet for your test evidence. Every time Playwright collects artifacts, it puts them here.
By default, this folder is called test-results/ and lives in your project root.
See It In Action
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
outputDir: './my-test-evidence',
});
What happens:
- All screenshots, videos, and traces go into
./my-test-evidence/ - Each test gets its own subfolder!
Real Life Example 🏠
Think of a school where every student has their own locker:
my-test-evidence/
├── login-test-chromium/
│ ├── screenshot.png
│ └── video.webm
├── cart-test-firefox/
│ └── trace.zip
Each test (student) has its own space (locker) inside the main cabinet!
📦 Artifact Collection
What Are Artifacts?
Artifacts are the evidence your tests collect:
| Artifact | What It Does | Like In Real Life |
|---|---|---|
| Screenshot | A picture of the page | Taking a photo |
| Video | Recording of the whole test | Security camera |
| Trace | Step-by-step replay | A detailed diary |
Collecting Screenshots
import { test, expect } from '@playwright/test';
test('take a photo', async ({ page }) => {
await page.goto('https://example.com');
// Take a screenshot!
await page.screenshot({
path: 'my-screenshot.png'
});
});
Automatic Collection (The Easy Way!)
// playwright.config.ts
export default defineConfig({
use: {
// Screenshot on failure
screenshot: 'only-on-failure',
// Record video always
video: 'on',
// Save trace on first retry
trace: 'on-first-retry',
},
});
Options explained:
'off'— Never collect'on'— Always collect'only-on-failure'— Only when test fails'on-first-retry'— Only on retry attempts
graph TD A["Test Runs"] --> B{Did it pass?} B -->|Yes| C["Keep going!"] B -->|No| D["Collect Evidence"] D --> E["Screenshot 📸"] D --> F["Video 🎬"] D --> G["Trace 📋"]
🔧 The test.info() Method
Your Test’s Memory Book
test.info() is like a personal notebook that each test carries. You can:
- Read information about the test
- Add notes and attachments
- Check the test’s status
Basic Usage
import { test } from '@playwright/test';
test('my test', async ({ page }) => {
// Get test info
const info = test.info();
// What's the test called?
console.log(info.title);
// Output: "my test"
// What file is it in?
console.log(info.file);
// How long can it run?
console.log(info.timeout);
});
Adding Your Own Attachments
test('save evidence', async ({ page }) => {
await page.goto('https://example.com');
// Attach a screenshot to the report
const screenshot = await page.screenshot();
await test.info().attach('homepage', {
body: screenshot,
contentType: 'image/png',
});
});
Attaching Text Notes
test('save notes', async ({ page }) => {
const apiResponse = await page.request
.get('/api/data');
const data = await apiResponse.text();
// Attach the API response as a note
await test.info().attach('api-response', {
body: data,
contentType: 'text/plain',
});
});
Why do this? When you view your test report, you’ll see these attachments right there — like sticky notes in your evidence file!
🏷️ Test Metadata
Labels for Your Tests
Metadata is like putting stickers on your test files:
- “This is a smoke test” 🔥
- “This is slow” 🐢
- “This tests login” 🔐
Adding Annotations
test('login test',
{ tag: ['@smoke', '@auth'] },
async ({ page }) => {
// Your test code here
});
Built-in Annotation Types
test('my test', async ({ page }) => {
// Mark as slow (3x timeout)
test.slow();
// Skip this test
test.skip();
// Mark as failing (expected to fail)
test.fail();
// Add a custom note
test.info().annotations.push({
type: 'issue',
description: 'Bug #123',
});
});
Conditional Annotations
test('windows only', async ({ page }) => {
// Skip on Mac and Linux
test.skip(
process.platform !== 'win32',
'Windows only feature'
);
// Now test the feature...
});
graph TD A["Test Starts"] --> B{Check Conditions} B -->|Skip?| C["Mark Skipped ⏭️"] B -->|Slow?| D["Extend Timeout 🐢"] B -->|Run!| E["Execute Test ▶️"] E --> F["Add Annotations 🏷️"]
Reading Metadata
test('check my labels', async ({ page }) => {
const info = test.info();
// See all annotations
console.log(info.annotations);
// Check tags
console.log(info.tags);
// ['@smoke', '@auth']
});
🖥️ Browser Console Logs
Listening to the Browser Talk
The browser’s console is like a chatty friend. It tells you:
- Errors and warnings ⚠️
- Debug messages 📝
- What JavaScript is doing
Capturing Console Logs
test('hear the browser', async ({ page }) => {
// Listen for ALL console messages
page.on('console', msg => {
console.log(`Browser says: ${msg.text()}`);
});
await page.goto('https://example.com');
});
Filtering by Type
test('errors only', async ({ page }) => {
const errors = [];
page.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
await page.goto('https://example.com');
// Check no errors happened
expect(errors).toHaveLength(0);
});
Console Message Types
| Type | What It Means | Example |
|---|---|---|
log |
Regular message | console.log() |
error |
Something broke! | console.error() |
warning |
Heads up! | console.warn() |
info |
FYI | console.info() |
Saving Console Logs as Artifacts
test('save all logs', async ({ page }) => {
const logs = [];
page.on('console', msg => {
logs.push({
type: msg.type(),
text: msg.text(),
time: new Date().toISOString(),
});
});
await page.goto('https://example.com');
// Attach logs to report
await test.info().attach('console-logs', {
body: JSON.stringify(logs, null, 2),
contentType: 'application/json',
});
});
graph TD A["Page Loads"] --> B["Browser Console"] B --> C{Message Type?} C -->|log| D["📝 Info"] C -->|error| E["❌ Error"] C -->|warning| F["⚠️ Warning"] D --> G["Save to Array"] E --> G F --> G G --> H["Attach to Report"]
🎯 Putting It All Together
Here’s a complete example using everything we learned:
import { test, expect } from '@playwright/test';
test('complete detective work',
{ tag: ['@smoke'] },
async ({ page }) => {
const consoleMessages = [];
// 1. Listen to browser console
page.on('console', msg => {
consoleMessages.push(msg.text());
});
// 2. Go to the page
await page.goto('https://example.com');
// 3. Take a screenshot
const screenshot = await page.screenshot();
// 4. Attach it using test.info()
await test.info().attach('evidence', {
body: screenshot,
contentType: 'image/png',
});
// 5. Attach console logs
await test.info().attach('browser-logs', {
body: consoleMessages.join('\n'),
contentType: 'text/plain',
});
// 6. Add metadata annotation
test.info().annotations.push({
type: 'note',
description: 'Collected all evidence!',
});
});
🚀 Quick Summary
| Concept | What It Does | Remember As |
|---|---|---|
| Output Directory | Where artifacts go | Filing cabinet 🗄️ |
| Artifact Collection | Screenshots, videos, traces | Evidence collection 📦 |
| test.info() | Test’s personal notebook | Memory book 📓 |
| Test Metadata | Labels and annotations | Sticky notes 🏷️ |
| Console Logs | Browser’s messages | Chatty friend 💬 |
You’re now a test detective! 🕵️♀️
Every time a test fails, you’ll have all the evidence you need to solve the mystery and fix the bug!
