diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0ef2231b4b59..ab275d205d9b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -308,12 +308,7 @@ npm run test:ui:runner ##### Browser Automation Notes -- Using Page Objects pattern (https://theintern.github.io/intern/#writing-functional-test) -- At least the initial tests for the Settings, Discover, and Visualize tabs all depend on a very specific set of logstash-type data (generated with makelogs). Since that is a static set of data, all the Discover and Visualize tests use a specific Absolute time range. This guarantees the same results each run. -- These tests have been developed and tested with Chrome and Firefox browser. In theory, they should work on all browsers (that's the benefit of Intern using Leadfoot). -- These tests should also work with an external testing service like https://saucelabs.com/ or https://www.browserstack.com/ but that has not been tested. -- https://theintern.github.io/ -- https://theintern.github.io/leadfoot/module-leadfoot_Element.html +[Read about the `FunctionalTestRunner`](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html) to learn more about how you can run and develop functional tests for Kibana core and plugins. ### Building OS packages @@ -374,4 +369,4 @@ Remember, someone is blocked by a pull awaiting review, make it count. Be thorou 1. **Hand it off** If you're the first reviewer and everything looks good but the changes are more than a few lines, hand the pull to someone else to take a second look. Again, try to find the right person to assign it to. 1. **Merge the code** When everything looks good, put in a `LGTM` (looks good to me) comment. Merge into the target branch. Check the labels on the pull to see if backporting is required, and perform the backport if so. -Thank you so much for reading our guidelines! :tada: \ No newline at end of file +Thank you so much for reading our guidelines! :tada: diff --git a/Gruntfile.js b/Gruntfile.js index 16157cf5e533c..9cbc183c42171 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -57,7 +57,7 @@ module.exports = function (grunt) { init: true, config: config, loadGruntTasks: { - pattern: ['grunt-*', '@*/grunt-*', 'gruntify-*', '@*/gruntify-*', 'intern'] + pattern: ['grunt-*', '@*/grunt-*', 'gruntify-*', '@*/gruntify-*'] } }); diff --git a/docs/development/core-development.asciidoc b/docs/development/core-development.asciidoc index 2b7709f003117..45f52c16ee332 100644 --- a/docs/development/core-development.asciidoc +++ b/docs/development/core-development.asciidoc @@ -5,6 +5,7 @@ * <> * <> * <> +* <> include::core/development-basepath.asciidoc[] @@ -12,4 +13,6 @@ include::core/development-dependencies.asciidoc[] include::core/development-modules.asciidoc[] -include::plugin/development-elasticsearch.asciidoc[] +include::core/development-elasticsearch.asciidoc[] + +include::core/development-functional-tests.asciidoc[] diff --git a/docs/development/plugin/development-elasticsearch.asciidoc b/docs/development/core/development-elasticsearch.asciidoc similarity index 100% rename from docs/development/plugin/development-elasticsearch.asciidoc rename to docs/development/core/development-elasticsearch.asciidoc diff --git a/docs/development/core/development-functional-tests.asciidoc b/docs/development/core/development-functional-tests.asciidoc new file mode 100644 index 0000000000000..af763bf6429d9 --- /dev/null +++ b/docs/development/core/development-functional-tests.asciidoc @@ -0,0 +1,383 @@ +[[development-functional-tests]] +=== Functional Testing + +We use functional tests to make sure the Kibana UI works as expected. It replaces hours of manual testing by automating user interaction. To have better control over our functional test environment, and to make it more accessible to plugin authors, Kibana uses a tool called the `FunctionalTestRunner`. + +[float] +==== Running functional tests + +The `FunctionalTestRunner` is very bare bones and gets most of its functionality from its config file, located at {blob}test/functional/config.js[test/functional/config.js]. If you’re writing a plugin you will have your own config file. See <> for more info. + +Execute the `FunctionalTestRunner`'s script with node.js to run the tests with Kibana's default configuration: + +["source","shell"] +----------- +node scripts/functional_test_runner +----------- + +When run without any arguments the `FunctionalTestRunner` automatically loads the configuration in the standard location, but you can override that behavior with the `--config` flag. There are also command line flags for `--bail` and `--grep`, which behave just like their mocha counterparts. The logging can also be customized with `--quiet`, `--debug`, or `--verbose` flags. + +Use the `--help` flag for more options. + +[float] +==== Writing functional tests + +[float] +===== Environment + +The tests are written in https://mochajs.org[mocha] using https://github.com/Automattic/expect.js[expect] for assertions. + +We use https://sites.google.com/a/chromium.org/chromedriver/[chromedriver], https://theintern.github.io/leadfoot[leadfoot], and https://github.com/theintern/digdug[digdug] for automating Chrome. When the `FunctionalTestRunner` launches, digdug opens a `Tunnel` which starts chromedriver and a stripped-down instance of Chrome. It also creates an instance of https://theintern.github.io/leadfoot/module-leadfoot_Command.html[Leadfoot's `Command`] class, which is available via the `remote` service. The `remote` communicates to Chrome through the digdug `Tunnel`. See the https://theintern.github.io/leadfoot/module-leadfoot_Command.html[leadfoot/Command API] docs for all the commands you can use with `remote`. + +The `FunctionalTestRunner` automatically transpiles functional tests using babel, so that tests can use the same ECMAScript features that Kibana source code uses. See [style_guides/js_style_guide.md]({blob}style_guides/js_style_guide.md). + +[float] +===== Definitions + +**Provider:** + +Code run by the `FunctionalTestRunner` is wrapped in a function so it can be passed around via config files and be parameterized. Any of these Provider functions may be asynchronous and should return/resolve-to the value they are meant to *provide*. Provider functions will always be called with a single argument: a provider API (see Provider API section). + +A config provder: + +["source","js"] +----------- +// config and test files use `export default` +export default function (/* { providerAPI } */) { + return { + // ... + } +} +----------- + +**Services:** + +Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. + +**Page objects:** + +Page objects are a special type of service that encapsulate behaviors common to a particular page or plugin. When you write your own plugin, you’ll likely want to add a page object (or several) that describes the common interactions your tests need to execute. + +**Test Files:** + +The `FunctionalTestRunner`'s primary purpose is to execute test files. These files export a Test Provider that is called with a Provider API but is not expected to return a value. Instead Test Providers define a suite using https://mochajs.org/#bdd[mocha's BDD interface]. + +**Test Suite:** + +A test suite is a collection of tests defined by calling `describe()`, and then populated with tests and setup/teardown hooks by calling `it()`, `before()`, `beforeEach()`, etc. Every test file must define only one top level test suite, and test suites can have as many nested test suites as they like. + +[float] +===== Anatomy of a test file + +The annotated example file below shows the basic structure every test suite uses. It starts by importing https://github.com/Automattic/expect.js[`expect.js`] and defining its default export: an anonymous Test Provider. The test provider then destructures the Provider API for the `getService()` and `getPageObjects()` functions. It uses these functions to collect the dependencies of this suite. The rest of the test file will look pretty normal to mocha.js users. `describe()`, `it()`, `before()` and the lot are used to define suites that happen to automate a browser via services and objects of type `PageObject`. + +["source","js"] +---- +import expect from 'expect.js'; +// test files must `export default` a function that defines a test suite +export default function ({ getService, getPageObject }) { + + // most test files will start off by loading some services + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + + // for historical reasons, PageObjects are loaded in a single API call + // and returned on an object with a key/value for each requested PageObject + const PageObjects = getPageObjects(['common', 'visualize']); + + // every file must define a top-level suite before defining hooks/tests + describe('My Test Suite', () => { + + // most suites start with a before hook that navigates to a specific + // app/page and restores some archives into elasticsearch with esArchiver + before(async () => { + await Promise.all([ + // start with an empty .kibana index + esArchiver.load('empty_kibana'), + // load some basic log data only if the index doesn't exist + esArchiver.loadIfNeeded('makelogs') + ]); + // go to the page described by `apps.visualize` in the config + await PageObjects.common.navigateTo('visualize'); + }); + + // right after the before() hook definition, add the teardown steps + // that will tidy up elasticsearch for other test suites + after(async () => { + // we unload the empty_kibana archive but not the makelogs + // archive because we don't make any changes to it, and subsequent + // suites could use it if they call `.loadIfNeeded()`. + await esArchiver.unload('empty_kibana'); + }); + + // This series of tests illustrate how tests generally verify + // one step of a larger process and then move on to the next in + // a new test, each step building on top of the previous + it('Vis Listing Page is empty'); + it('Create a new vis'); + it('Shows new vis in listing page'); + it('Opens the saved vis'); + it('Respects time filter changes'); + it(... + }); + +} +---- + +[float] +==== Provider API + +The first and only argument to all providers is a Provider API Object. This object can be used to load service/page objects and config/test files. + +Within config files the API has the following properties + +* `log` - An instance of the {blob}src/utils/tooling_log/tooling_log.js[`ToolingLog`] that is ready for use + +* `readConfigFile(path)` - Returns a promise that will resolve to a Config instance that provides the values from the config file at `path` + +Within service and PageObject Providers the API is: + +* `getService(name)` - Load and return the singleton instance of a service by name + +* `getPageObjects(names)` - Load the singleton instances of `PageObject`s and collect them on an object where each name is the key to the singleton instance of that PageObject + +Within a test Provider the API is exactly the same as the service providers API but with an additional method: + +* `loadTestFile(path)` - Load the test file at path in place. Use this method to nest suites from other files into a higher-level suite + +[float] +==== Service Index + +[float] +===== Built-in Services + +The `FunctionalTestRunner` comes with three built-in services: + +**config:** + +* Source: {blob}src/functional_test_runner/lib/config/config.js[src/functional_test_runner/lib/config/config.js] + +* Schema: {blob}src/functional_test_runner/lib/config/schema.js[src/functional_test_runner/lib/config/schema.js] + +* Use `config.get(path)` to read any value from the config file + +**log:** + +* Source: {blob}src/utils/tooling_log/tooling_log.js[src/utils/tooling_log/tooling_log.js] +* `ToolingLog` instances are readable streams. The instance provided by this service is automatically piped to stdout by the `FunctionalTestRunner` CLI +* `log.verbose()`, `log.debug()`, `log.info()`, `log.warning()` all work just like console.log but produce more organized output + +**lifecycle:** + +* Source: {blob}src/functional_test_runner/lib/lifecycle.js[src/functional_test_runner/lib/lifecycle.js] +* Designed primary for use in services +* Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously +* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup`, `phaseStart`, `phaseEnd` + +[float] +===== Kibana Services + +The Kibana functional tests define the vast majority of the actual functionality used by tests. + +**retry:** + +* Source: {blob}test/functional/services/retry.js[test/functional/services/retry.js] +* Helpers for retrying operations +* Popular methods: + * `retry.try(fn)` - execute `fn` in a loop until it succeeds or the default try timeout elapses + * `retry.tryForTime(ms, fn)` execute fn in a loop until it succeeds or `ms` milliseconds elapses + +**testSubjects:** + +* Source: {blob}test/functional/services/test_subjects.js[test/functional/services/test_subjects.js] +* Test subjects are elements that are tagged specifically for selecting from tests +* Use `testSubjects` over CSS selectors when possible +* Usage: + * Tag your test subject with a `data-test-subj` attribute: + +["source","html"] +----------- +
+
+----------- + +* Click this button using the `testSubjects` helper: + +["source","js"] +----------- +await testSubjects.click(‘containerButton’); +----------- + +* Popular methods: + * `testSubjects.find(testSubjectSelector)` - Find a test subject in the page; throw if it can't be found after some time + * `testSubjects.click(testSubjectSelector)` - Click a test subject in the page; throw if it can't be found after some time + +**find:** + +* Source: {blob}test/functional/services/find.js[test/functional/services/find.js] +* Helpers for `remote.findBy*` methods that log and manage timeouts +* Popular methods: + * `find.byCssSelector()` + * `find.allByCssSelector()` + +**kibanaServer:** + +* Source: {blob}test/functional/services/kibana_server/kibana_server.js[test/functional/services/kibana_server/kibana_server.js] +* Helpers for interacting with Kibana's server +* Commonly used methods: + * `kibanaServer.uiSettings.update()` + * `kibanaServer.version.get()` + * `kibanaServer.status.getOverallState()` + +**esArchiver:** + +* Source: {blob}test/functional/services/es_archiver.js[test/functional/services/es_archiver.js] +* Load/unload archives created with the `esArchiver` +* Popular methods: + * `esArchiver.load(name)` + * `esArchiver.loadIfNeeded(name)` + * `esArchiver.unload(name)` + +**docTable:** + +* Source: {blob}test/functional/services/doc_table.js[test/functional/services/doc_table.js] +* Helpers for interacting with doc tables + +**pointSeriesVis:** + +* Source: {blob}test/functional/services/point_series_vis.js[test/functional/services/point_series_vis.js] +* Helpers for interacting with point series visualizations + +**Low-level utilities:** + +* es + * Source: {blob}test/functional/services/es.js[test/functional/services/es.js] + * Elasticsearch client + * Higher level options: `kibanaServer.uiSettings` or `esArchiver` + +* remote + * Source: {blob}test/functional/services/remote/remote.js[test/functional/services/remote/remote.js] + * Instance of https://theintern.github.io/leadfoot/module-leadfoot_Command.html[Leadfoot's `Command]` class + * Responsible for all communication with the browser + * Higher level options: `testSubjects`, `find`, and `PageObjects.common` + * See the https://theintern.github.io/leadfoot/module-leadfoot_Command.html[leadfoot/Command API] for full API + +[float] +===== Custom Services + +Services are intentionally generic. They can be literally anything (even nothing). Some services have helpers for interacting with a specific types of UI elements, like `pointSeriesVis`, and others are more foundational, like `log` or `config`. Whenever you want to provide some functionality in a reusable package, consider making a custom service. + +To create a custom service `somethingUseful`: + +* Create a `test/functional/services/something_useful.js` file that looks like this: + +["source","js"] +----------- +// Services are defined by Provider functions that receive the ServiceProviderAPI +export function SomethingUsefulProvider({ getService }) { + const log = getService('log'); + + class SomethingUseful { + doSomething() { + } + } + return new SomethingUseful(); +} +----------- + +* Re-export your provider from `services/index.js` + +* Import it into `src/functional/config.js` and add it to the services config: + +["source","js"] +----------- +import { SomethingUsefulProvider } from './services'; + +export default function () { + return { + // … truncated ... + services: { + somethingUseful: SomethingUsefulProvider + } + } +} +----------- + +[float] +==== PageObjects + +The purpose for each PageObject is pretty self-explanatory. The visualize PageObject provides helpers for interacting with the visualize app, dashboard is the same for the dashboard app, and so on. + +One exception is the "common" PageObject. A holdover from the intern implementation, the common PageObject is a collection of helpers useful across pages. Now that we have shareable services, and those services can be shared with other `FunctionalTestRunner` configurations, we will continue to move functionality out of the common PageObject and into services. + +Please add new methods to existing or new services rather than further expanding the CommonPage class. + +[float] +==== Gotchas + +Remember that you can’t run an individual test in the file (`it` block) because the whole `describe` needs to be run in order. There should only be one top level `describe` in a file. + +[float] +===== Functional Test Timing + +Another important gotcha is writing stable tests by being mindful of timing. All methods on `remote` run asynchronously. It’s better to write interactions that wait for changes on the UI to appear before moving onto the next step. + +For example, rather than writing an interaction that simply clicks a button, write an interaction with the a higher-level purpose in mind: + +Bad example: `PageObjects.app.clickButton()` + +["source","js"] +----------- +class AppPage { + // what can people who call this method expect from the + // UI after the promise resolves? Since the reaction to most + // clicks is asynchronous the behavior is dependant on timing + // and likely to cause test that fail unexpectedly + async clickButton () { + await testSubjects.click(‘menuButton’); + } +} +----------- + +Good example: `PageObjects.app.openMenu()` + +["source","js"] +----------- +class AppPage { + // unlike `clickButton()`, callers of `openMenu()` know + // the state that the UI will be in before they move on to + // the next step + async openMenu () { + await testSubjects.click(‘menuButton’); + await testSubjects.exists(‘menu’); + } +} +----------- + +Writing in this way will ensure your test timings are not flaky or based on assumptions about UI updates after interactions. + +[float] +==== Debugging + +From the command line run: + +["source","shell"] +----------- +node --debug-brk --inspect scripts/functional_test_runner +----------- + +This prints out a URL that you can visit in Chrome and debug your functional tests in the browser. + +You can also see additional logs in the terminal by running the `FunctionalTestRunner` with the `--debug` or `--verbose` flag. Add more logs with statements in your tests like + +["source","js"] +----------- +// load the log service +const log = getService(‘log’); + +// log.debug only writes when using the `--debug` or `--verbose` flag. +log.debug(‘done clicking menu’); +----------- + diff --git a/docs/development/plugin-development.asciidoc b/docs/development/plugin-development.asciidoc index d86479685fafe..c930e5fbc92c9 100644 --- a/docs/development/plugin-development.asciidoc +++ b/docs/development/plugin-development.asciidoc @@ -8,8 +8,11 @@ The Kibana plugin interfaces are in a state of constant development. We cannot * <> * <> +* <> include::plugin/development-plugin-resources.asciidoc[] include::plugin/development-uiexports.asciidoc[] + +include::plugin/development-plugin-functional-tests.asciidoc[] diff --git a/docs/development/plugin/development-plugin-functional-tests.asciidoc b/docs/development/plugin/development-plugin-functional-tests.asciidoc new file mode 100644 index 0000000000000..aec24075f4368 --- /dev/null +++ b/docs/development/plugin/development-plugin-functional-tests.asciidoc @@ -0,0 +1,91 @@ +[[development-plugin-functional-tests]] +=== Functional Tests for Plugins + +Plugins use the `FunctionalTestRunner` by running it out of the Kibana repo. Ensure that your Kibana Development Environment is setup properly before continuing. + +[float] +==== Writing your own configuration + +Every project or plugin should have its own `FunctionalTestRunner` config file. Just like Kibana's, this config file will define all of the test files to load, providers for Services and PageObjects, as well as configuration options for certain services. + +To get started copy and paste this example to `test/functional/config.js`: + +["source","js"] +----------- +import { resolve } from 'path'; +import { MyServiceProvider } from './services/my_service'; +import { MyAppPageProvider } from './services/my_app_page; + +// allow overriding the default kibana directory +// using the KIBANA_DIR environment variable +const KIBANA_CONFIG_PATH = resolve(process.env.KIBANA_DIR || '../kibana', 'test/functional/config.js'); + +// the default export of config files must be a config provider +// that returns an object with the projects config values +export default async function ({ readConfigFile }) { + + // read the Kibana config file so that we can utilize some of + // its services and PageObjects + const kibanaConfig = await readConfigFile(KIBANA_CONFIG_PATH); + + return { + // list paths to the files that contain your plugins tests + testFiles: [ + resolve(__dirname, './my_test_file.js'), + ], + + // define the name and providers for services that should be + // available to your tests. If you don't specify anything here + // only the built-in services will be avaliable + services: { + ...kibanaConfig.get('services'), + myService: MyServiceProvider, + }, + + // just like services, PageObjects are defined as a map of + // names to Providers. Merge in Kibana's or pick specific ones + pageObjects: { + management: kibanaConfig.get('pageObjects.management'), + myApp: MyAppPageProvider, + }, + + // the apps section defines the urls that + // `PageObjects.common.navigateTo(appKey)` will use. + // Merge urls for your plugin with the urls defined in + // Kibana's config in order to use this helper + apps: { + ...kibanaConfig.get('apps'), + myApp: { + pathname: '/app/my_app', + } + }, + + // choose where esArchiver should load archives from + esArchiver: { + directory: resolve(__dirname, './es_archives'), + }, + + // choose where screenshots should be saved + screenshots: { + directory: resolve(__dirname, './tmp/screenshots'), + } + + // more settings, like timeouts, mochaOpts, etc are + // defined in the config schema. See {blob}src/functional_test_runner/lib/config/schema.js[src/functional_test_runner/lib/config/schema.js] + }; +} + +----------- + +From the root of your repo you should now be able to run the `FunctionalTestRunner` script from your plugin project. + +["source","shell"] +----------- +node ../kibana/scripts/functional_test_runner +----------- + +[float] +==== Using esArchiver + +We're working on documentation for this, but for now the best place to look is the original {pull}10359[pull request]. + diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 7919933715f2e..904da55ba3cec 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -16,6 +16,7 @@ release-state can be: released | prerelease | unreleased :issue: {repo}issues/ :pull: {repo}pull/ :commit: {repo}commit/ +:blob: {repo}blob/{branch}/ :security: https://www.elastic.co/community/security/ diff --git a/package.json b/package.json index 6b85ac2f60c2a..629889d9b9818 100644 --- a/package.json +++ b/package.json @@ -126,8 +126,8 @@ "expose-loader": "0.7.0", "extract-text-webpack-plugin": "0.8.2", "file-loader": "0.8.4", - "font-awesome": "4.4.0", "flot-charts": "0.8.3", + "font-awesome": "4.4.0", "glob": "5.0.13", "glob-all": "3.0.1", "good-squeeze": "2.1.0", @@ -209,8 +209,9 @@ "chance": "1.0.6", "cheerio": "0.22.0", "chokidar": "1.6.0", - "chromedriver": "2.24.1", + "chromedriver": "2.28.0", "classnames": "2.2.5", + "digdug": "1.6.3", "enzyme": "2.7.0", "enzyme-to-json": "1.4.5", "eslint": "3.11.1", @@ -239,7 +240,6 @@ "html-loader": "0.4.3", "husky": "0.8.1", "image-diff": "1.6.0", - "intern": "3.2.3", "istanbul-instrumenter-loader": "0.1.3", "jest": "19.0.0", "jest-cli": "19.0.0", @@ -252,6 +252,7 @@ "karma-mocha": "0.2.0", "karma-safari-launcher": "0.1.1", "keymirror": "0.1.1", + "leadfoot": "1.7.1", "license-checker": "5.1.2", "load-grunt-config": "0.19.2", "makelogs": "3.2.3", diff --git a/scripts/functional_test_runner.js b/scripts/functional_test_runner.js new file mode 100644 index 0000000000000..e6b555b449f1d --- /dev/null +++ b/scripts/functional_test_runner.js @@ -0,0 +1,2 @@ +require('../src/optimize/babel/register'); +require('../src/functional_test_runner/cli'); diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html index dfa313582acd8..87bd710e4d48c 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html @@ -12,7 +12,10 @@ -
+
diff --git a/src/es_archiver/actions/load.js b/src/es_archiver/actions/load.js index b849a22d4d0b9..91c4f0b6edafc 100644 --- a/src/es_archiver/actions/load.js +++ b/src/es_archiver/actions/load.js @@ -9,7 +9,7 @@ import { isGzip, createStats, prioritizeMappings, - getArchiveFiles, + readDirectory, createParseArchiveStreams, createCreateIndexStream, createIndexDocRecordsStream, @@ -19,7 +19,7 @@ export async function loadAction({ name, skipExisting, client, dataDir, log }) { const inputDir = resolve(dataDir, name); const stats = createStats(name, log); - const files = prioritizeMappings(await getArchiveFiles(inputDir)); + const files = prioritizeMappings(await readDirectory(inputDir)); for (const filename of files) { log.info('[%s] Loading %j', name, filename); diff --git a/src/es_archiver/actions/rebuild_all.js b/src/es_archiver/actions/rebuild_all.js index 8600c49e19cf8..2d6e36a38ec99 100644 --- a/src/es_archiver/actions/rebuild_all.js +++ b/src/es_archiver/actions/rebuild_all.js @@ -1,7 +1,6 @@ import { resolve } from 'path'; import { rename, - readdir, createReadStream, createWriteStream } from 'fs'; @@ -14,18 +13,18 @@ import { import { prioritizeMappings, - getArchiveFiles, + readDirectory, isGzip, createParseArchiveStreams, createFormatArchiveStreams, } from '../lib'; export async function rebuildAllAction({ dataDir, log }) { - const archiveNames = await fromNode(cb => readdir(dataDir, cb)); + const archiveNames = await readDirectory(dataDir); for (const name of archiveNames) { const inputDir = resolve(dataDir, name); - const files = prioritizeMappings(await getArchiveFiles(inputDir)); + const files = prioritizeMappings(await readDirectory(inputDir)); for (const filename of files) { log.info('[%s] Rebuilding %j', name, filename); diff --git a/src/es_archiver/actions/unload.js b/src/es_archiver/actions/unload.js index 55e1ef2a61480..429f6df31c8eb 100644 --- a/src/es_archiver/actions/unload.js +++ b/src/es_archiver/actions/unload.js @@ -9,7 +9,7 @@ import { isGzip, createStats, prioritizeMappings, - getArchiveFiles, + readDirectory, createParseArchiveStreams, createFilterRecordsStream, createDeleteIndexStream @@ -19,7 +19,7 @@ export async function unloadAction({ name, client, dataDir, log }) { const inputDir = resolve(dataDir, name); const stats = createStats(name, log); - const files = prioritizeMappings(await getArchiveFiles(inputDir)); + const files = prioritizeMappings(await readDirectory(inputDir)); for (const filename of files) { log.info('[%s] Unloading indices from %j', name, filename); diff --git a/src/es_archiver/cli.js b/src/es_archiver/cli.js index 3b5e1f975a23d..01e951436c1ac 100644 --- a/src/es_archiver/cli.js +++ b/src/es_archiver/cli.js @@ -6,12 +6,14 @@ import { resolve } from 'path'; import { readFileSync } from 'fs'; +import { format as formatUrl } from 'url'; import { Command } from 'commander'; import elasticsearch from 'elasticsearch'; import { EsArchiver } from './es_archiver'; -import { createLog } from './lib'; +import { createToolingLog } from '../utils'; +import { readConfigFile } from '../functional_test_runner'; const cmd = new Command('node scripts/es_archiver'); @@ -20,6 +22,7 @@ cmd .option('--es-url [url]', 'url for elasticsearch') .option(`--dir [path]`, 'where archives are stored') .option('--verbose', 'turn on verbose logging') + .option('--config [path]', 'path to a functional test config file to use for default values') .on('--help', () => { console.log(readFileSync(resolve(__dirname, './cli_help.txt'), 'utf8')); }); @@ -49,9 +52,16 @@ if (missingCommand) { async function execute(operation, ...args) { try { - const log = createLog(cmd.verbose ? 'debug' : 'info'); + const log = createToolingLog(cmd.verbose ? 'debug' : 'info'); log.pipe(process.stdout); + if (cmd.config) { + // load default values from the specified config file + const config = await readConfigFile(log, resolve(cmd.config)); + if (!cmd.esUrl) cmd.esUrl = formatUrl(config.get('servers.elasticsearch')); + if (!cmd.dir) cmd.dir = config.get('esArchiver.directory'); + } + // log and count all validation errors let errorCount = 0; const error = (msg) => { @@ -61,10 +71,10 @@ async function execute(operation, ...args) { if (!operation) error('Missing or invalid command'); if (!cmd.esUrl) { - error('You must specify either --es-url flag'); + error('You must specify either --es-url or --config flags'); } if (!cmd.dir) { - error('You must specify either --dir flag'); + error('You must specify either --dir or --config flags'); } // if there was a validation error display the help @@ -84,7 +94,7 @@ async function execute(operation, ...args) { const esArchiver = new EsArchiver({ log, client, - dataDir: resolve(cmd.dir) + dataDir: resolve(cmd.dir), }); await esArchiver[operation](...args); } finally { diff --git a/src/es_archiver/lib/__tests__/stats.js b/src/es_archiver/lib/__tests__/stats.js index f82298e330cc0..7287975e1d81d 100644 --- a/src/es_archiver/lib/__tests__/stats.js +++ b/src/es_archiver/lib/__tests__/stats.js @@ -2,9 +2,10 @@ import expect from 'expect.js'; import { uniq } from 'lodash'; import sinon from 'sinon'; -import { createStats, createLog } from '../'; +import { createStats } from '../'; import { + createToolingLog, createConcatStream, createPromiseFromStreams } from '../../../utils'; @@ -47,14 +48,14 @@ function assertDeepClones(a, b) { describe('esArchiver: Stats', () => { describe('#skippedIndex(index)', () => { it('marks the index as skipped', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.skippedIndex('index-name'); const indexStats = stats.toJSON()['index-name']; expect(indexStats).to.have.property('skipped', true); }); it('logs that the index was skipped', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.skippedIndex('index-name'); expect(await drain(log)).to.contain('Skipped'); @@ -63,13 +64,13 @@ describe('esArchiver: Stats', () => { describe('#deletedIndex(index)', () => { it('marks the index as deleted', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.deletedIndex('index-name'); const indexStats = stats.toJSON()['index-name']; expect(indexStats).to.have.property('deleted', true); }); it('logs that the index was deleted', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.deletedIndex('index-name'); expect(await drain(log)).to.contain('Deleted'); @@ -78,20 +79,20 @@ describe('esArchiver: Stats', () => { describe('#createdIndex(index, [metadata])', () => { it('marks the index as created', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.createdIndex('index-name'); const indexStats = stats.toJSON()['index-name']; expect(indexStats).to.have.property('created', true); }); it('logs that the index was created', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.createdIndex('index-name'); expect(await drain(log)).to.contain('Created'); }); describe('with metadata', () => { it('debug-logs each key from the metadata', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.createdIndex('index-name', { foo: 'bar' @@ -103,7 +104,7 @@ describe('esArchiver: Stats', () => { }); describe('without metadata', () => { it('no debug logging', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.createdIndex('index-name'); const output = await drain(log); @@ -114,20 +115,20 @@ describe('esArchiver: Stats', () => { describe('#archivedIndex(index, [metadata])', () => { it('marks the index as archived', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.archivedIndex('index-name'); const indexStats = stats.toJSON()['index-name']; expect(indexStats).to.have.property('archived', true); }); it('logs that the index was archived', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.archivedIndex('index-name'); expect(await drain(log)).to.contain('Archived'); }); describe('with metadata', () => { it('debug-logs each key from the metadata', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.archivedIndex('index-name', { foo: 'bar' @@ -139,7 +140,7 @@ describe('esArchiver: Stats', () => { }); describe('without metadata', () => { it('no debug logging', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); const stats = createStats('name', log); stats.archivedIndex('index-name'); const output = await drain(log); @@ -150,7 +151,7 @@ describe('esArchiver: Stats', () => { describe('#indexedDoc(index)', () => { it('increases the docs.indexed count for the index', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.indexedDoc('index-name'); expect(stats.toJSON()['index-name'].docs.indexed).to.be(1); stats.indexedDoc('index-name'); @@ -161,7 +162,7 @@ describe('esArchiver: Stats', () => { describe('#archivedDoc(index)', () => { it('increases the docs.archived count for the index', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.archivedDoc('index-name'); expect(stats.toJSON()['index-name'].docs.archived).to.be(1); stats.archivedDoc('index-name'); @@ -172,13 +173,13 @@ describe('esArchiver: Stats', () => { describe('#toJSON()', () => { it('returns the stats for all indexes', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.archivedIndex('index1'); stats.archivedIndex('index2'); expect(Object.keys(stats.toJSON())).to.eql(['index1', 'index2']); }); it('returns a deep clone of the stats', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.archivedIndex('index1'); stats.archivedIndex('index2'); stats.deletedIndex('index3'); @@ -189,7 +190,7 @@ describe('esArchiver: Stats', () => { describe('#forEachIndex(fn)', () => { it('iterates a clone of the index stats', () => { - const stats = createStats('name', createLog()); + const stats = createStats('name', createToolingLog()); stats.archivedIndex('index1'); stats.archivedIndex('index2'); stats.deletedIndex('index3'); diff --git a/src/es_archiver/lib/archives/filenames.js b/src/es_archiver/lib/archives/filenames.js index b0a07ed99f5e8..68f6765827e0a 100644 --- a/src/es_archiver/lib/archives/filenames.js +++ b/src/es_archiver/lib/archives/filenames.js @@ -1,20 +1,9 @@ -import { fromNode } from 'bluebird'; -import { readdir } from 'fs'; import { basename, extname } from 'path'; export function isGzip(path) { return extname(path) === '.gz'; } -/** - * Gead the list of files in an archive. - * - * @return {Promise} [description] - */ -export async function getArchiveFiles(archiveDir) { - return await fromNode(cb => readdir(archiveDir, cb)); -} - /** * Check if a path is for a, potentially gzipped, mapping file * @param {String} path diff --git a/src/es_archiver/lib/archives/index.js b/src/es_archiver/lib/archives/index.js index fbb44dc65c8bb..91e165474c6ff 100644 --- a/src/es_archiver/lib/archives/index.js +++ b/src/es_archiver/lib/archives/index.js @@ -1,6 +1,5 @@ export { isGzip, - getArchiveFiles, prioritizeMappings, } from './filenames'; diff --git a/src/es_archiver/lib/archives/parse.js b/src/es_archiver/lib/archives/parse.js index 8a3717fa4bb58..6cb27846ccc7c 100644 --- a/src/es_archiver/lib/archives/parse.js +++ b/src/es_archiver/lib/archives/parse.js @@ -12,6 +12,6 @@ export function createParseArchiveStreams({ gzip = false } = {}) { return [ gzip ? createGunzip() : new PassThrough(), createSplitStream(RECORD_SEPARATOR), - createJsonParseStream() + createJsonParseStream(), ]; } diff --git a/src/es_archiver/lib/directory.js b/src/es_archiver/lib/directory.js new file mode 100644 index 0000000000000..872ca6a8df80b --- /dev/null +++ b/src/es_archiver/lib/directory.js @@ -0,0 +1,8 @@ +import { readdir } from 'fs'; + +import { fromNode } from 'bluebird'; + +export async function readDirectory(path) { + const allNames = await fromNode(cb => readdir(path, cb)); + return allNames.filter(name => !name.startsWith('.')); +} diff --git a/src/es_archiver/lib/index.js b/src/es_archiver/lib/index.js index 1c5343f12a4ba..0a70116fedab8 100644 --- a/src/es_archiver/lib/index.js +++ b/src/es_archiver/lib/index.js @@ -19,12 +19,11 @@ export { export { isGzip, - getArchiveFiles, prioritizeMappings, createParseArchiveStreams, createFormatArchiveStreams, } from './archives'; export { - createLog -} from './log'; + readDirectory +} from './directory'; diff --git a/src/es_archiver/lib/log/index.js b/src/es_archiver/lib/log/index.js deleted file mode 100644 index 23a0f6a03311c..0000000000000 --- a/src/es_archiver/lib/log/index.js +++ /dev/null @@ -1 +0,0 @@ -export { createLog } from './log'; diff --git a/src/es_archiver/lib/log/log.js b/src/es_archiver/lib/log/log.js deleted file mode 100644 index 1eb14022de4a0..0000000000000 --- a/src/es_archiver/lib/log/log.js +++ /dev/null @@ -1,39 +0,0 @@ -import { format } from 'util'; -import { PassThrough } from 'stream'; - -import { createLogLevelFlags } from './log_levels'; -import { red, blue, brightBlack } from 'ansicolors'; - -export function createLog(logLevel = 'silent') { - const logLevelFlags = createLogLevelFlags(logLevel); - - function write(stream, ...args) { - format(...args).split('\n').forEach((line, i) => { - stream.write(`${i === 0 ? '' : ' '}${line}\n`); - }); - } - - class Log extends PassThrough { - debug(...args) { - if (!logLevelFlags.debug) return; - write(this, ' %s ', brightBlack('debg'), format(...args)); - } - - info(...args) { - if (!logLevelFlags.info) return; - write(this, ' %s ', blue('info'), format(...args)); - } - - error(err) { - if (!logLevelFlags.error) return; - - if (typeof err !== 'string' && !(err instanceof Error)) { - err = new Error(`"${err}" thrown`); - } - - write(this, '%s ', red('ERROR'), err.stack || err.message || err); - } - } - - return new Log(); -} diff --git a/src/fixtures/es_archives/visualize_source_filters/data.json.gz b/src/fixtures/es_archives/visualize_source_filters/data.json.gz new file mode 100644 index 0000000000000..62f6303cc780f Binary files /dev/null and b/src/fixtures/es_archives/visualize_source_filters/data.json.gz differ diff --git a/src/fixtures/es_archives/visualize_source_filters/mappings.json b/src/fixtures/es_archives/visualize_source_filters/mappings.json new file mode 100644 index 0000000000000..02102b49e6339 --- /dev/null +++ b/src/fixtures/es_archives/visualize_source_filters/mappings.json @@ -0,0 +1,76 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "0" + } + }, + "mappings": { + "config": { + "properties": { + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "index-pattern": { + "properties": { + "fields": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "sourceFilters": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "timeFieldName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "title": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/functional_test_runner/__tests__/fixtures/simple_project/config.js b/src/functional_test_runner/__tests__/fixtures/simple_project/config.js new file mode 100644 index 0000000000000..6b81b052d22b9 --- /dev/null +++ b/src/functional_test_runner/__tests__/fixtures/simple_project/config.js @@ -0,0 +1,7 @@ +import { resolve } from 'path'; + +export default () => ({ + testFiles: [ + resolve(__dirname, 'tests.js') + ] +}); diff --git a/src/functional_test_runner/__tests__/fixtures/simple_project/tests.js b/src/functional_test_runner/__tests__/fixtures/simple_project/tests.js new file mode 100644 index 0000000000000..fb3e204abb146 --- /dev/null +++ b/src/functional_test_runner/__tests__/fixtures/simple_project/tests.js @@ -0,0 +1,18 @@ +import expect from 'expect.js'; + +export default () => { + describe('app one', () => { + before(() => { + console.log('$BEFORE$'); + }); + + it('$TESTNAME$', () => { + expect(1).to.be(1); + console.log('$INTEST$'); + }); + + after(() => { + console.log('$AFTER$'); + }); + }); +}; diff --git a/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/data.json.gz b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/data.json.gz new file mode 100644 index 0000000000000..3a298fc586c0e Binary files /dev/null and b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/data.json.gz differ diff --git a/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/mappings.json b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/mappings.json new file mode 100644 index 0000000000000..e6697d24ae1eb --- /dev/null +++ b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/archives/test_archive/mappings.json @@ -0,0 +1,212 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "0" + } + }, + "mappings": { + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "text" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "config": { + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "timeFrom": { + "type": "text" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "title": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "uiStateJSON": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "index-pattern": { + "properties": { + "fields": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "sourceFilters": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "timeFieldName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "title": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + } +} diff --git a/src/functional_test_runner/__tests__/fixtures/with_es_archiver/config.js b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/config.js new file mode 100644 index 0000000000000..bbc911a7d3d09 --- /dev/null +++ b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/config.js @@ -0,0 +1,24 @@ +import { resolve } from 'path'; + +export default () => ({ + testFiles: [ + resolve(__dirname, 'tests.js') + ], + + esArchiver: { + directory: resolve(__dirname, 'archives') + }, + + servers: { + elasticsearch: { + protocol: 'http', + hostname: 'localhost', + port: 5700 + }, + kibana: { + protocol: 'http', + hostname: 'localhost', + port: 5701 + } + } +}); diff --git a/src/functional_test_runner/__tests__/fixtures/with_es_archiver/tests.js b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/tests.js new file mode 100644 index 0000000000000..fd2d1a2ef4026 --- /dev/null +++ b/src/functional_test_runner/__tests__/fixtures/with_es_archiver/tests.js @@ -0,0 +1,23 @@ +export default ({ getService }) => { + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('tests', () => { + before(async () => { + log.debug('before load()'); + await esArchiver.load('test_archive'); + log.debug('after load()'); + }); + + it('loaded the archive', async () => { + log.debug('es aliases', await es.indices.getAlias()); + }); + + after(async () => { + log.debug('before unload()'); + await esArchiver.unload('test_archive'); + log.debug('after unload()'); + }); + }); +}; diff --git a/src/functional_test_runner/__tests__/integration/basic.js b/src/functional_test_runner/__tests__/integration/basic.js new file mode 100644 index 0000000000000..2793dad66b218 --- /dev/null +++ b/src/functional_test_runner/__tests__/integration/basic.js @@ -0,0 +1,20 @@ +import { spawnSync } from 'child_process'; +import { resolve } from 'path'; + +import expect from 'expect.js'; + +const SCRIPT = resolve(__dirname, '../../../../scripts/functional_test_runner.js'); +const BASIC_CONFIG = resolve(__dirname, '../fixtures/simple_project/config.js'); + +describe('basic config file with a single app and test', function () { + this.timeout(60 * 1000); + + it('runs and prints expected output', () => { + const proc = spawnSync(process.execPath, [SCRIPT, '--config', BASIC_CONFIG]); + const stdout = proc.stdout.toString('utf8'); + expect(stdout).to.contain('$BEFORE$'); + expect(stdout).to.contain('$TESTNAME$'); + expect(stdout).to.contain('$INTEST$'); + expect(stdout).to.contain('$AFTER$'); + }); +}); diff --git a/src/functional_test_runner/__tests__/integration/with_es_archiver.js b/src/functional_test_runner/__tests__/integration/with_es_archiver.js new file mode 100644 index 0000000000000..2640879ba7846 --- /dev/null +++ b/src/functional_test_runner/__tests__/integration/with_es_archiver.js @@ -0,0 +1,71 @@ +import { spawn } from 'child_process'; +import { resolve } from 'path'; +import { format as formatUrl } from 'url'; + +import { readConfigFile } from '../../lib'; +import { createToolingLog, createReduceStream } from '../../../utils'; +import { startupEs, startupKibana } from '../lib'; + +const SCRIPT = resolve(__dirname, '../../../../scripts/functional_test_runner.js'); +const CONFIG = resolve(__dirname, '../fixtures/with_es_archiver/config.js'); + +describe('single test that uses esArchiver', function () { + this.timeout(60 * 1000); + + let log; + const cleanupWork = []; + + before(async () => { + log = createToolingLog('verbose', process.stdout); + log.indent(6); + + const config = await readConfigFile(log, CONFIG); + + log.info('starting elasticsearch'); + log.indent(2); + const es = await startupEs({ + log, + port: config.get('servers.elasticsearch.port'), + fresh: false + }); + log.indent(-2); + + log.info('starting kibana'); + log.indent(2); + const kibana = await startupKibana({ + port: config.get('servers.kibana.port'), + esUrl: formatUrl(config.get('servers.elasticsearch')) + }); + log.indent(-2); + + cleanupWork.push(() => es.shutdown()); + cleanupWork.push(() => kibana.close()); + }); + + it('test', async () => { + const proc = spawn(process.execPath, [SCRIPT, '--config', CONFIG], { + stdio: ['ignore', 'pipe', 'ignore'] + }); + + const concatChunks = (acc, chunk) => `${acc}${chunk}`; + const concatStdout = proc.stdout.pipe(createReduceStream(concatChunks)); + + const [stdout] = await Promise.all([ + new Promise((resolve, reject) => { + concatStdout.on('error', reject); + concatStdout.on('data', resolve); // reduce streams produce a single value, no need to wait for anything else + }), + + new Promise((resolve, reject) => { + proc.on('error', reject); + proc.on('close', resolve); + }) + ]); + + log.debug(stdout.toString('utf8')); + }); + + after(() => { + return Promise.all(cleanupWork.splice(0).map(fn => fn())); + }); +}); diff --git a/src/functional_test_runner/__tests__/lib/es.js b/src/functional_test_runner/__tests__/lib/es.js new file mode 100644 index 0000000000000..1dc7fbc6fec37 --- /dev/null +++ b/src/functional_test_runner/__tests__/lib/es.js @@ -0,0 +1,43 @@ +import { resolve } from 'path'; + +import { once, merge } from 'lodash'; +import libesvm from 'libesvm'; + +const VERSION = 'master'; +const DIRECTORY = resolve(__dirname, '../../../../esvm/functional_test_runner_tests'); + +const createCluster = (options = {}) => { + return libesvm.createCluster(merge({ + directory: DIRECTORY, + branch: VERSION, + }, options)); +}; + +const install = once(async (fresh) => { + await createCluster({ fresh }).install(); +}); + +export async function startupEs(opts) { + const { + port, + log, + fresh = true + } = opts; + + await install({ fresh }); + const cluster = createCluster({ + config: { + http: { + port + } + } + }); + + cluster.on('log', (event) => { + const method = event.level.toLowerCase() === 'info' ? 'verbose' : 'debug'; + log[method](`${event.level}: ${event.type} - ${event.message}`); + }); + + await cluster.start(); + return cluster; +} diff --git a/src/functional_test_runner/__tests__/lib/index.js b/src/functional_test_runner/__tests__/lib/index.js new file mode 100644 index 0000000000000..eae9e26514d62 --- /dev/null +++ b/src/functional_test_runner/__tests__/lib/index.js @@ -0,0 +1,2 @@ +export { startupEs } from './es'; +export { startupKibana } from './kibana'; diff --git a/src/functional_test_runner/__tests__/lib/kibana.js b/src/functional_test_runner/__tests__/lib/kibana.js new file mode 100644 index 0000000000000..d9fa8187ba2a5 --- /dev/null +++ b/src/functional_test_runner/__tests__/lib/kibana.js @@ -0,0 +1,25 @@ +import { resolve } from 'path'; + +import { createServer } from '../../../../test/utils/kbn_server'; + +export async function startupKibana({ port, esUrl }) { + const server = createServer({ + server: { + port, + autoListen: true, + }, + + plugins: { + scanDirs: [ + resolve(__dirname, '../../../core_plugins') + ], + }, + + elasticsearch: { + url: esUrl + } + }); + + await server.ready(); + return server; +} diff --git a/src/functional_test_runner/cli.js b/src/functional_test_runner/cli.js new file mode 100644 index 0000000000000..4b745ffd83023 --- /dev/null +++ b/src/functional_test_runner/cli.js @@ -0,0 +1,82 @@ +import { resolve } from 'path'; + +import { Command } from 'commander'; + +import { createToolingLog } from '../utils'; +import { createFunctionalTestRunner } from './functional_test_runner'; + +const cmd = new Command('node scripts/functional_test_runner'); + +const resolveConfigPath = v => resolve(process.cwd(), v); +const defaultConfigPath = resolveConfigPath('test/functional/config.js'); + +cmd + .option('--config [path]', 'Path to a config file', resolveConfigPath, defaultConfigPath) + .option('--bail', 'stop tests after the first failure', false) + .option('--grep ', 'pattern used to select which tests to run') + .option('--verbose', 'Log everything', false) + .option('--quiet', 'Only log errors', false) + .option('--silent', 'Log nothing', false) + .option('--debug', 'Run in debug mode', false) + .parse(process.argv); + +if (!cmd.config) { + console.log(''); + console.log(' --config is a required parameter'); + console.log(''); + process.exit(1); +} + +let logLevel = 'info'; +if (cmd.silent) logLevel = 'silent'; +if (cmd.quiet) logLevel = 'error'; +if (cmd.debug) logLevel = 'debug'; +if (cmd.verbose) logLevel = 'verbose'; + +const log = createToolingLog(logLevel); +log.pipe(process.stdout); + +const functionalTestRunner = createFunctionalTestRunner({ + log, + configFile: cmd.config, + configOverrides: { + mochaOpts: { + bail: cmd.bail, + grep: cmd.grep, + } + } +}); + +async function run() { + try { + const failureCount = await functionalTestRunner.run(); + process.exitCode = failureCount ? 1 : 0; + } catch (err) { + await teardown(err); + } finally { + await teardown(); + } +} + +let teardownRun = false; +async function teardown(err) { + if (teardownRun) return; + + teardownRun = true; + if (err) { + log.indent(-log.indent()); + log.error(err); + process.exitCode = 1; + } + + try { + await functionalTestRunner.close(); + } finally { + process.exit(); + } +} + +process.on('unhandledRejection', err => teardown(err)); +process.on('SIGTERM', () => teardown()); +process.on('SIGINT', () => teardown()); +run(); diff --git a/src/functional_test_runner/functional_test_runner.js b/src/functional_test_runner/functional_test_runner.js new file mode 100644 index 0000000000000..62a32047e6fba --- /dev/null +++ b/src/functional_test_runner/functional_test_runner.js @@ -0,0 +1,64 @@ +import { + createLifecycle, + readConfigFile, + createProviderCollection, + setupMocha, + runTests, +} from './lib'; + +export function createFunctionalTestRunner({ log, configFile, configOverrides }) { + const lifecycle = createLifecycle(); + + lifecycle.on('phaseStart', name => { + log.verbose('starting %j lifecycle phase', name); + }); + + lifecycle.on('phaseEnd', name => { + log.verbose('ending %j lifecycle phase', name); + }); + + class FunctionalTestRunner { + async run() { + let runErrorOccurred = false; + + try { + const config = await readConfigFile(log, configFile, configOverrides); + log.info('Config loaded'); + + const providers = createProviderCollection(lifecycle, log, config); + await providers.loadAll(); + + const mocha = await setupMocha(lifecycle, log, config, providers); + await lifecycle.trigger('beforeTests'); + log.info('Starting tests'); + return await runTests(lifecycle, log, mocha); + + } catch (runError) { + runErrorOccurred = true; + throw runError; + + } finally { + try { + await this.close(); + + } catch (closeError) { + if (runErrorOccurred) { + log.error('failed to close functional_test_runner'); + log.error(closeError); + } else { + throw closeError; + } + } + } + } + + async close() { + if (this._closed) return; + + this._closed = true; + await lifecycle.trigger('cleanup'); + } + } + + return new FunctionalTestRunner(); +} diff --git a/src/functional_test_runner/index.js b/src/functional_test_runner/index.js new file mode 100644 index 0000000000000..88ea2f2b83bab --- /dev/null +++ b/src/functional_test_runner/index.js @@ -0,0 +1 @@ +export { createFunctionalTestRunner } from './functional_test_runner'; diff --git a/src/functional_test_runner/lib/config/config.js b/src/functional_test_runner/lib/config/config.js new file mode 100644 index 0000000000000..448bf8bf3ea4a --- /dev/null +++ b/src/functional_test_runner/lib/config/config.js @@ -0,0 +1,64 @@ +import { get, has, cloneDeep } from 'lodash'; +import toPath from 'lodash/internal/toPath'; + +import { schema } from './schema'; + +const $values = Symbol('values'); + +export class Config { + constructor(settings) { + const { error, value } = schema.validate(settings); + if (error) throw error; + this[$values] = value; + } + + has(key) { + function recursiveHasCheck(path, values, schema) { + if (!schema._inner) return false; + + // normalize child and pattern checks so we can iterate the checks in a single loop + const checks = [].concat( + // match children first, they have priority + (schema._inner.children || []).map(child => ({ + test: key => child.key === key, + schema: child.schema + })), + // match patterns on any key that doesn't match an explicit child + (schema._inner.patterns || []).map(pattern => ({ + test: key => pattern.regex.test(key) && has(values, key), + schema: pattern.rule + })) + ); + + for (const check of checks) { + if (!check.test(path[0])) { + continue; + } + + if (path.length > 1) { + return recursiveHasCheck(path.slice(1), get(values, path[0]), check.schema); + } + + return true; + } + + return false; + } + + const path = toPath(key); + if (!path.length) return true; + return recursiveHasCheck(path, this[$values], schema); + } + + get(key, defaultValue) { + if (!this.has(key)) { + throw new Error(`Unknown config key "${key}"`); + } + + return cloneDeep(get(this[$values], key, defaultValue), (v) => { + if (typeof v === 'function') { + return v; + } + }); + } +} diff --git a/src/functional_test_runner/lib/config/create_config.js b/src/functional_test_runner/lib/config/create_config.js new file mode 100644 index 0000000000000..b3f522da3f2ae --- /dev/null +++ b/src/functional_test_runner/lib/config/create_config.js @@ -0,0 +1,28 @@ +import { defaultsDeep } from 'lodash'; + +import { Config } from './config'; + +export async function readConfigFile(log, configFile, settingOverrides) { + log.debug('Loading config file from %j', configFile); + + const configModule = require(configFile); + const configProvider = configModule.__esModule + ? configModule.default + : configModule; + + const settings = defaultsDeep( + {}, + settingOverrides, + await configProvider({ + log, + + // give a version of the readConfigFile function to + // the config file that already has has the logger bound + readConfigFile: async (...args) => ( + await readConfigFile(log, ...args) + ) + }) + ); + + return new Config(settings); +} diff --git a/src/functional_test_runner/lib/config/index.js b/src/functional_test_runner/lib/config/index.js new file mode 100644 index 0000000000000..6f5095b3f970b --- /dev/null +++ b/src/functional_test_runner/lib/config/index.js @@ -0,0 +1 @@ +export { readConfigFile } from './create_config'; diff --git a/src/functional_test_runner/lib/config/schema.js b/src/functional_test_runner/lib/config/schema.js new file mode 100644 index 0000000000000..d0b80719fde01 --- /dev/null +++ b/src/functional_test_runner/lib/config/schema.js @@ -0,0 +1,81 @@ +import Joi from 'joi'; + +import { ConsoleReporterProvider } from '../reporters'; + +// valid pattern for ID +// enforced camel-case identifiers for consistency +const ID_PATTERN = /^[a-zA-Z0-9_]+$/; +const INSPECTING = process.execArgv.includes('--inspect'); + +const urlPartsSchema = () => Joi.object().keys({ + protocol: Joi.string().valid('http', 'https'), + hostname: Joi.string().hostname(), + port: Joi.number(), + auth: Joi.string().regex(/^[^:]+:.+$/, 'username and password seperated by a colon'), + username: Joi.string(), + password: Joi.string(), + pathname: Joi.string().regex(/^\//, 'start with a /'), + hash: Joi.string().regex(/^\//, 'start with a /') +}).default(); + +export const schema = Joi.object().keys({ + testFiles: Joi.array().items(Joi.string()).required(), + + services: Joi.object().pattern( + ID_PATTERN, + Joi.func().required() + ).default(), + + pageObjects: Joi.object().pattern( + ID_PATTERN, + Joi.func().required() + ).default(), + + timeouts: Joi.object().keys({ + find: Joi.number().default(10000), + try: Joi.number().default(40000), + test: Joi.number().default(INSPECTING ? Infinity : 120000), + esRequestTimeout: Joi.number().default(30000), + kibanaStabilize: Joi.number().default(15000), + navigateStatusPageCheck: Joi.number().default(250), + }).default(), + + mochaOpts: Joi.object().keys({ + bail: Joi.boolean().default(false), + grep: Joi.string(), + slow: Joi.number().default(30000), + timeout: Joi.number().default(60000), + ui: Joi.string().default('bdd'), + reporterProvider: Joi.func().default(ConsoleReporterProvider), + }).default(), + + users: Joi.object().pattern( + ID_PATTERN, + Joi.object().keys({ + username: Joi.string().required(), + password: Joi.string().required(), + }).required() + ), + + servers: Joi.object().keys({ + webdriver: urlPartsSchema(), + kibana: urlPartsSchema(), + elasticsearch: urlPartsSchema(), + }).default(), + + // definition of apps that work with `common.navigateToApp()` + apps: Joi.object().pattern( + ID_PATTERN, + urlPartsSchema() + ).default(), + + // settings for the esArchiver module + esArchiver: Joi.object().keys({ + directory: Joi.string().required() + }), + + // settings for the screenshots module + screenshots: Joi.object().keys({ + directory: Joi.string().required() + }), +}).default(); diff --git a/src/functional_test_runner/lib/create_provider_collection.js b/src/functional_test_runner/lib/create_provider_collection.js new file mode 100644 index 0000000000000..0e65f92ab4a44 --- /dev/null +++ b/src/functional_test_runner/lib/create_provider_collection.js @@ -0,0 +1,31 @@ +import { + ProviderCollection, + readProviderSpec +} from './providers'; + +/** + * Create a ProviderCollection that includes the Service + * providers and PageObject providers from config, as well + * providers for the default services, lifecycle, log, and + * config + * + * @param {Lifecycle} lifecycle + * @param {ToolingLog} log + * @param {Config} config [description] + * @return {ProviderCollection} + */ +export function createProviderCollection(lifecycle, log, config) { + return new ProviderCollection([ + ...readProviderSpec('Service', { + // base level services that functional_test_runner exposes + lifecycle: () => lifecycle, + log: () => log, + config: () => config, + + ...config.get('services'), + }), + ...readProviderSpec('PageObject', { + ...config.get('pageObjects') + }) + ]); +} diff --git a/src/functional_test_runner/lib/describe_nesting_validator.js b/src/functional_test_runner/lib/describe_nesting_validator.js new file mode 100644 index 0000000000000..d09e1e86c6e5a --- /dev/null +++ b/src/functional_test_runner/lib/describe_nesting_validator.js @@ -0,0 +1,74 @@ +/** + * Creates an object that enables us to intercept all calls to mocha + * interface functions `describe()`, `before()`, etc. and ensure that: + * + * - all calls are made within a `describe()` + * - there is only one top-level `describe()` + * + * To do this we create a proxy to another object, `context`. Mocha + * interfaces will assign all of their exposed methods on this Proxy + * which will wrap all functions with checks for the above rules. + * + * @return {any} the context that mocha-ui interfaces will assign to + */ +export function createDescribeNestingValidator(context) { + let describeCount = 0; + let describeLevel = 0; + + function createContextProxy() { + return new Proxy(context, { + set(target, property, value) { + return Reflect.set(target, property, wrapContextAssignmentValue(property, value)); + } + }); + } + + function wrapContextAssignmentValue(name, value) { + if (typeof value !== 'function') { + return value; + } + + if (name === 'describe') { + return createDescribeProxy(value); + } + + return createNonDescribeProxy(name, value); + } + + function createDescribeProxy(describe) { + return new Proxy(describe, { + apply(target, thisArg, args) { + try { + if (describeCount > 0 && describeLevel === 0) { + throw new Error(` + Test files must only define a single top-level suite. Please ensure that + all calls to \`describe()\` are within a single \`describe()\` call in this file. + `); + } + + describeCount += 1; + describeLevel += 1; + return Reflect.apply(describe, thisArg, args); + } finally { + describeLevel -= 1; + } + } + }); + } + + function createNonDescribeProxy(name, nonDescribe) { + return new Proxy(nonDescribe, { + apply(target, thisArg, args) { + if (describeCount === 0) { + throw new Error(` + All ${name}() calls in test files must be within a describe() call. + `); + } + + return Reflect.apply(nonDescribe, thisArg, args); + } + }); + } + + return createContextProxy(); +} diff --git a/src/functional_test_runner/lib/index.js b/src/functional_test_runner/lib/index.js new file mode 100644 index 0000000000000..5452afe8c4414 --- /dev/null +++ b/src/functional_test_runner/lib/index.js @@ -0,0 +1,5 @@ +export { createLifecycle } from './lifecycle'; +export { readConfigFile } from './config'; +export { createProviderCollection } from './create_provider_collection'; +export { setupMocha } from './setup_mocha'; +export { runTests } from './run_tests'; diff --git a/src/functional_test_runner/lib/lifecycle.js b/src/functional_test_runner/lib/lifecycle.js new file mode 100644 index 0000000000000..7483c967dfc13 --- /dev/null +++ b/src/functional_test_runner/lib/lifecycle.js @@ -0,0 +1,42 @@ +export function createLifecycle() { + const listeners = { + beforeLoadTests: [], + beforeTests: [], + beforeEachTest: [], + cleanup: [], + phaseStart: [], + phaseEnd: [], + }; + + class Lifecycle { + on(name, fn) { + if (!listeners[name]) { + throw new TypeError(`invalid lifecycle event "${name}"`); + } + + listeners[name].push(fn); + } + + async trigger(name, ...args) { + if (!listeners[name]) { + throw new TypeError(`invalid lifecycle event "${name}"`); + } + + try { + if (name !== 'phaseStart' && name !== 'phaseEnd') { + await this.trigger('phaseStart', name); + } + + await Promise.all(listeners[name].map( + async fn => await fn(...args) + )); + } finally { + if (name !== 'phaseStart' && name !== 'phaseEnd') { + await this.trigger('phaseEnd', name); + } + } + } + } + + return new Lifecycle(); +} diff --git a/src/functional_test_runner/lib/load_test_files.js b/src/functional_test_runner/lib/load_test_files.js new file mode 100644 index 0000000000000..f98c7b56941e8 --- /dev/null +++ b/src/functional_test_runner/lib/load_test_files.js @@ -0,0 +1,60 @@ +import { isAbsolute } from 'path'; + +import { loadTracer } from './load_tracer'; +import { createDescribeNestingValidator } from './describe_nesting_validator'; + +/** + * Load an array of test files into a mocha instance + * + * @param {Mocha} mocha + * @param {ToolingLog} log + * @param {ProviderCollection} providers + * @param {String} path + * @return {undefined} - mutates mocha, no return value + */ +export const loadTestFiles = (mocha, log, providers, paths) => { + const innerLoadTestFile = (path) => { + if (typeof path !== 'string' || !isAbsolute(path)) { + throw new TypeError('loadTestFile() only accepts absolute paths'); + } + + loadTracer(path, `testFile[${path}]`, () => { + log.verbose('Loading test file %s', path); + + const testModule = require(path); + const testProvider = testModule.__esModule + ? testModule.default + : testModule; + + runTestProvider(testProvider, path); // eslint-disable-line + }); + }; + + const runTestProvider = (provider, path) => { + if (typeof provider !== 'function') { + throw new Error(`Default export of test files must be a function, got ${provider}`); + } + + loadTracer(provider, `testProvider[${path}]`, () => { + // mocha.suite hocus-pocus comes from: https://git.io/vDnXO + + mocha.suite.emit('pre-require', createDescribeNestingValidator(global), path, mocha); + + const returnVal = provider({ + loadTestFile: innerLoadTestFile, + getService: providers.getService, + getPageObject: providers.getPageObject, + getPageObjects: providers.getPageObjects, + }); + + if (returnVal && typeof returnVal.then === 'function') { + throw new TypeError('Default export of test files must not be an async function'); + } + + mocha.suite.emit('require', returnVal, path, mocha); + mocha.suite.emit('post-require', global, path, mocha); + }); + }; + + paths.forEach(innerLoadTestFile); +}; diff --git a/src/functional_test_runner/lib/load_tracer.js b/src/functional_test_runner/lib/load_tracer.js new file mode 100644 index 0000000000000..c4fc863824ee3 --- /dev/null +++ b/src/functional_test_runner/lib/load_tracer.js @@ -0,0 +1,46 @@ +const globalLoadPath = []; +function getPath(startAt = 0) { + return globalLoadPath + .slice(startAt) + .map(step => step.descrption) + .join(' -> '); +} + +function addPathToMessage(message, startAt) { + const path = getPath(startAt); + if (!path) return message; + return `${message} -- from ${path}`; +} + +/** + * Trace the path followed as dependencies are loaded and + * check for circular dependencies at each step + * + * @param {Any} ident identity of this load step, === compaired + * to identities of previous steps to find circles + * @param {String} descrption description of this step + * @param {Function} load function that executes this step + * @return {Any} the value produced by load() + */ +export function loadTracer(ident, descrption, load) { + const isCircular = globalLoadPath.find(step => step.ident === ident); + if (isCircular) { + throw new Error(addPathToMessage(`Circular reference to "${descrption}"`)); + } + + try { + globalLoadPath.unshift({ ident, descrption }); + return load(); + } catch (err) { + if (err.__fromLoadTracer) { + throw err; + } + + const wrapped = new Error(addPathToMessage(`Failure to load ${descrption}`, 1)); + wrapped.stack = `${wrapped.message}\n\n Original Error: ${err.stack}`; + wrapped.__fromLoadTracer = true; + throw wrapped; + } finally { + globalLoadPath.shift(); + } +} diff --git a/src/functional_test_runner/lib/providers/async_instance.js b/src/functional_test_runner/lib/providers/async_instance.js new file mode 100644 index 0000000000000..85187c705c1af --- /dev/null +++ b/src/functional_test_runner/lib/providers/async_instance.js @@ -0,0 +1,99 @@ +const createdInstanceProxies = new WeakSet(); + +export const isAsyncInstance = val =>( + createdInstanceProxies.has(val) +); + +export const createAsyncInstance = (type, name, promiseForValue) => { + let finalValue; + + const initPromise = promiseForValue.then(v => finalValue = v); + const initFn = () => initPromise; + + const assertReady = desc => { + if (!finalValue) { + throw new Error(` + ${type} \`${desc}\` is loaded asynchronously but isn't available yet. Either await the + promise returned from ${name}.init(), or move this access into a test hook + like \`before()\` or \`beforeEach()\`. + `); + } + }; + + const proxy = new Proxy({}, { + apply(target, context, args) { + assertReady(`${name}()`); + return Reflect.apply(finalValue, context, args); + }, + + construct(target, args, newTarget) { + assertReady(`new ${name}()`); + return Reflect.construct(finalValue, args, newTarget); + }, + + defineProperty(target, prop, descriptor) { + assertReady(`${name}.${prop}`); + return Reflect.defineProperty(finalValue, prop, descriptor); + }, + + deleteProperty(target, prop) { + assertReady(`${name}.${prop}`); + return Reflect.deleteProperty(finalValue, prop); + }, + + get(target, prop, receiver) { + if (prop === 'init') return initFn; + + assertReady(`${name}.${prop}`); + return Reflect.get(finalValue, prop, receiver); + }, + + getOwnPropertyDescriptor(target, prop) { + assertReady(`${name}.${prop}`); + return Reflect.getOwnPropertyDescriptor(finalValue, prop); + }, + + getPrototypeOf() { + assertReady(`${name}`); + return Reflect.getPrototypeOf(finalValue); + }, + + has(target, prop) { + if (prop === 'init') return true; + + assertReady(`${name}.${prop}`); + return Reflect.has(finalValue, prop); + }, + + isExtensible() { + assertReady(`${name}`); + return Reflect.isExtensible(finalValue); + }, + + ownKeys() { + assertReady(`${name}`); + return Reflect.ownKeys(finalValue); + }, + + preventExtensions() { + assertReady(`${name}`); + return Reflect.preventExtensions(finalValue); + }, + + set(target, prop, value, receiver) { + assertReady(`${name}.${prop}`); + return Reflect.set(finalValue, prop, value, receiver); + }, + + setPrototypeOf(target, prototype) { + assertReady(`${name}`); + return Reflect.setPrototypeOf(finalValue, prototype); + } + }); + + // add the created provider to the WeakMap so we can + // check for it later in `isAsyncProvider()` + createdInstanceProxies.add(proxy); + + return proxy; +}; diff --git a/src/functional_test_runner/lib/providers/index.js b/src/functional_test_runner/lib/providers/index.js new file mode 100644 index 0000000000000..728909d3a24e3 --- /dev/null +++ b/src/functional_test_runner/lib/providers/index.js @@ -0,0 +1,2 @@ +export { ProviderCollection } from './provider_collection'; +export { readProviderSpec } from './read_provider_spec'; diff --git a/src/functional_test_runner/lib/providers/provider_collection.js b/src/functional_test_runner/lib/providers/provider_collection.js new file mode 100644 index 0000000000000..9a22bfd9cb16f --- /dev/null +++ b/src/functional_test_runner/lib/providers/provider_collection.js @@ -0,0 +1,84 @@ +import { loadTracer } from '../load_tracer'; +import { createAsyncInstance, isAsyncInstance } from './async_instance'; + +export class ProviderCollection { + constructor(providers) { + this._instances = new Map(); + this._providers = providers; + } + + getService = name => ( + this._getInstance('Service', name) + ) + + getPageObject = name => ( + this._getInstance('PageObject', name) + ) + + getPageObjects = names => { + const pageObjects = {}; + names.forEach(name => pageObjects[name] = this.getPageObject(name)); + return pageObjects; + } + + loadExternalService(name, provider) { + return this._getInstance('Service', name, provider); + } + + async loadAll() { + const asyncInitErrors = []; + await Promise.all( + this._providers.map(async ({ type, name }) => { + try { + const instance = this._getInstance(type, name); + if (isAsyncInstance(instance)) { + await instance.init(); + } + } catch (err) { + asyncInitErrors.push(err); + } + }) + ); + + if (asyncInitErrors.length) { + // just throw the first, it probably caused the others and if not they + // will show up once we fix the first, but creating an AggregateError or + // something seems like overkill + throw asyncInitErrors[0]; + } + } + + _getProvider(type, name) { + const providerDef = this._providers.find(p => p.type === type && p.name === name); + if (!providerDef) { + throw new Error(`Unknown ${type} "${name}"`); + } + return providerDef.fn; + } + + _getInstance(type, name, provider = this._getProvider(type, name)) { + const instances = this._instances; + + return loadTracer(provider, `${type}(${name})`, () => { + if (!provider) { + throw new Error(`Unknown ${type} "${name}"`); + } + + if (!instances.has(provider)) { + let instance = provider({ + getService: this.getService, + getPageObject: this.getPageObject, + getPageObjects: this.getPageObjects, + }); + + if (instance && typeof instance.then === 'function') { + instance = createAsyncInstance(type, name, instance); + } + + instances.set(provider, instance); + } + + return instances.get(provider); + }); + } +} diff --git a/src/functional_test_runner/lib/providers/read_provider_spec.js b/src/functional_test_runner/lib/providers/read_provider_spec.js new file mode 100644 index 0000000000000..4c6fd95bc7e6a --- /dev/null +++ b/src/functional_test_runner/lib/providers/read_provider_spec.js @@ -0,0 +1,9 @@ +export function readProviderSpec(type, providers) { + return Object.keys(providers).map(name => { + return { + type, + name, + fn: providers[name], + }; + }); +} diff --git a/src/functional_test_runner/lib/reporters/console_reporter/colors.js b/src/functional_test_runner/lib/reporters/console_reporter/colors.js new file mode 100644 index 0000000000000..624dd9888787f --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/colors.js @@ -0,0 +1,19 @@ +import { brightBlack, green, yellow, red, brightWhite, brightCyan } from 'ansicolors'; + +export const suite = brightWhite; +export const pending = brightCyan; +export const pass = green; +export const fail = red; + +export function speed(name, txt) { + switch (name) { + case 'fast': + return green(txt); + case 'medium': + return yellow(txt); + case 'slow': + return red(txt); + default: + return brightBlack(txt); + } +} diff --git a/src/functional_test_runner/lib/reporters/console_reporter/console_reporter.js b/src/functional_test_runner/lib/reporters/console_reporter/console_reporter.js new file mode 100644 index 0000000000000..800746b9dbd46 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/console_reporter.js @@ -0,0 +1,122 @@ +import { format } from 'util'; + +import Mocha from 'mocha'; + +import * as colors from './colors'; +import * as symbols from './symbols'; +import { ms } from './ms'; +import { writeEpilogue } from './write_epilogue'; + +export function ConsoleReporterProvider({ getService }) { + const log = getService('log'); + + return class MochaReporter extends Mocha.reporters.Base { + constructor(runner) { + super(runner); + runner.on('start', this.onStart); + runner.on('hook', this.onHookStart); + runner.on('hook end', this.onHookEnd); + runner.on('test', this.onTestStart); + runner.on('suite', this.onSuiteStart); + runner.on('pending', this.onPending); + runner.on('pass', this.onPass); + runner.on('fail', this.onFail); + runner.on('test end', this.onTestEnd); + runner.on('suite end', this.onSuiteEnd); + runner.on('end', this.onEnd); + } + + onStart = () => { + log.write(''); + } + + onHookStart = hook => { + log.write('-> ' + colors.suite(hook.title)); + log.indent(2); + } + + onHookEnd = () => { + log.indent(-2); + } + + onSuiteStart = suite => { + if (!suite.root) { + log.write('-: ' + colors.suite(suite.title)); + } + + log.indent(2); + } + + onSuiteEnd = () => { + if (log.indent(-2) === '') { + log.write(); + } + } + + onTestStart = test => { + log.write(`-> ${test.title}`); + log.indent(2); + } + + onTestEnd = () => { + log.indent(-2); + } + + onPending = () => { + log.write('-> ' + colors.pending(test.title)); + } + + onPass = test => { + + let time = ''; + if (test.speed !== 'fast') { + time = colors.speed(test.speed, ` (${ms(test.duration)})`); + } + + const pass = colors.pass(`${symbols.ok} pass`); + log.write(`- ${pass} ${time}`); + } + + onFail = test => { + // NOTE: this is super gross + // + // - I started by trying to extract the Base.list() logic from mocha + // but it's a lot more complicated than this is horrible. + // - In order to fix the numbering and indentation we monkey-patch + // console.log and parse the logged output. + // + let output = ''; + const realLog = console.log; + console.log = (...args) => output += `${format(...args)}\n`; + try { + Mocha.reporters.Base.list([test]); + } finally { + console.log = realLog; + } + + log.indent(-2); + log.write( + `- ${symbols.err} ` + + colors.fail(`fail: "${test.fullTitle()}"`) + + '\n' + + output + .split('\n') + .slice(2) // drop the first two lines, (empty + test title) + .map(line => { + // move leading colors behind leading spaces + return line.replace(/^((?:\[.+m)+)(\s+)/, '$2$1'); + }) + .map(line => { + // shrink mocha's indentation + return line.replace(/^\s{5,5}/, ' '); + }) + .join('\n') + ); + log.indent(2); + } + + onEnd = () => { + writeEpilogue(log, this.stats); + } + }; +} diff --git a/src/functional_test_runner/lib/reporters/console_reporter/index.js b/src/functional_test_runner/lib/reporters/console_reporter/index.js new file mode 100644 index 0000000000000..0129f2eea7283 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/index.js @@ -0,0 +1 @@ +export { ConsoleReporterProvider } from './console_reporter'; diff --git a/src/functional_test_runner/lib/reporters/console_reporter/ms.js b/src/functional_test_runner/lib/reporters/console_reporter/ms.js new file mode 100644 index 0000000000000..24f80b6241d44 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/ms.js @@ -0,0 +1,28 @@ +import moment from 'moment'; + +/** + * Format a milliseconds value as a string + * + * @param {number} val + * @return {string} + */ +export function ms(val) { + const duration = moment.duration(val); + if (duration.days() >= 1) { + return duration.days().toFixed(1) + 'd'; + } + + if (duration.hours() >= 1) { + return duration.hours().toFixed(1) + 'h'; + } + + if (duration.minutes() >= 1) { + return duration.minutes().toFixed(1) + 'm'; + } + + if (duration.seconds() >= 1) { + return duration.as('seconds').toFixed(1) + 's'; + } + + return val + 'ms'; +} diff --git a/src/functional_test_runner/lib/reporters/console_reporter/symbols.js b/src/functional_test_runner/lib/reporters/console_reporter/symbols.js new file mode 100644 index 0000000000000..ce871ff1e9e23 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/symbols.js @@ -0,0 +1,9 @@ +// originally extracted from mocha https://git.io/v1PGh + +export const ok = process.platform === 'win32' + ? '\u221A' + : '✓'; + +export const err = process.platform === 'win32' + ? '\u00D7' + : '✖'; diff --git a/src/functional_test_runner/lib/reporters/console_reporter/write_epilogue.js b/src/functional_test_runner/lib/reporters/console_reporter/write_epilogue.js new file mode 100644 index 0000000000000..f829e27593933 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/console_reporter/write_epilogue.js @@ -0,0 +1,33 @@ +import * as colors from './colors'; +import { ms } from './ms'; + +export function writeEpilogue(log, stats) { + // header + log.write(); + + // passes + log.write( + `${colors.pass('%d passing')} (%s)`, + stats.passes || 0, + ms(stats.duration) + ); + + // pending + if (stats.pending) { + log.write( + colors.pending('%d pending'), + stats.pending + ); + } + + // failures + if (stats.failures) { + log.write( + '%d failing', + stats.failures + ); + } + + // footer + log.write(); +} diff --git a/src/functional_test_runner/lib/reporters/index.js b/src/functional_test_runner/lib/reporters/index.js new file mode 100644 index 0000000000000..0129f2eea7283 --- /dev/null +++ b/src/functional_test_runner/lib/reporters/index.js @@ -0,0 +1 @@ +export { ConsoleReporterProvider } from './console_reporter'; diff --git a/src/functional_test_runner/lib/run_tests.js b/src/functional_test_runner/lib/run_tests.js new file mode 100644 index 0000000000000..2f636f6761042 --- /dev/null +++ b/src/functional_test_runner/lib/run_tests.js @@ -0,0 +1,31 @@ +/** + * Run the tests that have already been loaded into + * mocha. aborts tests on 'cleanup' lifecycle runs + * + * @param {Lifecycle} lifecycle + * @param {ToolingLog} log + * @param {Mocha} mocha + * @return {Promise} resolves to the number of test failures + */ +export async function runTests(lifecycle, log, mocha) { + let runComplete = false; + const runner = mocha.run(() => { + runComplete = true; + }); + + lifecycle.on('cleanup', () => { + if (!runComplete) runner.abort(); + }); + + return new Promise((resolve) => { + const respond = () => resolve(runner.failures); + + // if there are no tests, mocha.run() is sync + // and the 'end' event can't be listened to + if (runComplete) { + respond(); + } else { + runner.on('end', respond); + } + }); +} diff --git a/src/functional_test_runner/lib/setup_mocha.js b/src/functional_test_runner/lib/setup_mocha.js new file mode 100644 index 0000000000000..32130e24af8f7 --- /dev/null +++ b/src/functional_test_runner/lib/setup_mocha.js @@ -0,0 +1,32 @@ +import Mocha from 'mocha'; + +import { loadTestFiles } from './load_test_files'; + +/** + * Instansiate mocha and load testfiles into it + * + * @param {Lifecycle} lifecycle + * @param {ToolingLog} log + * @param {Config} config + * @param {ProviderCollection} providers + * @return {Promise} + */ +export async function setupMocha(lifecycle, log, config, providers) { + // configure mocha + const mocha = new Mocha({ + timeout: config.get('timeouts.test'), + ...config.get('mochaOpts'), + reporter: await providers.loadExternalService( + 'configured mocha reporter', + config.get('mochaOpts.reporterProvider') + ) + }); + + // global beforeEach hook in root suite triggers before all others + mocha.suite.beforeEach('global before each', async () => { + await lifecycle.trigger('beforeEachTest'); + }); + + loadTestFiles(mocha, log, providers, config.get('testFiles')); + return mocha; +} diff --git a/src/ui/public/url/modify_url.js b/src/ui/public/url/modify_url.js index dcd32020a27b3..bb98cee85eff7 100644 --- a/src/ui/public/url/modify_url.js +++ b/src/ui/public/url/modify_url.js @@ -1,61 +1,2 @@ -import { parse as parseUrl, format as formatUrl } from 'url'; - -/** - * Takes a URL and a function that takes the meaningful parts - * of the URL as a key-value object, modifies some or all of - * the parts, and returns the modified parts formatted again - * as a url. - * - * Url Parts sent: - * - protocol - * - slashes (does the url have the //) - * - auth - * - hostname (just the name of the host, no port or auth information) - * - port - * - pathmame (the path after the hostname, no query or hash, starts - * with a slash if there was a path) - * - query (always an object, even when no query on original url) - * - hash - * - * Why? - * - The default url library in node produces several conflicting - * properties on the "parsed" output. Modifying any of these might - * lead to the modifications being ignored (depending on which - * property was modified) - * - It's not always clear wither to use path/pathname, host/hostname, - * so this trys to add helpful constraints - * - * @param {String} url - the url to parse - * @param {Function} block - a function that will modify the parsed url, or return a new one - * @return {String} the modified and reformatted url - */ -export function modifyUrl(url, block) { - if (typeof block !== 'function') { - throw new TypeError('You must pass a block to define the modifications desired'); - } - - const parsed = parseUrl(url, true); - - // copy over the most specific version of each - // property. By default, the parsed url includes - // several conflicting properties (like path and - // pathname + search, or search and query) and keeping - // track of which property is actually used when they - // are formatted is harder than necessary - const meaningfulParts = { - protocol: parsed.protocol, - slashes: parsed.slashes, - auth: parsed.auth, - hostname: parsed.hostname, - port: parsed.port, - pathname: parsed.pathname, - query: parsed.query || {}, - hash: parsed.hash, - }; - - // the block modifies the meaningfulParts object, or returns a new one - const modifications = block(meaningfulParts) || meaningfulParts; - - // format the modified/replaced meaningfulParts back into a url - return formatUrl(modifications); -} +// we select the modify_url directly so the other utils, which are not browser compatible, are not included +export { modifyUrl } from '../../../utils/modify_url'; diff --git a/src/ui/public/url/__tests__/modify_url.js b/src/utils/__tests__/modify_url.js similarity index 100% rename from src/ui/public/url/__tests__/modify_url.js rename to src/utils/__tests__/modify_url.js diff --git a/src/utils/index.js b/src/utils/index.js index a731f292a453b..1bf0a82264199 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -17,3 +17,6 @@ export { createReduceStream, createSplitStream, } from './streams'; + +export { modifyUrl } from './modify_url'; +export { createToolingLog } from './tooling_log'; diff --git a/src/utils/modify_url.js b/src/utils/modify_url.js new file mode 100644 index 0000000000000..dcd32020a27b3 --- /dev/null +++ b/src/utils/modify_url.js @@ -0,0 +1,61 @@ +import { parse as parseUrl, format as formatUrl } from 'url'; + +/** + * Takes a URL and a function that takes the meaningful parts + * of the URL as a key-value object, modifies some or all of + * the parts, and returns the modified parts formatted again + * as a url. + * + * Url Parts sent: + * - protocol + * - slashes (does the url have the //) + * - auth + * - hostname (just the name of the host, no port or auth information) + * - port + * - pathmame (the path after the hostname, no query or hash, starts + * with a slash if there was a path) + * - query (always an object, even when no query on original url) + * - hash + * + * Why? + * - The default url library in node produces several conflicting + * properties on the "parsed" output. Modifying any of these might + * lead to the modifications being ignored (depending on which + * property was modified) + * - It's not always clear wither to use path/pathname, host/hostname, + * so this trys to add helpful constraints + * + * @param {String} url - the url to parse + * @param {Function} block - a function that will modify the parsed url, or return a new one + * @return {String} the modified and reformatted url + */ +export function modifyUrl(url, block) { + if (typeof block !== 'function') { + throw new TypeError('You must pass a block to define the modifications desired'); + } + + const parsed = parseUrl(url, true); + + // copy over the most specific version of each + // property. By default, the parsed url includes + // several conflicting properties (like path and + // pathname + search, or search and query) and keeping + // track of which property is actually used when they + // are formatted is harder than necessary + const meaningfulParts = { + protocol: parsed.protocol, + slashes: parsed.slashes, + auth: parsed.auth, + hostname: parsed.hostname, + port: parsed.port, + pathname: parsed.pathname, + query: parsed.query || {}, + hash: parsed.hash, + }; + + // the block modifies the meaningfulParts object, or returns a new one + const modifications = block(meaningfulParts) || meaningfulParts; + + // format the modified/replaced meaningfulParts back into a url + return formatUrl(modifications); +} diff --git a/src/es_archiver/lib/log/__tests__/log.js b/src/utils/tooling_log/__tests__/log.js similarity index 89% rename from src/es_archiver/lib/log/__tests__/log.js rename to src/utils/tooling_log/__tests__/log.js index ea4f0365645af..e18840165d4b6 100644 --- a/src/es_archiver/lib/log/__tests__/log.js +++ b/src/utils/tooling_log/__tests__/log.js @@ -4,13 +4,13 @@ import Chance from 'chance'; import { createConcatStream, createPromiseFromStreams -} from '../../../../utils'; +} from '../../streams'; -import { createLog } from '../'; +import { createToolingLog } from '../'; const chance = new Chance(); const capture = (level, block) => { - const log = createLog(level); + const log = createToolingLog(level); block(log); log.end(); return createPromiseFromStreams([ log, createConcatStream('') ]); @@ -34,9 +34,9 @@ const somethingTest = (logLevel, method) => { }); }; -describe('esArchiver: createLog(logLevel, output)', () => { +describe('utils: createToolingLog(logLevel, output)', () => { it('is a readable stream', async () => { - const log = createLog('debug'); + const log = createToolingLog('debug'); log.info('Foo'); log.info('Bar'); log.info('Baz'); @@ -79,7 +79,7 @@ describe('esArchiver: createLog(logLevel, output)', () => { // by specifying a long length const level = chance.word({ length: 10 }); - expect(() => createLog(level)) + expect(() => createToolingLog(level)) .to.throwError(level); }); }); diff --git a/src/es_archiver/lib/log/__tests__/log_levels.js b/src/utils/tooling_log/__tests__/log_levels.js similarity index 65% rename from src/es_archiver/lib/log/__tests__/log_levels.js rename to src/utils/tooling_log/__tests__/log_levels.js index 5bdabefc08e53..0ce608d0d8a11 100644 --- a/src/es_archiver/lib/log/__tests__/log_levels.js +++ b/src/utils/tooling_log/__tests__/log_levels.js @@ -10,8 +10,10 @@ describe('createLogLevelFlags()', () => { expect(createLogLevelFlags('silent')).to.eql({ silent: true, error: false, + warning: false, info: false, debug: false, + verbose: false, }); }); }); @@ -21,8 +23,23 @@ describe('createLogLevelFlags()', () => { expect(createLogLevelFlags('error')).to.eql({ silent: true, error: true, + warning: false, info: false, debug: false, + verbose: false, + }); + }); + }); + + describe('logLevel=warning', () => { + it('produces correct map', () => { + expect(createLogLevelFlags('warning')).to.eql({ + silent: true, + error: true, + warning: true, + info: false, + debug: false, + verbose: false, }); }); }); @@ -32,8 +49,10 @@ describe('createLogLevelFlags()', () => { expect(createLogLevelFlags('info')).to.eql({ silent: true, error: true, + warning: true, info: true, debug: false, + verbose: false, }); }); }); @@ -43,8 +62,23 @@ describe('createLogLevelFlags()', () => { expect(createLogLevelFlags('debug')).to.eql({ silent: true, error: true, + warning: true, + info: true, + debug: true, + verbose: false, + }); + }); + }); + + describe('logLevel=verbose', () => { + it('produces correct map', () => { + expect(createLogLevelFlags('verbose')).to.eql({ + silent: true, + error: true, + warning: true, info: true, debug: true, + verbose: true, }); }); }); diff --git a/src/utils/tooling_log/index.js b/src/utils/tooling_log/index.js new file mode 100644 index 0000000000000..28f67ed09d3ab --- /dev/null +++ b/src/utils/tooling_log/index.js @@ -0,0 +1 @@ +export { createToolingLog } from './tooling_log'; diff --git a/src/es_archiver/lib/log/log_levels.js b/src/utils/tooling_log/log_levels.js similarity index 94% rename from src/es_archiver/lib/log/log_levels.js rename to src/utils/tooling_log/log_levels.js index a30076681a1a4..c45729394e55b 100644 --- a/src/es_archiver/lib/log/log_levels.js +++ b/src/utils/tooling_log/log_levels.js @@ -2,8 +2,10 @@ const LEVELS = [ 'silent', 'error', + 'warning', 'info', 'debug', + 'verbose', ]; export function createLogLevelFlags(levelLimit) { diff --git a/src/utils/tooling_log/tooling_log.js b/src/utils/tooling_log/tooling_log.js new file mode 100644 index 0000000000000..e08ff061c2af0 --- /dev/null +++ b/src/utils/tooling_log/tooling_log.js @@ -0,0 +1,59 @@ +import { format } from 'util'; +import { PassThrough } from 'stream'; + +import { createLogLevelFlags } from './log_levels'; +import { magenta, yellow, red, blue, brightBlack } from 'ansicolors'; + +export function createToolingLog(logLevel = 'silent') { + const logLevelFlags = createLogLevelFlags(logLevel); + + let indentString = ''; + + class ToolingLog extends PassThrough { + verbose(...args) { + if (!logLevelFlags.verbose) return; + this.write(' %s ', magenta('sill'), format(...args)); + } + + debug(...args) { + if (!logLevelFlags.debug) return; + this.write(' %s ', brightBlack('debg'), format(...args)); + } + + info(...args) { + if (!logLevelFlags.info) return; + this.write(' %s ', blue('info'), format(...args)); + } + + warning(...args) { + if (!logLevelFlags.warning) return; + this.write(' %s ', yellow('warn'), format(...args)); + } + + error(err) { + if (!logLevelFlags.error) return; + + if (typeof err !== 'string' && !(err instanceof Error)) { + err = new Error(`"${err}" thrown`); + } + + this.write('%s ', red('ERROR'), err.stack || err.message || err); + } + + indent(delta = 0) { + const width = Math.max(0, indentString.length + delta); + indentString = ' '.repeat(width); + return indentString.length; + } + + write(...args) { + format(...args).split('\n').forEach((line, i) => { + const subLineIndent = i === 0 ? '' : ' '; + const indent = !indentString ? '' : indentString.slice(0, -1) + (i === 0 && line[0] === '-' ? '└' : '│'); + super.write(`${indent}${subLineIndent}${line}\n`); + }); + } + } + + return new ToolingLog(); +} diff --git a/tasks/config/copy.js b/tasks/config/copy.js index 22bc6f6d3eed2..f93f7fa49f479 100644 --- a/tasks/config/copy.js +++ b/tasks/config/copy.js @@ -13,6 +13,7 @@ module.exports = function () { '!src/cli/cluster/**', '!src/ui_framework/doc_site/**', '!src/es_archiver/**', + '!src/functional_test_runner/**', 'bin/**', 'ui_framework/components/**', 'ui_framework/services/**', diff --git a/tasks/config/intern.js b/tasks/config/intern.js deleted file mode 100644 index a37095f06d23c..0000000000000 --- a/tasks/config/intern.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = function (grunt) { - return { - options: { - runType: 'runner', - config: 'test/intern', - bail: true, - reporters: ['Console'], - grep: grunt.option('grep'), - functionalSuites: grunt.option('functionalSuites'), - appSuites: grunt.option('appSuites') - }, - dev: {}, - api: { - options: { - runType: 'client', - config: 'test/intern_api' - } - }, - visualRegression: { - options: { - runType: 'runner', - config: 'test/intern_visual_regression' - } - } - }; -}; diff --git a/tasks/config/simplemocha.js b/tasks/config/simplemocha.js index 4c655d9b37473..16450d235d700 100644 --- a/tasks/config/simplemocha.js +++ b/tasks/config/simplemocha.js @@ -1,5 +1,3 @@ -require('../../test/mocha_setup'); - module.exports = { options: { timeout: 10000, @@ -10,11 +8,18 @@ module.exports = { }, all: { src: [ + 'test/mocha_setup.js', 'test/**/__tests__/**/*.js', 'src/**/__tests__/**/*.js', 'test/fixtures/__tests__/*.js', '!src/**/public/**', '!**/_*.js' ] + }, + api: { + src: [ + 'test/mocha_setup.js', + 'test/unit/**/*.js' + ] } }; diff --git a/tasks/functional_test_runner.js b/tasks/functional_test_runner.js new file mode 100644 index 0000000000000..7f778e15d5391 --- /dev/null +++ b/tasks/functional_test_runner.js @@ -0,0 +1,31 @@ +import { resolve } from 'path'; + +import { createFunctionalTestRunner } from '../src/functional_test_runner'; +import { createToolingLog } from '../src/utils'; + +export default function (grunt) { + grunt.registerTask('functionalTestRunner', function () { + const log = createToolingLog('debug'); + log.pipe(process.stdout); + + const functionalTestRunner = createFunctionalTestRunner({ + log, + configFile: resolve(__dirname, '../test/functional/config.js'), + }); + + const callback = this.async(); + functionalTestRunner.run() + .then(failureCount => { + if (failureCount) { + grunt.fail.error(`${failureCount} test failures`); + return; + } + + callback(); + }) + .catch(err => { + grunt.log.error(err.stack); + callback(err); + }); + }); +} diff --git a/tasks/test.js b/tasks/test.js index c66bb1539a16c..1378aef786854 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -3,16 +3,6 @@ import _, { keys } from 'lodash'; import visualRegression from '../utilities/visual_regression'; module.exports = function (grunt) { - grunt.registerTask('test:visualRegression', [ - 'intern:visualRegression:takeScreenshots', - 'test:visualRegression:buildGallery' - ]); - - grunt.registerTask('test:visualRegression:takeScreenshots', [ - 'clean:screenshots', - 'intern:visualRegression' - ]); - grunt.registerTask( 'test:visualRegression:buildGallery', 'Compare screenshots and generate diff images.', @@ -68,11 +58,9 @@ module.exports = function (grunt) { 'checkPlugins', 'esvm:ui', 'run:testUIServer', - 'run:chromeDriver', 'clean:screenshots', - 'intern:dev', + 'functionalTestRunner', 'esvm_shutdown:ui', - 'stop:chromeDriver', 'stop:testUIServer' ]); @@ -85,14 +73,13 @@ module.exports = function (grunt) { grunt.registerTask('test:ui:runner', [ 'checkPlugins', 'clean:screenshots', - 'run:devChromeDriver', - 'intern:dev' + 'functionalTestRunner' ]); grunt.registerTask('test:api', [ 'esvm:ui', 'run:apiTestServer', - 'intern:api', + 'simplemocha:api', 'esvm_shutdown:ui', 'stop:apiTestServer' ]); @@ -103,7 +90,7 @@ module.exports = function (grunt) { ]); grunt.registerTask('test:api:runner', [ - 'intern:api' + 'simplemocha:api' ]); grunt.registerTask('test', subTask => { diff --git a/test/functional/apps/console/_console.js b/test/functional/apps/console/_console.js index 8e804286298c1..9f40cedf08a0a 100644 --- a/test/functional/apps/console/_console.js +++ b/test/functional/apps/console/_console.js @@ -1,9 +1,5 @@ - import expect from 'expect.js'; -import PageObjects from '../../../support/page_objects'; -import { bdd } from '../../../support'; - const DEFAULT_REQUEST = ` GET _search @@ -15,54 +11,60 @@ GET _search `.trim(); -bdd.describe('console app', function describeIndexTests() { - bdd.before(function () { - PageObjects.common.debug('navigateTo console'); - return PageObjects.common.navigateToApp('console'); - }); +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'console']); - bdd.it('should show the default request', function () { - PageObjects.common.saveScreenshot('Console-help-expanded'); - // collapse the help pane because we only get the VISIBLE TEXT, not the part that is scrolled - return PageObjects.console.collapseHelp() - .then(function () { - PageObjects.common.saveScreenshot('Console-help-collapsed'); - return PageObjects.common.try(function () { - return PageObjects.console.getRequest() - .then(function (actualRequest) { - expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST); + describe('console app', function describeIndexTests() { + before(function () { + log.debug('navigateTo console'); + return PageObjects.common.navigateToApp('console'); + }); + + it('should show the default request', function () { + PageObjects.common.saveScreenshot('Console-help-expanded'); + // collapse the help pane because we only get the VISIBLE TEXT, not the part that is scrolled + return PageObjects.console.collapseHelp() + .then(function () { + PageObjects.common.saveScreenshot('Console-help-collapsed'); + return retry.try(function () { + return PageObjects.console.getRequest() + .then(function (actualRequest) { + expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST); + }); }); }); }); - }); - bdd.it('default request response should contain .kibana' , function () { - const expectedResponseContains = '"_index": ".kibana",'; + it('default request response should contain .kibana' , function () { + const expectedResponseContains = '"_index": ".kibana",'; - return PageObjects.console.clickPlay() - .then(function () { - PageObjects.common.saveScreenshot('Console-default-request'); - return PageObjects.common.try(function () { - return PageObjects.console.getResponse() - .then(function (actualResponse) { - PageObjects.common.debug(actualResponse); - expect(actualResponse).to.contain(expectedResponseContains); + return PageObjects.console.clickPlay() + .then(function () { + PageObjects.common.saveScreenshot('Console-default-request'); + return retry.try(function () { + return PageObjects.console.getResponse() + .then(function (actualResponse) { + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + }); }); }); }); - }); - bdd.it('settings should allow changing the text size', async function () { - await PageObjects.console.setFontSizeSetting(20); - await PageObjects.common.try(async () => { - // the settings are not applied synchronously, so we retry for a time - expect(await PageObjects.console.getRequestFontSize()).to.be('20px'); - }); + it('settings should allow changing the text size', async function () { + await PageObjects.console.setFontSizeSetting(20); + await retry.try(async () => { + // the settings are not applied synchronously, so we retry for a time + expect(await PageObjects.console.getRequestFontSize()).to.be('20px'); + }); - await PageObjects.console.setFontSizeSetting(24); - await PageObjects.common.try(async () => { - // the settings are not applied synchronously, so we retry for a time - expect(await PageObjects.console.getRequestFontSize()).to.be('24px'); + await PageObjects.console.setFontSizeSetting(24); + await retry.try(async () => { + // the settings are not applied synchronously, so we retry for a time + expect(await PageObjects.console.getRequestFontSize()).to.be('24px'); + }); }); }); -}); +} diff --git a/test/functional/apps/console/index.js b/test/functional/apps/console/index.js index 73ce7300ec554..de1ca12eae62d 100644 --- a/test/functional/apps/console/index.js +++ b/test/functional/apps/console/index.js @@ -1,16 +1,14 @@ +export default function ({ getService, loadTestFile }) { + const config = getService('config'); + const remote = getService('remote'); -import { - bdd, - remote, - defaultTimeout, -} from '../../../support'; + describe('console app', function () { + this.timeout(config.get('timeouts.find')); -bdd.describe('console app', function () { - this.timeout = defaultTimeout; + before(async function () { + await remote.setWindowSize(1200,800); + }); - bdd.before(function () { - return remote.setWindowSize(1200,800); + loadTestFile(require.resolve('./_console')); }); - - require('./_console'); -}); +} diff --git a/test/functional/apps/context/_discover_navigation.js b/test/functional/apps/context/_discover_navigation.js index b3cba31b9382c..87bf821bef669 100644 --- a/test/functional/apps/context/_discover_navigation.js +++ b/test/functional/apps/context/_discover_navigation.js @@ -1,54 +1,57 @@ import expect from 'expect.js'; -import { bdd } from '../../../support'; -import PageObjects from '../../../support/page_objects'; - - const TEST_DISCOVER_START_TIME = '2015-09-19 06:31:44.000'; const TEST_DISCOVER_END_TIME = '2015-09-23 18:31:44.000'; const TEST_COLUMN_NAMES = ['@message']; -bdd.describe('context link in discover', function contextSize() { - bdd.before(async function() { - await PageObjects.common.navigateToApp('discover'); - await PageObjects.header.setAbsoluteRange(TEST_DISCOVER_START_TIME, TEST_DISCOVER_END_TIME); - await Promise.all(TEST_COLUMN_NAMES.map((columnName) => ( - PageObjects.discover.clickFieldListItemAdd(columnName) - ))); - }); +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const docTable = getService('docTable'); + const PageObjects = getPageObjects(['common', 'header', 'discover']); + + describe('context link in discover', function contextSize() { + before(async function() { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.setAbsoluteRange(TEST_DISCOVER_START_TIME, TEST_DISCOVER_END_TIME); + await Promise.all(TEST_COLUMN_NAMES.map((columnName) => ( + PageObjects.discover.clickFieldListItemAdd(columnName) + ))); + }); - bdd.it('should open the context view with the selected document as anchor', async function () { - const discoverDocTable = await PageObjects.docTable.getTable(); - const firstRow = (await PageObjects.docTable.getBodyRows(discoverDocTable))[0]; - const firstTimestamp = await (await PageObjects.docTable.getFields(firstRow))[0] - .getVisibleText(); - - // add a column in Discover - await (await PageObjects.docTable.getRowExpandToggle(firstRow)).click(); - const firstDetailsRow = (await PageObjects.docTable.getDetailsRows(discoverDocTable))[0]; - await (await PageObjects.docTable.getRowActions(firstDetailsRow))[0].click(); - - // check the column in the Context View - await PageObjects.common.try(async () => { - const contextDocTable = await PageObjects.docTable.getTable(); - const anchorRow = await PageObjects.docTable.getAnchorRow(contextDocTable); - const anchorTimestamp = await (await PageObjects.docTable.getFields(anchorRow))[0] + it('should open the context view with the selected document as anchor', async function () { + const discoverDocTable = await docTable.getTable(); + const firstRow = (await docTable.getBodyRows(discoverDocTable))[0]; + const firstTimestamp = await (await docTable.getFields(firstRow))[0] .getVisibleText(); - expect(anchorTimestamp).to.equal(firstTimestamp); + + // add a column in Discover + await (await docTable.getRowExpandToggle(firstRow)).click(); + const firstDetailsRow = (await docTable.getDetailsRows(discoverDocTable))[0]; + await (await docTable.getRowActions(firstDetailsRow))[0].click(); + + // check the column in the Context View + await retry.try(async () => { + const contextDocTable = await docTable.getTable(); + const anchorRow = await docTable.getAnchorRow(contextDocTable); + const anchorTimestamp = await (await docTable.getFields(anchorRow))[0] + .getVisibleText(); + expect(anchorTimestamp).to.equal(firstTimestamp); + }); }); - }); - bdd.it('should open the context view with the same columns', async function () { - const docTable = await PageObjects.docTable.getTable(); - await PageObjects.common.try(async () => { - const headerFields = await PageObjects.docTable.getHeaderFields(docTable); - const columnNames = await Promise.all(headerFields.map((headerField) => ( - headerField.getVisibleText() - ))); - expect(columnNames).to.eql([ - 'Time', - ...TEST_COLUMN_NAMES, - ]); + it('should open the context view with the same columns', async function () { + const table = await docTable.getTable(); + await retry.try(async () => { + const headerFields = await docTable.getHeaderFields(table); + const columnNames = await Promise.all(headerFields.map((headerField) => ( + headerField.getVisibleText() + ))); + expect(columnNames).to.eql([ + 'Time', + ...TEST_COLUMN_NAMES, + ]); + }); }); }); -}); + +} diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js index adfa0670b7db9..b200bb8f74f1f 100644 --- a/test/functional/apps/context/_size.js +++ b/test/functional/apps/context/_size.js @@ -1,63 +1,68 @@ import expect from 'expect.js'; -import { bdd, esClient } from '../../../support'; -import PageObjects from '../../../support/page_objects'; - - const TEST_INDEX_PATTERN = 'logstash-*'; const TEST_ANCHOR_TYPE = 'apache'; const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI'; const TEST_DEFAULT_CONTEXT_SIZE = 7; const TEST_STEP_SIZE = 3; -bdd.describe('context size', function contextSize() { - bdd.before(async function() { - await esClient.updateConfigDoc({ - 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, - 'context:step': `${TEST_STEP_SIZE}`, +export default function ({ getService, getPageObjects }) { + const remote = getService('remote'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const docTable = getService('docTable'); + const PageObjects = getPageObjects(['context']); + + describe('context size', function contextSize() { + before(async function() { + await kibanaServer.uiSettings.update({ + 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, + 'context:step': `${TEST_STEP_SIZE}`, + }); }); - }); - bdd.it('should default to the `context:defaultSize` setting', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); + it('should default to the `context:defaultSize` setting', async function () { + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); - const docTable = await PageObjects.docTable.getTable(); - await PageObjects.common.try(async function () { - expect(await PageObjects.docTable.getBodyRows(docTable)).to.have.length(2 * TEST_DEFAULT_CONTEXT_SIZE + 1); - }); - await PageObjects.common.try(async function() { - const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); - expect(await predecessorCountPicker.getProperty('value')).to.equal(`${TEST_DEFAULT_CONTEXT_SIZE}`); + const table = await docTable.getTable(); + await retry.try(async function () { + expect(await docTable.getBodyRows(table)).to.have.length(2 * TEST_DEFAULT_CONTEXT_SIZE + 1); + }); + await retry.try(async function() { + const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); + expect(await predecessorCountPicker.getProperty('value')).to.equal(`${TEST_DEFAULT_CONTEXT_SIZE}`); + }); + await retry.try(async function() { + const successorCountPicker = await PageObjects.context.getSuccessorCountPicker(); + expect(await successorCountPicker.getProperty('value')).to.equal(`${TEST_DEFAULT_CONTEXT_SIZE}`); + }); }); - await PageObjects.common.try(async function() { - const successorCountPicker = await PageObjects.context.getSuccessorCountPicker(); - expect(await successorCountPicker.getProperty('value')).to.equal(`${TEST_DEFAULT_CONTEXT_SIZE}`); - }); - }); - bdd.it('should increase according to the `context:step` setting when clicking the `load newer` button', async function() { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); + it('should increase according to the `context:step` setting when clicking the `load newer` button', async function() { + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); - const docTable = await PageObjects.docTable.getTable(); - await (await PageObjects.context.getPredecessorLoadMoreButton()).click(); - await PageObjects.common.try(async function () { - expect(await PageObjects.docTable.getBodyRows(docTable)).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); + const table = await docTable.getTable(); + await (await PageObjects.context.getPredecessorLoadMoreButton()).click(); + await retry.try(async function () { + expect(await docTable.getBodyRows(table)).to.have.length( + 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 + ); + }); }); - }); - bdd.it('should increase according to the `context:step` setting when clicking the `load older` button', async function() { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); - - const docTable = await PageObjects.docTable.getTable(); - const successorLoadMoreButton = await PageObjects.context.getSuccessorLoadMoreButton(); - await this.remote.moveMouseTo(successorLoadMoreButton); // possibly scroll until the button is visible - await successorLoadMoreButton.click(); - await PageObjects.common.try(async function () { - expect(await PageObjects.docTable.getBodyRows(docTable)).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); + it('should increase according to the `context:step` setting when clicking the `load older` button', async function() { + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID); + + const table = await docTable.getTable(); + const successorLoadMoreButton = await PageObjects.context.getSuccessorLoadMoreButton(); + await remote.moveMouseTo(successorLoadMoreButton); // possibly scroll until the button is visible + await successorLoadMoreButton.click(); + await retry.try(async function () { + expect(await docTable.getBodyRows(table)).to.have.length( + 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 + ); + }); }); }); -}); + +} diff --git a/test/functional/apps/context/index.js b/test/functional/apps/context/index.js index 4347a86fbbeb2..e18b2feae9f10 100644 --- a/test/functional/apps/context/index.js +++ b/test/functional/apps/context/index.js @@ -1,26 +1,25 @@ +export default function ({ getService, getPageObjects, loadTestFile }) { + const config = getService('config'); + const remote = getService('remote'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common']); -import { - bdd, - defaultTimeout, - esArchiver, -} from '../../../support'; + describe('context app', function () { + this.timeout(config.get('timeouts.test')); -import PageObjects from '../../../support/page_objects'; + before(async function () { + await remote.setWindowSize(1200,800); + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('visualize'); + await PageObjects.common.navigateToApp('discover'); + }); -bdd.describe('context app', function () { - this.timeout = defaultTimeout; + after(function unloadMakelogs() { + return esArchiver.unload('logstash_functional'); + }); - bdd.before(async function () { - await PageObjects.remote.setWindowSize(1200,800); - await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.load('visualize'); - await PageObjects.common.navigateToApp('discover'); + loadTestFile(require.resolve('./_discover_navigation')); + loadTestFile(require.resolve('./_size')); }); - bdd.after(function unloadMakelogs() { - return esArchiver.unload('logstash_functional'); - }); - - require('./_discover_navigation'); - require('./_size'); -}); +} diff --git a/test/functional/apps/dashboard/_dashboard.js b/test/functional/apps/dashboard/_dashboard.js index 43291175e7214..a6e9e66247221 100644 --- a/test/functional/apps/dashboard/_dashboard.js +++ b/test/functional/apps/dashboard/_dashboard.js @@ -1,141 +1,148 @@ - import expect from 'expect.js'; + import { - DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT + DEFAULT_PANEL_WIDTH, + DEFAULT_PANEL_HEIGHT, } from '../../../../src/core_plugins/kibana/public/dashboard/panel/panel_state'; -import { bdd } from '../../../support'; -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize']); -bdd.describe('dashboard tab', function describeIndexTests() { - bdd.before(async function () { - return PageObjects.dashboard.initTests(); - }); + describe('dashboard tab', function describeIndexTests() { + before(async function () { + return PageObjects.dashboard.initTests(); + }); - bdd.it('should be able to add visualizations to dashboard', async function addVisualizations() { - PageObjects.common.saveScreenshot('Dashboard-no-visualizations'); + it('should be able to add visualizations to dashboard', async function addVisualizations() { + await PageObjects.common.saveScreenshot('Dashboard-no-visualizations'); - // This flip between apps fixes the url so state is preserved when switching apps in test mode. - // Without this flip the url in test mode looks something like - // "http://localhost:5620/app/kibana?_t=1486069030837#/dashboard?_g=...." - // after the initial flip, the url will look like this: "http://localhost:5620/app/kibana#/dashboard?_g=...." - await PageObjects.header.clickVisualize(); - await PageObjects.header.clickDashboard(); + // This flip between apps fixes the url so state is preserved when switching apps in test mode. + // Without this flip the url in test mode looks something like + // "http://localhost:5620/app/kibana?_t=1486069030837#/dashboard?_g=...." + // after the initial flip, the url will look like this: "http://localhost:5620/app/kibana#/dashboard?_g=...." + await PageObjects.header.clickVisualize(); + await PageObjects.header.clickDashboard(); - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames()); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames()); - PageObjects.common.debug('done adding visualizations'); - PageObjects.common.saveScreenshot('Dashboard-add-visualizations'); - }); + log.debug('done adding visualizations'); + await PageObjects.common.saveScreenshot('Dashboard-add-visualizations'); + }); - bdd.it('set the timepicker time to that which contains our test data', async function setTimepicker() { - await PageObjects.dashboard.setTimepickerInDataRange(); - }); + it('set the timepicker time to that which contains our test data', async function setTimepicker() { + await PageObjects.dashboard.setTimepickerInDataRange(); + }); - bdd.it('should save and load dashboard', async function saveAndLoadDashboard() { - const dashboardName = 'Dashboard Test 1'; - await PageObjects.dashboard.saveDashboard(dashboardName); - await PageObjects.dashboard.gotoDashboardLandingPage(); + it('should save and load dashboard', async function saveAndLoadDashboard() { + const dashboardName = 'Dashboard Test 1'; + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.common.try(function () { - PageObjects.common.debug('now re-load previously saved dashboard'); - return PageObjects.dashboard.loadSavedDashboard(dashboardName); + await retry.try(function () { + log.debug('now re-load previously saved dashboard'); + return PageObjects.dashboard.loadSavedDashboard(dashboardName); + }); + await PageObjects.common.saveScreenshot('Dashboard-load-saved'); }); - await PageObjects.common.saveScreenshot('Dashboard-load-saved'); - }); - bdd.it('should have all the expected visualizations', function checkVisualizations() { - return PageObjects.common.tryForTime(10000, function () { - return PageObjects.dashboard.getPanelTitles() + it('should have all the expected visualizations', function checkVisualizations() { + return retry.tryForTime(10000, function () { + return PageObjects.dashboard.getPanelTitles() .then(function (panelTitles) { - PageObjects.common.log('visualization titles = ' + panelTitles); + log.info('visualization titles = ' + panelTitles); expect(panelTitles).to.eql(PageObjects.dashboard.getTestVisualizationNames()); }); - }) + }) .then(function () { PageObjects.common.saveScreenshot('Dashboard-has-visualizations'); }); - }); + }); - bdd.it('should have all the expected initial sizes', function checkVisualizationSizes() { - const width = DEFAULT_PANEL_WIDTH; - const height = DEFAULT_PANEL_HEIGHT; - const titles = PageObjects.dashboard.getTestVisualizationNames(); - const visObjects = [ - { dataCol: '1', dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[0] }, - { dataCol: width + 1, dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[1] }, - { dataCol: '1', dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[2] }, - { dataCol: width + 1, dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[3] }, - { dataCol: '1', dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[4] }, - { dataCol: width + 1, dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[5] }, - { dataCol: '1', dataRow: (height * 3) + 1, dataSizeX: width, dataSizeY: height, title: titles[6] } - ]; - return PageObjects.common.tryForTime(10000, function () { - return PageObjects.dashboard.getPanelSizeData() - .then(function (panelTitles) { - PageObjects.common.log('visualization titles = ' + panelTitles); - PageObjects.common.saveScreenshot('Dashboard-visualization-sizes'); - expect(panelTitles).to.eql(visObjects); - }); + it('should have all the expected initial sizes', function checkVisualizationSizes() { + const width = DEFAULT_PANEL_WIDTH; + const height = DEFAULT_PANEL_HEIGHT; + const titles = PageObjects.dashboard.getTestVisualizationNames(); + const visObjects = [ + { dataCol: '1', dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[0] }, + { dataCol: width + 1, dataRow: '1', dataSizeX: width, dataSizeY: height, title: titles[1] }, + { dataCol: '1', dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[2] }, + { dataCol: width + 1, dataRow: height + 1, dataSizeX: width, dataSizeY: height, title: titles[3] }, + { dataCol: '1', dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[4] }, + { dataCol: width + 1, dataRow: (height * 2) + 1, dataSizeX: width, dataSizeY: height, title: titles[5] }, + { dataCol: '1', dataRow: (height * 3) + 1, dataSizeX: width, dataSizeY: height, title: titles[6] } + ]; + return retry.tryForTime(10000, function () { + return PageObjects.dashboard.getPanelSizeData() + .then(function (panelTitles) { + log.info('visualization titles = ' + panelTitles); + PageObjects.common.saveScreenshot('Dashboard-visualization-sizes'); + expect(panelTitles).to.eql(visObjects); + }); + }); }); - }); -}); -bdd.describe('filters', async function () { - bdd.it('are not selected by default', async function () { - const descriptions = await PageObjects.dashboard.getFilterDescriptions(1000); - expect(descriptions.length).to.equal(0); - }); + describe('filters', async function () { + it('are not selected by default', async function () { + const filters = await PageObjects.dashboard.getFilters(1000); + expect(filters.length).to.equal(0); + }); - bdd.it('are added when a pie chart slice is clicked', async function () { - await PageObjects.dashboard.filterOnPieSlice(); - const descriptions = await PageObjects.dashboard.getFilterDescriptions(); - expect(descriptions.length).to.equal(1); - }); -}); - -bdd.it('retains dark theme in state', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.useDarkTheme(true); - await PageObjects.header.clickVisualize(); - await PageObjects.header.clickDashboard(); - const isDarkThemeOn = await PageObjects.dashboard.isDarkThemeOn(); - expect(isDarkThemeOn).to.equal(true); -}); - -bdd.it('should have data-shared-items-count set to the number of visualizations', function checkSavedItemsCount() { - const visualizations = PageObjects.dashboard.getTestVisualizations(); - return PageObjects.common.tryForTime(10000, () => PageObjects.dashboard.getSharedItemsCount()) - .then(function (count) { - PageObjects.common.log('data-shared-items-count = ' + count); - expect(count).to.eql(visualizations.length); + it('are added when a pie chart slice is clicked', async function () { + await PageObjects.dashboard.filterOnPieSlice(); + const filters = await PageObjects.dashboard.getFilters(); + expect(filters.length).to.equal(1); + }); + }); + + it('retains dark theme in state', async function () { + await PageObjects.dashboard.clickEdit(); + await PageObjects.dashboard.useDarkTheme(true); + await PageObjects.header.clickVisualize(); + await PageObjects.header.clickDashboard(); + const isDarkThemeOn = await PageObjects.dashboard.isDarkThemeOn(); + expect(isDarkThemeOn).to.equal(true); }); -}); - -bdd.it('should have panels with expected data-shared-item title and description', function checkTitles() { - const visualizations = PageObjects.dashboard.getTestVisualizations(); - return PageObjects.common.tryForTime(10000, function () { - return PageObjects.dashboard.getPanelSharedItemData() - .then(function (data) { - expect(data.map(item => item.title)).to.eql(visualizations.map(v => v.name)); - expect(data.map(item => item.description)).to.eql(visualizations.map(v => v.description)); + + it('should have data-shared-items-count set to the number of visualizations', function checkSavedItemsCount() { + const visualizations = PageObjects.dashboard.getTestVisualizations(); + return retry.tryForTime(10000, () => PageObjects.dashboard.getSharedItemsCount()) + .then(function (count) { + log.info('data-shared-items-count = ' + count); + expect(count).to.eql(visualizations.length); }); - }); + }); - bdd.it('add new visualization link', async function checkTitles() { - await PageObjects.dashboard.clickAddVisualization(); - await PageObjects.dashboard.clickAddNewVisualizationLink(); - await PageObjects.visualize.clickAreaChart(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.visualize.saveVisualization('visualization from add new link'); - - const visualizations = PageObjects.dashboard.getTestVisualizations(); - return PageObjects.common.tryForTime(10000, async function () { - const panelTitles = await PageObjects.dashboard.getPanelSizeData(); - PageObjects.common.log('visualization titles = ' + panelTitles.map(item => item.title)); - PageObjects.common.saveScreenshot('Dashboard-visualization-sizes'); - expect(panelTitles.length).to.eql(visualizations.length + 1); + it('should have panels with expected data-shared-item title and description', function checkTitles() { + const visualizations = PageObjects.dashboard.getTestVisualizations(); + return retry.tryForTime(10000, function () { + return PageObjects.dashboard.getPanelSharedItemData() + .then(function (data) { + expect(data.map(item => item.title)).to.eql(visualizations.map(v => v.name)); + expect(data.map(item => item.description)).to.eql(visualizations.map(v => v.description)); + }); + }); + }); + + it('add new visualization link', async function checkTitles() { + await PageObjects.dashboard.clickAddVisualization(); + await PageObjects.dashboard.clickAddNewVisualizationLink(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualization('visualization from add new link'); + await PageObjects.header.clickToastOK(); + await PageObjects.header.clickToastOK(); + + const visualizations = PageObjects.dashboard.getTestVisualizations(); + return retry.tryForTime(10000, async function () { + const panelTitles = await PageObjects.dashboard.getPanelSizeData(); + log.info('visualization titles = ' + panelTitles.map(item => item.title)); + PageObjects.common.saveScreenshot('Dashboard-visualization-sizes'); + expect(panelTitles.length).to.eql(visualizations.length + 1); + }); }); }); -}); +} diff --git a/test/functional/apps/dashboard/_dashboard_save.js b/test/functional/apps/dashboard/_dashboard_save.js index 764f502d6ff79..eca9941d25a3a 100644 --- a/test/functional/apps/dashboard/_dashboard_save.js +++ b/test/functional/apps/dashboard/_dashboard_save.js @@ -1,89 +1,97 @@ import expect from 'expect.js'; -import { bdd } from '../../../support'; -import PageObjects from '../../../support/page_objects'; +export default function ({ getPageObjects }) { + const PageObjects = getPageObjects(['dashboard', 'header', 'common']); -bdd.describe('dashboard save', function describeIndexTests() { - const dashboardName = 'Dashboard Save Test'; + describe('dashboard save', function describeIndexTests() { + const dashboardName = 'Dashboard Save Test'; - bdd.before(async function () { - return PageObjects.dashboard.initTests(); - }); + before(async function () { + await PageObjects.dashboard.initTests(); + }); - bdd.it('warns on duplicate name for new dashboard', async function() { - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.saveDashboard(dashboardName); + after(async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + }); - let isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(false); + it('warns on duplicate name for new dashboard', async function() { + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); - await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); + let isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(false); - isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(true); - }); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); - bdd.it('does not save on reject confirmation', async function() { - await PageObjects.common.clickCancelOnModal(); + isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(true); + }); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName); - expect(countOfDashboards).to.equal(1); - }); + it('does not save on reject confirmation', async function() { + await PageObjects.common.clickCancelOnModal(); - bdd.it('Saves on confirm duplicate title warning', async function() { - await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); + const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName); + expect(countOfDashboards).to.equal(1); + }); - await PageObjects.common.clickConfirmOnModal(); + it('Saves on confirm duplicate title warning', async function() { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); - // This is important since saving a new dashboard will cause a refresh of the page. We have to - // wait till it finishes reloading or it might reload the url after simulating the - // dashboard landing page click. - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.header.clickToastOK(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName); - expect(countOfDashboards).to.equal(2); - }); + // This is important since saving a new dashboard will cause a refresh of the page. We have to + // wait till it finishes reloading or it might reload the url after simulating the + // dashboard landing page click. + await PageObjects.header.waitUntilLoadingHasFinished(); - bdd.it('Does not warn when you save an existing dashboard with the title it already has, and that title is a duplicate', - async function() { - await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); - await PageObjects.header.isGlobalLoadingIndicatorHidden(); - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.saveDashboard(dashboardName); + const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName); + expect(countOfDashboards).to.equal(2); + }); - const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(false); - } - ); + it('Does not warn when you save an existing dashboard with the title it already has, and that title is a duplicate', + async function() { + await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); + await PageObjects.header.isGlobalLoadingIndicatorHidden(); + await PageObjects.dashboard.clickEdit(); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); - bdd.it('Warns you when you Save as New Dashboard, and the title is a duplicate', async function() { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName, { saveAsNew: true }); + const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(false); + } + ); - const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(true); + it('Warns you when you Save as New Dashboard, and the title is a duplicate', async function() { + await PageObjects.dashboard.clickEdit(); + await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName, { saveAsNew: true }); - await PageObjects.common.clickCancelOnModal(); - }); + const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(true); - bdd.it('Does not warn when only the prefix matches', async function() { - await PageObjects.dashboard.saveDashboard(dashboardName.split(' ')[0]); + await PageObjects.common.clickCancelOnModal(); + }); - const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(false); - }); + it('Does not warn when only the prefix matches', async function() { + await PageObjects.dashboard.saveDashboard(dashboardName.split(' ')[0]); - bdd.it('Warns when case is different', async function() { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName.toUpperCase()); + const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(false); + }); + + it('Warns when case is different', async function() { + await PageObjects.dashboard.clickEdit(); + await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName.toUpperCase()); - const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isConfirmOpen).to.equal(true); + const isConfirmOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isConfirmOpen).to.equal(true); - await PageObjects.common.clickCancelOnModal(); + await PageObjects.common.clickCancelOnModal(); + }); }); -}); +} diff --git a/test/functional/apps/dashboard/_dashboard_time.js b/test/functional/apps/dashboard/_dashboard_time.js index b4d8966072712..955e8baf7cc16 100644 --- a/test/functional/apps/dashboard/_dashboard_time.js +++ b/test/functional/apps/dashboard/_dashboard_time.js @@ -1,86 +1,95 @@ import expect from 'expect.js'; -import { bdd } from '../../../support'; -import PageObjects from '../../../support/page_objects'; - const dashboardName = 'Dashboard Test Time'; const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; -bdd.describe('dashboard time', function dashboardSaveWithTime() { - bdd.before(async function () { - await PageObjects.dashboard.initTests(); +export default function ({ getPageObjects }) { + const PageObjects = getPageObjects(['dashboard', 'header']); - // This flip between apps fixes the url so state is preserved when switching apps in test mode. - await PageObjects.header.clickVisualize(); - await PageObjects.header.clickDashboard(); - }); + describe('dashboard time', function dashboardSaveWithTime() { + before(async function () { + await PageObjects.dashboard.initTests(); + + // This flip between apps fixes the url so state is preserved when switching apps in test mode. + await PageObjects.header.clickVisualize(); + await PageObjects.header.clickDashboard(); + }); - bdd.describe('dashboard without stored timed', async function () { - bdd.it('is saved', async function () { - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.addVisualizations([PageObjects.dashboard.getTestVisualizationNames()[0]]); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false }); + after(async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); }); - bdd.it('Does not set the time picker on open', async function () { - await PageObjects.header.setAbsoluteRange(fromTime, toTime); + describe('dashboard without stored timed', async function () { + it('is saved', async function () { + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.addVisualizations([PageObjects.dashboard.getTestVisualizationNames()[0]]); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false }); + await PageObjects.header.clickToastOK(); + }); - await PageObjects.dashboard.loadSavedDashboard(dashboardName); + it('Does not set the time picker on open', async function () { + await PageObjects.header.setAbsoluteRange(fromTime, toTime); - const fromTimeNext = await PageObjects.header.getFromTime(); - const toTimeNext = await PageObjects.header.getToTime(); - expect(fromTimeNext).to.equal(fromTime); - expect(toTimeNext).to.equal(toTime); - }); - }); + await PageObjects.dashboard.loadSavedDashboard(dashboardName); - bdd.describe('dashboard with stored timed', async function () { - bdd.it('is saved with quick time', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.header.setQuickTime('Today'); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + const fromTimeNext = await PageObjects.header.getFromTime(); + const toTimeNext = await PageObjects.header.getToTime(); + expect(fromTimeNext).to.equal(fromTime); + expect(toTimeNext).to.equal(toTime); + }); }); - bdd.it('sets quick time on open', async function () { - await PageObjects.header.setAbsoluteRange(fromTime, toTime); + describe('dashboard with stored timed', async function () { + it('is saved with quick time', async function () { + await PageObjects.dashboard.clickEdit(); + await PageObjects.header.setQuickTime('Today'); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + await PageObjects.header.clickToastOK(); + }); - await PageObjects.dashboard.loadSavedDashboard(dashboardName); + it('sets quick time on open', async function () { + await PageObjects.header.setAbsoluteRange(fromTime, toTime); - const prettyPrint = await PageObjects.header.getPrettyDuration(); - expect(prettyPrint).to.equal('Today'); - }); + await PageObjects.dashboard.loadSavedDashboard(dashboardName); - bdd.it('is saved with absolute time', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); - }); + const prettyPrint = await PageObjects.header.getPrettyDuration(); + expect(prettyPrint).to.equal('Today'); + }); - bdd.it('sets absolute time on open', async function () { - await PageObjects.header.setQuickTime('Today'); + it('is saved with absolute time', async function () { + await PageObjects.dashboard.clickEdit(); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + await PageObjects.header.clickToastOK(); + }); - await PageObjects.dashboard.loadSavedDashboard(dashboardName); + it('sets absolute time on open', async function () { + await PageObjects.header.setQuickTime('Today'); - const fromTimeNext = await PageObjects.header.getFromTime(); - const toTimeNext = await PageObjects.header.getToTime(); - expect(fromTimeNext).to.equal(fromTime); - expect(toTimeNext).to.equal(toTime); + await PageObjects.dashboard.loadSavedDashboard(dashboardName); + + const fromTimeNext = await PageObjects.header.getFromTime(); + const toTimeNext = await PageObjects.header.getToTime(); + expect(fromTimeNext).to.equal(fromTime); + expect(toTimeNext).to.equal(toTime); + }); }); // If the user has time stored with a dashboard, it's supposed to override the current time settings // when it's opened. However, if the user then changes the time, navigates to visualize, then navigates // back to dashboard, the overridden time should be preserved. The time is *only* reset on open, not // during navigation or page refreshes. - bdd.it('preserves time changes during navigation', async function () { - await PageObjects.header.setQuickTime('Today'); - await PageObjects.header.clickVisualize(); - await PageObjects.header.clickDashboard(); - - const prettyPrint = await PageObjects.header.getPrettyDuration(); - expect(prettyPrint).to.equal('Today'); + describe('time changes', function () { + it('preserved during navigation', async function () { + await PageObjects.header.setQuickTime('Today'); + await PageObjects.header.clickVisualize(); + await PageObjects.header.clickDashboard(); + + const prettyPrint = await PageObjects.header.getPrettyDuration(); + expect(prettyPrint).to.equal('Today'); + }); }); }); - -}); +} diff --git a/test/functional/apps/dashboard/_view_edit.js b/test/functional/apps/dashboard/_view_edit.js index 7123fd146ee6d..d7b114b9f4068 100644 --- a/test/functional/apps/dashboard/_view_edit.js +++ b/test/functional/apps/dashboard/_view_edit.js @@ -1,283 +1,298 @@ import expect from 'expect.js'; -import { bdd } from '../../../support'; -import PageObjects from '../../../support/page_objects'; - -const dashboardName = 'Dashboard View Edit Test'; - -bdd.describe('dashboard view edit mode', function viewEditModeTests() { - bdd.before(async function () { - return PageObjects.dashboard.initTests(); - }); - - bdd.it('create new dashboard opens in edit mode', async function () { - // This flip between apps fixes the url so state is preserved when switching apps in test mode. - // Without this flip the url in test mode looks something like - // "http://localhost:5620/app/kibana?_t=1486069030837#/dashboard?_g=...." - // after the initial flip, the url will look like this: "http://localhost:5620/app/kibana#/dashboard?_g=...." - await PageObjects.header.clickVisualize(); - await PageObjects.header.clickDashboard(); - - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); - }); - - bdd.it('create test dashboard', async function () { - await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames()); - await PageObjects.dashboard.saveDashboard(dashboardName); - }); - - bdd.it('existing dashboard opens in view mode', async function () { - await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); - const inViewMode = await PageObjects.dashboard.getIsInViewMode(); - - expect(inViewMode).to.equal(true); - }); +export default function ({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['dashboard', 'header', 'common', 'visualize']); + const dashboardName = 'Dashboard View Edit Test'; + + describe('dashboard view edit mode', function viewEditModeTests() { + before(async function () { + await PageObjects.dashboard.initTests(); + }); - bdd.describe('panel edit controls', function () { - bdd.it('are hidden in view mode', async function () { + after(async function () { await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); + }); - const editLinkExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelEditLink'); - const moveExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelMoveIcon'); - const removeExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelRemoveIcon'); + it('create new dashboard opens in edit mode', async function () { + // This flip between apps fixes the url so state is preserved when switching apps in test mode. + // Without this flip the url in test mode looks something like + // "http://localhost:5620/app/kibana?_t=1486069030837#/dashboard?_g=...." + // after the initial flip, the url will look like this: "http://localhost:5620/app/kibana#/dashboard?_g=...." + await PageObjects.header.clickVisualize(); + await PageObjects.header.clickDashboard(); - expect(editLinkExists).to.equal(false); - expect(moveExists).to.equal(false); - expect(removeExists).to.equal(false); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); }); - bdd.it('are shown in edit mode', async function () { - await PageObjects.dashboard.clickEdit(); + it('create test dashboard', async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames()); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); + }); - const editLinkExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelEditLink'); - const moveExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelMoveIcon'); - const removeExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelRemoveIcon'); + it('existing dashboard opens in view mode', async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); - expect(editLinkExists).to.equal(true); - expect(moveExists).to.equal(true); - expect(removeExists).to.equal(true); + expect(inViewMode).to.equal(true); }); - bdd.describe('on an expanded panel', function () { - bdd.it('are hidden in view mode', async function () { - await PageObjects.dashboard.saveDashboard(dashboardName); - await PageObjects.dashboard.toggleExpandPanel(); + describe('panel edit controls', function () { + it('are hidden in view mode', async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickDashboardByLinkText(dashboardName); - const editLinkExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelEditLink'); - const moveExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelMoveIcon'); - const removeExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelRemoveIcon'); + const editLinkExists = await testSubjects.exists('dashboardPanelEditLink'); + const moveExists = await testSubjects.exists('dashboardPanelMoveIcon'); + const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon'); expect(editLinkExists).to.equal(false); expect(moveExists).to.equal(false); expect(removeExists).to.equal(false); }); - bdd.it('in edit mode hides move and remove icons ', async function () { + it('are shown in edit mode', async function () { await PageObjects.dashboard.clickEdit(); - const editLinkExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelEditLink'); - const moveExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelMoveIcon'); - const removeExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelRemoveIcon'); + const editLinkExists = await testSubjects.exists('dashboardPanelEditLink'); + const moveExists = await testSubjects.exists('dashboardPanelMoveIcon'); + const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon'); expect(editLinkExists).to.equal(true); - expect(moveExists).to.equal(false); - expect(removeExists).to.equal(false); - - await PageObjects.dashboard.toggleExpandPanel(); + expect(moveExists).to.equal(true); + expect(removeExists).to.equal(true); }); - }); - }); - // Panel expand should also be shown in view mode, but only on mouse hover. - bdd.describe('panel expand control shown in edit mode', async function () { - const expandExists = await PageObjects.common.doesTestSubjectExist('dashboardPanelExpandIcon'); - expect(expandExists).to.equal(true); - }); + describe('on an expanded panel', function () { + it('are hidden in view mode', async function () { + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); + await PageObjects.dashboard.toggleExpandPanel(); - bdd.it('save auto exits out of edit mode', async function () { - await PageObjects.dashboard.saveDashboard(dashboardName); - const isViewMode = await PageObjects.dashboard.getIsInViewMode(); + const editLinkExists = await testSubjects.exists('dashboardPanelEditLink'); + const moveExists = await testSubjects.exists('dashboardPanelMoveIcon'); + const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon'); - expect(isViewMode).to.equal(true); - }); + expect(editLinkExists).to.equal(false); + expect(moveExists).to.equal(false); + expect(removeExists).to.equal(false); + }); - bdd.describe('shows lose changes warning', async function () { - bdd.describe('and loses changes on confirmation', function () { - bdd.it('when time changed is stored with dashboard', async function () { - await PageObjects.dashboard.clickEdit(); - const originalFromTime = '2015-09-19 06:31:44.000'; - const originalToTime = '2015-09-19 06:31:44.000'; - await PageObjects.header.setAbsoluteRange(originalFromTime, originalToTime); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + it('in edit mode hides move and remove icons ', async function () { + await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.clickEdit(); - await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000'); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + const editLinkExists = await testSubjects.exists('dashboardPanelEditLink'); + const moveExists = await testSubjects.exists('dashboardPanelMoveIcon'); + const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon'); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); + expect(editLinkExists).to.equal(true); + expect(moveExists).to.equal(false); + expect(removeExists).to.equal(false); - const newFromTime = await PageObjects.header.getFromTime(); - const newToTime = await PageObjects.header.getToTime(); + await PageObjects.dashboard.toggleExpandPanel(); + }); + }); + }); - expect(newFromTime).to.equal(originalFromTime); - expect(newToTime).to.equal(originalToTime); + // Panel expand should also be shown in view mode, but only on mouse hover. + describe('panel expand control', function () { + it('shown in edit mode', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + const expandExists = await testSubjects.exists('dashboardPanelExpandIcon'); + expect(expandExists).to.equal(true); }); + }); - bdd.it('when the query is edited and applied', async function () { - await PageObjects.dashboard.clickEdit(); + describe('save', function () { + it('auto exits out of edit mode', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); + const isViewMode = await PageObjects.dashboard.getIsInViewMode(); + expect(isViewMode).to.equal(true); + }); + }); - const originalQuery = await PageObjects.dashboard.getQuery(); - await PageObjects.dashboard.appendQuery('extra stuff'); - await PageObjects.dashboard.clickFilterButton(); + describe('shows lose changes warning', async function () { + describe('and loses changes on confirmation', function () { + beforeEach(async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + }); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + it('when time changed is stored with dashboard', async function () { + const originalFromTime = '2015-09-19 06:31:44.000'; + const originalToTime = '2015-09-19 06:31:44.000'; + await PageObjects.header.setAbsoluteRange(originalFromTime, originalToTime); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + await PageObjects.header.clickToastOK(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); + await PageObjects.dashboard.clickEdit(); + await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000'); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const query = await PageObjects.dashboard.getQuery(); - expect(query).to.equal(originalQuery); - }); + // confirm lose changes + await PageObjects.common.clickConfirmOnModal(); - bdd.it('when a filter is deleted', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.setTimepickerInDataRange(); - await PageObjects.dashboard.filterOnPieSlice(); - await PageObjects.dashboard.saveDashboard(dashboardName); + const newFromTime = await PageObjects.header.getFromTime(); + const newToTime = await PageObjects.header.getToTime(); - // This may seem like a pointless line but there was a bug that only arose when the dashboard - // was loaded initially - await PageObjects.dashboard.loadSavedDashboard(dashboardName); - await PageObjects.dashboard.clickEdit(); + expect(newFromTime).to.equal(originalFromTime); + expect(newToTime).to.equal(originalToTime); + }); - const originalFilters = await PageObjects.dashboard.getFilters(); + it('when the query is edited and applied', async function () { + const originalQuery = await PageObjects.dashboard.getQuery(); + await PageObjects.dashboard.appendQuery('extra stuff'); + await PageObjects.dashboard.clickFilterButton(); - // Click to cause hover menu to show up, but it will also actually click the filter, which will turn - // it off, so we need to click twice to turn it back on. - await originalFilters[0].click(); - await originalFilters[0].click(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const removeFilterButton = await PageObjects.common.findTestSubject('removeFilter-memory'); - await removeFilterButton.click(); + // confirm lose changes + await PageObjects.common.clickConfirmOnModal(); - const noFilters = await PageObjects.dashboard.getFilters(1000); - expect(noFilters.length).to.equal(0); + const query = await PageObjects.dashboard.getQuery(); + expect(query).to.equal(originalQuery); + }); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + it('when a filter is deleted', async function () { + await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.dashboard.filterOnPieSlice(); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); + // This may seem like a pointless line but there was a bug that only arose when the dashboard + // was loaded initially + await PageObjects.dashboard.loadSavedDashboard(dashboardName); + await PageObjects.dashboard.clickEdit(); - const reloadedFilters = await PageObjects.dashboard.getFilters(); - expect(reloadedFilters.length).to.equal(1); - }); + const originalFilters = await PageObjects.dashboard.getFilters(); - bdd.it('when a new vis is added', async function () { - await PageObjects.dashboard.loadSavedDashboard(dashboardName); - await PageObjects.dashboard.clickEdit(); + // Click to cause hover menu to show up, but it will also actually click the filter, which will turn + // it off, so we need to click twice to turn it back on. + await originalFilters[0].click(); + await originalFilters[0].click(); - await PageObjects.dashboard.clickAddVisualization(); - await PageObjects.dashboard.clickAddNewVisualizationLink(); - await PageObjects.visualize.clickAreaChart(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.visualize.saveVisualization('new viz panel'); + const removeFilterButton = await testSubjects.find('removeFilter-memory'); + await removeFilterButton.click(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + const noFilters = await PageObjects.dashboard.getFilters(1000); + expect(noFilters.length).to.equal(0); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const visualizations = PageObjects.dashboard.getTestVisualizations(); - const panelTitles = await PageObjects.dashboard.getPanelSizeData(); - expect(panelTitles.length).to.eql(visualizations.length); - }); + // confirm lose changes + await PageObjects.common.clickConfirmOnModal(); - bdd.it('when an existing vis is added', async function () { - await PageObjects.dashboard.loadSavedDashboard(dashboardName); - await PageObjects.dashboard.clickEdit(); + const reloadedFilters = await PageObjects.dashboard.getFilters(); + expect(reloadedFilters.length).to.equal(1); + }); - await PageObjects.dashboard.addVisualization('new viz panel'); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + it('when a new vis is added', async function () { + await PageObjects.dashboard.clickAddVisualization(); + await PageObjects.dashboard.clickAddNewVisualizationLink(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualization('new viz panel'); + await PageObjects.header.clickToastOK(); + await PageObjects.header.clickToastOK(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const visualizations = PageObjects.dashboard.getTestVisualizations(); - const panelTitles = await PageObjects.dashboard.getPanelSizeData(); - expect(panelTitles.length).to.eql(visualizations.length); - }); - }); + // confirm lose changes + await PageObjects.common.clickConfirmOnModal(); - bdd.describe('and preserves edits on cancel', function () { - bdd.it('when time changed is stored with dashboard', async function () { - await PageObjects.dashboard.clickEdit(); - const newFromTime = '2015-09-19 06:31:44.000'; - const newToTime = '2015-09-19 06:31:44.000'; - await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000'); - await PageObjects.dashboard.saveDashboard(dashboardName, true); - await PageObjects.dashboard.clickEdit(); - await PageObjects.header.setAbsoluteRange(newToTime, newToTime); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + const visualizations = PageObjects.dashboard.getTestVisualizations(); + const panelTitles = await PageObjects.dashboard.getPanelSizeData(); + expect(panelTitles.length).to.eql(visualizations.length); + }); - await PageObjects.common.clickCancelOnModal(); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + it('when an existing vis is added', async function () { + await PageObjects.dashboard.addVisualization('new viz panel'); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - await PageObjects.dashboard.loadSavedDashboard(dashboardName); + // confirm lose changes + await PageObjects.common.clickConfirmOnModal(); - const fromTime = await PageObjects.header.getFromTime(); - const toTime = await PageObjects.header.getToTime(); + const visualizations = PageObjects.dashboard.getTestVisualizations(); + const panelTitles = await PageObjects.dashboard.getPanelSizeData(); + expect(panelTitles.length).to.eql(visualizations.length); + }); + }); - expect(fromTime).to.equal(newFromTime); - expect(toTime).to.equal(newToTime); + describe('and preserves edits on cancel', function () { + it('when time changed is stored with dashboard', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + const newFromTime = '2015-09-19 06:31:44.000'; + const newToTime = '2015-09-19 06:31:44.000'; + await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000'); + await PageObjects.dashboard.saveDashboard(dashboardName, true); + await PageObjects.header.clickToastOK(); + await PageObjects.dashboard.clickEdit(); + await PageObjects.header.setAbsoluteRange(newToTime, newToTime); + await PageObjects.dashboard.clickCancelOutOfEditMode(); + + await PageObjects.common.clickCancelOnModal(); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); + await PageObjects.header.clickToastOK(); + + await PageObjects.dashboard.loadSavedDashboard(dashboardName); + + const fromTime = await PageObjects.header.getFromTime(); + const toTime = await PageObjects.header.getToTime(); + + expect(fromTime).to.equal(newFromTime); + expect(toTime).to.equal(newToTime); + }); }); }); - }); - bdd.describe('Does not show lose changes warning', async function () { - bdd.it('when time changed is not stored with dashboard', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false }); - await PageObjects.dashboard.clickEdit(); - await PageObjects.header.setAbsoluteRange('2013-10-19 06:31:44.000', '2013-12-19 06:31:44.000'); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + describe('Does not show lose changes warning', async function () { + it('when time changed is not stored with dashboard', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false }); + await PageObjects.header.clickToastOK(); + await PageObjects.dashboard.clickEdit(); + await PageObjects.header.setAbsoluteRange('2013-10-19 06:31:44.000', '2013-12-19 06:31:44.000'); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const isOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isOpen).to.be(false); - }); + const isOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isOpen).to.be(false); + }); - bdd.it('when a dashboard has a filter and remains unchanged', async function () { - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.setTimepickerInDataRange(); - await PageObjects.dashboard.filterOnPieSlice(); - await PageObjects.dashboard.saveDashboard(dashboardName); - await PageObjects.dashboard.clickEdit(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + it('when a dashboard has a filter and remains unchanged', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); + await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.dashboard.filterOnPieSlice(); + await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.header.clickToastOK(); + await PageObjects.dashboard.clickEdit(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const isOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isOpen).to.be(false); - }); + const isOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isOpen).to.be(false); + }); - // See https://github.com/elastic/kibana/issues/10110 - this is intentional. - bdd.it('when the query is edited but not applied', async function () { - await PageObjects.dashboard.clickEdit(); + // See https://github.com/elastic/kibana/issues/10110 - this is intentional. + it('when the query is edited but not applied', async function () { + await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); - const originalQuery = await PageObjects.dashboard.getQuery(); - await PageObjects.dashboard.appendQuery('extra stuff'); + const originalQuery = await PageObjects.dashboard.getQuery(); + await PageObjects.dashboard.appendQuery('extra stuff'); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); - const isOpen = await PageObjects.common.isConfirmModalOpen(); - expect(isOpen).to.be(false); + const isOpen = await PageObjects.common.isConfirmModalOpen(); + expect(isOpen).to.be(false); - await PageObjects.dashboard.loadSavedDashboard(dashboardName); - const query = await PageObjects.dashboard.getQuery(); - expect(query).to.equal(originalQuery); + await PageObjects.dashboard.loadSavedDashboard(dashboardName); + const query = await PageObjects.dashboard.getQuery(); + expect(query).to.equal(originalQuery); + }); }); }); -}); +} diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index d61480fe946a7..f044b3d34df8f 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -1,19 +1,15 @@ +export default function ({ getService, loadTestFile }) { + const config = getService('config'); + const remote = getService('remote'); -import { - bdd, - remote, - defaultTimeout, -} from '../../../support'; + describe('dashboard app', function () { + this.timeout(config.get('timeouts.test')); -bdd.describe('dashboard app', function () { - this.timeout = defaultTimeout; + before(() => remote.setWindowSize(1200,800)); - bdd.before(function () { - return remote.setWindowSize(1200,800); + loadTestFile(require.resolve('./_view_edit')); + loadTestFile(require.resolve('./_dashboard')); + loadTestFile(require.resolve('./_dashboard_save')); + loadTestFile(require.resolve('./_dashboard_time')); }); - - require('./_view_edit'); - require('./_dashboard'); - require('./_dashboard_save'); - require('./_dashboard_time'); -}); +} diff --git a/test/functional/apps/discover/_collapse_expand.js b/test/functional/apps/discover/_collapse_expand.js index a7c7ebe973d7e..6569467b206ba 100644 --- a/test/functional/apps/discover/_collapse_expand.js +++ b/test/functional/apps/discover/_collapse_expand.js @@ -1,72 +1,73 @@ - import expect from 'expect.js'; -import { - bdd, - esArchiver, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'header']); -bdd.describe('discover tab', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('discover tab', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - // delete .kibana index and update configDoc - return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) - .then(function loadkibanaIndexPattern() { - PageObjects.common.debug('load kibana index with default index pattern'); - return esArchiver.load('discover'); - }) - // and load a set of makelogs data - .then(function loadIfEmptyMakelogs() { - return esArchiver.loadIfNeeded('logstash_functional'); - }) - .then(function () { - PageObjects.common.debug('discover'); - return PageObjects.common.navigateToApp('discover'); - }) - .then(function () { - PageObjects.common.debug('setAbsoluteRange'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); + // delete .kibana index and update configDoc + return kibanaServer.uiSettings.replace({ + 'dateFormat:tz':'UTC', + 'defaultIndex':'logstash-*' + }) + .then(function loadkibanaIndexPattern() { + log.debug('load kibana index with default index pattern'); + return esArchiver.load('discover'); + }) + // and load a set of makelogs data + .then(function loadIfEmptyMakelogs() { + return esArchiver.loadIfNeeded('logstash_functional'); + }) + .then(function () { + log.debug('discover'); + return PageObjects.common.navigateToApp('discover'); + }) + .then(function () { + log.debug('setAbsoluteRange'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }); }); - }); - bdd.describe('field data', function () { - bdd.it('should initially be expanded', function () { - PageObjects.common.saveScreenshot('Discover-sidebar-expanded'); - return PageObjects.discover.getSidebarWidth() - .then(function (width) { - PageObjects.common.debug('expanded sidebar width = ' + width); - expect(width > 20).to.be(true); - }); - }); + describe('field data', function () { + it('should initially be expanded', function () { + PageObjects.common.saveScreenshot('Discover-sidebar-expanded'); + return PageObjects.discover.getSidebarWidth() + .then(function (width) { + log.debug('expanded sidebar width = ' + width); + expect(width > 20).to.be(true); + }); + }); - bdd.it('should collapse when clicked', function () { - return PageObjects.discover.toggleSidebarCollapse() - .then(function () { - PageObjects.common.saveScreenshot('Discover-sidebar-collapsed'); - PageObjects.common.debug('PageObjects.discover.getSidebarWidth()'); - return PageObjects.discover.getSidebarWidth(); - }) - .then(function (width) { - PageObjects.common.debug('collapsed sidebar width = ' + width); - expect(width < 20).to.be(true); - }); - }); + it('should collapse when clicked', function () { + return PageObjects.discover.toggleSidebarCollapse() + .then(function () { + PageObjects.common.saveScreenshot('Discover-sidebar-collapsed'); + log.debug('PageObjects.discover.getSidebarWidth()'); + return PageObjects.discover.getSidebarWidth(); + }) + .then(function (width) { + log.debug('collapsed sidebar width = ' + width); + expect(width < 20).to.be(true); + }); + }); - bdd.it('should expand when clicked', function () { - return PageObjects.discover.toggleSidebarCollapse() - .then(function () { - PageObjects.common.debug('PageObjects.discover.getSidebarWidth()'); - return PageObjects.discover.getSidebarWidth(); - }) - .then(function (width) { - PageObjects.common.debug('expanded sidebar width = ' + width); - expect(width > 20).to.be(true); - }); + it('should expand when clicked', function () { + return PageObjects.discover.toggleSidebarCollapse() + .then(function () { + log.debug('PageObjects.discover.getSidebarWidth()'); + return PageObjects.discover.getSidebarWidth(); + }) + .then(function (width) { + log.debug('expanded sidebar width = ' + width); + expect(width > 20).to.be(true); + }); + }); }); }); -}); +} diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 07dbbcdd30b09..431d44155c729 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -1,242 +1,245 @@ - import expect from 'expect.js'; -import { - bdd, - esArchiver, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('discover app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const fromTimeString = 'September 19th 2015, 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - const toTimeString = 'September 23rd 2015, 18:31:44.000'; - - bdd.before(async function () { - // delete .kibana index and update configDoc - await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }); - PageObjects.common.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); - - // and load a set of makelogs data - await esArchiver.loadIfNeeded('logstash_functional'); - PageObjects.common.debug('discover'); - await PageObjects.common.navigateToApp('discover'); - PageObjects.common.debug('setAbsoluteRange'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - }); - - bdd.describe('query', function () { - const queryName1 = 'Query # 1'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const remote = getService('remote'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'header']); + + describe('discover app', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const fromTimeString = 'September 19th 2015, 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; + const toTimeString = 'September 23rd 2015, 18:31:44.000'; + + before(async function () { + // delete .kibana index and update configDoc + await kibanaServer.uiSettings.replace({ + 'dateFormat:tz':'UTC', + 'defaultIndex':'logstash-*' + }); - bdd.it('should show correct time range string by timepicker', async function () { - const actualTimeString = await PageObjects.discover.getTimespanText(); + log.debug('load kibana index with default index pattern'); + await esArchiver.load('discover'); - const expectedTimeString = `${fromTimeString} to ${toTimeString}`; - expect(actualTimeString).to.be(expectedTimeString); + // and load a set of makelogs data + await esArchiver.loadIfNeeded('logstash_functional'); + log.debug('discover'); + await PageObjects.common.navigateToApp('discover'); + log.debug('setAbsoluteRange'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); }); - bdd.it('save query should show toast message and display query name', async function () { - await PageObjects.discover.saveSearch(queryName1); - const toastMessage = await PageObjects.header.getToastMessage(); + describe('query', function () { + const queryName1 = 'Query # 1'; - const expectedToastMessage = `Discover: Saved Data Source "${queryName1}"`; - expect(toastMessage).to.be(expectedToastMessage); - await PageObjects.common.saveScreenshot('Discover-save-query-toast'); + it('should show correct time range string by timepicker', async function () { + const actualTimeString = await PageObjects.discover.getTimespanText(); - await PageObjects.header.waitForToastMessageGone(); - const actualQueryNameString = await PageObjects.discover.getCurrentQueryName(); + const expectedTimeString = `${fromTimeString} to ${toTimeString}`; + expect(actualTimeString).to.be(expectedTimeString); + }); - expect(actualQueryNameString).to.be(queryName1); - }); + it('save query should show toast message and display query name', async function () { + await PageObjects.discover.saveSearch(queryName1); + const toastMessage = await PageObjects.header.getToastMessage(); - bdd.it('load query should show query name', async function () { - await PageObjects.discover.loadSavedSearch(queryName1); + const expectedToastMessage = `Discover: Saved Data Source "${queryName1}"`; + expect(toastMessage).to.be(expectedToastMessage); + await PageObjects.common.saveScreenshot('Discover-save-query-toast'); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getCurrentQueryName()).to.be(queryName1); + await PageObjects.header.waitForToastMessageGone(); + const actualQueryNameString = await PageObjects.discover.getCurrentQueryName(); + + expect(actualQueryNameString).to.be(queryName1); }); - await PageObjects.common.saveScreenshot('Discover-load-query'); - }); - bdd.it('should show the correct hit count', async function () { - const expectedHitCount = '14,004'; - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); + it('load query should show query name', async function () { + await PageObjects.discover.loadSavedSearch(queryName1); + + await retry.try(async function() { + expect(await PageObjects.discover.getCurrentQueryName()).to.be(queryName1); + }); + await PageObjects.common.saveScreenshot('Discover-load-query'); }); - }); - bdd.it('should show the correct bar chart', async function () { - const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, 640, - 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; - await verifyChartData(expectedBarChartData); - }); + it('should show the correct hit count', async function () { + const expectedHitCount = '14,004'; + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); + }); + }); - bdd.it('should show correct time range string in chart', async function () { - const actualTimeString = await PageObjects.discover.getChartTimespan(); + it('should show the correct bar chart', async function () { + const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, 640, + 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; + await verifyChartData(expectedBarChartData); + }); - const expectedTimeString = `${fromTimeString} - ${toTimeString}`; - expect(actualTimeString).to.be(expectedTimeString); - }); + it('should show correct time range string in chart', async function () { + const actualTimeString = await PageObjects.discover.getChartTimespan(); - bdd.it('should show correct initial chart interval of Auto', async function () { - const actualInterval = await PageObjects.discover.getChartInterval(); + const expectedTimeString = `${fromTimeString} - ${toTimeString}`; + expect(actualTimeString).to.be(expectedTimeString); + }); - const expectedInterval = 'Auto'; - expect(actualInterval).to.be(expectedInterval); - }); + it('should show correct initial chart interval of Auto', async function () { + const actualInterval = await PageObjects.discover.getChartInterval(); - bdd.it('should show correct data for chart interval Hourly', async function () { - await PageObjects.discover.setChartInterval('Hourly'); + const expectedInterval = 'Auto'; + expect(actualInterval).to.be(expectedInterval); + }); - const expectedBarChartData = [ 4, 7, 16, 23, 38, 87, 132, 159, 248, 320, 349, 376, 380, - 324, 293, 233, 188, 125, 69, 40, 28, 17, 2, 3, 8, 10, 12, 28, 36, 84, 111, 157, 229, 292, - 324, 373, 378, 345, 306, 223, 167, 124, 72, 35, 22, 11, 7, 1, 6, 5, 12, 25, 27, 76, 111, 175, - 228, 294, 358, 372, 366, 344, 276, 213, 201, 113, 72, 39, 36, 12, 7, 3 ]; - await verifyChartData(expectedBarChartData); - }); + it('should show correct data for chart interval Hourly', async function () { + await PageObjects.discover.setChartInterval('Hourly'); - bdd.it('should show correct data for chart interval Daily', async function () { - const chartInterval = 'Daily'; - const expectedBarChartData = [ 4757, 4614, 4633 ]; - await PageObjects.discover.setChartInterval(chartInterval); - await PageObjects.common.try(async () => { + const expectedBarChartData = [ 4, 7, 16, 23, 38, 87, 132, 159, 248, 320, 349, 376, 380, + 324, 293, 233, 188, 125, 69, 40, 28, 17, 2, 3, 8, 10, 12, 28, 36, 84, 111, 157, 229, 292, + 324, 373, 378, 345, 306, 223, 167, 124, 72, 35, 22, 11, 7, 1, 6, 5, 12, 25, 27, 76, 111, 175, + 228, 294, 358, 372, 366, 344, 276, 213, 201, 113, 72, 39, 36, 12, 7, 3 ]; await verifyChartData(expectedBarChartData); }); - }); - bdd.it('should show correct data for chart interval Weekly', async function () { - const chartInterval = 'Weekly'; - const expectedBarChartData = [ 4757, 9247 ]; + it('should show correct data for chart interval Daily', async function () { + const chartInterval = 'Daily'; + const expectedBarChartData = [ 4757, 4614, 4633 ]; + await PageObjects.discover.setChartInterval(chartInterval); + await retry.try(async () => { + await verifyChartData(expectedBarChartData); + }); + }); + + it('should show correct data for chart interval Weekly', async function () { + const chartInterval = 'Weekly'; + const expectedBarChartData = [ 4757, 9247 ]; - await PageObjects.discover.setChartInterval(chartInterval); - await PageObjects.common.try(async () => { - await verifyChartData(expectedBarChartData); + await PageObjects.discover.setChartInterval(chartInterval); + await retry.try(async () => { + await verifyChartData(expectedBarChartData); + }); }); - }); - bdd.it('browser back button should show previous interval Daily', async function () { - const expectedChartInterval = 'Daily'; - const expectedBarChartData = [ 4757, 4614, 4633 ]; + it('browser back button should show previous interval Daily', async function () { + const expectedChartInterval = 'Daily'; + const expectedBarChartData = [ 4757, 4614, 4633 ]; - await this.remote.goBack(); - await PageObjects.common.try(async function tryingForTime() { - const actualInterval = await PageObjects.discover.getChartInterval(); - expect(actualInterval).to.be(expectedChartInterval); + await remote.goBack(); + await retry.try(async function tryingForTime() { + const actualInterval = await PageObjects.discover.getChartInterval(); + expect(actualInterval).to.be(expectedChartInterval); + }); + await verifyChartData(expectedBarChartData); }); - await verifyChartData(expectedBarChartData); - }); - bdd.it('should show correct data for chart interval Monthly', async function () { - const chartInterval = 'Monthly'; - const expectedBarChartData = [ 13129 ]; + it('should show correct data for chart interval Monthly', async function () { + const chartInterval = 'Monthly'; + const expectedBarChartData = [ 13129 ]; - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); + await PageObjects.discover.setChartInterval(chartInterval); + await verifyChartData(expectedBarChartData); + }); - bdd.it('should show correct data for chart interval Yearly', async function () { - const chartInterval = 'Yearly'; - const expectedBarChartData = [ 13129 ]; + it('should show correct data for chart interval Yearly', async function () { + const chartInterval = 'Yearly'; + const expectedBarChartData = [ 13129 ]; - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); + await PageObjects.discover.setChartInterval(chartInterval); + await verifyChartData(expectedBarChartData); + }); - bdd.it('should show correct data for chart interval Auto', async function () { - const chartInterval = 'Auto'; - const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, - 640, 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; + it('should show correct data for chart interval Auto', async function () { + const chartInterval = 'Auto'; + const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, + 640, 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); + await PageObjects.discover.setChartInterval(chartInterval); + await verifyChartData(expectedBarChartData); + }); - bdd.it('should show Auto chart interval', async function () { - const expectedChartInterval = 'Auto'; + it('should show Auto chart interval', async function () { + const expectedChartInterval = 'Auto'; - const actualInterval = await PageObjects.discover.getChartInterval(); - expect(actualInterval).to.be(expectedChartInterval); - }); + const actualInterval = await PageObjects.discover.getChartInterval(); + expect(actualInterval).to.be(expectedChartInterval); + }); - bdd.it('should not show "no results"', async () => { - const isVisible = await PageObjects.discover.hasNoResults(); - expect(isVisible).to.be(false); - }); + it('should not show "no results"', async () => { + const isVisible = await PageObjects.discover.hasNoResults(); + expect(isVisible).to.be(false); + }); - async function verifyChartData(expectedBarChartData) { - await PageObjects.common.try(async function tryingForTime() { - const paths = await PageObjects.discover.getBarChartData(); - // the largest bars are over 100 pixels high so this is less than 1% tolerance - const barHeightTolerance = 1; - let stringResults = ''; - let hasFailure = false; - for (let y = 0; y < expectedBarChartData.length; y++) { - stringResults += y + ': expected = ' + expectedBarChartData[y] + ', actual = ' + paths[y] + - ', Pass = ' + (Math.abs(expectedBarChartData[y] - paths[y]) < barHeightTolerance) + '\n'; - if ((Math.abs(expectedBarChartData[y] - paths[y]) > barHeightTolerance)) { - hasFailure = true; + async function verifyChartData(expectedBarChartData) { + await retry.try(async function tryingForTime() { + const paths = await PageObjects.discover.getBarChartData(); + // the largest bars are over 100 pixels high so this is less than 1% tolerance + const barHeightTolerance = 1; + let stringResults = ''; + let hasFailure = false; + for (let y = 0; y < expectedBarChartData.length; y++) { + stringResults += y + ': expected = ' + expectedBarChartData[y] + ', actual = ' + paths[y] + + ', Pass = ' + (Math.abs(expectedBarChartData[y] - paths[y]) < barHeightTolerance) + '\n'; + if ((Math.abs(expectedBarChartData[y] - paths[y]) > barHeightTolerance)) { + hasFailure = true; + } } - } - if (hasFailure) { - PageObjects.common.log(stringResults); - PageObjects.common.log(paths); - } - for (let x = 0; x < expectedBarChartData.length; x++) { - expect(Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance).to.be.ok(); - } - }); - } - }); + if (hasFailure) { + log.debug(stringResults); + log.debug(paths); + } + for (let x = 0; x < expectedBarChartData.length; x++) { + expect(Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance).to.be.ok(); + } + }); + } + }); - bdd.describe('query #2, which has an empty time range', function () { - const fromTime = '1999-06-11 09:22:11.000'; - const toTime = '1999-06-12 11:21:04.000'; + describe('query #2, which has an empty time range', function () { + const fromTime = '1999-06-11 09:22:11.000'; + const toTime = '1999-06-12 11:21:04.000'; - bdd.before(() => { - PageObjects.common.debug('setAbsoluteRangeForAnotherQuery'); - return PageObjects.header - .setAbsoluteRange(fromTime, toTime); - }); + before(() => { + log.debug('setAbsoluteRangeForAnotherQuery'); + return PageObjects.header + .setAbsoluteRange(fromTime, toTime); + }); - bdd.it('should show "no results"', async () => { - const isVisible = await PageObjects.discover.hasNoResults(); - expect(isVisible).to.be(true); - await PageObjects.common.saveScreenshot('Discover-no-results'); - }); + it('should show "no results"', async () => { + const isVisible = await PageObjects.discover.hasNoResults(); + expect(isVisible).to.be(true); + await PageObjects.common.saveScreenshot('Discover-no-results'); + }); - bdd.it('should suggest a new time range is picked', async () => { - const isVisible = await PageObjects.discover.hasNoResultsTimepicker(); - expect(isVisible).to.be(true); - }); + it('should suggest a new time range is picked', async () => { + const isVisible = await PageObjects.discover.hasNoResultsTimepicker(); + expect(isVisible).to.be(true); + }); - bdd.it('should have a link that opens and closes the time picker', async function() { - const noResultsTimepickerLink = await PageObjects.discover.getNoResultsTimepicker(); - expect(await PageObjects.header.isTimepickerOpen()).to.be(false); + it('should have a link that opens and closes the time picker', async function() { + const noResultsTimepickerLink = await PageObjects.discover.getNoResultsTimepicker(); + expect(await PageObjects.header.isTimepickerOpen()).to.be(false); - await noResultsTimepickerLink.click(); - expect(await PageObjects.header.isTimepickerOpen()).to.be(true); + await noResultsTimepickerLink.click(); + expect(await PageObjects.header.isTimepickerOpen()).to.be(true); - await noResultsTimepickerLink.click(); - expect(await PageObjects.header.isTimepickerOpen()).to.be(false); + await noResultsTimepickerLink.click(); + expect(await PageObjects.header.isTimepickerOpen()).to.be(false); + }); }); - }); - - bdd.describe('data-shared-item', function () { - bdd.it('should have correct data-shared-item title and description', async () => { - const expected = { - title: 'A Saved Search', - description: 'A Saved Search Description' - }; + describe('data-shared-item', function () { + it('should have correct data-shared-item title and description', async () => { + const expected = { + title: 'A Saved Search', + description: 'A Saved Search Description' + }; - await PageObjects.discover.loadSavedSearch(expected.title); - const { title, description } = await PageObjects.common.getSharedItemTitleAndDescription(); - expect(title).to.eql(expected.title); - expect(description).to.eql(expected.description); + await PageObjects.discover.loadSavedSearch(expected.title); + const { title, description } = await PageObjects.common.getSharedItemTitleAndDescription(); + expect(title).to.eql(expected.title); + expect(description).to.eql(expected.description); + }); }); }); -}); +} diff --git a/test/functional/apps/discover/_field_data.js b/test/functional/apps/discover/_field_data.js index dd3a77f0ca0c7..6921018bce9c3 100644 --- a/test/functional/apps/discover/_field_data.js +++ b/test/functional/apps/discover/_field_data.js @@ -1,242 +1,244 @@ - import expect from 'expect.js'; -import { - bdd, - esArchiver, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'header', 'discover']); -bdd.describe('discover app', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('discover app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - // delete .kibana index and update configDoc - return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) - .then(function loadkibanaIndexPattern() { - PageObjects.common.debug('load kibana index with default index pattern'); - return esArchiver.load('discover'); - }) - // and load a set of makelogs data - .then(function loadIfEmptyMakelogs() { - return esArchiver.loadIfNeeded('logstash_functional'); - }) - .then(function () { - PageObjects.common.debug('discover'); - return PageObjects.common.navigateToApp('discover'); - }) - .then(function () { - PageObjects.common.debug('setAbsoluteRange'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); + // delete .kibana index and update configDoc + return kibanaServer.uiSettings.replace({ + 'dateFormat:tz': 'UTC', + 'defaultIndex': 'logstash-*' + }) + .then(function loadkibanaIndexPattern() { + log.debug('load kibana index with default index pattern'); + return esArchiver.load('discover'); + }) + // and load a set of makelogs data + .then(function loadIfEmptyMakelogs() { + return esArchiver.loadIfNeeded('logstash_functional'); + }) + .then(function () { + log.debug('discover'); + return PageObjects.common.navigateToApp('discover'); + }) + .then(function () { + log.debug('setAbsoluteRange'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }); }); - }); - bdd.describe('field data', function () { - bdd.it('search php should show the correct hit count', function () { - const expectedHitCount = '445'; - return PageObjects.discover.query('php') - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.discover.getHitCount() - .then(function compareData(hitCount) { - PageObjects.common.saveScreenshot('Discover-field-data'); - expect(hitCount).to.be(expectedHitCount); + describe('field data', function () { + it('search php should show the correct hit count', function () { + const expectedHitCount = '445'; + return PageObjects.discover.query('php') + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.discover.getHitCount() + .then(function compareData(hitCount) { + PageObjects.common.saveScreenshot('Discover-field-data'); + expect(hitCount).to.be(expectedHitCount); + }); }); }); }); - }); - bdd.it('the search term should be highlighted in the field data', function () { - // marks is the style that highlights the text in yellow - return PageObjects.discover.getMarks() - .then(function (marks) { - expect(marks.length).to.be(50); - expect(marks.indexOf('php')).to.be(0); + it('the search term should be highlighted in the field data', function () { + // marks is the style that highlights the text in yellow + return PageObjects.discover.getMarks() + .then(function (marks) { + expect(marks.length).to.be(50); + expect(marks.indexOf('php')).to.be(0); + }); }); - }); - bdd.it('search _type:apache should show the correct hit count', function () { - const expectedHitCount = '11,156'; - return PageObjects.discover.query('_type:apache') - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.discover.getHitCount() - .then(function compareData(hitCount) { - expect(hitCount).to.be(expectedHitCount); + it('search _type:apache should show the correct hit count', function () { + const expectedHitCount = '11,156'; + return PageObjects.discover.query('_type:apache') + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.discover.getHitCount() + .then(function compareData(hitCount) { + expect(hitCount).to.be(expectedHitCount); + }); }); }); }); - }); - bdd.it('doc view should show Time and _source columns', function () { - const expectedHeader = 'Time _source'; - return PageObjects.discover.getDocHeader() - .then(function (header) { - expect(header).to.be(expectedHeader); + it('doc view should show Time and _source columns', function () { + const expectedHeader = 'Time _source'; + return PageObjects.discover.getDocHeader() + .then(function (header) { + expect(header).to.be(expectedHeader); + }); }); - }); - bdd.it('doc view should show oldest time first', function () { - // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. - const ExpectedDoc = - 'September 22nd 2015, 23:50:13.253 index:logstash-2015.09.22 @timestamp:September 22nd 2015, 23:50:13.253' - + ' ip:238.171.34.42 extension:jpg response:200 geo.coordinates:{ "lat": 38.66494528, "lon": -88.45299556' - + ' } geo.src:FR geo.dest:KH geo.srcdest:FR:KH @tags:success, info utc_time:September 22nd 2015,' - + ' 23:50:13.253 referer:http://twitter.com/success/nancy-currie agent:Mozilla/4.0 (compatible; MSIE 6.0;' - + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:238.171.34.42 bytes:7,124' - + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/karl-henize.jpg' - + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/karl-henize.jpg' - + ' @message:238.171.34.42 - - [2015-09-22T23:50:13.253Z] "GET /uploads/karl-henize.jpg HTTP/1.1" 200 7124' - + ' "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' - + ' thing with lots of spaces wwwwoooooo xss:' - + ' headings:

alexander-viktorenko

, http://nytimes.com/warning/michael-massimino' - + ' links:@www.slate.com, http://www.slate.com/security/frederick-w-leslie, www.www.slate.com' - + ' relatedContent:{ "url": "http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191",' - + ' "og:type": "article", "og:title": "Bjork at the Nokia Theatre, 12/12", "og:description": "Bjork at the' - + ' Nokia Theater, December 12 By Randall Roberts Last night’s Bjork show at the Dystopia –' - + ' er, I mean Nokia -- Theatre downtown di...", "og:url": "' - + 'http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191", "article:published_time":' - + ' "2007-12-13T12:19:35-08:00", "article:modified_time": "2014-11-27T08:28:42-08:00", "article:section":' - + ' "Music", "og:image": "' - + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' - + ' "og:image:height": "334", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title":' - + ' "Bjork at the Nokia Theatre, 12/12", "twitter:description": "Bjork at the Nokia Theater, December 12' - + ' By Randall Roberts Last night’s Bjork show at the Dystopia – er, I mean Nokia -- Theatre' - + ' downtown di...", "twitter:card": "summary", "twitter:image": "' - + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' - + ' "twitter:site": "@laweekly" }, { "url": "' - + 'http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011", "og:type": "article", "og:title":' - + ' "The Rapture at the Mayan, 7/25", "og:description": "If you haven’t yet experienced the' - + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' - + ' Here’s...", "og:url": "http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011",' - + ' "article:published_time": "2007-07-26T12:42:30-07:00", "article:modified_time":' - + ' "2014-11-27T08:00:51-08:00", "article:section": "Music", "og:image": "' - + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' - + ' "og:image:height": "321", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": "The' - + ' Rapture at the Mayan, 7/25", "twitter:description": "If you haven’t yet experienced the' - + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' - + ' Here’s...", "twitter:card": "summary", "twitter:image": "' - + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' - + ' "twitter:site": "@laweekly" } machine.os:win 7 machine.ram:7,516,192,768 _id:AU_x3_g4GFA8no6QjkYX' - + ' _type:apache _index:logstash-2015.09.22 _score: - relatedContent.article:modified_time:November 27th' - + ' 2014, 16:00:51.000, November 27th 2014, 16:28:42.000 relatedContent.article:published_time:July 26th' - + ' 2007, 19:42:30.000, December 13th 2007, 20:19:35.000'; - return PageObjects.discover.getDocTableIndex(1) - .then(function (rowData) { - expect(rowData).to.be(ExpectedDoc); + it('doc view should show oldest time first', function () { + // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. + const ExpectedDoc = + 'September 22nd 2015, 23:50:13.253 index:logstash-2015.09.22 @timestamp:September 22nd 2015, 23:50:13.253' + + ' ip:238.171.34.42 extension:jpg response:200 geo.coordinates:{ "lat": 38.66494528, "lon": -88.45299556' + + ' } geo.src:FR geo.dest:KH geo.srcdest:FR:KH @tags:success, info utc_time:September 22nd 2015,' + + ' 23:50:13.253 referer:http://twitter.com/success/nancy-currie agent:Mozilla/4.0 (compatible; MSIE 6.0;' + + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:238.171.34.42 bytes:7,124' + + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/karl-henize.jpg' + + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/karl-henize.jpg' + + ' @message:238.171.34.42 - - [2015-09-22T23:50:13.253Z] "GET /uploads/karl-henize.jpg HTTP/1.1" 200 7124' + + ' "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' + + ' thing with lots of spaces wwwwoooooo xss:' + + ' headings:

alexander-viktorenko

, http://nytimes.com/warning/michael-massimino' + + ' links:@www.slate.com, http://www.slate.com/security/frederick-w-leslie, www.www.slate.com' + + ' relatedContent:{ "url": "http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191",' + + ' "og:type": "article", "og:title": "Bjork at the Nokia Theatre, 12/12", "og:description": "Bjork at the' + + ' Nokia Theater, December 12 By Randall Roberts Last night’s Bjork show at the Dystopia –' + + ' er, I mean Nokia -- Theatre downtown di...", "og:url": "' + + 'http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191", "article:published_time":' + + ' "2007-12-13T12:19:35-08:00", "article:modified_time": "2014-11-27T08:28:42-08:00", "article:section":' + + ' "Music", "og:image": "' + + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' + + ' "og:image:height": "334", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Bjork at the Nokia Theatre, 12/12", "twitter:description": "Bjork at the Nokia Theater, December 12' + + ' By Randall Roberts Last night’s Bjork show at the Dystopia – er, I mean Nokia -- Theatre' + + ' downtown di...", "twitter:card": "summary", "twitter:image": "' + + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' + + ' "twitter:site": "@laweekly" }, { "url": "' + + 'http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011", "og:type": "article", "og:title":' + + ' "The Rapture at the Mayan, 7/25", "og:description": "If you haven’t yet experienced the' + + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' + + ' Here’s...", "og:url": "http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011",' + + ' "article:published_time": "2007-07-26T12:42:30-07:00", "article:modified_time":' + + ' "2014-11-27T08:00:51-08:00", "article:section": "Music", "og:image": "' + + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' + + ' "og:image:height": "321", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": "The' + + ' Rapture at the Mayan, 7/25", "twitter:description": "If you haven’t yet experienced the' + + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' + + ' Here’s...", "twitter:card": "summary", "twitter:image": "' + + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' + + ' "twitter:site": "@laweekly" } machine.os:win 7 machine.ram:7,516,192,768 _id:AU_x3_g4GFA8no6QjkYX' + + ' _type:apache _index:logstash-2015.09.22 _score: - relatedContent.article:modified_time:November 27th' + + ' 2014, 16:00:51.000, November 27th 2014, 16:28:42.000 relatedContent.article:published_time:July 26th' + + ' 2007, 19:42:30.000, December 13th 2007, 20:19:35.000'; + return PageObjects.discover.getDocTableIndex(1) + .then(function (rowData) { + expect(rowData).to.be(ExpectedDoc); + }); }); - }); - bdd.it('doc view should sort ascending', function () { - // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. - const ExpectedDoc = - 'September 20th 2015, 00:00:00.000 index:logstash-2015.09.20 @timestamp:September 20th 2015, 00:00:00.000' - + ' ip:143.84.142.7 extension:jpg response:200 geo.coordinates:{ "lat": 38.68407028, "lon": -120.9871642 }' - + ' geo.src:ES geo.dest:US geo.srcdest:ES:US @tags:error, info utc_time:September 20th 2015, 00:00:00.000' - + ' referer:http://www.slate.com/success/vladimir-kovalyonok agent:Mozilla/4.0 (compatible; MSIE 6.0;' - + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:143.84.142.7 bytes:1,623' - + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/steven-hawley.jpg' - + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/steven-hawley.jpg' - + ' @message:143.84.142.7 - - [2015-09-20T00:00:00.000Z] "GET /uploads/steven-hawley.jpg HTTP/1.1" 200' - + ' 1623 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' - + ' thing with lots of spaces wwwwoooooo xss:' - + ' headings:

kimiya-yui

, http://www.slate.com/success/koichi-wakata' - + ' links:thomas-marshburn@twitter.com, http://www.slate.com/info/michael-p-anderson, www.twitter.com' - + ' relatedContent:{ "url":' - + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' - + ' "og:type": "article", "og:title": "Jay Electronica: Much Better Than His Name Would Suggest",' - + ' "og:description": "You may not know who Jay Electronica is yet, but I'm willing to bet that you' - + ' would had he chosen a better name. Jay Electronica does not sound like the ...", "og:url":' - + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' - + ' "article:published_time": "2008-04-04T16:00:00-07:00", "article:modified_time":' - + ' "2014-11-27T08:01:03-08:00", "article:section": "Music", "og:site_name": "LA Weekly", "twitter:title":' - + ' "Jay Electronica: Much Better Than His Name Would Suggest", "twitter:description": "You may not know' - + ' who Jay Electronica is yet, but I'm willing to bet that you would had he chosen a better name.' - + ' Jay Electronica does not sound like the ...", "twitter:card": "summary", "twitter:site": "@laweekly"' - + ' }, { "url": "http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "og:type":' - + ' "article", "og:title": "MANDOE On Gower Near Fountain", "og:description": "MANDOE has a stunner on a' - + ' wall north of an east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE' - + ' and FUKM are listed on t...", "og:url": "' - + 'http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "article:published_time":' - + ' "2008-04-25T07:26:41-07:00", "article:modified_time": "2014-10-28T15:00:08-07:00", "article:section":' - + ' "News", "og:image": "' - + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg",' - + ' "og:image:height": "640", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": ' - + '"MANDOE On Gower Near Fountain", "twitter:description": "MANDOE has a stunner on a wall north of an' - + ' east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE and FUKM are' - + ' listed on t...", "twitter:card": "summary", "twitter:image": "' - + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg", ' - + '"twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' - + ' "og:type": "article", "og:title": "Meghan Finds The Love", "og:description": "LA Weekly is the' - + ' definitive source of information for news, music, movies, restaurants, reviews, and events in Los' - + ' Angeles.", "og:url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' - + ' "article:published_time": "2005-10-20T18:10:25-07:00", "article:modified_time":' - + ' "2014-11-25T19:52:35-08:00", "article:section": "Arts", "og:site_name": "LA Weekly", "twitter:title":' - + ' "Meghan Finds The Love", "twitter:description": "LA Weekly is the definitive source of information for' - + ' news, music, movies, restaurants, reviews, and events in Los Angeles.", "twitter:card": "summary",' - + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/these-clowns-are-with-me-2371051' - + '", "og:type": "article", "og:title": "These Clowns Are With Me", "og:description": "    I' - + ' didn't mean to blow off all my responsibilities yesterday, but when a schmoozy Hollywood luncheon' - + ' turns into a full-on party by 3pm, and...", "og:url": "' - + 'http://www.laweekly.com/arts/these-clowns-are-with-me-2371051", "article:published_time": ' - + '"2006-03-04T17:03:42-08:00", "article:modified_time": "2014-11-25T17:05:47-08:00", "article:section":' - + ' "Arts", "og:image": "' - + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' - + ' "og:image:height": "375", "og:image:width": "500", "og:site_name": "LA Weekly", "twitter:title":' - + ' "These Clowns Are With Me", "twitter:description": "    I didn't mean to blow off all' - + ' my responsibilities yesterday, but when a schmoozy Hollywood luncheon turns into a full-on party by' - + ' 3pm, and...", "twitter:card": "summary", "twitter:image": "' - + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' - + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/shopping-daze-2373807",' - + ' "og:type": "article", "og:title": "Shopping Daze", "og:description": "LA Weekly is the definitive ' - + 'source of information for news, music, movies, restaurants, reviews, and events in Los Angeles.",' - + ' "og:url": "http://www.laweekly.com/arts/shopping-daze-2373807", "article:published_time":' - + ' "2006-12-13T12:12:04-08:00", "article:modified_time": "2014-11-25T20:15:21-08:00", "article:section":' - + ' "Arts", "og:site_name": "LA Weekly", "twitter:title": "Shopping Daze", "twitter:description": "LA' - + ' Weekly is the definitive source of information for news, music, movies, restaurants, reviews, and' - + ' events in Los Angeles.", "twitter:card": "summary", "twitter:site": "@laweekly" } machine.os:osx' - + ' machine.ram:15,032,385,536 _id:AU_x3_g3GFA8no6QjkFm _type:apache _index:logstash-2015.09.20 _score: -' - + ' relatedContent.article:modified_time:October 28th 2014, 22:00:08.000, November 26th 2014,' - + ' 01:05:47.000, November 26th 2014, 03:52:35.000, November 26th 2014, 04:15:21.000, November 27th 2014,' - + ' 16:01:03.000 relatedContent.article:published_time:October 21st 2005, 01:10:25.000, March 5th 2006,' - + ' 01:03:42.000, December 13th 2006, 20:12:04.000, April 4th 2008, 23:00:00.000, April 25th 2008,' - + ' 14:26:41.000'; - return PageObjects.discover.clickDocSortDown() - .then(function () { - // we don't technically need this sleep here because the tryForTime will retry and the - // results will match on the 2nd or 3rd attempt, but that debug output is huge in this - // case and it can be avoided with just a few seconds sleep. - return PageObjects.common.sleep(2000); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.discover.getDocTableIndex(1) - .then(function (rowData) { - PageObjects.common.saveScreenshot('Discover-sort-down'); - expect(rowData).to.be(ExpectedDoc); + it('doc view should sort ascending', function () { + // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. + const ExpectedDoc = + 'September 20th 2015, 00:00:00.000 index:logstash-2015.09.20 @timestamp:September 20th 2015, 00:00:00.000' + + ' ip:143.84.142.7 extension:jpg response:200 geo.coordinates:{ "lat": 38.68407028, "lon": -120.9871642 }' + + ' geo.src:ES geo.dest:US geo.srcdest:ES:US @tags:error, info utc_time:September 20th 2015, 00:00:00.000' + + ' referer:http://www.slate.com/success/vladimir-kovalyonok agent:Mozilla/4.0 (compatible; MSIE 6.0;' + + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:143.84.142.7 bytes:1,623' + + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/steven-hawley.jpg' + + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/steven-hawley.jpg' + + ' @message:143.84.142.7 - - [2015-09-20T00:00:00.000Z] "GET /uploads/steven-hawley.jpg HTTP/1.1" 200' + + ' 1623 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' + + ' thing with lots of spaces wwwwoooooo xss:' + + ' headings:

kimiya-yui

, http://www.slate.com/success/koichi-wakata' + + ' links:thomas-marshburn@twitter.com, http://www.slate.com/info/michael-p-anderson, www.twitter.com' + + ' relatedContent:{ "url":' + + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' + + ' "og:type": "article", "og:title": "Jay Electronica: Much Better Than His Name Would Suggest",' + + ' "og:description": "You may not know who Jay Electronica is yet, but I'm willing to bet that you' + + ' would had he chosen a better name. Jay Electronica does not sound like the ...", "og:url":' + + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' + + ' "article:published_time": "2008-04-04T16:00:00-07:00", "article:modified_time":' + + ' "2014-11-27T08:01:03-08:00", "article:section": "Music", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Jay Electronica: Much Better Than His Name Would Suggest", "twitter:description": "You may not know' + + ' who Jay Electronica is yet, but I'm willing to bet that you would had he chosen a better name.' + + ' Jay Electronica does not sound like the ...", "twitter:card": "summary", "twitter:site": "@laweekly"' + + ' }, { "url": "http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "og:type":' + + ' "article", "og:title": "MANDOE On Gower Near Fountain", "og:description": "MANDOE has a stunner on a' + + ' wall north of an east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE' + + ' and FUKM are listed on t...", "og:url": "' + + 'http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "article:published_time":' + + ' "2008-04-25T07:26:41-07:00", "article:modified_time": "2014-10-28T15:00:08-07:00", "article:section":' + + ' "News", "og:image": "' + + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg",' + + ' "og:image:height": "640", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": ' + + '"MANDOE On Gower Near Fountain", "twitter:description": "MANDOE has a stunner on a wall north of an' + + ' east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE and FUKM are' + + ' listed on t...", "twitter:card": "summary", "twitter:image": "' + + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg", ' + + '"twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' + + ' "og:type": "article", "og:title": "Meghan Finds The Love", "og:description": "LA Weekly is the' + + ' definitive source of information for news, music, movies, restaurants, reviews, and events in Los' + + ' Angeles.", "og:url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' + + ' "article:published_time": "2005-10-20T18:10:25-07:00", "article:modified_time":' + + ' "2014-11-25T19:52:35-08:00", "article:section": "Arts", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Meghan Finds The Love", "twitter:description": "LA Weekly is the definitive source of information for' + + ' news, music, movies, restaurants, reviews, and events in Los Angeles.", "twitter:card": "summary",' + + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/these-clowns-are-with-me-2371051' + + '", "og:type": "article", "og:title": "These Clowns Are With Me", "og:description": "    I' + + ' didn't mean to blow off all my responsibilities yesterday, but when a schmoozy Hollywood luncheon' + + ' turns into a full-on party by 3pm, and...", "og:url": "' + + 'http://www.laweekly.com/arts/these-clowns-are-with-me-2371051", "article:published_time": ' + + '"2006-03-04T17:03:42-08:00", "article:modified_time": "2014-11-25T17:05:47-08:00", "article:section":' + + ' "Arts", "og:image": "' + + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' + + ' "og:image:height": "375", "og:image:width": "500", "og:site_name": "LA Weekly", "twitter:title":' + + ' "These Clowns Are With Me", "twitter:description": "    I didn't mean to blow off all' + + ' my responsibilities yesterday, but when a schmoozy Hollywood luncheon turns into a full-on party by' + + ' 3pm, and...", "twitter:card": "summary", "twitter:image": "' + + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' + + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/shopping-daze-2373807",' + + ' "og:type": "article", "og:title": "Shopping Daze", "og:description": "LA Weekly is the definitive ' + + 'source of information for news, music, movies, restaurants, reviews, and events in Los Angeles.",' + + ' "og:url": "http://www.laweekly.com/arts/shopping-daze-2373807", "article:published_time":' + + ' "2006-12-13T12:12:04-08:00", "article:modified_time": "2014-11-25T20:15:21-08:00", "article:section":' + + ' "Arts", "og:site_name": "LA Weekly", "twitter:title": "Shopping Daze", "twitter:description": "LA' + + ' Weekly is the definitive source of information for news, music, movies, restaurants, reviews, and' + + ' events in Los Angeles.", "twitter:card": "summary", "twitter:site": "@laweekly" } machine.os:osx' + + ' machine.ram:15,032,385,536 _id:AU_x3_g3GFA8no6QjkFm _type:apache _index:logstash-2015.09.20 _score: -' + + ' relatedContent.article:modified_time:October 28th 2014, 22:00:08.000, November 26th 2014,' + + ' 01:05:47.000, November 26th 2014, 03:52:35.000, November 26th 2014, 04:15:21.000, November 27th 2014,' + + ' 16:01:03.000 relatedContent.article:published_time:October 21st 2005, 01:10:25.000, March 5th 2006,' + + ' 01:03:42.000, December 13th 2006, 20:12:04.000, April 4th 2008, 23:00:00.000, April 25th 2008,' + + ' 14:26:41.000'; + return PageObjects.discover.clickDocSortDown() + .then(function () { + // we don't technically need this sleep here because the tryForTime will retry and the + // results will match on the 2nd or 3rd attempt, but that debug output is huge in this + // case and it can be avoided with just a few seconds sleep. + return PageObjects.common.sleep(2000); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.discover.getDocTableIndex(1) + .then(function (rowData) { + PageObjects.common.saveScreenshot('Discover-sort-down'); + expect(rowData).to.be(ExpectedDoc); + }); }); }); }); - }); - bdd.it('a bad syntax query should show an error message', function () { - const expectedError = 'Discover: Failed to parse query [xxx(yyy]'; - return PageObjects.discover.query('xxx(yyy') - .then(function () { - return PageObjects.header.getToastMessage(); - }) - .then(function (toastMessage) { - PageObjects.common.saveScreenshot('Discover-syntax-error-toast'); - expect(toastMessage).to.be(expectedError); - }) - .then(function () { - return PageObjects.header.clickToastOK(); + it('a bad syntax query should show an error message', function () { + const expectedError = 'Discover: Failed to parse query [xxx(yyy]'; + return PageObjects.discover.query('xxx(yyy') + .then(function () { + return PageObjects.header.getToastMessage(); + }) + .then(function (toastMessage) { + PageObjects.common.saveScreenshot('Discover-syntax-error-toast'); + expect(toastMessage).to.be(expectedError); + }) + .then(function () { + return PageObjects.header.clickToastOK(); + }); }); }); }); -}); +} diff --git a/test/functional/apps/discover/_shared_links.js b/test/functional/apps/discover/_shared_links.js index c91565d8f8f0e..40841514f900e 100644 --- a/test/functional/apps/discover/_shared_links.js +++ b/test/functional/apps/discover/_shared_links.js @@ -1,129 +1,131 @@ - import expect from 'expect.js'; -import { - bdd, - esArchiver, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('shared links', function describeIndexTests() { - let baseUrl; - // The message changes for Firefox < 41 and Firefox >= 41 - // var expectedToastMessage = 'Share search: URL selected. Press Ctrl+C to copy.'; - // var expectedToastMessage = 'Share search: URL copied to clipboard.'; - // Pass either one. - const expectedToastMessage = /Share search: URL (selected\. Press Ctrl\+C to copy\.|copied to clipboard\.)/; - - bdd.before(function () { - baseUrl = PageObjects.common.getHostPort(); - PageObjects.common.debug('baseUrl = ' + baseUrl); - // browsers don't show the ':port' if it's 80 or 443 so we have to - // remove that part so we can get a match in the tests. - baseUrl = baseUrl.replace(':80','').replace(':443',''); - PageObjects.common.debug('New baseUrl = ' + baseUrl); +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'header']); - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('shared links', function describeIndexTests() { + let baseUrl; + // The message changes for Firefox < 41 and Firefox >= 41 + // var expectedToastMessage = 'Share search: URL selected. Press Ctrl+C to copy.'; + // var expectedToastMessage = 'Share search: URL copied to clipboard.'; + // Pass either one. + const expectedToastMessage = /Share search: URL (selected\. Press Ctrl\+C to copy\.|copied to clipboard\.)/; - // delete .kibana index and update configDoc - return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) - .then(function loadkibanaIndexPattern() { - PageObjects.common.debug('load kibana index with default index pattern'); - return esArchiver.load('discover'); - }) - // and load a set of makelogs data - .then(function loadIfEmptyMakelogs() { - return esArchiver.loadIfNeeded('logstash_functional'); - }) - .then(function () { - PageObjects.common.debug('discover'); - return PageObjects.common.navigateToApp('discover'); - }) - .then(function () { - PageObjects.common.debug('setAbsoluteRange'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function () { - //After hiding the time picker, we need to wait for - //the refresh button to hide before clicking the share button - return PageObjects.common.sleep(1000); - }); - }); + before(function () { + baseUrl = PageObjects.common.getHostPort(); + log.debug('baseUrl = ' + baseUrl); + // browsers don't show the ':port' if it's 80 or 443 so we have to + // remove that part so we can get a match in the tests. + baseUrl = baseUrl.replace(':80','').replace(':443',''); + log.debug('New baseUrl = ' + baseUrl); + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.describe('shared link', function () { - bdd.it('should show "Share a link" caption', function () { - const expectedCaption = 'Share saved'; - return PageObjects.discover.clickShare() + // delete .kibana index and update configDoc + return kibanaServer.uiSettings.replace({ + 'dateFormat:tz': 'UTC', + 'defaultIndex': 'logstash-*' + }) + .then(function loadkibanaIndexPattern() { + log.debug('load kibana index with default index pattern'); + return esArchiver.load('discover'); + }) + // and load a set of makelogs data + .then(function loadIfEmptyMakelogs() { + return esArchiver.loadIfNeeded('logstash_functional'); + }) + .then(function () { + log.debug('discover'); + return PageObjects.common.navigateToApp('discover'); + }) .then(function () { - PageObjects.common.saveScreenshot('Discover-share-link'); - return PageObjects.discover.getShareCaption(); + log.debug('setAbsoluteRange'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); }) - .then(function (actualCaption) { - expect(actualCaption).to.contain(expectedCaption); + .then(function () { + //After hiding the time picker, we need to wait for + //the refresh button to hide before clicking the share button + return PageObjects.common.sleep(1000); }); }); - bdd.it('should show the correct formatted URL', function () { - const expectedUrl = baseUrl - + '/app/kibana?_t=1453775307251#' - + '/discover?_g=(refreshInterval:(display:Off,pause:!f,value:0),time' - + ':(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 PageObjects.discover.getSharedUrl() - .then(function (actualUrl) { - // strip the timestamp out of each URL - expect(actualUrl.replace(/_t=\d{13}/,'_t=TIMESTAMP')) - .to.be(expectedUrl.replace(/_t=\d{13}/,'_t=TIMESTAMP')); + + describe('shared link', function () { + it('should show "Share a link" caption', function () { + const expectedCaption = 'Share saved'; + return PageObjects.discover.clickShare() + .then(function () { + PageObjects.common.saveScreenshot('Discover-share-link'); + return PageObjects.discover.getShareCaption(); + }) + .then(function (actualCaption) { + expect(actualCaption).to.contain(expectedCaption); + }); }); - }); - bdd.it('should show toast message for copy to clipboard', function () { - return PageObjects.discover.clickCopyToClipboard() - .then(function () { - return PageObjects.header.getToastMessage(); - }) - .then(function (toastMessage) { - PageObjects.common.saveScreenshot('Discover-copy-to-clipboard-toast'); - expect(toastMessage).to.match(expectedToastMessage); - }) - .then(function () { - return PageObjects.header.waitForToastMessageGone(); + it('should show the correct formatted URL', function () { + const expectedUrl = baseUrl + + '/app/kibana?_t=1453775307251#' + + '/discover?_g=(refreshInterval:(display:Off,pause:!f,value:0),time' + + ':(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 PageObjects.discover.getSharedUrl() + .then(function (actualUrl) { + // strip the timestamp out of each URL + expect(actualUrl.replace(/_t=\d{13}/,'_t=TIMESTAMP')) + .to.be(expectedUrl.replace(/_t=\d{13}/,'_t=TIMESTAMP')); + }); }); - }); - // TODO: verify clipboard contents - bdd.it('shorten URL button should produce a short URL', function () { - const re = new RegExp(baseUrl + '/goto/[0-9a-f]{32}$'); - return PageObjects.discover.clickShortenUrl() - .then(function () { - return PageObjects.common.try(function tryingForTime() { - PageObjects.common.saveScreenshot('Discover-shorten-url-button'); - return PageObjects.discover.getSharedUrl() - .then(function (actualUrl) { - expect(actualUrl).to.match(re); + it('should show toast message for copy to clipboard', function () { + return PageObjects.discover.clickCopyToClipboard() + .then(function () { + return PageObjects.header.getToastMessage(); + }) + .then(function (toastMessage) { + PageObjects.common.saveScreenshot('Discover-copy-to-clipboard-toast'); + expect(toastMessage).to.match(expectedToastMessage); + }) + .then(function () { + return PageObjects.header.waitForToastMessageGone(); + }); + }); + + // TODO: verify clipboard contents + it('shorten URL button should produce a short URL', function () { + const re = new RegExp(baseUrl + '/goto/[0-9a-f]{32}$'); + return PageObjects.discover.clickShortenUrl() + .then(function () { + return retry.try(function tryingForTime() { + PageObjects.common.saveScreenshot('Discover-shorten-url-button'); + return PageObjects.discover.getSharedUrl() + .then(function (actualUrl) { + expect(actualUrl).to.match(re); + }); }); }); }); - }); - // NOTE: This test has to run immediately after the test above - bdd.it('should show toast message for copy to clipboard', function () { - return PageObjects.discover.clickCopyToClipboard() - .then(function () { - return PageObjects.header.getToastMessage(); - }) - .then(function (toastMessage) { - expect(toastMessage).to.match(expectedToastMessage); - }) - .then(function () { - return PageObjects.header.waitForToastMessageGone(); + // NOTE: This test has to run immediately after the test above + it('should show toast message for copy to clipboard', function () { + return PageObjects.discover.clickCopyToClipboard() + .then(function () { + return PageObjects.header.getToastMessage(); + }) + .then(function (toastMessage) { + expect(toastMessage).to.match(expectedToastMessage); + }) + .then(function () { + return PageObjects.header.waitForToastMessageGone(); + }); }); }); }); -}); +} diff --git a/test/functional/apps/discover/_source_filters.js b/test/functional/apps/discover/_source_filters.js index 56950272acd52..99a0b413266e8 100644 --- a/test/functional/apps/discover/_source_filters.js +++ b/test/functional/apps/discover/_source_filters.js @@ -1,50 +1,51 @@ -import { - bdd, - esArchiver, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - import expect from 'expect.js'; -bdd.describe('source filters', function describeIndexTests() { - bdd.before(function () { +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'header', 'discover']); - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('source filters', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - // delete .kibana index and update configDoc - return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) - .then(function loadkibanaIndexPattern() { - PageObjects.common.debug('load kibana index with default index pattern'); - return esArchiver.load('visualize_source-filters'); - }) - // and load a set of makelogs data - .then(function loadIfEmptyMakelogs() { - return esArchiver.loadIfNeeded('logstash_functional'); - }) - .then(function () { - PageObjects.common.debug('discover'); - return PageObjects.common.navigateToApp('discover'); - }) - .then(function () { - PageObjects.common.debug('setAbsoluteRange'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function () { - //After hiding the time picker, we need to wait for - //the refresh button to hide before clicking the share button - return PageObjects.common.sleep(1000); + // delete .kibana index and update configDoc + return kibanaServer.uiSettings.replace({ + 'dateFormat:tz': 'UTC', + 'defaultIndex':'logstash-*' + }) + .then(function loadkibanaIndexPattern() { + log.debug('load kibana index with default index pattern'); + return esArchiver.load('visualize_source-filters'); + }) + // and load a set of makelogs data + .then(function loadIfEmptyMakelogs() { + return esArchiver.loadIfNeeded('logstash_functional'); + }) + .then(function () { + log.debug('discover'); + return PageObjects.common.navigateToApp('discover'); + }) + .then(function () { + log.debug('setAbsoluteRange'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function () { + //After hiding the time picker, we need to wait for + //the refresh button to hide before clicking the share button + return PageObjects.common.sleep(1000); + }); }); - }); - bdd.it('should not get the field referer', function () { - return PageObjects.discover.getAllFieldNames() - .then(function (fieldNames) { - expect(fieldNames).to.not.contain('referer'); - const relatedContentFields = fieldNames.filter((fieldName) => fieldName.indexOf('relatedContent') === 0); - expect(relatedContentFields).to.have.length(0); + it('should not get the field referer', function () { + return PageObjects.discover.getAllFieldNames() + .then(function (fieldNames) { + expect(fieldNames).to.not.contain('referer'); + const relatedContentFields = fieldNames.filter((fieldName) => fieldName.indexOf('relatedContent') === 0); + expect(relatedContentFields).to.have.length(0); + }); }); }); -}); +} diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index e1eb273b5ab2a..71291b4c3b3c8 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -1,26 +1,23 @@ +export default function ({ getService, loadTestFile }) { + const config = getService('config'); + const esArchiver = getService('esArchiver'); + const remote = getService('remote'); -import { - bdd, - esArchiver, - defaultTimeout, -} from '../../../support'; + describe('discover app', function () { + this.timeout(config.get('timeouts.test')); -import PageObjects from '../../../support/page_objects'; + before(function () { + return remote.setWindowSize(1200,800); + }); -bdd.describe('discover app', function () { - this.timeout = defaultTimeout; + after(function unloadMakelogs() { + return esArchiver.unload('logstash_functional'); + }); - bdd.before(function () { - return PageObjects.remote.setWindowSize(1200,800); + loadTestFile(require.resolve('./_discover')); + loadTestFile(require.resolve('./_field_data')); + loadTestFile(require.resolve('./_shared_links')); + loadTestFile(require.resolve('./_collapse_expand')); + loadTestFile(require.resolve('./_source_filters')); }); - - bdd.after(function unloadMakelogs() { - return esArchiver.unload('logstash_functional'); - }); - - require('./_discover'); - require('./_field_data'); - require('./_shared_links'); - require('./_collapse_expand'); - require('./_source_filters'); -}); +} diff --git a/test/functional/apps/management/_creation_form_changes.js b/test/functional/apps/management/_creation_form_changes.js index 7343401a29e0c..41bc88b5ccf01 100644 --- a/test/functional/apps/management/_creation_form_changes.js +++ b/test/functional/apps/management/_creation_form_changes.js @@ -1,59 +1,56 @@ - import expect from 'expect.js'; -import { - bdd, - esClient, -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['settings', 'common']); -bdd.describe('user input reactions', function () { - bdd.beforeEach(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); + describe('user input reactions', function () { + beforeEach(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }) + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }); }); - }); - bdd.it('should hide time-based index pattern when time-based option is unchecked', function () { - const self = this; - return PageObjects.settings.getTimeBasedEventsCheckbox() - .then(function (selected) { - // uncheck the 'time-based events' checkbox - return selected.click(); - }) - // try to find the checkbox (this shouldn fail) - .then(function () { - const waitTime = 10000; - return PageObjects.settings.getTimeBasedIndexPatternCheckbox(waitTime); - }) - .then(function () { - PageObjects.common.saveScreenshot('Settings-indices-hide-time-based-index-pattern'); - // we expect the promise above to fail - const handler = PageObjects.common.createErrorHandler(self); - const msg = 'Found time based index pattern checkbox'; - handler(msg); - }) - .catch(function () { - // we expect this failure since checkbox should be hidden - return; + it('should hide time-based index pattern when time-based option is unchecked', function () { + const self = this; + return PageObjects.settings.getTimeBasedEventsCheckbox() + .then(function (selected) { + // uncheck the 'time-based events' checkbox + return selected.click(); + }) + // try to find the checkbox (this shouldn fail) + .then(function () { + const waitTime = 10000; + return PageObjects.settings.getTimeBasedIndexPatternCheckbox(waitTime); + }) + .then(function () { + PageObjects.common.saveScreenshot('Settings-indices-hide-time-based-index-pattern'); + // we expect the promise above to fail + const handler = PageObjects.common.createErrorHandler(self); + const msg = 'Found time based index pattern checkbox'; + handler(msg); + }) + .catch(function () { + // we expect this failure since checkbox should be hidden + return; + }); }); - }); - bdd.it('should enable creation after selecting time field', function () { - // select a time field and check that Create button is enabled - return PageObjects.settings.selectTimeFieldOption('@timestamp') - .then(function () { - return PageObjects.settings.getCreateButton().isEnabled() - .then(function (enabled) { - PageObjects.common.saveScreenshot('Settings-indices-enable-creation'); - expect(enabled).to.be.ok(); + it('should enable creation after selecting time field', function () { + // select a time field and check that Create button is enabled + return PageObjects.settings.selectTimeFieldOption('@timestamp') + .then(function () { + return PageObjects.settings.getCreateButton().isEnabled() + .then(function (enabled) { + PageObjects.common.saveScreenshot('Settings-indices-enable-creation'); + expect(enabled).to.be.ok(); + }); }); }); }); -}); +} diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index 3d47b4c17e9d7..592a0f1557d52 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -1,102 +1,101 @@ - import expect from 'expect.js'; -import { - bdd, - remote, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const remote = getService('remote'); + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['settings', 'common']); -bdd.describe('creating and deleting default index', function describeIndexTests() { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); + describe('creating and deleting default index', function describeIndexTests() { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }) + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }); }); - }); - bdd.describe('index pattern creation', function indexPatternCreation() { - bdd.before(function () { - return PageObjects.settings.createIndexPattern(); - }); + describe('index pattern creation', function indexPatternCreation() { + before(function () { + return PageObjects.settings.createIndexPattern(); + }); - bdd.it('should have index pattern in page header', function () { - return PageObjects.settings.getIndexPageHeading().getVisibleText() - .then(function (patternName) { - PageObjects.common.saveScreenshot('Settings-indices-new-index-pattern'); - expect(patternName).to.be('logstash-*'); + it('should have index pattern in page header', function () { + return PageObjects.settings.getIndexPageHeading().getVisibleText() + .then(function (patternName) { + PageObjects.common.saveScreenshot('Settings-indices-new-index-pattern'); + expect(patternName).to.be('logstash-*'); + }); }); - }); - bdd.it('should have index pattern in url', function url() { - return PageObjects.common.try(function tryingForTime() { - return remote.getCurrentUrl() - .then(function (currentUrl) { - expect(currentUrl).to.contain('logstash-*'); + it('should have index pattern in url', function url() { + return retry.try(function tryingForTime() { + return remote.getCurrentUrl() + .then(function (currentUrl) { + expect(currentUrl).to.contain('logstash-*'); + }); }); }); - }); - bdd.it('should have expected table headers', function checkingHeader() { - return PageObjects.settings.getTableHeader() - .then(function (headers) { - PageObjects.common.debug('header.length = ' + headers.length); - const expectedHeaders = [ - 'name', - 'type', - 'format', - 'searchable', - 'aggregatable', - 'analyzed', - 'excluded', - 'controls' - ]; + it('should have expected table headers', function checkingHeader() { + return PageObjects.settings.getTableHeader() + .then(function (headers) { + log.debug('header.length = ' + headers.length); + const expectedHeaders = [ + 'name', + 'type', + 'format', + 'searchable', + 'aggregatable', + 'analyzed', + 'excluded', + 'controls' + ]; - expect(headers.length).to.be(expectedHeaders.length); + expect(headers.length).to.be(expectedHeaders.length); - const comparedHeaders = headers.map(function compareHead(header, i) { - return header.getVisibleText() - .then(function (text) { - expect(text).to.be(expectedHeaders[i]); + const comparedHeaders = headers.map(function compareHead(header, i) { + return header.getVisibleText() + .then(function (text) { + expect(text).to.be(expectedHeaders[i]); + }); }); - }); - return Promise.all(comparedHeaders); + return Promise.all(comparedHeaders); + }); }); }); - }); - bdd.describe('index pattern deletion', function indexDelete() { - bdd.before(function () { - const expectedAlertText = 'Are you sure you want to remove this index pattern?'; - return PageObjects.settings.removeIndexPattern() - .then(function (alertText) { - PageObjects.common.saveScreenshot('Settings-indices-confirm-remove-index-pattern'); - expect(alertText).to.be(expectedAlertText); + describe('index pattern deletion', function indexDelete() { + before(function () { + const expectedAlertText = 'Are you sure you want to remove this index pattern?'; + return PageObjects.settings.removeIndexPattern() + .then(function (alertText) { + PageObjects.common.saveScreenshot('Settings-indices-confirm-remove-index-pattern'); + expect(alertText).to.be(expectedAlertText); + }); }); - }); - bdd.it('should return to index pattern creation page', function returnToPage() { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.settings.getCreateButton(); + it('should return to index pattern creation page', function returnToPage() { + return retry.try(function tryingForTime() { + return PageObjects.settings.getCreateButton(); + }); }); - }); - bdd.it('should remove index pattern from url', function indexNotInUrl() { - // give the url time to settle - return PageObjects.common.try(function tryingForTime() { - return remote.getCurrentUrl() - .then(function (currentUrl) { - PageObjects.common.debug('currentUrl = ' + currentUrl); - expect(currentUrl).to.not.contain('logstash-*'); + it('should remove index pattern from url', function indexNotInUrl() { + // give the url time to settle + return retry.try(function tryingForTime() { + return remote.getCurrentUrl() + .then(function (currentUrl) { + log.debug('currentUrl = ' + currentUrl); + expect(currentUrl).to.not.contain('logstash-*'); + }); }); }); }); }); -}); +} diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.js index f558ade4cd0db..7d6349b5f6d34 100644 --- a/test/functional/apps/management/_index_pattern_filter.js +++ b/test/functional/apps/management/_index_pattern_filter.js @@ -1,57 +1,54 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('index pattern filter', function describeIndexTests() { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['settings']); + + describe('index pattern filter', function describeIndexTests() { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }) + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }); }); - }); - - bdd.beforeEach(function () { - return PageObjects.settings.createIndexPattern(); - }); - - bdd.afterEach(function () { - return PageObjects.settings.removeIndexPattern(); - }); - - bdd.it('should filter indexed fields', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - await PageObjects.settings.getFieldTypes(); - - await PageObjects.settings.setFieldTypeFilter('string'); - - await PageObjects.common.try(async function() { - const fieldTypes = await PageObjects.settings.getFieldTypes(); - expect(fieldTypes.length).to.be.above(0); - for (const fieldType of fieldTypes) { - expect(fieldType).to.be('string'); - } + beforeEach(function () { + return PageObjects.settings.createIndexPattern(); }); - await PageObjects.settings.setFieldTypeFilter('number'); + afterEach(function () { + return PageObjects.settings.removeIndexPattern(); + }); - await PageObjects.common.try(async function() { - const fieldTypes = await PageObjects.settings.getFieldTypes(); - expect(fieldTypes.length).to.be.above(0); - for (const fieldType of fieldTypes) { - expect(fieldType).to.be('number'); - } + it('should filter indexed fields', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.getFieldTypes(); + + await PageObjects.settings.setFieldTypeFilter('string'); + + await retry.try(async function() { + const fieldTypes = await PageObjects.settings.getFieldTypes(); + expect(fieldTypes.length).to.be.above(0); + for (const fieldType of fieldTypes) { + expect(fieldType).to.be('string'); + } + }); + + await PageObjects.settings.setFieldTypeFilter('number'); + + await retry.try(async function() { + const fieldTypes = await PageObjects.settings.getFieldTypes(); + expect(fieldTypes.length).to.be.above(0); + for (const fieldType of fieldTypes) { + expect(fieldType).to.be('number'); + } + }); }); }); -}); +} diff --git a/test/functional/apps/management/_index_pattern_popularity.js b/test/functional/apps/management/_index_pattern_popularity.js index 02109105ddc83..5a8c26c56addc 100644 --- a/test/functional/apps/management/_index_pattern_popularity.js +++ b/test/functional/apps/management/_index_pattern_popularity.js @@ -1,109 +1,80 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const log = getService('log'); + const PageObjects = getPageObjects(['settings', 'common']); -import PageObjects from '../../../support/page_objects'; + describe('index result popularity', function describeIndexTests() { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }); + }); -bdd.describe('index result popularity', function describeIndexTests() { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); + beforeEach(function be() { + return PageObjects.settings.createIndexPattern(); }); - }); - bdd.beforeEach(function be() { - return PageObjects.settings.createIndexPattern(); - }); + afterEach(function ae() { + return PageObjects.settings.removeIndexPattern(); + }); - bdd.afterEach(function ae() { - return PageObjects.settings.removeIndexPattern(); - }); + describe('change popularity', function indexPatternCreation() { + const fieldName = 'geo.coordinates'; - bdd.describe('change popularity', function indexPatternCreation() { - const fieldName = 'geo.coordinates'; + // set the page size to All again, https://github.com/elastic/kibana/issues/5030 + // TODO: remove this after issue #5030 is closed + async function fix5030() { + await PageObjects.settings.setPageSize('All'); + await PageObjects.common.sleep(1000); + } - // set the page size to All again, https://github.com/elastic/kibana/issues/5030 - // TODO: remove this after issue #5030 is closed - function fix5030() { - return PageObjects.settings.setPageSize('All') - .then(function () { - return PageObjects.common.sleep(1000); + beforeEach(async function () { + // increase Popularity of geo.coordinates + await PageObjects.settings.setPageSize('All'); + await PageObjects.common.sleep(1000); + log.debug('Starting openControlsByName (' + fieldName + ')'); + await PageObjects.settings.openControlsByName(fieldName); + log.debug('increasePopularity'); + await PageObjects.settings.increasePopularity(); }); - } - bdd.beforeEach(function () { - // increase Popularity of geo.coordinates - return PageObjects.settings.setPageSize('All') - .then(function () { - return PageObjects.common.sleep(1000); - }) - .then(function openControlsByName() { - PageObjects.common.debug('Starting openControlsByName (' + fieldName + ')'); - return PageObjects.settings.openControlsByName(fieldName); - }) - .then(function increasePopularity() { - PageObjects.common.debug('increasePopularity'); - return PageObjects.settings.increasePopularity(); + afterEach(async function () { + // Cancel saving the popularity change (we didn't make a change in this case, just checking the value) + await PageObjects.settings.controlChangeCancel(); }); - }); - bdd.afterEach(function () { - // Cancel saving the popularity change (we didn't make a change in this case, just checking the value) - return PageObjects.settings.controlChangeCancel(); - }); - - bdd.it('should update the popularity input', function () { - return PageObjects.settings.getPopularity() - .then(function (popularity) { - PageObjects.common.debug('popularity = ' + popularity); + it('should update the popularity input', async function () { + const popularity = await PageObjects.settings.getPopularity(); + log.debug('popularity = ' + popularity); expect(popularity).to.be('1'); PageObjects.common.saveScreenshot('Settings-indices-result-popularity-updated'); }); - }); - bdd.it('should be reset on cancel', function () { - // Cancel saving the popularity change - return PageObjects.settings.controlChangeCancel() - .then(function () { - return fix5030(); - }) - .then(function openControlsByName() { - return PageObjects.settings.openControlsByName(fieldName); - }) - // check that its 0 (previous increase was cancelled) - .then(function getPopularity() { - return PageObjects.settings.getPopularity(); - }) - .then(function (popularity) { - PageObjects.common.debug('popularity = ' + popularity); + it('should be reset on cancel', async function () { + // Cancel saving the popularity change + await PageObjects.settings.controlChangeCancel(); + await fix5030(); + await PageObjects.settings.openControlsByName(fieldName); + // check that it is 0 (previous increase was cancelled + const popularity = await PageObjects.settings.getPopularity(); + log.debug('popularity = ' + popularity); expect(popularity).to.be('0'); }); - }); - bdd.it('can be saved', function () { - // Saving the popularity change - return PageObjects.settings.controlChangeSave() - .then(function () { - return fix5030(); - }) - .then(function openControlsByName() { - return PageObjects.settings.openControlsByName(fieldName); - }) - // check that its 0 (previous increase was cancelled) - .then(function getPopularity() { - return PageObjects.settings.getPopularity(); - }) - .then(function (popularity) { - PageObjects.common.debug('popularity = ' + popularity); + it('can be saved', async function () { + // Saving the popularity change + await PageObjects.settings.controlChangeSave(); + await fix5030(); + await PageObjects.settings.openControlsByName(fieldName); + const popularity = await PageObjects.settings.getPopularity(); + log.debug('popularity = ' + popularity); expect(popularity).to.be('1'); PageObjects.common.saveScreenshot('Settings-indices-result-popularity-saved'); }); - }); - }); // end 'change popularity' -}); // end index result popularity + }); // end 'change popularity' + }); // end index result popularity +} diff --git a/test/functional/apps/management/_index_pattern_results_sort.js b/test/functional/apps/management/_index_pattern_results_sort.js index f322452ee33d6..fc4bbf0bfcd43 100644 --- a/test/functional/apps/management/_index_pattern_results_sort.js +++ b/test/functional/apps/management/_index_pattern_results_sort.js @@ -1,124 +1,122 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['settings', 'common']); -bdd.describe('index result field sort', function describeIndexTests() { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc(); - }); - - const columns = [{ - heading: 'name', - first: '@message', - last: 'xss.raw', - selector: function () { - return PageObjects.settings.getTableRow(0, 0).getVisibleText(); - } - }, { - heading: 'type', - first: '_source', - last: 'string', - selector: function () { - return PageObjects.settings.getTableRow(0, 1).getVisibleText(); - } - }]; + describe('index result field sort', function describeIndexTests() { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}); + }); - columns.forEach(function (col) { - bdd.describe('sort by heading - ' + col.heading, function indexPatternCreation() { - bdd.before(function () { - return PageObjects.settings.navigateTo() - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); + const columns = [{ + heading: 'name', + first: '@message', + last: 'xss.raw', + selector: function () { + return PageObjects.settings.getTableRow(0, 0).getVisibleText(); + } + }, { + heading: 'type', + first: '_source', + last: 'string', + selector: function () { + return PageObjects.settings.getTableRow(0, 1).getVisibleText(); + } + }]; + + columns.forEach(function (col) { + describe('sort by heading - ' + col.heading, function indexPatternCreation() { + before(function () { + return PageObjects.settings.navigateTo() + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }); }); - }); - bdd.beforeEach(function () { - return PageObjects.settings.createIndexPattern(); - }); + beforeEach(function () { + return PageObjects.settings.createIndexPattern(); + }); - bdd.afterEach(function () { - return PageObjects.settings.removeIndexPattern(); - }); + afterEach(function () { + return PageObjects.settings.removeIndexPattern(); + }); - bdd.it('should sort ascending', function () { - return PageObjects.settings.sortBy(col.heading) - .then(function getText() { - return col.selector(); - }) - .then(function (rowText) { - PageObjects.common.saveScreenshot(`Settings-indices-column-${col.heading}-sort-ascending`); - expect(rowText).to.be(col.first); + it('should sort ascending', function () { + return PageObjects.settings.sortBy(col.heading) + .then(function getText() { + return col.selector(); + }) + .then(function (rowText) { + PageObjects.common.saveScreenshot(`Settings-indices-column-${col.heading}-sort-ascending`); + expect(rowText).to.be(col.first); + }); }); - }); - bdd.it('should sort descending', function () { - return PageObjects.settings.sortBy(col.heading) - .then(function sortAgain() { - return PageObjects.settings.sortBy(col.heading); - }) - .then(function getText() { - return col.selector(); - }) - .then(function (rowText) { - PageObjects.common.saveScreenshot(`Settings-indices-column-${col.heading}-sort-descending`); - expect(rowText).to.be(col.last); + it('should sort descending', function () { + return PageObjects.settings.sortBy(col.heading) + .then(function sortAgain() { + return PageObjects.settings.sortBy(col.heading); + }) + .then(function getText() { + return col.selector(); + }) + .then(function (rowText) { + PageObjects.common.saveScreenshot(`Settings-indices-column-${col.heading}-sort-descending`); + expect(rowText).to.be(col.last); + }); }); }); }); - }); - bdd.describe('field list pagination', function () { - const EXPECTED_DEFAULT_PAGE_SIZE = 25; - const EXPECTED_FIELD_COUNT = 85; - const EXPECTED_LAST_PAGE_COUNT = 10; - const LAST_PAGE_NUMBER = 4; + describe('field list pagination', function () { + const EXPECTED_DEFAULT_PAGE_SIZE = 25; + const EXPECTED_FIELD_COUNT = 85; + const EXPECTED_LAST_PAGE_COUNT = 10; + const LAST_PAGE_NUMBER = 4; - bdd.before(function () { - return PageObjects.settings.navigateTo() - .then(function () { - return PageObjects.settings.createIndexPattern(); + before(function () { + return PageObjects.settings.navigateTo() + .then(function () { + return PageObjects.settings.createIndexPattern(); + }); }); - }); - bdd.after(function () { - return PageObjects.settings.removeIndexPattern(); - }); + after(function () { + return PageObjects.settings.removeIndexPattern(); + }); - bdd.it('makelogs data should have expected number of fields', function () { - return PageObjects.common.try(function () { - return PageObjects.settings.getFieldsTabCount() - .then(function (tabCount) { - expect(tabCount).to.be('' + EXPECTED_FIELD_COUNT); + it('makelogs data should have expected number of fields', function () { + return retry.try(function () { + return PageObjects.settings.getFieldsTabCount() + .then(function (tabCount) { + expect(tabCount).to.be('' + EXPECTED_FIELD_COUNT); + }); }); }); - }); - bdd.it('should have correct default page size selected', function () { - return PageObjects.settings.getPageSize() - .then(function (pageSize) { - expect(pageSize).to.be('' + EXPECTED_DEFAULT_PAGE_SIZE); + it('should have correct default page size selected', function () { + return PageObjects.settings.getPageSize() + .then(function (pageSize) { + expect(pageSize).to.be('' + EXPECTED_DEFAULT_PAGE_SIZE); + }); }); - }); - - bdd.it('should have the correct number of rows per page', async function () { - for (let pageNum = 1; pageNum <= LAST_PAGE_NUMBER; pageNum += 1) { - await PageObjects.settings.goToPage(pageNum); - const pageFieldNames = await PageObjects.common.tryMethod(PageObjects.settings, 'getFieldNames'); - await PageObjects.common.saveScreenshot(`Settings-indexed-fields-page-${pageNum}`); - if (pageNum === LAST_PAGE_NUMBER) { - expect(pageFieldNames).to.have.length(EXPECTED_LAST_PAGE_COUNT); - } else { - expect(pageFieldNames).to.have.length(EXPECTED_DEFAULT_PAGE_SIZE); + it('should have the correct number of rows per page', async function () { + for (let pageNum = 1; pageNum <= LAST_PAGE_NUMBER; pageNum += 1) { + await PageObjects.settings.goToPage(pageNum); + const pageFieldNames = await retry.tryMethod(PageObjects.settings, 'getFieldNames'); + await PageObjects.common.saveScreenshot(`Settings-indexed-fields-page-${pageNum}`); + + if (pageNum === LAST_PAGE_NUMBER) { + expect(pageFieldNames).to.have.length(EXPECTED_LAST_PAGE_COUNT); + } else { + expect(pageFieldNames).to.have.length(EXPECTED_DEFAULT_PAGE_SIZE); + } } - } - }); - }); // end describe pagination -}); // end index result field sort + }); + }); // end describe pagination + }); // end index result field sort +} diff --git a/test/functional/apps/management/_initial_state.js b/test/functional/apps/management/_initial_state.js index 7a69d82b8b2c8..5f97d14c3698d 100644 --- a/test/functional/apps/management/_initial_state.js +++ b/test/functional/apps/management/_initial_state.js @@ -1,61 +1,59 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('initial state', function () { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const log = getService('log'); + const PageObjects = getPageObjects(['settings', 'common']); + + describe('initial state', function () { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }) + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }); }); - }); - bdd.it('should load with time pattern checked', function () { - return PageObjects.settings.getTimeBasedEventsCheckbox().isSelected() - .then(function (selected) { - PageObjects.common.saveScreenshot('Settings-initial-state'); - expect(selected).to.be.ok(); + it('should load with time pattern checked', function () { + return PageObjects.settings.getTimeBasedEventsCheckbox().isSelected() + .then(function (selected) { + PageObjects.common.saveScreenshot('Settings-initial-state'); + expect(selected).to.be.ok(); + }); }); - }); - bdd.it('should load with name pattern unchecked', function () { - return PageObjects.settings.getTimeBasedIndexPatternCheckbox().isSelected() - .then(function (selected) { - expect(selected).to.not.be.ok(); + it('should load with name pattern unchecked', function () { + return PageObjects.settings.getTimeBasedIndexPatternCheckbox().isSelected() + .then(function (selected) { + expect(selected).to.not.be.ok(); + }); }); - }); - bdd.it('should contain default index pattern', function () { - const defaultPattern = 'logstash-*'; + it('should contain default index pattern', function () { + const defaultPattern = 'logstash-*'; - return PageObjects.settings.getIndexPatternField().getProperty('value') - .then(function (pattern) { - expect(pattern).to.be(defaultPattern); + return PageObjects.settings.getIndexPatternField().getProperty('value') + .then(function (pattern) { + expect(pattern).to.be(defaultPattern); + }); }); - }); - bdd.it('should not select the time field', function () { - return PageObjects.settings.getTimeFieldNameField().isSelected() - .then(function (timeFieldIsSelected) { - PageObjects.common.debug('timeField isSelected = ' + timeFieldIsSelected); - expect(timeFieldIsSelected).to.not.be.ok(); + it('should not select the time field', function () { + return PageObjects.settings.getTimeFieldNameField().isSelected() + .then(function (timeFieldIsSelected) { + log.debug('timeField isSelected = ' + timeFieldIsSelected); + expect(timeFieldIsSelected).to.not.be.ok(); + }); }); - }); - bdd.it('should not be enable creation', function () { - return PageObjects.settings.getCreateButton().isEnabled() - .then(function (enabled) { - expect(enabled).to.not.be.ok(); + it('should not be enable creation', function () { + return PageObjects.settings.getCreateButton().isEnabled() + .then(function (enabled) { + expect(enabled).to.not.be.ok(); + }); }); }); -}); +} diff --git a/test/functional/apps/management/_kibana_settings.js b/test/functional/apps/management/_kibana_settings.js index 58f9c0c282190..dd1b72cd49cb1 100644 --- a/test/functional/apps/management/_kibana_settings.js +++ b/test/functional/apps/management/_kibana_settings.js @@ -1,56 +1,57 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('creating and deleting default index', function describeIndexTests() { - bdd.before(function () { - // delete .kibana index and then wait for Kibana to re-create it - return esClient.deleteAndUpdateConfigDoc() - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndicies(); - }) - .then(function () { - return PageObjects.settings.createIndexPattern(); - }) - .then(function () { - return PageObjects.settings.navigateTo(); +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const log = getService('log'); + const PageObjects = getPageObjects(['settings', 'common']); + + describe('creating and deleting default index', function describeIndexTests() { + before(function () { + // delete .kibana index and then wait for Kibana to re-create it + return kibanaServer.uiSettings.replace({}) + .then(function () { + return PageObjects.settings.navigateTo(); + }) + .then(function () { + return PageObjects.settings.clickKibanaIndicies(); + }) + .then(function () { + return PageObjects.settings.createIndexPattern(); + }) + .then(function () { + return PageObjects.settings.navigateTo(); + }); }); - }); + after(async function afterAll() { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.removeIndexPattern(); + }); - bdd.it('should allow setting advanced settings', function () { - return PageObjects.settings.clickKibanaSettings() - .then(function TestCallSetAdvancedSettingsForTimezone() { - PageObjects.common.saveScreenshot('Settings-advanced-tab'); - PageObjects.common.debug('calling setAdvancedSetting'); - return PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'America/Phoenix'); - }) - .then(function GetAdvancedSetting() { - PageObjects.common.saveScreenshot('Settings-set-timezone'); - return PageObjects.settings.getAdvancedSettings('dateFormat:tz'); - }) - .then(function (advancedSetting) { - expect(advancedSetting).to.be('America/Phoenix'); + it('should allow setting advanced settings', function () { + return PageObjects.settings.clickKibanaSettings() + .then(function TestCallSetAdvancedSettingsForTimezone() { + PageObjects.common.saveScreenshot('Settings-advanced-tab'); + log.debug('calling setAdvancedSetting'); + return PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'America/Phoenix'); + }) + .then(function GetAdvancedSetting() { + PageObjects.common.saveScreenshot('Settings-set-timezone'); + return PageObjects.settings.getAdvancedSettings('dateFormat:tz'); + }) + .then(function (advancedSetting) { + expect(advancedSetting).to.be('America/Phoenix'); + }); }); - }); - bdd.after(function () { - return PageObjects.settings.clickKibanaSettings() - .then(function TestCallSetAdvancedSettingsForTimezone() { - PageObjects.common.saveScreenshot('Settings-advanced-tab'); - PageObjects.common.debug('calling setAdvancedSetting'); - return PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'UTC'); + after(function () { + return PageObjects.settings.clickKibanaSettings() + .then(function TestCallSetAdvancedSettingsForTimezone() { + PageObjects.common.saveScreenshot('Settings-advanced-tab'); + log.debug('calling setAdvancedSetting'); + return PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'UTC'); + }); }); }); - - -}); +} diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 11623bc944680..073c76cada174 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -13,386 +13,396 @@ import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.before(async function () { - await PageObjects.remote.setWindowSize(1200,800); - // delete .kibana index and then wait for Kibana to re-create it - await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC' }); - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - await PageObjects.settings.createIndexPattern(); - await esClient.updateConfigDoc({ 'dateFormat:tz':'UTC' }); -}); - -bdd.describe('creating and using Lucence expression scripted fields', function describeIndexTests() { - - const scriptedExpressionFieldName = 'ram_expr1'; - - bdd.it('should create scripted field', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); - await PageObjects.settings.clickScriptedFieldsTab(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings - .addScriptedField(scriptedExpressionFieldName, - 'expression', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)' - ); - await PageObjects.common.try(async function() { - expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const log = getService('log'); + const remote = getService('remote'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'discover']); + + describe('scripted fields', () => { + + before(async function () { + await remote.setWindowSize(1200,800); + // delete .kibana index and then wait for Kibana to re-create it + await kibanaServer.uiSettings.replace({ 'dateFormat:tz':'UTC' }); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.createIndexPattern(); + await kibanaServer.uiSettings.update({ 'dateFormat:tz':'UTC' }); }); - }); - - bdd.it('should see scripted field value in Discover', async function () { - const fromTime = '2015-09-17 06:31:44.000'; - const toTime = '2015-09-18 18:31:44.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.common.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); - await PageObjects.common.try(async function() { - await PageObjects.discover.clickFieldListItemAdd(scriptedExpressionFieldName); - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('September 18th 2015, 18:20:57.916 18'); - }); - - }); - - bdd.it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); - await PageObjects.common.debug('filter by the first value (14) in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedExpressionFieldName, '14'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be('31'); - }); - }); - - bdd.it('should visualize scripted field in vertical bar chart', async function () { - const expectedChartValues = [ '14 31', '10 29', '7 24', '11 24', '12 23', - '20 23', '19 21', '6 20', '17 20', '30 20', '13 19', '18 18', '16 17', '5 16', - '8 16', '15 14', '3 13', '2 12', '9 10', '4 9' - ]; - await PageObjects.discover.removeAllFilters(); - await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); - await PageObjects.discover.clickFieldListItemVisualize(scriptedExpressionFieldName); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.collapseChart(); - await PageObjects.settings.setPageSize('All'); - const data = await PageObjects.visualize.getDataTableData(); - await PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - await PageObjects.common.debug('data=' + data); - await PageObjects.common.debug('data.length=' + data.length); - await PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); - expect(data.trim().split('\n')).to.eql(expectedChartValues); - }); - -}); - -bdd.describe('creating and using Painless numeric scripted fields', function describeIndexTests() { - - const scriptedPainlessFieldName = 'ram_Pain1'; - bdd.it('should create scripted field', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); - await PageObjects.settings.clickScriptedFieldsTab(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings - .addScriptedField(scriptedPainlessFieldName, 'painless', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)'); - await PageObjects.common.try(async function() { - expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + after(async function afterAll() { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.removeIndexPattern(); }); - }); - - bdd.it('should see scripted field value in Discover', async function () { - const fromTime = '2015-09-17 06:31:44.000'; - const toTime = '2015-09-18 18:31:44.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.common.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); - await PageObjects.common.try(async function() { - await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName); - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('September 18th 2015, 18:20:57.916 18'); - }); - }); - - bdd.it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); - await PageObjects.common.debug('filter by the first value (14) in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be('31'); - }); - }); - - bdd.it('should visualize scripted field in vertical bar chart', async function () { - const expectedChartValues = [ '14 31', '10 29', '7 24', '11 24', '12 23', - '20 23', '19 21', '6 20', '17 20', '30 20', '13 19', '18 18', '16 17', '5 16', - '8 16', '15 14', '3 13', '2 12', '9 10', '4 9' - ]; - await PageObjects.discover.removeAllFilters(); - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.collapseChart(); - await PageObjects.settings.setPageSize('All'); - const data = await PageObjects.visualize.getDataTableData(); - await PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - await PageObjects.common.debug('data=' + data); - await PageObjects.common.debug('data.length=' + data.length); - await PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); - expect(data.trim().split('\n')).to.eql(expectedChartValues); - }); - -}); -bdd.describe('creating and using Painless string scripted fields', function describeIndexTests() { - - const scriptedPainlessFieldName2 = 'painString'; - - bdd.it('should create scripted field', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); - await PageObjects.settings.clickScriptedFieldsTab(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings - .addScriptedField(scriptedPainlessFieldName2, 'painless', 'string', null, '1', - 'if (doc[\'response.raw\'].value == \'200\') { return \'good\'} else { return \'bad\'}'); - await PageObjects.common.try(async function() { - expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); - }); - }); + describe('creating and using Lucence expression scripted fields', function describeIndexTests() { + + const scriptedExpressionFieldName = 'ram_expr1'; + + it('should create scripted field', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); + await PageObjects.settings.clickScriptedFieldsTab(); + await log.debug('add scripted field'); + await PageObjects.settings + .addScriptedField(scriptedExpressionFieldName, + 'expression', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)' + ); + await retry.try(async function() { + expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + }); + }); + + it('should see scripted field value in Discover', async function () { + const fromTime = '2015-09-17 06:31:44.000'; + const toTime = '2015-09-18 18:31:44.000'; + await PageObjects.common.navigateToApp('discover'); + await log.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); + await retry.try(async function() { + await PageObjects.discover.clickFieldListItemAdd(scriptedExpressionFieldName); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('September 18th 2015, 18:20:57.916 18'); + }); + + }); + + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); + await log.debug('filter by the first value (14) in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedExpressionFieldName, '14'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be('31'); + }); + }); + + it('should visualize scripted field in vertical bar chart', async function () { + const expectedChartValues = [ '14 31', '10 29', '7 24', '11 24', '12 23', + '20 23', '19 21', '6 20', '17 20', '30 20', '13 19', '18 18', '16 17', '5 16', + '8 16', '15 14', '3 13', '2 12', '9 10', '4 9' + ]; + await PageObjects.discover.removeAllFilters(); + await PageObjects.discover.clickFieldListItem(scriptedExpressionFieldName); + await PageObjects.discover.clickFieldListItemVisualize(scriptedExpressionFieldName); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.visualize.collapseChart(); + await PageObjects.settings.setPageSize('All'); + const data = await PageObjects.visualize.getDataTableData(); + await log.debug('getDataTableData = ' + data.split('\n')); + await log.debug('data=' + data); + await log.debug('data.length=' + data.length); + await PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); + expect(data.trim().split('\n')).to.eql(expectedChartValues); + }); - bdd.it('should see scripted field value in Discover', async function () { - const fromTime = '2015-09-17 06:31:44.000'; - const toTime = '2015-09-18 18:31:44.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.common.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.try(async function() { - await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('September 18th 2015, 18:20:57.916 good'); - }); - }); + describe('creating and using Painless numeric scripted fields', function describeIndexTests() { + + const scriptedPainlessFieldName = 'ram_Pain1'; + + it('should create scripted field', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); + await PageObjects.settings.clickScriptedFieldsTab(); + await log.debug('add scripted field'); + const script = 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)'; + await PageObjects.settings.addScriptedField(scriptedPainlessFieldName, 'painless', 'number', null, '1', script); + await retry.try(async function() { + expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + }); + }); + + it('should see scripted field value in Discover', async function () { + const fromTime = '2015-09-17 06:31:44.000'; + const toTime = '2015-09-18 18:31:44.000'; + await PageObjects.common.navigateToApp('discover'); + await log.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); + await retry.try(async function() { + await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('September 18th 2015, 18:20:57.916 18'); + }); + }); + + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); + await log.debug('filter by the first value (14) in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be('31'); + }); + }); + + it('should visualize scripted field in vertical bar chart', async function () { + const expectedChartValues = [ '14 31', '10 29', '7 24', '11 24', '12 23', + '20 23', '19 21', '6 20', '17 20', '30 20', '13 19', '18 18', '16 17', '5 16', + '8 16', '15 14', '3 13', '2 12', '9 10', '4 9' + ]; + await PageObjects.discover.removeAllFilters(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.visualize.collapseChart(); + await PageObjects.settings.setPageSize('All'); + const data = await PageObjects.visualize.getDataTableData(); + await log.debug('getDataTableData = ' + data.split('\n')); + await log.debug('data=' + data); + await log.debug('data.length=' + data.length); + await PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); + expect(data.trim().split('\n')).to.eql(expectedChartValues); + }); - bdd.it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.debug('filter by "bad" in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'bad'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be('27'); }); - await PageObjects.discover.removeAllFilters(); - }); - - bdd.it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.collapseChart(); - await PageObjects.settings.setPageSize('All'); - const data = await PageObjects.visualize.getDataTableData(); - await PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - await PageObjects.common.debug('data=' + data); - await PageObjects.common.debug('data.length=' + data.length); - expect(data.trim().split('\n')).to.eql([ 'good 359', 'bad 27' ]); - }); - -}); + describe('creating and using Painless string scripted fields', function describeIndexTests() { + + const scriptedPainlessFieldName2 = 'painString'; + + it('should create scripted field', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); + await PageObjects.settings.clickScriptedFieldsTab(); + await log.debug('add scripted field'); + await PageObjects.settings + .addScriptedField(scriptedPainlessFieldName2, 'painless', 'string', null, '1', + 'if (doc[\'response.raw\'].value == \'200\') { return \'good\'} else { return \'bad\'}'); + await retry.try(async function() { + expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + }); + }); + + it('should see scripted field value in Discover', async function () { + const fromTime = '2015-09-17 06:31:44.000'; + const toTime = '2015-09-18 18:31:44.000'; + await PageObjects.common.navigateToApp('discover'); + await log.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await retry.try(async function() { + await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('September 18th 2015, 18:20:57.916 good'); + + }); + }); + + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await log.debug('filter by "bad" in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'bad'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be('27'); + }); + await PageObjects.discover.removeAllFilters(); + }); + + it('should visualize scripted field in vertical bar chart', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.visualize.collapseChart(); + await PageObjects.settings.setPageSize('All'); + const data = await PageObjects.visualize.getDataTableData(); + await log.debug('getDataTableData = ' + data.split('\n')); + await log.debug('data=' + data); + await log.debug('data.length=' + data.length); + expect(data.trim().split('\n')).to.eql([ 'good 359', 'bad 27' ]); + }); -bdd.describe('creating and using Painless boolean scripted fields', function describeIndexTests() { - - const scriptedPainlessFieldName2 = 'painBool'; - - bdd.it('should create scripted field', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); - await PageObjects.settings.clickScriptedFieldsTab(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings - .addScriptedField(scriptedPainlessFieldName2, 'painless', 'boolean', null, '1', - 'doc[\'response.raw\'].value == \'200\''); - await PageObjects.common.try(async function() { - expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); }); - }); - bdd.it('should see scripted field value in Discover', async function () { - const fromTime = '2015-09-17 06:31:44.000'; - const toTime = '2015-09-18 18:31:44.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.common.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.try(async function() { - await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('September 18th 2015, 18:20:57.916 true'); - }); - }); + describe('creating and using Painless boolean scripted fields', function describeIndexTests() { + + const scriptedPainlessFieldName2 = 'painBool'; + + it('should create scripted field', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); + await PageObjects.settings.clickScriptedFieldsTab(); + await log.debug('add scripted field'); + await PageObjects.settings + .addScriptedField(scriptedPainlessFieldName2, 'painless', 'boolean', null, '1', + 'doc[\'response.raw\'].value == \'200\''); + await retry.try(async function() { + expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + }); + }); + + it('should see scripted field value in Discover', async function () { + const fromTime = '2015-09-17 06:31:44.000'; + const toTime = '2015-09-18 18:31:44.000'; + await PageObjects.common.navigateToApp('discover'); + await log.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await retry.try(async function() { + await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('September 18th 2015, 18:20:57.916 true'); + + }); + }); + + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await log.debug('filter by "true" in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'true'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be('359'); + }); + await PageObjects.discover.removeAllFilters(); + }); + + it('should visualize scripted field in vertical bar chart', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.visualize.collapseChart(); + await PageObjects.settings.setPageSize('All'); + const data = await PageObjects.visualize.getDataTableData(); + await log.debug('getDataTableData = ' + data.split('\n')); + await log.debug('data=' + data); + await log.debug('data.length=' + data.length); + expect(data.trim().split('\n')).to.eql([ 'true 359', 'false 27' ]); + }); - bdd.it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.debug('filter by "true" in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'true'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be('359'); }); - await PageObjects.discover.removeAllFilters(); - }); - bdd.it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.collapseChart(); - await PageObjects.settings.setPageSize('All'); - const data = await PageObjects.visualize.getDataTableData(); - await PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - await PageObjects.common.debug('data=' + data); - await PageObjects.common.debug('data.length=' + data.length); - expect(data.trim().split('\n')).to.eql([ 'true 359', 'false 27' ]); - }); - -}); - - -bdd.describe('creating and using Painless date scripted fields', function describeIndexTests() { - - const scriptedPainlessFieldName2 = 'painDate'; - - bdd.it('should create scripted field', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); - await PageObjects.settings.clickScriptedFieldsTab(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings - .addScriptedField(scriptedPainlessFieldName2, 'painless', 'date', - { format: 'Date', datePattern: 'YYYY-MM-DD HH:00' }, '1', - 'doc[\'utc_time\'].value.getMillis() + (1000) * 60 * 60'); - await PageObjects.common.try(async function() { - expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); - }); - }); - bdd.it('should see scripted field value in Discover', async function () { - const fromTime = '2015-09-17 19:22:00.000'; - const toTime = '2015-09-18 07:00:00.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.common.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.try(async function() { - await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('September 18th 2015, 06:52:55.953 2015-09-18 07:00'); - }); - }); + describe('creating and using Painless date scripted fields', function describeIndexTests() { + + const scriptedPainlessFieldName2 = 'painDate'; + + it('should create scripted field', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount()); + await PageObjects.settings.clickScriptedFieldsTab(); + await log.debug('add scripted field'); + await PageObjects.settings + .addScriptedField(scriptedPainlessFieldName2, 'painless', 'date', + { format: 'Date', datePattern: 'YYYY-MM-DD HH:00' }, '1', + 'doc[\'utc_time\'].value.getMillis() + (1000) * 60 * 60'); + await retry.try(async function() { + expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(startingCount + 1); + }); + }); + + it('should see scripted field value in Discover', async function () { + const fromTime = '2015-09-17 19:22:00.000'; + const toTime = '2015-09-18 07:00:00.000'; + await PageObjects.common.navigateToApp('discover'); + await log.debug('setAbsoluteRange (' + fromTime + ') to (' + toTime + ')'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await retry.try(async function() { + await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('September 18th 2015, 06:52:55.953 2015-09-18 07:00'); + }); + }); + + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await log.debug('filter by "2015-09-17 23:00" in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, '2015-09-17 23:00'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await retry.try(async function() { + expect(await PageObjects.discover.getHitCount()).to.be('1'); + }); + await PageObjects.discover.removeAllFilters(); + }); + + it('should visualize scripted field in vertical bar chart', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.waitForVisualization(); + await PageObjects.visualize.collapseChart(); + await PageObjects.settings.setPageSize('All'); + const data = await PageObjects.visualize.getDataTableData(); + await log.debug('getDataTableData = ' + data.split('\n')); + await log.debug('data=' + data); + await log.debug('data.length=' + data.length); + expect(data.trim().split('\n')).to.eql([ + '2015-09-17 20:00 1', + '2015-09-17 21:00 1', + '2015-09-17 23:00 1', + '2015-09-18 00:00 1', + '2015-09-18 03:00 1', + '2015-09-18 04:00 1', + '2015-09-18 04:00 1', + '2015-09-18 04:00 1', + '2015-09-18 04:00 1', + '2015-09-18 05:00 1', + '2015-09-18 05:00 1', + '2015-09-18 05:00 1', + '2015-09-18 05:00 1', + '2015-09-18 06:00 1', + '2015-09-18 06:00 1', + '2015-09-18 06:00 1', + '2015-09-18 06:00 1', + '2015-09-18 07:00 1', + '2015-09-18 07:00 1', + '2015-09-18 07:00 1', + ]); + }); - bdd.it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.common.debug('filter by "2015-09-17 23:00" in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, '2015-09-17 23:00'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.common.try(async function() { - expect(await PageObjects.discover.getHitCount()).to.be('1'); }); - await PageObjects.discover.removeAllFilters(); }); - - bdd.it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.collapseChart(); - await PageObjects.settings.setPageSize('All'); - const data = await PageObjects.visualize.getDataTableData(); - await PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - await PageObjects.common.debug('data=' + data); - await PageObjects.common.debug('data.length=' + data.length); - expect(data.trim().split('\n')).to.eql([ - '2015-09-17 20:00 1', - '2015-09-17 21:00 1', - '2015-09-17 23:00 1', - '2015-09-18 00:00 1', - '2015-09-18 03:00 1', - '2015-09-18 04:00 1', - '2015-09-18 04:00 1', - '2015-09-18 04:00 1', - '2015-09-18 04:00 1', - '2015-09-18 05:00 1', - '2015-09-18 05:00 1', - '2015-09-18 05:00 1', - '2015-09-18 05:00 1', - '2015-09-18 06:00 1', - '2015-09-18 06:00 1', - '2015-09-18 06:00 1', - '2015-09-18 06:00 1', - '2015-09-18 07:00 1', - '2015-09-18 07:00 1', - '2015-09-18 07:00 1' - ]); - }); - -}); +} diff --git a/test/functional/apps/management/_scripted_fields_filter.js b/test/functional/apps/management/_scripted_fields_filter.js index 8ceea76b15f59..9d6956f32b52b 100644 --- a/test/functional/apps/management/_scripted_fields_filter.js +++ b/test/functional/apps/management/_scripted_fields_filter.js @@ -1,69 +1,68 @@ - import expect from 'expect.js'; -import { - bdd, - esClient -} from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const log = getService('log'); + const remote = getService('remote'); + const PageObjects = getPageObjects(['settings']); -bdd.describe('filter scripted fields', function describeIndexTests() { + describe('filter scripted fields', function describeIndexTests() { - bdd.beforeEach(async function () { - await PageObjects.remote.setWindowSize(1200,800); - // delete .kibana index and then wait for Kibana to re-create it - await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC' }); - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - await PageObjects.settings.createIndexPattern(); - await esClient.updateConfigDoc({ 'dateFormat:tz':'UTC' }); - }); + beforeEach(async function () { + await remote.setWindowSize(1200,800); + // delete .kibana index and then wait for Kibana to re-create it + await kibanaServer.uiSettings.replace({ 'dateFormat:tz':'UTC' }); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.createIndexPattern(); + await kibanaServer.uiSettings.update({ 'dateFormat:tz':'UTC' }); + }); - const scriptedExpressionFieldName = 'ram_expr1'; - const scriptedPainlessFieldName = 'ram_pain1'; + const scriptedExpressionFieldName = 'ram_expr1'; + const scriptedPainlessFieldName = 'ram_pain1'; - bdd.it('should filter scripted fields', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndicies(); - await PageObjects.settings.clickScriptedFieldsTab(); - const scriptedFieldLangsBefore = await PageObjects.settings.getScriptedFieldLangs(); - await PageObjects.common.debug('add scripted field'); - await PageObjects.settings + it('should filter scripted fields', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndicies(); + await PageObjects.settings.clickScriptedFieldsTab(); + const scriptedFieldLangsBefore = await PageObjects.settings.getScriptedFieldLangs(); + await log.debug('add scripted field'); + await PageObjects.settings .addScriptedField(scriptedExpressionFieldName, 'expression', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)' ); - await PageObjects.settings + await PageObjects.settings .addScriptedField(scriptedPainlessFieldName, 'painless', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)' ); - // confirm two additional scripted fields were created - await PageObjects.common.try(async function() { - const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); - expect(scriptedFieldLangs.length).to.be(scriptedFieldLangsBefore.length + 2); - }); + // confirm two additional scripted fields were created + await retry.try(async function() { + const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); + expect(scriptedFieldLangs.length).to.be(scriptedFieldLangsBefore.length + 2); + }); - await PageObjects.settings.setScriptedFieldLanguageFilter('painless'); + await PageObjects.settings.setScriptedFieldLanguageFilter('painless'); - await PageObjects.common.try(async function() { - const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); - expect(scriptedFieldLangs.length).to.be.above(0); - for (const lang of scriptedFieldLangs) { - expect(lang).to.be('painless'); - } - }); + await retry.try(async function() { + const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); + expect(scriptedFieldLangs.length).to.be.above(0); + for (const lang of scriptedFieldLangs) { + expect(lang).to.be('painless'); + } + }); - await PageObjects.settings.setScriptedFieldLanguageFilter('expression'); + await PageObjects.settings.setScriptedFieldLanguageFilter('expression'); - await PageObjects.common.try(async function() { - const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); - expect(scriptedFieldLangs.length).to.be.above(0); - for (const lang of scriptedFieldLangs) { - expect(lang).to.be('expression'); - } + await retry.try(async function() { + const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs(); + expect(scriptedFieldLangs.length).to.be.above(0); + for (const lang of scriptedFieldLangs) { + expect(lang).to.be('expression'); + } + }); }); - }); -}); + }); +} diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js index 8cada9fd6fef4..dbdec3d2cd8c3 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.js @@ -1,32 +1,32 @@ -import { - bdd, - defaultTimeout, - esArchiver, -} from '../../../support'; +export default function ({ getService, loadTestFile }) { + const config = getService('config'); + const esArchiver = getService('esArchiver'); -bdd.describe('settings app', function () { - this.timeout = defaultTimeout; + describe('management', function () { + this.timeout(config.get('timeouts.test')); - // on setup, we create an settingsPage instance - // that we will use for all the tests - bdd.before(async function () { - await esArchiver.unload('logstash_functional'); - await esArchiver.load('empty_kibana'); - await esArchiver.loadIfNeeded('makelogs'); - }); + // on setup, we create an settingsPage instance + // that we will use for all the tests + before(async function () { + await esArchiver.unload('logstash_functional'); + await esArchiver.load('empty_kibana'); + await esArchiver.loadIfNeeded('makelogs'); + }); + + after(async function () { + await esArchiver.unload('makelogs'); + await esArchiver.unload('empty_kibana'); + }); - bdd.after(async function () { - await esArchiver.unload('makelogs'); - await esArchiver.unload('empty_kibana'); + loadTestFile(require.resolve('./_initial_state')); + loadTestFile(require.resolve('./_creation_form_changes')); + loadTestFile(require.resolve('./_index_pattern_create_delete')); + loadTestFile(require.resolve('./_index_pattern_results_sort')); + loadTestFile(require.resolve('./_index_pattern_popularity')); + loadTestFile(require.resolve('./_kibana_settings')); + loadTestFile(require.resolve('./_scripted_fields')); + loadTestFile(require.resolve('./_index_pattern_filter')); + loadTestFile(require.resolve('./_scripted_fields_filter')); }); - require('./_initial_state'); - require('./_creation_form_changes'); - require('./_index_pattern_create_delete'); - require('./_index_pattern_results_sort'); - require('./_index_pattern_popularity'); - require('./_kibana_settings'); - require('./_scripted_fields'); - require('./_index_pattern_filter'); - require('./_scripted_fields_filter'); -}); +} diff --git a/test/functional/apps/status_page/index.js b/test/functional/apps/status_page/index.js new file mode 100644 index 0000000000000..c3ac3b2059132 --- /dev/null +++ b/test/functional/apps/status_page/index.js @@ -0,0 +1,24 @@ +import expect from 'expect.js'; + +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common']); + + describe('status page', function () { + before(function () { + return PageObjects.common.navigateToApp('status_page'); + }); + + it('should show the kibana plugin as ready', function () { + return retry.tryForTime(6000, function () { + return testSubjects.find('statusBreakdown') + .getVisibleText() + .then(function (text) { + PageObjects.common.saveScreenshot('Status'); + expect(text.indexOf('plugin:kibana')).to.be.above(-1); + }); + }); + }); + }); +} diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index d5c5fd94fe154..f82c84ee2fd98 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -1,182 +1,183 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']); -bdd.describe('visualize app', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('visualize app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickAreaChart'); - return PageObjects.visualize.clickAreaChart(); - }) - .then(function clickNewSearch() { - PageObjects.common.debug('clickNewSearch'); - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Click X-Axis'); - return PageObjects.visualize.clickBucket('X-Axis'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Click Date Histogram'); - return PageObjects.visualize.selectAggregation('Date Histogram'); - }) - .then(function getField() { - PageObjects.common.debug('Check field value'); - return PageObjects.visualize.getField(); - }) - .then(function (fieldValue) { - PageObjects.common.debug('fieldValue = ' + fieldValue); - expect(fieldValue).to.be('@timestamp'); - }) - .then(function getInterval() { - return PageObjects.visualize.getInterval(); - }) - .then(function (intervalValue) { - PageObjects.common.debug('intervalValue = ' + intervalValue); - expect(intervalValue).to.be('Auto'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function waitUntilLoadingHasFinished() { - PageObjects.common.debug('Waiting...'); - return PageObjects.header.waitUntilLoadingHasFinished(); + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickAreaChart'); + return PageObjects.visualize.clickAreaChart(); + }) + .then(function clickNewSearch() { + log.debug('clickNewSearch'); + return PageObjects.visualize.clickNewSearch(); + }) + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Click X-Axis'); + return PageObjects.visualize.clickBucket('X-Axis'); + }) + .then(function selectAggregation() { + log.debug('Click Date Histogram'); + return PageObjects.visualize.selectAggregation('Date Histogram'); + }) + .then(function getField() { + log.debug('Check field value'); + return PageObjects.visualize.getField(); + }) + .then(function (fieldValue) { + log.debug('fieldValue = ' + fieldValue); + expect(fieldValue).to.be('@timestamp'); + }) + .then(function getInterval() { + return PageObjects.visualize.getInterval(); + }) + .then(function (intervalValue) { + log.debug('intervalValue = ' + intervalValue); + expect(intervalValue).to.be('Auto'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function waitUntilLoadingHasFinished() { + log.debug('Waiting...'); + return PageObjects.header.waitUntilLoadingHasFinished(); + }); }); - }); - bdd.describe('area charts', function indexPatternCreation() { - const vizName1 = 'Visualization AreaChart Name Test'; + describe('area charts', function indexPatternCreation() { + const vizName1 = 'Visualization AreaChart Name Test'; - bdd.it('should save and load with special characters', function () { - const vizNamewithSpecialChars = vizName1 + '/?&=%'; - return PageObjects.visualize.saveVisualization(vizNamewithSpecialChars) - .then(function (message) { - PageObjects.common.debug(`Saved viz message = ${message}`); - expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`); - }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); + it('should save and load with special characters', function () { + const vizNamewithSpecialChars = vizName1 + '/?&=%'; + return PageObjects.visualize.saveVisualization(vizNamewithSpecialChars) + .then(function (message) { + log.debug(`Saved viz message = ${message}`); + expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }); }); - }); - bdd.it('should save and load with non-ascii characters', async function () { - const vizNamewithSpecialChars = `${vizName1} with Umlaut ä`; - const message = await PageObjects.visualize.saveVisualization(vizNamewithSpecialChars); + it('should save and load with non-ascii characters', async function () { + const vizNamewithSpecialChars = `${vizName1} with Umlaut ä`; + const message = await PageObjects.visualize.saveVisualization(vizNamewithSpecialChars); - PageObjects.common.debug(`Saved viz message with umlaut = ${message}`); - expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`); + log.debug(`Saved viz message with umlaut = ${message}`); + expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`); - await PageObjects.visualize.waitForToastMessageGone(); - }); + await PageObjects.visualize.waitForToastMessageGone(); + }); - bdd.it('should save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - PageObjects.common.saveScreenshot('Visualize-area-chart-save-toast'); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); - }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); - }) - .then(function loadSavedVisualization() { - return PageObjects.visualize.loadSavedVisualization(vizName1); - }) - .then(function () { - return PageObjects.visualize.waitForVisualization(); - }) - // We have to sleep sometime between loading the saved visTitle - // and trying to access the chart below with getXAxisLabels - // otherwise it hangs. - .then(function sleep() { - return PageObjects.common.sleep(2000); + it('should save and load', function () { + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + PageObjects.common.saveScreenshot('Visualize-area-chart-save-toast'); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function loadSavedVisualization() { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function () { + return PageObjects.visualize.waitForVisualization(); + }) + // We have to sleep sometime between loading the saved visTitle + // and trying to access the chart below with getXAxisLabels + // otherwise it hangs. + .then(function sleep() { + return PageObjects.common.sleep(2000); + }); }); - }); - bdd.it('should show correct chart, take screenshot', function () { - const xAxisLabels = [ '2015-09-20 00:00', '2015-09-21 00:00', - '2015-09-22 00:00', '2015-09-23 00:00' - ]; - const yAxisLabels = ['0','200','400','600','800','1,000','1,200','1,400','1,600']; - const expectedAreaChartData = [37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, - 683, 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 - ]; + it('should show correct chart, take screenshot', function () { + const xAxisLabels = [ '2015-09-20 00:00', '2015-09-21 00:00', + '2015-09-22 00:00', '2015-09-23 00:00' + ]; + const yAxisLabels = ['0','200','400','600','800','1,000','1,200','1,400','1,600']; + const expectedAreaChartData = [37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, + 683, 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 + ]; - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getXAxisLabels() - .then(function compareLabels(labels) { - PageObjects.common.debug('X-Axis labels = ' + labels); - expect(labels).to.eql(xAxisLabels); + return retry.try(function tryingForTime() { + return PageObjects.visualize.getXAxisLabels() + .then(function compareLabels(labels) { + log.debug('X-Axis labels = ' + labels); + expect(labels).to.eql(xAxisLabels); + }); + }) + .then(function getYAxisLabels() { + return PageObjects.visualize.getYAxisLabels(); + }) + .then(function (labels) { + log.debug('Y-Axis labels = ' + labels); + expect(labels).to.eql(yAxisLabels); + }) + .then(function getAreaChartData() { + return PageObjects.visualize.getAreaChartData('Count'); + }) + .then(function (paths) { + log.debug('expectedAreaChartData = ' + expectedAreaChartData); + log.debug('actual chart data = ' + paths); + PageObjects.common.saveScreenshot('Visualize-area-chart'); + expect(paths).to.eql(expectedAreaChartData); }); - }) - .then(function getYAxisLabels() { - return PageObjects.visualize.getYAxisLabels(); - }) - .then(function (labels) { - PageObjects.common.debug('Y-Axis labels = ' + labels); - expect(labels).to.eql(yAxisLabels); - }) - .then(function getAreaChartData() { - return PageObjects.visualize.getAreaChartData('Count'); - }) - .then(function (paths) { - PageObjects.common.debug('expectedAreaChartData = ' + expectedAreaChartData); - PageObjects.common.debug('actual chart data = ' + paths); - PageObjects.common.saveScreenshot('Visualize-area-chart'); - expect(paths).to.eql(expectedAreaChartData); }); - }); - bdd.it('should show correct data', function () { - const expectedTableData = [ 'September 20th 2015, 00:00:00.000 37', - 'September 20th 2015, 03:00:00.000 202', - 'September 20th 2015, 06:00:00.000 740', - 'September 20th 2015, 09:00:00.000 1,437', - 'September 20th 2015, 12:00:00.000 1,371', - 'September 20th 2015, 15:00:00.000 751', - 'September 20th 2015, 18:00:00.000 188', - 'September 20th 2015, 21:00:00.000 31', - 'September 21st 2015, 00:00:00.000 42', - 'September 21st 2015, 03:00:00.000 202', - 'September 21st 2015, 06:00:00.000 683', - 'September 21st 2015, 09:00:00.000 1,361', - 'September 21st 2015, 12:00:00.000 1,415', - 'September 21st 2015, 15:00:00.000 707', - 'September 21st 2015, 18:00:00.000 177', - 'September 21st 2015, 21:00:00.000 27', - 'September 22nd 2015, 00:00:00.000 32', - 'September 22nd 2015, 03:00:00.000 175', - 'September 22nd 2015, 06:00:00.000 707', - 'September 22nd 2015, 09:00:00.000 1,408', - 'September 22nd 2015, 12:00:00.000 1,355', - 'September 22nd 2015, 15:00:00.000 726', - 'September 22nd 2015, 18:00:00.000 201', - 'September 22nd 2015, 21:00:00.000 29' - ]; + it('should show correct data', function () { + const expectedTableData = [ 'September 20th 2015, 00:00:00.000 37', + 'September 20th 2015, 03:00:00.000 202', + 'September 20th 2015, 06:00:00.000 740', + 'September 20th 2015, 09:00:00.000 1,437', + 'September 20th 2015, 12:00:00.000 1,371', + 'September 20th 2015, 15:00:00.000 751', + 'September 20th 2015, 18:00:00.000 188', + 'September 20th 2015, 21:00:00.000 31', + 'September 21st 2015, 00:00:00.000 42', + 'September 21st 2015, 03:00:00.000 202', + 'September 21st 2015, 06:00:00.000 683', + 'September 21st 2015, 09:00:00.000 1,361', + 'September 21st 2015, 12:00:00.000 1,415', + 'September 21st 2015, 15:00:00.000 707', + 'September 21st 2015, 18:00:00.000 177', + 'September 21st 2015, 21:00:00.000 27', + 'September 22nd 2015, 00:00:00.000 32', + 'September 22nd 2015, 03:00:00.000 175', + 'September 22nd 2015, 06:00:00.000 707', + 'September 22nd 2015, 09:00:00.000 1,408', + 'September 22nd 2015, 12:00:00.000 1,355', + 'September 22nd 2015, 15:00:00.000 726', + 'September 22nd 2015, 18:00:00.000 201', + 'September 22nd 2015, 21:00:00.000 29' + ]; - return PageObjects.visualize.collapseChart() - .then(function setPageSize() { - return PageObjects.settings.setPageSize('All'); - }) - .then(function getDataTableData() { - return PageObjects.visualize.getDataTableData(); - }) - .then(function showData(data) { - PageObjects.common.debug('getDataTableData = ' + data.split('\n')); - expect(data.trim().split('\n')).to.eql(expectedTableData); + return PageObjects.visualize.collapseChart() + .then(function setPageSize() { + return PageObjects.settings.setPageSize('All'); + }) + .then(function getDataTableData() { + return PageObjects.visualize.getDataTableData(); + }) + .then(function showData(data) { + log.debug('getDataTableData = ' + data.split('\n')); + expect(data.trim().split('\n')).to.eql(expectedTableData); + }); }); }); }); -}); +} diff --git a/test/functional/apps/visualize/_chart_types.js b/test/functional/apps/visualize/_chart_types.js index 273e5552dbc4a..c265f3435b582 100644 --- a/test/functional/apps/visualize/_chart_types.js +++ b/test/functional/apps/visualize/_chart_types.js @@ -1,44 +1,43 @@ import expect from 'expect.js'; -import { - bdd -} from '../../../support'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'visualize']); -import PageObjects from '../../../support/page_objects'; + describe('visualize app', function describeIndexTests() { -bdd.describe('visualize app', function describeIndexTests() { - - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new'); - }); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new'); + }); - bdd.describe('chart types', function indexPatternCreation() { - bdd.it('should show the correct chart types', function () { - const expectedChartTypes = [ - 'Area', - 'Heat Map', - 'Horizontal Bar', - 'Line', - 'Pie', - 'Vertical Bar', - 'Data Table', - 'Metric', - 'Tile Map', - 'Timelion', - 'Visual Builder', - 'Markdown', - 'Tag Cloud', - ]; + describe('chart types', function indexPatternCreation() { + it('should show the correct chart types', function () { + const expectedChartTypes = [ + 'Area', + 'Heat Map', + 'Horizontal Bar', + 'Line', + 'Pie', + 'Vertical Bar', + 'Data Table', + 'Metric', + 'Tile Map', + 'Timelion', + 'Visual Builder', + 'Markdown', + 'Tag Cloud', + ]; - // find all the chart types and make sure there all there - return PageObjects.visualize.getChartTypes() + // find all the chart types and make sure there all there + return PageObjects.visualize.getChartTypes() .then(function testChartTypes(chartTypes) { - PageObjects.common.debug('returned chart types = ' + chartTypes); - PageObjects.common.debug('expected chart types = ' + expectedChartTypes); + log.debug('returned chart types = ' + chartTypes); + log.debug('expected chart types = ' + expectedChartTypes); PageObjects.common.saveScreenshot('Visualize-chart-types'); expect(chartTypes).to.eql(expectedChartTypes); }); + }); }); }); -}); +} diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index c46d202b7b43d..43a52bdb85eb6 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -1,88 +1,89 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('visualize app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickDataTable'); - return PageObjects.visualize.clickDataTable(); - }) - .then(function clickNewSearch() { - PageObjects.common.debug('clickNewSearch'); - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Bucket = Split Rows'); - return PageObjects.visualize.clickBucket('Split Rows'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Histogram'); - return PageObjects.visualize.selectAggregation('Histogram'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = bytes'); - return PageObjects.visualize.selectField('bytes'); - }) - .then(function setInterval() { - PageObjects.common.debug('Interval = 2000'); - return PageObjects.visualize.setNumericInterval('2000'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - }); +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); - bdd.describe('data table', function indexPatternCreation() { - const vizName1 = 'Visualization DataTable'; + describe('visualize app', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.it('should be able to save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickDataTable'); + return PageObjects.visualize.clickDataTable(); }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); + .then(function clickNewSearch() { + log.debug('clickNewSearch'); + return PageObjects.visualize.clickNewSearch(); }) - .then(function () { - return PageObjects.visualize.loadSavedVisualization(vizName1); + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Bucket = Split Rows'); + return PageObjects.visualize.clickBucket('Split Rows'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Histogram'); + return PageObjects.visualize.selectAggregation('Histogram'); + }) + .then(function selectField() { + log.debug('Field = bytes'); + return PageObjects.visualize.selectField('bytes'); + }) + .then(function setInterval() { + log.debug('Interval = 2000'); + return PageObjects.visualize.setNumericInterval('2000'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); }) .then(function () { - return PageObjects.visualize.waitForVisualization(); + return PageObjects.header.waitUntilLoadingHasFinished(); }); }); - bdd.it('should show correct data, take screenshot', function () { - const expectedChartData = [ - '0 2,088', '2,000 2,748', '4,000 2,707', '6,000 2,876', - '8,000 2,863', '10,000 147', '12,000 148', '14,000 129', '16,000 161', '18,000 137' - ]; + describe('data table', function indexPatternCreation() { + const vizName1 = 'Visualization DataTable'; - return PageObjects.common.try(function () { - return PageObjects.visualize.getDataTableData() - .then(function showData(data) { - PageObjects.common.debug(data.split('\n')); - PageObjects.common.saveScreenshot('Visualize-data-table'); - expect(data.split('\n')).to.eql(expectedChartData); + it('should be able to save and load', function () { + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function () { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function () { + return PageObjects.visualize.waitForVisualization(); }); }); - }); + it('should show correct data, take screenshot', function () { + const expectedChartData = [ + '0 2,088', '2,000 2,748', '4,000 2,707', '6,000 2,876', + '8,000 2,863', '10,000 147', '12,000 148', '14,000 129', '16,000 161', '18,000 137' + ]; + + return retry.try(function () { + return PageObjects.visualize.getDataTableData() + .then(function showData(data) { + log.debug(data.split('\n')); + PageObjects.common.saveScreenshot('Visualize-data-table'); + expect(data.split('\n')).to.eql(expectedChartData); + }); + }); + }); + + }); }); -}); +} diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js index 7461e20c33767..ff8205cd52652 100644 --- a/test/functional/apps/visualize/_heatmap_chart.js +++ b/test/functional/apps/visualize/_heatmap_chart.js @@ -1,66 +1,42 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('visualize app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickHeatmapChart'); - return PageObjects.visualize.clickHeatmapChart(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Bucket = X-Axis'); - return PageObjects.visualize.clickBucket('X-Axis'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Date Histogram'); - return PageObjects.visualize.selectAggregation('Date Histogram'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = @timestamp'); - return PageObjects.visualize.selectField('@timestamp'); - }) - // leaving Interval set to Auto - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }) - .then(function waitForVisualization() { - return PageObjects.visualize.waitForVisualization(); - }); - }); +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); - bdd.describe('heatmap chart', function indexPatternCreation() { - const vizName1 = 'Visualization HeatmapChart'; + describe('visualize app', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.it('should save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickHeatmapChart'); + return PageObjects.visualize.clickHeatmapChart(); }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); }) - .then(function () { - return PageObjects.visualize.loadSavedVisualization(vizName1); + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Bucket = X-Axis'); + return PageObjects.visualize.clickBucket('X-Axis'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Date Histogram'); + return PageObjects.visualize.selectAggregation('Date Histogram'); + }) + .then(function selectField() { + log.debug('Field = @timestamp'); + return PageObjects.visualize.selectField('@timestamp'); + }) + // leaving Interval set to Auto + .then(function clickGo() { + return PageObjects.visualize.clickGo(); }) .then(function () { return PageObjects.header.waitUntilLoadingHasFinished(); @@ -70,50 +46,74 @@ bdd.describe('visualize app', function describeIndexTests() { }); }); - bdd.it('should show correct chart, take screenshot', function () { - const expectedChartValues = ['0 - 400', '0 - 400', '400 - 800', '1,200 - 1,600', - '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400', '0 - 400', '0 - 400', '400 - 800', - '1,200 - 1,600', '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400', '0 - 400', '0 - 400', - '400 - 800', '1,200 - 1,600', '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400' ]; + describe('heatmap chart', function indexPatternCreation() { + const vizName1 = 'Visualization HeatmapChart'; - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - return PageObjects.common.sleep(5000) - .then(function () { - return PageObjects.visualize.getHeatmapData(); - }) - .then(function showData(data) { - PageObjects.common.debug('data=' + data); - PageObjects.common.debug('data.length=' + data.length); - PageObjects.common.saveScreenshot('Visualize-heatmap-chart'); - expect(data).to.eql(expectedChartValues); + it('should save and load', function () { + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function () { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); + }) + .then(function waitForVisualization() { + return PageObjects.visualize.waitForVisualization(); + }); }); - }); + it('should show correct chart, take screenshot', function () { + const expectedChartValues = ['0 - 400', '0 - 400', '400 - 800', '1,200 - 1,600', + '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400', '0 - 400', '0 - 400', '400 - 800', + '1,200 - 1,600', '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400', '0 - 400', '0 - 400', + '400 - 800', '1,200 - 1,600', '1,200 - 1,600', '400 - 800', '0 - 400', '0 - 400' ]; - bdd.it('should show correct data', function () { - // this is only the first page of the tabular data. - const expectedChartData = [ 'September 20th 2015, 00:00:00.000 37', - 'September 20th 2015, 03:00:00.000 202', - 'September 20th 2015, 06:00:00.000 740', - 'September 20th 2015, 09:00:00.000 1,437', - 'September 20th 2015, 12:00:00.000 1,371', - 'September 20th 2015, 15:00:00.000 751', - 'September 20th 2015, 18:00:00.000 188', - 'September 20th 2015, 21:00:00.000 31', - 'September 21st 2015, 00:00:00.000 42', - 'September 21st 2015, 03:00:00.000 202' - ]; + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + return PageObjects.common.sleep(5000) + .then(function () { + return PageObjects.visualize.getHeatmapData(); + }) + .then(function showData(data) { + log.debug('data=' + data); + log.debug('data.length=' + data.length); + PageObjects.common.saveScreenshot('Visualize-heatmap-chart'); + expect(data).to.eql(expectedChartValues); + }); + }); - return PageObjects.visualize.collapseChart() - .then(function showData() { - return PageObjects.visualize.getDataTableData(); - }) - .then(function showData(data) { - PageObjects.common.debug(data.split('\n')); - expect(data.trim().split('\n')).to.eql(expectedChartData); + + it('should show correct data', function () { + // this is only the first page of the tabular data. + const expectedChartData = [ 'September 20th 2015, 00:00:00.000 37', + 'September 20th 2015, 03:00:00.000 202', + 'September 20th 2015, 06:00:00.000 740', + 'September 20th 2015, 09:00:00.000 1,437', + 'September 20th 2015, 12:00:00.000 1,371', + 'September 20th 2015, 15:00:00.000 751', + 'September 20th 2015, 18:00:00.000 188', + 'September 20th 2015, 21:00:00.000 31', + 'September 21st 2015, 00:00:00.000 42', + 'September 21st 2015, 03:00:00.000 202' + ]; + + return PageObjects.visualize.collapseChart() + .then(function showData() { + return PageObjects.visualize.getDataTableData(); + }) + .then(function showData(data) { + log.debug(data.split('\n')); + expect(data.trim().split('\n')).to.eql(expectedChartData); + }); }); }); }); -}); +} diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 033d33311ec27..6d30670ab0dbc 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -1,146 +1,147 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('visualize app', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickLineChart'); - return PageObjects.visualize.clickLineChart(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Bucket = Split Chart'); - return PageObjects.visualize.clickBucket('Split Chart'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Terms'); - return PageObjects.visualize.selectAggregation('Terms'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = extension'); - return PageObjects.visualize.selectField('extension.raw'); - }) - .then(function setInterval() { - PageObjects.common.debug('switch from Rows to Columns'); - return PageObjects.visualize.clickColumns(); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - }); - - bdd.describe('line charts', function indexPatternCreation() { - const vizName1 = 'Visualization LineChart'; - - bdd.it('should show correct chart, take screenshot', function () { +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); - // this test only verifies the numerical part of this data - // it could also check the legend to verify the extensions - const expectedChartData = ['jpg 9,109', 'css 2,159', 'png 1,373', 'gif 918', 'php 445']; + describe('visualize app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - // sleep a bit before trying to get the chart data - return PageObjects.common.sleep(3000) + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') .then(function () { - return PageObjects.visualize.getLineChartData('fill="#6eadc1"') - .then(function showData(data) { - PageObjects.common.debug('data=' + data); - PageObjects.common.saveScreenshot('Visualize-line-chart'); - const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% - for (let x = 0; x < data.length; x++) { - PageObjects.common.debug('x=' + x + ' expectedChartData[x].split(\' \')[1] = ' + - (expectedChartData[x].split(' ')[1]).replace(',', '') + ' data[x]=' + data[x] + - ' diff=' + Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x])); - expect(Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x]) < tolerance).to.be.ok(); - } - PageObjects.common.debug('Done'); - }); + log.debug('clickLineChart'); + return PageObjects.visualize.clickLineChart(); + }) + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); + }) + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Bucket = Split Chart'); + return PageObjects.visualize.clickBucket('Split Chart'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Terms'); + return PageObjects.visualize.selectAggregation('Terms'); + }) + .then(function selectField() { + log.debug('Field = extension'); + return PageObjects.visualize.selectField('extension.raw'); + }) + .then(function setInterval() { + log.debug('switch from Rows to Columns'); + return PageObjects.visualize.clickColumns(); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); }); }); + describe('line charts', function indexPatternCreation() { + const vizName1 = 'Visualization LineChart'; - bdd.it('should show correct chart order by Term', function () { + it('should show correct chart, take screenshot', function () { - // this test only verifies the numerical part of this data - // https://github.com/elastic/kibana/issues/8141 - const expectedChartData = ['png 1,373', 'php 445', 'jpg 9,109', 'gif 918', 'css 2,159']; + // this test only verifies the numerical part of this data + // it could also check the legend to verify the extensions + const expectedChartData = ['jpg 9,109', 'css 2,159', 'png 1,373', 'gif 918', 'php 445']; - PageObjects.common.debug('Order By = Term'); - return PageObjects.visualize.selectOrderBy('_term') - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function () { + // sleep a bit before trying to get the chart data + return PageObjects.common.sleep(3000) + .then(function () { return PageObjects.visualize.getLineChartData('fill="#6eadc1"') .then(function showData(data) { - PageObjects.common.debug('data=' + data); + log.debug('data=' + data); PageObjects.common.saveScreenshot('Visualize-line-chart'); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { - PageObjects.common.debug('x=' + x + ' expectedChartData[x].split(\' \')[1] = ' + + log.debug('x=' + x + ' expectedChartData[x].split(\' \')[1] = ' + (expectedChartData[x].split(' ')[1]).replace(',', '') + ' data[x]=' + data[x] + ' diff=' + Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x])); expect(Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x]) < tolerance).to.be.ok(); } - PageObjects.common.debug('Done'); + log.debug('Done'); }); }); }); - }); - bdd.it('should show correct data, ordered by Term', function () { + it('should show correct chart order by Term', function () { + + // this test only verifies the numerical part of this data + // https://github.com/elastic/kibana/issues/8141 + const expectedChartData = ['png 1,373', 'php 445', 'jpg 9,109', 'gif 918', 'css 2,159']; + + log.debug('Order By = Term'); + return PageObjects.visualize.selectOrderBy('_term') + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function () { + return PageObjects.visualize.getLineChartData('fill="#6eadc1"') + .then(function showData(data) { + log.debug('data=' + data); + PageObjects.common.saveScreenshot('Visualize-line-chart'); + const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% + for (let x = 0; x < data.length; x++) { + log.debug('x=' + x + ' expectedChartData[x].split(\' \')[1] = ' + + (expectedChartData[x].split(' ')[1]).replace(',', '') + ' data[x]=' + data[x] + + ' diff=' + Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x])); + expect(Math.abs(expectedChartData[x].split(' ')[1].replace(',', '') - data[x]) < tolerance).to.be.ok(); + } + log.debug('Done'); + }); + }); + }); + }); + - const expectedChartData = ['png 1,373', 'php 445', 'jpg 9,109', 'gif 918', 'css 2,159']; + it('should show correct data, ordered by Term', function () { - return PageObjects.visualize.collapseChart() - .then(function getDataTableData() { - return PageObjects.visualize.getDataTableData(); - }) - .then(function showData(data) { - PageObjects.common.debug(data.split('\n')); - expect(data.trim().split('\n')).to.eql(expectedChartData); - }); - }); + const expectedChartData = ['png 1,373', 'php 445', 'jpg 9,109', 'gif 918', 'css 2,159']; + return PageObjects.visualize.collapseChart() + .then(function getDataTableData() { + return PageObjects.visualize.getDataTableData(); + }) + .then(function showData(data) { + log.debug(data.split('\n')); + expect(data.trim().split('\n')).to.eql(expectedChartData); + }); + }); - bdd.it('should be able to save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); - }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); - }) - .then(function () { - return PageObjects.visualize.loadSavedVisualization(vizName1); - }) - .then(function waitForVisualization() { - return PageObjects.visualize.waitForVisualization(); + it('should be able to save and load', function () { + + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function () { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function waitForVisualization() { + return PageObjects.visualize.waitForVisualization(); + }); }); - }); + }); }); -}); +} diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index 4b771b31dcac6..a6840035bedd6 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -1,261 +1,262 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); -bdd.describe('visualize app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; + describe('visualize app', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickMetric'); - return PageObjects.visualize.clickMetric(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickMetric'); + return PageObjects.visualize.clickMetric(); + }) + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); + }) + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }); }); - }); - bdd.describe('metric chart', function indexPatternCreation() { + describe('metric chart', function indexPatternCreation() { - bdd.it('should show Count', function () { - const expectedCount = ['14,004', 'Count']; + it('should show Count', function () { + const expectedCount = ['14,004', 'Count']; - // initial metric of "Count" is selected by default - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - PageObjects.common.saveScreenshot('Visualize-metric-chart'); - expect(expectedCount).to.eql(metricValue.split('\n')); + // initial metric of "Count" is selected by default + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + PageObjects.common.saveScreenshot('Visualize-metric-chart'); + expect(expectedCount).to.eql(metricValue.split('\n')); + }); }); }); - }); - bdd.it('should show Average', function () { - const avgMachineRam = ['13,104,036,080.615', 'Average machine.ram']; - return PageObjects.visualize.clickMetricEditor() - .then(function () { - PageObjects.common.debug('Aggregation = Average'); - return PageObjects.visualize.selectAggregation('Average'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = machine.ram'); - return PageObjects.visualize.selectField('machine.ram', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(avgMachineRam).to.eql(metricValue.split('\n')); - }); + it('should show Average', function () { + const avgMachineRam = ['13,104,036,080.615', 'Average machine.ram']; + return PageObjects.visualize.clickMetricEditor() + .then(function () { + log.debug('Aggregation = Average'); + return PageObjects.visualize.selectAggregation('Average'); + }) + .then(function selectField() { + log.debug('Field = machine.ram'); + return PageObjects.visualize.selectField('machine.ram', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(avgMachineRam).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Sum', function () { - const sumPhpMemory = ['85,865,880', 'Sum of phpmemory']; - PageObjects.common.debug('Aggregation = Sum'); - return PageObjects.visualize.selectAggregation('Sum') - .then(function selectField() { - PageObjects.common.debug('Field = phpmemory'); - return PageObjects.visualize.selectField('phpmemory', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(sumPhpMemory).to.eql(metricValue.split('\n')); - }); + it('should show Sum', function () { + const sumPhpMemory = ['85,865,880', 'Sum of phpmemory']; + log.debug('Aggregation = Sum'); + return PageObjects.visualize.selectAggregation('Sum') + .then(function selectField() { + log.debug('Field = phpmemory'); + return PageObjects.visualize.selectField('phpmemory', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(sumPhpMemory).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Median', function () { - const medianBytes = ['5,565.263', '50th percentile of bytes']; - // For now, only comparing the text label part of the metric - PageObjects.common.debug('Aggregation = Median'); - return PageObjects.visualize.selectAggregation('Median') - .then(function selectField() { - PageObjects.common.debug('Field = bytes'); - return PageObjects.visualize.selectField('bytes', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - // only comparing the text label! - expect(medianBytes[1]).to.eql(metricValue.split('\n')[1]); - }); + it('should show Median', function () { + const medianBytes = ['5,565.263', '50th percentile of bytes']; + // For now, only comparing the text label part of the metric + log.debug('Aggregation = Median'); + return PageObjects.visualize.selectAggregation('Median') + .then(function selectField() { + log.debug('Field = bytes'); + return PageObjects.visualize.selectField('bytes', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + // only comparing the text label! + expect(medianBytes[1]).to.eql(metricValue.split('\n')[1]); + }); + }); }); }); - }); - bdd.it('should show Min', function () { - const minTimestamp = ['September 20th 2015, 00:00:00.000', 'Min @timestamp']; - PageObjects.common.debug('Aggregation = Min'); - return PageObjects.visualize.selectAggregation('Min') - .then(function selectField() { - PageObjects.common.debug('Field = @timestamp'); - return PageObjects.visualize.selectField('@timestamp', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(minTimestamp).to.eql(metricValue.split('\n')); - }); + it('should show Min', function () { + const minTimestamp = ['September 20th 2015, 00:00:00.000', 'Min @timestamp']; + log.debug('Aggregation = Min'); + return PageObjects.visualize.selectAggregation('Min') + .then(function selectField() { + log.debug('Field = @timestamp'); + return PageObjects.visualize.selectField('@timestamp', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(minTimestamp).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Max', function () { - const maxRelatedContentArticleModifiedTime = ['April 4th 2015, 00:54:41.000', 'Max relatedContent.article:modified_time']; - PageObjects.common.debug('Aggregation = Max'); - return PageObjects.visualize.selectAggregation('Max') - .then(function selectField() { - PageObjects.common.debug('Field = relatedContent.article:modified_time'); - return PageObjects.visualize.selectField('relatedContent.article:modified_time', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(maxRelatedContentArticleModifiedTime).to.eql(metricValue.split('\n')); - }); + it('should show Max', function () { + const maxRelatedContentArticleModifiedTime = ['April 4th 2015, 00:54:41.000', 'Max relatedContent.article:modified_time']; + log.debug('Aggregation = Max'); + return PageObjects.visualize.selectAggregation('Max') + .then(function selectField() { + log.debug('Field = relatedContent.article:modified_time'); + return PageObjects.visualize.selectField('relatedContent.article:modified_time', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(maxRelatedContentArticleModifiedTime).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Standard Deviation', function () { - const standardDeviationBytes = [ - '-1,435.138', 'Lower Standard Deviation of bytes', - '12,889.766', 'Upper Standard Deviation of bytes' - ]; - PageObjects.common.debug('Aggregation = Standard Deviation'); - return PageObjects.visualize.selectAggregation('Standard Deviation') - .then(function selectField() { - PageObjects.common.debug('Field = bytes'); - return PageObjects.visualize.selectField('bytes', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(standardDeviationBytes).to.eql(metricValue.split('\n')); - }); + it('should show Standard Deviation', function () { + const standardDeviationBytes = [ + '-1,435.138', 'Lower Standard Deviation of bytes', + '12,889.766', 'Upper Standard Deviation of bytes' + ]; + log.debug('Aggregation = Standard Deviation'); + return PageObjects.visualize.selectAggregation('Standard Deviation') + .then(function selectField() { + log.debug('Field = bytes'); + return PageObjects.visualize.selectField('bytes', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(standardDeviationBytes).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Unique Count', function () { - const uniqueCountClientip = ['1,000', 'Unique count of clientip']; - PageObjects.common.debug('Aggregation = Unique Count'); - return PageObjects.visualize.selectAggregation('Unique Count') - .then(function selectField() { - PageObjects.common.debug('Field = clientip'); - return PageObjects.visualize.selectField('clientip', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { + it('should show Unique Count', function () { + const uniqueCountClientip = ['1,000', 'Unique count of clientip']; + log.debug('Aggregation = Unique Count'); + return PageObjects.visualize.selectAggregation('Unique Count') + .then(function selectField() { + log.debug('Field = clientip'); + return PageObjects.visualize.selectField('clientip', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(uniqueCountClientip).to.eql(metricValue.split('\n')); + }); + }); + }) + .then(function () { return PageObjects.visualize.getMetric() .then(function (metricValue) { + log.debug('metricValue=' + metricValue.split('\n')); expect(uniqueCountClientip).to.eql(metricValue.split('\n')); }); }); - }) - .then(function () { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - PageObjects.common.debug('metricValue=' + metricValue.split('\n')); - expect(uniqueCountClientip).to.eql(metricValue.split('\n')); - }); }); - }); - bdd.it('should show Percentiles', function () { - const percentileMachineRam = [ - '2,147,483,648', '1st percentile of machine.ram', - '3,221,225,472', '5th percentile of machine.ram', - '7,516,192,768', '25th percentile of machine.ram', - '12,884,901,888', '50th percentile of machine.ram', - '18,253,611,008', '75th percentile of machine.ram', - '32,212,254,720', '95th percentile of machine.ram', - '32,212,254,720', '99th percentile of machine.ram' - ]; + it('should show Percentiles', function () { + const percentileMachineRam = [ + '2,147,483,648', '1st percentile of machine.ram', + '3,221,225,472', '5th percentile of machine.ram', + '7,516,192,768', '25th percentile of machine.ram', + '12,884,901,888', '50th percentile of machine.ram', + '18,253,611,008', '75th percentile of machine.ram', + '32,212,254,720', '95th percentile of machine.ram', + '32,212,254,720', '99th percentile of machine.ram' + ]; - PageObjects.common.debug('Aggregation = Percentiles'); - return PageObjects.visualize.selectAggregation('Percentiles') - .then(function selectField() { - PageObjects.common.debug('Field = machine.ram'); - return PageObjects.visualize.selectField('machine.ram', 'metrics'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(percentileMachineRam).to.eql(metricValue.split('\n')); - }); + log.debug('Aggregation = Percentiles'); + return PageObjects.visualize.selectAggregation('Percentiles') + .then(function selectField() { + log.debug('Field = machine.ram'); + return PageObjects.visualize.selectField('machine.ram', 'metrics'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(percentileMachineRam).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); - bdd.it('should show Percentile Ranks', function () { - const percentileRankBytes = [ '2.036%', 'Percentile rank 99 of "memory"']; - PageObjects.common.debug('Aggregation = Percentile Ranks'); - return PageObjects.visualize.selectAggregation('Percentile Ranks') - .then(function selectField() { - PageObjects.common.debug('Field = bytes'); - return PageObjects.visualize.selectField('memory', 'metrics'); - }) - .then(function selectField() { - PageObjects.common.debug('Values = 99'); - return PageObjects.visualize.setValue('99'); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.try(function tryingForTime() { - return PageObjects.visualize.getMetric() - .then(function (metricValue) { - expect(percentileRankBytes).to.eql(metricValue.split('\n')); - }); + it('should show Percentile Ranks', function () { + const percentileRankBytes = [ '2.036%', 'Percentile rank 99 of "memory"']; + log.debug('Aggregation = Percentile Ranks'); + return PageObjects.visualize.selectAggregation('Percentile Ranks') + .then(function selectField() { + log.debug('Field = bytes'); + return PageObjects.visualize.selectField('memory', 'metrics'); + }) + .then(function selectField() { + log.debug('Values = 99'); + return PageObjects.visualize.setValue('99'); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return retry.try(function tryingForTime() { + return PageObjects.visualize.getMetric() + .then(function (metricValue) { + expect(percentileRankBytes).to.eql(metricValue.split('\n')); + }); + }); }); }); - }); + }); }); -}); +} diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js index 0496700b70206..e38af52ef80c2 100644 --- a/test/functional/apps/visualize/_pie_chart.js +++ b/test/functional/apps/visualize/_pie_chart.js @@ -1,111 +1,111 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('visualize app', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickPieChart'); - return PageObjects.visualize.clickPieChart(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function () { - PageObjects.common.debug('select bucket Split Slices'); - return PageObjects.visualize.clickBucket('Split Slices'); - }) - .then(function () { - PageObjects.common.debug('Click aggregation Histogram'); - return PageObjects.visualize.selectAggregation('Histogram'); - }) - .then(function () { - PageObjects.common.debug('Click field memory'); - return PageObjects.visualize.selectField('memory'); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }) - .then(function sleep() { - return PageObjects.common.sleep(1003); - }) - .then(function () { - PageObjects.common.debug('setNumericInterval 4000'); - return PageObjects.visualize.setNumericInterval('40000'); - }) - .then(function () { - PageObjects.common.debug('clickGo'); - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - }); - +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']); - bdd.describe('pie chart', function indexPatternCreation() { - const vizName1 = 'Visualization PieChart'; + describe('visualize app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.it('should save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickPieChart'); + return PageObjects.visualize.clickPieChart(); + }) + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function () { + log.debug('select bucket Split Slices'); + return PageObjects.visualize.clickBucket('Split Slices'); }) .then(function () { - return PageObjects.visualize.loadSavedVisualization(vizName1); + log.debug('Click aggregation Histogram'); + return PageObjects.visualize.selectAggregation('Histogram'); }) - .then(function waitForVisualization() { - return PageObjects.visualize.waitForVisualization(); + .then(function () { + log.debug('Click field memory'); + return PageObjects.visualize.selectField('memory'); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); }) - // sleep a bit before trying to get the pie chart data below .then(function sleep() { - return PageObjects.common.sleep(2000); + return PageObjects.common.sleep(1003); + }) + .then(function () { + log.debug('setNumericInterval 4000'); + return PageObjects.visualize.setNumericInterval('40000'); + }) + .then(function () { + log.debug('clickGo'); + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); }); }); - bdd.it('should show 10 slices in pie chart, take screenshot', function () { - const expectedPieChartSliceCount = 10; - return PageObjects.visualize.getPieChartData() - .then(function (pieData) { - PageObjects.common.debug('pieData.length = ' + pieData.length); - PageObjects.common.saveScreenshot('Visualize-pie-chart'); - expect(pieData.length).to.be(expectedPieChartSliceCount); + describe('pie chart', function indexPatternCreation() { + const vizName1 = 'Visualization PieChart'; + + it('should save and load', function () { + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function () { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function waitForVisualization() { + return PageObjects.visualize.waitForVisualization(); + }) + // sleep a bit before trying to get the pie chart data below + .then(function sleep() { + return PageObjects.common.sleep(2000); + }); }); - }); - bdd.it('should show correct data', function () { - const expectedTableData = [ '0 55', '40,000 50', '80,000 41', '120,000 43', - '160,000 44', '200,000 40', '240,000 46', '280,000 39', '320,000 40', '360,000 47' - ]; + it('should show 10 slices in pie chart, take screenshot', function () { + const expectedPieChartSliceCount = 10; - return PageObjects.visualize.collapseChart() - .then(function () { - return PageObjects.settings.setPageSize('All'); - }) - .then(function getDataTableData() { - return PageObjects.visualize.getDataTableData(); - }) - .then(function showData(data) { - PageObjects.common.debug(data.split('\n')); - expect(data.trim().split('\n')).to.eql(expectedTableData); + return PageObjects.visualize.getPieChartData() + .then(function (pieData) { + log.debug('pieData.length = ' + pieData.length); + PageObjects.common.saveScreenshot('Visualize-pie-chart'); + expect(pieData.length).to.be(expectedPieChartSliceCount); + }); + }); + + it('should show correct data', function () { + const expectedTableData = [ '0 55', '40,000 50', '80,000 41', '120,000 43', + '160,000 44', '200,000 40', '240,000 46', '280,000 39', '320,000 40', '360,000 47' + ]; + + return PageObjects.visualize.collapseChart() + .then(function () { + return PageObjects.settings.setPageSize('All'); + }) + .then(function getDataTableData() { + return PageObjects.visualize.getDataTableData(); + }) + .then(function showData(data) { + log.debug(data.split('\n')); + expect(data.trim().split('\n')).to.eql(expectedTableData); + }); }); }); }); -}); +} diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index 8198d875f7657..3681f90201d60 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -1,192 +1,192 @@ import expect from 'expect.js'; -import { - bdd, -} from '../../../support'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const pointSeriesVis = getService('pointSeriesVis'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); -import PageObjects from '../../../support/page_objects'; + describe('visualize app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; -bdd.describe('visualize app', function describeIndexTests() { - bdd.before(function () { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickLineChart'); - return PageObjects.visualize.clickLineChart(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Bucket = X-Axis'); - return PageObjects.visualize.clickBucket('X-Axis'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Date Histogram'); - return PageObjects.visualize.selectAggregation('Date Histogram'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = @timestamp'); - return PageObjects.visualize.selectField('@timestamp'); - }) - // add another metrics - .then(function clickAddMetrics() { - PageObjects.common.debug('Add Metric'); - return PageObjects.visualize.clickAddMetric(); - }) - .then(function () { - PageObjects.common.debug('Metric = Value Axis'); - return PageObjects.visualize.clickBucket('Y-Axis'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Average'); - return PageObjects.visualize.selectAggregation('Average'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = memory'); - return PageObjects.visualize.selectField('machine.ram', 'metrics'); - }) - // go to options page - .then(function gotoAxisOptions() { - PageObjects.common.debug('Going to axis options'); - return PageObjects.visualizeOptions.clickAxisOptions(); - }) - // add another value axis - .then(function addAxis() { - PageObjects.common.debug('adding axis'); - return PageObjects.visualizeOptions.clickAddAxis(); - }) - // set average count to use second value axis - .then(function setAxis() { - return PageObjects.visualizeOptions.toggleCollapsibleTitle('Average machine.ram') - .then(function () { - PageObjects.common.debug('Average memory value axis - ValueAxis-2'); - return PageObjects.visualizeOptions.setSeriesAxis(1, 'ValueAxis-2'); - }); - }) - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.isGlobalLoadingIndicatorHidden(); + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickLineChart'); + return PageObjects.visualize.clickLineChart(); + }) + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); + }) + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Bucket = X-Axis'); + return PageObjects.visualize.clickBucket('X-Axis'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Date Histogram'); + return PageObjects.visualize.selectAggregation('Date Histogram'); + }) + .then(function selectField() { + log.debug('Field = @timestamp'); + return PageObjects.visualize.selectField('@timestamp'); + }) + // add another metrics + .then(function clickAddMetrics() { + log.debug('Add Metric'); + return PageObjects.visualize.clickAddMetric(); + }) + .then(function () { + log.debug('Metric = Value Axis'); + return PageObjects.visualize.clickBucket('Y-Axis'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Average'); + return PageObjects.visualize.selectAggregation('Average'); + }) + .then(function selectField() { + log.debug('Field = memory'); + return PageObjects.visualize.selectField('machine.ram', 'metrics'); + }) + // go to options page + .then(function gotoAxisOptions() { + log.debug('Going to axis options'); + return pointSeriesVis.clickAxisOptions(); + }) + // add another value axis + .then(function addAxis() { + log.debug('adding axis'); + return pointSeriesVis.clickAddAxis(); + }) + // set average count to use second value axis + .then(function setAxis() { + return pointSeriesVis.toggleCollapsibleTitle('Average machine.ram') + .then(function () { + log.debug('Average memory value axis - ValueAxis-2'); + return pointSeriesVis.setSeriesAxis(1, 'ValueAxis-2'); + }); + }) + .then(function clickGo() { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.header.isGlobalLoadingIndicatorHidden(); + }); }); - }); - bdd.describe('secondary value axis', function () { + describe('secondary value axis', function () { - bdd.it('should show correct chart, take screenshot', function () { - const expectedChartValues = [ - [ 37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, 683, - 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 ], - [ 14018296036, 13284815935, 13198764883, 13093365683, 13067752146, 12976598848, - 13561826081, 14339648875, 14011021362, 12775336396, 13304506791, 12988890398, - 13143466970, 13244378772, 12154757448, 15907286281, 13757317120, 13022240959, - 12807319386, 13375732998, 13190755620, 12627508458, 12731510199, 13153337344 ], - ]; + it('should show correct chart, take screenshot', function () { + const expectedChartValues = [ + [ 37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, 683, + 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 ], + [ 14018296036, 13284815935, 13198764883, 13093365683, 13067752146, 12976598848, + 13561826081, 14339648875, 14011021362, 12775336396, 13304506791, 12988890398, + 13143466970, 13244378772, 12154757448, 15907286281, 13757317120, 13022240959, + 12807319386, 13375732998, 13190755620, 12627508458, 12731510199, 13153337344 ], + ]; - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - return PageObjects.common.sleep(2000) - .then(function () { - return PageObjects.visualize.getLineChartData('fill="#6eadc1"'); - }) - .then(function showData(data) { - PageObjects.common.debug('count data=' + data); - PageObjects.common.debug('data.length=' + data.length); - PageObjects.common.saveScreenshot('Visualize-secondary-value-axis'); - expect(data).to.eql(expectedChartValues[0]); - }) - .then(function () { - return PageObjects.visualize.getLineChartData('fill="#57c17b"', 'ValueAxis-2'); - }) - .then(function showData(data) { - PageObjects.common.debug('average memory data=' + data); - PageObjects.common.debug('data.length=' + data.length); - expect(data).to.eql(expectedChartValues[1]); - }); - }); + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + return PageObjects.common.sleep(2000) + .then(function () { + return PageObjects.visualize.getLineChartData('fill="#6eadc1"'); + }) + .then(function showData(data) { + log.debug('count data=' + data); + log.debug('data.length=' + data.length); + PageObjects.common.saveScreenshot('Visualize-secondary-value-axis'); + expect(data).to.eql(expectedChartValues[0]); + }) + .then(function () { + return PageObjects.visualize.getLineChartData('fill="#57c17b"', 'ValueAxis-2'); + }) + .then(function showData(data) { + log.debug('average memory data=' + data); + log.debug('data.length=' + data.length); + expect(data).to.eql(expectedChartValues[1]); + }); + }); - bdd.it('should put secondary axis on the right', function () { - PageObjects.visualizeOptions.getRightValueAxes().then(length => { - expect(length).to.be(1); + it('should put secondary axis on the right', function () { + pointSeriesVis.getRightValueAxes().then(length => { + expect(length).to.be(1); + }); }); }); - }); - bdd.describe('multiple chart types', function () { - bdd.it('should change average series type to histogram', function () { - return PageObjects.visualizeOptions.toggleCollapsibleTitle('RightAxis-1') - .then(function () { - return PageObjects.visualizeOptions.setSeriesType(1, 'bar'); - }) - .then(function () { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.sleep(2000); - }) - .then(function checkSeriesTypes() { - PageObjects.visualizeOptions.getHistogramSeries().then(length => { - expect(length).to.be(1); + describe('multiple chart types', function () { + it('should change average series type to histogram', function () { + return pointSeriesVis.toggleCollapsibleTitle('RightAxis-1') + .then(function () { + return pointSeriesVis.setSeriesType(1, 'bar'); + }) + .then(function () { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.common.sleep(2000); + }) + .then(function checkSeriesTypes() { + pointSeriesVis.getHistogramSeries().then(length => { + expect(length).to.be(1); + }); }); - }); + }); }); - }); - bdd.describe('grid lines', function () { - bdd.before(function () { - return PageObjects.visualizeOptions.clickOptions(); - }); + describe('grid lines', function () { + before(function () { + return pointSeriesVis.clickOptions(); + }); - bdd.it('should show category grid lines', function () { - return PageObjects.visualizeOptions.toggleGridCategoryLines() - .then(function () { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.sleep(2000); - }) - .then(function () { - return PageObjects.visualizeOptions.getGridLines(); - }) - .then(function checkGridLines(gridLines) { - expect(gridLines.length).to.be(9); - gridLines.forEach(gridLine => { - expect(gridLine.y).to.be(0); - }); + it('should show category grid lines', function () { + return pointSeriesVis.toggleGridCategoryLines() + .then(function () { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.common.sleep(2000); + }) + .then(function () { + return pointSeriesVis.getGridLines(); + }) + .then(function checkGridLines(gridLines) { + expect(gridLines.length).to.be(9); + gridLines.forEach(gridLine => { + expect(gridLine.y).to.be(0); + }); - }); - }); + }); + }); - bdd.it('should show value axis grid lines', function () { - return PageObjects.visualizeOptions.setGridValueAxis('ValueAxis-2') - .then(function () { - return PageObjects.visualizeOptions.toggleGridCategoryLines(); - }) - .then(function () { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.common.sleep(5000); - }) - .then(function () { - return PageObjects.visualizeOptions.getGridLines(); - }) - .then(function checkGridLines(gridLines) { - expect(gridLines.length).to.be(9); - gridLines.forEach(gridLine => { - expect(gridLine.x).to.be(0); + it('should show value axis grid lines', function () { + return pointSeriesVis.setGridValueAxis('ValueAxis-2') + .then(function () { + return pointSeriesVis.toggleGridCategoryLines(); + }) + .then(function () { + return PageObjects.visualize.clickGo(); + }) + .then(function () { + return PageObjects.common.sleep(5000); + }) + .then(function () { + return pointSeriesVis.getGridLines(); + }) + .then(function checkGridLines(gridLines) { + expect(gridLines.length).to.be(9); + gridLines.forEach(gridLine => { + expect(gridLine.x).to.be(0); + }); }); - }); + }); }); - }); -}); + }); +} diff --git a/test/functional/apps/visualize/_shared_item.js b/test/functional/apps/visualize/_shared_item.js index e89cd8848848b..5e9c1b1fbd2f1 100644 --- a/test/functional/apps/visualize/_shared_item.js +++ b/test/functional/apps/visualize/_shared_item.js @@ -1,34 +1,34 @@ import expect from 'expect.js'; -import { - bdd -} from '../../../support'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize']); -import PageObjects from '../../../support/page_objects'; + describe('visualize app', function describeIndexTests() { -bdd.describe('visualize app', function describeIndexTests() { - - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToApp('visualize'); - }); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToApp('visualize'); + }); - bdd.describe('data-shared-item', function indexPatternCreation() { + describe('data-shared-item', function indexPatternCreation() { - bdd.it('should have the correct data-shared-item title and description', function () { - const expected = { - title: 'Shared-Item Visualization AreaChart', - description: 'AreaChart' - }; + it('should have the correct data-shared-item title and description', function () { + const expected = { + title: 'Shared-Item Visualization AreaChart', + description: 'AreaChart' + }; - return PageObjects.visualize.clickVisualizationByName('Shared-Item Visualization AreaChart') - .then (() => PageObjects.common.try(function () { + return PageObjects.visualize.clickVisualizationByName('Shared-Item Visualization AreaChart') + .then(() => retry.try(function () { return PageObjects.common.getSharedItemTitleAndDescription() .then(({ title, description }) => { expect(title).to.eql(expected.title); expect(description).to.eql(expected.description); }); })); + }); }); }); -}); +} diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js index 25aa2c21dc2eb..4a488fb99d177 100644 --- a/test/functional/apps/visualize/_tile_map.js +++ b/test/functional/apps/visualize/_tile_map.js @@ -1,39 +1,39 @@ import expect from 'expect.js'; -import { bdd } from '../../../support'; +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']); -import PageObjects from '../../../support/page_objects'; + describe('visualize app', function describeIndexTests() { + before(function () { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; -bdd.describe('visualize app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - bdd.before(function () { - - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') .then(function () { - PageObjects.common.debug('clickTileMap'); + log.debug('clickTileMap'); return PageObjects.visualize.clickTileMap(); }) .then(function () { return PageObjects.visualize.clickNewSearch(); }) .then(function () { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); return PageObjects.header.setAbsoluteRange(fromTime, toTime); }) .then(function () { - PageObjects.common.debug('select bucket Geo Coordinates'); + log.debug('select bucket Geo Coordinates'); return PageObjects.visualize.clickBucket('Geo Coordinates'); }) .then(function () { - PageObjects.common.debug('Click aggregation Geohash'); + log.debug('Click aggregation Geohash'); return PageObjects.visualize.selectAggregation('Geohash'); }) .then(function () { - PageObjects.common.debug('Click field geo.coordinates'); - return PageObjects.common.try(function tryingForTime() { + log.debug('Click field geo.coordinates'); + return retry.try(function tryingForTime() { return PageObjects.visualize.selectField('geo.coordinates'); }); }) @@ -43,20 +43,19 @@ bdd.describe('visualize app', function describeIndexTests() { .then(function () { return PageObjects.header.waitUntilLoadingHasFinished(); }); - }); - - bdd.describe('tile map chart', function indexPatternCreation() { + }); - bdd.it('should show correct tile map data on default zoom level', function () { - const expectedTableData = ['9 5,787 { "lat": 37.22448418632405, "lon": -103.01935195013255 }', - 'd 5,600 { "lat": 37.44271478370398, "lon": -81.72692197253595 }', - 'c 1,319 { "lat": 47.72720855392425, "lon": -109.84745063951028 }', - 'b 999 { "lat": 62.04130042948433, "lon": -155.28087269195967 }', - 'f 187 { "lat": 45.656166475784175, "lon": -82.45831044201545 }', - '8 108 { "lat": 18.85260305600241, "lon": -156.5148810390383 }']; + describe('tile map chart', function indexPatternCreation() { + it('should show correct tile map data on default zoom level', function () { + const expectedTableData = ['9 5,787 { "lat": 37.22448418632405, "lon": -103.01935195013255 }', + 'd 5,600 { "lat": 37.44271478370398, "lon": -81.72692197253595 }', + 'c 1,319 { "lat": 47.72720855392425, "lon": -109.84745063951028 }', + 'b 999 { "lat": 62.04130042948433, "lon": -155.28087269195967 }', + 'f 187 { "lat": 45.656166475784175, "lon": -82.45831044201545 }', + '8 108 { "lat": 18.85260305600241, "lon": -156.5148810390383 }']; - return PageObjects.visualize.collapseChart() + return PageObjects.visualize.collapseChart() .then(function () { return PageObjects.visualize.clickMapZoomOut(); }) @@ -65,119 +64,119 @@ bdd.describe('visualize app', function describeIndexTests() { }) .then(function getDataTableData() { return PageObjects.visualize.getDataTableData() - .then(function showData(data) { - expect(data.trim().split('\n')).to.eql(expectedTableData); - return PageObjects.visualize.collapseChart(); - }); + .then(function showData(data) { + expect(data.trim().split('\n')).to.eql(expectedTableData); + return PageObjects.visualize.collapseChart(); + }); }); - }); - + }); - bdd.it('should not be able to zoom out beyond 0', function () { - return PageObjects.visualize.getMapZoomOutEnabled() - // we can tell we're at level 1 because zoom out is disabled + it('should not be able to zoom out beyond 0', function () { + return PageObjects.visualize.getMapZoomOutEnabled() + // we can tell we're at level 1 because zoom out is disabled .then(function () { - return PageObjects.common.try(function tryingForTime() { + return retry.try(function tryingForTime() { return PageObjects.visualize.getMapZoomOutEnabled() - .then(function (enabled) { - //should be able to zoom more as current config has 0 as min level. - expect(enabled).to.be(false); - }); + .then(function (enabled) { + //should be able to zoom more as current config has 0 as min level. + expect(enabled).to.be(false); + }); }); }) .then(function takeScreenshot() { - PageObjects.common.debug('Take screenshot (success)'); + log.debug('Take screenshot (success)'); PageObjects.common.saveScreenshot('map-at-zoom-0'); }); - }); + }); - bdd.it('Fit data bounds should zoom to level 3', function () { - const expectedPrecision2ZoomCircles = [{ color: '#750000', radius: 192 }, - { color: '#750000', radius: 191 }, - { color: '#750000', radius: 177 }, - { color: '#a40000', radius: 168 }, - { color: '#a40000', radius: 167 }, - { color: '#a40000', radius: 159 }, - { color: '#a40000', radius: 156 }, - { color: '#b45100', radius: 136 }, - { color: '#b67501', radius: 111 }, - { color: '#b67501', radius: 109 }, - { color: '#b67501', radius: 108 }, - { color: '#b67501', radius: 104 }, - { color: '#b67501', radius: 101 }, - { color: '#b67501', radius: 101 }, - { color: '#b99939', radius: 84 }, - { color: '#b99939', radius: 84 }, - { color: '#b99939', radius: 74 }, - { color: '#b99939', radius: 73 }, - { color: '#b99939', radius: 73 }, - { color: '#b99939', radius: 66 }, - { color: '#b99939', radius: 60 }, - { color: '#b99939', radius: 57 }, - { color: '#b99939', radius: 57 }, - { color: '#b99939', radius: 47 }, - { color: '#b99939', radius: 43 }, - { color: '#b99939', radius: 43 }, - { color: '#b99939', radius: 43 }, - { color: '#b99939', radius: 38 }, - { color: '#b99939', radius: 36 }, - { color: '#b99939', radius: 35 }, - { color: '#b99939', radius: 34 }, - { color: '#b99939', radius: 34 }, - { color: '#b99939', radius: 31 }, - { color: '#b99939', radius: 30 }, - { color: '#b99939', radius: 28 }, - { color: '#b99939', radius: 27 }, - { color: '#b99939', radius: 24 }, - { color: '#b99939', radius: 22 }, - { color: '#b99939', radius: 19 }, - { color: '#b99939', radius: 19 }, - { color: '#b99939', radius: 15 }, - { color: '#b99939', radius: 15 }, - { color: '#b99939', radius: 15 }, - { color: '#b99939', radius: 12 }, - { color: '#b99939', radius: 9 }, - { color: '#b99939', radius: 9 } - ]; + it('Fit data bounds should zoom to level 3', function () { + const expectedPrecision2ZoomCircles = [ + { color: '#750000', radius: 192 }, + { color: '#750000', radius: 191 }, + { color: '#750000', radius: 177 }, + { color: '#a40000', radius: 168 }, + { color: '#a40000', radius: 167 }, + { color: '#a40000', radius: 159 }, + { color: '#a40000', radius: 156 }, + { color: '#b45100', radius: 136 }, + { color: '#b67501', radius: 111 }, + { color: '#b67501', radius: 109 }, + { color: '#b67501', radius: 108 }, + { color: '#b67501', radius: 104 }, + { color: '#b67501', radius: 101 }, + { color: '#b67501', radius: 101 }, + { color: '#b99939', radius: 84 }, + { color: '#b99939', radius: 84 }, + { color: '#b99939', radius: 74 }, + { color: '#b99939', radius: 73 }, + { color: '#b99939', radius: 73 }, + { color: '#b99939', radius: 66 }, + { color: '#b99939', radius: 60 }, + { color: '#b99939', radius: 57 }, + { color: '#b99939', radius: 57 }, + { color: '#b99939', radius: 47 }, + { color: '#b99939', radius: 43 }, + { color: '#b99939', radius: 43 }, + { color: '#b99939', radius: 43 }, + { color: '#b99939', radius: 38 }, + { color: '#b99939', radius: 36 }, + { color: '#b99939', radius: 35 }, + { color: '#b99939', radius: 34 }, + { color: '#b99939', radius: 34 }, + { color: '#b99939', radius: 31 }, + { color: '#b99939', radius: 30 }, + { color: '#b99939', radius: 28 }, + { color: '#b99939', radius: 27 }, + { color: '#b99939', radius: 24 }, + { color: '#b99939', radius: 22 }, + { color: '#b99939', radius: 19 }, + { color: '#b99939', radius: 19 }, + { color: '#b99939', radius: 15 }, + { color: '#b99939', radius: 15 }, + { color: '#b99939', radius: 15 }, + { color: '#b99939', radius: 12 }, + { color: '#b99939', radius: 9 }, + { color: '#b99939', radius: 9 } + ]; - return PageObjects.visualize.clickMapFitDataBounds() + return PageObjects.visualize.clickMapFitDataBounds() .then(function () { return PageObjects.visualize.getTileMapData(); }) .then(function (data) { expect(data).to.eql(expectedPrecision2ZoomCircles); }); - }); + }); - /* - ** NOTE: Since we don't have a reliable way to know the zoom level, we can - ** check some data after we save the viz, then zoom in and check that the data - ** changed, then open the saved viz and check that it's back to the original data. - */ - bdd.it('should save with zoom level and load, take screenshot', function () { - const expectedTableData = [ 'dr4 127 { "lat": 40.142432276496855, "lon": -75.17097956302949 }', - 'dr7 92 { "lat": 41.48015560278588, "lon": -73.90037568609999 }', - '9q5 91 { "lat": 34.293431888365156, "lon": -118.57068410102319 }', - '9qc 89 { "lat": 38.645468642830515, "lon": -121.59105310990904 }', - 'drk 87 { "lat": 41.38891646156794, "lon": -72.50977680472464 }', - 'dps 82 { "lat": 42.79333563657796, "lon": -83.55129436180904 }', - 'dph 82 { "lat": 40.03466797526926, "lon": -83.6603344113725 }', - 'dp3 79 { "lat": 41.68207621697006, "lon": -87.98703811709073 }', - 'dpe 78 { "lat": 42.83740988287788, "lon": -85.13176125187714 }', - 'dp8 77 { "lat": 43.00976751178697, "lon": -89.27605860007854 }' ]; - const expectedTableDataZoomed = [ 'dr5r 21 { "lat": 40.73313889359789, "lon": -74.00737997410553 }', - 'dps8 20 { "lat": 42.25258858362213, "lon": -83.4615091625601 }', - '9q5b 19 { "lat": 33.8619567100939, "lon": -118.28354520723224 }', - 'b6uc 17 { "lat": 60.721656321274004, "lon": -161.86279475141097 }', - '9y63 17 { "lat": 35.48034298178904, "lon": -97.90940423550852 }', - 'c20g 16 { "lat": 45.59211885672994, "lon": -122.47455088770948 }', - 'dqfz 15 { "lat": 39.24278838559985, "lon": -74.69487586989999 }', - 'dr8h 14 { "lat": 42.9455179042582, "lon": -78.65373932623437 }', - 'dp8p 14 { "lat": 43.52336289028504, "lon": -89.84673104515034 }', - 'dp3k 14 { "lat": 41.569707432229606, "lon": -88.12707824898618 }' ]; - const vizName1 = 'Visualization TileMap'; + /* + ** NOTE: Since we don't have a reliable way to know the zoom level, we can + ** check some data after we save the viz, then zoom in and check that the data + ** changed, then open the saved viz and check that it's back to the original data. + */ + it('should save with zoom level and load, take screenshot', function () { + const expectedTableData = [ 'dr4 127 { "lat": 40.142432276496855, "lon": -75.17097956302949 }', + 'dr7 92 { "lat": 41.48015560278588, "lon": -73.90037568609999 }', + '9q5 91 { "lat": 34.293431888365156, "lon": -118.57068410102319 }', + '9qc 89 { "lat": 38.645468642830515, "lon": -121.59105310990904 }', + 'drk 87 { "lat": 41.38891646156794, "lon": -72.50977680472464 }', + 'dps 82 { "lat": 42.79333563657796, "lon": -83.55129436180904 }', + 'dph 82 { "lat": 40.03466797526926, "lon": -83.6603344113725 }', + 'dp3 79 { "lat": 41.68207621697006, "lon": -87.98703811709073 }', + 'dpe 78 { "lat": 42.83740988287788, "lon": -85.13176125187714 }', + 'dp8 77 { "lat": 43.00976751178697, "lon": -89.27605860007854 }' ]; + const expectedTableDataZoomed = [ 'dr5r 21 { "lat": 40.73313889359789, "lon": -74.00737997410553 }', + 'dps8 20 { "lat": 42.25258858362213, "lon": -83.4615091625601 }', + '9q5b 19 { "lat": 33.8619567100939, "lon": -118.28354520723224 }', + 'b6uc 17 { "lat": 60.721656321274004, "lon": -161.86279475141097 }', + '9y63 17 { "lat": 35.48034298178904, "lon": -97.90940423550852 }', + 'c20g 16 { "lat": 45.59211885672994, "lon": -122.47455088770948 }', + 'dqfz 15 { "lat": 39.24278838559985, "lon": -74.69487586989999 }', + 'dr8h 14 { "lat": 42.9455179042582, "lon": -78.65373932623437 }', + 'dp8p 14 { "lat": 43.52336289028504, "lon": -89.84673104515034 }', + 'dp3k 14 { "lat": 41.569707432229606, "lon": -88.12707824898618 }' ]; + const vizName1 = 'Visualization TileMap'; - return PageObjects.visualize.clickMapZoomIn() + return PageObjects.visualize.clickMapZoomIn() .then(function () { return PageObjects.visualize.clickMapZoomIn(); }) @@ -185,7 +184,7 @@ bdd.describe('visualize app', function describeIndexTests() { return PageObjects.visualize.saveVisualization(vizName1); }) .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); + log.debug('Saved viz message = ' + message); expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); }) .then(function testVisualizeWaitForToastMessageGone() { @@ -196,7 +195,7 @@ bdd.describe('visualize app', function describeIndexTests() { }) // we're not selecting page size all, so we only have to verify the first page of data .then(function getDataTableData() { - PageObjects.common.debug('first get the zoom level 5 page data and verify it'); + log.debug('first get the zoom level 5 page data and verify it'); return PageObjects.visualize.getDataTableData(); }) .then(function showData(data) { @@ -211,7 +210,7 @@ bdd.describe('visualize app', function describeIndexTests() { return PageObjects.visualize.collapseChart(); }) .then(function getDataTableData() { - PageObjects.common.debug('second get the zoom level 6 page data and verify it'); + log.debug('second get the zoom level 6 page data and verify it'); return PageObjects.visualize.getDataTableData(); }) .then(function showData(data) { @@ -233,7 +232,7 @@ bdd.describe('visualize app', function describeIndexTests() { return PageObjects.visualize.collapseChart(); }) .then(function getDataTableData() { - PageObjects.common.debug('third get the zoom level 5 page data and verify it'); + log.debug('third get the zoom level 5 page data and verify it'); return PageObjects.visualize.getDataTableData(); }) .then(function showData(data) { @@ -241,15 +240,14 @@ bdd.describe('visualize app', function describeIndexTests() { return PageObjects.visualize.collapseChart(); }) .then(function takeScreenshot() { - PageObjects.common.debug('Take screenshot'); + log.debug('Take screenshot'); PageObjects.common.saveScreenshot('Visualize-site-map'); }); - }); - + }); - bdd.it('should zoom in to level 10', function () { - // 6 - return PageObjects.visualize.clickMapZoomIn() + it('should zoom in to level 10', function () { + // 6 + return PageObjects.visualize.clickMapZoomIn() .then(function () { // 7 return PageObjects.visualize.clickMapZoomIn(); @@ -263,11 +261,11 @@ bdd.describe('visualize app', function describeIndexTests() { return PageObjects.visualize.clickMapZoomIn(); }) .then(function () { - return PageObjects.common.try(function tryingForTime() { + return retry.try(function tryingForTime() { return PageObjects.visualize.getMapZoomInEnabled() - .then(function (enabled) { - expect(enabled).to.be(true); - }); + .then(function (enabled) { + expect(enabled).to.be(true); + }); }); }) .then(function () { @@ -280,8 +278,7 @@ bdd.describe('visualize app', function describeIndexTests() { .then(function (enabled) { expect(enabled).to.be(false); }); + }); }); - - }); -}); +} diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index f60dbfd2a0c4e..54a91fc82d90c 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -1,66 +1,42 @@ - import expect from 'expect.js'; -import { bdd } from '../../../support'; - -import PageObjects from '../../../support/page_objects'; - -bdd.describe('visualize app', function describeIndexTests() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - - bdd.before(function () { - PageObjects.common.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new') - .then(function () { - PageObjects.common.debug('clickVerticalBarChart'); - return PageObjects.visualize.clickVerticalBarChart(); - }) - .then(function clickNewSearch() { - return PageObjects.visualize.clickNewSearch(); - }) - .then(function setAbsoluteRange() { - PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); - return PageObjects.header.setAbsoluteRange(fromTime, toTime); - }) - .then(function clickBucket() { - PageObjects.common.debug('Bucket = X-Axis'); - return PageObjects.visualize.clickBucket('X-Axis'); - }) - .then(function selectAggregation() { - PageObjects.common.debug('Aggregation = Date Histogram'); - return PageObjects.visualize.selectAggregation('Date Histogram'); - }) - .then(function selectField() { - PageObjects.common.debug('Field = @timestamp'); - return PageObjects.visualize.selectField('@timestamp'); - }) - // leaving Interval set to Auto - .then(function clickGo() { - return PageObjects.visualize.clickGo(); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }) - .then(function waitForVisualization() { - return PageObjects.visualize.waitForVisualization(); - }); - }); +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'visualize', 'header']); - bdd.describe('vertical bar chart', function indexPatternCreation() { - const vizName1 = 'Visualization VerticalBarChart'; + describe('visualize app', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; - bdd.it('should save and load', function () { - return PageObjects.visualize.saveVisualization(vizName1) - .then(function (message) { - PageObjects.common.debug('Saved viz message = ' + message); - expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + before(function () { + log.debug('navigateToApp visualize'); + return PageObjects.common.navigateToUrl('visualize', 'new') + .then(function () { + log.debug('clickVerticalBarChart'); + return PageObjects.visualize.clickVerticalBarChart(); }) - .then(function testVisualizeWaitForToastMessageGone() { - return PageObjects.visualize.waitForToastMessageGone(); + .then(function clickNewSearch() { + return PageObjects.visualize.clickNewSearch(); }) - .then(function () { - return PageObjects.visualize.loadSavedVisualization(vizName1); + .then(function setAbsoluteRange() { + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + return PageObjects.header.setAbsoluteRange(fromTime, toTime); + }) + .then(function clickBucket() { + log.debug('Bucket = X-Axis'); + return PageObjects.visualize.clickBucket('X-Axis'); + }) + .then(function selectAggregation() { + log.debug('Aggregation = Date Histogram'); + return PageObjects.visualize.selectAggregation('Date Histogram'); + }) + .then(function selectField() { + log.debug('Field = @timestamp'); + return PageObjects.visualize.selectField('@timestamp'); + }) + // leaving Interval set to Auto + .then(function clickGo() { + return PageObjects.visualize.clickGo(); }) .then(function () { return PageObjects.header.waitUntilLoadingHasFinished(); @@ -70,49 +46,73 @@ bdd.describe('visualize app', function describeIndexTests() { }); }); - bdd.it('should show correct chart, take screenshot', function () { - const expectedChartValues = [37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, 683, - 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 - ]; + describe('vertical bar chart', function indexPatternCreation() { + const vizName1 = 'Visualization VerticalBarChart'; - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - return PageObjects.common.sleep(50000) - .then(function () { - return PageObjects.visualize.getBarChartData(); - }) - .then(function showData(data) { - PageObjects.common.debug('data=' + data); - PageObjects.common.debug('data.length=' + data.length); - PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); - expect(data).to.eql(expectedChartValues); + it('should save and load', function () { + return PageObjects.visualize.saveVisualization(vizName1) + .then(function (message) { + log.debug('Saved viz message = ' + message); + expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"'); + }) + .then(function testVisualizeWaitForToastMessageGone() { + return PageObjects.visualize.waitForToastMessageGone(); + }) + .then(function () { + return PageObjects.visualize.loadSavedVisualization(vizName1); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); + }) + .then(function waitForVisualization() { + return PageObjects.visualize.waitForVisualization(); + }); }); - }); + it('should show correct chart, take screenshot', function () { + const expectedChartValues = [37, 202, 740, 1437, 1371, 751, 188, 31, 42, 202, 683, + 1361, 1415, 707, 177, 27, 32, 175, 707, 1408, 1355, 726, 201, 29 + ]; - bdd.it('should show correct data', function () { - // this is only the first page of the tabular data. - const expectedChartData = [ 'September 20th 2015, 00:00:00.000 37', - 'September 20th 2015, 03:00:00.000 202', - 'September 20th 2015, 06:00:00.000 740', - 'September 20th 2015, 09:00:00.000 1,437', - 'September 20th 2015, 12:00:00.000 1,371', - 'September 20th 2015, 15:00:00.000 751', - 'September 20th 2015, 18:00:00.000 188', - 'September 20th 2015, 21:00:00.000 31', - 'September 21st 2015, 00:00:00.000 42', - 'September 21st 2015, 03:00:00.000 202' - ]; + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + return PageObjects.common.sleep(50000) + .then(function () { + return PageObjects.visualize.getBarChartData(); + }) + .then(function showData(data) { + log.debug('data=' + data); + log.debug('data.length=' + data.length); + PageObjects.common.saveScreenshot('Visualize-vertical-bar-chart'); + expect(data).to.eql(expectedChartValues); + }); + }); - return PageObjects.visualize.collapseChart() - .then(function showData() { - return PageObjects.visualize.getDataTableData(); - }) - .then(function showData(data) { - PageObjects.common.debug(data.split('\n')); - expect(data.trim().split('\n')).to.eql(expectedChartData); + + it('should show correct data', function () { + // this is only the first page of the tabular data. + const expectedChartData = [ 'September 20th 2015, 00:00:00.000 37', + 'September 20th 2015, 03:00:00.000 202', + 'September 20th 2015, 06:00:00.000 740', + 'September 20th 2015, 09:00:00.000 1,437', + 'September 20th 2015, 12:00:00.000 1,371', + 'September 20th 2015, 15:00:00.000 751', + 'September 20th 2015, 18:00:00.000 188', + 'September 20th 2015, 21:00:00.000 31', + 'September 21st 2015, 00:00:00.000 42', + 'September 21st 2015, 03:00:00.000 202' + ]; + + return PageObjects.visualize.collapseChart() + .then(function showData() { + return PageObjects.visualize.getDataTableData(); + }) + .then(function showData(data) { + log.debug(data.split('\n')); + expect(data.trim().split('\n')).to.eql(expectedChartData); + }); }); }); }); -}); +} diff --git a/test/functional/apps/visualize/index.js b/test/functional/apps/visualize/index.js index 885b6e987b8b8..96118dbe8ce7b 100644 --- a/test/functional/apps/visualize/index.js +++ b/test/functional/apps/visualize/index.js @@ -1,43 +1,40 @@ +export default function ({ getService, loadTestFile }) { + const config = getService('config'); + const remote = getService('remote'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); -import { - bdd, - remote, - defaultTimeout, - esArchiver, - esClient, - } from '../../../support'; + describe('visualize app', function () { + this.timeout(config.get('timeouts.test')); -import PageObjects from '../../../support/page_objects'; + before(function () { + remote.setWindowSize(1280,800); -bdd.describe('visualize app', function () { - this.timeout = defaultTimeout; - - bdd.before(function () { - remote.setWindowSize(1280,800); - - PageObjects.common.debug('Starting visualize before method'); - const logstash = esArchiver.loadIfNeeded('logstash_functional'); - // delete .kibana index and update configDoc - return esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) - .then(function loadkibanaIndexPattern() { - PageObjects.common.debug('load kibana index with default index pattern'); - return esArchiver.load('visualize'); - }) - // wait for the logstash data load to finish if it hasn't already - .then(function () { - return logstash; + log.debug('Starting visualize before method'); + const logstash = esArchiver.loadIfNeeded('logstash_functional'); + // delete .kibana index and update configDoc + return kibanaServer.uiSettings.replace({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }) + .then(function loadkibanaIndexPattern() { + log.debug('load kibana index with default index pattern'); + return esArchiver.load('visualize'); + }) + // wait for the logstash data load to finish if it hasn't already + .then(function () { + return logstash; + }); }); - }); - require('./_chart_types'); - require('./_area_chart'); - require('./_line_chart'); - require('./_data_table'); - require('./_metric_chart'); - require('./_pie_chart'); - require('./_tile_map'); - require('./_vertical_bar_chart'); - require('./_heatmap_chart'); - require('./_point_series_options'); - require('./_shared_item'); -}); + loadTestFile(require.resolve('./_chart_types')); + loadTestFile(require.resolve('./_area_chart')); + loadTestFile(require.resolve('./_line_chart')); + loadTestFile(require.resolve('./_data_table')); + loadTestFile(require.resolve('./_metric_chart')); + loadTestFile(require.resolve('./_pie_chart')); + loadTestFile(require.resolve('./_tile_map')); + loadTestFile(require.resolve('./_vertical_bar_chart')); + loadTestFile(require.resolve('./_heatmap_chart')); + loadTestFile(require.resolve('./_point_series_options')); + loadTestFile(require.resolve('./_shared_item')); + }); +} diff --git a/test/functional/apps/xpack/index.js b/test/functional/apps/xpack/index.js index 6a496b82472ba..052b6daf7386d 100644 --- a/test/functional/apps/xpack/index.js +++ b/test/functional/apps/xpack/index.js @@ -1,32 +1,36 @@ -import { bdd, defaultTimeout } from '../../../support'; +export default function ({ getService, getPageObjects }) { + const config = getService('config'); + const log = getService('log'); + const PageObjects = getPageObjects(['monitoring', 'settings']); -import PageObjects from '../../../support/page_objects'; + describe('dismiss x-pack', function () { + this.timeout(config.get('timeouts.test')); -bdd.describe('dismiss x-pack', function () { - this.timeout = defaultTimeout; + // Putting everything here in 'before' so it doesn't count as a test + // since x-pack may or may not be installed. We just want the banner closed. + before(function () { + log.debug('check for X-Pack welcome, opt-out, and dismiss it'); + + // find class toaster and see if there's any list items in it? + return PageObjects.settings.navigateTo() + .then(() => { + return PageObjects.monitoring.getToasterContents(); + }) + .then((contents) => { + // Welcome to X-Pack! + // Sharing your cluster statistics with us helps us improve. Your data is never shared with anyone. Not interested? Opt out here. + // Dismiss + log.debug('Toast banner contents = ' + contents); + if (contents.includes('X-Pack')) { + return PageObjects.monitoring.clickOptOut() + .then(() => { + return PageObjects.monitoring.dismissWelcome(); + }); + } + }); - // Putting everything here in 'before' so it doesn't count as a test - // since x-pack may or may not be installed. We just want the banner closed. - bdd.before(function () { - PageObjects.common.debug('check for X-Pack welcome, opt-out, and dismiss it'); - // find class toaster and see if there's any list items in it? - return PageObjects.settings.navigateTo() - .then(() => { - return PageObjects.monitoring.getToasterContents(); - }) - .then((contents) => { - // Welcome to X-Pack! - // Sharing your cluster statistics with us helps us improve. Your data is never shared with anyone. Not interested? Opt out here. - // Dismiss - PageObjects.common.debug('Toast banner contents = ' + contents); - if (contents.includes('X-Pack')) { - return PageObjects.monitoring.clickOptOut() - .then(() => { - return PageObjects.monitoring.dismissWelcome(); - }); - } }); }); -}); +} diff --git a/test/functional/config.js b/test/functional/config.js new file mode 100644 index 0000000000000..94e0eba42e81a --- /dev/null +++ b/test/functional/config.js @@ -0,0 +1,74 @@ +import { resolve } from 'path'; + +import { + CommonPageProvider, + ConsolePageProvider, + ShieldPageProvider, + ContextPageProvider, + DiscoverPageProvider, + HeaderPageProvider, + DashboardPageProvider, + VisualizePageProvider, + SettingsPageProvider, + MonitoringPageProvider, +} from './page_objects'; + +import { + RemoteProvider, + FindProvider, + RetryProvider, + TestSubjectsProvider, + KibanaServerProvider, + EsProvider, + EsArchiverProvider, + DocTableProvider, + PointSeriesVisProvider, +} from './services'; + +import { servers, apps } from '../server_config'; + +export default function () { + return { + testFiles: [ + resolve(__dirname, './apps/console/index.js'), + resolve(__dirname, './apps/context/index.js'), + resolve(__dirname, './apps/dashboard/index.js'), + resolve(__dirname, './apps/discover/index.js'), + resolve(__dirname, './apps/management/index.js'), + resolve(__dirname, './apps/status_page/index.js'), + resolve(__dirname, './apps/visualize/index.js'), + resolve(__dirname, './apps/xpack/index.js'), + ], + pageObjects: { + common: CommonPageProvider, + console: ConsolePageProvider, + shield: ShieldPageProvider, + context: ContextPageProvider, + discover: DiscoverPageProvider, + header: HeaderPageProvider, + dashboard: DashboardPageProvider, + visualize: VisualizePageProvider, + settings: SettingsPageProvider, + monitoring: MonitoringPageProvider, + }, + services: { + kibanaServer: KibanaServerProvider, + remote: RemoteProvider, + find: FindProvider, + retry: RetryProvider, + testSubjects: TestSubjectsProvider, + es: EsProvider, + esArchiver: EsArchiverProvider, + docTable: DocTableProvider, + pointSeriesVis: PointSeriesVisProvider + }, + servers, + apps, + esArchiver: { + directory: resolve(__dirname, '../../src/fixtures/es_archives') + }, + screenshots: { + directory: resolve(__dirname, '../screenshots/session') + } + }; +} diff --git a/test/functional/index.js b/test/functional/index.js deleted file mode 100644 index 4b067580a087f..0000000000000 --- a/test/functional/index.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; // eslint-disable-line - -define(function (require) { - require('intern/dojo/node!../support/env_setup'); - - const bdd = require('intern!bdd'); - const intern = require('intern'); - - global.__kibana__intern__ = { intern, bdd }; - - bdd.describe('kibana', function () { - let PageObjects; - let support; - - bdd.before(function () { - PageObjects.init(this.remote); - support.init(this.remote); - }); - const supportPages = [ - 'intern/dojo/node!../support/page_objects', - 'intern/dojo/node!../support' - ]; - - const requestedApps = process.argv.reduce((previous, arg) => { - const option = arg.split('='); - const key = option[0]; - const value = option[1]; - if (key === 'appSuites' && value) return value.split(','); - }); - - const apps = [ - 'intern/dojo/node!./apps/xpack', - 'intern/dojo/node!./apps/discover', - 'intern/dojo/node!./apps/management', - 'intern/dojo/node!./apps/visualize', - 'intern/dojo/node!./apps/console', - 'intern/dojo/node!./apps/dashboard', - 'intern/dojo/node!./status_page', - 'intern/dojo/node!./apps/context' - ].filter((suite) => { - if (!requestedApps) return true; - return requestedApps.reduce((previous, app) => { - return previous || ~suite.indexOf(app); - }, false); - }); - - require(supportPages.concat(apps), (loadedPageObjects, loadedSupport) => { - PageObjects = loadedPageObjects; - support = loadedSupport; - }); - }); -}); diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js new file mode 100644 index 0000000000000..6192d6bba9af5 --- /dev/null +++ b/test/functional/page_objects/common_page.js @@ -0,0 +1,281 @@ +import { delay, promisify } from 'bluebird'; +import fs from 'fs'; +import mkdirp from 'mkdirp'; +import { resolve } from 'path'; + +import getUrl from '../../utils/get_url'; + +const mkdirpAsync = promisify(mkdirp); +const writeFileAsync = promisify(fs.writeFile); + +export function CommonPageProvider({ getService, getPageObjects }) { + const log = getService('log'); + const config = getService('config'); + const remote = getService('remote'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['shield']); + + const screenshotDirectory = config.get('screenshots.directory'); + const defaultTryTimeout = config.get('timeouts.try'); + const defaultFindTimeout = config.get('timeouts.find'); + + class CommonPage { + getHostPort() { + return getUrl.baseUrl(config.get('servers.kibana')); + } + + getEsHostPort() { + return getUrl.baseUrl(config.get('servers.elasticsearch')); + } + + navigateToUrl(appName, subUrl) { + const appConfig = Object.assign({}, config.get(['apps', appName]), { + // Overwrite the default hash with the URL we really want. + hash: `${appName}/${subUrl}`, + }); + + const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig); + return remote.get(appUrl); + } + + navigateToApp(appName) { + const self = this; + const appUrl = getUrl.noAuth(config.get('servers.kibana'), config.get(['apps', appName])); + log.debug('navigating to ' + appName + ' url: ' + appUrl); + + function navigateTo(url) { + return retry.try(function () { + // since we're using hash URLs, always reload first to force re-render + return kibanaServer.uiSettings.getDefaultIndex() + .then(function (defaultIndex) { + if (appName === 'discover' || appName === 'visualize' || appName === 'dashboard') { + if (!defaultIndex) { + // https://github.com/elastic/kibana/issues/7496 + // Even though most tests are using esClient to set the default index, sometimes Kibana clobbers + // that change. If we got here, fix it. + log.debug(' >>>>>>>> WARNING Navigating to [' + appName + '] with defaultIndex=' + defaultIndex); + log.debug(' >>>>>>>> Setting defaultIndex to "logstash-*""'); + return kibanaServer.uiSettings.update({ + 'dateFormat:tz':'UTC', + 'defaultIndex':'logstash-*' + }); + } + } + }) + .then(function () { + log.debug('navigate to: ' + url); + return remote.get(url); + }) + .then(function () { + return self.sleep(700); + }) + .then(function () { + log.debug('returned from get, calling refresh'); + return remote.refresh(); + }) + .then(function () { + return remote.getCurrentUrl(); + }) + .then(function (currentUrl) { + const loginPage = new RegExp('login').test(currentUrl); + if (loginPage) { + log.debug('Found loginPage username = ' + + config.get('servers.kibana.username')); + return PageObjects.shield.login(config.get('servers.kibana.username'), + config.get('servers.kibana.password')) + .then(function () { + return remote.getCurrentUrl(); + }); + } else { + return currentUrl; + } + }) + .then(function (currentUrl) { + currentUrl = currentUrl.replace(/\/\/\w+:\w+@/, '//'); + const maxAdditionalLengthOnNavUrl = 230; + // On several test failures at the end of the TileMap test we try to navigate back to + // Visualize so we can create the next Vertical Bar Chart, but we can see from the + // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" + // with a new timestamped URL go? I thought that sleep(700) between the get and the + // refresh would solve the problem but didn't seem to always work. + // So this hack fails the navSuccessful check if the currentUrl doesn't match the + // appUrl plus up to 230 other chars. + // Navigating to Settings when there is a default index pattern has a URL length of 196 + // (from debug output). Some other tabs may also be long. But a rather simple configured + // visualization is about 1000 chars long. So at least we catch that case. + + // Browsers don't show the ':port' if it's 80 or 443 so we have to + // remove that part so we can get a match in the tests. + const navSuccessful = new RegExp(appUrl.replace(':80','').replace(':443','') + + '.{0,' + maxAdditionalLengthOnNavUrl + '}$') + .test(currentUrl); + + if (!navSuccessful) { + const msg = 'App failed to load: ' + appName + + ' in ' + defaultFindTimeout + 'ms' + + ' appUrl = ' + appUrl + + ' currentUrl = ' + currentUrl; + log.debug(msg); + throw new Error(msg); + } + + return currentUrl; + }); + }); + } + + return retry.tryForTime(defaultTryTimeout * 3, () => { + return navigateTo(appUrl) + .then(function (currentUrl) { + let lastUrl = currentUrl; + return retry.try(function () { + // give the app time to update the URL + return self.sleep(501) + .then(function () { + return remote.getCurrentUrl(); + }) + .then(function (currentUrl) { + log.debug('in navigateTo url = ' + currentUrl); + if (lastUrl !== currentUrl) { + lastUrl = currentUrl; + throw new Error('URL changed, waiting for it to settle'); + } + }); + }); + }) + .then(async () => { + if (appName === 'status_page') return; + if (await testSubjects.exists('statusPageContainer')) { + throw new Error('Navigation ended up at the status page.'); + } + }); + }); + } + + runScript(fn, timeout = 10000) { + // wait for deps on window before running script + return remote + .setExecuteAsyncTimeout(timeout) + .executeAsync(function (done) { + const interval = setInterval(function () { + const ready = (document.readyState === 'complete'); + const hasJQuery = !!window.$; + + if (ready && hasJQuery) { + console.log('doc ready, jquery loaded'); + clearInterval(interval); + done(); + } + }, 10); + }).then(function () { + return remote.execute(fn); + }); + } + + async sleep(sleepMilliseconds) { + log.debug('... sleep(' + sleepMilliseconds + ') start'); + await delay(sleepMilliseconds); + log.debug('... sleep(' + sleepMilliseconds + ') end'); + } + + createErrorHandler(testObj) { + const testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name; + return error => { + const now = Date.now(); + const fileName = `failure_${now}_${testName}`; + + return this.saveScreenshot(fileName, true) + .then(function () { + throw error; + }); + }; + } + + async saveScreenshot(fileName, isFailure = false) { + try { + const type = isFailure ? 'failure' : 'session'; + const directory = resolve(screenshotDirectory, type); + const path = resolve(directory, `${fileName}.png`); + log.debug(`Taking screenshot "${path}"`); + + const screenshot = await remote.takeScreenshot(); + await mkdirpAsync(directory); + await writeFileAsync(path, screenshot); + } catch (err) { + log.warning(`SCREENSHOT FAILED: ${err}`); + } + } + + async waitUntilUrlIncludes(path) { + await retry.try(async () => { + const url = await remote.getCurrentUrl(); + if (!url.includes(path)) { + throw new Error('Url not found'); + } + }); + } + + async getSharedItemTitleAndDescription() { + const element = await remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('[data-shared-item]'); + + return { + title: await element.getAttribute('data-title'), + description: await element.getAttribute('data-description') + }; + } + + /** + * Makes sure the modal overlay is not showing, tries a few times in case it is in the process of hiding. + */ + async ensureModalOverlayHidden() { + return retry.try(async () => { + const shown = await testSubjects.exists('modalOverlay'); + if (shown) { + throw new Error('Modal overlay is showing'); + } + }); + } + + async clickConfirmOnModal() { + log.debug('Clicking modal confirm'); + await testSubjects.click('confirmModalConfirmButton'); + await this.ensureModalOverlayHidden(); + } + + async clickCancelOnModal() { + log.debug('Clicking modal cancel'); + await testSubjects.click('confirmModalCancelButton'); + await this.ensureModalOverlayHidden(); + } + + async isConfirmModalOpen() { + const isOpen = await testSubjects + .find('confirmModalCancelButton', 2000) + .then(() => true, () => false); + + await remote.setFindTimeout(defaultFindTimeout); + return isOpen; + } + + async doesCssSelectorExist(selector) { + log.debug(`doesCssSelectorExist ${selector}`); + + const exists = await remote + .setFindTimeout(1000) + .findByCssSelector(selector) + .then(() => true) + .catch(() => false); + + remote.setFindTimeout(defaultFindTimeout); + + log.debug(`exists? ${exists}`); + return exists; + } + } + + return new CommonPage(); +} diff --git a/test/functional/page_objects/console_page.js b/test/functional/page_objects/console_page.js new file mode 100644 index 0000000000000..e23b6d407417d --- /dev/null +++ b/test/functional/page_objects/console_page.js @@ -0,0 +1,63 @@ +import Bluebird from 'bluebird'; + +export function ConsolePageProvider({ getService }) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + async function getVisibleTextFromAceEditor(editor) { + const lines = await editor.findAllByClassName('ace_line_group'); + const linesText = await Bluebird.map(lines, l => l.getVisibleText()); + return linesText.join('\n'); + } + + return new class ConsolePage { + async getRequestEditor() { + return await testSubjects.find('request-editor'); + } + + async getRequest() { + const requestEditor = await this.getRequestEditor(); + return await getVisibleTextFromAceEditor(requestEditor); + } + + async getResponse() { + const responseEditor = await testSubjects.find('response-editor'); + return await getVisibleTextFromAceEditor(responseEditor); + } + + async clickPlay() { + await testSubjects.click('send-request-button'); + } + + async collapseHelp() { + await testSubjects.click('help-close-button'); + } + + async openSettings() { + await testSubjects.click('consoleSettingsButton'); + } + + async setFontSizeSetting(newSize) { + await this.openSettings(); + + // while the settings form opens/loads this may fail, so retry for a while + await retry.try(async () => { + const fontSizeInput = await testSubjects.find('setting-font-size-input'); + await fontSizeInput.clearValue(); + await fontSizeInput.click(); + await fontSizeInput.type(String(newSize)); + }); + + await testSubjects.click('settings-save-button'); + } + + async getFontSize(editor) { + const aceLine = await editor.findByClassName('ace_line'); + return await aceLine.getComputedStyle('font-size'); + } + + async getRequestFontSize() { + return await this.getFontSize(await this.getRequestEditor()); + } + }; +} diff --git a/test/functional/page_objects/context_page.js b/test/functional/page_objects/context_page.js new file mode 100644 index 0000000000000..537e200132062 --- /dev/null +++ b/test/functional/page_objects/context_page.js @@ -0,0 +1,60 @@ +import rison from 'rison-node'; + +import getUrl from '../../utils/get_url'; + +const DEFAULT_INITIAL_STATE = { + columns: ['@message'], +}; + +export function ContextPageProvider({ getService }) { + const remote = getService('remote'); + const config = getService('config'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + class ContextPage { + async navigateTo(indexPattern, anchorType, anchorId, overrideInitialState = {}) { + const initialState = rison.encode({ + ...DEFAULT_INITIAL_STATE, + ...overrideInitialState, + }); + const appUrl = getUrl.noAuth(config.get('servers.kibana'), { + ...config.get('apps.context'), + hash: `${config.get('apps.context.hash')}/${indexPattern}/${anchorType}/${anchorId}?_a=${initialState}`, + }); + + await remote.get(appUrl); + await remote.refresh(); + await this.waitUntilContextLoadingHasFinished(); + } + + getPredecessorCountPicker() { + return testSubjects.find('predecessorCountPicker'); + } + + getSuccessorCountPicker() { + return testSubjects.find('successorCountPicker'); + } + + getPredecessorLoadMoreButton() { + return testSubjects.find('predecessorLoadMoreButton'); + } + + getSuccessorLoadMoreButton() { + return testSubjects.find('predecessorLoadMoreButton'); + } + + waitUntilContextLoadingHasFinished() { + return retry.try(async () => { + if ( + !(await this.getSuccessorLoadMoreButton().isEnabled()) + || !(await this.getPredecessorLoadMoreButton().isEnabled()) + ) { + throw new Error('loading context rows'); + } + }); + } + } + + return new ContextPage(); +} diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js new file mode 100644 index 0000000000000..a058a6756b624 --- /dev/null +++ b/test/functional/page_objects/dashboard_page.js @@ -0,0 +1,449 @@ +import _ from 'lodash'; +import { DashboardConstants } from '../../../src/core_plugins/kibana/public/dashboard/dashboard_constants'; + +export function DashboardPageProvider({ getService, getPageObjects }) { + const log = getService('log'); + const find = getService('find'); + const retry = getService('retry'); + const config = getService('config'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header']); + + const defaultFindTimeout = config.get('timeouts.find'); + + const getRemote = () => ( + getService('remote') + .setFindTimeout(config.get('timeouts.find')) + ); + + class DashboardPage { + async initTests() { + const logstash = esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace({ + 'dateFormat:tz':'UTC', + 'defaultIndex':'logstash-*' + }); + + log.debug('load kibana index with visualizations'); + await esArchiver.load('dashboard'); + + await PageObjects.common.navigateToApp('dashboard'); + return logstash; + } + + /** + * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). + * @returns {Promise} + */ + async onDashboardLandingPage() { + log.debug(`onDashboardLandingPage`); + const exists = await testSubjects.exists('dashboardLandingPage'); + return exists; + } + + async clickDashboardBreadcrumbLink() { + log.debug('clickDashboardBreadcrumbLink'); + await retry.try(() => getRemote().findByCssSelector(`a[href="#${DashboardConstants.LANDING_PAGE_PATH}"]`).click()); + } + + async gotoDashboardLandingPage() { + log.debug('gotoDashboardLandingPage'); + const onPage = await this.onDashboardLandingPage(); + if (!onPage) { + await retry.try(async () => { + await this.clickDashboardBreadcrumbLink(); + await testSubjects.find('searchFilter'); + }); + } + } + + async getQuery() { + const queryObject = await testSubjects.find('dashboardQuery'); + return await queryObject.getProperty('value'); + } + + appendQuery(query) { + log.debug('Appending query'); + return retry.try(() => testSubjects.find('dashboardQuery').type(query)); + } + + clickFilterButton() { + log.debug('Clicking filter button'); + return testSubjects.click('dashboardQueryFilterButton'); + } + + clickEdit() { + log.debug('Clicking edit'); + return testSubjects.click('dashboardEditMode'); + } + + getIsInViewMode() { + log.debug('getIsInViewMode'); + return testSubjects.exists('dashboardEditMode'); + } + + clickCancelOutOfEditMode() { + log.debug('Clicking cancel'); + return testSubjects.click('dashboardViewOnlyMode'); + } + + clickNewDashboard() { + return testSubjects.click('newDashboardLink'); + } + + clickAddVisualization() { + return testSubjects.click('dashboardAddPanelButton'); + } + + clickAddNewVisualizationLink() { + return testSubjects.click('addNewSavedObjectLink'); + } + + clickOptions() { + return testSubjects.click('dashboardOptionsButton'); + } + + isOptionsOpen() { + log.debug('isOptionsOpen'); + return testSubjects.exists('dashboardDarkThemeCheckbox'); + } + + async openOptions() { + log.debug('openOptions'); + const isOpen = await this.isOptionsOpen(); + if (!isOpen) { + return testSubjects.click('dashboardOptionsButton'); + } + } + + async isDarkThemeOn() { + log.debug('isDarkThemeOn'); + await this.openOptions(); + const darkThemeCheckbox = await testSubjects.find('dashboardDarkThemeCheckbox'); + return await darkThemeCheckbox.getProperty('checked'); + } + + async useDarkTheme(on) { + await this.openOptions(); + const isDarkThemeOn = await this.isDarkThemeOn(); + if (isDarkThemeOn !== on) { + return testSubjects.click('dashboardDarkThemeCheckbox'); + } + } + + filterVizNames(vizName) { + return retry.try(() => getRemote() + .findByCssSelector('input[placeholder="Visualizations Filter..."]') + .click() + .pressKeys(vizName)); + } + + clickVizNameLink(vizName) { + return retry.try(() => getRemote() + .findByPartialLinkText(vizName) + .click()); + } + + closeAddVizualizationPanel() { + log.debug('closeAddVizualizationPanel'); + return retry.try(() => getRemote() + .findByCssSelector('i.fa fa-chevron-up') + .click()); + } + + async gotoDashboardEditMode(dashboardName) { + await this.loadSavedDashboard(dashboardName); + await this.clickEdit(); + } + + async addVisualization(vizName) { + await this.clickAddVisualization(); + log.debug('filter visualization (' + vizName + ')'); + await this.filterVizNames(vizName); + // this second wait is usually enough to avoid the + // 'stale element reference: element is not attached to the page document' + // on the next step + await PageObjects.common.sleep(1000); + // but wrap in a try loop since it can still happen + await retry.try(() => { + log.debug('click visualization (' + vizName + ')'); + return this.clickVizNameLink(vizName); + }); + await PageObjects.header.clickToastOK(); + // this second click of 'Add' collapses the Add Visualization pane + await this.clickAddVisualization(); + } + + async renameDashboard(dashName) { + log.debug(`Naming dashboard ` + dashName); + await testSubjects.click('dashboardRenameButton'); + await getRemote().findById('dashboardTitle').type(dashName); + } + + /** + * + * @param dashName {String} + * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean}} + */ + async saveDashboard(dashName, saveOptions = {}) { + await this.enterDashboardTitleAndClickSave(dashName, saveOptions); + + await PageObjects.header.waitUntilLoadingHasFinished(); + + // verify that green message at the top of the page. + // it's only there for about 5 seconds + await retry.try(() => { + log.debug('verify toast-message for saved dashboard'); + return getRemote() + .findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') + .getVisibleText(); + }); + } + + /** + * + * @param dashboardTitle {String} + * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean}} + */ + async enterDashboardTitleAndClickSave(dashboardTitle, saveOptions = {}) { + await testSubjects.click('dashboardSaveButton'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + + log.debug('entering new title'); + await getRemote().findById('dashboardTitle').type(dashboardTitle); + + if (saveOptions.storeTimeWithDashboard !== undefined) { + await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard); + } + + if (saveOptions.saveAsNew !== undefined) { + await this.setSaveAsNewCheckBox(saveOptions.saveAsNew); + } + + await retry.try(() => { + log.debug('clicking final Save button for named dashboard'); + return getRemote().findByCssSelector('.btn-primary').click(); + }); + } + + clickDashboardByLinkText(dashName) { + return getRemote() + .findByLinkText(dashName) + .click(); + } + + async searchForDashboardWithName(dashName) { + log.debug(`searchForDashboardWithName: ${dashName}`); + + await this.gotoDashboardLandingPage(); + + await retry.try(async () => { + const searchFilter = await testSubjects.find('searchFilter'); + await searchFilter.click(); + // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. + await searchFilter.type(dashName.replace('-',' ')); + }); + + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async getDashboardCountWithName(dashName) { + log.debug(`getDashboardCountWithName: ${dashName}`); + + await this.searchForDashboardWithName(dashName); + const links = await getRemote().findAllByLinkText(dashName); + return links.length; + } + + // use the search filter box to narrow the results down to a single + // entry, or at least to a single page of results + async loadSavedDashboard(dashName) { + log.debug(`Load Saved Dashboard ${dashName}`); + + await this.searchForDashboardWithName(dashName); + await this.clickDashboardByLinkText(dashName); + return PageObjects.header.waitUntilLoadingHasFinished(); + } + + getPanelTitles() { + log.debug('in getPanelTitles'); + return getRemote() + .findAllByCssSelector('span.panel-title') + .then(function (titleObjects) { + + function getTitles(chart) { + return chart.getAttribute('title'); + } + + const getTitlePromises = titleObjects.map(getTitles); + return Promise.all(getTitlePromises); + }); + } + + getPanelSizeData() { + log.debug('in getPanelSizeData'); + return getRemote() + .findAllByCssSelector('li.gs-w') + .then(function (titleObjects) { + + function getTitles(chart) { + let obj = {}; + return chart.getAttribute('data-col') + .then(theData => { + obj = { dataCol:theData }; + return chart; + }) + .then(chart => { + return chart.getAttribute('data-row') + .then(theData => { + obj.dataRow = theData; + return chart; + }); + }) + .then(chart => { + return chart.getAttribute('data-sizex') + .then(theData => { + obj.dataSizeX = theData; + return chart; + }); + }) + .then(chart => { + return chart.getAttribute('data-sizey') + .then(theData => { + obj.dataSizeY = theData; + return chart; + }); + }) + .then(chart => { + return chart.findByCssSelector('span.panel-title') + .then(function (titleElement) { + return titleElement.getAttribute('title'); + }) + .then(theData => { + obj.title = theData; + return obj; + }); + }); + } + + const getTitlePromises = titleObjects.map(getTitles); + return Promise.all(getTitlePromises); + }); + } + + getTestVisualizations() { + return [ + { name: 'Visualization PieChart', description: 'PieChart' }, + { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, + { name: 'Visualization漢字 AreaChart', description: 'AreaChart' }, + { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, + { name: 'Visualization漢字 LineChart', description: 'LineChart' }, + { name: 'Visualization TileMap', description: 'TileMap' }, + { name: 'Visualization MetricChart', description: 'MetricChart' } + ]; + } + + getTestVisualizationNames() { + return this.getTestVisualizations().map(visualization => visualization.name); + } + + async addVisualizations(visualizations) { + for (const vizName of visualizations) { + await this.addVisualization(vizName); + } + } + + async setTimepickerInDataRange() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + } + + async setSaveAsNewCheckBox(checked) { + log.debug('saveAsNewCheckbox: ' + checked); + const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + const isAlreadyChecked = await saveAsNewCheckbox.getProperty('checked'); + if (isAlreadyChecked !== checked) { + log.debug('Flipping save as new checkbox'); + await retry.try(() => saveAsNewCheckbox.click()); + } + } + + async setStoreTimeWithDashboard(checked) { + log.debug('Storing time with dashboard: ' + checked); + const storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); + const isAlreadyChecked = await storeTimeCheckbox.getProperty('checked'); + if (isAlreadyChecked !== checked) { + log.debug('Flipping store time checkbox'); + await retry.try(() => storeTimeCheckbox.click()); + } + } + + async getFilters(timeout = defaultFindTimeout) { + return await find.allByCssSelector('.filter-bar > .filter', timeout); + } + + async getFilterDescriptions(timeout = defaultFindTimeout) { + const filters = await find.allByCssSelector( + '.filter-bar > .filter > .filter-description', + timeout); + return _.map(filters, async (filter) => await filter.getVisibleText()); + } + + async filterOnPieSlice() { + log.debug('Filtering on a pie slice'); + await retry.try(async () => { + const slices = await find.allByCssSelector('svg > g > path.slice'); + log.debug('Slices found:' + slices.length); + return slices[0].click(); + }); + } + + async toggleExpandPanel() { + log.debug('toggleExpandPanel'); + const expandShown = await testSubjects.exists('dashboardPanelExpandIcon'); + if (!expandShown) { + const panelElements = await getRemote().findAllByCssSelector('span.panel-title'); + log.debug('click title'); + await retry.try(() => panelElements[0].click()); // Click to simulate hover. + } + const expandButton = await testSubjects.find('dashboardPanelExpandIcon'); + log.debug('click expand icon'); + await retry.try(() => expandButton.click()); + } + + getSharedItemsCount() { + log.debug('in getSharedItemsCount'); + const attributeName = 'data-shared-items-count'; + return getRemote() + .findByCssSelector(`[${attributeName}]`) + .then(function (element) { + if (element) { + return element.getAttribute(attributeName); + } + + throw new Error('no element'); + }); + } + + getPanelSharedItemData() { + log.debug('in getPanelSharedItemData'); + return getRemote() + .findAllByCssSelector('li.gs-w') + .then(function (elements) { + return Promise.all(elements.map(async element => { + const sharedItem = await element.findByCssSelector('[data-shared-item]'); + return { + title: await sharedItem.getAttribute('data-title'), + description: await sharedItem.getAttribute('data-description') + }; + })); + }); + } + } + + return new DashboardPage(); +} diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js new file mode 100644 index 0000000000000..5fbe3f3c93593 --- /dev/null +++ b/test/functional/page_objects/discover_page.js @@ -0,0 +1,302 @@ +export function DiscoverPageProvider({ getService, getPageObjects }) { + const config = getService('config'); + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['header', 'common']); + + const getRemote = () => ( + getService('remote') + .setFindTimeout(config.get('timeouts.find')) + ); + + class DiscoverPage { + getQueryField() { + return getRemote() + .findByCssSelector('input[ng-model=\'state.query\']'); + } + + getQuerySearchButton() { + return getRemote() + .findByCssSelector('button[aria-label=\'Search\']'); + } + + getTimespanText() { + return testSubjects.find('globalTimepickerRange') + .getVisibleText(); + } + + getChartTimespan() { + return getRemote() + .findByCssSelector('center.small > span:nth-child(1)') + .getVisibleText(); + } + + saveSearch(searchName) { + return this.clickSaveSearchButton() + .then(() => { + log.debug('--saveSearch button clicked'); + return getRemote().findDisplayedById('SaveSearch') + .pressKeys(searchName); + }) + .then(() => { + log.debug('--find save button'); + return testSubjects.click('discover-save-search-btn'); + }); + } + + loadSavedSearch(searchName) { + return this.clickLoadSavedSearchButton() + .then(() => { + getRemote().findByPartialLinkText(searchName).click(); + }) + .then(() => { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + + clickNewSearchButton() { + return testSubjects.click('discoverNewButton'); + } + + clickSaveSearchButton() { + return testSubjects.click('discoverSaveButton'); + } + + clickLoadSavedSearchButton() { + return testSubjects.click('discoverOpenButton'); + } + + getCurrentQueryName() { + return testSubjects.find('discoverCurrentQuery') + .getVisibleText(); + } + + getBarChartData() { + let yAxisLabel = 0; + let yAxisHeight; + + return PageObjects.header.waitUntilLoadingHasFinished() + .then(() => { + return getRemote() + .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type'); + }) + .then(function setYAxisLabel(y) { + return y + .getVisibleText() + .then(function (yLabel) { + yAxisLabel = yLabel.replace(',', ''); + log.debug('yAxisLabel = ' + yAxisLabel); + return yLabel; + }); + }) + // 2). find and save the y-axis pixel size (the chart height) + .then(function getRect() { + return getRemote() + .findByCssSelector('rect.background') + .then(function getRectHeight(chartAreaObj) { + return chartAreaObj + .getAttribute('height') + .then(function (theHeight) { + yAxisHeight = theHeight; // - 5; // MAGIC NUMBER - clipPath extends a bit above the top of the y-axis and below x-axis + log.debug('theHeight = ' + theHeight); + return theHeight; + }); + }); + }) + // 3). get the chart-wrapper elements + .then(function () { + return getRemote() + // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) + .findAllByCssSelector('svg > g > g.series > rect') // rect + .then(function (chartTypes) { + function getChartType(chart) { + return chart + .getAttribute('height') + .then(function (barHeight) { + return Math.round(barHeight / yAxisHeight * yAxisLabel); + }); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (bars) { + return bars; + }); + }); + } + + getChartInterval() { + return testSubjects.find('discoverIntervalSelect') + .getProperty('value') + .then(selectedValue => { + return getRemote() + .findByCssSelector('option[value="' + selectedValue + '"]') + .getVisibleText(); + }); + } + + setChartInterval(interval) { + return getRemote() + .setFindTimeout(5000) + .findByCssSelector('option[label="' + interval + '"]') + .click() + .then(() => { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + + getHitCount() { + return PageObjects.header.waitUntilLoadingHasFinished() + .then(() => { + return testSubjects.find('discoverQueryHits') + .getVisibleText(); + }); + } + + query(queryString) { + return getRemote() + .findByCssSelector('input[aria-label="Search input"]') + .clearValue() + .type(queryString) + .then(() => { + return getRemote() + .findByCssSelector('button[aria-label="Search"]') + .click(); + }) + .then(() => { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + + getDocHeader() { + return getRemote() + .findByCssSelector('thead.ng-isolate-scope > tr:nth-child(1)') + .getVisibleText(); + } + + getDocTableIndex(index) { + return getRemote() + .findByCssSelector('tr.discover-table-row:nth-child(' + (index) + ')') + .getVisibleText(); + } + + clickDocSortDown() { + return getRemote() + .findByCssSelector('.fa-sort-down') + .click(); + } + + clickDocSortUp() { + return getRemote() + .findByCssSelector('.fa-sort-up') + .click(); + } + + getMarks() { + return getRemote() + .findAllByCssSelector('mark') + .getVisibleText(); + } + + clickShare() { + return testSubjects.click('discoverShareButton'); + } + + clickShortenUrl() { + return testSubjects.click('sharedSnapshotShortUrlButton'); + } + + clickCopyToClipboard() { + return testSubjects.click('sharedSnapshotCopyButton'); + } + + getShareCaption() { + return testSubjects.find('shareUiTitle') + .getVisibleText(); + } + + getSharedUrl() { + return testSubjects.find('sharedSnapshotUrl') + .getProperty('value'); + } + + toggleSidebarCollapse() { + return getRemote().findDisplayedByCssSelector('.sidebar-collapser .chevron-cont') + .click(); + } + + getAllFieldNames() { + return getRemote() + .findAllByClassName('sidebar-item') + .then((items) => { + return Promise.all(items.map((item) => item.getVisibleText())); + }); + } + + getSidebarWidth() { + return getRemote() + .findByClassName('sidebar-list') + .getProperty('clientWidth'); + } + + hasNoResults() { + return testSubjects.find('discoverNoResults') + .then(() => true) + .catch(() => false); + } + + getNoResultsTimepicker() { + return testSubjects.find('discoverNoResultsTimefilter'); + } + + hasNoResultsTimepicker() { + return this + .getNoResultsTimepicker() + .then(() => true) + .catch(() => false); + } + + clickFieldListItem(field) { + return getRemote() + .findByCssSelector('li[attr-field="' + field + '"]').click(); + } + + async clickFieldListItemAdd(field) { + const listEntry = await testSubjects.find(`field-${field}`); + await getRemote().moveMouseTo(listEntry); + await testSubjects.click(`fieldToggle-${field}`); + } + + async clickFieldListItemVisualize(field) { + return await retry.try(async () => { + await testSubjects.click('fieldVisualize-' + field); + }); + } + + clickFieldListPlusFilter(field, value) { + // this method requires the field details to be open from clickFieldListItem() + // testSubjects.find doesn't handle spaces in the data-test-subj value + return getRemote() + .findByCssSelector('i[data-test-subj="plus-' + field + '-' + value + '"]') + .click(); + } + + clickFieldListMinusFilter(field, value) { + // this method requires the field details to be open from clickFieldListItem() + // testSubjects.find doesn't handle spaces in the data-test-subj value + return getRemote() + .findByCssSelector('i[data-test-subj="minus-' + field + '-' + value + '"]') + .click(); + } + + async removeAllFilters() { + await testSubjects.click('showFilterActions'); + await testSubjects.click('removeAllFilters'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.waitUntilUrlIncludes('filters:!()'); + } + } + + return new DiscoverPage(); +} diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js new file mode 100644 index 0000000000000..a5fca0e09b0a1 --- /dev/null +++ b/test/functional/page_objects/header_page.js @@ -0,0 +1,221 @@ +export function HeaderPageProvider({ getService, getPageObjects }) { + const config = getService('config'); + const remote = getService('remote'); + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common']); + + const defaultFindTimeout = config.get('timeouts.find'); + + class HeaderPage { + async clickSelector(selector) { + remote.setFindTimeout(defaultFindTimeout); + await remote.findByCssSelector(selector).click(); + } + + async clickDiscover() { + log.debug('click Discover tab'); + await this.clickSelector('a[href*=\'discover\']'); + await PageObjects.common.sleep(3000); + } + + async clickVisualize() { + log.debug('click Visualize tab'); + await this.clickSelector('a[href*=\'visualize\']'); + await PageObjects.common.sleep(3000); + } + + async clickDashboard() { + log.debug('click Dashboard tab'); + await this.clickSelector('a[href*=\'dashboard\']'); + await PageObjects.common.sleep(3000); + } + + async clickSettings() { + log.debug('click Settings tab'); + await this.clickSelector('a[href*=\'settings\']'); + } + + async clickTimepicker() { + await testSubjects.click('globalTimepickerButton'); + } + + async clickQuickButton() { + await retry.try(async () => { + remote.setFindTimeout(defaultFindTimeout); + await remote.findByLinkText('Quick').click(); + }); + } + + async isTimepickerOpen() { + remote.setFindTimeout(2000); + try { + await remote.findDisplayedByCssSelector('.kbn-timepicker'); + return true; + } catch (error) { + return false; + } + } + + async isAbsoluteSectionShowing() { + log.debug('isAbsoluteSectionShowing'); + return await PageObjects.common.doesCssSelectorExist('input[ng-model=\'absolute.from\']'); + } + + async showAbsoluteSection() { + log.debug('showAbsoluteSection'); + const isAbsoluteSectionShowing = await this.isAbsoluteSectionShowing(); + if (!isAbsoluteSectionShowing) { + await retry.try(async () => { + await remote.setFindTimeout(defaultFindTimeout); + const absoluteButton = await remote.findByLinkText('Absolute'); + await absoluteButton.click(); + // Check to make sure one of the elements on the absolute section is showing. + await this.getFromTime(); + }); + } + } + + async getFromTime() { + log.debug('getFromTime'); + return await retry.try(async () => { + await this.ensureTimePickerIsOpen(); + await this.showAbsoluteSection(); + remote.setFindTimeout(defaultFindTimeout); + return await remote.findByCssSelector('input[ng-model=\'absolute.from\']') + .getProperty('value'); + }); + } + + async getToTime() { + log.debug('getToTime'); + return await retry.try(async () => { + await this.ensureTimePickerIsOpen(); + await this.showAbsoluteSection(); + remote.setFindTimeout(defaultFindTimeout); + return await remote.findByCssSelector('input[ng-model=\'absolute.to\']') + .getProperty('value'); + }); + } + + async setFromTime(timeString) { + log.debug(`setFromTime: ${timeString}`); + await retry.try(async () => { + await this.ensureTimePickerIsOpen(); + await this.showAbsoluteSection(); + remote.setFindTimeout(defaultFindTimeout); + await remote.findByCssSelector('input[ng-model=\'absolute.from\']') + .clearValue() + .type(timeString); + }); + } + + async setToTime(timeString) { + log.debug(`setToTime: ${timeString}`); + await retry.try(async () => { + await this.ensureTimePickerIsOpen(); + await this.showAbsoluteSection(); + remote.setFindTimeout(defaultFindTimeout); + await remote.findByCssSelector('input[ng-model=\'absolute.to\']') + .clearValue() + .type(timeString); + }); + } + + async clickGoButton() { + log.debug('clickGoButton'); + await retry.try(async () => { + remote.setFindTimeout(defaultFindTimeout); + await remote.findByClassName('kbn-timepicker-go').click(); + await this.waitUntilLoadingHasFinished(); + }); + } + + async ensureTimePickerIsOpen() { + const isOpen = await this.isTimepickerOpen(); + log.debug(`ensureTimePickerIsOpen: ${isOpen}`); + if (!isOpen) { + log.debug('--Opening time picker'); + await this.clickTimepicker(); + } + } + + async setAbsoluteRange(fromTime, toTime) { + log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); + await this.ensureTimePickerIsOpen(); + log.debug('--Clicking Absolute button'); + await this.showAbsoluteSection(); + log.debug('--Setting From Time : ' + fromTime); + await this.setFromTime(fromTime); + log.debug('--Setting To Time : ' + toTime); + await this.setToTime(toTime); + await this.clickGoButton(); + await this.isGlobalLoadingIndicatorHidden(); + } + + async setQuickTime(quickTime) { + await this.ensureTimePickerIsOpen(); + log.debug('--Clicking Quick button'); + await this.clickQuickButton(); + await remote.setFindTimeout(defaultFindTimeout) + .findByLinkText(quickTime).click(); + } + + async getToastMessage() { + remote.setFindTimeout(defaultFindTimeout); + return await remote.findDisplayedByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') + .getVisibleText(); + } + + async waitForToastMessageGone() { + remote.setFindTimeout(defaultFindTimeout); + await remote.waitForDeletedByCssSelector('kbn-truncated.toast-message'); + } + + async clickToastOK() { + log.debug('clickToastOK'); + await retry.try(async () => { + remote.setFindTimeout(defaultFindTimeout); + await remote.findByCssSelector('button[ng-if="notif.accept"]') + .click(); + }); + } + + async waitUntilLoadingHasFinished() { + try { + await this.isGlobalLoadingIndicatorVisible(); + } catch (exception) { + if (exception.name === 'ElementNotVisible') { + // selenium might just have been too slow to catch it + } else { + throw exception; + } + } + await this.isGlobalLoadingIndicatorHidden(); + } + + async isGlobalLoadingIndicatorVisible() { + return await testSubjects.find('globalLoadingIndicator', defaultFindTimeout / 5); + } + + async isGlobalLoadingIndicatorHidden() { + remote.setFindTimeout(defaultFindTimeout * 10); + return await remote.findByCssSelector('[data-test-subj="globalLoadingIndicator"].ng-hide'); + } + + async getPrettyDuration() { + return await testSubjects.find('globalTimepickerRange').getVisibleText(); + } + + async isSharedTimefilterEnabled() { + const element = await remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`[shared-timefilter=true]`); + + return !!element; + } + } + + return new HeaderPage(); +} diff --git a/test/functional/page_objects/index.js b/test/functional/page_objects/index.js new file mode 100644 index 0000000000000..8c8c911017979 --- /dev/null +++ b/test/functional/page_objects/index.js @@ -0,0 +1,10 @@ +export { ConsolePageProvider } from './console_page'; +export { CommonPageProvider } from './common_page'; +export { ShieldPageProvider } from './shield_page'; +export { ContextPageProvider } from './context_page'; +export { DiscoverPageProvider } from './discover_page'; +export { HeaderPageProvider } from './header_page'; +export { DashboardPageProvider } from './dashboard_page'; +export { VisualizePageProvider } from './visualize_page'; +export { SettingsPageProvider } from './settings_page'; +export { MonitoringPageProvider } from './monitoring_page'; diff --git a/test/functional/page_objects/monitoring_page.js b/test/functional/page_objects/monitoring_page.js new file mode 100644 index 0000000000000..919509ed7ab53 --- /dev/null +++ b/test/functional/page_objects/monitoring_page.js @@ -0,0 +1,34 @@ +export function MonitoringPageProvider({ getService }) { + const getRemote = (timeout) => + getService('remote') + .setFindTimeout( + timeout || getService('config').get('timeouts.find') + ); + + class MonitoringPage { + getWelcome() { + return getRemote() + .findDisplayedByCssSelector('render-directive') + .getVisibleText(); + } + + dismissWelcome() { + return getRemote(3000) + .findDisplayedByCssSelector('button.btn-banner') + .click(); + } + + getToasterContents() { + return getRemote() + .findByCssSelector('div.toaster-container.ng-isolate-scope') + .getVisibleText(); + } + + clickOptOut() { + return getRemote().findByLinkText('Opt out here').click(); + } + + } + + return new MonitoringPage(); +} diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js new file mode 100644 index 0000000000000..1b8c02a48a744 --- /dev/null +++ b/test/functional/page_objects/settings_page.js @@ -0,0 +1,460 @@ +import { map as mapAsync } from 'bluebird'; + +export function SettingsPageProvider({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const config = getService('config'); + const remote = getService('remote'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['header', 'common']); + + const defaultFindTimeout = config.get('timeouts.find'); + + class SettingsPage { + async clickNavigation() { + // TODO: find better way to target the element + await remote.findDisplayedByCssSelector('.app-link:nth-child(5) a').click(); + } + + async clickLinkText(text) { + await retry.try(async () => { + await remote.findDisplayedByLinkText(text).click(); + }); + } + + async clickKibanaSettings() { + await this.clickLinkText('Advanced Settings'); + } + + async clickKibanaIndicies() { + await this.clickLinkText('Index Patterns'); + } + + getAdvancedSettings(propertyName) { + log.debug('in setAdvancedSettings'); + return testSubjects.find('advancedSetting-' + propertyName + '-currentValue') + .getVisibleText(); + } + + async setAdvancedSettings(propertyName, propertyValue) { + await testSubjects.click('advancedSetting-' + propertyName + '-editButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.sleep(1000); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('option[label="' + propertyValue + '"]').click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.click('advancedSetting-' + propertyName + '-saveButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async navigateTo() { + await PageObjects.common.navigateToApp('settings'); + } + + getTimeBasedEventsCheckbox() { + return remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="index.isTimeBased"]'); + } + + getTimeBasedIndexPatternCheckbox(timeout) { + timeout = timeout || defaultFindTimeout; + // fail faster since we're sometimes checking that it doesn't exist + return remote.setFindTimeout(timeout) + .findByCssSelector('input[ng-model="index.nameIsPattern"]'); + } + + getIndexPatternField() { + return remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('[ng-model="index.name"]'); + } + + getTimeFieldNameField() { + return remote.setFindTimeout(defaultFindTimeout) + .findDisplayedByCssSelector('select[ng-model="index.timeField"]'); + } + + async selectTimeFieldOption(selection) { + // open dropdown + (await this.getTimeFieldNameField()).click(); + // close dropdown, keep focus + (await this.getTimeFieldNameField()).click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async () => { + (await this.getTimeFieldOption(selection)).click(); + const selected = (await this.getTimeFieldOption(selection)).isSelected(); + if (!selected) throw new Error('option not selected: ' + selected); + }); + } + + getTimeFieldOption(selection) { + return remote.setFindTimeout(defaultFindTimeout) + .findDisplayedByCssSelector('option[label="' + selection + '"]'); + } + + getCreateButton() { + return remote.setFindTimeout(defaultFindTimeout) + .findDisplayedByCssSelector('[type="submit"]'); + } + + async clickDefaultIndexButton() { + await testSubjects.find('setDefaultIndexPatternButton').click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async clickDeletePattern() { + await testSubjects.find('deleteIndexPatternButton').click(); + } + + getIndexPageHeading() { + return testSubjects.find('indexPatternTitle'); + } + + getConfigureHeader() { + return remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('h1'); + } + + getTableHeader() { + return remote.setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('table.table.table-condensed thead tr th'); + } + + sortBy(columnName) { + return remote.setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('table.table.table-condensed thead tr th span') + .then(function (chartTypes) { + function getChartType(chart) { + return chart.getVisibleText() + .then(function (chartString) { + if (chartString === columnName) { + return chart.click() + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + }); + } + + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }); + } + + getTableRow(rowNumber, colNumber) { + return remote.setFindTimeout(defaultFindTimeout) + // passing in zero-based index, but adding 1 for css 1-based indexes + .findByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr:nth-child(' + + (rowNumber + 1) + ') td.ng-scope:nth-child(' + + (colNumber + 1) + ') span.ng-binding' + ); + } + + getFieldsTabCount() { + return retry.try(() => { + return testSubjects.find('tab-count-indexedFields') + .getVisibleText() + .then((theText) => { + // the value has () around it, remove them + return theText.replace(/\((.*)\)/, '$1'); + }); + }); + } + + async getScriptedFieldsTabCount() { + const selector = '[data-test-subj="tab-count-scriptedFields"]'; + return await retry.try(async () => { + const theText = await remote.setFindTimeout(defaultFindTimeout / 10) + .findByCssSelector(selector).getVisibleText(); + return theText.replace(/\((.*)\)/, '$1'); + }); + } + + getPageSize() { + let selectedItemLabel = ''; + return remote.setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('select.ng-pristine.ng-valid.ng-untouched option') + .then(function (chartTypes) { + function getChartType(chart) { + const thisChart = chart; + return chart.isSelected() + .then(function (isSelected) { + if (isSelected === true) { + return thisChart.getProperty('label') + .then(function (theLabel) { + selectedItemLabel = theLabel; + }); + } + }); + } + + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(() => { + return selectedItemLabel; + }); + } + + async getFieldNames() { + const fieldNameCells = await testSubjects.findAll('editIndexPattern indexedFieldName'); + return await mapAsync(fieldNameCells, async cell => { + return (await cell.getVisibleText()).trim(); + }); + } + + async getFieldTypes() { + const fieldNameCells = await testSubjects.findAll('editIndexPattern indexedFieldType'); + return await mapAsync(fieldNameCells, async cell => { + return (await cell.getVisibleText()).trim(); + }); + } + + async getScriptedFieldLangs() { + const fieldNameCells = await testSubjects.findAll('editIndexPattern scriptedFieldLang'); + return await mapAsync(fieldNameCells, async cell => { + return (await cell.getVisibleText()).trim(); + }); + } + + async setFieldTypeFilter(type) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]') + .click(); + } + + async setScriptedFieldLanguageFilter(language) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + '"]') + .click(); + } + + async goToPage(pageNum) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' + + (pageNum + 1) + ') a.ng-binding') + .click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async openControlsRow(row) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('table.table.table-condensed tbody tr:nth-child(' + + (row + 1) + ') td.ng-scope div.actions a.btn.btn-xs.btn-default i.fa.fa-pencil') + .click(); + } + + async openControlsByName(name) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('div.actions a.btn.btn-xs.btn-default[href$="/' + name + '"]') + .click(); + } + + async increasePopularity() { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('button.btn.btn-default[aria-label="Plus"]') + .click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + getPopularity() { + return remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="editor.field.count"]') + .getProperty('value'); + } + + async controlChangeCancel() { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]') + .click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async controlChangeSave() { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]') + .click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async setPageSize(size) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector(`[data-test-subj="paginateControlsPageSizeSelect"] option[label="${size}"]`) + .click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async createIndexPattern() { + await retry.try(async () => { + await this.navigateTo(); + await this.clickKibanaIndicies(); + await this.selectTimeFieldOption('@timestamp'); + await this.getCreateButton().click(); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async () => { + const currentUrl = await remote.getCurrentUrl(); + log.info('currentUrl', currentUrl); + if (!currentUrl.match(/indices\/.+\?/)) { + throw new Error('Index pattern not created'); + } else { + log.debug('Index pattern created: ' + currentUrl); + } + }); + } + + async removeIndexPattern() { + let alertText; + await retry.try(async () => { + log.debug('click delete index pattern button'); + await this.clickDeletePattern(); + }); + await retry.try(async () => { + log.debug('getAlertText'); + alertText = await testSubjects.find('confirmModalBodyText').getVisibleText(); + }); + await retry.try(async () => { + log.debug('acceptConfirmation'); + await testSubjects.click('confirmModalConfirmButton'); + }); + await retry.try(async () => { + const currentUrl = await remote.getCurrentUrl(); + if (currentUrl.match(/indices\/.+\?/)) { + throw new Error('Index pattern not removed'); + } + }); + return alertText; + } + + async clickFieldsTab() { + log.debug('click Fields tab'); + await testSubjects.click('tab-indexFields'); + } + + async clickScriptedFieldsTab() { + log.debug('click Scripted Fields tab'); + await testSubjects.click('tab-scriptedFields'); + } + + async clickSourceFiltersTab() { + log.debug('click Source Filters tab'); + await testSubjects.click('tab-sourceFilters'); + } + + async addScriptedField(name, language, type, format, popularity, script) { + await this.clickAddScriptedField(); + await this.setScriptedFieldName(name); + if (language) await this.setScriptedFieldLanguage(language); + if (type) await this.setScriptedFieldType(type); + if (format) { + await this.setScriptedFieldFormat(format.format); + // null means leave - default - which has no other settings + // Url adds Type, Url Template, and Label Template + // Date adds moment.js format pattern (Default: "MMMM Do YYYY, HH:mm:ss.SSS") + // String adds Transform + switch(format.format) { + case 'Url': + await this.setScriptedFieldUrlType(format.type); + await this.setScriptedFieldUrlTemplate(format.template); + await this.setScriptedFieldUrlLabelTemplate(format.labelTemplate); + break; + case 'Date': + await this.setScriptedFieldDatePattern(format.datePattern); + break; + case 'String': + await this.setScriptedFieldStringTransform(format.stringTransform); + break; + } + } + if (popularity) await this.setScriptedFieldPopularity(popularity); + await this.setScriptedFieldScript(script); + await this.clickSaveScriptedField(); + } + + async clickAddScriptedField() { + log.debug('click Add Scripted Field'); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('a[aria-label="Add Scripted Field"]') + .click(); + } + + async clickSaveScriptedField() { + log.debug('click Save Scripted Field'); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('button[aria-label="Create Field"]') + .click(); + } + + async setScriptedFieldName(name) { + log.debug('set scripted field name = ' + name); + await testSubjects.find('editorFieldName').type(name); + } + + async setScriptedFieldLanguage(language) { + log.debug('set scripted field language = ' + language); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[data-test-subj="editorFieldLang"] > option[label="' + language + '"]') + .click(); + } + + async setScriptedFieldType(type) { + log.debug('set scripted field type = ' + type); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[data-test-subj="editorFieldType"] > option[label="' + type + '"]') + .click(); + } + + async setScriptedFieldFormat(format) { + log.debug('set scripted field format = ' + format); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[data-test-subj="editorSelectedFormatId"] > option[label="' + format + '"]') + .click(); + } + + async setScriptedFieldUrlType(type) { + log.debug('set scripted field Url type = ' + type); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[ng-model="editor.formatParams.type"] > option[label="' + type + '"]') + .click(); + } + + async setScriptedFieldUrlTemplate(template) { + log.debug('set scripted field Url Template = ' + template); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="editor.formatParams.labelTemplate"]') + .type(template); + } + + async setScriptedFieldUrlLabelTemplate(labelTemplate) { + log.debug('set scripted field Url Label Template = ' + labelTemplate); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="editor.formatParams.labelTemplate"]') + .type(labelTemplate); + } + + async setScriptedFieldDatePattern(datePattern) { + log.debug('set scripted field Date Pattern = ' + datePattern); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="model"]') + .clearValue().type(datePattern); + } + + async setScriptedFieldStringTransform(stringTransform) { + log.debug('set scripted field string Transform = ' + stringTransform); + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[ng-model="editor.formatParams.transform"] > option[label="' + stringTransform + '"]') + .click(); + } + + async setScriptedFieldPopularity(popularity) { + log.debug('set scripted field popularity = ' + popularity); + await testSubjects.find('editorFieldCount').type(popularity); + } + + async setScriptedFieldScript(script) { + log.debug('set scripted field script = ' + script); + await testSubjects.find('editorFieldScript').type(script); + } + } + + return new SettingsPage(); +} diff --git a/test/functional/page_objects/shield_page.js b/test/functional/page_objects/shield_page.js new file mode 100644 index 0000000000000..a01887fa42572 --- /dev/null +++ b/test/functional/page_objects/shield_page.js @@ -0,0 +1,24 @@ +export function ShieldPageProvider({ getService }) { + const remote = getService('remote'); + const config = getService('config'); + + const defaultFindTimeout = config.get('timeouts.find'); + + class ShieldPage { + login(user, pwd) { + return remote.setFindTimeout(defaultFindTimeout) + .findById('username') + .type(user) + .then(function () { + return remote.findById('password') + .type(pwd); + }) + .then(function () { + return remote.findByCssSelector('.btn') + .click(); + }); + } + } + + return new ShieldPage(); +} diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js new file mode 100644 index 0000000000000..6e7a3abae4e85 --- /dev/null +++ b/test/functional/page_objects/visualize_page.js @@ -0,0 +1,769 @@ +export function VisualizePageProvider({ getService, getPageObjects }) { + const remote = getService('remote'); + const config = getService('config'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'header']); + const defaultFindTimeout = config.get('timeouts.find'); + + class VisualizePage { + clickAreaChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Area') + .click(); + } + + clickDataTable() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Data Table') + .click(); + } + + clickLineChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Line') + .click(); + } + + clickMarkdownWidget() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Markdown') + .click(); + } + + clickAddMetric() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('[group-name="metrics"] .vis-editor-agg-add .vis-editor-agg-wide-btn div.btn') + .click(); + } + + clickMetric() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Metric') + .click(); + } + + clickPieChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Pie') + .click(); + } + + clickTileMap() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Tile Map') + .click(); + } + + clickVerticalBarChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Vertical Bar') + .click(); + } + + clickHeatmapChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Heat Map') + .click(); + } + + getChartTypeCount() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('a.wizard-vis-type.ng-scope') + .length; + } + + getChartTypes() { + return testSubjects.findAll('visualizeWizardChartTypeTitle') + .then(function (chartTypes) { + function getChartType(chart) { + return chart.getVisibleText(); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (texts) { + return texts; + }); + } + + clickAbsoluteButton() { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('ul.nav.nav-pills.nav-stacked.kbn-timepicker-modes:contains("absolute")') + .click(); + } + + setFromTime(timeString) { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('input[ng-model="absolute.from"]') + .clearValue() + .type(timeString); + } + + setToTime(timeString) { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('input[ng-model="absolute.to"]') + .clearValue() + .type(timeString); + } + + clickGoButton() { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByClassName('kbn-timepicker-go') + .click(); + } + + collapseChart() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('div.visualize-show-spy > div > i') + .click(); + } + + getMetric() { + return remote + .setFindTimeout(2000) + .findByCssSelector('div[ng-controller="KbnMetricVisController"]') + .getVisibleText(); + } + + clickMetricEditor() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('button[aria-label="Open Editor"]') + .click(); + } + + clickNewSearch() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('.list-group-item a') + .click(); + } + + setValue(newValue) { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('button[ng-click="numberListCntr.add()"]') + .click() + .then(() => { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') + .clearValue(); + }) + .then(() => { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') + .type(newValue); + }); + } + + clickSavedSearch() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('li[ng-click="stepTwoMode=\'saved\'"]') + .click(); + } + + selectSearch(searchName) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByLinkText(searchName) + .click(); + } + + + getErrorMessage() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('.item>h4') + .getVisibleText(); + } + + // clickBucket(bucketType) 'X-Axis', 'Split Area', 'Split Chart' + clickBucket(bucketName) { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('li.list-group-item.list-group-menu-item.ng-binding.ng-scope') + .then(chartTypes => { + log.debug('found bucket types ' + chartTypes.length); + + function getChartType(chart) { + return chart + .getVisibleText() + .then(chartString => { + //log.debug(chartString); + if (chartString === bucketName) { + chart.click(); + } + }); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }); + } + + selectAggregation(myString) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('vis-editor-agg-params:not(.ng-hide) option[label="' + myString + '"]') + .click(); + } + + getField() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('.ng-valid-required[name="field"] option[selected="selected"]') + .getVisibleText(); + } + + selectField(fieldValue, groupName = 'buckets') { + return retry.try(function tryingForTime() { + return remote + .setFindTimeout(defaultFindTimeout) + // the css below should be more selective + .findByCssSelector(`[group-name="${groupName}"] option[label="${fieldValue}"]`) + .click(); + }); + } + + orderBy(fieldValue) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('select.form-control.ng-pristine.ng-valid.ng-untouched.ng-valid-required[ng-model="agg.params.orderBy"] ' + + 'option.ng-binding.ng-scope:contains("' + fieldValue + '")' + ) + .click(); + } + + selectOrderBy(fieldValue) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[name="orderBy"] > option[value="' + fieldValue + '"]') + .click(); + } + + + getInterval() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[ng-model="agg.params.interval"]') + .getProperty('selectedIndex') + .then(selectedIndex => { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[ng-model="agg.params.interval"] option:nth-child(' + (selectedIndex + 1) + ')') + .getProperty('label'); + }); + } + + setInterval(newValue) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('select[ng-model="agg.params.interval"]') + .type(newValue); + } + + setNumericInterval(newValue) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[name="interval"]') + .type(newValue); + } + + clickGo() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('.btn-success') + .click() + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + + saveVisualization(vizName) { + return testSubjects.click('visualizeSaveButton') + .then(() => { + return PageObjects.common.sleep(1000); + }) + .then(() => { + log.debug('saveButton button clicked'); + return remote + .setFindTimeout(defaultFindTimeout) + .findByName('visTitle') + .type(vizName); + }) + // // click save button + .then(() => { + log.debug('click submit button'); + return testSubjects.click('saveVisualizationButton'); + }) + .then(function () { + return PageObjects.header.waitUntilLoadingHasFinished(); + }) + // verify that green message at the top of the page. + // it's only there for about 5 seconds + .then(() => { + return retry.try(function tryingForTime() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') + .getVisibleText(); + }); + }); + } + + clickLoadSavedVisButton() { + // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb + // element as a child instead of building the breadcrumbs dynamically. + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('[href="#/visualize"]') + .click(); + } + + filterVisByName(vizName) { + return remote + .findByCssSelector('input[name="filter"]') + .click() + // can't uses dashes in saved visualizations when filtering + // or extended character sets + // https://github.com/elastic/kibana/issues/6300 + .type(vizName.replace('-',' ')); + } + + clickVisualizationByName(vizName) { + log.debug('clickVisualizationByLinkText(' + vizName + ')'); + + return retry.try(function tryingForTime() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText(vizName) + .click(); + }); + } + + // this starts by clicking the Load Saved Viz button, not from the + // bottom half of the "Create a new visualization Step 1" page + loadSavedVisualization(vizName) { + return this.clickLoadSavedVisButton() + .then(() => this.openSavedVisualization(vizName)); + } + + openSavedVisualization(vizName) { + return this.clickVisualizationByName(vizName); + } + + getXAxisLabels() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.x > g') + .then(chartTypes => { + function getChartType(chart) { + return chart + .getVisibleText(); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(texts => { + // log.debug('returning types array ' + texts + ' array length =' + texts.length); + return texts; + }); + } + + + getYAxisLabels() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.y > g') + .then(chartTypes => { + function getChartType(chart) { + return chart + .getVisibleText(); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(texts => { + // log.debug('returning types array ' + texts + ' array length =' + texts.length); + return texts; + }); + } + + + /* + ** This method gets the chart data and scales it based on chart height and label. + ** Returns an array of height values + */ + getAreaChartData(aggregateName) { + const chartData = []; + let tempArray = []; + let chartSections = 0; + let yAxisLabel = 0; + let yAxisHeight = 0; + + // 1). get the maximim chart Y-Axis marker value + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type') + .getVisibleText() + .then(function (yLabel) { + // since we're going to use the y-axis 'last' (top) label as a number to + // scale the chart pixel data, we need to clean out commas and % marks. + yAxisLabel = yLabel.replace(/(%|,)/g, ''); + log.debug('yAxisLabel = ' + yAxisLabel); + return yLabel; + }) + // 2). find and save the y-axis pixel size (the chart height) + .then(function () { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('rect.background') // different here + .getAttribute('height'); + }) + .then(function (chartH) { + yAxisHeight = chartH; + log.debug('height --------- ' + yAxisHeight); + }) + .then(function () { + return remote.setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('path[data-label="' + aggregateName + '"]') + .getAttribute('d'); + }) + .then(function (data) { + log.debug(data); + // This area chart data starts with a 'M'ove to a x,y location, followed + // by a bunch of 'L'ines from that point to the next. Those points are + // the values we're going to use to calculate the data values we're testing. + // So git rid of the one 'M' and split the rest on the 'L's. + tempArray = data.replace('M','').split('L'); + chartSections = tempArray.length / 2; + log.debug('chartSections = ' + chartSections + ' height = ' + yAxisHeight + ' yAxisLabel = ' + yAxisLabel); + for (let i = 0; i < chartSections; i++) { + chartData[i] = Math.round((yAxisHeight - tempArray[i].split(',')[1]) / yAxisHeight * yAxisLabel); + log.debug('chartData[i] =' + chartData[i]); + } + return chartData; + }); + } + + + // The current test shows dots, not a line. This function gets the dots and normalizes their height. + getLineChartData(cssPart, axis = 'ValueAxis-1') { + let yAxisLabel = 0; + let yAxisHeight; + + // 1). get the maximim chart Y-Axis marker value + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`div.y-axis-div-wrapper > div > svg > g.${axis} > g:last-of-type`) + .getVisibleText() + .then(function (yLabel) { + yAxisLabel = yLabel.replace(/,/g, ''); + log.debug('yAxisLabel = ' + yAxisLabel); + return yLabel; + }) + // 2). find and save the y-axis pixel size (the chart height) + .then(function getRect() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('clipPath rect') + .getAttribute('height') + .then(function (theHeight) { + yAxisHeight = theHeight; + log.debug('theHeight = ' + theHeight); + return theHeight; + }); + }) + // 3). get the chart-wrapper elements + .then(function getChartWrapper() { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findAllByCssSelector(`.chart-wrapper circle[${cssPart}]`) + .then(function (chartTypes) { + + // 5). for each chart element, find the green circle, then the cy position + function getChartType(chart) { + return chart + .getAttribute('cy') + .then(function (cy) { + // log.debug(' yAxisHeight=' + yAxisHeight + ' yAxisLabel=' + yAxisLabel + ' cy=' + cy + + // ' ((yAxisHeight - cy)/yAxisHeight * yAxisLabel)=' + ((yAxisHeight - cy) / yAxisHeight * yAxisLabel)); + return Math.round((yAxisHeight - cy) / yAxisHeight * yAxisLabel); + }); + } + + // 4). pass the chartTypes to the getChartType function + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }); + }) + + .then(function (yCoords) { + return yCoords; + }); + } + + + // this is ALMOST identical to DiscoverPage.getBarChartData + getBarChartData() { + let yAxisLabel = 0; + let yAxisHeight; + + // 1). get the maximim chart Y-Axis marker value + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type') + .then(function setYAxisLabel(y) { + return y + .getVisibleText() + .then(function (yLabel) { + yAxisLabel = yLabel.replace(',', ''); + log.debug('yAxisLabel = ' + yAxisLabel); + return yLabel; + }); + }) + // 2). find and save the y-axis pixel size (the chart height) + .then(function getRect() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('rect.background') + .then(function getRectHeight(chartAreaObj) { + return chartAreaObj + .getAttribute('height') + .then(function (theHeight) { + yAxisHeight = theHeight; + log.debug('theHeight = ' + theHeight); + return theHeight; + }); + }); + }) + // 3). get the chart-wrapper elements + .then(function () { + return remote + .setFindTimeout(defaultFindTimeout * 2) + // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) + .findAllByCssSelector('svg > g > g.series > rect') // rect + .then(function (chartTypes) { + function getChartType(chart) { + return chart + .getAttribute('fill') + .then(function (fillColor) { + // we're only getting the Green Bars + if (fillColor === '#6eadc1') { + return chart + .getAttribute('height') + .then(function (barHeight) { + return Math.round(barHeight / yAxisHeight * yAxisLabel); + }); + } + }); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (bars) { + return bars; + }); + }); + } + + getHeatmapData() { + // 1). get the maximim chart Y-Axis marker value + return remote + .setFindTimeout(defaultFindTimeout * 2) + // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) + .findAllByCssSelector('svg > g > g.series rect') // rect + .then(function (chartTypes) { + log.debug('rects=' + chartTypes); + function getChartType(chart) { + return chart + .getAttribute('data-label'); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (labels) { + log.debug('labels=' + labels); + return labels; + }); + } + + getPieChartData() { + // 1). get the maximim chart Y-Axis marker value + return remote + .setFindTimeout(defaultFindTimeout * 2) + // path.slice:nth-child(11) + .findAllByCssSelector('path.slice') + .then(function (chartTypes) { + function getChartType(chart) { + return chart + .getAttribute('d') + .then(function (slice) { + return slice; + }); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (slices) { + log.debug('slices=' + slices); + return slices; + }); + } + + getChartAreaWidth() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('clipPath rect') + .getAttribute('width'); + } + getChartAreaHeight() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('clipPath rect') + .getAttribute('height'); + } + + getDataTableData() { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('table.table.table-condensed tbody') + .getVisibleText(); + } + + getMarkdownData() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('visualize.ng-isolate-scope') + .getVisibleText(); + } + + clickColumns() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('div.schemaEditors.ng-scope > div > div > button:nth-child(2)') + .click(); + } + + waitForToastMessageGone() { + return retry.try(function tryingForTime() { + return remote + .setFindTimeout(100) + .findAllByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') + .then(function toastMessage(messages) { + if (messages.length > 0) { + throw new Error('waiting for toast message to clear'); + } else { + log.debug('now messages = 0 "' + messages + '"'); + return messages; + } + }); + }); + } + + waitForVisualization() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('visualize-legend'); + } + + clickMapButton(zoomSelector) { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector(zoomSelector) + .click() + .then(() => { + return PageObjects.common.sleep(1000); + }) + .then(() => { + return PageObjects.header.waitUntilLoadingHasFinished(); + }); + } + + clickMapZoomIn() { + return this.clickMapButton('a.leaflet-control-zoom-in'); + } + + clickMapZoomOut() { + return this.clickMapButton('a.leaflet-control-zoom-out'); + } + + getMapZoomEnabled(zoomSelector) { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector(zoomSelector) + .getAttribute('class') + .then((element) => { + return !element.toString().includes('leaflet-disabled'); + }); + } + + getMapZoomInEnabled() { + return this.getMapZoomEnabled('a.leaflet-control-zoom-in'); + } + + getMapZoomOutEnabled() { + return this.getMapZoomEnabled('a.leaflet-control-zoom-out'); + } + + clickMapFitDataBounds() { + return this.clickMapButton('a.fa-crop'); + } + + getTileMapData() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('path.leaflet-clickable') + .then((chartTypes) => { + + function getChartType(chart) { + let color; + let radius; + return chart.getAttribute('stroke') + .then((stroke) => { + color = stroke; + }) + .then(() => { + return chart.getAttribute('d'); + }) + .then((d) => { + // scale the radius up (sometimes less than 1) and then round to int + radius = d.replace(/.*A(\d+\.\d+),.*/,'$1') * 10; + radius = Math.round(radius); + }) + .then(() => { + return { color: color, radius: radius }; + }); + } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then((circles) => { + return circles; + }); + } + + } + + return new VisualizePage(); +} diff --git a/test/functional/services/doc_table.js b/test/functional/services/doc_table.js new file mode 100644 index 0000000000000..4463556a38e8b --- /dev/null +++ b/test/functional/services/doc_table.js @@ -0,0 +1,39 @@ +export function DocTableProvider({ getService }) { + const testSubjects = getService('testSubjects'); + + class DocTable { + getTable() { + return testSubjects.find('docTable'); + } + + async getBodyRows(table) { + return await table.findAllByCssSelector('[data-test-subj~="docTableRow"]'); + } + + async getAnchorRow(table) { + return await table.findByCssSelector('[data-test-subj~="docTableAnchorRow"]'); + } + + async getRowExpandToggle(row) { + return await row.findByCssSelector('[data-test-subj~="docTableExpandToggleColumn"]'); + } + + async getDetailsRows(table) { + return await table.findAllByCssSelector('[data-test-subj~="docTableRow"] + tr'); + } + + async getRowActions(row) { + return await row.findAllByCssSelector('[data-test-subj~="docTableRowAction"]'); + } + + async getFields(row) { + return await row.findAllByCssSelector('[data-test-subj~="docTableField"]'); + } + + async getHeaderFields(table) { + return await table.findAllByCssSelector('[data-test-subj~="docTableHeaderField"]'); + } + } + + return new DocTable(); +} diff --git a/test/functional/services/es.js b/test/functional/services/es.js new file mode 100644 index 0000000000000..a4c0d5b054e25 --- /dev/null +++ b/test/functional/services/es.js @@ -0,0 +1,12 @@ +import { format as formatUrl } from 'url'; + +import elasticsearch from 'elasticsearch'; + +export function EsProvider({ getService }) { + const config = getService('config'); + + return new elasticsearch.Client({ + host: formatUrl(config.get('servers.elasticsearch')), + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); +} diff --git a/test/functional/services/es_archiver.js b/test/functional/services/es_archiver.js new file mode 100644 index 0000000000000..6e3e5b2aa561a --- /dev/null +++ b/test/functional/services/es_archiver.js @@ -0,0 +1,19 @@ +import { EsArchiver } from '../../../src/es_archiver'; + +export async function EsArchiverProvider({ getService }) { + const config = getService('config'); + const client = getService('es'); + const log = getService('log'); + + if (!config.get('esArchiver')) { + throw new Error(`esArchiver can't be used unless you specify it's config in your config file`); + } + + const dataDir = config.get('esArchiver.directory'); + + return new EsArchiver({ + client, + dataDir, + log, + }); +} diff --git a/test/functional/services/find.js b/test/functional/services/find.js new file mode 100644 index 0000000000000..bb21929e09e7f --- /dev/null +++ b/test/functional/services/find.js @@ -0,0 +1,28 @@ +export function FindProvider({ getService }) { + const log = getService('log'); + const config = getService('config'); + const remote = getService('remote'); + + const defaultFindTimeout = config.get('timeouts.find'); + + class Find { + byCssSelector(selector) { + log.debug(`findByCssSelector ${selector}`); + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(selector); + } + + async allByCssSelector(selector, timeout = defaultFindTimeout) { + log.debug('in findAllByCssSelector: ' + selector); + const remoteWithTimeout = remote.setFindTimeout(timeout); + let elements = await remoteWithTimeout.findAllByCssSelector(selector); + remoteWithTimeout.setFindTimeout(defaultFindTimeout); + if (!elements) elements = []; + log.debug(`Found ${elements.length} for selector ${selector}`); + return elements; + } + } + + return new Find(); +} diff --git a/test/functional/services/index.js b/test/functional/services/index.js new file mode 100644 index 0000000000000..2f4ca50c62206 --- /dev/null +++ b/test/functional/services/index.js @@ -0,0 +1,9 @@ +export { RetryProvider } from './retry'; +export { FindProvider } from './find'; +export { TestSubjectsProvider } from './test_subjects'; +export { RemoteProvider } from './remote'; +export { KibanaServerProvider } from './kibana_server'; +export { EsProvider } from './es'; +export { EsArchiverProvider } from './es_archiver'; +export { DocTableProvider } from './doc_table'; +export { PointSeriesVisProvider } from './point_series_vis'; diff --git a/test/functional/services/kibana_server/index.js b/test/functional/services/kibana_server/index.js new file mode 100644 index 0000000000000..a0a934e4cac9b --- /dev/null +++ b/test/functional/services/kibana_server/index.js @@ -0,0 +1 @@ +export { KibanaServerProvider } from './kibana_server'; diff --git a/test/functional/services/kibana_server/kibana_server.js b/test/functional/services/kibana_server/kibana_server.js new file mode 100644 index 0000000000000..0f7a4b1eecb49 --- /dev/null +++ b/test/functional/services/kibana_server/kibana_server.js @@ -0,0 +1,62 @@ +import { format as formatUrl } from 'url'; + +import { delay } from 'bluebird'; + +import { KibanaServerStatus } from './status'; +import { KibanaServerUiSettings } from './ui_settings'; +import { KibanaServerVersion } from './version'; + +export function KibanaServerProvider({ getService }) { + const log = getService('log'); + const config = getService('config'); + const lifecycle = getService('lifecycle'); + const es = getService('es'); + + class KibanaServer { + constructor() { + const url = formatUrl(config.get('servers.kibana')); + this.status = new KibanaServerStatus(url); + this.version = new KibanaServerVersion(this.status); + this.uiSettings = new KibanaServerUiSettings(log, es, this.version); + + lifecycle.on('beforeEachTest', async () => { + await this.waitForStabilization(); + }); + } + + async waitForStabilization() { + const { status, uiSettings } = this; + + let firstCheck = true; + const pingInterval = 500; // ping every 500 ms for an update + const startMs = Date.now(); + const timeout = config.get('timeouts.kibanaStabilize'); + + while (true) { + const exists = await uiSettings.existInEs(); + const state = await status.getOverallState(); + + if (exists && state === 'green') { + log.debug(`Kibana uiSettings are in elasticsearch and the server is reporting a green status`); + return; + } + + if (firstCheck) { + // we only log once, and only if we failed the first check + firstCheck = false; + log.debug(`waiting up to ${timeout}ms for kibana to stabilize...`); + } + + if (Date.now() - startMs < timeout) { + await delay(pingInterval); + continue; + } + + const docState = exists ? 'exists' : `doesn't exist`; + throw new Error(`Kibana never stabilized: config doc ${docState} and status is ${state}`); + } + } + } + + return new KibanaServer(); +} diff --git a/test/functional/services/kibana_server/status.js b/test/functional/services/kibana_server/status.js new file mode 100644 index 0000000000000..e6af5e5d41982 --- /dev/null +++ b/test/functional/services/kibana_server/status.js @@ -0,0 +1,25 @@ +import { resolve as resolveUrl } from 'url'; + +import { fromNode } from 'bluebird'; +import Wreck from 'wreck'; + +const get = async (url) => fromNode(cb => { + Wreck.get(url, { json: 'force' }, (err, resp, payload) => { + cb(err, payload); // resp is an Http#IncomingMessage, payload is the parsed version + }); +}); + +export class KibanaServerStatus { + constructor(kibanaServerUrl) { + this.kibanaServerUrl = kibanaServerUrl; + } + + async get() { + return await get(resolveUrl(this.kibanaServerUrl, './api/status')); + } + + async getOverallState() { + const status = await this.get(); + return status.status.overall.state; + } +} diff --git a/test/functional/services/kibana_server/ui_settings.js b/test/functional/services/kibana_server/ui_settings.js new file mode 100644 index 0000000000000..2ebe771fc1cbc --- /dev/null +++ b/test/functional/services/kibana_server/ui_settings.js @@ -0,0 +1,70 @@ +import { get } from 'lodash'; + +export class KibanaServerUiSettings { + constructor(log, es, kibanaVersion) { + this.es = es; + this.log = log; + this.kibanaVersion = kibanaVersion; + } + + async _docParams() { + const { kibanaVersion } = this; + return { + index: '.kibana', + type: 'config', + id: await kibanaVersion.get() + }; + } + + async existInEs() { + const { es } = this; + return await es.exists(await this._docParams()); + } + + async _read() { + const { log, es } = this; + try { + const doc = await es.get(await this._docParams()); + log.verbose('Fetched kibana config doc', doc); + return doc; + } catch (err) { + log.debug('Failed to fetch kibana config doc', err.message); + return; + } + } + + /* + ** Gets defaultIndex from the config doc. + */ + async getDefaultIndex() { + const { log } = this; + const doc = await this._read(); + const defaultIndex = get(doc, ['_source', 'defaultIndex']); + log.verbose('uiSettings.defaultIndex: %j', defaultIndex); + return defaultIndex; + } + + async replace(doc) { + const { log, es } = this; + log.debug('updating kibana config doc: %j', doc); + await es.index({ + ...(await this._docParams()), + refresh: 'wait_for', + body: doc, + }); + } + + /** + * Add fields to the config doc (like setting timezone and defaultIndex) + * @return {Promise} A promise that is resolved when elasticsearch has a response + */ + async update(doc) { + const { log, es } = this; + log.debug('updating kibana config doc: %j', doc); + await es.update({ + ...(await this._docParams()), + refresh: 'wait_for', + body: { doc, upsert: doc }, + }); + } +} diff --git a/test/functional/services/kibana_server/version.js b/test/functional/services/kibana_server/version.js new file mode 100644 index 0000000000000..7d2c3363c3059 --- /dev/null +++ b/test/functional/services/kibana_server/version.js @@ -0,0 +1,12 @@ +import { once } from 'lodash'; + +export class KibanaServerVersion { + constructor(kibanaStatus) { + this.kibanaStatus = kibanaStatus; + } + + get = once(async () => { + const status = await this.kibanaStatus.get(); + return status.version.number; + }) +} diff --git a/test/functional/services/point_series_vis.js b/test/functional/services/point_series_vis.js new file mode 100644 index 0000000000000..9aa33432f206d --- /dev/null +++ b/test/functional/services/point_series_vis.js @@ -0,0 +1,164 @@ +export function PointSeriesVisProvider({ getService }) { + const remote = getService('remote'); + const config = getService('config'); + const log = getService('log'); + + const defaultFindTimeout = config.get('timeouts.find'); + + class PointSeriesVis { + clickOptions() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Panel Settings') + .click(); + } + + clickAxisOptions() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByPartialLinkText('Metrics & Axes') + .click(); + } + + clickAddAxis() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('button[aria-label="Add value axis"]') + .click(); + } + + getValueAxesCount() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.kuiSideBarSection:contains("Value Axes") > .kuiSideBarSection') + .then(all => all.length); + } + + getSeriesCount() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.kuiSideBarSection:contains("Series") > .kuiSideBarSection') + .then(all => all.length); + } + + getRightValueAxes() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.axis-wrapper-right g.axis') + .then(all => all.length); + } + + getHistogramSeries() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.series.histogram') + .then(all => all.length); + } + + getGridLines() { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('g.grid > path') + .then(function (data) { + function getGridLine(gridLine) { + return gridLine + .getAttribute('d') + .then(dAttribute => { + const firstPoint = dAttribute.split('L')[0].replace('M', '').split(','); + return { x: parseFloat(firstPoint[0]), y: parseFloat(firstPoint[1]) }; + }); + } + const promises = data.map(getGridLine); + return Promise.all(promises); + }) + .then(function (gridLines) { + return gridLines; + }); + } + + toggleGridCategoryLines() { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('#showCategoryLines') + .click(); + } + + setGridValueAxis(axis) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select#gridAxis option[value="string:${axis}"]`) + .click(); + } + + toggleCollapsibleTitle(title) { + return remote + .setFindTimeout(defaultFindTimeout) + .findAllByCssSelector('.kuiSideBarCollapsibleTitle .kuiSideBarCollapsibleTitle__text') + .then(sidebarTitles => { + log.debug('found sidebar titles ' + sidebarTitles.length); + function getTitle(titleDiv) { + return titleDiv + .getVisibleText() + .then(titleString => { + log.debug('sidebar title ' + titleString); + if (titleString === title) { + log.debug('clicking sidebar title ' + titleString); + return titleDiv.click(); + } + }); + } + const sidebarTitlePromises = sidebarTitles.map(getTitle); + return Promise.all(sidebarTitlePromises); + }); + } + + setValue(newValue) { + return remote + .setFindTimeout(defaultFindTimeout * 2) + .findByCssSelector('button[ng-click="numberListCntr.add()"]') + .click() + .then(() => { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') + .clearValue(); + }) + .then(() => { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') + .type(newValue); + }); + } + + setValueAxisPosition(axis, position) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select#valueAxisPosition${axis} option[label="${position}"]`) + .click(); + } + + setCategoryAxisPosition(newValue) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select#categoryAxisPosition option[label="${newValue}"]`) + .click(); + } + + setSeriesAxis(series, axis) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select#seriesValueAxis${series} option[value="${axis}"]`) + .click(); + } + + setSeriesType(series, type) { + return remote + .setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select#seriesType${series} option[label="${type}"]`) + .click(); + } + } + + return new PointSeriesVis(); +} diff --git a/test/functional/services/remote/index.js b/test/functional/services/remote/index.js new file mode 100644 index 0000000000000..58e8d54cde355 --- /dev/null +++ b/test/functional/services/remote/index.js @@ -0,0 +1 @@ +export { RemoteProvider } from './remote'; diff --git a/test/functional/services/remote/interceptors.js b/test/functional/services/remote/interceptors.js new file mode 100644 index 0000000000000..37303a566f993 --- /dev/null +++ b/test/functional/services/remote/interceptors.js @@ -0,0 +1,21 @@ +import { modifyUrl } from '../../../../src/utils'; + +export const createRemoteInterceptors = remote => ({ + // inject _t=Date query param on navigation + async get(url) { + const urlWithTime = modifyUrl(url, parsed => { + parsed.query._t = Date.now(); + }); + + return await remote.get(urlWithTime); + }, + + // strip _t=Date query param when url is read + async getCurrentUrl() { + const current = await remote.getCurrentUrl(); + const currentWithoutTime = modifyUrl(current, parsed => { + delete parsed.query._t; + }); + return currentWithoutTime; + } +}); diff --git a/test/functional/services/remote/leadfoot_command.js b/test/functional/services/remote/leadfoot_command.js new file mode 100644 index 0000000000000..dbf70cdda0aa9 --- /dev/null +++ b/test/functional/services/remote/leadfoot_command.js @@ -0,0 +1,37 @@ +import { delay } from 'bluebird'; +import Command from 'leadfoot/Command'; + +import { createTunnel } from './leadfoot_tunnel'; +import { createSession } from './leadfoot_session'; + +const MINUTE = 1000 * 60; + +export async function initLeadfootCommand({ log, tunnelConfig, lifecycle }) { + return await Promise.race([ + (async () => { + await delay(2 * MINUTE); + throw new Error('remote failed to start within 2 minutes'); + })(), + + (async () => { + const tunnel = await createTunnel({ log, tunnelConfig, lifecycle }); + const session = await createSession({ log, tunnel }); + + const command = new Command(session); + + lifecycle.on('cleanup', async () => { + log.verbose('remote: closing leadfoot remote'); + await command.quit(); + + log.verbose('remote: closing digdug tunnel'); + await tunnel.stop(); + }); + + log.verbose('remote: created leadfoot command'); + // command looks like a promise beacuse it has a then function + // so we wrap it in an object to prevent our promise from trying to unwrap/resolve + // the remote + return { command }; + })() + ]); +} diff --git a/test/functional/services/remote/leadfoot_session.js b/test/functional/services/remote/leadfoot_session.js new file mode 100644 index 0000000000000..07cc5ddee693b --- /dev/null +++ b/test/functional/services/remote/leadfoot_session.js @@ -0,0 +1,16 @@ +import Server from 'leadfoot/Server'; + +// intern.Runner#loadTestModules +// intern.Runner#_createSuites +export async function createSession({ log, tunnel }) { + const server = new Server(tunnel.clientUrl); + log.verbose('remote: created leadfoot server'); + + const session = await server.createSession({ + browserName: 'chrome' + }); + + log.verbose('remote: created leadfoot session'); + + return session; +} diff --git a/test/functional/services/remote/leadfoot_tunnel.js b/test/functional/services/remote/leadfoot_tunnel.js new file mode 100644 index 0000000000000..9aa20acfd38cf --- /dev/null +++ b/test/functional/services/remote/leadfoot_tunnel.js @@ -0,0 +1,29 @@ +import SeleniumTunnel from 'digdug/SeleniumTunnel'; +import uuid from 'node-uuid'; + +// intern.Runner#loadTunnel +export async function createTunnel({ log, tunnelConfig }) { + const tunnel = new SeleniumTunnel({ + // https://git.io/vDnfv + ...tunnelConfig, + drivers: ['chrome'], + tunnelId: uuid.v4() + }); + + // override https://git.io/vDnfe so shutdown is fast + tunnel._stop = async () => { + const proc = tunnel._process; + + if (!proc) { + log.error('Update to stop tunnel, child process not found'); + return; + } + + const exitted = new Promise(resolve => proc.on('exit', resolve)); + tunnel._process.kill('SIGTERM'); + await exitted; + }; + + await tunnel.start(); + return tunnel; +} diff --git a/test/functional/services/remote/remote.js b/test/functional/services/remote/remote.js new file mode 100644 index 0000000000000..74543c8922054 --- /dev/null +++ b/test/functional/services/remote/remote.js @@ -0,0 +1,34 @@ +import { initLeadfootCommand } from './leadfoot_command'; +import { createRemoteInterceptors } from './interceptors'; + +export async function RemoteProvider({ getService }) { + const lifecycle = getService('lifecycle'); + const config = getService('config'); + const log = getService('log'); + + const { command } = await initLeadfootCommand({ + log, + lifecycle, + tunnelConfig: config.get('servers.webdriver'), + }); + + const interceptors = createRemoteInterceptors(command); + + log.info('Remote initialized'); + + return new Proxy({}, { + get(obj, prop) { + if (prop === 'then' || prop === 'catch' || prop === 'finally') { + // prevent the remote from being treated like a promise by + // hiding it's promise-like properties + return undefined; + } + + if (interceptors.hasOwnProperty(prop)) { + return interceptors[prop]; + } + + return command[prop]; + } + }); +} diff --git a/test/functional/services/retry.js b/test/functional/services/retry.js new file mode 100644 index 0000000000000..71aff727f3aea --- /dev/null +++ b/test/functional/services/retry.js @@ -0,0 +1,49 @@ +import bluebird from 'bluebird'; + +export function RetryProvider({ getService }) { + const config = getService('config'); + const log = getService('log'); + + class Retry { + tryForTime(timeout, block) { + const start = Date.now(); + const retryDelay = 502; + let lastTry = 0; + let finalMessage; + let prevMessage; + + function attempt() { + lastTry = Date.now(); + + if (lastTry - start > timeout) { + throw new Error('tryForTime timeout: ' + finalMessage); + } + + return bluebird + .try(block) + .catch(function tryForTimeCatch(err) { + if (err.message === prevMessage) { + log.debug('--- tryForTime failed again with the same message ...'); + } else { + prevMessage = err.message; + log.debug('--- tryForTime failure: ' + prevMessage); + } + finalMessage = err.stack || err.message; + return bluebird.delay(retryDelay).then(attempt); + }); + } + + return bluebird.try(attempt); + } + + try(block) { + return this.tryForTime(config.get('timeouts.try'), block); + } + + tryMethod(object, method, ...args) { + return this.try(() => object[method](...args)); + } + } + + return new Retry(); +} diff --git a/test/functional/services/test_subjects.js b/test/functional/services/test_subjects.js new file mode 100644 index 0000000000000..45c6b502d5bbf --- /dev/null +++ b/test/functional/services/test_subjects.js @@ -0,0 +1,56 @@ +import testSubjSelector from '@spalger/test-subj-selector'; +import { filter as filterAsync } from 'bluebird'; + +export function TestSubjectsProvider({ getService }) { + const log = getService('log'); + const retry = getService('retry'); + const remote = getService('remote'); + const find = getService('find'); + const config = getService('config'); + const defaultFindTimeout = config.get('timeouts.find'); + + class TestSubjects { + async exists(selector) { + log.debug(`doesTestSubjectExist ${selector}`); + + const exists = await remote + .setFindTimeout(1000) + .findDisplayedByCssSelector(testSubjSelector(selector)) + .then(() => true) + .catch(() => false); + + remote.setFindTimeout(defaultFindTimeout); + return exists; + } + + async click(selector) { + return await retry.try(async () => { + await this.find(selector).click(); + }); + } + + find(selector, timeout = defaultFindTimeout) { + log.debug('in findTestSubject: ' + testSubjSelector(selector)); + let originalFindTimeout = null; + return remote + .getFindTimeout() + .then((findTimeout) => originalFindTimeout = findTimeout) + .setFindTimeout(timeout) + .findDisplayedByCssSelector(testSubjSelector(selector)) + .then( + (result) => remote.setFindTimeout(originalFindTimeout) + .finally(() => result), + (error) => remote.setFindTimeout(originalFindTimeout) + .finally(() => { throw error; }), + ); + } + + async findAll(selector) { + log.debug('in findAllTestSubjects: ' + testSubjSelector(selector)); + const all = await find.allByCssSelector(testSubjSelector(selector)); + return await filterAsync(all, el => el.isDisplayed()); + } + } + + return new TestSubjects(); +} diff --git a/test/functional/status_page/index.js b/test/functional/status_page/index.js deleted file mode 100644 index a19b0aa2bfb05..0000000000000 --- a/test/functional/status_page/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - bdd, -} from '../../support'; - -import PageObjects from '../../support/page_objects'; - -import expect from 'expect.js'; - -bdd.describe('status page', function () { - bdd.before(function () { - return PageObjects.common.navigateToApp('status_page'); - }); - - bdd.it('should show the kibana plugin as ready', function () { - const self = this; - - return PageObjects.common.tryForTime(6000, function () { - return PageObjects.common.findTestSubject('statusBreakdown') - .getVisibleText() - .then(function (text) { - PageObjects.common.saveScreenshot('Status'); - expect(text.indexOf('plugin:kibana')).to.be.above(-1); - }); - }) - .catch(PageObjects.common.createErrorHandler(self)); - }); -}); diff --git a/test/intern.js b/test/intern.js deleted file mode 100644 index 095ae1cb8000f..0000000000000 --- a/test/intern.js +++ /dev/null @@ -1,22 +0,0 @@ -define(function (require) { - const serverConfig = require('intern/dojo/node!./server_config'); - return Object.assign({ - debug: true, - capabilities: { - 'idle-timeout': 99 - }, - environments: [{ - browserName: 'chrome' - }], - tunnelOptions: serverConfig.servers.webdriver, - functionalSuites: [ - 'test/functional/index' - ], - - excludeInstrumentation: /.*/, - - defaultTimeout: 90000, - defaultTryTimeout: 40000, // tryForTime could include multiple 'find timeouts' - defaultFindTimeout: 10000 // this is how long we try to find elements on page - }, serverConfig); -}); diff --git a/test/intern_api.js b/test/intern_api.js deleted file mode 100644 index 08beab27fb3b4..0000000000000 --- a/test/intern_api.js +++ /dev/null @@ -1,14 +0,0 @@ -define({ - suites: [ - 'test/unit/api/ingest/index', - 'test/unit/api/search/index', - 'test/unit/api/scripts/index' - ], - excludeInstrumentation: /(fixtures|node_modules)\//, - loaderOptions: { - paths: { - 'bluebird': './node_modules/bluebird/js/browser/bluebird.js', - 'moment': './node_modules/moment/moment.js' - } - } -}); diff --git a/test/intern_visual_regression.js b/test/intern_visual_regression.js deleted file mode 100644 index 778e17f0cbf8b..0000000000000 --- a/test/intern_visual_regression.js +++ /dev/null @@ -1,24 +0,0 @@ -define(function (require) { - const serverConfig = require('intern/dojo/node!./server_config'); - return Object.assign({ - debug: true, - capabilities: { - 'selenium-version': '2.53.0', - // must match URL in tasks/config/downloadSelenium.js - 'idle-timeout': 99 - }, - environments: [{ - browserName: 'chrome' - }], - tunnelOptions: serverConfig.servers.webdriver, - functionalSuites: [ - 'test/visual_regression/index' - ], - - excludeInstrumentation: /.*/, - - defaultTimeout: 90000, - defaultTryTimeout: 40000, // tryForTime could include multiple 'find timeouts' - defaultFindTimeout: 10000 // this is how long we try to find elements on page - }, serverConfig); -}); diff --git a/test/server_config.js b/test/server_config.js index a2ba55cca1cd9..0b4f39da7415b 100644 --- a/test/server_config.js +++ b/test/server_config.js @@ -27,7 +27,7 @@ module.exports = { }, apps: { status_page: { - pathname: 'status' + pathname: '/status' }, discover: { pathname: kibanaURL, diff --git a/test/support/env_setup.js b/test/support/env_setup.js deleted file mode 100644 index c3892a1ed2036..0000000000000 --- a/test/support/env_setup.js +++ /dev/null @@ -1,10 +0,0 @@ -const defaults = require('lodash').defaults; -const babelOptions = require('../../src/optimize/babel/options'); - -require('source-map-support').install(); -require('babel-register')(defaults({ - ignore: [ - 'test/fixtures/scenarios/**/*', - 'node_modules/**', - ] -}, babelOptions.node)); diff --git a/test/support/index.js b/test/support/index.js deleted file mode 100644 index b07d48a3a694c..0000000000000 --- a/test/support/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import { resolve } from 'path'; -import url from 'url'; - -import { - BddWrapper, - EsClient, - Log -} from './utils'; - -import { EsArchiver } from '../../src/es_archiver'; - -// Intern values provided via the root index file of the test suite. -const kbnInternVars = global.__kibana__intern__; -exports.intern = kbnInternVars.intern; -exports.bdd = new BddWrapper(kbnInternVars.bdd); - -// Config options -const config = exports.config = kbnInternVars.intern.config; -exports.defaultTimeout = config.defaultTimeout; -exports.defaultTryTimeout = config.defaultTryTimeout; -exports.defaultFindTimeout = config.defaultFindTimeout; - -// Helper instances -exports.esClient = new EsClient(url.format(config.servers.elasticsearch)); -exports.esArchiver = new EsArchiver({ - client: exports.esClient.client, - dataDir: resolve(__dirname, '../../src/fixtures/es_archives'), - log: Log, -}); - -// TODO: We're using this facade to avoid breaking existing functionality as -// we migrate test suites to the PageObject service. Once they're all migrated -// over, we can delete this facade code. -exports.init = function init(remote) { - exports.remote = remote; -}; diff --git a/test/support/page_objects/common.js b/test/support/page_objects/common.js deleted file mode 100644 index 054785632d2a4..0000000000000 --- a/test/support/page_objects/common.js +++ /dev/null @@ -1,398 +0,0 @@ - -import bluebird, { - promisify, - filter as filterAsync -} from 'bluebird'; -import fs from 'fs'; -import _ from 'lodash'; -import mkdirp from 'mkdirp'; -import path from 'path'; -import testSubjSelector from '@spalger/test-subj-selector'; -import { - format, - parse -} from 'url'; - -import getUrl from '../../utils/get_url'; - -import { - config, - defaultFindTimeout, - defaultTryTimeout, - esClient -} from '../index'; - -import PageObjects from './index'; - -import { - Log, - Try -} from '../utils'; - -const mkdirpAsync = promisify(mkdirp); -const writeFileAsync = promisify(fs.writeFile); - -export default class Common { - - init(remote) { - function injectTimestampQuery(func, url) { - const formatted = modifyQueryString(url, function (parsed) { - parsed.query._t = Date.now(); - }); - return func.call(this, formatted); - } - - function removeTimestampQuery(func) { - return func.call(this) - .then(function (url) { - return modifyQueryString(url, function (parsed) { - parsed.query = _.omit(parsed.query, '_t'); - }); - }); - } - - function modifyQueryString(url, func) { - const parsed = parse(url, true); - if (parsed.query === null) { - parsed.query = {}; - } - func(parsed); - return format(_.pick(parsed, 'protocol', 'hostname', 'port', 'pathname', 'query', 'hash', 'auth')); - } - - this.remote = remote; - if (remote.get.wrapper !== injectTimestampQuery) { - this.remote.get = _.wrap(this.remote.get, injectTimestampQuery); - remote.get.wrapper = injectTimestampQuery; - this.remote.getCurrentUrl = _.wrap(this.remote.getCurrentUrl, removeTimestampQuery); - } - } - - waitUntilUrlIncludes(path) { - return this.tryForTime(defaultFindTimeout, async () => { - const url = await this.remote.getCurrentUrl(); - if (!url.includes(path)) throw new Error('Url not found'); - }); - } - - getHostPort() { - return getUrl.baseUrl(config.servers.kibana); - } - - getEsHostPort() { - return getUrl.baseUrl(config.servers.elasticsearch); - } - - navigateToUrl(appName, subUrl) { - const appConfig = Object.assign({}, config.apps[appName], { - // Overwrite the default hash with the URL we really want. - hash: `${appName}/${subUrl}`, - }); - const appUrl = getUrl.noAuth(config.servers.kibana, appConfig); - return this.remote.get(appUrl); - } - - navigateToApp(appName) { - const self = this; - const appUrl = getUrl.noAuth(config.servers.kibana, config.apps[appName]); - self.debug('navigating to ' + appName + ' url: ' + appUrl); - - function navigateTo(url) { - return self.try(function () { - // since we're using hash URLs, always reload first to force re-render - return esClient.getDefaultIndex() - .then(function (defaultIndex) { - if (appName === 'discover' || appName === 'visualize' || appName === 'dashboard') { - if (!defaultIndex) { - // https://github.com/elastic/kibana/issues/7496 - // Even though most tests are using esClient to set the default index, sometimes Kibana clobbers - // that change. If we got here, fix it. - self.debug(' >>>>>>>> WARNING Navigating to [' + appName + '] with defaultIndex=' + defaultIndex); - self.debug(' >>>>>>>> Setting defaultIndex to "logstash-*""'); - return esClient.updateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }); - } - } - }) - .then(function () { - self.debug('navigate to: ' + url); - return self.remote.get(url); - }) - .then(function () { - return self.sleep(700); - }) - .then(function () { - self.debug('returned from get, calling refresh'); - return self.remote.refresh(); - }) - .then(function () { - return self.remote.getCurrentUrl(); - }) - .then(function (currentUrl) { - const loginPage = new RegExp('login').test(currentUrl); - if (loginPage) { - self.debug('Found loginPage username = ' - + config.servers.kibana.username); - return PageObjects.shield.login(config.servers.kibana.username, - config.servers.kibana.password) - .then(function () { - return self.remote.getCurrentUrl(); - }); - } else { - return currentUrl; - } - }) - .then(function (currentUrl) { - currentUrl = currentUrl.replace(/\/\/\w+:\w+@/, '//'); - const maxAdditionalLengthOnNavUrl = 230; - // On several test failures at the end of the TileMap test we try to navigate back to - // Visualize so we can create the next Vertical Bar Chart, but we can see from the - // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" - // with a new timestamped URL go? I thought that sleep(700) between the get and the - // refresh would solve the problem but didn't seem to always work. - // So this hack fails the navSuccessful check if the currentUrl doesn't match the - // appUrl plus up to 230 other chars. - // Navigating to Settings when there is a default index pattern has a URL length of 196 - // (from debug output). Some other tabs may also be long. But a rather simple configured - // visualization is about 1000 chars long. So at least we catch that case. - - // Browsers don't show the ':port' if it's 80 or 443 so we have to - // remove that part so we can get a match in the tests. - const navSuccessful = new RegExp(appUrl.replace(':80','').replace(':443','') - + '.{0,' + maxAdditionalLengthOnNavUrl + '}$') - .test(currentUrl); - - if (!navSuccessful) { - const msg = 'App failed to load: ' + appName + - ' in ' + defaultFindTimeout + 'ms' + - ' appUrl = ' + appUrl + - ' currentUrl = ' + currentUrl; - self.debug(msg); - throw new Error(msg); - } - - return currentUrl; - }); - }); - } - - return self.tryForTime(defaultTryTimeout * 3, () => { - return navigateTo(appUrl) - .then(function (currentUrl) { - let lastUrl = currentUrl; - return self.try(function () { - // give the app time to update the URL - return self.sleep(501) - .then(function () { - return self.remote.getCurrentUrl(); - }) - .then(function (currentUrl) { - self.debug('in navigateTo url = ' + currentUrl); - if (lastUrl !== currentUrl) { - lastUrl = currentUrl; - throw new Error('URL changed, waiting for it to settle'); - } - }); - }); - }) - .then(async () => { - if (appName === 'status_page') return; - if (await self.doesTestSubjectExist('statusPageContainer')) { - throw new Error('Navigation ended up at the status page.'); - } - }); - }); - } - - runScript(fn, timeout) { - const self = this; - // by default, give the app 10 seconds to load - timeout = timeout || 10000; - - // wait for deps on window before running script - return self.remote - .setExecuteAsyncTimeout(timeout) - .executeAsync(function (done) { - const interval = setInterval(function () { - const ready = (document.readyState === 'complete'); - const hasJQuery = !!window.$; - - if (ready && hasJQuery) { - console.log('doc ready, jquery loaded'); - clearInterval(interval); - done(); - } - }, 10); - }).then(function () { - return self.remote.execute(fn); - }); - } - - tryForTime(timeout, block) { - return Try.tryForTime(timeout, block); - } - - try(block) { - return Try.try(block); - } - - tryMethod(object, method, ...args) { - return this.try(() => object[method](...args)); - } - - log(...args) { - Log.log(...args); - } - - debug(...args) { - Log.debug(...args); - } - - sleep(sleepMilliseconds) { - const self = this; - self.debug('... sleep(' + sleepMilliseconds + ') start'); - - return bluebird.resolve().delay(sleepMilliseconds) - .then(function () { self.debug('... sleep(' + sleepMilliseconds + ') end'); }); - } - - createErrorHandler(testObj) { - const testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name; - return error => { - return this.remote.getCurrentUrl() - .then(url => { - PageObjects.common.debug(`Failure at URL ${url}`); - const now = Date.now(); - const fileName = `failure_${now}_${testName}`; - - return this.saveScreenshot(fileName, true) - .then(function () { - throw error; - }); - }); - }; - } - - async saveScreenshot(fileName, isFailure = false) { - try { - const directoryName = isFailure ? 'failure' : 'session'; - const directoryPath = path.resolve(`test/screenshots/${directoryName}`); - const filePath = path.resolve(directoryPath, `${fileName}.png`); - this.debug(`Taking screenshot "${filePath}"`); - - const screenshot = await this.remote.takeScreenshot(); - await mkdirpAsync(directoryPath); - await writeFileAsync(filePath, screenshot); - } catch (err) { - this.log(`SCREENSHOT FAILED: ${err}`); - } - } - - async doesCssSelectorExist(selector) { - PageObjects.common.debug(`doesCssSelectorExist ${selector}`); - const exists = await this.remote - .setFindTimeout(1000) - .findByCssSelector(selector) - .then(() => true) - .catch(() => false); - await this.remote.setFindTimeout(defaultFindTimeout); - - PageObjects.common.debug(`exists? ${exists}`); - return exists; - } - - findByCssSelector(selector) { - PageObjects.common.debug(`findByCssSelector ${selector}`); - return this.remote.setFindTimeout(defaultFindTimeout).findByCssSelector(selector); - } - - async doesTestSubjectExist(selector) { - PageObjects.common.debug(`doesTestSubjectExist ${selector}`); - const exists = await this.remote - .setFindTimeout(1000) - .findDisplayedByCssSelector(testSubjSelector(selector)) - .then(() => true) - .catch(() => false); - this.remote.setFindTimeout(defaultFindTimeout); - return exists; - } - - async clickTestSubject(selector) { - return await Try.try(async () => { - await this.findTestSubject(selector).click(); - }); - } - - findTestSubject(selector, timeout = defaultFindTimeout) { - this.debug('in findTestSubject: ' + testSubjSelector(selector)); - let originalFindTimeout = null; - return this.remote - .getFindTimeout() - .then((findTimeout) => originalFindTimeout = findTimeout) - .setFindTimeout(timeout) - .findDisplayedByCssSelector(testSubjSelector(selector)) - .then( - (result) => this.remote.setFindTimeout(originalFindTimeout) - .finally(() => result), - (error) => this.remote.setFindTimeout(originalFindTimeout) - .finally(() => { throw error; }), - ); - } - - async findAllTestSubjects(selector) { - this.debug('in findAllTestSubjects: ' + testSubjSelector(selector)); - const all = await this.findAllByCssSelector(testSubjSelector(selector)); - return await filterAsync(all, el => el.isDisplayed()); - } - - async findAllByCssSelector(selector, timeout = defaultFindTimeout) { - this.debug('in findAllByCssSelector: ' + selector); - const remote = this.remote.setFindTimeout(timeout); - let elements = await remote.findAllByCssSelector(selector); - this.remote.setFindTimeout(defaultFindTimeout); - if (!elements) elements = []; - this.debug(`Found ${elements.length} for selector ${selector}`); - return elements; - } - - async getSharedItemTitleAndDescription() { - const element = await this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('[data-shared-item]'); - - return { - title: await element.getAttribute('data-title'), - description: await element.getAttribute('data-description') - }; - } - - /** - * Makes sure the modal overlay is not showing, tries a few times in case it is in the process of hiding. - */ - async ensureModalOverlayHidden() { - return PageObjects.common.try(async () => { - const shown = await this.doesTestSubjectExist('modalOverlay'); - if (shown) { - throw new Error('Modal overlay is showing'); - } - }); - } - - async clickConfirmOnModal() { - this.debug('Clicking modal confirm'); - await this.findTestSubject('confirmModalConfirmButton').click(); - await this.ensureModalOverlayHidden(); - } - - async clickCancelOnModal() { - this.debug('Clicking modal cancel'); - await this.findTestSubject('confirmModalCancelButton').click(); - await this.ensureModalOverlayHidden(); - } - - async isConfirmModalOpen() { - let isOpen = true; - await this.findTestSubject('confirmModalCancelButton', 2000).catch(() => isOpen = false); - await this.remote.setFindTimeout(defaultFindTimeout); - return isOpen; - } -} diff --git a/test/support/page_objects/console_page.js b/test/support/page_objects/console_page.js deleted file mode 100644 index a51778c899aca..0000000000000 --- a/test/support/page_objects/console_page.js +++ /dev/null @@ -1,64 +0,0 @@ -import Bluebird from 'bluebird'; - -import PageObjects from './'; - -async function getVisibleTextFromAceEditor(editor) { - const lines = await editor.findAllByClassName('ace_line_group'); - const linesText = await Bluebird.map(lines, l => l.getVisibleText()); - return linesText.join('\n'); -} - -export default class ConsolePage { - init(remote) { - this.remote = remote; - } - - async getRequestEditor() { - return await PageObjects.common.findTestSubject('request-editor'); - } - - async getRequest() { - const requestEditor = await this.getRequestEditor(); - return await getVisibleTextFromAceEditor(requestEditor); - } - - async getResponse() { - const responseEditor = await PageObjects.common.findTestSubject('response-editor'); - return await getVisibleTextFromAceEditor(responseEditor); - } - - async clickPlay() { - await PageObjects.common.clickTestSubject('send-request-button'); - } - - async collapseHelp() { - await PageObjects.common.clickTestSubject('help-close-button'); - } - - async openSettings() { - await PageObjects.common.clickTestSubject('consoleSettingsButton'); - } - - async setFontSizeSetting(newSize) { - await this.openSettings(); - - // while the settings form opens/loads this may fail, so retry for a while - await PageObjects.common.try(async () => { - const fontSizeInput = await PageObjects.common.findTestSubject('setting-font-size-input'); - await fontSizeInput.clearValue(); - await fontSizeInput.click(); - await fontSizeInput.type(String(newSize)); - }); - - await PageObjects.common.clickTestSubject('settings-save-button'); - } - - async getFontSize(editor) { - const aceLine = await editor.findByClassName('ace_line'); - return await aceLine.getComputedStyle('font-size'); - } - - async getRequestFontSize() { - return await this.getFontSize(await this.getRequestEditor()); - } -} diff --git a/test/support/page_objects/context_page.js b/test/support/page_objects/context_page.js deleted file mode 100644 index bb201066ebe6f..0000000000000 --- a/test/support/page_objects/context_page.js +++ /dev/null @@ -1,58 +0,0 @@ -import rison from 'rison-node'; - -import { config } from '../'; -import PageObjects from './'; -import getUrl from '../../utils/get_url'; - - -const DEFAULT_INITIAL_STATE = { - columns: ['@message'], -}; - -export default class ContextPage { - init(remote) { - this.remote = remote; - } - - async navigateTo(indexPattern, anchorType, anchorId, overrideInitialState = {}) { - const initialState = rison.encode({ - ...DEFAULT_INITIAL_STATE, - ...overrideInitialState, - }); - const appUrl = getUrl.noAuth(config.servers.kibana, { - ...config.apps.context, - hash: `${config.apps.context.hash}/${indexPattern}/${anchorType}/${anchorId}?_a=${initialState}`, - }); - - await this.remote.get(appUrl); - await this.remote.refresh(); - await this.waitUntilContextLoadingHasFinished(); - } - - getPredecessorCountPicker() { - return PageObjects.common.findTestSubject('predecessorCountPicker'); - } - - getSuccessorCountPicker() { - return PageObjects.common.findTestSubject('successorCountPicker'); - } - - getPredecessorLoadMoreButton() { - return PageObjects.common.findTestSubject('predecessorLoadMoreButton'); - } - - getSuccessorLoadMoreButton() { - return PageObjects.common.findTestSubject('predecessorLoadMoreButton'); - } - - waitUntilContextLoadingHasFinished() { - return PageObjects.common.try(async () => { - if ( - !(await this.getSuccessorLoadMoreButton().isEnabled()) - || !(await this.getPredecessorLoadMoreButton().isEnabled()) - ) { - throw new Error('loading context rows'); - } - }); - } -} diff --git a/test/support/page_objects/dashboard_page.js b/test/support/page_objects/dashboard_page.js deleted file mode 100644 index af64aa6887b39..0000000000000 --- a/test/support/page_objects/dashboard_page.js +++ /dev/null @@ -1,444 +0,0 @@ -import _ from 'lodash'; -import { defaultFindTimeout } from '../'; - -import { - DashboardConstants -} from '../../../src/core_plugins/kibana/public/dashboard/dashboard_constants'; - -import { - esArchiver, - esClient, -} from '../'; - -import PageObjects from './'; - -export default class DashboardPage { - - init(remote) { - this.remote = remote; - this.findTimeout = this.remote.setFindTimeout(defaultFindTimeout); - } - - async initTests() { - const logstash = esArchiver.loadIfNeeded('logstash_functional'); - await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*' }); - - PageObjects.common.debug('load kibana index with visualizations'); - await esArchiver.load('dashboard'); - - await PageObjects.common.navigateToApp('dashboard'); - - return logstash; - } - - /** - * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). - * @returns {Promise} - */ - async onDashboardLandingPage() { - PageObjects.common.debug(`onDashboardLandingPage`); - const exists = await PageObjects.common.doesCssSelectorExist(`a[href="#${DashboardConstants.LANDING_PAGE_PATH}"]`); - return !exists; - } - - async gotoDashboardLandingPage() { - PageObjects.common.debug('gotoDashboardLandingPage'); - const onPage = await this.onDashboardLandingPage(); - if (!onPage) { - await PageObjects.common.try(async () => { - const goToDashboardLink = - await PageObjects.common.findByCssSelector(`a[href="#${DashboardConstants.LANDING_PAGE_PATH}"]`); - await goToDashboardLink.click(); - // Once the searchFilter can be found, we know the page finished loading. - await PageObjects.common.findTestSubject('searchFilter'); - }); - } - } - - async getQuery() { - const queryObject = await PageObjects.common.findTestSubject('dashboardQuery'); - return queryObject.getProperty('value'); - } - - appendQuery(query) { - return PageObjects.common.findTestSubject('dashboardQuery').type(query); - } - - clickFilterButton() { - return PageObjects.common.findTestSubject('dashboardQueryFilterButton') - .click(); - } - - clickEdit() { - PageObjects.common.debug('Clicking edit'); - return PageObjects.common.findTestSubject('dashboardEditMode') - .click(); - } - - getIsInViewMode() { - PageObjects.common.debug('getIsInViewMode'); - return PageObjects.common.doesTestSubjectExist('dashboardEditMode'); - } - - clickCancelOutOfEditMode() { - PageObjects.common.debug('Clicking cancel'); - return PageObjects.common.findTestSubject('dashboardViewOnlyMode').click(); - } - - clickNewDashboard() { - return PageObjects.common.clickTestSubject('newDashboardLink'); - } - - clickAddVisualization() { - return PageObjects.common.clickTestSubject('dashboardAddPanelButton'); - } - - clickOptions() { - return PageObjects.common.clickTestSubject('dashboardOptionsButton'); - } - - isOptionsOpen() { - PageObjects.common.debug('isOptionsOpen'); - return PageObjects.common.doesTestSubjectExist('dashboardDarkThemeCheckbox'); - } - - async openOptions() { - PageObjects.common.debug('openOptions'); - const isOpen = await this.isOptionsOpen(); - if (!isOpen) { - return PageObjects.common.clickTestSubject('dashboardOptionsButton'); - } - } - - async isDarkThemeOn() { - PageObjects.common.debug('isDarkThemeOn'); - await this.openOptions(); - const darkThemeCheckbox = await PageObjects.common.findTestSubject('dashboardDarkThemeCheckbox'); - return await darkThemeCheckbox.getProperty('checked'); - } - - async useDarkTheme(on) { - await this.openOptions(); - const isDarkThemeOn = await this.isDarkThemeOn(); - if (isDarkThemeOn !== on) { - return PageObjects.common.clickTestSubject('dashboardDarkThemeCheckbox'); - } - } - - filterVizNames(vizName) { - return this.findTimeout - .findByCssSelector('input[placeholder="Visualizations Filter..."]') - .click() - .pressKeys(vizName); - } - - clickVizNameLink(vizName) { - return this.findTimeout - .findByPartialLinkText(vizName) - .click(); - } - - closeAddVizualizationPanel() { - PageObjects.common.debug('closeAddVizualizationPanel'); - return this.findTimeout - .findByCssSelector('i.fa fa-chevron-up') - .click(); - } - - addVisualization(vizName) { - return this.clickAddVisualization() - .then(() => { - PageObjects.common.debug('filter visualization (' + vizName + ')'); - return this.filterVizNames(vizName); - }) - // this second wait is usually enough to avoid the - // 'stale element reference: element is not attached to the page document' - // on the next step - .then(() => { - return PageObjects.common.sleep(1000); - }) - .then(() => { - // but wrap in a try loop since it can still happen - return PageObjects.common.try(() => { - PageObjects.common.debug('click visualization (' + vizName + ')'); - return this.clickVizNameLink(vizName); - }); - }) - // this second click of 'Add' collapses the Add Visualization pane - .then(() => { - return this.clickAddVisualization(); - }); - } - - async renameDashboard(dashName) { - PageObjects.common.debug(`Naming dashboard ` + dashName); - await PageObjects.common.findTestSubject('dashboardRenameButton').click(); - await this.findTimeout.findById('dashboardTitle').type(dashName); - } - - /** - * - * @param dashName {String} - * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean}} - */ - async saveDashboard(dashName, saveOptions = {}) { - await this.enterDashboardTitleAndClickSave(dashName, saveOptions); - - await PageObjects.header.waitUntilLoadingHasFinished(); - - // verify that green message at the top of the page. - // it's only there for about 5 seconds - await PageObjects.common.try(() => { - PageObjects.common.debug('verify toast-message for saved dashboard'); - return this.findTimeout - .findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') - .getVisibleText(); - }); - } - - /** - * - * @param dashboardTitle {String} - * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean}} - */ - async enterDashboardTitleAndClickSave(dashboardTitle, saveOptions = {}) { - await PageObjects.common.clickTestSubject('dashboardSaveButton'); - - await PageObjects.header.waitUntilLoadingHasFinished(); - - PageObjects.common.debug('entering new title'); - await this.findTimeout.findById('dashboardTitle').type(dashboardTitle); - - if (saveOptions.storeTimeWithDashboard !== undefined) { - await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard); - } - - if (saveOptions.saveAsNew !== undefined) { - await this.setSaveAsNewCheckBox(saveOptions.saveAsNew); - } - - await PageObjects.common.try(() => { - PageObjects.common.debug('clicking final Save button for named dashboard'); - return this.findTimeout.findByCssSelector('.btn-primary').click(); - }); - } - - clickDashboardByLinkText(dashName) { - PageObjects.common.debug('clickDashboardByLinkText: ' + dashName); - return PageObjects.common.try(() => this.findTimeout.findByLinkText(dashName).click()); - } - - async searchForDashboardWithName(dashName) { - PageObjects.common.debug(`searchForDashboardWithName: ${dashName}`); - - await this.gotoDashboardLandingPage(); - - await PageObjects.common.try(async () => { - const searchFilter = await PageObjects.common.findTestSubject('searchFilter'); - await searchFilter.click(); - // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. - await searchFilter.type(dashName.replace('-',' ')); - }); - - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async getDashboardCountWithName(dashName) { - PageObjects.common.debug(`getDashboardCountWithName: ${dashName}`); - - await this.searchForDashboardWithName(dashName); - const links = await this.findTimeout.findAllByLinkText(dashName); - return links.length; - } - - // use the search filter box to narrow the results down to a single - // entry, or at least to a single page of results - async loadSavedDashboard(dashName) { - PageObjects.common.debug(`Load Saved Dashboard ${dashName}`); - - await this.searchForDashboardWithName(dashName); - await this.clickDashboardByLinkText(dashName); - return PageObjects.header.waitUntilLoadingHasFinished(); - } - - getPanelTitles() { - PageObjects.common.debug('in getPanelTitles'); - return this.findTimeout - .findAllByCssSelector('span.panel-title') - .then(function (titleObjects) { - - function getTitles(chart) { - return chart.getAttribute('title'); - } - - const getTitlePromises = titleObjects.map(getTitles); - return Promise.all(getTitlePromises); - }); - } - - getPanelSizeData() { - PageObjects.common.debug('in getPanelSizeData'); - return this.findTimeout - .findAllByCssSelector('li.gs-w') - .then(function (titleObjects) { - - function getTitles(chart) { - let obj = {}; - return chart.getAttribute('data-col') - .then(theData => { - obj = { dataCol:theData }; - return chart; - }) - .then(chart => { - return chart.getAttribute('data-row') - .then(theData => { - obj.dataRow = theData; - return chart; - }); - }) - .then(chart => { - return chart.getAttribute('data-sizex') - .then(theData => { - obj.dataSizeX = theData; - return chart; - }); - }) - .then(chart => { - return chart.getAttribute('data-sizey') - .then(theData => { - obj.dataSizeY = theData; - return chart; - }); - }) - .then(chart => { - return chart.findByCssSelector('span.panel-title') - .then(function (titleElement) { - return titleElement.getAttribute('title'); - }) - .then(theData => { - obj.title = theData; - return obj; - }); - }); - } - - const getTitlePromises = titleObjects.map(getTitles); - return Promise.all(getTitlePromises); - }); - } - - getTestVisualizations() { - return [ - { name: 'Visualization PieChart', description: 'PieChart' }, - { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, - { name: 'Visualization漢字 AreaChart', description: 'AreaChart' }, - { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, - { name: 'Visualization漢字 LineChart', description: 'LineChart' }, - { name: 'Visualization TileMap', description: 'TileMap' }, - { name: 'Visualization MetricChart', description: 'MetricChart' } - ]; - } - - getTestVisualizationNames() { - return this.getTestVisualizations().map(visualization => visualization.name); - } - - addVisualizations(visualizations) { - return visualizations.reduce(function (promise, vizName) { - return promise - .then(() => PageObjects.dashboard.addVisualization(vizName)); - }, Promise.resolve()); - } - - clickAddNewVisualizationLink() { - return PageObjects.common.clickTestSubject('addNewSavedObjectLink'); - } - - async setTimepickerInDataRange() { - const fromTime = '2015-09-19 06:31:44.000'; - const toTime = '2015-09-23 18:31:44.000'; - await PageObjects.header.setAbsoluteRange(fromTime, toTime); - } - - async setSaveAsNewCheckBox(checked) { - PageObjects.common.debug('saveAsNewCheckbox: ' + checked); - const saveAsNewCheckbox = await PageObjects.common.findTestSubject('saveAsNewCheckbox'); - const isAlreadyChecked = await saveAsNewCheckbox.getProperty('checked'); - if (isAlreadyChecked !== checked) { - PageObjects.common.debug('Flipping save as new checkbox'); - await saveAsNewCheckbox.click(); - } - } - - async setStoreTimeWithDashboard(checked) { - PageObjects.common.debug('Storing time with dashboard: ' + checked); - const storeTimeCheckbox = await PageObjects.common.findTestSubject('storeTimeWithDashboard'); - const isAlreadyChecked = await storeTimeCheckbox.getProperty('checked'); - if (isAlreadyChecked !== checked) { - PageObjects.common.debug('Flipping store time checkbox'); - await storeTimeCheckbox.click(); - } - } - - async getFilters(timeout = defaultFindTimeout) { - return PageObjects.common.findAllByCssSelector('.filter-bar > .filter', timeout); - } - - async getFilterDescriptions(timeout = defaultFindTimeout) { - const filters = await PageObjects.common.findAllByCssSelector( - '.filter-bar > .filter > .filter-description', - timeout); - return _.map(filters, async (filter) => await filter.getVisibleText()); - } - - async filterOnPieSlice() { - PageObjects.common.debug('Filtering on a pie slice'); - await PageObjects.common.try(async () => { - const slices = await PageObjects.common.findAllByCssSelector('svg > g > path.slice'); - PageObjects.common.debug('Slices found:' + slices.length); - return slices[0].click(); - }); - } - - async toggleExpandPanel() { - PageObjects.common.debug('toggleExpandPanel'); - const expandShown = await PageObjects.common.doesTestSubjectExist('dashboardPanelExpandIcon'); - if (!expandShown) { - const panelElements = await this.findTimeout.findAllByCssSelector('span.panel-title'); - PageObjects.common.debug('click title'); - await panelElements[0].click(); // Click to simulate hover. - } - const expandButton = await PageObjects.common.findTestSubject('dashboardPanelExpandIcon'); - PageObjects.common.debug('click expand icon'); - expandButton.click(); - } - - getSharedItemsCount() { - PageObjects.common.debug('in getSharedItemsCount'); - const attributeName = 'data-shared-items-count'; - return this.findTimeout - .findByCssSelector(`[${attributeName}]`) - .then(function (element) { - if (element) { - return element.getAttribute(attributeName); - } - - return Promise.reject(); - }); - } - - getPanelSharedItemData() { - PageObjects.common.debug('in getPanelSharedItemData'); - return this.findTimeout - .findAllByCssSelector('li.gs-w') - .then(function (elements) { - return Promise.all(elements.map(async element => { - const sharedItem = await element.findByCssSelector('[data-shared-item]'); - return { - title: await sharedItem.getAttribute('data-title'), - description: await sharedItem.getAttribute('data-description') - }; - })); - }); - } -} diff --git a/test/support/page_objects/discover_page.js b/test/support/page_objects/discover_page.js deleted file mode 100644 index ba12f8ea54a84..0000000000000 --- a/test/support/page_objects/discover_page.js +++ /dev/null @@ -1,303 +0,0 @@ - -import { - defaultFindTimeout -} from '../'; - -import PageObjects from './'; - -export default class DiscoverPage { - - init(remote) { - this.remote = remote; - this.findTimeout = this.remote.setFindTimeout(defaultFindTimeout); - } - - getQueryField() { - return this.findTimeout - .findByCssSelector('input[ng-model=\'state.query\']'); - } - - getQuerySearchButton() { - return this.findTimeout - .findByCssSelector('button[aria-label=\'Search\']'); - } - - getTimespanText() { - return PageObjects.common.findTestSubject('globalTimepickerRange') - .getVisibleText(); - } - - getChartTimespan() { - return this.findTimeout - .findByCssSelector('center.small > span:nth-child(1)') - .getVisibleText(); - } - - saveSearch(searchName) { - return this.clickSaveSearchButton() - .then(() => { - PageObjects.common.debug('--saveSearch button clicked'); - return this.findTimeout.findDisplayedById('SaveSearch') - .pressKeys(searchName); - }) - .then(() => { - PageObjects.common.debug('--find save button'); - return PageObjects.common.clickTestSubject('discover-save-search-btn'); - }); - } - - loadSavedSearch(searchName) { - return this.clickLoadSavedSearchButton() - .then(() => { - this.findTimeout.findByPartialLinkText(searchName).click(); - }) - .then(() => { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - - clickNewSearchButton() { - return PageObjects.common.clickTestSubject('discoverNewButton'); - } - - clickSaveSearchButton() { - return PageObjects.common.clickTestSubject('discoverSaveButton'); - } - - clickLoadSavedSearchButton() { - return PageObjects.common.clickTestSubject('discoverOpenButton'); - } - - getCurrentQueryName() { - return PageObjects.common.findTestSubject('discoverCurrentQuery') - .getVisibleText(); - } - - getBarChartData() { - const self = this; - let yAxisLabel = 0; - let yAxisHeight; - - return PageObjects.header.waitUntilLoadingHasFinished() - .then(() => { - return this.findTimeout - .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type'); - }) - .then(function setYAxisLabel(y) { - return y - .getVisibleText() - .then(function (yLabel) { - yAxisLabel = yLabel.replace(',', ''); - PageObjects.common.debug('yAxisLabel = ' + yAxisLabel); - return yLabel; - }); - }) - // 2). find and save the y-axis pixel size (the chart height) - .then(function getRect() { - return self - .findTimeout - .findByCssSelector('rect.background') - .then(function getRectHeight(chartAreaObj) { - return chartAreaObj - .getAttribute('height') - .then(function (theHeight) { - yAxisHeight = theHeight; // - 5; // MAGIC NUMBER - clipPath extends a bit above the top of the y-axis and below x-axis - PageObjects.common.debug('theHeight = ' + theHeight); - return theHeight; - }); - }); - }) - // 3). get the chart-wrapper elements - .then(function () { - return self - .findTimeout - // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) - .findAllByCssSelector('svg > g > g.series > rect') // rect - .then(function (chartTypes) { - function getChartType(chart) { - return chart - .getAttribute('height') - .then(function (barHeight) { - return Math.round(barHeight / yAxisHeight * yAxisLabel); - }); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(function (bars) { - return bars; - }); - }); - } - - getChartInterval() { - return PageObjects.common.findTestSubject('discoverIntervalSelect') - .getProperty('value') - .then(selectedValue => { - return this.findTimeout - .findByCssSelector('option[value="' + selectedValue + '"]') - .getVisibleText(); - }); - } - - setChartInterval(interval) { - return this.remote.setFindTimeout(5000) - .findByCssSelector('option[label="' + interval + '"]') - .click() - .then(() => { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - - getHitCount() { - return PageObjects.header.waitUntilLoadingHasFinished() - .then(() => { - return PageObjects.common.findTestSubject('discoverQueryHits') - .getVisibleText(); - }); - } - - query(queryString) { - return this.findTimeout - .findByCssSelector('input[aria-label="Search input"]') - .clearValue() - .type(queryString) - .then(() => { - return this.findTimeout - .findByCssSelector('button[aria-label="Search"]') - .click(); - }) - .then(() => { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - - getDocHeader() { - return this.findTimeout - .findByCssSelector('thead.ng-isolate-scope > tr:nth-child(1)') - .getVisibleText(); - } - - getDocTableIndex(index) { - return this.findTimeout - .findByCssSelector('tr.discover-table-row:nth-child(' + (index) + ')') - .getVisibleText(); - } - - clickDocSortDown() { - return this.findTimeout - .findByCssSelector('.fa-sort-down') - .click(); - } - - clickDocSortUp() { - return this.findTimeout - .findByCssSelector('.fa-sort-up') - .click(); - } - - getMarks() { - return this.findTimeout - .findAllByCssSelector('mark') - .getVisibleText(); - } - - clickShare() { - return PageObjects.common.clickTestSubject('discoverShareButton'); - } - - clickShortenUrl() { - return PageObjects.common.clickTestSubject('sharedSnapshotShortUrlButton'); - } - - clickCopyToClipboard() { - return PageObjects.common.clickTestSubject('sharedSnapshotCopyButton'); - } - - getShareCaption() { - return PageObjects.common.findTestSubject('shareUiTitle') - .getVisibleText(); - } - - getSharedUrl() { - return PageObjects.common.findTestSubject('sharedSnapshotUrl') - .getProperty('value'); - } - - toggleSidebarCollapse() { - return this.findTimeout.findDisplayedByCssSelector('.sidebar-collapser .chevron-cont') - .click(); - } - - getAllFieldNames() { - return this.findTimeout - .findAllByClassName('sidebar-item') - .then((items) => { - return Promise.all(items.map((item) => item.getVisibleText())); - }); - } - - getSidebarWidth() { - return this.findTimeout - .findByClassName('sidebar-list') - .getProperty('clientWidth'); - } - - hasNoResults() { - return PageObjects.common.findTestSubject('discoverNoResults') - .then(() => true) - .catch(() => false); - } - - getNoResultsTimepicker() { - return PageObjects.common.findTestSubject('discoverNoResultsTimefilter'); - } - - hasNoResultsTimepicker() { - return this - .getNoResultsTimepicker() - .then(() => true) - .catch(() => false); - } - - clickFieldListItem(field) { - return PageObjects.common.clickTestSubject(`field-${field}`); - } - - async clickFieldListItemAdd(field) { - const listEntry = await PageObjects.common.findTestSubject(`field-${field}`); - await this.remote.moveMouseTo(listEntry); - await PageObjects.common.clickTestSubject(`fieldToggle-${field}`); - } - - async clickFieldListItemVisualize(field) { - return await PageObjects.common.try(async () => { - await PageObjects.common.clickTestSubject('fieldVisualize-' + field); - }); - } - - clickFieldListPlusFilter(field, value) { - // this method requires the field details to be open from clickFieldListItem() - // findTestSubject doesn't handle spaces in the data-test-subj value - return this.findTimeout - .findByCssSelector('i[data-test-subj="plus-' + field + '-' + value + '"]') - .click(); - } - - clickFieldListMinusFilter(field, value) { - // this method requires the field details to be open from clickFieldListItem() - // findTestSubject doesn't handle spaces in the data-test-subj value - return this.findTimeout - .findByCssSelector('i[data-test-subj="minus-' + field + '-' + value + '"]') - .click(); - } - - async removeAllFilters() { - await PageObjects.common.clickTestSubject('showFilterActions'); - await PageObjects.common.clickTestSubject('removeAllFilters'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.waitUntilUrlIncludes('filters:!()'); - } - - -} diff --git a/test/support/page_objects/doc_table.js b/test/support/page_objects/doc_table.js deleted file mode 100644 index 0c31073a59c91..0000000000000 --- a/test/support/page_objects/doc_table.js +++ /dev/null @@ -1,40 +0,0 @@ -import PageObjects from './'; - -export default class DocTable { - - init(remote) { - this.remote = remote; - } - - getTable() { - return PageObjects.common.findTestSubject('docTable'); - } - - async getBodyRows(table) { - return await table.findAllByCssSelector('[data-test-subj~="docTableRow"]'); - } - - async getAnchorRow(table) { - return await table.findByCssSelector('[data-test-subj~="docTableAnchorRow"]'); - } - - async getRowExpandToggle(row) { - return await row.findByCssSelector('[data-test-subj~="docTableExpandToggleColumn"]'); - } - - async getDetailsRows(table) { - return await table.findAllByCssSelector('[data-test-subj~="docTableRow"] + tr'); - } - - async getRowActions(row) { - return await row.findAllByCssSelector('[data-test-subj~="docTableRowAction"]'); - } - - async getFields(row) { - return await row.findAllByCssSelector('[data-test-subj~="docTableField"]'); - } - - async getHeaderFields(table) { - return await table.findAllByCssSelector('[data-test-subj~="docTableHeaderField"]'); - } -} diff --git a/test/support/page_objects/header_page.js b/test/support/page_objects/header_page.js deleted file mode 100644 index 5fb4ce7c61417..0000000000000 --- a/test/support/page_objects/header_page.js +++ /dev/null @@ -1,252 +0,0 @@ - -import { - defaultFindTimeout -} from '../'; - -import PageObjects from './'; - -export default class HeaderPage { - - init(remote) { - this.remote = remote; - } - - clickSelector(selector) { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector(selector) - .then(tab => { - return tab.click(); - }); - } - - clickDiscover() { - PageObjects.common.debug('click Discover tab'); - this.clickSelector('a[href*=\'discover\']'); - return PageObjects.common.sleep(3000); - } - - clickVisualize() { - PageObjects.common.debug('click Visualize tab'); - this.clickSelector('a[href*=\'visualize\']'); - return PageObjects.common.sleep(3000); - } - - clickDashboard() { - PageObjects.common.debug('click Dashboard tab'); - this.clickSelector('a[href*=\'dashboard\']'); - return PageObjects.common.sleep(3000); - } - - clickSettings() { - PageObjects.common.debug('click Settings tab'); - this.clickSelector('a[href*=\'settings\']'); - } - - clickTimepicker() { - return PageObjects.common.try(() => { - return PageObjects.common.clickTestSubject('globalTimepickerButton'); - }); - } - - clickQuickButton() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByLinkText('Quick').click(); - } - - isTimepickerOpen() { - return this.remote.setFindTimeout(2000) - .findDisplayedByCssSelector('.kbn-timepicker') - .then(() => true) - .catch(() => false); - } - - async clickAbsoluteButton() { - await PageObjects.common.try(async () => { - await this.remote.setFindTimeout(defaultFindTimeout); - const absoluteButton = await this.remote.findByLinkText('Absolute'); - await absoluteButton.click(); - }); - } - - clickQuickButton() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByLinkText('Quick').click(); - } - - async getFromTime() { - await this.ensureTimePickerIsOpen(); - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model=\'absolute.from\']') - .getProperty('value'); - } - - async getToTime() { - await this.ensureTimePickerIsOpen(); - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model=\'absolute.to\']') - .getProperty('value'); - } - - async getFromTime() { - await this.ensureTimePickerIsOpen(); - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model=\'absolute.from\']') - .getProperty('value'); - } - - async getToTime() { - await this.ensureTimePickerIsOpen(); - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model=\'absolute.to\']') - .getProperty('value'); - } - - 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() { - const self = this; - return this.remote.setFindTimeout(defaultFindTimeout) - .findByClassName('kbn-timepicker-go') - .click() - .then(function () { - return self.waitUntilLoadingHasFinished(); - }); - } - - setAbsoluteRange(fromTime, toTime) { - PageObjects.common.debug('clickTimepicker'); - return this.clickTimepicker() - .then(() => { - PageObjects.common.debug('--Clicking Absolute button'); - return this.clickAbsoluteButton(); - }) - .then(() => { - PageObjects.common.debug('--Setting From Time : ' + fromTime); - return this.setFromTime(fromTime); - }) - .then(() => { - PageObjects.common.debug('--Setting To Time : ' + toTime); - return this.setToTime(toTime); - }) - .then(() => { - return this.clickGoButton(); - }) - .then(() => { - return this.waitUntilLoadingHasFinished(); - }); - } - - async ensureTimePickerIsOpen() { - const isOpen = await PageObjects.header.isTimepickerOpen(); - PageObjects.common.debug(`time picker open: ${isOpen}`); - if (!isOpen) { - PageObjects.common.debug('--Opening time picker'); - await PageObjects.header.clickTimepicker(); - } - } - - async setAbsoluteRange(fromTime, toTime) { - PageObjects.common.debug(`Setting absolute range to ${fromTime} to ${toTime}`); - await this.ensureTimePickerIsOpen(); - PageObjects.common.debug('--Clicking Absolute button'); - await this.clickAbsoluteButton(); - PageObjects.common.debug('--Setting From Time : ' + fromTime); - await this.setFromTime(fromTime); - PageObjects.common.debug('--Setting To Time : ' + toTime); - await this.setToTime(toTime); - await this.clickGoButton(); - await this.isGlobalLoadingIndicatorHidden(); - } - - async setQuickTime(quickTime) { - await this.ensureTimePickerIsOpen(); - PageObjects.common.debug('--Clicking Quick button'); - await this.clickQuickButton(); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByLinkText(quickTime).click(); - } - - async getPrettyDuration() { - return await PageObjects.common.findTestSubject('globalTimepickerRange').getVisibleText(); - } - - getToastMessage() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findDisplayedByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') - .getVisibleText(); - } - - waitForToastMessageGone() { - return this.remote.setFindTimeout(defaultFindTimeout) - .waitForDeletedByCssSelector('kbn-truncated.toast-message'); - } - - clickToastOK() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('button[ng-if="notif.accept"]') - .click(); - } - - async waitUntilLoadingHasFinished() { - try { - await this.isGlobalLoadingIndicatorVisible(); - } catch (exception) { - if (exception.name === 'ElementNotVisible') { - // selenium might just have been too slow to catch it - } else { - throw exception; - } - } - await this.isGlobalLoadingIndicatorHidden(); - } - - isGlobalLoadingIndicatorVisible() { - return PageObjects.common.findTestSubject('globalLoadingIndicator', defaultFindTimeout / 5); - } - - isGlobalLoadingIndicatorHidden() { - return this.remote.setFindTimeout(defaultFindTimeout * 10) - .findByCssSelector('[data-test-subj="globalLoadingIndicator"].ng-hide'); - } - - async ensureTimePickerIsOpen() { - const isOpen = await PageObjects.header.isTimepickerOpen(); - PageObjects.common.debug(`time picker open: ${isOpen}`); - if (!isOpen) { - PageObjects.common.debug('--Opening time picker'); - await PageObjects.header.clickTimepicker(); - } - } - - async setQuickTime(quickTime) { - await this.ensureTimePickerIsOpen(); - PageObjects.common.debug('--Clicking Quick button'); - await this.clickQuickButton(); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByLinkText(quickTime).click(); - } - - async getPrettyDuration() { - return await PageObjects.common.findTestSubject('globalTimepickerRange').getVisibleText(); - } - - async isSharedTimefilterEnabled() { - const element = await this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`[data-shared-timefilter=true]`); - - return new Boolean(element); - } -} diff --git a/test/support/page_objects/index.js b/test/support/page_objects/index.js deleted file mode 100644 index 9673f8e85021a..0000000000000 --- a/test/support/page_objects/index.js +++ /dev/null @@ -1,113 +0,0 @@ - -import Common from './common'; -import ConsolePage from './console_page'; -import ContextPage from './context_page'; -import DashboardPage from './dashboard_page'; -import DiscoverPage from './discover_page'; -import HeaderPage from './header_page'; -import SettingsPage from './settings_page'; -import ShieldPage from './shield_page'; -import VisualizePage from './visualize_page'; -import VisualizePointSeriesOptions from './visualize_point_series_options'; -import MonitoringPage from './monitoring_page'; -import DocTable from './doc_table'; - -const common = new Common(); -const consolePage = new ConsolePage(); -const contextPage = new ContextPage(); -const dashboardPage = new DashboardPage(); -const discoverPage = new DiscoverPage(); -const headerPage = new HeaderPage(); -const settingsPage = new SettingsPage(); -const shieldPage = new ShieldPage(); -const visualizePage = new VisualizePage(); -const visualizePointSeriesOptions = new VisualizePointSeriesOptions(); -const monitoringPage = new MonitoringPage(); - -const docTable = new DocTable(); - -class PageObjects { - - constructor() { - this.isInitialized = false; - this.remote = undefined; - this.pageObjects = [ - common, - consolePage, - contextPage, - dashboardPage, - discoverPage, - headerPage, - settingsPage, - shieldPage, - visualizePage, - visualizePointSeriesOptions, - monitoringPage, - docTable, - ]; - } - - init(remote) { - this.isInitialized = true; - this.remote = remote; - this.pageObjects.map((pageObject) => pageObject.init(remote)); - } - - assertInitialized() { - 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.assertInitialized() && common; - } - - get console() { - return this.assertInitialized() && consolePage; - } - - get context() { - return this.assertInitialized() && contextPage; - } - - get dashboard() { - return this.assertInitialized() && dashboardPage; - } - - get discover() { - return this.assertInitialized() && discoverPage; - } - - get header() { - return this.assertInitialized() && headerPage; - } - - get settings() { - return this.assertInitialized() && settingsPage; - } - - get shield() { - return this.assertInitialized() && shieldPage; - } - - get visualize() { - return this.assertInitialized() && visualizePage; - } - - get visualizeOptions() { - return this.assertInitialized() && visualizePointSeriesOptions; - } - - get monitoring() { - return this.assertInitialized() && monitoringPage; - } - - get docTable() { - return this.assertInitialized() && docTable; - } - -} - -export default new PageObjects(); diff --git a/test/support/page_objects/monitoring_page.js b/test/support/page_objects/monitoring_page.js deleted file mode 100644 index f06952348419e..0000000000000 --- a/test/support/page_objects/monitoring_page.js +++ /dev/null @@ -1,35 +0,0 @@ - -import { - defaultFindTimeout, -} from '../'; - -export default class MonitoringPage { - - init(remote) { - this.remote = remote; - this.findTimeout = this.remote.setFindTimeout(defaultFindTimeout); - } - - getWelcome() { - return this.findTimeout - .findDisplayedByCssSelector('render-directive') - .getVisibleText(); - } - - dismissWelcome() { - return this.remote.setFindTimeout(3000) - .findDisplayedByCssSelector('button.btn-banner') - .click(); - } - - getToasterContents() { - return this.findTimeout - .findByCssSelector('div.toaster-container.ng-isolate-scope') - .getVisibleText(); - } - - clickOptOut() { - return this.findTimeout.findByLinkText('Opt out here').click(); - } - -} diff --git a/test/support/page_objects/settings_page.js b/test/support/page_objects/settings_page.js deleted file mode 100644 index 6853a82956ebe..0000000000000 --- a/test/support/page_objects/settings_page.js +++ /dev/null @@ -1,463 +0,0 @@ - -import Bluebird, { map as mapAsync } from 'bluebird'; - -import { - defaultFindTimeout, -} from '../'; - -import PageObjects from './'; - -export default class SettingsPage { - - init(remote) { - this.remote = remote; - } - - async clickNavigation() { - // TODO: find better way to target the element - await this.remote.findDisplayedByCssSelector('.app-link:nth-child(5) a').click(); - } - - async clickLinkText(text) { - await PageObjects.common.try(async () => { - await this.remote.findDisplayedByLinkText(text).click(); - }); - } - - async clickKibanaSettings() { - await this.clickLinkText('Advanced Settings'); - } - - async clickKibanaIndicies() { - await this.clickLinkText('Index Patterns'); - } - - getAdvancedSettings(propertyName) { - PageObjects.common.debug('in setAdvancedSettings'); - return PageObjects.common.findTestSubject('advancedSetting-' + propertyName + '-currentValue') - .getVisibleText(); - } - - async setAdvancedSettings(propertyName, propertyValue) { - await PageObjects.common.clickTestSubject('advancedSetting-' + propertyName + '-editButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.sleep(1000); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('option[label="' + propertyValue + '"]').click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.clickTestSubject('advancedSetting-' + propertyName + '-saveButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async navigateTo() { - await PageObjects.common.navigateToApp('settings'); - } - - getTimeBasedEventsCheckbox() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="index.isTimeBased"]'); - } - - getTimeBasedIndexPatternCheckbox(timeout) { - timeout = timeout || defaultFindTimeout; - // fail faster since we're sometimes checking that it doesn't exist - return this.remote.setFindTimeout(timeout) - .findByCssSelector('input[ng-model="index.nameIsPattern"]'); - } - - getIndexPatternField() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('[ng-model="index.name"]'); - } - - getTimeFieldNameField() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findDisplayedByCssSelector('select[ng-model="index.timeField"]'); - } - - async selectTimeFieldOption(selection) { - // open dropdown - (await this.getTimeFieldNameField()).click(); - // close dropdown, keep focus - (await this.getTimeFieldNameField()).click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.try(async () => { - (await this.getTimeFieldOption(selection)).click(); - const selected = (await this.getTimeFieldOption(selection)).isSelected(); - if (!selected) throw new Error('option not selected: ' + selected); - }); - } - - getTimeFieldOption(selection) { - return this.remote.setFindTimeout(defaultFindTimeout) - .findDisplayedByCssSelector('option[label="' + selection + '"]'); - } - - getCreateButton() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findDisplayedByCssSelector('[type="submit"]'); - } - - async clickDefaultIndexButton() { - await PageObjects.common.findTestSubject('setDefaultIndexPatternButton').click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickDeletePattern() { - await PageObjects.common.findTestSubject('deleteIndexPatternButton').click(); - } - - getIndexPageHeading() { - return PageObjects.common.findTestSubject('indexPatternTitle'); - } - - getConfigureHeader() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('h1'); - } - - getTableHeader() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('table.table.table-condensed thead tr th'); - } - - sortBy(columnName) { - return this.remote.setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('table.table.table-condensed thead tr th span') - .then(function (chartTypes) { - function getChartType(chart) { - return chart.getVisibleText() - .then(function (chartString) { - if (chartString === columnName) { - return chart.click() - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - }); - } - - const getChartTypesPromises = chartTypes.map(getChartType); - return Bluebird.all(getChartTypesPromises); - }); - } - - getTableRow(rowNumber, colNumber) { - return this.remote.setFindTimeout(defaultFindTimeout) - // passing in zero-based index, but adding 1 for css 1-based indexes - .findByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr:nth-child(' + - (rowNumber + 1) + ') td.ng-scope:nth-child(' + - (colNumber + 1) + ') span.ng-binding' - ); - } - - getFieldsTabCount() { - return PageObjects.common.try(() => { - return PageObjects.common.findTestSubject('tab-count-indexedFields') - .getVisibleText() - .then((theText) => { - // the value has () around it, remove them - return theText.replace(/\((.*)\)/, '$1'); - }); - }); - } - - async getScriptedFieldsTabCount() { - const selector = '[data-test-subj="tab-count-scriptedFields"]'; - return await PageObjects.common.try(async () => { - const theText = await this.remote.setFindTimeout(defaultFindTimeout / 10) - .findByCssSelector(selector).getVisibleText(); - return theText.replace(/\((.*)\)/, '$1'); - }); - } - - getPageSize() { - let selectedItemLabel = ''; - return this.remote.setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('select.ng-pristine.ng-valid.ng-untouched option') - .then(function (chartTypes) { - function getChartType(chart) { - const thisChart = chart; - return chart.isSelected() - .then(function (isSelected) { - if (isSelected === true) { - return thisChart.getProperty('label') - .then(function (theLabel) { - selectedItemLabel = theLabel; - }); - } - }); - } - - const getChartTypesPromises = chartTypes.map(getChartType); - return Bluebird.all(getChartTypesPromises); - }) - .then(() => { - return selectedItemLabel; - }); - } - - async getFieldNames() { - const fieldNameCells = await PageObjects.common.findAllTestSubjects('editIndexPattern indexedFieldName'); - return await mapAsync(fieldNameCells, async cell => { - return (await cell.getVisibleText()).trim(); - }); - } - - async getFieldTypes() { - const fieldNameCells = await PageObjects.common.findAllTestSubjects('editIndexPattern indexedFieldType'); - return await mapAsync(fieldNameCells, async cell => { - return (await cell.getVisibleText()).trim(); - }); - } - - async getScriptedFieldLangs() { - const fieldNameCells = await PageObjects.common.findAllTestSubjects('editIndexPattern scriptedFieldLang'); - return await mapAsync(fieldNameCells, async cell => { - return (await cell.getVisibleText()).trim(); - }); - } - - - async setFieldTypeFilter(type) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]') - .click(); - } - - - async setScriptedFieldLanguageFilter(language) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + '"]') - .click(); - } - - async goToPage(pageNum) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' + - (pageNum + 1) + ') a.ng-binding') - .click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async openControlsRow(row) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('table.table.table-condensed tbody tr:nth-child(' + - (row + 1) + ') td.ng-scope div.actions a.btn.btn-xs.btn-default i.fa.fa-pencil') - .click(); - } - - async openControlsByName(name) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('div.actions a.btn.btn-xs.btn-default[href$="/' + name + '"]') - .click(); - } - - async increasePopularity() { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('button.btn.btn-default[aria-label="Plus"]') - .click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - getPopularity() { - return this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="editor.field.count"]') - .getProperty('value'); - } - - async controlChangeCancel() { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]') - .click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async controlChangeSave() { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]') - .click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async setPageSize(size) { - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector(`[data-test-subj="paginateControlsPageSizeSelect"] option[label="${size}"]`) - .click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async createIndexPattern() { - await PageObjects.common.try(async () => { - await this.navigateTo(); - await this.clickKibanaIndicies(); - await this.selectTimeFieldOption('@timestamp'); - await this.getCreateButton().click(); - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.try(async () => { - const currentUrl = await this.remote.getCurrentUrl(); - PageObjects.common.log('currentUrl', currentUrl); - if (!currentUrl.match(/indices\/.+\?/)) { - throw new Error('Index pattern not created'); - } else { - PageObjects.common.debug('Index pattern created: ' + currentUrl); - } - }); - } - - async removeIndexPattern() { - let alertText; - await PageObjects.common.try(async () => { - PageObjects.common.debug('click delete index pattern button'); - await this.clickDeletePattern(); - }); - await PageObjects.common.try(async () => { - PageObjects.common.debug('getAlertText'); - alertText = await PageObjects.common.findTestSubject('confirmModalBodyText').getVisibleText(); - }); - await PageObjects.common.try(async () => { - PageObjects.common.debug('acceptConfirmation'); - await PageObjects.common.clickTestSubject('confirmModalConfirmButton'); - }); - await PageObjects.common.try(async () => { - const currentUrl = await this.remote.getCurrentUrl(); - if (currentUrl.match(/indices\/.+\?/)) { - throw new Error('Index pattern not removed'); - } - }); - return alertText; - } - - async clickFieldsTab() { - PageObjects.common.debug('click Fields tab'); - await PageObjects.common.clickTestSubject('tab-indexFields'); - } - - async clickScriptedFieldsTab() { - PageObjects.common.debug('click Scripted Fields tab'); - await PageObjects.common.clickTestSubject('tab-scriptedFields'); - } - - async clickSourceFiltersTab() { - PageObjects.common.debug('click Source Filters tab'); - await PageObjects.common.clickTestSubject('tab-sourceFilters'); - } - - async addScriptedField(name, language, type, format, popularity, script) { - await this.clickAddScriptedField(); - await this.setScriptedFieldName(name); - if (language) await this.setScriptedFieldLanguage(language); - if (type) await this.setScriptedFieldType(type); - if (format) { - await this.setScriptedFieldFormat(format.format); - // null means leave - default - which has no other settings - // Url adds Type, Url Template, and Label Template - // Date adds moment.js format pattern (Default: "MMMM Do YYYY, HH:mm:ss.SSS") - // String adds Transform - switch(format.format) { - case 'Url': - await this.setScriptedFieldUrlType(format.type); - await this.setScriptedFieldUrlTemplate(format.template); - await this.setScriptedFieldUrlLabelTemplate(format.labelTemplate); - break; - case 'Date': - await this.setScriptedFieldDatePattern(format.datePattern); - break; - case 'String': - await this.setScriptedFieldStringTransform(format.stringTransform); - break; - } - } - if (popularity) await this.setScriptedFieldPopularity(popularity); - await this.setScriptedFieldScript(script); - await this.clickSaveScriptedField(); - } - - async clickAddScriptedField() { - PageObjects.common.debug('click Add Scripted Field'); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('a[aria-label="Add Scripted Field"]') - .click(); - } - - async clickSaveScriptedField() { - PageObjects.common.debug('click Save Scripted Field'); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('button[aria-label="Create Field"]') - .click(); - } - - async setScriptedFieldName(name) { - PageObjects.common.debug('set scripted field name = ' + name); - await PageObjects.common.findTestSubject('editorFieldName').type(name); - } - - async setScriptedFieldLanguage(language) { - PageObjects.common.debug('set scripted field language = ' + language); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[data-test-subj="editorFieldLang"] > option[label="' + language + '"]') - .click(); - } - - async setScriptedFieldType(type) { - PageObjects.common.debug('set scripted field type = ' + type); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[data-test-subj="editorFieldType"] > option[label="' + type + '"]') - .click(); - } - - async setScriptedFieldFormat(format) { - PageObjects.common.debug('set scripted field format = ' + format); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[data-test-subj="editorSelectedFormatId"] > option[label="' + format + '"]') - .click(); - } - - async setScriptedFieldUrlType(type) { - PageObjects.common.debug('set scripted field Url type = ' + type); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[ng-model="editor.formatParams.type"] > option[label="' + type + '"]') - .click(); - } - - async setScriptedFieldUrlTemplate(template) { - PageObjects.common.debug('set scripted field Url Template = ' + template); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="editor.formatParams.labelTemplate"]') - .type(template); - } - - async setScriptedFieldUrlLabelTemplate(labelTemplate) { - PageObjects.common.debug('set scripted field Url Label Template = ' + labelTemplate); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="editor.formatParams.labelTemplate"]') - .type(labelTemplate); - } - - async setScriptedFieldDatePattern(datePattern) { - PageObjects.common.debug('set scripted field Date Pattern = ' + datePattern); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="model"]') - .clearValue().type(datePattern); - } - - async setScriptedFieldStringTransform(stringTransform) { - PageObjects.common.debug('set scripted field string Transform = ' + stringTransform); - await this.remote.setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[ng-model="editor.formatParams.transform"] > option[label="' + stringTransform + '"]') - .click(); - } - - async setScriptedFieldPopularity(popularity) { - PageObjects.common.debug('set scripted field popularity = ' + popularity); - await PageObjects.common.findTestSubject('editorFieldCount').type(popularity); - } - - async setScriptedFieldScript(script) { - PageObjects.common.debug('set scripted field script = ' + script); - await PageObjects.common.findTestSubject('editorFieldScript').type(script); - } - - -} diff --git a/test/support/page_objects/shield_page.js b/test/support/page_objects/shield_page.js deleted file mode 100644 index a6d2ae956e8cb..0000000000000 --- a/test/support/page_objects/shield_page.js +++ /dev/null @@ -1,27 +0,0 @@ - -import { - defaultFindTimeout, -} from '../'; - -export default class ShieldPage { - - init(remote) { - this.remote = remote; - } - - login(user, pwd) { - const remote = this.remote; - return remote.setFindTimeout(defaultFindTimeout) - .findById('username') - .type(user) - .then(function () { - return remote.findById('password') - .type(pwd); - }) - .then(function () { - return remote.findByCssSelector('.btn') - .click(); - }); - } - -} diff --git a/test/support/page_objects/visualize_page.js b/test/support/page_objects/visualize_page.js deleted file mode 100644 index 9f9ce67bfdc1c..0000000000000 --- a/test/support/page_objects/visualize_page.js +++ /dev/null @@ -1,781 +0,0 @@ - -import { - defaultFindTimeout, -} from '../'; - -import PageObjects from './'; - -export default class VisualizePage { - - init(remote) { - this.remote = remote; - this.xAxisBucket = 'Category Axis'; - this.yAxisBucket = 'Value Axis'; - } - - clickAreaChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Area') - .click(); - } - - clickDataTable() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Data Table') - .click(); - } - - clickLineChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Line') - .click(); - } - - clickMarkdownWidget() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Markdown') - .click(); - } - - clickAddMetric() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('[group-name="metrics"] .vis-editor-agg-add .vis-editor-agg-wide-btn div.btn') - .click(); - } - - clickMetric() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Metric') - .click(); - } - - clickPieChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Pie') - .click(); - } - - clickTileMap() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Tile Map') - .click(); - } - - clickVerticalBarChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Vertical Bar') - .click(); - } - - clickHeatmapChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Heat Map') - .click(); - } - - getChartTypeCount() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('a.wizard-vis-type.ng-scope') - .length; - } - - getChartTypes() { - return PageObjects.common.findAllTestSubjects('visualizeWizardChartTypeTitle') - .then(function (chartTypes) { - function getChartType(chart) { - return chart.getVisibleText(); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(function (texts) { - return texts; - }); - } - - clickAbsoluteButton() { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('ul.nav.nav-pills.nav-stacked.kbn-timepicker-modes:contains("absolute")') - .click(); - } - - setFromTime(timeString) { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('input[ng-model="absolute.from"]') - .clearValue() - .type(timeString); - } - - setToTime(timeString) { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('input[ng-model="absolute.to"]') - .clearValue() - .type(timeString); - } - - clickGoButton() { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByClassName('kbn-timepicker-go') - .click(); - } - - collapseChart() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('div.visualize-show-spy > div > i') - .click(); - } - - getMetric() { - return this.remote - .setFindTimeout(2000) - .findByCssSelector('div[ng-controller="KbnMetricVisController"]') - .getVisibleText(); - } - - clickMetricEditor() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('button[aria-label="Open Editor"]') - .click(); - } - - clickNewSearch() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('.list-group-item a') - .click(); - } - - setValue(newValue) { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('button[ng-click="numberListCntr.add()"]') - .click() - .then(() => { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') - .clearValue(); - }) - .then(() => { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') - .type(newValue); - }); - } - - clickSavedSearch() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('li[ng-click="stepTwoMode=\'saved\'"]') - .click(); - } - - selectSearch(searchName) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByLinkText(searchName) - .click(); - } - - - getErrorMessage() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('.item>h4') - .getVisibleText(); - } - - // clickBucket(bucketType) 'X-Axis', 'Split Area', 'Split Chart' - clickBucket(bucketName) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('li.list-group-item.list-group-menu-item.ng-binding.ng-scope') - .then(chartTypes => { - PageObjects.common.debug('found bucket types ' + chartTypes.length); - - function getChartType(chart) { - return chart - .getVisibleText() - .then(chartString => { - //PageObjects.common.debug(chartString); - if (chartString === bucketName) { - chart.click(); - } - }); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }); - } - - selectAggregation(myString) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('vis-editor-agg-params:not(.ng-hide) option[label="' + myString + '"]') - .click(); - } - - getField() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('.ng-valid-required[name="field"] option[selected="selected"]') - .getVisibleText(); - } - - selectField(fieldValue, groupName = 'buckets') { - const self = this; - return PageObjects.common.try(function tryingForTime() { - return self.remote - .setFindTimeout(defaultFindTimeout) - // the css below should be more selective - .findByCssSelector(`[group-name="${groupName}"] option[label="${fieldValue}"]`) - .click(); - }); - } - - orderBy(fieldValue) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('select.form-control.ng-pristine.ng-valid.ng-untouched.ng-valid-required[ng-model="agg.params.orderBy"] ' + - 'option.ng-binding.ng-scope:contains("' + fieldValue + '")' - ) - .click(); - } - - selectOrderBy(fieldValue) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[name="orderBy"] > option[value="' + fieldValue + '"]') - .click(); - } - - - getInterval() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[ng-model="agg.params.interval"]') - .getProperty('selectedIndex') - .then(selectedIndex => { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[ng-model="agg.params.interval"] option:nth-child(' + (selectedIndex + 1) + ')') - .getProperty('label'); - }); - } - - setInterval(newValue) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('select[ng-model="agg.params.interval"]') - .type(newValue); - } - - setNumericInterval(newValue) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[name="interval"]') - .type(newValue); - } - - clickGo() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('.btn-success') - .click() - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - - saveVisualization(vizName) { - return PageObjects.common.clickTestSubject('visualizeSaveButton') - .then(() => { - return PageObjects.common.sleep(1000); - }) - .then(() => { - PageObjects.common.debug('saveButton button clicked'); - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByName('visTitle') - .type(vizName); - }) - // // click save button - .then(() => { - PageObjects.common.debug('click submit button'); - return PageObjects.common.clickTestSubject('saveVisualizationButton'); - }) - .then(function () { - return PageObjects.header.waitUntilLoadingHasFinished(); - }) - // verify that green message at the top of the page. - // it's only there for about 5 seconds - .then(() => { - const self = this; - return PageObjects.common.try(function tryingForTime() { - return self.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') - .getVisibleText(); - }); - }); - } - - clickLoadSavedVisButton() { - // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb - // element as a child instead of building the breadcrumbs dynamically. - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('[href="#/visualize"]') - .click(); - } - - filterVisByName(vizName) { - return this.remote - .findByCssSelector('input[name="filter"]') - .click() - // can't uses dashes in saved visualizations when filtering - // or extended character sets - // https://github.com/elastic/kibana/issues/6300 - .type(vizName.replace('-',' ')); - } - - clickVisualizationByName(vizName) { - const self = this; - PageObjects.common.debug('clickVisualizationByLinkText(' + vizName + ')'); - - return PageObjects.common.try(function tryingForTime() { - return self.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText(vizName) - .click(); - }); - } - - // this starts by clicking the Load Saved Viz button, not from the - // bottom half of the "Create a new visualization Step 1" page - loadSavedVisualization(vizName) { - const self = this; - return this.clickLoadSavedVisButton() - .then(function filterVisualization() { - return self.openSavedVisualization(vizName); - }); - } - - openSavedVisualization(vizName) { - return this.clickVisualizationByName(vizName); - } - - getXAxisLabels() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.x > g') - .then(chartTypes => { - function getChartType(chart) { - return chart - .getVisibleText(); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(texts => { - // PageObjects.common.debug('returning types array ' + texts + ' array length =' + texts.length); - return texts; - }); - } - - - getYAxisLabels() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.y > g') - .then(chartTypes => { - function getChartType(chart) { - return chart - .getVisibleText(); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(texts => { - // PageObjects.common.debug('returning types array ' + texts + ' array length =' + texts.length); - return texts; - }); - } - - - /* - ** This method gets the chart data and scales it based on chart height and label. - ** Returns an array of height values - */ - getAreaChartData(aggregateName) { - const self = this.remote; - const chartData = []; - let tempArray = []; - let chartSections = 0; - let yAxisLabel = 0; - let yAxisHeight = 0; - - // 1). get the maximim chart Y-Axis marker value - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type') - .getVisibleText() - .then(function (yLabel) { - // since we're going to use the y-axis 'last' (top) label as a number to - // scale the chart pixel data, we need to clean out commas and % marks. - yAxisLabel = yLabel.replace(/(%|,)/g, ''); - PageObjects.common.debug('yAxisLabel = ' + yAxisLabel); - return yLabel; - }) - // 2). find and save the y-axis pixel size (the chart height) - .then(function () { - return self - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('rect.background') // different here - .getAttribute('height'); - }) - .then(function (chartH) { - yAxisHeight = chartH; - PageObjects.common.debug('height --------- ' + yAxisHeight); - }) - .then(function () { - return self.setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('path[data-label="' + aggregateName + '"]') - .getAttribute('d'); - }) - .then(function (data) { - PageObjects.common.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - tempArray = data.replace('M','').split('L'); - chartSections = tempArray.length / 2; - PageObjects.common.debug('chartSections = ' + chartSections + ' height = ' + yAxisHeight + ' yAxisLabel = ' + yAxisLabel); - for (let i = 0; i < chartSections; i++) { - chartData[i] = Math.round((yAxisHeight - tempArray[i].split(',')[1]) / yAxisHeight * yAxisLabel); - PageObjects.common.debug('chartData[i] =' + chartData[i]); - } - return chartData; - }); - } - - - // The current test shows dots, not a line. This function gets the dots and normalizes their height. - getLineChartData(cssPart, axis = 'ValueAxis-1') { - const self = this.remote; - let yAxisLabel = 0; - let yAxisHeight; - - // 1). get the maximim chart Y-Axis marker value - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`div.y-axis-div-wrapper > div > svg > g.${axis} > g:last-of-type`) - .getVisibleText() - .then(function (yLabel) { - yAxisLabel = yLabel.replace(/,/g, ''); - PageObjects.common.debug('yAxisLabel = ' + yAxisLabel); - return yLabel; - }) - // 2). find and save the y-axis pixel size (the chart height) - .then(function getRect() { - return self - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('clipPath rect') - .getAttribute('height') - .then(function (theHeight) { - yAxisHeight = theHeight; - PageObjects.common.debug('theHeight = ' + theHeight); - return theHeight; - }); - }) - // 3). get the chart-wrapper elements - .then(function getChartWrapper() { - return self - .setFindTimeout(defaultFindTimeout * 2) - .findAllByCssSelector(`.chart-wrapper circle[${cssPart}]`) - .then(function (chartTypes) { - - // 5). for each chart element, find the green circle, then the cy position - function getChartType(chart) { - return chart - .getAttribute('cy') - .then(function (cy) { - // PageObjects.common.debug(' yAxisHeight=' + yAxisHeight + ' yAxisLabel=' + yAxisLabel + ' cy=' + cy + - // ' ((yAxisHeight - cy)/yAxisHeight * yAxisLabel)=' + ((yAxisHeight - cy) / yAxisHeight * yAxisLabel)); - return Math.round((yAxisHeight - cy) / yAxisHeight * yAxisLabel); - }); - } - - // 4). pass the chartTypes to the getChartType function - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }); - }) - - .then(function (yCoords) { - return yCoords; - }); - } - - - // this is ALMOST identical to DiscoverPage.getBarChartData - getBarChartData() { - const self = this.remote; - let yAxisLabel = 0; - let yAxisHeight; - - // 1). get the maximim chart Y-Axis marker value - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type') - .then(function setYAxisLabel(y) { - return y - .getVisibleText() - .then(function (yLabel) { - yAxisLabel = yLabel.replace(',', ''); - PageObjects.common.debug('yAxisLabel = ' + yAxisLabel); - return yLabel; - }); - }) - // 2). find and save the y-axis pixel size (the chart height) - .then(function getRect() { - return self - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('rect.background') - .then(function getRectHeight(chartAreaObj) { - return chartAreaObj - .getAttribute('height') - .then(function (theHeight) { - yAxisHeight = theHeight; - PageObjects.common.debug('theHeight = ' + theHeight); - return theHeight; - }); - }); - }) - // 3). get the chart-wrapper elements - .then(function () { - return self - .setFindTimeout(defaultFindTimeout * 2) - // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) - .findAllByCssSelector('svg > g > g.series > rect') // rect - .then(function (chartTypes) { - function getChartType(chart) { - return chart - .getAttribute('fill') - .then(function (fillColor) { - // we're only getting the Green Bars - if (fillColor === '#6eadc1') { - return chart - .getAttribute('height') - .then(function (barHeight) { - return Math.round(barHeight / yAxisHeight * yAxisLabel); - }); - } - }); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(function (bars) { - return bars; - }); - }); - } - - getHeatmapData() { - // 1). get the maximim chart Y-Axis marker value - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) - .findAllByCssSelector('svg > g > g.series rect') // rect - .then(function (chartTypes) { - PageObjects.common.debug('rects=' + chartTypes); - function getChartType(chart) { - return chart - .getAttribute('data-label'); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(function (labels) { - PageObjects.common.debug('labels=' + labels); - return labels; - }); - } - - getPieChartData() { - // 1). get the maximim chart Y-Axis marker value - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - // path.slice:nth-child(11) - .findAllByCssSelector('path.slice') - .then(function (chartTypes) { - function getChartType(chart) { - return chart - .getAttribute('d') - .then(function (slice) { - return slice; - }); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then(function (slices) { - PageObjects.common.debug('slices=' + slices); - return slices; - }); - } - - getChartAreaWidth() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('clipPath rect') - .getAttribute('width'); - } - getChartAreaHeight() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('clipPath rect') - .getAttribute('height'); - } - - getDataTableData() { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('table.table.table-condensed tbody') - .getVisibleText(); - } - - getMarkdownData() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('visualize.ng-isolate-scope') - .getVisibleText(); - } - - clickColumns() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('div.schemaEditors.ng-scope > div > div > button:nth-child(2)') - .click(); - } - - waitForToastMessageGone() { - const self = this; - return PageObjects.common.try(function tryingForTime() { - return self.remote - .setFindTimeout(100) - .findAllByCssSelector('kbn-truncated.toast-message.ng-isolate-scope') - .then(function toastMessage(messages) { - if (messages.length > 0) { - throw new Error('waiting for toast message to clear'); - } else { - PageObjects.common.debug('now messages = 0 "' + messages + '"'); - return messages; - } - }); - }); - } - - waitForVisualization() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('visualize-legend'); - } - - clickMapButton(zoomSelector) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector(zoomSelector) - .click() - .then(() => { - return PageObjects.common.sleep(1000); - }) - .then(() => { - return PageObjects.header.waitUntilLoadingHasFinished(); - }); - } - - clickMapZoomIn() { - return this.clickMapButton('a.leaflet-control-zoom-in'); - } - - clickMapZoomOut() { - return this.clickMapButton('a.leaflet-control-zoom-out'); - } - - getMapZoomEnabled(zoomSelector) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector(zoomSelector) - .getAttribute('class') - .then((element) => { - return !element.toString().includes('leaflet-disabled'); - }); - } - - getMapZoomInEnabled() { - return this.getMapZoomEnabled('a.leaflet-control-zoom-in'); - } - - getMapZoomOutEnabled() { - return this.getMapZoomEnabled('a.leaflet-control-zoom-out'); - } - - clickMapFitDataBounds() { - return this.clickMapButton('a.fa-crop'); - } - - getTileMapData() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('path.leaflet-clickable') - .then((chartTypes) => { - - function getChartType(chart) { - let color; - let radius; - return chart.getAttribute('stroke') - .then((stroke) => { - color = stroke; - }) - .then(() => { - return chart.getAttribute('d'); - }) - .then((d) => { - // scale the radius up (sometimes less than 1) and then round to int - radius = d.replace(/.*A(\d+\.\d+),.*/,'$1') * 10; - radius = Math.round(radius); - }) - .then(() => { - return { color: color, radius: radius }; - }); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - }) - .then((circles) => { - return circles; - }); - } - -} diff --git a/test/support/page_objects/visualize_point_series_options.js b/test/support/page_objects/visualize_point_series_options.js deleted file mode 100644 index 881e192da50c0..0000000000000 --- a/test/support/page_objects/visualize_point_series_options.js +++ /dev/null @@ -1,171 +0,0 @@ - -import { - defaultFindTimeout, -} from '../'; - -import PageObjects from './'; - -export default class VisualizePointSeriesOptions { - - init(remote) { - this.remote = remote; - } - - clickOptions() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Panel Settings') - .click(); - } - - clickAxisOptions() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByPartialLinkText('Metrics & Axes') - .click(); - } - - clickAddAxis() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('button[aria-label="Add value axis"]') - .click(); - } - - getValueAxesCount() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.kuiSideBarSection:contains("Value Axes") > .kuiSideBarSection') - .length; - } - - getSeriesCount() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.kuiSideBarSection:contains("Series") > .kuiSideBarSection') - .length; - } - - getRightValueAxes() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.axis-wrapper-right g.axis') - .then(function (data) { - return Promise.all([function () { return data.length; }]); - }); - } - - getHistogramSeries() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.series.histogram') - .then(function (data) { - return Promise.all([function () { return data.length; }]); - }); - } - - getGridLines() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('g.grid > path') - .then(function (data) { - function getGridLine(gridLine) { - return gridLine - .getAttribute('d') - .then(dAttribute => { - const firstPoint = dAttribute.split('L')[0].replace('M', '').split(','); - return { x: parseFloat(firstPoint[0]), y: parseFloat(firstPoint[1]) }; - }); - } - const promises = data.map(getGridLine); - return Promise.all(promises); - }) - .then(function (gridLines) { - return gridLines; - }); - } - - toggleGridCategoryLines() { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('#showCategoryLines') - .click(); - } - - setGridValueAxis(axis) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`select#gridAxis option[value="string:${axis}"]`) - .click(); - } - - toggleCollapsibleTitle(title) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findAllByCssSelector('.kuiSideBarCollapsibleTitle .kuiSideBarCollapsibleTitle__text') - .then(sidebarTitles => { - PageObjects.common.debug('found sidebar titles ' + sidebarTitles.length); - function getTitle(titleDiv) { - return titleDiv - .getVisibleText() - .then(titleString => { - PageObjects.common.debug('sidebar title ' + titleString); - if (titleString === title) { - PageObjects.common.debug('clicking sidebar title ' + titleString); - return titleDiv.click(); - } - }); - } - const sidebarTitlePromises = sidebarTitles.map(getTitle); - return Promise.all(sidebarTitlePromises); - }); - } - - setValue(newValue) { - return this.remote - .setFindTimeout(defaultFindTimeout * 2) - .findByCssSelector('button[ng-click="numberListCntr.add()"]') - .click() - .then(() => { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') - .clearValue(); - }) - .then(() => { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector('input[ng-model="numberListCntr.getList()[$index]"]') - .type(newValue); - }); - } - - setValueAxisPosition(axis, position) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`select#valueAxisPosition${axis} option[label="${position}"]`) - .click(); - } - - setCategoryAxisPosition(newValue) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`select#categoryAxisPosition option[label="${newValue}"]`) - .click(); - } - - setSeriesAxis(series, axis) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`select#seriesValueAxis${series} option[value="${axis}"]`) - .click(); - } - - setSeriesType(series, type) { - return this.remote - .setFindTimeout(defaultFindTimeout) - .findByCssSelector(`select#seriesType${series} option[label="${type}"]`) - .click(); - } - -} diff --git a/test/support/utils/bdd_wrapper.js b/test/support/utils/bdd_wrapper.js deleted file mode 100644 index bc4fe370f9e5b..0000000000000 --- a/test/support/utils/bdd_wrapper.js +++ /dev/null @@ -1,44 +0,0 @@ - -import { attempt } from 'bluebird'; - -import PageObjects from '../page_objects'; - -export default class BddWrapper { - constructor(bdd) { - this.bdd = bdd; - } - - errorWrapper = fn => { - // we want to assume the context set - // by the test runner, so don't use an arrow - return function () { - const suiteOrTest = this; - const errorHandler = PageObjects.common.createErrorHandler(suiteOrTest); - return attempt(fn.bind(suiteOrTest)).catch(errorHandler); - }; - } - - describe = (name, fn) => { - this.bdd.describe(name, fn); - } - - before = (fn) => { - this.bdd.before(this.errorWrapper(fn)); - } - - beforeEach = (fn) => { - this.bdd.beforeEach(this.errorWrapper(fn)); - } - - it = (name, fn) => { - this.bdd.it(name, this.errorWrapper(fn)); - } - - afterEach = (fn) => { - this.bdd.afterEach(this.errorWrapper(fn)); - } - - after = (fn) => { - this.bdd.after(this.errorWrapper(fn)); - } -} diff --git a/test/support/utils/es_client.js b/test/support/utils/es_client.js deleted file mode 100644 index 9902f088ee836..0000000000000 --- a/test/support/utils/es_client.js +++ /dev/null @@ -1,160 +0,0 @@ - -import { - Log, - Try, -} from './'; - -import elasticsearch from 'elasticsearch'; -import Promise from 'bluebird'; - -export default (function () { - function EsClient(server) { - if (!server) throw new Error('No server defined'); - - // NOTE: some large sets of test data can take several minutes to load - this.client = new elasticsearch.Client({ - host: server, - requestTimeout: 300000, - defer: function () { - return Promise.defer(); - } - }); - } - - EsClient.prototype = { - constructor: EsClient, - - - /** - * Delete an index - * @param {string} index - * @return {Promise} A promise that is resolved when elasticsearch has a response - */ - delete: function (index) { - - return this.client.indices.delete({ - index: index - }) - .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) { - Log.debug('reason.message: ' + reason.message); - throw reason; - } - }); - }, - - /* - ** Gets configId which is needed when we're going to update the config doc. - ** Also used after deleting .kibana index to know Kibana has recreated it. - */ - getConfigId: function () { - let configId; - - return this.client.search({ - index: '.kibana', - type: 'config' - }) - .then(function (response) { - if (response.errors) { - throw new Error( - 'get config failed\n' + - response.items - .map(i => i[Object.keys(i)[0]].error) - .filter(Boolean) - .map(err => ' ' + JSON.stringify(err)) - .join('\n') - ); - } else { - configId = response.hits.hits[0]._id; - Log.debug('config._id = ' + configId); - return configId; - } - }); - }, - - /* - ** Gets defaultIndex from the config doc. - */ - getDefaultIndex: function () { - let defaultIndex; - - return this.client.search({ - index: '.kibana', - type: 'config' - }) - .then(function (response) { - if (response.errors) { - throw new Error( - 'get config failed\n' + - response.items - .map(i => i[Object.keys(i)[0]].error) - .filter(Boolean) - .map(err => ' ' + JSON.stringify(err)) - .join('\n') - ); - } else { - defaultIndex = response.hits.hits[0]._source.defaultIndex; - Log.debug('config.defaultIndex = ' + defaultIndex); - return defaultIndex; - } - }); - }, - - /** - * Add fields to the config doc (like setting timezone and defaultIndex) - * @return {Promise} A promise that is resolved when elasticsearch has a response - */ - updateConfigDoc: function (docMap) { - // first we need to get the config doc's id so we can use it in our _update call - const self = this; - const docMapString = JSON.stringify(docMap); - - return this.getConfigId() - // now that we have the id, we can update - .then(function (configId) { - Log.debug('updating config with ' + docMapString); - return self.client.update({ - index: '.kibana', - type: 'config', - id: configId, - body: { - 'doc': - docMap - } - }); - }) - .catch(function (err) { - throw err; - }); - }, - - /** - * Wrap the common 'delete index', 'updateConfigDoc' into one. - * [docMap] is optional. - * @return {Promise} A promise that is resolved when elasticsearch has a response - */ - deleteAndUpdateConfigDoc: function (docMap) { - const self = this; - - return this.delete('.kibana') - .then(function () { - if (!docMap) { - return Try.try(function () { - return self.getConfigId(); - }); - } else { - return Try.try(function () { - return self.updateConfigDoc(docMap); - }); - } - }) - .catch(function (err) { - throw err; - }); - } - - }; - - return EsClient; -}()); diff --git a/test/support/utils/index.js b/test/support/utils/index.js deleted file mode 100644 index 8a0874d04f824..0000000000000 --- a/test/support/utils/index.js +++ /dev/null @@ -1,16 +0,0 @@ - -export { - default as BddWrapper -} from './bdd_wrapper'; - -export { - default as EsClient -} from './es_client'; - -export { - default as Log -} from './log'; - -export { - default as Try -} from './try'; diff --git a/test/support/utils/log.js b/test/support/utils/log.js deleted file mode 100644 index eb30a6595e8f6..0000000000000 --- a/test/support/utils/log.js +++ /dev/null @@ -1,29 +0,0 @@ - -import moment from 'moment'; -import util from 'util'; - -import { - config -} from '../index'; - -class Log { - - log(...args) { - console.log(moment().format('HH:mm:ss.SSS') + ':', util.format(...args)); - } - - info(...args) { - this.log('INFO', util.format(...args)); - } - - error(...args) { - this.log('ERROR', util.format(...args)); - } - - debug(...args) { - if (config.debug) this.log(...args); - } - -} - -export default new Log(); diff --git a/test/support/utils/try.js b/test/support/utils/try.js deleted file mode 100644 index 25fb6a52b06f8..0000000000000 --- a/test/support/utils/try.js +++ /dev/null @@ -1,49 +0,0 @@ - -import bluebird from 'bluebird'; - -import { - defaultTryTimeout -} from '../index'; - -import Log from './log.js'; - -class Try { - - tryForTime(timeout, block) { - const start = Date.now(); - const retryDelay = 502; - let lastTry = 0; - let finalMessage; - let prevMessage; - - function attempt() { - lastTry = Date.now(); - - if (lastTry - start > timeout) { - throw new Error('tryForTime timeout: ' + finalMessage); - } - - return bluebird - .try(block) - .catch(function tryForTimeCatch(err) { - if (err.message === prevMessage) { - Log.debug('--- tryForTime failed again with the same message ...'); - } else { - prevMessage = err.message; - Log.debug('--- tryForTime failure: ' + prevMessage); - } - finalMessage = err.stack || err.message; - return bluebird.delay(retryDelay).then(attempt); - }); - } - - return bluebird.try(attempt); - } - - try(block) { - return this.tryForTime(defaultTryTimeout, block); - } - -} - -export default new Try(); diff --git a/test/unit/api/ingest/_field_capabilities.js b/test/unit/api/ingest/_field_capabilities.js index e22689b0ffaed..4584423be6ea4 100644 --- a/test/unit/api/ingest/_field_capabilities.js +++ b/test/unit/api/ingest/_field_capabilities.js @@ -1,85 +1,83 @@ -define(function (require) { - const expect = require('intern/dojo/node!expect.js'); - const { client, emptyKibana } = require('intern/dojo/node!../lib/es'); +import expect from 'expect.js'; +import { client, emptyKibana } from '../lib/es'; - return function (bdd, request) { - bdd.describe('field_capabilities API', function postIngest() { +export default function (request) { + describe('field_capabilities API', function postIngest() { - bdd.before(function () { + before(() => { + return client.create({ + index: 'foo-1', + type: 'bar', + id: '1', + body: { + foo: 'bar' + } + }) + .then(() => { return client.create({ - index: 'foo-1', + index: 'foo-2', type: 'bar', - id: '1', + id: '2', body: { - foo: 'bar' + baz: 'bar' } - }) - .then(function () { - return client.create({ - index: 'foo-2', - type: 'bar', - id: '2', - body: { - baz: 'bar' - } - }); - }) - .then(function () { - return client.indices.refresh({ - index: ['foo-1', 'foo-2'] - }); }); - }); - - bdd.after(function () { - return emptyKibana.setup().then(function () { - return client.indices.delete({ - index: 'foo*' - }); + }) + .then(() => { + return client.indices.refresh({ + index: ['foo-1', 'foo-2'] }); }); + }); - bdd.it('should return searchable/aggregatable flags for fields in the indices specified', function () { - return request.get('/kibana/foo-1/field_capabilities') - .expect(200) - .then(function (response) { - const fields = response.body.fields; - expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); - expect(fields['foo.keyword']).to.eql({ searchable: true, aggregatable: true }); - expect(fields).to.not.have.property('baz'); + after(() => { + return emptyKibana.setup().then(() => { + return client.indices.delete({ + index: 'foo*' }); }); + }); - bdd.it('should accept wildcards in the index name', function () { - return request.get('/kibana/foo-*/field_capabilities') - .expect(200) - .then(function (response) { - const fields = response.body.fields; - expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); - expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); - }); + it('should return searchable/aggregatable flags for fields in the indices specified', () => { + return request.get('/kibana/foo-1/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields['foo.keyword']).to.eql({ searchable: true, aggregatable: true }); + expect(fields).to.not.have.property('baz'); }); + }); - bdd.it('should accept comma delimited lists of indices', function () { - return request.get('/kibana/foo-1,foo-2/field_capabilities') - .expect(200) - .then(function (response) { - const fields = response.body.fields; - expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); - expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); - }); + it('should accept wildcards in the index name', () => { + return request.get('/kibana/foo-*/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); }); + }); - bdd.it('should return 404 if a pattern matches no indices', function () { - return request.post('/kibana/doesnotexist-*/field_capabilities') - .expect(404); + it('should accept comma delimited lists of indices', () => { + return request.get('/kibana/foo-1,foo-2/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); }); + }); - bdd.it('should return 404 if a concrete index does not exist', function () { - return request.post('/kibana/concrete/field_capabilities') - .expect(404); - }); + it('should return 404 if a pattern matches no indices', () => { + return request.post('/kibana/doesnotexist-*/field_capabilities') + .expect(404); + }); + it('should return 404 if a concrete index does not exist', () => { + return request.post('/kibana/concrete/field_capabilities') + .expect(404); }); - }; -}); + + }); +} diff --git a/test/unit/api/ingest/data.js b/test/unit/api/ingest/data.js deleted file mode 100644 index b64c1f5007fbf..0000000000000 --- a/test/unit/api/ingest/data.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = function createTestData() { - return { - 'index_pattern': { - 'id': 'logstash-*', - 'title': 'logstash-*', - 'time_field_name': '@timestamp', - 'fields': [ - { - 'name': 'ip', - 'type': 'ip' - }, { - 'name': '@timestamp', - 'type': 'date' - }, { - 'name': 'agent', - 'type': 'string' - }, { - 'name': 'bytes', - 'type': 'number' - }, - { - 'name': 'geo.coordinates', - 'type': 'geo_point' - } - ] - } - }; -}; diff --git a/test/unit/api/ingest/index.js b/test/unit/api/ingest/index.js index 5c44fbbea9fb9..256ce9f2fe545 100644 --- a/test/unit/api/ingest/index.js +++ b/test/unit/api/ingest/index.js @@ -1,22 +1,19 @@ -define(function (require) { - const bdd = require('intern!bdd'); - const serverConfig = require('intern/dojo/node!../../../server_config'); - const { emptyKibana } = require('intern/dojo/node!../lib/es'); - let request = require('intern/dojo/node!supertest-as-promised'); - const url = require('intern/dojo/node!url'); - const fieldCapabilities = require('./_field_capabilities'); +import serverConfig from '../../../server_config'; +import { emptyKibana } from '../lib/es'; +import supertestAsPromised from 'supertest-as-promised'; +import url from 'url'; +import fieldCapabilities from './_field_capabilities'; - bdd.describe('ingest API', function () { - request = request(url.format(serverConfig.servers.kibana) + '/api'); +describe('ingest API', () => { + const request = supertestAsPromised(url.format(serverConfig.servers.kibana) + '/api'); - bdd.before(function () { - return emptyKibana.setup(); - }); - - bdd.after(function () { - return emptyKibana.teardown(); - }); + before(() => { + return emptyKibana.setup(); + }); - fieldCapabilities(bdd, request); + after(() => { + return emptyKibana.teardown(); }); + + fieldCapabilities(request); }); diff --git a/test/unit/api/ingest_field_capabilities.js b/test/unit/api/ingest_field_capabilities.js new file mode 100644 index 0000000000000..9043ac77534b1 --- /dev/null +++ b/test/unit/api/ingest_field_capabilities.js @@ -0,0 +1,84 @@ +import expect from 'expect.js'; + +import { emptyKibana, client, supertest } from './lib'; + +describe('ingest field_capabilities API', () => { + before(() => emptyKibana.setup()); + after(() => emptyKibana.teardown()); + + before(() => { + return client.create({ + index: 'foo-1', + type: 'bar', + id: '1', + body: { + foo: 'bar' + } + }) + .then(() => { + return client.create({ + index: 'foo-2', + type: 'bar', + id: '2', + body: { + baz: 'bar' + } + }); + }) + .then(() => { + return client.indices.refresh({ + index: ['foo-1', 'foo-2'] + }); + }); + }); + + after(() => { + return emptyKibana.setup().then(() => { + return client.indices.delete({ + index: 'foo*' + }); + }); + }); + + it('should return searchable/aggregatable flags for fields in the indices specified', () => { + return supertest.get('/kibana/foo-1/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields['foo.keyword']).to.eql({ searchable: true, aggregatable: true }); + expect(fields).to.not.have.property('baz'); + }); + }); + + it('should accept wildcards in the index name', () => { + return supertest.get('/kibana/foo-*/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); + }); + }); + + it('should accept comma delimited lists of indices', () => { + return supertest.get('/kibana/foo-1,foo-2/field_capabilities') + .expect(200) + .then((response) => { + const fields = response.body.fields; + expect(fields.foo).to.eql({ searchable: true, aggregatable: false }); + expect(fields.baz).to.eql({ searchable: true, aggregatable: false }); + }); + }); + + it('should return 404 if a pattern matches no indices', () => { + return supertest.post('/kibana/doesnotexist-*/field_capabilities') + .expect(404); + }); + + it('should return 404 if a concrete index does not exist', () => { + return supertest.post('/kibana/concrete/field_capabilities') + .expect(404); + }); + +}); diff --git a/test/unit/api/lib/es.js b/test/unit/api/lib/es.js index d837b233e5fbd..d392ae8a56c8f 100644 --- a/test/unit/api/lib/es.js +++ b/test/unit/api/lib/es.js @@ -1,8 +1,8 @@ -const { Client } = require('elasticsearch'); -const serverConfig = require('../../../server_config'); -const pkg = require('../../../../package.json'); +import { Client } from 'elasticsearch'; +import serverConfig from '../../../server_config'; +import pkg from '../../../../package.json'; -const client = new Client({ +export const client = new Client({ host: serverConfig.servers.elasticsearch }); @@ -23,6 +23,4 @@ class EmptyKibana { } } -const emptyKibana = new EmptyKibana(); - -module.exports = { client, emptyKibana }; +export const emptyKibana = new EmptyKibana(); diff --git a/test/unit/api/lib/index.js b/test/unit/api/lib/index.js new file mode 100644 index 0000000000000..3f71c48c76ce3 --- /dev/null +++ b/test/unit/api/lib/index.js @@ -0,0 +1,2 @@ +export { client, emptyKibana } from './es'; +export { supertest } from './supertest'; diff --git a/test/unit/api/lib/supertest.js b/test/unit/api/lib/supertest.js new file mode 100644 index 0000000000000..23d2e290b960b --- /dev/null +++ b/test/unit/api/lib/supertest.js @@ -0,0 +1,12 @@ +import { format as formatUrl } from 'url'; + +import supertestAsPromised from 'supertest-as-promised'; + +import serverConfig from '../../../server_config'; + +export const supertest = supertestAsPromised( + formatUrl({ + ...serverConfig.servers.kibana, + pathname: '/api', + }) +); diff --git a/test/unit/api/scripts/_languages.js b/test/unit/api/scripts/_languages.js deleted file mode 100644 index b2a57475a3767..0000000000000 --- a/test/unit/api/scripts/_languages.js +++ /dev/null @@ -1,27 +0,0 @@ -define(function (require) { - const expect = require('intern/dojo/node!expect.js'); - - return function (bdd, request) { - bdd.describe('Languages API', function getLanguages() { - - bdd.it('should return 200 with an array of languages', function () { - return request.get('/kibana/scripts/languages') - .expect(200) - .then(function (response) { - expect(response.body).to.be.an('array'); - }); - }); - - bdd.it('should only return langs enabled for inline scripting', function () { - return request.get('/kibana/scripts/languages') - .expect(200) - .then(function (response) { - expect(response.body).to.contain('expression'); - expect(response.body).to.contain('painless'); - - expect(response.body).to.not.contain('groovy'); - }); - }); - }); - }; -}); diff --git a/test/unit/api/scripts/index.js b/test/unit/api/scripts/index.js deleted file mode 100644 index 320fcd0754d71..0000000000000 --- a/test/unit/api/scripts/index.js +++ /dev/null @@ -1,13 +0,0 @@ -define(function (require) { - const bdd = require('intern!bdd'); - const serverConfig = require('intern/dojo/node!../../../server_config'); - let request = require('intern/dojo/node!supertest-as-promised'); - const url = require('intern/dojo/node!url'); - const languages = require('./_languages'); - - bdd.describe('scripts API', function () { - request = request(url.format(serverConfig.servers.kibana) + '/api'); - - languages(bdd, request); - }); -}); diff --git a/test/unit/api/scripts_languages.js b/test/unit/api/scripts_languages.js new file mode 100644 index 0000000000000..6c09d0900b992 --- /dev/null +++ b/test/unit/api/scripts_languages.js @@ -0,0 +1,27 @@ +import expect from 'expect.js'; + +import { supertest, emptyKibana } from './lib'; + +describe('Script Languages API', function getLanguages() { + before(() => emptyKibana.setup()); + after(() => emptyKibana.teardown()); + + it('should return 200 with an array of languages', () => { + return supertest.get('/kibana/scripts/languages') + .expect(200) + .then((response) => { + expect(response.body).to.be.an('array'); + }); + }); + + it('should only return langs enabled for inline scripting', () => { + return supertest.get('/kibana/scripts/languages') + .expect(200) + .then((response) => { + expect(response.body).to.contain('expression'); + expect(response.body).to.contain('painless'); + + expect(response.body).to.not.contain('groovy'); + }); + }); +}); diff --git a/test/unit/api/search/_count.js b/test/unit/api/search/_count.js index b83a23f0f85b8..2058e8a7f0795 100644 --- a/test/unit/api/search/_count.js +++ b/test/unit/api/search/_count.js @@ -1,70 +1,68 @@ -define(function (require) { - const expect = require('intern/dojo/node!expect.js'); - const { client, emptyKibana } = require('intern/dojo/node!../lib/es'); +import expect from 'expect.js'; +import { client, emptyKibana } from '../lib/es'; - return function (bdd, request) { - bdd.describe('Count API', function postIngest() { +export default function (request) { + describe('Count API', function postIngest() { - bdd.before(function () { + before(() => { + return client.index({ + index: 'foo-1', + type: 'bar', + id: '1', + body: { + foo: 'bar' + } + }) + .then(() => { return client.index({ - index: 'foo-1', + index: 'foo-2', type: 'bar', - id: '1', + id: '2', body: { foo: 'bar' } - }) - .then(function () { - return client.index({ - index: 'foo-2', - type: 'bar', - id: '2', - body: { - foo: 'bar' - } - }); - }) - .then(function () { - return client.indices.refresh({ - index: ['foo-1', 'foo-2'] - }); }); - }); - - bdd.after(function () { - return emptyKibana.setup().then(function () { - return client.indices.delete({ - index: 'foo*' - }); + }) + .then(() => { + return client.indices.refresh({ + index: ['foo-1', 'foo-2'] }); }); + }); - bdd.it('should return 200 with a document count for existing indices', function () { - return request.post('/kibana/foo-*/_count') - .expect(200) - .then(function (response) { - expect(response.body.count).to.be(2); + after(() => { + return emptyKibana.setup().then(() => { + return client.indices.delete({ + index: 'foo*' }); }); + }); - bdd.it('should support GET requests as well', function () { - return request.get('/kibana/foo-*/_count') - .expect(200) - .then(function (response) { - expect(response.body.count).to.be(2); - }); + it('should return 200 with a document count for existing indices', () => { + return request.post('/kibana/foo-*/_count') + .expect(200) + .then((response) => { + expect(response.body.count).to.be(2); }); + }); - bdd.it('should return 404 if a pattern matches no indices', function () { - return request.post('/kibana/doesnotexist-*/_count') - .expect(404); + it('should support GET requests as well', () => { + return request.get('/kibana/foo-*/_count') + .expect(200) + .then((response) => { + expect(response.body.count).to.be(2); }); + }); - bdd.it('should return 404 if a concrete index does not exist', function () { - return request.post('/kibana/concrete/_count') - .expect(404); - }); + it('should return 404 if a pattern matches no indices', () => { + return request.post('/kibana/doesnotexist-*/_count') + .expect(404); + }); + it('should return 404 if a concrete index does not exist', () => { + return request.post('/kibana/concrete/_count') + .expect(404); }); - }; -}); + + }); +} diff --git a/test/unit/api/search/index.js b/test/unit/api/search/index.js index f84cca03c1c2c..8bf9a56bd5547 100644 --- a/test/unit/api/search/index.js +++ b/test/unit/api/search/index.js @@ -1,22 +1,19 @@ -define(function (require) { - const bdd = require('intern!bdd'); - const serverConfig = require('intern/dojo/node!../../../server_config'); - const { emptyKibana } = require('intern/dojo/node!../lib/es'); - let request = require('intern/dojo/node!supertest-as-promised'); - const url = require('intern/dojo/node!url'); - const count = require('./_count'); +import serverConfig from '../../../server_config'; +import { emptyKibana } from '../lib/es'; +import supertestAsPromised from 'supertest-as-promised'; +import url from 'url'; +import count from './_count'; - bdd.describe('search API', function () { - request = request(url.format(serverConfig.servers.kibana) + '/api'); +describe('search API', () => { + const request = supertestAsPromised(url.format(serverConfig.servers.kibana) + '/api'); - bdd.before(function () { - return emptyKibana.setup(); - }); - - bdd.after(function () { - return emptyKibana.teardown(); - }); + before(() => { + return emptyKibana.setup(); + }); - count(bdd, request); + after(() => { + return emptyKibana.teardown(); }); + + count(request); }); diff --git a/test/unit/api/search_count.js b/test/unit/api/search_count.js new file mode 100644 index 0000000000000..074826a9153d6 --- /dev/null +++ b/test/unit/api/search_count.js @@ -0,0 +1,68 @@ +import expect from 'expect.js'; + +import { client, emptyKibana, supertest } from './lib'; + +describe('Search Count API', function postIngest() { + before(() => emptyKibana.setup()); + after(() => emptyKibana.teardown()); + + before(() => { + return client.index({ + index: 'foo-1', + type: 'bar', + id: '1', + body: { + foo: 'bar' + } + }) + .then(() => { + return client.index({ + index: 'foo-2', + type: 'bar', + id: '2', + body: { + foo: 'bar' + } + }); + }) + .then(() => { + return client.indices.refresh({ + index: ['foo-1', 'foo-2'] + }); + }); + }); + + after(() => { + return emptyKibana.setup().then(() => { + return client.indices.delete({ + index: 'foo*' + }); + }); + }); + + it('should return 200 with a document count for existing indices', () => { + return supertest.post('/kibana/foo-*/_count') + .expect(200) + .then((response) => { + expect(response.body.count).to.be(2); + }); + }); + + it('should support GET requests as well', () => { + return supertest.get('/kibana/foo-*/_count') + .expect(200) + .then((response) => { + expect(response.body.count).to.be(2); + }); + }); + + it('should return 404 if a pattern matches no indices', () => { + return supertest.post('/kibana/doesnotexist-*/_count') + .expect(404); + }); + + it('should return 404 if a concrete index does not exist', () => { + return supertest.post('/kibana/concrete/_count') + .expect(404); + }); +}); diff --git a/test/utils/get_url.js b/test/utils/get_url.js index f381502bb1969..5939f4fe8a748 100644 --- a/test/utils/get_url.js +++ b/test/utils/get_url.js @@ -23,7 +23,7 @@ import url from 'url'; module.exports = getUrl; function getUrl(config, app) { - return url.format(_.assign(config, app)); + return url.format(_.assign({}, config, app)); } getUrl.noAuth = function getUrlNoAuth(config, app) { diff --git a/test/visual_regression/home/_loading.js b/test/visual_regression/home/_loading.js deleted file mode 100644 index e28e749fa31f4..0000000000000 --- a/test/visual_regression/home/_loading.js +++ /dev/null @@ -1,7 +0,0 @@ -import { bdd } from '../../support'; - -bdd.describe('Loading', function coverLoadingUi() { - bdd.it('should show loading feebdack', async function () { - // TODO: Take screenshots here. - }); -}); diff --git a/test/visual_regression/index.js b/test/visual_regression/index.js deleted file mode 100644 index 17bec9761f36e..0000000000000 --- a/test/visual_regression/index.js +++ /dev/null @@ -1,26 +0,0 @@ -define(function (require) { - require('intern/dojo/node!../support/env_setup'); - - const bdd = require('intern!bdd'); - const intern = require('intern'); - const initCallbacks = []; - - function onInit(callback) { - initCallbacks.push(callback); - } - - global.__kibana__intern__ = { intern, bdd, onInit }; - - bdd.describe('Kibana visual regressions', function () { - bdd.before(function () { - initCallbacks.forEach(callback => { - callback.call(this); - }); - }); - - require([ - 'intern/dojo/node!../support/index', - 'intern/dojo/node!./home', - ], function () {}); - }); -});