mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 03:26:36 +03:00
feat: add ffmpeg video recorder for e2e tests (#4686)
This commit is contained in:
parent
00e3094795
commit
13a270613c
1
.github/workflows/test-e2e.yml
vendored
1
.github/workflows/test-e2e.yml
vendored
@ -29,6 +29,7 @@ jobs:
|
|||||||
libayatana-appindicator3-dev \
|
libayatana-appindicator3-dev \
|
||||||
libwebkit2gtk-4.0-dev \
|
libwebkit2gtk-4.0-dev \
|
||||||
webkit2gtk-driver \
|
webkit2gtk-driver \
|
||||||
|
ffmpeg \
|
||||||
xvfb
|
xvfb
|
||||||
- name: Setup rust-toolchain stable
|
- name: Setup rust-toolchain stable
|
||||||
id: rust-toolchain
|
id: rust-toolchain
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@ generated-do-not-edit/
|
|||||||
|
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
apps/desktop/e2e/videos/*.mp4
|
||||||
|
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
82
apps/desktop/e2e/record.ts
Normal file
82
apps/desktop/e2e/record.ts
Normal 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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,12 @@ CLI=${1:?The first argument is the GitButler CLI}
|
|||||||
# Convert to absolute path
|
# Convert to absolute path
|
||||||
CLI=$(realpath "$CLI")
|
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() {
|
function tick() {
|
||||||
if test -z "${tick+set}"; then
|
if test -z "${tick+set}"; then
|
||||||
tick=1675176957
|
tick=1675176957
|
||||||
@ -20,7 +26,9 @@ function tick() {
|
|||||||
}
|
}
|
||||||
tick
|
tick
|
||||||
|
|
||||||
mkdir "$TEMP_DIR"
|
if [ ! -d "$TEMP_DIR" ]; then
|
||||||
|
mkdir "$TEMP_DIR"
|
||||||
|
fi
|
||||||
cd "$TEMP_DIR"
|
cd "$TEMP_DIR"
|
||||||
|
|
||||||
git init remote
|
git init remote
|
||||||
@ -28,9 +36,7 @@ git init remote
|
|||||||
(
|
(
|
||||||
cd remote
|
cd remote
|
||||||
|
|
||||||
git config user.email "test@example.com"
|
setGitDefaults
|
||||||
git config user.name "Test User"
|
|
||||||
git config init.defaultBranch master
|
|
||||||
|
|
||||||
echo first >file
|
echo first >file
|
||||||
git add . && git commit -m "init"
|
git add . && git commit -m "init"
|
||||||
|
0
apps/desktop/e2e/videos/.keep
Normal file
0
apps/desktop/e2e/videos/.keep
Normal 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 os from 'node:os';
|
||||||
import path from 'node:path';
|
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;
|
let tauriDriver: ChildProcess;
|
||||||
|
|
||||||
export const config: Options.WebdriverIO = {
|
export const config: Options.WebdriverIO = {
|
||||||
@ -37,6 +39,15 @@ export const config: Options.WebdriverIO = {
|
|||||||
connectionRetryTimeout: 120000,
|
connectionRetryTimeout: 120000,
|
||||||
connectionRetryCount: 0,
|
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
|
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
|
||||||
beforeSession: () =>
|
beforeSession: () =>
|
||||||
(tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {
|
(tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {
|
||||||
|
Loading…
Reference in New Issue
Block a user