diff --git a/src/test/cli.ts b/src/test/cli.ts index 7ba46a53a6..fe8c8ad3d2 100644 --- a/src/test/cli.ts +++ b/src/test/cli.ts @@ -28,11 +28,11 @@ const builtinReporters = ['list', 'line', 'dot', 'json', 'junit', 'null']; const tsConfig = 'playwright.config.ts'; const jsConfig = 'playwright.config.js'; const defaultConfig: Config = { - preserveOutput: process.env.CI ? 'failures-only' : 'always', + preserveOutput: 'failures-only', reporter: [ [defaultReporter] ], reportSlowTests: { max: 5, threshold: 15000 }, timeout: defaultTimeout, - updateSnapshots: process.env.CI ? 'none' : 'missing', + updateSnapshots: 'missing', workers: Math.ceil(require('os').cpus().length / 2), }; diff --git a/src/test/index.ts b/src/test/index.ts index 2389bdcf02..d136a986eb 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -import type { LaunchOptions, BrowserContextOptions, Page } from '../../types/types'; +import type { LaunchOptions, BrowserContextOptions, Page, ViewportSize } from '../../types/types'; import type { TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions } from '../../types/test'; import { rootTestType } from './testType'; import { createGuid, removeFolders } from '../utils/utils'; @@ -80,16 +80,18 @@ export const test = _baseTest.extend { - const video = page.video(); - if (!video) + const v = page.video(); + if (!v) return; try { - const videoPath = await video.path(); + const videoPath = await v.path(); const fileName = path.basename(videoPath); - await video.saveAs(testInfo.outputPath(fileName)); + await v.saveAs(testInfo.outputPath(fileName)); } catch (e) { // Silent catch empty videos. } diff --git a/tests/playwright-test/config.spec.ts b/tests/playwright-test/config.spec.ts index 2daa4ffdd3..f27c9f2da8 100644 --- a/tests/playwright-test/config.spec.ts +++ b/tests/playwright-test/config.spec.ts @@ -330,7 +330,7 @@ test('should work with undefined values and base', async ({ runInlineTest }) => 'a.test.ts': ` const { test } = pwt; test('pass', async ({}, testInfo) => { - expect(testInfo.config.updateSnapshots).toBe('none'); + expect(testInfo.config.updateSnapshots).toBe('missing'); }); ` }, {}, { CI: '1' }); diff --git a/tests/playwright-test/golden.spec.ts b/tests/playwright-test/golden.spec.ts index a87f1b13a3..5de00e74d2 100644 --- a/tests/playwright-test/golden.spec.ts +++ b/tests/playwright-test/golden.spec.ts @@ -80,20 +80,6 @@ test('should write missing expectations locally', async ({runInlineTest}, testIn expect(data.toString()).toBe('Hello world'); }); -test('should not write missing expectations on CI', async ({runInlineTest}, testInfo) => { - const result = await runInlineTest({ - 'a.spec.js': ` - const { test } = pwt; - test('is a test', ({}) => { - expect('Hello world').toMatchSnapshot('snapshot.txt'); - }); - ` - }, {}, { CI: '1' }); - expect(result.exitCode).toBe(1); - expect(result.output).toContain('snapshot.txt is missing in snapshots'); - expect(fs.existsSync(testInfo.outputPath('a.spec.js-snapshots/snapshot.txt'))).toBe(false); -}); - test('should update expectations', async ({runInlineTest}, testInfo) => { const result = await runInlineTest({ 'a.spec.js-snapshots/snapshot.txt': `Hello world`, diff --git a/tests/playwright-test/playwright.spec.ts b/tests/playwright-test/playwright.spec.ts index 536b4f0a2b..94f5822e4f 100644 --- a/tests/playwright-test/playwright.spec.ts +++ b/tests/playwright-test/playwright.spec.ts @@ -15,7 +15,27 @@ */ import { test, expect } from './playwright-test-fixtures'; -import * as fs from 'fs'; +import fs from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; +import { Registry } from '../../src/utils/registry'; + +const registry = new Registry(path.join(__dirname, '..', '..')); +const ffmpeg = registry.executablePath('ffmpeg') || ''; + +export class VideoPlayer { + videoWidth: number; + videoHeight: number; + + constructor(fileName: string) { + var output = spawnSync(ffmpeg, ['-i', fileName, '-r', '25', `${fileName}-%03d.png`]).stderr.toString(); + const lines = output.split('\n'); + const streamLine = lines.find(l => l.trim().startsWith('Stream #0:0')); + const resolutionMatch = streamLine.match(/, (\d+)x(\d+),/); + this.videoWidth = parseInt(resolutionMatch![1], 10); + this.videoHeight = parseInt(resolutionMatch![2], 10); + } +} test('should respect viewport option', async ({ runInlineTest }) => { const result = await runInlineTest({ @@ -194,3 +214,29 @@ test('should work with video: retry-with-video', async ({ runInlineTest }, testI const videoFailRetry = fs.readdirSync(testInfo.outputPath('test-results', 'a-fail-chromium-retry1')).find(file => file.endsWith('webm')); expect(videoFailRetry).toBeTruthy(); }); + +test('should work with video size', async ({ runInlineTest, browserName }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.js': ` + module.exports = { + use: { video: { mode: 'on', size: { width: 220, height: 110 } } }, + preserveOutput: 'always', + }; + `, + 'a.test.ts': ` + const { test } = pwt; + test('pass', async ({ page }) => { + await page.setContent('
PASS
'); + await page.waitForTimeout(3000); + test.expect(1 + 1).toBe(2); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + const folder = testInfo.outputPath(`test-results/a-pass-${browserName}/`); + const [file] = fs.readdirSync(folder); + const videoPlayer = new VideoPlayer(path.join(folder, file)); + expect(videoPlayer.videoWidth).toBe(220); + expect(videoPlayer.videoHeight).toBe(110); +}); diff --git a/tests/playwright-test/test-output-dir.spec.ts b/tests/playwright-test/test-output-dir.spec.ts index 67a80d4b8d..96ddb961f1 100644 --- a/tests/playwright-test/test-output-dir.spec.ts +++ b/tests/playwright-test/test-output-dir.spec.ts @@ -213,7 +213,7 @@ test('should remove folders with preserveOutput=never', async ({ runInlineTest } expect(fs.existsSync(testInfo.outputPath('test-results', 'dir-my-test-test-1-retry2'))).toBe(false); }); -test('should not remove folders on non-CI', async ({ runInlineTest }, testInfo) => { +test('should preserve failed results', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ 'dir/my-test.spec.js': ` const { test } = pwt; @@ -223,13 +223,12 @@ test('should not remove folders on non-CI', async ({ runInlineTest }, testInfo) throw new Error('Give me retries'); }); `, - }, { 'retries': 2 }, { CI: '' }); + }, { 'retries': 2 }); expect(result.exitCode).toBe(0); expect(result.results.length).toBe(3); expect(fs.existsSync(testInfo.outputPath('test-results', 'dir-my-test-test-1'))).toBe(true); expect(fs.existsSync(testInfo.outputPath('test-results', 'dir-my-test-test-1-retry1'))).toBe(true); - expect(fs.existsSync(testInfo.outputPath('test-results', 'dir-my-test-test-1-retry2'))).toBe(true); }); diff --git a/types/test.d.ts b/types/test.d.ts index 4230f45105..323f2b1500 100644 --- a/types/test.d.ts +++ b/types/test.d.ts @@ -962,6 +962,15 @@ export type PlaywrightWorkerOptions = { launchOptions: LaunchOptions; }; +/** + * Video recording mode: + * - `off`: Do not record video. + * - `on`: Record video for each test. + * - `retain-on-failure`: Record video for each test, but remove all videos from successful test runs. + * - `retry-with-video`: Record video only when retrying a test. + */ +export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'retry-with-video'; + /** * Options available to configure each test. * - Set options in config: @@ -994,13 +1003,13 @@ export type PlaywrightTestOptions = { trace: 'off' | 'on' | 'retain-on-failure' | 'retry-with-trace'; /** - * Whether to record video for each test, off by default. - * - `off`: Do not record video. - * - `on`: Record video for each test. - * - `retain-on-failure`: Record video for each test, but remove all videos from successful test runs. - * - `retry-with-video`: Record video only when retrying a test. - */ - video: 'off' | 'on' | 'retain-on-failure' | 'retry-with-video'; + * Whether to record video for each test, off by default. + * - `off`: Do not record video. + * - `on`: Record video for each test. + * - `retain-on-failure`: Record video for each test, but remove all videos from successful test runs. + * - `retry-with-video`: Record video only when retrying a test. + */ + video: VideoMode | { mode: VideoMode, size: ViewportSize }; /** * Whether to automatically download all the attachments. Takes priority over `contextOptions`.