diff --git a/.gitignore b/.gitignore index 71735b71d2f46..80ed93f76b628 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,8 @@ target .idea *.iml *.log -/test/output +/test/screenshots/failure/*.png +/test/screenshots/session/*.png /esvm .htpasswd .eslintcache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02cd6298cbafc..7288710afc8c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,26 +111,18 @@ The standard `npm run test` task runs several sub tasks and can take several min #### Running tests using npm task: -*The Selenium server that is started currently only runs the tests in Firefox* +*The ChromeDriver that is started currently only runs the tests in Chrome browser* -To runt the functional UI tests, execute the following command: +To run the functional UI tests, execute the following command: `npm run test:ui` The task above takes a little time to start the servers. You can also start the servers and leave them running, and then run the tests separately: -`npm run test:ui:server` will start the server required to run the selenium tests, leave this open +`npm run test:ui:server` will start the server required to run the UI tests, leave this open `npm run test:ui:runner` will run the frontend tests and close when complete -#### Running tests locally with your existing (and already running) ElasticSearch, Kibana, and Selenium Server: - -Set your es and kibana ports in `test/intern.js` to 9220 and 5620, respectively. You can configure your Selenium server to run the tests on Chrome,IE, or other browsers here. - -Once you've got the services running, execute the following: - -`npm run test:ui:runner` - #### General notes: - Using Page Objects pattern (https://theintern.github.io/intern/#writing-functional-test) diff --git a/package.json b/package.json index ed4bc3726ead3..b26381f91f773 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "Court Ewing ", "Joe Fleming ", "Khalah Jones-Golden ", + "Lee Drengenberg ", "Lukas Olson ", "Juan Thomassie ", "Shelby Sturgis ", @@ -142,6 +143,7 @@ "auto-release-sinon": "1.0.3", "babel-eslint": "4.1.7", "chokidar": "1.0.5", + "chromedriver": "2.21.2", "eslint": "1.5.1", "eslint-plugin-mocha": "1.0.0", "expect.js": "0.3.1", @@ -160,7 +162,7 @@ "gruntify-eslint": "1.0.1", "html-entities": "1.1.3", "husky": "0.8.1", - "intern": "3.0.1", + "intern": "3.2.3", "istanbul-instrumenter-loader": "0.1.3", "karma": "0.13.9", "karma-chrome-launcher": "0.2.0", diff --git a/tasks/config/downloadSelenium.js b/tasks/config/downloadSelenium.js deleted file mode 100644 index d337d89ba7740..0000000000000 --- a/tasks/config/downloadSelenium.js +++ /dev/null @@ -1,15 +0,0 @@ -var path = require('path'); - - -module.exports = function (grunt) { - return { - options: { - selenium: { - filename: 'selenium-server-standalone-2.53.0.jar', - server: 'https://selenium-release.storage.googleapis.com/2.53/', - md5: '774efe2d84987fb679f2dea038c2fa32', - directory: path.join(grunt.config.get('root'), 'selenium') - } - } - }; -}; diff --git a/tasks/config/run.js b/tasks/config/run.js index 0739bdc44128d..dc7f2e0062db9 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -5,6 +5,7 @@ module.exports = function (grunt) { let root = p => resolve(__dirname, '../../', p); let binScript = /^win/.test(platform) ? '.\\bin\\kibana.bat' : './bin/kibana'; let uiConfig = require(root('test/serverConfig')); + let chromedriver = require('chromedriver'); return { testServer: { @@ -78,35 +79,31 @@ module.exports = function (grunt) { ] }, - seleniumServer: { + chromeDriver: { options: { wait: false, - ready: /Selenium Server is up and running/, - quiet: true, + ready: /Starting ChromeDriver/, + quiet: false, failOnError: false }, - cmd: 'java', + cmd: chromedriver.path, args: [ - '-jar', - 'selenium/selenium-server-standalone-2.53.0.jar', - '-port', - uiConfig.servers.webdriver.port + `--port=${uiConfig.servers.webdriver.port}`, + '--url-base=wd/hub' ] }, - devSeleniumServer: { + devChromeDriver: { options: { wait: false, - ready: /Selenium Server is up and running/, + ready: /Starting ChromeDriver/, quiet: false, failOnError: false }, - cmd: 'java', + cmd: chromedriver.path, args: [ - '-jar', - 'selenium/selenium-server-standalone-2.53.0.jar', - '-port', - uiConfig.servers.webdriver.port + `--port=${uiConfig.servers.webdriver.port}`, + '--url-base=wd/hub' ] }, diff --git a/tasks/downloadSelenium.js b/tasks/downloadSelenium.js deleted file mode 100644 index 62840493ee2a8..0000000000000 --- a/tasks/downloadSelenium.js +++ /dev/null @@ -1,60 +0,0 @@ -var _ = require('lodash'); -var request = require('request'); -var fs = require('fs'); -var path = require('path'); -var colors = require('ansicolors'); -var crypto = require('crypto'); -var {spawn} = require('child_process'); - -module.exports = function (grunt) { - grunt.registerTask('downloadSelenium', 'Download selenium standalone', function (keepalive) { - const done = this.async(); - const config = this.options(); - - const SELENIUM_FILE_PATH = path.join(config.selenium.directory, config.selenium.filename); - const SELENIUM_DOWNLOAD_URL = config.selenium.server + config.selenium.filename; - - function validateDownload(path, expectedHash, success) { - grunt.log.write('Validating hash...'); - fs.readFile(path, function checkHash(err, data) { - if (err) grunt.fail.warn(err); - - const calculatedHash = crypto.createHash('md5').update(data).digest('hex'); - if (calculatedHash !== expectedHash) return grunt.fail.warn('Selenium download has an invalid hash'); - - grunt.log.writeln('done'); - success(); - }); - } - - function downloadSelenium(success) { - grunt.log.write(`Downloading ${SELENIUM_DOWNLOAD_URL}...`); - request.get(SELENIUM_DOWNLOAD_URL) - .pipe(fs.createWriteStream(SELENIUM_FILE_PATH)) - .on('error', function downloadError(err) { - grunt.fail.warn(err); - }) - .on('finish', function downloadFinish() { - grunt.log.writeln('done'); - validateDownload(SELENIUM_FILE_PATH, config.selenium.md5, success); - }); - } - - function start() { - try { - fs.mkdirSync(config.selenium.directory); - } catch (err) { - if (err && err.code !== 'EEXIST') grunt.fail.warn(err); - } - - if (fs.existsSync(SELENIUM_FILE_PATH)) { - validateDownload(SELENIUM_FILE_PATH, config.selenium.md5, done); - } else { - downloadSelenium(done); - } - } - - start(); - - }); -}; diff --git a/tasks/test.js b/tasks/test.js index 8d3170d327b28..9d9769957806b 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -18,19 +18,17 @@ module.exports = function (grunt) { grunt.registerTask('test:ui', [ 'esvm:ui', 'run:testUIServer', - 'downloadSelenium', - 'run:seleniumServer', + 'run:chromeDriver', 'intern:dev', 'esvm_shutdown:ui', - 'stop:seleniumServer', + 'stop:chromeDriver', 'stop:testUIServer' ]); grunt.registerTask('test:ui:server', [ 'esvm:ui', 'run:testUIServer', - 'downloadSelenium', - 'run:devSeleniumServer:keepalive' + 'run:devChromeDriver:keepalive' ]); grunt.registerTask('test:ui:runner', [ diff --git a/test/functional/apps/discover/_shared_links.js b/test/functional/apps/discover/_shared_links.js index f590cc2dd44c6..a61fd78fe7620 100644 --- a/test/functional/apps/discover/_shared_links.js +++ b/test/functional/apps/discover/_shared_links.js @@ -74,10 +74,10 @@ define(function (require) { var expectedUrl = baseUrl + '/app/kibana?_t=1453775307251#' + '/discover?_g=(refreshInterval:(display:Off,pause:!f,value:0),time' - + ':(from:%272015-09-19T06:31:44.000Z%27,mode:absolute,to:%272015-09' - + '-23T18:31:44.000Z%27))&_a=(columns:!(_source),index:%27logstash-' - + '*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query' - + ':%27*%27)),sort:!(%27@timestamp%27,desc))'; + + ':(from:\'2015-09-19T06:31:44.000Z\',mode:absolute,to:\'2015-09' + + '-23T18:31:44.000Z\'))&_a=(columns:!(_source),index:\'logstash-' + + '*\',interval:auto,query:(query_string:(analyze_wildcard:!t,query' + + ':\'*\')),sort:!(\'@timestamp\',desc))'; return discoverPage.getSharedUrl() .then(function (actualUrl) { // strip the timestamp out of each URL diff --git a/test/intern.js b/test/intern.js index dc256a83494db..8267e5a97a284 100644 --- a/test/intern.js +++ b/test/intern.js @@ -5,11 +5,10 @@ define(function (require) { return _.assign({ debug: true, capabilities: { - 'selenium-version': '2.53.0', 'idle-timeout': 99 }, environments: [{ - browserName: 'firefox' + browserName: 'chrome' }], tunnelOptions: serverConfig.servers.webdriver, functionalSuites: [ diff --git a/test/screenshots/.empty b/test/screenshots/.empty new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/screenshots/failure/.empty b/test/screenshots/failure/.empty new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/screenshots/session/.empty b/test/screenshots/session/.empty new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/support/pages/common.js b/test/support/pages/common.js index 60212a0c9982e..d27f8054d5fa2 100644 --- a/test/support/pages/common.js +++ b/test/support/pages/common.js @@ -57,11 +57,8 @@ define(function (require) { navigateToApp: function (appName, testStatusPage) { var self = this; - // navUrl includes user:password@ for use with Shield - // appUrl excludes user:password@ to match what getCurrentUrl returns - var navUrl = getUrl(config.servers.kibana, config.apps[appName]); var appUrl = getUrl.noAuth(config.servers.kibana, config.apps[appName]); - self.debug('navigating to ' + appName + ' url: ' + navUrl); + self.debug('navigating to ' + appName + ' url: ' + appUrl); var doNavigation = function (url) { return self.tryForTime(defaultTimeout, function () { @@ -106,7 +103,7 @@ define(function (require) { }); }; - return doNavigation(navUrl) + return doNavigation(appUrl) .then(function (currentUrl) { var lastUrl = currentUrl; return self.tryForTime(defaultTimeout, function () { @@ -229,24 +226,26 @@ define(function (require) { return function (reason) { var now = Date.now(); - var filename = ['failure', now, testName].join('_') + '.png'; + var fileName = ['failure', now, testName].join('_') + '.png'; - return self.saveScreenshot(filename) + return self.saveScreenshot(fileName, true) .finally(function () { throw new Error(reason); }); }; }, - saveScreenshot: function saveScreenshot(filename) { + saveScreenshot: function saveScreenshot(fileName, isFailure) { var self = this; - var outDir = path.resolve('test', 'output'); + var outDir = path.resolve('test', 'screenshots'); + var directoryName = isFailure ? 'failure' : 'session'; + var directoryPath = path.resolve('test', 'screenshots', directoryName); + var filePath = path.resolve(directoryPath, fileName); + self.debug('Taking screenshot "' + filePath + '"'); return self.remote.takeScreenshot() .then(function writeScreenshot(data) { - var filepath = path.resolve(outDir, filename); - self.debug('Taking screenshot "' + filepath + '"'); - fs.writeFileSync(filepath, data); + fs.writeFileSync(filePath, data); }) .catch(function (err) { self.log('SCREENSHOT FAILED: ' + err); diff --git a/test/support/pages/settings_page.js b/test/support/pages/settings_page.js index 871d7081a9d35..5877a4902eb84 100644 --- a/test/support/pages/settings_page.js +++ b/test/support/pages/settings_page.js @@ -3,13 +3,16 @@ define(function (require) { var config = require('intern').config; var Promise = require('bluebird'); var Common = require('./common'); + var HeaderPage = require('./header_page'); var defaultTimeout = config.timeouts.default; var common; + var headerPage; function settingsPage(remote) { this.remote = remote; common = new Common(this.remote); + headerPage = new HeaderPage(this.remote); } settingsPage.prototype = { @@ -238,6 +241,9 @@ define(function (require) { ) .then(function (page) { return page.click(); + }) + .then(function () { + return headerPage.getSpinnerDone(); }); }, diff --git a/test/support/pages/visualize_page.js b/test/support/pages/visualize_page.js index 2ac4fbb2484d6..022b94f4bdd3d 100644 --- a/test/support/pages/visualize_page.js +++ b/test/support/pages/visualize_page.js @@ -3,13 +3,16 @@ define(function (require) { var config = require('intern').config; var registerSuite = require('intern!object'); var Common = require('./common'); + var HeaderPage = require('./header_page'); var defaultTimeout = config.timeouts.default; var common; + var headerPage; function VisualizePage(remote) { this.remote = remote; common = new Common(this.remote); + headerPage = new HeaderPage(this.remote); } VisualizePage.prototype = { @@ -122,7 +125,10 @@ define(function (require) { return this.remote .setFindTimeout(defaultTimeout * 2) .findByClassName('kbn-timepicker-go') - .click(); + .click() + .then(function () { + return headerPage.getSpinnerDone(); + }); }, collapseChart: function collapseChart() { @@ -151,7 +157,10 @@ define(function (require) { return this.remote .setFindTimeout(defaultTimeout) .findByCssSelector('li[ng-click="stepTwoMode=\'new\'"]') - .click(); + .click() + .then(function () { + return headerPage.getSpinnerDone(); + }); }, setValue: function setValue(newValue) { @@ -178,7 +187,10 @@ define(function (require) { return this.remote .setFindTimeout(defaultTimeout) .findByCssSelector('li[ng-click="stepTwoMode=\'saved\'"]') - .click(); + .click() + .then(function () { + return headerPage.getSpinnerDone(); + }); }, selectSearch: function selectSearch(searchName) { @@ -282,7 +294,10 @@ define(function (require) { return this.remote .setFindTimeout(defaultTimeout) .findByCssSelector('.btn-success') - .click(); + .click() + .then(function () { + return headerPage.getSpinnerDone(); + }); },