I'm running Playwright tests for my web application. The tests execute successfully, but no video recordings are being generated. I've configured video recording in my playwright.config.js, but the handleTestVideo function in my afterEach hook always reports that no video was recorded.
export default defineConfig({
use: {
video: {
mode: 'on',
size: { width: 640, height: 480 }
},
},
});
see full code FYI
// @ts-check
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './playwright',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['list'], ['json', { outputFile: 'test-results.json' }]],
outputDir: 'test-results',
use: {
baseURL: 'http://localhost:8080',
trace: 'on-first-retry',
screenshot: 'on',
video: {
mode: 'on',
size: { width: 640, height: 480 }
},
headless: true,
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
webServer: {
command: 'python3 manage.py runserver',
url: 'http://localhost:8080',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000
},
preserveOutput: 'never',
});
test('Validate message send form and capture screenshots', async ({ page }) => {
// Test actions, pass as expected...
});
test.afterEach(async ({ }, testInfo) => {
await handleTestVideo(testInfo);
});
Looking at the official playwright docs it should be unnecessary to use any utils, the solution should be much more simple than this.
I am aware that this debugging step is almost worthless (be gentle on me I am quite junior...) But my main point is that there is no video.
export async function handleTestVideo(testInfo) {
console.log('Handling test video...');
console.log('Test status:', testInfo.status);
// Wait for the video to be ready
await testInfo.attachments.find(attachment => attachment.name === 'video');
console.log('Video attachment found:', testInfo.attachments.find(attachment => attachment.name === 'video'));
const videoAttachment = testInfo.attachments.find(attachment => attachment.name === 'video');
console.log('Video attachment:', videoAttachment);
if (videoAttachment) {
const videoPath = videoAttachment.path;
console.log('Video path:', videoPath);
if (videoPath) {
const videoDir = path.join('playwright', 'videos', testInfo.title);
const newPath = path.join(videoDir, `${testInfo.title}.webm`);
// Ensure the directory exists
fs.mkdirSync(videoDir, { recursive: true });
// Wait for the video file to be fully written
await new Promise(resolve => setTimeout(resolve, 1000));
// Move the video file
fs.renameSync(videoPath, newPath);
console.log(`Video saved to: ${newPath}`);
} else {
console.log('Video path is undefined.');
}
} else {
console.log('No video attachment found for this test.');
}
}
[test logs as expected, pass]
Handling test video...
Test status: passed
Video attachment found: undefined
Video attachment: undefined
No video attachment found for this test
import { test, expect } from '@playwright/test';
import { login, captureScreenshot, handleTestVideo, baseURL, handleFailedTestVideo } from './testUtils';
import path from 'path';
const BASE_URL = baseURL;
test.describe('Message Detail Send Form Validation', () => {
test('Validate message send form and capture screenshots', async ({ page }) => {
await login(page);
console.log(`Navigating to ${BASE_URL}/messages/1`);
await page.goto(`${BASE_URL}/messages/1`);
console.log('Starting message send form validation tests');
await page.waitForSelector('form', { state: 'visible' });
// Test: Empty submission
console.log('Testing empty submission');
await page.click('button:has-text("Send")');
// Wait for the alert to appear
await page.waitForSelector('.alert-warning', { state: 'visible', timeout: 5000 });
const alertText = await page.textContent('.alert-warning');
expect(alertText).toContain('Please enter a message or upload an image');
await captureScreenshot(page, 'message-send', '1-empty-submission');
console.log('Captured screenshot for empty submission');
// Test: Message too long
console.log('Testing message too long');
await page.fill('textarea[name="content"]', 'a'.repeat(1001));
await page.click('button:has-text("Send")');
// Wait for the alert to appear
await page.waitForSelector('.alert-warning', { state: 'visible', timeout: 5000 });
const longMessageAlertText = await page.textContent('.alert-warning');
expect(longMessageAlertText).toContain('Message should not exceed 1000 characters');
await captureScreenshot(page, 'message-send', '2-message-too-long');
console.log('Captured screenshot for message too long');
// Test: Message with only spaces
console.log('Testing message with only spaces');
await page.fill('textarea[name="content"]', ' ');
await page.click('button:has-text("Send")');
// Wait for the alert to appear
await page.waitForSelector('.alert-warning', { state: 'visible', timeout: 5000 });
const spacesAlertText = await page.textContent('.alert-warning');
expect(spacesAlertText).toContain('Please enter a message or upload an image');
await captureScreenshot(page, 'message-send', '3-spaces-only');
console.log('Captured screenshot for spaces-only message');
// Test: Image file too large
console.log('Testing image file too large');
const largeImagePath = path.join(__dirname, 'test-assets', 'large-image.jpg');
await page.setInputFiles('input[type="file"]', largeImagePath);
// Wait for the alert to appear
await page.waitForSelector('.alert-warning', { state: 'visible', timeout: 5000 });
const largeImageAlertText = await page.textContent('.alert-warning');
expect(largeImageAlertText).toContain('Image file size should not exceed 5MB');
await captureScreenshot(page, 'message-send', '4-image-too-large');
console.log('Captured screenshot for image too large');
// Test: invalid image type
console.log('Testing invalid image type');
const invalidImagePath = path.join(__dirname, 'test-assets', 'test-file.txt');
await page.setInputFiles('input[type="file"]', invalidImagePath);
// Wait for the alert to appear
await page.waitForSelector('.alert-warning', { state: 'visible', timeout: 5000 });
const invalidImageAlertText = await page.textContent('.alert-warning');
expect(invalidImageAlertText).toContain('Only JPEG, PNG, and GIF images are allowed');
await captureScreenshot(page, 'message-send', '5-invalid-image-type');
console.log('Captured screenshot for invalid image type');
console.log('Message send form validation tests completed');
// Test: Valid image with no message
console.log('Testing valid image with no message');
const validImagePath = path.join(__dirname, 'test-assets', 'test-image.jpg');
await page.fill('textarea[name="content"]', '');
await page.setInputFiles('input[type="file"]', validImagePath);
await captureScreenshot(page, 'message-send', '6.1-valid-image-no-message-preview');
await page.click('button:has-text("Send")');
await page.waitForTimeout(200);
// Check if there are no error messages
const errorMessages = await page.$$('.alert-warning');
expect(errorMessages.length).toBe(0);
await page.waitForTimeout(2000);
await captureScreenshot(page, 'message-send', '6-valid-image-no-message');
console.log('Captured screenshot for valid image with no message');
// Test: Valid message with no image
console.log('Testing valid message with no image');
await page.fill('textarea[name="content"]', 'Hello, world!');
await page.click('button:has-text("Send")');
// wait for network idle
await page.waitForTimeout(2000);
await captureScreenshot(page, 'message-send', '7-valid-message-no-image');
});
});
test.afterEach(async ({ }, testInfo) => {
await handleTestVideo(testInfo);
});
Has anyone out there got video recording in playwright and how did they do it? What am I missing?
docs suggest it is very simple - my screenshots are working perfectly.
https://playwright.dev/docs/videos#record-video
This can be fixed in playwright.config.ts and there's no need for a custom handleTestVideo method. You don't need afterAll either, unless you created the browser context yourself.
When using the default JavaScript/TypeScript test runner, Playwright will always record videos when playwright.config.ts includes this:
use: {
video: {
mode: 'on',
},
contextOptions: {
recordVideo: {
dir: 'test-results',
}
}
}
The playwright documentation doesn't make it clear that you need to add recordVideo under contextOptions, but it won't work without it. I recommend using mode: 'retain-on-failure' instead of mode: 'on' so that it deletes your videos whenever the test passes.
https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-video
https://playwright.dev/docs/api/class-testoptions#test-options-context-options https://playwright.dev/docs/videos
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With