Skip to content

Commit

Permalink
Add to 'TestRunInfo' object information about recorded videos (closes D…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexKamaev committed Mar 27, 2020
1 parent a242700 commit 4e122cf
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 73 deletions.
5 changes: 5 additions & 0 deletions src/reporter/index.js
Expand Up @@ -45,6 +45,7 @@ export default class Reporter {
testRunIds: [],
screenshotPath: null,
screenshots: [],
videos: [],
quarantine: null,
errs: [],
warnings: [],
Expand Down Expand Up @@ -72,6 +73,7 @@ export default class Reporter {
unstable: reportItem.unstable,
screenshotPath: reportItem.screenshotPath,
screenshots: reportItem.screenshots,
videos: reportItem.videos,
quarantine: reportItem.quarantine,
skipped: reportItem.test.skip
};
Expand Down Expand Up @@ -107,6 +109,9 @@ export default class Reporter {
reportItem.screenshots = this.task.screenshots.getScreenshotsInfo(testRun.test);
}

if (this.task.videos)
reportItem.videos = this.task.videos.getTestVideos(reportItem.test);

if (testRun.quarantine) {
reportItem.quarantine = testRun.quarantine.attempts.reduce((result, errors, index) => {
const passed = !errors.length;
Expand Down
10 changes: 2 additions & 8 deletions src/runner/task.js
Expand Up @@ -3,10 +3,10 @@ import moment from 'moment';
import AsyncEventEmitter from '../utils/async-event-emitter';
import BrowserJob from './browser-job';
import Screenshots from '../screenshots';
import VideoRecorder from '../video-recorder';
import WarningLog from '../notifications/warning-log';
import FixtureHookController from './fixture-hook-controller';
import * as clientScriptsRouting from '../custom-client-scripts/routing';
import Videos from '../video-recorder/videos';

export default class Task extends AsyncEventEmitter {
constructor (tests, browserConnectionGroups, proxy, opts) {
Expand All @@ -32,7 +32,7 @@ export default class Task extends AsyncEventEmitter {
this.testStructure = this._prepareTestStructure(tests);

if (this.opts.videoPath)
this.videoRecorders = this._createVideoRecorders(this.pendingBrowserJobs);
this.videos = new Videos(this.pendingBrowserJobs, this.opts, this.warningLog, this.timeStamp);
}

_assignBrowserJobEventHandlers (job) {
Expand Down Expand Up @@ -109,12 +109,6 @@ export default class Task extends AsyncEventEmitter {
});
}

_createVideoRecorders (browserJobs) {
const videoOptions = { timeStamp: this.timeStamp, ...this.opts.videoOptions };

return browserJobs.map(browserJob => new VideoRecorder(browserJob, this.opts.videoPath, videoOptions, this.opts.videoEncodingOptions, this.warningLog));
}

unRegisterClientScriptRouting () {
clientScriptsRouting.unRegister(this.proxy, this.clientScriptRoutes);
}
Expand Down
15 changes: 10 additions & 5 deletions src/video-recorder/index.js
Expand Up @@ -9,14 +9,17 @@ import WARNING_MESSAGES from '../notifications/warning-message';
import { getPluralSuffix, getConcatenatedValuesString, getToBeInPastTense } from '../utils/string';

import TestRunVideoRecorder from './test-run-video-recorder';
import { EventEmitter } from 'events';

const DEBUG_LOGGER = debug('testcafe:video-recorder');

const VIDEO_EXTENSION = 'mp4';
const TEMP_DIR_PREFIX = 'video';

export default class VideoRecorder {
export default class VideoRecorder extends EventEmitter {
constructor (browserJob, basePath, opts, encodingOpts, warningLog) {
super();

this.browserJob = browserJob;
this.basePath = basePath;
this.failedOnly = opts.failedOnly;
Expand Down Expand Up @@ -160,12 +163,14 @@ export default class VideoRecorder {
if (this.failedOnly && !testRunRecorder.hasErrors)
return;

await this._saveFiles(testRunRecorder);
}

async _saveFiles (testRunRecorder) {
const videoPath = this._getTargetVideoPath(testRunRecorder);

await this._saveFiles(testRunRecorder, videoPath);

this.emit('test-run-video-saved', { testRun: testRunRecorder.testRun, videoPath, singleFile: !!this.singleFile });
}

async _saveFiles (testRunRecorder, videoPath) {
await makeDir(dirname(videoPath));

if (this.singleFile)
Expand Down
42 changes: 42 additions & 0 deletions src/video-recorder/videos.js
@@ -0,0 +1,42 @@
import VideoRecorder from './index';

export default class Videos {
constructor (browserJobs, { videoPath, videoOptions, videoEncodingOptions }, warningLog, timeStamp) {
const options = { timeStamp: timeStamp, ...videoOptions };

this.recordings = {};

browserJobs.forEach(browserJob => {
const recorder = this._createVideoRecorder(browserJob, videoPath, options, videoEncodingOptions, warningLog);

recorder.on('test-run-video-saved', args => this._addTestRunVideoInfo(args));
});
}

getTestVideos (test) {
const rec = this.recordings[test.id];

return rec ? rec.runs : [];
}

_createVideoRecorder (browserJob, videoPath, options, videoEncodingOptions, warningLog) {
return new VideoRecorder(browserJob, videoPath, options, videoEncodingOptions, warningLog);
}

_addTestRunVideoInfo ({ testRun, videoPath, singleFile }) {
const testId = testRun.test.id;
let rec = this.recordings[testId];

if (!rec) {
rec = { runs: [] };

this.recordings[testId] = rec;
}

rec.runs.push({
testRunId: testRun.id,
videoPath,
singleFile
});
}
}
86 changes: 76 additions & 10 deletions test/functional/fixtures/video-recording/test.js
@@ -1,68 +1,128 @@
const expect = require('chai').expect;
const config = require('../../config');
const expect = require('chai').expect;
const path = require('path');
const { uniq } = require('lodash');
const config = require('../../config');
const assertionHelper = require('../../assertion-helper.js');

function customReporter (errs, videos) {
return () => {
return {
async reportTaskStart () {
},
async reportFixtureStart () {
},
async reportTestDone (name, testRunInfo) {
testRunInfo.errs.forEach(err => {
errs[err.errMsg] = true;
});

testRunInfo.videos.forEach(video => {
videos.push(video);
});
},
async reportTaskDone () {
}
};
};
}

function checkVideoPaths (videoLog, videoPaths) {
const testRunIds = uniq(videoLog.map(video => video.testRunId));

expect(videoLog.length).eql(testRunIds.length);

const loggedPaths = uniq(videoLog.map(video => video.videoPath)).sort();
const actualPaths = [...videoPaths].sort();

expect(loggedPaths.length).eql(videoPaths.length);

for (let i = 0; i < loggedPaths.length; i++)
expect(path.relative(actualPaths[i], loggedPaths[i])).eql('');
}

if (config.useLocalBrowsers) {
describe('Video Recording', () => {
afterEach(assertionHelper.removeVideosDir);

it('Should record video without options', () => {
const errs = {};
const videos = [];

return runTests('./testcafe-fixtures/index-test.js', '', {
only: 'chrome,firefox',
setVideoPath: true,
shouldFail: true
reporter: customReporter(errs, videos)
})
.catch(errors => {
.then(() => {
const errors = Object.keys(errs);

expect(errors.length).to.equal(2);
expect(errors[0]).to.match(/^Error: Error 1/);
expect(errors[1]).to.match(/^Error: Error 2/);
})
.then(assertionHelper.getVideoFilesList)
.then(videoFiles => {
expect(videoFiles.length).to.equal(3 * config.browsers.length);

checkVideoPaths(videos, videoFiles);
});
});

it('Should record video in a single file', ()=> {
it('Should record video in a single file', () => {
const errs = {};
const videos = [];

return runTests('./testcafe-fixtures/index-test.js', '', {
only: 'chrome,firefox',
shouldFail: true,
setVideoPath: true,
reporter: customReporter(errs, videos),

videoOptions: {
singleFile: true
}
})
.catch(assertionHelper.getVideoFilesList)
.catch(errors => {
.then(() => {
const errors = Object.keys(errs);

expect(errors.length).to.equal(2);
expect(errors[0]).to.match(/^Error: Error 1/);
expect(errors[1]).to.match(/^Error: Error 2/);
})
.then(assertionHelper.getVideoFilesList)
.then(videoFiles => {
expect(videoFiles.length).to.equal(1 * config.browsers.length);

checkVideoPaths(videos, videoFiles);
});
});

it('Should record only failed tests', () => {
const errs = {};
const videos = [];

return runTests('./testcafe-fixtures/index-test.js', '', {
only: 'chrome,firefox',
shouldFail: true,
setVideoPath: true,
reporter: customReporter(errs, videos),

videoOptions: {
failedOnly: true
}
})
.catch(assertionHelper.getVideoFilesList)
.catch(errors => {
.then(() => {
const errors = Object.keys(errs);

expect(errors.length).to.equal(2);
expect(errors[0]).to.match(/^Error: Error 1/);
expect(errors[1]).to.match(/^Error: Error 2/);
})
.then(assertionHelper.getVideoFilesList)
.then(videoFiles => {
expect(videoFiles.length).to.equal(2 * config.browsers.length);

checkVideoPaths(videos, videoFiles);
});
});

Expand All @@ -89,14 +149,20 @@ if (config.useLocalBrowsers) {
});

it('Should record video with quarantine mode enabled', () => {
const errs = {};
const videos = [];

return runTests('./testcafe-fixtures/quarantine-test.js', '', {
only: 'chrome',
quarantineMode: true,
setVideoPath: true
setVideoPath: true,
reporter: customReporter(errs, videos),
})
.then(assertionHelper.getVideoFilesList)
.then(videoFiles => {
expect(videoFiles.length).to.equal(2);

checkVideoPaths(videos, videoFiles);
});
});

Expand Down

0 comments on commit 4e122cf

Please sign in to comment.