Skip to content

CBS-2332 Test video recording support #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ npm install -g oxygen-cli
#### Windows:
* ```npm --add-python-to-path='true' --debug install --global windows-build-tools``` from ```cmd``` with admin rights.
* [Optional. Required for DB support] Windows SDK
* [Optional. Required for Video recording support] https://github.com/BtbN/FFmpeg-Builds/releases win64-lgpl-shared-4.4\bin add to PATH env

#### Linux
* [Optional. Required for DB support] unixodbc binaries and development libraries:
Expand Down
44 changes: 43 additions & 1 deletion src/core/OxygenCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ const DEFAULT_CTX = {
const DEFAULT_RESULT_STORE = {
steps: [],
logs: [],
har: null
har: null,
video: null
};

export default class Oxygen extends OxygenEvents {
Expand Down Expand Up @@ -205,6 +206,7 @@ export default class Oxygen extends OxygenEvents {
this.resultStore.steps = [];
this.resultStore.logs = [];
this.har = null;
this.video = null;
}

async onBeforeCase(context) {
Expand All @@ -216,6 +218,7 @@ export default class Oxygen extends OxygenEvents {
try {
module.onBeforeCase && await module.onBeforeCase(context);
module._iterationStart && await module._iterationStart();
await this._callServicesonBeforeCase(context, module);
}
catch (e) {
this.logger.error(`Failed to call "onBeforeCase" method of ${moduleName} module.`, e);
Expand All @@ -233,6 +236,7 @@ export default class Oxygen extends OxygenEvents {
// await for avoid stuck on *.dispose call
module.onAfterCase && await module.onAfterCase(error);
module._iterationEnd && await module._iterationEnd(error);
await this._callServicesonAfterCase(module);
}
catch (e) {
this.logger.error(`Failed to call "onAfterCase" method of ${moduleName} module.`, e);
Expand Down Expand Up @@ -905,6 +909,44 @@ export default class Oxygen extends OxygenEvents {
}
}
}
async _callServicesonBeforeCase(context, module) {
if (!this || !this.services) {
return;
}
for (let serviceName in this.services) {
const service = this.services[serviceName];
if (!service) {
continue;
}
try {
if (service.onBeforeCase) {
service.onBeforeCase(context, module);
}
}
catch (e) {
this.logger.error(`Failed to call "_callServicesonBeforeCase" method of ${serviceName} service.`, e);
}
}
}
async _callServicesonAfterCase(module) {
if (!this || !this.services) {
return;
}
for (let serviceName in this.services) {
const service = this.services[serviceName];
if (!service) {
continue;
}
try {
if (service.onAfterCase) {
service.onAfterCase(module);
}
}
catch (e) {
this.logger.error(`Failed to call "_callServicesonAfterCase" method of ${serviceName} service.`, e);
}
}
}
_populateParametersValue(args) {
if (!args || !Array.isArray(args) || args.length == 0) {
return args;
Expand Down
1 change: 1 addition & 0 deletions src/model/case-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = function () {
steps: [], // array of step-result.js
logs: [],
har: null,
video: null,
failure: null,
context: null
};
Expand Down
3 changes: 3 additions & 0 deletions src/ox_reporters/html/tests-details.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapse-<%=resultIndex%>-<%=suiteIndex%>-<%=caseIndex%>">
<%= caseResult.name %> (Iteration #<%= caseResult.iterationNum %>)</a>
<%- caseResult.status === 'passed' ? PASSED_LABEL : FAILED_LABEL %>
<% if (caseResult.video) { %>
<a target="_blank" href="<%= caseResult.video %>">Video</a>
<% } %>
</h3>
</div>
<div id="collapse-<%=resultIndex%>-<%=suiteIndex%>-<%=caseIndex%>" class="panel-collapse collapse in">
Expand Down
81 changes: 81 additions & 0 deletions src/ox_services/service-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import OxygenService from '../core/OxygenService';
import path from 'path';

const allowedModules = ['web'];

export default class VideoService extends OxygenService {
constructor(options, ctx, results, logger) {
super(options, ctx, results, logger);
this.enabled = options.enableVideo || false;
this.videoFolderPath = options.videoFolderPath || null;
this.results = results;
this.logger = logger;
}
logBuffer (buffer, prefix) {
const lines = buffer.toString().trim().split('\n');
lines.forEach((line) => {
this.logger.debug(prefix + line);
});
}
onBeforeCase(context, module) {
if (this.enabled && allowedModules.includes(module.name)) {
let name = +new Date();
if (
context &&
context.test &&
context.test.suite &&
context.test.case
) {
name = context.test.suite.name + '_' + context.test.suite.iteration + '_' + context.test.case.name + '_' + context.test.case.iteration;
}

this.videoPath = path.join(this.videoFolderPath, name+'.mp4');
const { spawn } = require('child_process');
try {
this.ffmpeg = spawn('ffmpeg', [
'-f',
'gdigrab',
'-framerate',
10,
'-i',
'desktop',
this.videoPath, // Output file
'-y', // Overwrite output files without asking
'-loglevel',
'error', // Log only errors
]);
this.ffmpeg.on('error', (e) => {
this.logger.error('Failed to init ffmpeg' + e);
});
} catch (e) {
this.logger.error('Failed to init ffmpeg' + e);
}

this.ffmpeg.stdout.on('data', (data) => {
this.logBuffer(data, 'ffmpeg stdout: ');
});

this.ffmpeg.stderr.on('data', (data) => {
this.logBuffer(data, 'ffmpeg stderr: ');
});

this.ffmpeg.on('close', (code, signal) => {
this.logger.debug('Video location:', this.videoPath, '\n');
if (code !== null) {
this.logger.debug(`ffmpeg exited with code ${code} ${this.videoPath}`);
}
if (signal !== null) {
this.logger.debug(`ffmpeg received signal ${signal} ${this.videoPath}`);
}
});
}
}
onAfterCase(module) {
if (this.enabled && allowedModules.includes(module.name)) {
if (this.ffmpeg) {
this.ffmpeg.kill('SIGINT');
}
this.results.video = this.videoPath;
}
}
}
1 change: 1 addition & 0 deletions src/runners/oxygen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ export default class OxygenRunner extends EventEmitter {
caseResult.steps = resultStore && resultStore.steps ? resultStore.steps : [];
caseResult.logs = resultStore && resultStore.logs ? resultStore.logs : [];
caseResult.har = resultStore && resultStore.har ? resultStore.har : null;
caseResult.video = resultStore && resultStore.video ? resultStore.video : null;

// determine test case iteration status - mark it as failed if any step has failed
var failedSteps = _.find(caseResult.steps, {status: Status.FAILED});
Expand Down