diff --git a/Gruntfile.js b/Gruntfile.js index eae3e9672f..51af4a18cb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,6 +15,7 @@ module.exports = function ( grunt ) { grunt.loadNpmTasks( 'grunt-contrib-watch' ); grunt.loadNpmTasks( 'grunt-jsonlint' ); grunt.loadNpmTasks( 'grunt-banana-checker' ); + grunt.loadNpmTasks( 'grunt-mocha-test' ); grunt.loadNpmTasks( 'grunt-jscs' ); grunt.loadNpmTasks( 'grunt-stylelint' ); grunt.loadNpmTasks( 'grunt-tyops' ); @@ -54,6 +55,15 @@ module.exports = function ( grunt ) { indent: '\t\t' } }, + mochaTest: { + screenshots: { + options: { + reporter: 'spec', + timeout: 20000 + }, + src: [ 'build/screenshots.js' ] + } + }, tyops: { options: { typos: 'build/typos.json' @@ -153,6 +163,7 @@ module.exports = function ( grunt ) { grunt.registerTask( 'fix', [ 'jscs:fix' ] ); grunt.registerTask( 'test', [ 'build', 'lint' ] ); grunt.registerTask( 'test-ci', [ 'git-status' ] ); + grunt.registerTask( 'screenshots', [ 'mochaTest:screenshots' ] ); grunt.registerTask( 'default', 'test' ); if ( process.env.JENKINS_HOME ) { diff --git a/build/screenshots.js b/build/screenshots.js new file mode 100644 index 0000000000..986eaaedb3 --- /dev/null +++ b/build/screenshots.js @@ -0,0 +1,266 @@ +/* jshint node: true */ +/* global seleniumUtils */ + +var chrome = require( 'selenium-webdriver/chrome' ), + test = require( 'selenium-webdriver/testing' ), + fs = require( 'fs' ), + Jimp = require( 'jimp' ), + lang = 'en'; + +test.describe( 'Screenshot', function () { + var driver; + + function runScreenshotTest( name, clientScript, padding ) { + var filename = './screenshots/' + name + '-' + lang + '.png'; + driver.get( 'http://en.wikipedia.beta.wmflabs.org/wiki/PageDoesNotExist?veaction=edit&uselang=' + lang ); + driver.wait( + driver.executeAsyncScript( + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + + window.seleniumUtils = { + getBoundingRect: function ( elements ) { + var i, l, rect, boundingRect; + for ( i = 0, l = elements.length; i < l; i++ ) { + rect = elements[ i ].getBoundingClientRect(); + if ( !boundingRect ) { + boundingRect = { + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom + }; + } else { + boundingRect.left = Math.min( boundingRect.left, rect.left ); + boundingRect.top = Math.min( boundingRect.top, rect.top ); + boundingRect.right = Math.max( boundingRect.right, rect.right ); + boundingRect.bottom = Math.max( boundingRect.bottom, rect.bottom ); + } + } + if ( boundingRect ) { + boundingRect.width = boundingRect.right - boundingRect.left; + boundingRect.height = boundingRect.bottom - boundingRect.top; + } + return boundingRect; + } + }; + + // Suppress welcome dialog + localStorage.setItem( 've-beta-welcome-dialog', 1 ); + // Suppress user education indicators + localStorage.setItem( 've-hideusered', 1 ); + mw.hook( 've.activationComplete' ).add( function () { + var target = ve.init.target, + surfaceView = target.getSurface().getView(); + // Hide edit notices + target.actionsToolbar.tools.notices.getPopup().toggle( false ); + // Modify the document to make the save button blue + // Wait for focus + surfaceView.once( 'focus', function () { + target.surface.getModel().getFragment().insertContent( ' ' ).collapseToStart().select(); + // Wait for save button fade + setTimeout( done, 100 ); + } ); + } ); + } + ).then( function () { + return driver.executeAsyncScript( clientScript ).then( function ( rect ) { + return driver.takeScreenshot().then( function ( base64Image ) { + var imageBuffer; + if ( rect ) { + imageBuffer = new Buffer( base64Image, 'base64' ); + return cropScreenshot( filename, imageBuffer, rect, padding ); + } else { + fs.writeFile( filename, base64Image, 'base64' ); + } + } ); + } ); + } ), + 20000 + ); + } + + function cropScreenshot( filename, imageBuffer, rect, padding ) { + if ( padding === undefined ) { + padding = 5; + } + + return Jimp.read( imageBuffer ).then( function ( jimpImage ) { + jimpImage + .crop( + rect.left - padding, + rect.top - padding, + rect.width + ( padding * 2 ), + rect.height + ( padding * 2 ) + ) + .write( filename ); + } ); + } + + test.beforeEach( function () { + driver = new chrome.Driver(); + driver.manage().timeouts().setScriptTimeout( 20000 ); + driver.manage().window().setSize( 1200, 800 ); + } ); + + test.afterEach( function () { + driver.quit(); + } ); + + test.it( 'Toolbar', function () { + runScreenshotTest( 'VisualEditor_toolbar', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + done( + seleniumUtils.getBoundingRect( [ + ve.init.target.toolbar.$element[ 0 ], + $( '#ca-nstab-main' )[ 0 ] + ] ) + ); + }, + 0 + ); + } ); + test.it( 'Citoid inspector', function () { + runScreenshotTest( 'VisualEditor_Citoid_Inspector', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + ve.init.target.toolbar.tools.citefromid.onSelect(); + setTimeout( function () { + var rect = ve.init.target.surface.context.inspectors.currentWindow.$element[ 0 ].getBoundingClientRect(); + done( { + top: rect.top - 20, + left: rect.left, + width: rect.width, + height: rect.height + 20 + } ); + }, 500 ); + } + ); + } ); + test.it( 'Headings tool list', function () { + runScreenshotTest( 'VisualEditor_Toolbar_Headings', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ], + toolGroup = ve.init.target.toolbar.tools.paragraph.toolGroup; + toolGroup.setActive( true ); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + toolGroup.$element[ 0 ], + toolGroup.$group[ 0 ] + ] ) + ); + }, 500 ); + } + ); + } ); + test.it( 'Text style tool list', function () { + runScreenshotTest( 'VisualEditor_Toolbar_Formatting', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ], + toolGroup = ve.init.target.toolbar.tools.bold.toolGroup; + toolGroup.setActive( true ); + toolGroup.getExpandCollapseTool().onSelect(); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + toolGroup.$element[ 0 ], + toolGroup.$group[ 0 ] + ] ) + ); + }, 500 ); + } + ); + } ); + test.it( 'Indentation tool list', function () { + runScreenshotTest( 'VisualEditor_Toolbar_List_and_indentation', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ], + toolGroup = ve.init.target.toolbar.tools.bullet.toolGroup; + toolGroup.setActive( true ); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + toolGroup.$element[ 0 ], + toolGroup.$group[ 0 ] + ] ) + ); + }, 500 ); + } + ); + } ); + test.it( 'Page options list', function () { + runScreenshotTest( 'VisualEditor_More_Settings', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ], + toolGroup = ve.init.target.actionsToolbar.tools.advancedSettings.toolGroup; + toolGroup.setActive( true ); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + toolGroup.$element[ 0 ], + toolGroup.$group[ 0 ], + // Include save button for context + ve.init.target.toolbarSaveButton.$element[ 0 ] + ] ) + ); + }, 500 ); + } + ); + } ); + test.it( 'Toolbar actions', function () { + runScreenshotTest( 'VisualEditor_toolbar_actions', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + done( + seleniumUtils.getBoundingRect( [ + ve.init.target.toolbar.$actions[ 0 ] + ] ) + ); + }, + 0 + ); + } ); + test.it( 'Save dialog', function () { + runScreenshotTest( 'VisualEditor_save_dialog', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + ve.init.target.toolbarSaveButton.emit( 'click' ); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + ve.init.target.surface.dialogs.currentWindow.$frame[ 0 ] + ] ) + ); + }, 500 ); + } + ); + } ); + test.it( 'Special character inserter', function () { + runScreenshotTest( 'VisualEditor_Toolbar_SpecialCharacters', + // This function is converted to a string and executed in the browser + function () { + var done = arguments[ arguments.length - 1 ]; + ve.init.target.toolbar.tools.specialCharacter.onSelect(); + setTimeout( function () { + done( + seleniumUtils.getBoundingRect( [ + ve.init.target.toolbar.tools.specialCharacter.$element[ 0 ], + ve.init.target.surface.toolbarDialogs.$element[ 0 ] + ] + ) ); + }, 1000 ); + } + ); + } ); +} ); diff --git a/package.json b/package.json index e0d528ebff..cc20571b14 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,12 @@ "grunt-contrib-watch": "1.0.0", "grunt-jscs": "3.0.0", "grunt-jsonlint": "1.0.8", + "grunt-mocha-test": "0.12.7", "grunt-stylelint": "0.4.0", "grunt-tyops": "0.1.0", + "jimp": "0.2.24", + "mocha": "2.5.3", + "selenium-webdriver": "2.53.2", "stylelint-config-wikimedia": "0.2.2" } } diff --git a/screenshots/README.md b/screenshots/README.md new file mode 100644 index 0000000000..e114d51714 --- /dev/null +++ b/screenshots/README.md @@ -0,0 +1 @@ +Generated screenshots will be put here.