feat: add ffmpeg video recorder for e2e tests (#4686)

This commit is contained in:
Nico Domino 2024-08-15 11:54:10 +02:00 committed by GitHub
parent 00e3094795
commit 13a270613c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 107 additions and 6 deletions

View File

@ -29,6 +29,7 @@ jobs:
libayatana-appindicator3-dev \
libwebkit2gtk-4.0-dev \
webkit2gtk-driver \
ffmpeg \
xvfb
- name: Setup rust-toolchain stable
id: rust-toolchain

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ generated-do-not-edit/
.sentryclirc
.DS_Store
apps/desktop/e2e/videos/*.mp4
.env
.env.*

View File

@ -0,0 +1,82 @@
import path from 'node:path';
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process';
import type { Frameworks } from '@wdio/types';
function filePath({
test,
videoPath,
extension
}: {
test: Frameworks.Test;
videoPath: string;
extension: string;
}) {
return path.join(videoPath, `${fileName(test.parent)}-${fileName(test.title)}.${extension}`);
}
function fileName(title: string) {
return encodeURIComponent(title.trim().replace(/\s+/g, '-'));
}
export class TestRecorder {
ffmpeg!: ChildProcessWithoutNullStreams;
constructor() {}
stop() {
this.ffmpeg?.kill('SIGINT');
}
start(test: Frameworks.Test, videoPath: string) {
if (!videoPath) {
throw new Error('Video path not set. Set using setPath() function.');
}
if (process.env.DISPLAY && process.env.DISPLAY.startsWith(':')) {
const parsedPath = filePath({
test,
videoPath,
extension: 'mp4'
});
this.ffmpeg = spawn('ffmpeg', [
'-f',
'x11grab', // Grab the X11 display
'-video_size',
'1280x1024', // Video size
'-i',
process.env.DISPLAY, // Input file url
'-loglevel',
'error', // Log only errors
'-y', // Overwrite output files without asking
'-pix_fmt',
'yuv420p', // QuickTime Player support, "Use -pix_fmt yuv420p for compatibility with outdated media players"
parsedPath // Output file
]);
const logBuffer = function (buffer: Buffer, prefix: string) {
const lines = buffer.toString().trim().split('\n');
lines.forEach(function (line) {
console.log(prefix + line);
});
};
this.ffmpeg.stdout.on('data', (data: Buffer) => {
logBuffer(data, '[ffmpeg:stdout] ');
});
this.ffmpeg.stderr.on('data', (data: Buffer) => {
logBuffer(data, '[ffmpeg:error] ');
});
this.ffmpeg.on('close', (code: number, signal: string | unknown) => {
if (code !== null) {
console.log(`[ffmpeg:stdout] exited with code ${code}: ${videoPath}`);
}
if (signal !== null) {
console.log(`[ffmpeg:stdout] received signal ${signal}: ${videoPath}`);
}
});
}
}
}

View File

@ -8,6 +8,12 @@ CLI=${1:?The first argument is the GitButler CLI}
# Convert to absolute path
CLI=$(realpath "$CLI")
function setGitDefaults() {
git config user.email "test@example.com"
git config user.name "Test User"
git config init.defaultBranch master
}
function tick() {
if test -z "${tick+set}"; then
tick=1675176957
@ -20,7 +26,9 @@ function tick() {
}
tick
if [ ! -d "$TEMP_DIR" ]; then
mkdir "$TEMP_DIR"
fi
cd "$TEMP_DIR"
git init remote
@ -28,9 +36,7 @@ git init remote
(
cd remote
git config user.email "test@example.com"
git config user.name "Test User"
git config init.defaultBranch master
setGitDefaults
echo first >file
git add . && git commit -m "init"

View File

View File

@ -1,8 +1,10 @@
import { spawn, ChildProcess } from 'node:child_process';
import { TestRecorder } from './e2e/record.js';
import { spawn, type ChildProcess } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import type { Options } from '@wdio/types';
import type { Options, Frameworks } from '@wdio/types';
const videoRecorder = new TestRecorder();
let tauriDriver: ChildProcess;
export const config: Options.WebdriverIO = {
@ -37,6 +39,15 @@ export const config: Options.WebdriverIO = {
connectionRetryTimeout: 120000,
connectionRetryCount: 0,
beforeTest: function (test: Frameworks.Test) {
const videoPath = path.join(import.meta.dirname, '/e2e/videos');
videoRecorder.start(test, videoPath);
},
afterTest: function () {
videoRecorder.stop();
},
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
beforeSession: () =>
(tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {