Refactor functional tests, particularly support/index.js.

- Each page object exposes an init method, which accepts a reference to remote.
- Extract page objects from support/index.js into PageObjects.js.
- PageObjects.js also exposes an init method, which accepts a reference to remote. It’s responsible for passing this onto all of the page object instances.
- The functional tests index file calls PageObjects.init and provides the remote reference.
- Extract log and try method functionality from common.js into Log and Try utils.
- Remove common.js dependency from es_client.js and elastic_dump.js.
- Partially convert “discover” tests to use PageObjects, as a demonstration.
This commit is contained in:
CJ Cenizal 2016-06-22 19:45:32 -07:00
parent b4c3991588
commit 31137131f3
21 changed files with 639 additions and 575 deletions

View file

@ -1,15 +1,14 @@
import expect from 'expect.js';
import {
bdd,
scenarioManager,
common,
discoverPage,
settingsPage,
headerPage,
esClient,
elasticDump
} from '../../../support';
var expect = require('expect.js');
import PageObjects from '../../../support/page_objects';
bdd.describe('discover app', function describeIndexTests() {
bdd.before(function () {
@ -19,7 +18,7 @@ bdd.describe('discover app', function describeIndexTests() {
// delete .kibana index and update configDoc
return esClient.deleteAndUpdateConfigDoc({'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*'})
.then(function loadkibanaIndexPattern() {
common.debug('load kibana index with default index pattern');
PageObjects.common.debug('load kibana index with default index pattern');
return elasticDump.elasticLoad('visualize','.kibana');
})
// and load a set of makelogs data
@ -27,12 +26,12 @@ bdd.describe('discover app', function describeIndexTests() {
return scenarioManager.loadIfEmpty('logstashFunctional');
})
.then(function () {
common.debug('discover');
return common.navigateToApp('discover');
PageObjects.common.debug('discover');
return PageObjects.common.navigateToApp('discover');
})
.then(function () {
common.debug('setAbsoluteRange');
return headerPage.setAbsoluteRange(fromTime, toTime);
PageObjects.common.debug('setAbsoluteRange');
return PageObjects.header.setAbsoluteRange(fromTime, toTime);
});
});
@ -44,28 +43,27 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct time range string by timepicker', function () {
var expectedTimeRangeString = fromTimeString + ' to ' + toTimeString;
return discoverPage.getTimespanText()
return PageObjects.discover.getTimespanText()
.then(function (actualTimeString) {
expect(actualTimeString).to.be(expectedTimeRangeString);
});
});
bdd.it('save query should show toast message and display query name', function () {
var expectedSavedQueryMessage = 'Discover: Saved Data Source "' + queryName1 + '"';
return discoverPage.saveSearch(queryName1)
return PageObjects.discover.saveSearch(queryName1)
.then(function () {
return headerPage.getToastMessage();
return PageObjects.header.getToastMessage();
})
.then(function (toastMessage) {
common.saveScreenshot('Discover-save-query-toast');
PageObjects.common.saveScreenshot('Discover-save-query-toast');
expect(toastMessage).to.be(expectedSavedQueryMessage);
})
.then(function () {
return headerPage.waitForToastMessageGone();
return PageObjects.header.waitForToastMessageGone();
})
.then(function () {
return discoverPage.getCurrentQueryName();
return PageObjects.discover.getCurrentQueryName();
})
.then(function (actualQueryNameString) {
expect(actualQueryNameString).to.be(queryName1);
@ -73,23 +71,23 @@ bdd.describe('discover app', function describeIndexTests() {
});
bdd.it('load query should show query name', function () {
return discoverPage.loadSavedSearch(queryName1)
return PageObjects.discover.loadSavedSearch(queryName1)
.then(function () {
return common.sleep(3000);
return PageObjects.common.sleep(3000);
})
.then(function () {
return discoverPage.getCurrentQueryName();
return PageObjects.discover.getCurrentQueryName();
})
.then(function (actualQueryNameString) {
common.saveScreenshot('Discover-load-query');
PageObjects.common.saveScreenshot('Discover-load-query');
expect(actualQueryNameString).to.be(queryName1);
});
});
bdd.it('should show the correct hit count', function () {
var expectedHitCount = '14,004';
return common.try(function tryingForTime() {
return discoverPage.getHitCount()
return PageObjects.common.try(function tryingForTime() {
return PageObjects.discover.getHitCount()
.then(function compareData(hitCount) {
expect(hitCount).to.be(expectedHitCount);
});
@ -103,7 +101,7 @@ bdd.describe('discover app', function describeIndexTests() {
'61.862', '15.487', '2.362', '2.800', '15.312', '61.862', '123.2',
'118.562', '63.524', '17.587', '2.537'
];
return common.sleep(4000)
return PageObjects.common.sleep(4000)
.then(function () {
return verifyChartData(expectedBarChartData);
});
@ -111,7 +109,7 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct time range string in chart', function () {
var expectedTimeRangeString = fromTimeString + ' - ' + toTimeString;
return discoverPage.getChartTimespan()
return PageObjects.discover.getChartTimespan()
.then(function (actualTimeString) {
expect(actualTimeString).to.be(expectedTimeRangeString);
});
@ -119,7 +117,7 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct initial chart interval of 3 hours', function () {
var expectedChartInterval = 'by 3 hours';
return discoverPage.getChartInterval()
return PageObjects.discover.getChartInterval()
.then(function (actualInterval) {
expect(actualInterval).to.be(expectedChartInterval);
});
@ -140,9 +138,9 @@ bdd.describe('discover app', function describeIndexTests() {
'120.4', '96.472', '74.581', '70.509', '39.709', '25.199', '13.490',
'12.472', '4.072', '2.290', '1.018'
];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(4000);
return PageObjects.common.sleep(4000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -154,9 +152,9 @@ bdd.describe('discover app', function describeIndexTests() {
var expectedBarChartData = [
'133.196', '129.192', '129.724'
];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(4000);
return PageObjects.common.sleep(4000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -166,9 +164,9 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct data for chart interval Weekly', function () {
var chartInterval = 'Weekly';
var expectedBarChartData = [ '66.598', '129.458'];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(2000);
return PageObjects.common.sleep(2000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -182,8 +180,8 @@ bdd.describe('discover app', function describeIndexTests() {
];
return this.remote.goBack()
.then(function () {
return common.try(function tryingForTime() {
return discoverPage.getChartInterval()
return PageObjects.common.try(function tryingForTime() {
return PageObjects.discover.getChartInterval()
.then(function (actualInterval) {
expect(actualInterval).to.be(expectedChartInterval);
});
@ -197,9 +195,9 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct data for chart interval Monthly', function () {
var chartInterval = 'Monthly';
var expectedBarChartData = [ '122.535'];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(2000);
return PageObjects.common.sleep(2000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -209,9 +207,9 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show correct data for chart interval Yearly', function () {
var chartInterval = 'Yearly';
var expectedBarChartData = [ '122.535'];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(2000);
return PageObjects.common.sleep(2000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -226,9 +224,9 @@ bdd.describe('discover app', function describeIndexTests() {
'61.862', '15.487', '2.362', '2.800', '15.312', '61.862', '123.2',
'118.562', '63.524', '17.587', '2.537'
];
return discoverPage.setChartInterval(chartInterval)
return PageObjects.discover.setChartInterval(chartInterval)
.then(function () {
return common.sleep(4000);
return PageObjects.common.sleep(4000);
})
.then(function () {
return verifyChartData(expectedBarChartData);
@ -237,21 +235,21 @@ bdd.describe('discover app', function describeIndexTests() {
bdd.it('should show Auto chart interval of 3 hours', function () {
var expectedChartInterval = 'by 3 hours';
return discoverPage.getChartInterval()
return PageObjects.discover.getChartInterval()
.then(function (actualInterval) {
expect(actualInterval).to.be(expectedChartInterval);
});
});
bdd.it('should not show "no results"', () => {
return discoverPage.hasNoResults().then(visible => {
return PageObjects.discover.hasNoResults().then(visible => {
expect(visible).to.be(false);
});
});
function verifyChartData(expectedBarChartData) {
return common.try(function tryingForTime() {
return discoverPage.getBarChartData()
return PageObjects.common.try(function tryingForTime() {
return PageObjects.discover.getBarChartData()
.then(function compareData(paths) {
// the largest bars are over 100 pixels high so this is less than 1% tolerance
var barHeightTolerance = 1;
@ -265,8 +263,8 @@ bdd.describe('discover app', function describeIndexTests() {
};
};
if (hasFailure) {
common.log(stringResults);
common.log(paths);
PageObjects.common.log(stringResults);
PageObjects.common.log(paths);
}
for (var x = 0; x < expectedBarChartData.length; x++) {
expect(Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance).to.be.ok();
@ -278,26 +276,25 @@ bdd.describe('discover app', function describeIndexTests() {
});
bdd.describe('query #2, which has an empty time range', function () {
var fromTime = '1999-06-11 09:22:11.000';
var toTime = '1999-06-12 11:21:04.000';
bdd.before(() => {
common.debug('setAbsoluteRangeForAnotherQuery');
return headerPage
PageObjects.common.debug('setAbsoluteRangeForAnotherQuery');
return PageObjects.header
.setAbsoluteRange(fromTime, toTime);
});
bdd.it('should show "no results"', () => {
return discoverPage.hasNoResults().then(visible => {
common.saveScreenshot('Discover-no-results');
return PageObjects.discover.hasNoResults().then(visible => {
PageObjects.common.saveScreenshot('Discover-no-results');
expect(visible).to.be(true);
});
});
bdd.it('should suggest a new time range is picked', () => {
return discoverPage.hasNoResultsTimepicker().then(visible => {
return PageObjects.discover.hasNoResultsTimepicker().then(visible => {
expect(visible).to.be(true);
});
});
@ -311,25 +308,25 @@ bdd.describe('discover app', function describeIndexTests() {
.then(() => isTimepickerOpen(true))
.then(el => el.click()) // close
.then(() => isTimepickerOpen(false))
.catch(common.handleError(this))
.catch(PageObjects.common.createErrorHandler(this))
);
function closeTimepicker() {
return headerPage.isTimepickerOpen().then(shown => {
return PageObjects.header.isTimepickerOpen().then(shown => {
if (!shown) {
return;
}
return discoverPage
return PageObjects.discover
.getNoResultsTimepicker()
.click(); // close
});
}
function isTimepickerOpen(expected) {
return headerPage.isTimepickerOpen().then(shown => {
common.debug(`expect (#${++i}) timepicker to be ${peek(expected)} (is ${peek(shown)}).`);
return PageObjects.header.isTimepickerOpen().then(shown => {
PageObjects.common.debug(`expect (#${++i}) timepicker to be ${peek(expected)} (is ${peek(shown)}).`);
expect(shown).to.be(expected);
return discoverPage.getNoResultsTimepicker();
return PageObjects.discover.getNoResultsTimepicker();
function peek(state) {
return state ? 'open' : 'closed';
}

View file

@ -1,10 +1,12 @@
import { bdd, remote, scenarioManager, defaultTimeout } from '../../../support';
import PageObjects from '../../../support/page_objects';
bdd.describe('discover app', function () {
this.timeout = defaultTimeout;
bdd.before(function () {
return remote.setWindowSize(1200,800);
return PageObjects.remote.setWindowSize(1200,800);
});
bdd.after(function unloadMakelogs() {
@ -12,7 +14,8 @@ bdd.describe('discover app', function () {
});
require('./_discover');
require('./_field_data');
require('./_shared_links');
require('./_collapse_expand');
// TODO: Convert the rest of these to use PageObjects.
// require('./_field_data');
// require('./_shared_links');
// require('./_collapse_expand');
});

View file

@ -35,7 +35,7 @@ bdd.describe('user input reactions', function () {
.then(function () {
common.saveScreenshot('Settings-indices-hide-time-based-index-pattern');
// we expect the promise above to fail
var handler = common.handleError(self);
var handler = common.createErrorHandler(self);
var msg = 'Found time based index pattern checkbox';
handler(msg);
})

View file

@ -127,7 +127,7 @@ bdd.describe('index result field sort', function describeIndexTests() {
});
}, Promise.resolve());
return chain.catch(common.handleError(this));
return chain.catch(common.createErrorHandler(this));
});
}); // end describe pagination
}); // end index result field sort

View file

@ -3,29 +3,26 @@ define(function (require) {
const bdd = require('intern!bdd');
const intern = require('intern');
const initCallbacks = [];
function onInit(callback) {
initCallbacks.push(callback);
}
global.__kibana__intern__ = { intern, bdd, onInit };
global.__kibana__intern__ = { intern, bdd };
bdd.describe('kibana', function () {
bdd.before(function () {
initCallbacks.forEach(callback => {
callback.call(this);
});
this.PageObjects.init(this.remote);
});
require([
'intern/dojo/node!../support/index',
'intern/dojo/node!../support/page_objects.js',
'intern/dojo/node!../support',
'intern/dojo/node!./apps/discover',
'intern/dojo/node!./status_page',
'intern/dojo/node!./apps/management',
'intern/dojo/node!./apps/visualize',
'intern/dojo/node!./apps/console',
'intern/dojo/node!./apps/dashboard'
], function () {});
// TODO: Convert the rest of these to use PageObjects.
// 'intern/dojo/node!./status_page',
// 'intern/dojo/node!./apps/management',
// 'intern/dojo/node!./apps/visualize',
// 'intern/dojo/node!./apps/console',
// 'intern/dojo/node!./apps/dashboard'
], PageObjects => {
this.PageObjects = PageObjects;
});
});
});

View file

@ -21,6 +21,6 @@ bdd.describe('status page', function () {
expect(text.indexOf('plugin:kibana')).to.be.above(-1);
});
})
.catch(common.handleError(self));
.catch(common.createErrorHandler(self));
});
});

View file

@ -1,6 +1,7 @@
import { attempt } from 'bluebird';
import { common } from './';
import PageObjects from './page_objects';
export default class BddWrapper {
constructor(real) {
@ -12,7 +13,8 @@ export default class BddWrapper {
// by the test runner, so don't use an arrow
return function () {
const suiteOrTest = this;
return attempt(fn.bind(suiteOrTest)).catch(common.handleError(suiteOrTest));
const errorHandler = PageObjects.common.createErrorHandler(suiteOrTest);
return attempt(fn.bind(suiteOrTest)).catch(errorHandler);
};
}

View file

@ -1,4 +1,5 @@
import { common, config} from './';
import { config } from './';
import Log from './log.js';
export default (function () {
var util = require('util');
@ -45,13 +46,13 @@ export default (function () {
};
var dumper = new Elasticdump(options.input, options.output, options);
dumper.on('log', function (message) { common.debug(message); });
dumper.on('error', function (error) { common.debug('error', 'Error Emitted => ' + (error.message || JSON.stringify(error))); });
dumper.on('log', function (message) { Log.debug(message); });
dumper.on('error', function (error) { Log.debug('error', 'Error Emitted => ' + (error.message || JSON.stringify(error))); });
var promise = new Promise(function (resolve, reject) {
dumper.dump(function (error, totalWrites) {
if (error) {
common.debug('THERE WAS AN ERROR :-(');
Log.debug('THERE WAS AN ERROR :-(');
reject(Error(error));
} else {
resolve ('elasticdumpModule success');
@ -70,12 +71,12 @@ export default (function () {
*/
elasticDump: function elasticDump(index, file) {
var self = this;
common.debug('Dumping mapping from ' + url.format(config.servers.elasticsearch) + '/' + index
Log.debug('Dumping mapping from ' + url.format(config.servers.elasticsearch) + '/' + index
+ ' to (' + file + '.mapping.json)');
return this.elasticdumpModule(url.format(config.servers.elasticsearch),
file + '.mapping.json', index, 'mapping')
.then(function () {
common.debug('Dumping data from ' + url.format(config.servers.elasticsearch) + '/' + index
Log.debug('Dumping data from ' + url.format(config.servers.elasticsearch) + '/' + index
+ ' to (' + file + '.data.json)');
return self.elasticdumpModule(url.format(config.servers.elasticsearch),
file + '.data.json', index, 'data');
@ -92,12 +93,12 @@ export default (function () {
// TODO: should we have a flag to delete the index first?
// or use scenarioManager.unload(index) ? <<- currently this
var self = this;
common.debug('Loading mapping (test/fixtures/dump_data/' + file + '.mapping.json) into '
Log.debug('Loading mapping (test/fixtures/dump_data/' + file + '.mapping.json) into '
+ url.format(config.servers.elasticsearch) + '/' + index);
return this.elasticdumpModule('test/fixtures/dump_data/' + file + '.mapping.json',
url.format(config.servers.elasticsearch), index, 'mapping')
.then(function () {
common.debug('Loading data (test/fixtures/dump_data/' + file + '.data.json) into '
Log.debug('Loading data (test/fixtures/dump_data/' + file + '.data.json) into '
+ url.format(config.servers.elasticsearch) + '/' + index);
return self.elasticdumpModule('test/fixtures/dump_data/' + file + '.data.json',
url.format(config.servers.elasticsearch), index, 'data');

View file

@ -1,4 +1,6 @@
import { common, remote} from './';
import Log from './log.js';
import Try from './try.js';
export default (function () {
@ -6,7 +8,6 @@ export default (function () {
var Promise = require('bluebird');
function EsClient(server) {
this.remote = remote;
if (!server) throw new Error('No server defined');
// NOTE: some large sets of test data can take several minutes to load
@ -36,7 +37,7 @@ export default (function () {
.catch(function (reason) {
// if the index never existed yet, or was already deleted it's OK
if (reason.message.indexOf('index_not_found_exception') < 0) {
common.debug('reason.message: ' + reason.message);
Log.debug('reason.message: ' + reason.message);
throw reason;
}
});
@ -65,7 +66,7 @@ export default (function () {
);
} else {
configId = response.hits.hits[0]._id;
common.debug('config._id =' + configId);
Log.debug('config._id =' + configId);
return configId;
}
});
@ -85,7 +86,7 @@ export default (function () {
return this.getConfigId()
// now that we have the id, we can update
.then(function (configId) {
common.debug('updating config with ' + docMapString);
Log.debug('updating config with ' + docMapString);
return self.client.update({
index: '.kibana',
type: 'config',
@ -113,12 +114,12 @@ export default (function () {
return this.delete('.kibana')
.then(function () {
if (!docMap) {
return common.try(function () {
return Try.try(function () {
return self.getConfigId();
});
} else {
var docMapString = JSON.stringify(docMap);
return common.try(function () {
return Try.try(function () {
return self.updateConfigDoc(docMap);
});
}

View file

@ -1,86 +1,23 @@
import url from 'url';
import EsClient from './es_client';
import ElasticDump from './elastic_dump';
import BddWrapper from './bdd_wrapper';
import ScenarioManager from '../fixtures/scenario_manager';
import Common from './pages/common';
import DiscoverPage from './pages/discover_page';
import SettingsPage from './pages/settings_page';
import HeaderPage from './pages/header_page';
import VisualizePage from './pages/visualize_page';
import DashboardPage from './pages/dashboard_page';
import ShieldPage from './pages/shield_page';
import ConsolePage from './pages/console_page';
// Intern values provided via the root index file of the test suite.
const kbnInternVars = global.__kibana__intern__;
exports.intern = kbnInternVars.intern;
exports.config = exports.intern.config;
exports.defaultTimeout = exports.config.defaultTimeout;
exports.defaultTryTimeout = exports.config.defaultTryTimeout;
exports.defaultFindTimeout = exports.config.defaultFindTimeout;
exports.scenarioManager = new ScenarioManager(url.format(exports.config.servers.elasticsearch));
exports.esClient = new EsClient(url.format(exports.config.servers.elasticsearch));
exports.bdd = new BddWrapper(kbnInternVars.bdd);
const delayedExports = [{
name: 'remote',
exportProvider: (suite) => suite.remote
}, {
name: 'common',
exportProvider: () => new Common()
}, {
name: 'discoverPage',
exportProvider: () => new DiscoverPage()
}, {
name: 'headerPage',
exportProvider: () => new HeaderPage()
}, {
name: 'settingsPage',
exportProvider: () => new SettingsPage()
}, {
name: 'visualizePage',
exportProvider: () => new VisualizePage()
}, {
name: 'dashboardPage',
exportProvider: () => new DashboardPage()
}, {
name: 'shieldPage',
exportProvider: () => new ShieldPage()
}, {
name: 'consolePage',
exportProvider: () => new ConsolePage()
}, {
name: 'elasticDump',
exportProvider: () => new ElasticDump()
}];
// Config options
const config = exports.config = kbnInternVars.intern.config;
exports.defaultTimeout = config.defaultTimeout;
exports.defaultTryTimeout = config.defaultTryTimeout;
exports.defaultFindTimeout = config.defaultFindTimeout;
delayedExports.forEach(exportConfig => {
delayExportUntilInit(
exportConfig.name,
exportConfig.exportProvider
);
});
// Creates an export for values that aren't actually avaialable until
// tests start to run. These getters will throw errors if the export
// is accessed before it's available, hopefully making debugging easier.
// Once the first before() handler is called the onInit() call
// will fire and rewrite all of these exports to be their correct value.
function delayExportUntilInit(name, provideExport) {
Object.defineProperty(exports, name, {
configurable: true,
get() {
throw new TypeError(
'Remote is not available until tests start to run. Move your ' +
'usage of the import inside a test setup hook or a test itself.'
);
}
});
kbnInternVars.onInit(function defineExport() {
Object.defineProperty(exports, name, {
value: provideExport(this),
});
});
}
// Helper instances
exports.scenarioManager =
new ScenarioManager(url.format(config.servers.elasticsearch));
exports.elasticDump = new ElasticDump();
exports.esClient = new EsClient(url.format(config.servers.elasticsearch));

17
test/support/log.js Normal file
View file

@ -0,0 +1,17 @@
import moment from 'moment';
import util from 'util';
import {
config
} from './index';
export default {
log(...args) {
console.log(moment().format('HH:mm:ss.SSS') + ':', util.format(...args));
},
debug(...args) {
if (config.debug) this.log(...args);
}
};

View file

@ -0,0 +1,74 @@
import Common from './pages/common.js';
import ConsolePage from './pages/console_page.js';
import DashboardPage from './pages/dashboard_page.js';
import DiscoverPage from './pages/discover_page.js';
import HeaderPage from './pages/header_page.js';
import SettingsPage from './pages/settings_page.js';
import ShieldPage from './pages/shield_page.js';
import VisualizePage from './pages/visualize_page.js';
const common = new Common();
const consolePage = new ConsolePage();
const dashboardPage = new DashboardPage();
const discoverPage = new DiscoverPage();
const headerPage = new HeaderPage();
const settingsPage = new SettingsPage();
const shieldPage = new ShieldPage();
const visualizePage = new VisualizePage();
export default {
isInitialized: false,
init: function init(remote) {
this.isInitialized = true;
this.remote = remote;
common.init(remote);
consolePage.init(remote);
dashboardPage.init(remote);
discoverPage.init(remote);
headerPage.init(remote);
settingsPage.init(remote);
shieldPage.init(remote);
visualizePage.init(remote);
},
checkInitialization() {
if (this.isInitialized) {
return true;
}
throw new TypeError('Please call init and provide a reference to `remote` before trying to access a page object.');
},
get common() {
return this.checkInitialization() && common;
},
get console() {
return this.checkInitialization() && consolePage;
},
get dashboard() {
return this.checkInitialization() && dashboardPage;
},
get discover() {
return this.checkInitialization() && discoverPage;
},
get header() {
return this.checkInitialization() && headerPage;
},
get settings() {
return this.checkInitialization() && settingsPage;
},
get shield() {
return this.checkInitialization() && shieldPage;
},
get visualize() {
return this.checkInitialization() && visualizePage;
},
};

View file

@ -22,12 +22,18 @@ import {
shieldPage
} from '../index';
import Log from '../log.js';
import Try from '../try.js';
const mkdirpAsync = promisify(mkdirp);
const writeFileAsync = promisify(fs.writeFile);
export default class Common {
constructor() {
}
init(remote) {
function injectTimestampQuery(func, url) {
var formatted = modifyQueryString(url, function (parsed) {
parsed.query._t = Date.now();
@ -224,41 +230,19 @@ export default class Common {
}
tryForTime(timeout, block) {
var self = this;
var start = Date.now();
var retryDelay = 502;
var lastTry = 0;
var tempMessage;
function attempt() {
lastTry = Date.now();
if (lastTry - start > timeout) {
throw new Error('timeout ' + tempMessage);
}
return bluebird
.try(block)
.catch(function tryForTimeCatch(err) {
self.debug('tryForTime failure: ' + err.message);
tempMessage = err.message;
return bluebird.delay(retryDelay).then(attempt);
});
}
return bluebird.try(attempt);
return Try.tryForTime(timeout, block);
}
try(block) {
return this.tryForTime(defaultTryTimeout, block);
return Try.try(block);
}
log(...args) {
console.log(moment().format('HH:mm:ss.SSS') + ':', util.format(...args));
Log.log(...args);
}
debug(...args) {
if (config.debug) this.log(...args);
Log.debug(...args);
}
sleep(sleepMilliseconds) {
@ -269,15 +253,15 @@ export default class Common {
.then(function () { self.debug('... sleep(' + sleepMilliseconds + ') end'); });
}
handleError(testObj) {
createErrorHandler(testObj) {
const testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name;
return reason => {
return error => {
const now = Date.now();
const fileName = `failure_${now}_${testName}`;
return this.saveScreenshot(fileName, true)
.then(function () {
throw reason;
throw error;
});
};
}
@ -304,4 +288,4 @@ export default class Common {
.findDisplayedByCssSelector(testSubjSelector(selector));
}
};
}

View file

@ -8,13 +8,15 @@ export default (function (require) {
var thisTime;
function ConsolePage() {
this.remote = remote;
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
}
ConsolePage.prototype = {
constructor: ConsolePage,
init(remote) {
this.remote = remote;
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
},
getServer: function getServer() {
return thisTime

View file

@ -4,13 +4,16 @@ export default (function () {
var thisTime;
function DashboardPage() {
this.remote = remote;
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
}
DashboardPage.prototype = {
constructor: DashboardPage,
init(remote) {
this.remote = remote;
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
},
clickNewDashboard: function clickNewDashboard() {
return thisTime
.findByCssSelector('button.ng-scope[aria-label="New Dashboard"]')

View file

@ -1,250 +1,251 @@
import { remote, common, defaultFindTimeout } from '../';
export default (function () {
var thisTime;
import Common from './common.js';
import { defaultFindTimeout } from '../';
function DiscoverPage() {
this.remote = remote;
let thisTime;
export default class DiscoverPage extends Common {
constructor() {
super();
}
init(remote) {
super.init(remote);
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
}
DiscoverPage.prototype = {
constructor: DiscoverPage,
getQueryField() {
return thisTime
.findByCssSelector('input[ng-model=\'state.query\']');
}
getQueryField: function getQueryField() {
return thisTime
.findByCssSelector('input[ng-model=\'state.query\']');
},
getQuerySearchButton() {
return thisTime
.findByCssSelector('button[aria-label=\'Search\']');
}
getQuerySearchButton: function getQuerySearchButton() {
return thisTime
.findByCssSelector('button[aria-label=\'Search\']');
},
getTimespanText() {
return thisTime
.findByCssSelector('.kibana-nav-options .navbar-timepicker-time-desc pretty-duration')
.getVisibleText();
}
getTimespanText: function getTimespanText() {
return thisTime
.findByCssSelector('.kibana-nav-options .navbar-timepicker-time-desc pretty-duration')
getChartTimespan() {
return thisTime
.findByCssSelector('center.small > span:nth-child(1)')
.getVisibleText();
}
saveSearch(searchName) {
return this.clickSaveSearchButton()
.then(() => {
this.debug('--saveSearch button clicked');
return thisTime.findDisplayedById('SaveSearch')
.pressKeys(searchName);
})
.then(() => {
this.debug('--find save button');
return this.findTestSubject('discover-save-search-btn').click();
});
}
loadSavedSearch(searchName) {
var self = this;
return self.clickLoadSavedSearchButton()
.then(function () {
thisTime.findByLinkText(searchName).click();
});
}
clickNewSearchButton() {
return thisTime
.findByCssSelector('button[aria-label="New Search"]')
.click();
}
clickSaveSearchButton() {
return thisTime
.findByCssSelector('button[aria-label="Save Search"]')
.click();
}
clickLoadSavedSearchButton() {
return thisTime
.findDisplayedByCssSelector('button[aria-label="Load Saved Search"]')
.click();
}
getCurrentQueryName() {
return thisTime
.findByCssSelector('span.kibana-nav-info-title span')
.getVisibleText();
},
}
getChartTimespan: function getChartTimespan() {
return thisTime
.findByCssSelector('center.small > span:nth-child(1)')
.getVisibleText();
},
getBarChartData() {
return thisTime
.findAllByCssSelector('rect[data-label="Count"]')
.then(function (chartData) {
saveSearch: function saveSearch(searchName) {
var self = this;
return self.clickSaveSearchButton()
.then(function () {
common.debug('--saveSearch button clicked');
return thisTime.findDisplayedById('SaveSearch')
.pressKeys(searchName);
})
.then(function clickSave() {
common.debug('--find save button');
return common.findTestSubject('discover-save-search-btn').click();
});
},
function getChartData(chart) {
return chart
.getAttribute('height');
}
loadSavedSearch: function loadSavedSearch(searchName) {
var self = this;
return self.clickLoadSavedSearchButton()
.then(function () {
thisTime.findByLinkText(searchName).click();
});
},
var getChartDataPromises = chartData.map(getChartData);
return Promise.all(getChartDataPromises);
})
.then(function (bars) {
return bars;
});
}
clickNewSearchButton: function clickNewSearchButton() {
return thisTime
.findByCssSelector('button[aria-label="New Search"]')
.click();
},
clickSaveSearchButton: function clickSaveSearchButton() {
return thisTime
.findByCssSelector('button[aria-label="Save Search"]')
.click();
},
clickLoadSavedSearchButton: function clickLoadSavedSearchButton() {
return thisTime
.findDisplayedByCssSelector('button[aria-label="Load Saved Search"]')
.click();
},
getCurrentQueryName: function getCurrentQueryName() {
return thisTime
.findByCssSelector('span.kibana-nav-info-title span')
.getVisibleText();
},
getBarChartData: function getBarChartData() {
return thisTime
.findAllByCssSelector('rect[data-label="Count"]')
.then(function (chartData) {
function getChartData(chart) {
return chart
.getAttribute('height');
}
var getChartDataPromises = chartData.map(getChartData);
return Promise.all(getChartDataPromises);
})
.then(function (bars) {
return bars;
});
},
getChartInterval: function getChartInterval() {
return thisTime
.findByCssSelector('a[ng-click="toggleInterval()"]')
.getVisibleText()
.then(function (intervalText) {
if (intervalText.length > 0) {
return intervalText;
} else {
getChartInterval() {
return thisTime
.findByCssSelector('a[ng-click="toggleInterval()"]')
.getVisibleText()
.then(function (intervalText) {
if (intervalText.length > 0) {
return intervalText;
} else {
return thisTime
.findByCssSelector('select[ng-model="state.interval"]')
.getProperty('value') // this gets 'string:d' for Daily
.then(function (selectedValue) {
return thisTime
.findByCssSelector('select[ng-model="state.interval"]')
.getProperty('value') // this gets 'string:d' for Daily
.then(function (selectedValue) {
return thisTime
.findByCssSelector('option[value="' + selectedValue + '"]')
.getVisibleText();
});
}
});
},
.findByCssSelector('option[value="' + selectedValue + '"]')
.getVisibleText();
});
}
});
}
setChartInterval: function setChartInterval(interval) {
return this.remote.setFindTimeout(5000)
.findByCssSelector('a[ng-click="toggleInterval()"]')
.click()
.catch(function () {
// in some cases we have the link above, but after we've made a
// selection we just have a select list.
})
.then(function () {
return thisTime
.findByCssSelector('option[label="' + interval + '"]')
.click();
});
},
getHitCount: function getHitCount() {
setChartInterval(interval) {
return this.remote.setFindTimeout(5000)
.findByCssSelector('a[ng-click="toggleInterval()"]')
.click()
.catch(function () {
// in some cases we have the link above, but after we've made a
// selection we just have a select list.
})
.then(function () {
return thisTime
.findByCssSelector('strong.discover-info-hits')
.getVisibleText();
},
query: function query(queryString) {
return thisTime
.findByCssSelector('input[aria-label="Search input"]')
.clearValue()
.type(queryString)
.then(function () {
return thisTime
.findByCssSelector('button[aria-label="Search"]')
.click();
});
},
getDocHeader: function getDocHeader() {
return thisTime
.findByCssSelector('thead.ng-isolate-scope > tr:nth-child(1)')
.getVisibleText();
},
getDocTableIndex: function getDocTableIndex(index) {
return thisTime
.findByCssSelector('tr.discover-table-row:nth-child(' + (index) + ')')
.getVisibleText();
},
clickDocSortDown: function clickDocSortDown() {
return thisTime
.findByCssSelector('.fa-sort-down')
.findByCssSelector('option[label="' + interval + '"]')
.click();
},
});
}
clickDocSortUp: function clickDocSortUp() {
getHitCount() {
return thisTime
.findByCssSelector('strong.discover-info-hits')
.getVisibleText();
}
query(queryString) {
return thisTime
.findByCssSelector('input[aria-label="Search input"]')
.clearValue()
.type(queryString)
.then(function () {
return thisTime
.findByCssSelector('.fa-sort-up')
.findByCssSelector('button[aria-label="Search"]')
.click();
},
});
}
getMarks: function getMarks() {
return thisTime
.findAllByCssSelector('mark')
.getVisibleText();
},
getDocHeader() {
return thisTime
.findByCssSelector('thead.ng-isolate-scope > tr:nth-child(1)')
.getVisibleText();
}
clickShare: function clickShare() {
return thisTime
.findByCssSelector('button[aria-label="Share Search"]')
getDocTableIndex(index) {
return thisTime
.findByCssSelector('tr.discover-table-row:nth-child(' + (index) + ')')
.getVisibleText();
}
clickDocSortDown() {
return thisTime
.findByCssSelector('.fa-sort-down')
.click();
}
clickDocSortUp() {
return thisTime
.findByCssSelector('.fa-sort-up')
.click();
}
getMarks() {
return thisTime
.findAllByCssSelector('mark')
.getVisibleText();
}
clickShare() {
return thisTime
.findByCssSelector('button[aria-label="Share Search"]')
.click();
}
clickShortenUrl() {
return thisTime
.findByCssSelector('button.shorten-button')
.click();
}
clickCopyToClipboard() {
return thisTime
.findDisplayedByCssSelector('button.clipboard-button')
.click();
}
getShareCaption() {
return thisTime
.findByCssSelector('.vis-share label')
.getVisibleText();
}
getSharedUrl() {
return thisTime
.findByCssSelector('.url')
.getProperty('value');
}
getShortenedUrl() {
return thisTime
.findByCssSelector('.url')
.getProperty('value');
}
toggleSidebarCollapse() {
return thisTime.findDisplayedByCssSelector('.sidebar-collapser .chevron-cont')
.click();
},
}
clickShortenUrl: function clickShortenUrl() {
return thisTime
.findByCssSelector('button.shorten-button')
.click();
},
getSidebarWidth() {
return thisTime
.findByClassName('sidebar-list')
.getProperty('clientWidth');
}
clickCopyToClipboard: function clickCopyToClipboard() {
return thisTime
.findDisplayedByCssSelector('button.clipboard-button')
.click();
},
hasNoResults() {
return this
.findTestSubject('discoverNoResults')
.then(() => true)
.catch(() => false);
}
getShareCaption: function getShareCaption() {
return thisTime
.findByCssSelector('.vis-share label')
.getVisibleText();
},
getNoResultsTimepicker() {
return this.findTestSubject('discoverNoResultsTimefilter');
}
getSharedUrl: function getSharedUrl() {
return thisTime
.findByCssSelector('.url')
.getProperty('value');
},
hasNoResultsTimepicker() {
return this
.getNoResultsTimepicker()
.then(() => true)
.catch(() => false);
}
getShortenedUrl: function getShortenedUrl() {
return thisTime
.findByCssSelector('.url')
.getProperty('value');
},
toggleSidebarCollapse: function toggleSidebarCollapse() {
return thisTime.findDisplayedByCssSelector('.sidebar-collapser .chevron-cont')
.click();
},
getSidebarWidth: function getSidebarWidth() {
return thisTime
.findByClassName('sidebar-list')
.getProperty('clientWidth');
},
hasNoResults: function hasNoResults() {
return common
.findTestSubject('discoverNoResults')
.then(() => true)
.catch(() => false);
},
getNoResultsTimepicker: function getNoResultsTimepicker() {
return common.findTestSubject('discoverNoResultsTimefilter');
},
hasNoResultsTimepicker: function hasNoResultsTimepicker() {
return this
.getNoResultsTimepicker()
.then(() => true)
.catch(() => false);
}
};
return DiscoverPage;
}());
}

View file

@ -1,153 +1,149 @@
import { common, remote, defaultFindTimeout } from '../';
export default (function () {
import Common from './common.js';
import { defaultFindTimeout } from '../';
// the page object is created as a constructor
// so we can provide the remote Command object
// at runtime
function HeaderPage() {
this.remote = remote;
export default class HeaderPage extends Common {
constructor() {
super();
}
HeaderPage.prototype = {
constructor: HeaderPage,
init(remote) {
super.init(remote);
}
clickSelector: function (selector) {
var self = this;
return common.try(function () {
return self.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector(selector)
.then(function (tab) {
return tab.click();
});
});
},
clickDiscover: function () {
common.debug('click Discover tab');
this.clickSelector('a[href*=\'discover\']');
},
clickVisualize: function () {
common.debug('click Visualize tab');
this.clickSelector('a[href*=\'visualize\']');
},
clickDashboard: function () {
common.debug('click Dashboard tab');
this.clickSelector('a[href*=\'dashboard\']');
},
clickSettings: function () {
common.debug('click Settings tab');
this.clickSelector('a[href*=\'settings\']');
},
clickTimepicker: function clickTimepicker() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByClassName('navbar-timepicker-time-desc').click();
},
isTimepickerOpen: function isTimepickerOpen() {
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('.kbn-timepicker')
.then(() => true)
.catch(() => false);
},
clickAbsoluteButton: function clickAbsoluteButton() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByLinkText('Absolute').click();
},
setFromTime: function setFromTime(timeString) {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.from\']')
.clearValue()
.type(timeString);
},
setToTime: function setToTime(timeString) {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.to\']')
.clearValue()
.type(timeString);
},
clickGoButton: function clickGoButton() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByClassName('kbn-timepicker-go')
.click()
.then(function () {
return self.getSpinnerDone();
});
},
setAbsoluteRange: function setAbsoluteRange(fromTime, toTime) {
var self = this;
common.debug('clickTimepicker');
return self.clickTimepicker()
.then(function () {
common.debug('--Clicking Absolute button');
return self.clickAbsoluteButton();
})
.then(function () {
common.debug('--Setting From Time : ' + fromTime);
return self.setFromTime(fromTime);
})
.then(function () {
common.debug('--Setting To Time : ' + toTime);
return self.setToTime(toTime);
})
.then(function () {
return self.clickGoButton();
})
.then(function () {
return self.getSpinnerDone();
})
.then(function () {
return self.collapseTimepicker();
});
},
collapseTimepicker: function collapseTimepicker() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('.fa.fa-chevron-circle-up')
.click();
},
getToastMessage: function getToastMessage() {
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('kbn-truncated.toast-message.ng-isolate-scope')
.getVisibleText();
},
waitForToastMessageGone: function waitForToastMessageGone() {
var self = this;
clickSelector(selector) {
var self = this;
return this.try(function () {
return self.remote.setFindTimeout(defaultFindTimeout)
.waitForDeletedByCssSelector('kbn-truncated.toast-message');
},
.findByCssSelector(selector)
.then(function (tab) {
return tab.click();
});
});
}
clickToastOK: function clickToastOK() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button[ng-if="notif.accept"]')
.click();
},
clickDiscover() {
this.debug('click Discover tab');
this.clickSelector('a[href*=\'discover\']');
}
getSpinnerDone: function getSpinnerDone() {
var self = this;
return this.remote
.setFindTimeout(defaultFindTimeout * 10)
.findByCssSelector('.spinner.ng-hide');
}
clickVisualize() {
this.debug('click Visualize tab');
this.clickSelector('a[href*=\'visualize\']');
}
};
clickDashboard() {
this.debug('click Dashboard tab');
this.clickSelector('a[href*=\'dashboard\']');
}
return HeaderPage;
}());
clickSettings() {
this.debug('click Settings tab');
this.clickSelector('a[href*=\'settings\']');
}
clickTimepicker() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByClassName('navbar-timepicker-time-desc').click();
}
isTimepickerOpen() {
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('.kbn-timepicker')
.then(() => true)
.catch(() => false);
}
clickAbsoluteButton() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByLinkText('Absolute').click();
}
setFromTime(timeString) {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.from\']')
.clearValue()
.type(timeString);
}
setToTime(timeString) {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model=\'absolute.to\']')
.clearValue()
.type(timeString);
}
clickGoButton() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByClassName('kbn-timepicker-go')
.click()
.then(function () {
return self.getSpinnerDone();
});
}
setAbsoluteRange(fromTime, toTime) {
this.debug('clickTimepicker');
return this.clickTimepicker()
.then(() => {
this.debug('--Clicking Absolute button');
return this.clickAbsoluteButton();
})
.then(() => {
this.debug('--Setting From Time : ' + fromTime);
return this.setFromTime(fromTime);
})
.then(() => {
this.debug('--Setting To Time : ' + toTime);
return this.setToTime(toTime);
})
.then(() => {
return this.clickGoButton();
})
.then(() => {
return this.getSpinnerDone();
})
.then(() => {
return this.collapseTimepicker();
});
}
collapseTimepicker() {
var self = this;
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('.fa.fa-chevron-circle-up')
.click();
}
getToastMessage() {
return this.remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('kbn-truncated.toast-message.ng-isolate-scope')
.getVisibleText();
}
waitForToastMessageGone() {
var self = this;
return self.remote.setFindTimeout(defaultFindTimeout)
.waitForDeletedByCssSelector('kbn-truncated.toast-message');
}
clickToastOK() {
return this.remote
.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button[ng-if="notif.accept"]')
.click();
}
getSpinnerDone() {
var self = this;
return this.remote
.setFindTimeout(defaultFindTimeout * 10)
.findByCssSelector('.spinner.ng-hide');
}
}

View file

@ -3,12 +3,15 @@ import { common, remote, defaultFindTimeout, headerPage } from '../';
export default (function () {
function SettingsPage() {
this.remote = remote;
}
SettingsPage.prototype = {
constructor: SettingsPage,
init(remote) {
this.remote = remote;
},
clickNavigation: function () {
// TODO: find better way to target the element
return this.remote.findDisplayedByCssSelector('.app-link:nth-child(5) a').click();

View file

@ -6,12 +6,15 @@ export default (function (require) {
// so we can provide the remote Command object
// at runtime
function ShieldPage() {
this.remote = remote;
}
ShieldPage.prototype = {
constructor: ShieldPage,
init(remote) {
this.remote = remote;
},
login: function login(user, pwd) {
var remote = this.remote;
return remote.setFindTimeout(defaultFindTimeout)

View file

@ -2,12 +2,15 @@ import { common, defaultFindTimeout, remote } from '../';
export default (function () {
function VisualizePage() {
this.remote = remote;
}
VisualizePage.prototype = {
constructor: VisualizePage,
init(remote) {
this.remote = remote;
},
clickAreaChart: function clickAreaChart() {
return this.remote
.setFindTimeout(defaultFindTimeout)

40
test/support/try.js Normal file
View file

@ -0,0 +1,40 @@
import bluebird from 'bluebird';
import {
defaultTryTimeout
} from './index';
import Log from './log.js';
export default {
tryForTime(timeout, block) {
var self = this;
var start = Date.now();
var retryDelay = 502;
var lastTry = 0;
var tempMessage;
function attempt() {
lastTry = Date.now();
if (lastTry - start > timeout) {
throw new Error('timeout ' + tempMessage);
}
return bluebird
.try(block)
.catch(function tryForTimeCatch(err) {
Log.debug('tryForTime failure: ' + err.message);
tempMessage = err.message;
return bluebird.delay(retryDelay).then(attempt);
});
}
return bluebird.try(attempt);
},
try(block) {
return this.tryForTime(defaultTryTimeout, block);
}
};