Selenium: record video of every test
authorŽeljko Filipin <zeljko.filipin@gmail.com>
Fri, 30 Mar 2018 16:42:04 +0000 (18:42 +0200)
committerKrinkle <krinklemail@gmail.com>
Tue, 16 Oct 2018 22:07:22 +0000 (22:07 +0000)
In addition to two existing modes (headless and visible), this patch
introcues another mode, headless recording.

If DISPLAY environment variable is not set, Chrome will run in it's
built-in headless mode.
If it is set, and the value starts with colon (`:`), Chrome will
run in headless mode using Xvfb and record video of every test using FFmpeg.
If the value does not start with colon, Chrome will be visible.

Bug: T179188
Change-Id: Ic1723c5f2d57a28201caf6ba7056cb73fb74a957

tests/selenium/README.md
tests/selenium/wdio.conf.js

index a7c9aa6..038b757 100644 (file)
@@ -20,8 +20,26 @@ If using MediaWiki-Vagrant:
 
     npm run selenium
 
-By default, Chrome will run in headless mode. If you want to see Chrome, set DISPLAY
-environment variable to any value:
+There are three supported modes of running the tests:
+
+- Headless. It's the default. You will not see the browser while tests are
+  running because it's running in a headless mode. This mode should run fine
+  on all supported platforms.
+- Headless recording. Set DISPLAY environment variable to a value that starts
+  with colon (`:`) and video of each test will be recorded. Browser will run
+  headless. Recording videos works only on Linux.
+- Visible. If you want to see the browser, set DISPLAY environment variable to
+  any value that does not start with colon. This mode will not work in a
+  headless environment like MediaWiki-Vagrant.
+
+Example recording session:
+
+    sudo apt-get install chromedriver ffmpeg xvfb
+    export DISPLAY=:94
+    Xvfb "$DISPLAY" -screen 0 1280x1024x24 &
+    npm run selenium
+
+Example visible session:
 
     DISPLAY=1 npm run selenium
 
index 8b47dff..916ee74 100644 (file)
@@ -1,8 +1,20 @@
 const fs = require( 'fs' ),
        path = require( 'path' ),
-       saveScreenshot = require( 'wdio-mediawiki' ).saveScreenshot,
-       logPath = process.env.LOG_DIR || __dirname + '/log';
+       logPath = process.env.LOG_DIR || path.join( __dirname, '/log' );
 
+let ffmpeg;
+
+// get current test title and clean it, to use it as file name
+function fileName( title ) {
+       return encodeURIComponent( title.replace( /\s+/g, '-' ) );
+}
+
+// build file path
+function filePath( test, screenshotPath, extension ) {
+       return path.join( screenshotPath, `${fileName( test.parent )}-${fileName( test.title )}.${extension}` );
+}
+
+// relative path
 function relPath( foo ) {
        return path.resolve( __dirname, '../..', foo );
 }
@@ -135,16 +147,57 @@ exports.config = {
        // =====
        // See also: http://webdriver.io/guide/testrunner/configurationfile.html
 
+       /**
+       * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+       * @param {Object} test test details
+       */
+       beforeTest: function ( test ) {
+               if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
+                       let videoPath = filePath( test, this.screenshotPath, 'mp4' );
+                       const { spawn } = require( 'child_process' );
+                       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"
+                               videoPath // output file
+                       ] );
+
+                       ffmpeg.stdout.on( 'data', ( data ) => {
+                               console.log( `ffmpeg stdout: ${data}` );
+                       } );
+
+                       ffmpeg.stderr.on( 'data', ( data ) => {
+                               console.log( `ffmpeg stderr: ${data}` );
+                       } );
+
+                       ffmpeg.on( 'close', ( code ) => {
+                               console.log( '\n\tVideo location:', videoPath, '\n' );
+                               console.log( `ffmpeg exited with code ${code}` );
+                       } );
+               }
+       },
+
        /**
         * Save a screenshot when test fails.
         *
         * @param {Object} test Mocha Test object
         */
        afterTest: function ( test ) {
-               var filePath;
-               if ( !test.passed ) {
-                       filePath = saveScreenshot( test.title );
-                       console.log( '\n\tScreenshot: ' + filePath + '\n' );
+               if ( ffmpeg ) {
+                       // stop video recording
+                       ffmpeg.kill( 'SIGINT' );
+               }
+
+               // if test passed, ignore, else take and save screenshot
+               if ( test.passed ) {
+                       return;
                }
+               // save screenshot
+               let screenshotPath = filePath( test, this.screenshotPath, 'png' );
+               browser.saveScreenshot( screenshotPath );
+               console.log( '\n\tScreenshot location:', screenshotPath, '\n' );
        }
 };