You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jo...@apache.org on 2018/09/06 21:55:51 UTC

[incubator-superset] branch master updated: Adding simple Cypress tests (#5693)

This is an automated email from the ASF dual-hosted git repository.

johnbodley pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new d40ded0  Adding simple Cypress tests (#5693)
d40ded0 is described below

commit d40ded0be8d4d777530c609b1fc14840c754bc10
Author: michellethomas <mi...@gmail.com>
AuthorDate: Thu Sep 6 14:55:48 2018 -0700

    Adding simple Cypress tests (#5693)
    
    * Adding simple Cypress tests
    
    * Changing visitChart into multiple commands
    
    * Adding Cypress to tox
---
 .travis.yml                                        |   2 +
 superset/assets/cypress.json                       |   3 +
 .../integration/dashboard/dashboard_tests.js       |  26 ++
 .../cypress/integration/explore/control_tests.js   |  59 +++
 .../integration/explore/visualization_tests.js     |  54 +++
 superset/assets/cypress/plugins/index.js           |  17 +
 superset/assets/cypress/support/commands.js        |  59 +++
 superset/assets/cypress/support/index.js           |  20 +
 superset/assets/cypress_build.sh                   |  16 +
 superset/assets/package.json                       |   4 +-
 superset/assets/src/explore/components/Control.jsx |   1 +
 superset/assets/yarn.lock                          | 485 +++++++++++++++++----
 superset/cli.py                                    |  67 +++
 superset/data/__init__.py                          |   8 +
 tests/base_tests.py                                |  59 +--
 tests/celery_tests.py                              |  11 +-
 tests/core_tests.py                                |  19 +-
 tests/dict_import_export_tests.py                  |   3 +-
 tests/form_tests.py                                |   2 +-
 tests/model_tests.py                               |   7 +-
 tests/sqllab_tests.py                              |   3 +-
 tests/utils.py                                     |  11 +
 tox.ini                                            |  16 +-
 23 files changed, 801 insertions(+), 151 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 16cdbb3..a02d009 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,6 +28,8 @@ matrix:
       env: TOXENV=py36-sqlite
     - python: 3.6
       env: TOXENV=pylint
+    - python: 3.6
+      env: TOXENV=cypress
   exclude:
     - python: 2.7
     - python: 3.6
diff --git a/superset/assets/cypress.json b/superset/assets/cypress.json
new file mode 100644
index 0000000..c84e05d
--- /dev/null
+++ b/superset/assets/cypress.json
@@ -0,0 +1,3 @@
+{
+	"baseUrl": "http://localhost:8081"
+}
diff --git a/superset/assets/cypress/integration/dashboard/dashboard_tests.js b/superset/assets/cypress/integration/dashboard/dashboard_tests.js
new file mode 100644
index 0000000..10e4f11
--- /dev/null
+++ b/superset/assets/cypress/integration/dashboard/dashboard_tests.js
@@ -0,0 +1,26 @@
+describe('Load dashboard', function () {
+  it('Load birth names dashboard', function () {
+    cy.server();
+    cy.login();
+
+    cy.visit('/superset/dashboard/births');
+
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+    cy.wait(10000, ['@getJson']);
+
+    let sliceData;
+
+    cy.get('@getJson.all').then((xhrs) => {
+      sliceData = xhrs;
+      xhrs.forEach((data) => {
+        expect(data.status).to.eq(200);
+        expect(data.response.body).to.have.property('error', null);
+        cy.get(`#slice-container-${data.response.body.form_data.slice_id}`);
+      });
+      cy.get('#app').then((data) => {
+        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+        expect(bootstrapData.dashboard_data.slices.length).to.eq(sliceData.length);
+      });
+    });
+  });
+});
diff --git a/superset/assets/cypress/integration/explore/control_tests.js b/superset/assets/cypress/integration/explore/control_tests.js
new file mode 100644
index 0000000..d4c5e4c
--- /dev/null
+++ b/superset/assets/cypress/integration/explore/control_tests.js
@@ -0,0 +1,59 @@
+// ***********************************************
+// Tests for setting controls in the UI
+// ***********************************************
+
+describe('Groupby', function () {
+  it('Set groupby', function () {
+    cy.server();
+    cy.login();
+
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+    cy.visitChartByName('Num Births Trend');
+    cy.verifySliceSuccess('@getJson');
+
+    cy.get('[data-test=groupby]').within(() => {
+      cy.get('.Select-control').click();
+      cy.get('input.select-input').type('state', { force: true });
+      cy.get('.VirtualizedSelectFocusedOption').click();
+    });
+    cy.get('button.query').click();
+    cy.verifySliceSuccess('@getJson');
+  });
+});
+
+describe('SimpleAdhocMetric', function () {
+  it('Clear metric and set simple adhoc metric', function () {
+    cy.server();
+    cy.login();
+
+    const metricName = 'Girl Births';
+
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+    cy.visitChartByName('Num Births Trend');
+    cy.verifySliceSuccess('@getJson');
+
+    cy.get('[data-test=metrics]').within(() => {
+      cy.get('.select-clear').click();
+      cy.get('.Select-control').click({ force: true });
+      cy.get('input').type('sum_girls', { force: true });
+      cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click();
+    });
+
+    cy.get('#metrics-edit-popover').within(() => {
+      cy.get('.popover-title').within(() => {
+        cy.get('span').click();
+        cy.get('input').type(metricName);
+      });
+      cy.get('button').contains('Save').click();
+    });
+
+    cy.get('button.query').click();
+    cy.wait(['@getJson']).then((data) => {
+      expect(data.status).to.eq(200);
+      expect(data.response.body).to.have.property('error', null);
+      expect(data.response.body.data[0].key).to.equal(metricName);
+      cy.get('.slice_container');
+    });
+  });
+});
+
diff --git a/superset/assets/cypress/integration/explore/visualization_tests.js b/superset/assets/cypress/integration/explore/visualization_tests.js
new file mode 100644
index 0000000..50c331d
--- /dev/null
+++ b/superset/assets/cypress/integration/explore/visualization_tests.js
@@ -0,0 +1,54 @@
+// ***********************************************
+// Tests for visualization types
+// ***********************************************
+
+const FORM_DATA_DEFAULTS = {
+  datasource: '3__table',
+  viz_type: 'line',
+  granularity_sqla: 'ds',
+  time_grain_sqla: null,
+  time_range: '100+years+ago+:+now',
+  adhoc_filters: [],
+  groupby: [],
+  limit: null,
+  timeseries_limit_metric: null,
+  order_desc: false,
+  contribution: false,
+};
+
+describe('Line', function () {
+  it('Test line chart with adhoc metric', function () {
+    cy.server();
+    cy.login();
+
+    const metrics = [{
+      expressionType: 'SIMPLE',
+      column: {
+        id: 336,
+        column_name: 'num',
+        verbose_name: null,
+        description: null,
+        expression: '',
+        filterable: false,
+        groupby: false,
+        is_dttm: false,
+        type: 'BIGINT',
+        database_expression: null,
+        python_date_format: null,
+        optionName: '_col_num',
+      },
+      aggregate: 'SUM',
+      sqlExpression: null,
+      hasCustomLabel: false,
+      fromFormData: false,
+      label: 'SUM(num)',
+      optionName: 'metric_1de0s4viy5d_ly7y8k6ghvk',
+    }];
+
+    const formData = { ...FORM_DATA_DEFAULTS, metrics };
+
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess('@getJson');
+  });
+});
diff --git a/superset/assets/cypress/plugins/index.js b/superset/assets/cypress/plugins/index.js
new file mode 100644
index 0000000..df3a5ae
--- /dev/null
+++ b/superset/assets/cypress/plugins/index.js
@@ -0,0 +1,17 @@
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+module.exports = (/* on, config */) => {
+  // `on` is used to hook into various events Cypress emits
+  // `config` is the resolved Cypress config
+};
diff --git a/superset/assets/cypress/support/commands.js b/superset/assets/cypress/support/commands.js
new file mode 100644
index 0000000..41c64a7
--- /dev/null
+++ b/superset/assets/cypress/support/commands.js
@@ -0,0 +1,59 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This is will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+
+const BASE_EXPLORE_URL = '/superset/explore/?form_data=';
+
+Cypress.Commands.add('login', () => {
+  cy.request({
+    method: 'POST',
+    url: 'http://localhost:8081/login/',
+    body: { username: 'admin', password: 'general' },
+  }).then((response) => {
+    expect(response.status).to.eq(200);
+  });
+});
+
+Cypress.Commands.add('visitChartByName', (name) => {
+  cy.request(`http://localhost:8081/chart/api/read?_flt_3_slice_name=${name}`).then((response) => {
+    cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${response.body.pks[0]}}`);
+  });
+});
+
+Cypress.Commands.add('visitChartById', (chartId) => {
+  cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${chartId}}`);
+});
+
+Cypress.Commands.add('visitChartByParams', (params) => {
+  cy.visit(`${BASE_EXPLORE_URL}${params}`);
+});
+
+Cypress.Commands.add('verifySliceSuccess', (waitAlias) => {
+  cy.wait([waitAlias]).then((data) => {
+    expect(data.status).to.eq(200);
+    expect(data.response.body).to.have.property('error', null);
+    cy.get('.slice_container');
+  });
+});
diff --git a/superset/assets/cypress/support/index.js b/superset/assets/cypress/support/index.js
new file mode 100644
index 0000000..37a498f
--- /dev/null
+++ b/superset/assets/cypress/support/index.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands';
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/superset/assets/cypress_build.sh b/superset/assets/cypress_build.sh
new file mode 100755
index 0000000..dd80e87
--- /dev/null
+++ b/superset/assets/cypress_build.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+
+superset/bin/superset db upgrade
+superset/bin/superset load_test_users
+superset/bin/superset load_examples
+superset/bin/superset init
+superset/bin/superset runserver &
+
+cd "$(dirname "$0")"
+
+npm install -g yarn
+yarn
+npm run build
+npm run cypress run
+kill %1
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 13122fe..f36ffde 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -17,7 +17,8 @@
     "build": "webpack --mode=production --colors --progress",
     "lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx .",
     "lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx .",
-    "sync-backend": "babel-node --presets env src/syncBackend.js"
+    "sync-backend": "babel-node --presets env src/syncBackend.js",
+    "cypress": "cypress"
   },
   "repository": {
     "type": "git",
@@ -141,6 +142,7 @@
     "chai": "^4.0.2",
     "clean-webpack-plugin": "^0.1.19",
     "css-loader": "^0.28.0",
+    "cypress": "^3.0.3",
     "enzyme": "^2.0.0",
     "eslint": "^4.19.0",
     "eslint-config-airbnb": "^15.0.1",
diff --git a/superset/assets/src/explore/components/Control.jsx b/superset/assets/src/explore/components/Control.jsx
index 52682de..bc58ec2 100644
--- a/superset/assets/src/explore/components/Control.jsx
+++ b/superset/assets/src/explore/components/Control.jsx
@@ -84,6 +84,7 @@ export default class Control extends React.PureComponent {
     const divStyle = this.props.hidden ? { display: 'none' } : null;
     return (
       <div
+        data-test={this.props.name}
         style={divStyle}
         onMouseEnter={this.setHover.bind(this, true)}
         onMouseLeave={this.setHover.bind(this, false)}
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index e1a0d88..2b330de 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -172,6 +172,22 @@
     lodash "^4.17.10"
     to-fast-properties "^2.0.0"
 
+"@cypress/listr-verbose-renderer@0.4.1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a"
+  dependencies:
+    chalk "^1.1.3"
+    cli-cursor "^1.0.2"
+    date-fns "^1.27.2"
+    figures "^1.7.0"
+
+"@cypress/xvfb@1.2.3":
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.3.tgz#6319afdcdcff7d1505daeeaa84484d0596189860"
+  dependencies:
+    debug "^3.1.0"
+    lodash.once "^4.1.1"
+
 "@data-ui/event-flow@^0.0.54":
   version "0.0.54"
   resolved "https://registry.yarnpkg.com/@data-ui/event-flow/-/event-flow-0.0.54.tgz#bb03e1fd2b5634248655b8df9d3c6c38a747e65e"
@@ -448,10 +464,68 @@
   dependencies:
     samsam "1.3.0"
 
+"@types/blob-util@1.3.3":
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a"
+
+"@types/bluebird@3.5.18":
+  version "3.5.18"
+  resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6"
+
+"@types/chai-jquery@1.1.35":
+  version "1.1.35"
+  resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.35.tgz#9a8f0a39ec0851b2768a8f8c764158c2a2568d04"
+  dependencies:
+    "@types/chai" "*"
+    "@types/jquery" "*"
+
+"@types/chai@*":
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.4.tgz#5ca073b330d90b4066d6ce18f60d57f2084ce8ca"
+
+"@types/chai@4.0.8":
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.8.tgz#d27600e9ba2f371e08695d90a0fe0408d89c7be7"
+
 "@types/d3@3.5.38":
   version "3.5.38"
   resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.38.tgz#76f8f2e9159ae562965b2fa0e6fbee1aa643a1bc"
 
+"@types/jquery@*":
+  version "3.3.6"
+  resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.6.tgz#5932ead926307ca21e5b36808257f7c926b06565"
+
+"@types/jquery@3.2.16":
+  version "3.2.16"
+  resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.2.16.tgz#04419c404a3194350e7d3f339a90e72c88db3111"
+
+"@types/lodash@4.14.87":
+  version "4.14.87"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.87.tgz#55f92183b048c2c64402afe472f8333f4e319a6b"
+
+"@types/minimatch@3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
+
+"@types/mocha@2.2.44":
+  version "2.2.44"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e"
+
+"@types/sinon-chai@2.7.29":
+  version "2.7.29"
+  resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-2.7.29.tgz#4db01497e2dd1908b2bd30d1782f456353f5f723"
+  dependencies:
+    "@types/chai" "*"
+    "@types/sinon" "*"
+
+"@types/sinon@*":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21"
+
+"@types/sinon@4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.0.0.tgz#9a93ffa4ee1329e85166278a5ed99f81dc4c8362"
+
 "@vx/axis@0.0.140":
   version "0.0.140"
   resolved "https://registry.yarnpkg.com/@vx/axis/-/axis-0.0.140.tgz#aad557c281c6cc21c1516977301552c7052e5224"
@@ -1035,14 +1109,14 @@ ansi-html@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
 
-ansi-regex@*, ansi-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-
 ansi-regex@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
 
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1297,6 +1371,12 @@ async@1.x, async@^1.5.0, async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
+async@2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.4.0.tgz#4990200f18ea5b837c2cc4f8c031a6985c385611"
+  dependencies:
+    lodash "^4.14.0"
+
 async@^2.1.4, async@^2.5.0, async@^2.6.0:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
@@ -2258,6 +2338,10 @@ bluebird@1.0.3:
   version "1.0.3"
   resolved "http://registry.npmjs.org/bluebird/-/bluebird-1.0.3.tgz#c4b441184802e3b64a61eeed4578271b4c8bf6ac"
 
+bluebird@3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
+
 bluebird@^3.4.3, bluebird@^3.5.1:
   version "3.5.2"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a"
@@ -2484,6 +2568,10 @@ buffer-alloc@^1.2.0:
     buffer-alloc-unsafe "^1.1.0"
     buffer-fill "^1.0.0"
 
+buffer-crc32@~0.2.3:
+  version "0.2.13"
+  resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+
 buffer-equal@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b"
@@ -2580,6 +2668,12 @@ cacheable-request@^2.1.1:
     normalize-url "2.0.1"
     responselike "1.0.2"
 
+cachedir@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4"
+  dependencies:
+    os-homedir "^1.0.1"
+
 call-me-maybe@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -2658,6 +2752,14 @@ chain-function@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
 
+chalk@2.4.1, chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
 chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -2668,14 +2770,6 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
 chalk@~0.4.0:
   version "0.4.0"
   resolved "http://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
@@ -2716,6 +2810,10 @@ check-error@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
 
+check-more-types@2.24.0:
+  version "2.24.0"
+  resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
+
 cheerio@^0.22.0:
   version "0.22.0"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
@@ -2781,7 +2879,7 @@ chrome-trace-event@^1.0.0:
   dependencies:
     tslib "^1.9.0"
 
-ci-info@^1.3.0:
+ci-info@^1.0.0, ci-info@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.4.0.tgz#4841d53cad49f11b827b648ebde27a6e189b412f"
 
@@ -2841,6 +2939,10 @@ cli-cursor@^2.1.0:
   dependencies:
     restore-cursor "^2.0.0"
 
+cli-spinners@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
+
 cli-table@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
@@ -3033,6 +3135,10 @@ comma-separated-tokens@^1.0.0:
   dependencies:
     trim "0.0.1"
 
+commander@2.11.0:
+  version "2.11.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
+
 commander@2.9.0:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
@@ -3047,6 +3153,12 @@ commander@~2.13.0:
   version "2.13.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
 
+common-tags@1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
+  dependencies:
+    babel-runtime "^6.18.0"
+
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -3085,6 +3197,14 @@ concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
+concat-stream@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+  dependencies:
+    inherits "^2.0.3"
+    readable-stream "^2.2.2"
+    typedarray "^0.0.6"
+
 concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0, concat-stream@~1.6.0:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
@@ -3563,6 +3683,51 @@ cyclist@~0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
 
+cypress@^3.0.3:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.0.tgz#b718ba64289b887c7ab7a7f09245d871a4a409ba"
+  dependencies:
+    "@cypress/listr-verbose-renderer" "0.4.1"
+    "@cypress/xvfb" "1.2.3"
+    "@types/blob-util" "1.3.3"
+    "@types/bluebird" "3.5.18"
+    "@types/chai" "4.0.8"
+    "@types/chai-jquery" "1.1.35"
+    "@types/jquery" "3.2.16"
+    "@types/lodash" "4.14.87"
+    "@types/minimatch" "3.0.3"
+    "@types/mocha" "2.2.44"
+    "@types/sinon" "4.0.0"
+    "@types/sinon-chai" "2.7.29"
+    bluebird "3.5.0"
+    cachedir "1.3.0"
+    chalk "2.4.1"
+    check-more-types "2.24.0"
+    commander "2.11.0"
+    common-tags "1.4.0"
+    debug "3.1.0"
+    execa "0.10.0"
+    executable "4.1.1"
+    extract-zip "1.6.6"
+    fs-extra "4.0.1"
+    getos "3.1.0"
+    glob "7.1.2"
+    is-ci "1.0.10"
+    is-installed-globally "0.1.0"
+    lazy-ass "1.6.0"
+    listr "0.12.0"
+    lodash "4.17.10"
+    log-symbols "2.2.0"
+    minimist "1.2.0"
+    progress "1.1.8"
+    ramda "0.24.1"
+    request "2.87.0"
+    request-progress "0.3.1"
+    supports-color "5.1.0"
+    tmp "0.0.31"
+    url "0.11.0"
+    yauzl "2.8.0"
+
 d3-array@1, d3-array@^1.2.0, d3-array@^1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
@@ -3792,13 +3957,13 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.
   dependencies:
     ms "2.0.0"
 
-debug@^3.1.0:
+debug@3.1.0, debug@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   dependencies:
     ms "2.0.0"
 
-debuglog@*, debuglog@^1.0.1:
+debuglog@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
 
@@ -4576,7 +4741,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
 
-execa@^0.10.0:
+execa@0.10.0, execa@^0.10.0:
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
   dependencies:
@@ -4600,6 +4765,12 @@ execa@^0.7.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
+executable@4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
+  dependencies:
+    pify "^2.2.0"
+
 exenv@^1.2.0:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
@@ -4744,6 +4915,15 @@ extglob@^2.0.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+extract-zip@1.6.6:
+  version "1.6.6"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c"
+  dependencies:
+    concat-stream "1.6.0"
+    debug "2.6.9"
+    mkdirp "0.5.0"
+    yauzl "2.4.1"
+
 extsprintf@1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -4832,6 +5012,12 @@ fbjs@^0.8.1, fbjs@^0.8.4, fbjs@^0.8.9:
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.18"
 
+fd-slicer@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
+  dependencies:
+    pend "~1.2.0"
+
 figures@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -5061,6 +5247,14 @@ from2@^2.1.0, from2@^2.1.1:
     inherits "^2.0.1"
     readable-stream "^2.0.0"
 
+fs-extra@4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880"
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^3.0.0"
+    universalify "^0.1.0"
+
 fs-minipass@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
@@ -5253,6 +5447,12 @@ get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
 
+getos@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.0.tgz#db3aa4df15a3295557ce5e81aa9e3e5cdfaa6567"
+  dependencies:
+    async "2.4.0"
+
 getpass@^0.1.1:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -5400,9 +5600,9 @@ glob@7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.0:
-  version "7.1.3"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
+glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.0:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -5658,6 +5858,10 @@ has-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
 
+has-flag@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
 has-flag@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -6031,10 +6235,16 @@ imports-loader@^0.7.1:
     loader-utils "^1.0.2"
     source-map "^0.5.6"
 
-imurmurhash@*, imurmurhash@^0.1.4:
+imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
 
+indent-string@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+  dependencies:
+    repeating "^2.0.0"
+
 indent-string@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
@@ -6244,6 +6454,12 @@ is-callable@^1.1.1, is-callable@^1.1.3:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
 
+is-ci@1.0.10:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
+  dependencies:
+    ci-info "^1.0.0"
+
 is-ci@^1.0.10:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.0.tgz#3f4a08d6303a09882cef3f0fb97439c5f5ce2d53"
@@ -6371,7 +6587,7 @@ is-hexadecimal@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
 
-is-installed-globally@^0.1.0:
+is-installed-globally@0.1.0, is-installed-globally@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
   dependencies:
@@ -6919,6 +7135,12 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
+jsonfile@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -7005,6 +7227,10 @@ latest-version@^3.0.0:
   dependencies:
     package-json "^4.0.0"
 
+lazy-ass@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
+
 lcid@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -7043,6 +7269,19 @@ listr-silent-renderer@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
 
+listr-update-renderer@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9"
+  dependencies:
+    chalk "^1.1.3"
+    cli-truncate "^0.2.1"
+    elegant-spinner "^1.0.1"
+    figures "^1.7.0"
+    indent-string "^3.0.0"
+    log-symbols "^1.0.2"
+    log-update "^1.0.2"
+    strip-ansi "^3.0.1"
+
 listr-update-renderer@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7"
@@ -7065,6 +7304,27 @@ listr-verbose-renderer@^0.4.0:
     date-fns "^1.27.2"
     figures "^1.7.0"
 
+listr@0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a"
+  dependencies:
+    chalk "^1.1.3"
+    cli-truncate "^0.2.1"
+    figures "^1.7.0"
+    indent-string "^2.1.0"
+    is-promise "^2.1.0"
+    is-stream "^1.1.0"
+    listr-silent-renderer "^1.1.1"
+    listr-update-renderer "^0.2.0"
+    listr-verbose-renderer "^0.4.0"
+    log-symbols "^1.0.2"
+    log-update "^1.0.2"
+    ora "^0.2.3"
+    p-map "^1.1.1"
+    rxjs "^5.0.0-beta.11"
+    stream-to-observable "^0.1.0"
+    strip-ansi "^3.0.1"
+
 listr@^0.14.1:
   version "0.14.2"
   resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.2.tgz#cbe44b021100a15376addfc2d79349ee430bfe14"
@@ -7170,10 +7430,6 @@ lodash._basecreate@^3.0.0:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
 
-lodash._baseindexof@*:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
-
 lodash._baseuniq@~4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@@ -7181,25 +7437,11 @@ lodash._baseuniq@~4.6.0:
     lodash._createset "~4.0.0"
     lodash._root "~3.0.0"
 
-lodash._bindcallback@*:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
-
-lodash._cacheindexof@*:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
-
-lodash._createcache@*:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
-  dependencies:
-    lodash._getnative "^3.0.0"
-
 lodash._createset@~4.0.0:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
 
-lodash._getnative@*, lodash._getnative@^3.0.0:
+lodash._getnative@^3.0.0:
   version "3.9.1"
   resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
 
@@ -7299,6 +7541,10 @@ lodash.merge@^4.4.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
 
+lodash.once@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+
 lodash.pick@^4.2.1:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@@ -7311,10 +7557,6 @@ lodash.reject@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415"
 
-lodash.restparam@*:
-  version "3.6.1"
-  resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
-
 lodash.some@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
@@ -7343,22 +7585,22 @@ lodash@3.x:
   version "3.10.1"
   resolved "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
 
-lodash@^4.0.1, lodash@^4.0.8, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
+lodash@4.17.10, lodash@^4.0.1, lodash@^4.0.8, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
 
+log-symbols@2.2.0, log-symbols@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
+  dependencies:
+    chalk "^2.0.1"
+
 log-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
   dependencies:
     chalk "^1.0.0"
 
-log-symbols@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  dependencies:
-    chalk "^2.0.1"
-
 log-update@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
@@ -7823,6 +8065,12 @@ mjolnir.js@^1.0.0, mjolnir.js@^1.2.1:
   dependencies:
     hammerjs "^2.0.8"
 
+mkdirp@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
+  dependencies:
+    minimist "0.0.8"
+
 mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -8537,6 +8785,15 @@ optionator@^0.8.1, optionator@^0.8.2:
     type-check "~0.3.2"
     wordwrap "~1.0.0"
 
+ora@^0.2.3:
+  version "0.2.3"
+  resolved "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
+  dependencies:
+    chalk "^1.1.1"
+    cli-cursor "^1.0.2"
+    cli-spinners "^0.1.2"
+    object-assign "^4.0.1"
+
 original@>=0.0.5:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -8547,7 +8804,7 @@ os-browserify@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
 
-os-homedir@^1.0.0:
+os-homedir@^1.0.0, os-homedir@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
 
@@ -8559,7 +8816,7 @@ os-locale@^2.0.0:
     lcid "^1.0.0"
     mem "^1.1.0"
 
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
 
@@ -8851,6 +9108,10 @@ pbkdf2@^3.0.3:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+pend@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+
 performance-now@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
@@ -8863,7 +9124,7 @@ phin@^2.9.1:
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.1.tgz#0de9059b1a9bd56fcb1bd8a374344a06f25f1901"
 
-pify@^2.0.0, pify@^2.3.0:
+pify@^2.0.0, pify@^2.2.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
 
@@ -9468,6 +9729,10 @@ process@~0.5.1:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
 
+progress@1.1.8:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
 progress@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
@@ -9648,6 +9913,10 @@ raf@^3.3.0:
   dependencies:
     performance-now "^2.1.0"
 
+ramda@0.24.1:
+  version "0.24.1"
+  resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
+
 randomatic@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116"
@@ -10218,7 +10487,7 @@ readable-stream@~2.1.5:
     string_decoder "~0.10.x"
     util-deprecate "~1.0.1"
 
-readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0:
+readdir-scoped-modules@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
   dependencies:
@@ -10461,30 +10730,36 @@ replace-ext@1.0.0, replace-ext@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
 
-request@2, request@^2.65.0, request@^2.74.0, request@^2.79.0, request@^2.85.0:
-  version "2.88.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
+request-progress@0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.3.1.tgz#0721c105d8a96ac6b2ce8b2c89ae2d5ecfcf6b3a"
+  dependencies:
+    throttleit "~0.0.2"
+
+request@2, request@2.87.0, request@^2.74.0, request@^2.79.0:
+  version "2.87.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
   dependencies:
     aws-sign2 "~0.7.0"
-    aws4 "^1.8.0"
+    aws4 "^1.6.0"
     caseless "~0.12.0"
-    combined-stream "~1.0.6"
-    extend "~3.0.2"
+    combined-stream "~1.0.5"
+    extend "~3.0.1"
     forever-agent "~0.6.1"
-    form-data "~2.3.2"
-    har-validator "~5.1.0"
+    form-data "~2.3.1"
+    har-validator "~5.0.3"
     http-signature "~1.2.0"
     is-typedarray "~1.0.0"
     isstream "~0.1.2"
     json-stringify-safe "~5.0.1"
-    mime-types "~2.1.19"
-    oauth-sign "~0.9.0"
+    mime-types "~2.1.17"
+    oauth-sign "~0.8.2"
     performance-now "^2.1.0"
-    qs "~6.5.2"
-    safe-buffer "^5.1.2"
-    tough-cookie "~2.4.3"
+    qs "~6.5.1"
+    safe-buffer "^5.1.1"
+    tough-cookie "~2.3.3"
     tunnel-agent "^0.6.0"
-    uuid "^3.3.2"
+    uuid "^3.1.0"
 
 request@2.81.0:
   version "2.81.0"
@@ -10513,6 +10788,31 @@ request@2.81.0:
     tunnel-agent "^0.6.0"
     uuid "^3.0.0"
 
+request@^2.65.0, request@^2.85.0:
+  version "2.88.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    har-validator "~5.1.0"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.4.3"
+    tunnel-agent "^0.6.0"
+    uuid "^3.3.2"
+
 request@~2.22.0:
   version "2.22.0"
   resolved "http://registry.npmjs.org/request/-/request-2.22.0.tgz#b883a769cc4a909571eb5004b344c43cf7e51592"
@@ -10717,9 +11017,9 @@ rx-lite@*, rx-lite@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
 
-rxjs@^5.5.2:
-  version "5.5.12"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
+rxjs@^5.0.0-beta.11, rxjs@^5.5.2:
+  version "5.5.11"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87"
   dependencies:
     symbol-observable "1.0.1"
 
@@ -11342,6 +11642,10 @@ stream-to-buffer@^0.1.0:
   dependencies:
     stream-to "~0.2.0"
 
+stream-to-observable@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
+
 stream-to@~0.2.0:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/stream-to/-/stream-to-0.2.2.tgz#84306098d85fdb990b9fa300b1b3ccf55e8ef01d"
@@ -11475,6 +11779,12 @@ supports-color@3.1.2, supports-color@3.1.x:
   dependencies:
     has-flag "^1.0.0"
 
+supports-color@5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5"
+  dependencies:
+    has-flag "^2.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -11600,6 +11910,10 @@ textextensions@2:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286"
 
+throttleit@~0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
+
 through2@^2.0.0, through2@^2.0.3, through2@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -11648,6 +11962,12 @@ tinyqueue@^1.1.0:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-1.2.3.tgz#b6a61de23060584da29f82362e45df1ec7353f3d"
 
+tmp@0.0.31:
+  version "0.0.31"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
+  dependencies:
+    os-tmpdir "~1.0.1"
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -11941,6 +12261,10 @@ unist-util-visit@^1.1.0, unist-util-visit@^1.3.0:
   dependencies:
     unist-util-visit-parents "^2.0.0"
 
+universalify@^0.1.0:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -12038,7 +12362,7 @@ url-to-options@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
 
-url@^0.11.0:
+url@0.11.0, url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
   dependencies:
@@ -12098,7 +12422,7 @@ v8flags@^2.1.1:
   dependencies:
     user-home "^1.1.1"
 
-validate-npm-package-license@*, validate-npm-package-license@^3.0.1:
+validate-npm-package-license@^3.0.1:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
   dependencies:
@@ -12628,6 +12952,19 @@ yargs@~1.2.6:
   dependencies:
     minimist "^0.1.0"
 
+yauzl@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
+  dependencies:
+    fd-slicer "~1.0.1"
+
+yauzl@2.8.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.8.0.tgz#79450aff22b2a9c5a41ef54e02db907ccfbf9ee2"
+  dependencies:
+    buffer-crc32 "~0.2.3"
+    fd-slicer "~1.0.1"
+
 yeoman-environment@^2.0.5, yeoman-environment@^2.1.1:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.3.3.tgz#1bd9720714cc49036e901503a789d809df8f51bf"
diff --git a/superset/cli.py b/superset/cli.py
index 074e12f..68e3de7 100755
--- a/superset/cli.py
+++ b/superset/cli.py
@@ -20,6 +20,7 @@ import yaml
 from superset import (
     app, data, db, dict_import_export_util, security_manager, utils,
 )
+from tests.utils import get_main_database
 
 config = app.config
 celery_app = utils.get_celery_app(config)
@@ -365,3 +366,69 @@ def flower(port, address):
     print(Fore.YELLOW + cmd)
     print(Fore.BLUE + '-=' * 40)
     Popen(cmd, shell=True).wait()
+
+
+@app.cli.command()
+def load_test_users():
+    """
+    Loads admin, alpha, and gamma user for testing purposes
+
+    Syncs permissions for those users/roles
+    """
+    load_test_users_run()
+
+
+def load_test_users_run():
+    """
+    Loads admin, alpha, and gamma user for testing purposes
+
+    Syncs permissions for those users/roles
+    """
+    if config.get('TESTING'):
+        security_manager.sync_role_definitions()
+        gamma_sqllab_role = security_manager.add_role('gamma_sqllab')
+        for perm in security_manager.find_role('Gamma').permissions:
+            security_manager.add_permission_role(gamma_sqllab_role, perm)
+        utils.get_or_create_main_db()
+        db_perm = get_main_database(security_manager.get_session).perm
+        security_manager.merge_perm('database_access', db_perm)
+        db_pvm = security_manager.find_permission_view_menu(
+            view_menu_name=db_perm, permission_name='database_access')
+        gamma_sqllab_role.permissions.append(db_pvm)
+        for perm in security_manager.find_role('sql_lab').permissions:
+            security_manager.add_permission_role(gamma_sqllab_role, perm)
+
+        admin = security_manager.find_user('admin')
+        if not admin:
+            security_manager.add_user(
+                'admin', 'admin', ' user', 'admin@fab.org',
+                security_manager.find_role('Admin'),
+                password='general')
+
+        gamma = security_manager.find_user('gamma')
+        if not gamma:
+            security_manager.add_user(
+                'gamma', 'gamma', 'user', 'gamma@fab.org',
+                security_manager.find_role('Gamma'),
+                password='general')
+
+        gamma2 = security_manager.find_user('gamma2')
+        if not gamma2:
+            security_manager.add_user(
+                'gamma2', 'gamma2', 'user', 'gamma2@fab.org',
+                security_manager.find_role('Gamma'),
+                password='general')
+
+        gamma_sqllab_user = security_manager.find_user('gamma_sqllab')
+        if not gamma_sqllab_user:
+            security_manager.add_user(
+                'gamma_sqllab', 'gamma_sqllab', 'user', 'gamma_sqllab@fab.org',
+                gamma_sqllab_role, password='general')
+
+        alpha = security_manager.find_user('alpha')
+        if not alpha:
+            security_manager.add_user(
+                'alpha', 'alpha', 'user', 'alpha@fab.org',
+                security_manager.find_role('Alpha'),
+                password='general')
+        security_manager.get_session.commit()
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index 0b232f7..56af4a6 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -955,6 +955,14 @@ def load_birth_names():
                     'aggregate': 'SUM',
                     'label': 'SUM(num_california)',
                 })),
+        Slice(
+            slice_name="Num Births Trend",
+            viz_type='line',
+            datasource_type='table',
+            datasource_id=tbl.id,
+            params=get_slice_json(
+                defaults,
+                viz_type="line")),
     ]
     for slc in slices:
         merge_slice(slc)
diff --git a/tests/base_tests.py b/tests/base_tests.py
index 782cedd..f89aaf7 100644
--- a/tests/base_tests.py
+++ b/tests/base_tests.py
@@ -14,10 +14,11 @@ from flask_appbuilder.security.sqla import models as ab_models
 from mock import Mock
 import pandas as pd
 
-from superset import app, cli, db, security_manager, utils
+from superset import app, cli, db, security_manager
 from superset.connectors.druid.models import DruidCluster, DruidDatasource
 from superset.connectors.sqla.models import SqlaTable
 from superset.models import core as models
+from .utils import get_main_database
 
 
 BASE_DIR = app.config.get('BASE_DIR')
@@ -43,52 +44,7 @@ class SupersetTestCase(unittest.TestCase):
         self.client = app.test_client()
         self.maxDiff = None
 
-        gamma_sqllab_role = security_manager.add_role('gamma_sqllab')
-        for perm in security_manager.find_role('Gamma').permissions:
-            security_manager.add_permission_role(gamma_sqllab_role, perm)
-        utils.get_or_create_main_db()
-        db_perm = self.get_main_database(security_manager.get_session).perm
-        security_manager.merge_perm('database_access', db_perm)
-        db_pvm = security_manager.find_permission_view_menu(
-            view_menu_name=db_perm, permission_name='database_access')
-        gamma_sqllab_role.permissions.append(db_pvm)
-        for perm in security_manager.find_role('sql_lab').permissions:
-            security_manager.add_permission_role(gamma_sqllab_role, perm)
-
-        admin = security_manager.find_user('admin')
-        if not admin:
-            security_manager.add_user(
-                'admin', 'admin', ' user', 'admin@fab.org',
-                security_manager.find_role('Admin'),
-                password='general')
-
-        gamma = security_manager.find_user('gamma')
-        if not gamma:
-            security_manager.add_user(
-                'gamma', 'gamma', 'user', 'gamma@fab.org',
-                security_manager.find_role('Gamma'),
-                password='general')
-
-        gamma2 = security_manager.find_user('gamma2')
-        if not gamma2:
-            security_manager.add_user(
-                'gamma2', 'gamma2', 'user', 'gamma2@fab.org',
-                security_manager.find_role('Gamma'),
-                password='general')
-
-        gamma_sqllab_user = security_manager.find_user('gamma_sqllab')
-        if not gamma_sqllab_user:
-            security_manager.add_user(
-                'gamma_sqllab', 'gamma_sqllab', 'user', 'gamma_sqllab@fab.org',
-                gamma_sqllab_role, password='general')
-
-        alpha = security_manager.find_user('alpha')
-        if not alpha:
-            security_manager.add_user(
-                'alpha', 'alpha', 'user', 'alpha@fab.org',
-                security_manager.find_role('Alpha'),
-                password='general')
-        security_manager.get_session.commit()
+        cli.load_test_users_run()
         # create druid cluster and druid datasources
         session = db.session
         cluster = (
@@ -185,13 +141,6 @@ class SupersetTestCase(unittest.TestCase):
         resp = self.get_resp(url, data, follow_redirects, raise_on_error)
         return json.loads(resp)
 
-    def get_main_database(self, session):
-        return (
-            db.session.query(models.Database)
-            .filter_by(database_name='main')
-            .first()
-        )
-
     def get_access_requests(self, username, ds_type, ds_id):
         DAR = models.DatasourceAccessRequest
         return (
@@ -227,7 +176,7 @@ class SupersetTestCase(unittest.TestCase):
         if user_name:
             self.logout()
             self.login(username=(user_name if user_name else 'admin'))
-        dbid = self.get_main_database(db.session).id
+        dbid = get_main_database(db.session).id
         resp = self.get_json_resp(
             '/superset/sql_json/',
             raise_on_error=False,
diff --git a/tests/celery_tests.py b/tests/celery_tests.py
index 243aace..7542abe 100644
--- a/tests/celery_tests.py
+++ b/tests/celery_tests.py
@@ -19,6 +19,7 @@ from superset.models.helpers import QueryStatus
 from superset.models.sql_lab import Query
 from superset.sql_parse import SupersetQuery
 from .base_tests import SupersetTestCase
+from .utils import get_main_database
 
 
 BASE_DIR = app.config.get('BASE_DIR')
@@ -140,14 +141,14 @@ class CeleryTestCase(SupersetTestCase):
         return json.loads(resp.data.decode('utf-8'))
 
     def test_run_sync_query_dont_exist(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         db_id = main_db.id
         sql_dont_exist = 'SELECT name FROM table_dont_exist'
         result1 = self.run_sql(db_id, sql_dont_exist, '1', cta='true')
         self.assertTrue('error' in result1)
 
     def test_run_sync_query_cta(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         db_id = main_db.id
         eng = main_db.get_sqla_engine()
         perm_name = 'can_sql_json'
@@ -166,7 +167,7 @@ class CeleryTestCase(SupersetTestCase):
         self.assertEqual([{'name': perm_name}], data2)
 
     def test_run_sync_query_cta_no_data(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         db_id = main_db.id
         sql_empty_result = 'SELECT * FROM ab_user WHERE id=666'
         result3 = self.run_sql(
@@ -179,7 +180,7 @@ class CeleryTestCase(SupersetTestCase):
         self.assertEqual(QueryStatus.SUCCESS, query3.status)
 
     def test_run_async_query(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         eng = main_db.get_sqla_engine()
         sql_where = "SELECT name FROM ab_role WHERE name='Admin'"
         result = self.run_sql(
@@ -207,7 +208,7 @@ class CeleryTestCase(SupersetTestCase):
         self.assertEqual(True, query.select_as_cta_used)
 
     def test_run_async_query_with_lower_limit(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         eng = main_db.get_sqla_engine()
         sql_where = "SELECT name FROM ab_role WHERE name='Alpha' LIMIT 1"
         result = self.run_sql(
diff --git a/tests/core_tests.py b/tests/core_tests.py
index f03c51f..d4aeece 100644
--- a/tests/core_tests.py
+++ b/tests/core_tests.py
@@ -29,6 +29,7 @@ from superset.models import core as models
 from superset.models.sql_lab import Query
 from superset.views.core import DatabaseView
 from .base_tests import SupersetTestCase
+from .utils import get_main_database
 
 
 class CoreTests(SupersetTestCase):
@@ -312,7 +313,7 @@ class CoreTests(SupersetTestCase):
 
     def test_testconn(self, username='admin'):
         self.login(username=username)
-        database = self.get_main_database(db.session)
+        database = get_main_database(db.session)
 
         # validate that the endpoint works with the password-masked sqlalchemy uri
         data = json.dumps({
@@ -341,7 +342,7 @@ class CoreTests(SupersetTestCase):
         assert response.headers['Content-Type'] == 'application/json'
 
     def test_custom_password_store(self):
-        database = self.get_main_database(db.session)
+        database = get_main_database(db.session)
         conn_pre = sqla.engine.url.make_url(database.sqlalchemy_uri_decrypted)
 
         def custom_password_store(uri):
@@ -359,13 +360,13 @@ class CoreTests(SupersetTestCase):
         # validate that sending a password-masked uri does not over-write the decrypted
         # uri
         self.login(username=username)
-        database = self.get_main_database(db.session)
+        database = get_main_database(db.session)
         sqlalchemy_uri_decrypted = database.sqlalchemy_uri_decrypted
         url = 'databaseview/edit/{}'.format(database.id)
         data = {k: database.__getattribute__(k) for k in DatabaseView.add_columns}
         data['sqlalchemy_uri'] = database.safe_sqlalchemy_uri()
         self.client.post(url, data=data)
-        database = self.get_main_database(db.session)
+        database = get_main_database(db.session)
         self.assertEqual(sqlalchemy_uri_decrypted, database.sqlalchemy_uri_decrypted)
 
     def test_warm_up_cache(self):
@@ -452,27 +453,27 @@ class CoreTests(SupersetTestCase):
 
     def test_extra_table_metadata(self):
         self.login('admin')
-        dbid = self.get_main_database(db.session).id
+        dbid = get_main_database(db.session).id
         self.get_json_resp(
             '/superset/extra_table_metadata/{dbid}/'
             'ab_permission_view/panoramix/'.format(**locals()))
 
     def test_process_template(self):
-        maindb = self.get_main_database(db.session)
+        maindb = get_main_database(db.session)
         sql = "SELECT '{{ datetime(2017, 1, 1).isoformat() }}'"
         tp = jinja_context.get_template_processor(database=maindb)
         rendered = tp.process_template(sql)
         self.assertEqual("SELECT '2017-01-01T00:00:00'", rendered)
 
     def test_get_template_kwarg(self):
-        maindb = self.get_main_database(db.session)
+        maindb = get_main_database(db.session)
         s = '{{ foo }}'
         tp = jinja_context.get_template_processor(database=maindb, foo='bar')
         rendered = tp.process_template(s)
         self.assertEqual('bar', rendered)
 
     def test_template_kwarg(self):
-        maindb = self.get_main_database(db.session)
+        maindb = get_main_database(db.session)
         s = '{{ foo }}'
         tp = jinja_context.get_template_processor(database=maindb)
         rendered = tp.process_template(s, foo='bar')
@@ -485,7 +486,7 @@ class CoreTests(SupersetTestCase):
         self.assertEqual(data['data'][0]['test'], '2017-01-01T00:00:00')
 
     def test_table_metadata(self):
-        maindb = self.get_main_database(db.session)
+        maindb = get_main_database(db.session)
         backend = maindb.backend
         data = self.get_json_resp(
             '/superset/table/{}/ab_user/null/'.format(maindb.id))
diff --git a/tests/dict_import_export_tests.py b/tests/dict_import_export_tests.py
index cbe8aa2..21a46ee 100644
--- a/tests/dict_import_export_tests.py
+++ b/tests/dict_import_export_tests.py
@@ -16,6 +16,7 @@ from superset.connectors.druid.models import (
 )
 from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn
 from .base_tests import SupersetTestCase
+from .utils import get_main_database
 
 DBREF = 'dict_import__export_test'
 NAME_PREFIX = 'dict_'
@@ -55,7 +56,7 @@ class DictImportExportTests(SupersetTestCase):
         params = {DBREF: id, 'database_name': database_name}
 
         dict_rep = {
-            'database_id': self.get_main_database(db.session).id,
+            'database_id': get_main_database(db.session).id,
             'table_name': name,
             'schema': schema,
             'id': id,
diff --git a/tests/form_tests.py b/tests/form_tests.py
index 82178a2..93bef66 100644
--- a/tests/form_tests.py
+++ b/tests/form_tests.py
@@ -4,11 +4,11 @@ from __future__ import division
 from __future__ import print_function
 from __future__ import unicode_literals
 
-from tests.base_tests import SupersetTestCase
 from wtforms.form import Form
 
 from superset.forms import (
     CommaSeparatedListField, filter_not_empty_values)
+from tests.base_tests import SupersetTestCase
 
 
 class FormTestCase(SupersetTestCase):
diff --git a/tests/model_tests.py b/tests/model_tests.py
index 74dc822..565791a 100644
--- a/tests/model_tests.py
+++ b/tests/model_tests.py
@@ -11,6 +11,7 @@ from sqlalchemy.engine.url import make_url
 from superset import app, db
 from superset.models.core import Database
 from .base_tests import SupersetTestCase
+from .utils import get_main_database
 
 
 class DatabaseModelTestCase(SupersetTestCase):
@@ -77,7 +78,7 @@ class DatabaseModelTestCase(SupersetTestCase):
         self.assertNotEquals(example_user, user_name)
 
     def test_select_star(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         table_name = 'bart_lines'
         sql = main_db.select_star(
             table_name, show_cols=False, latest_partition=False)
@@ -107,7 +108,7 @@ class DatabaseModelTestCase(SupersetTestCase):
         self.assertEquals(d.get('Time Column').function, '{col}')
 
     def test_single_statement(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
 
         if main_db.backend == 'mysql':
             df = main_db.get_df('SELECT 1', None)
@@ -117,7 +118,7 @@ class DatabaseModelTestCase(SupersetTestCase):
             self.assertEquals(df.iat[0, 0], 1)
 
     def test_multi_statement(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
 
         if main_db.backend == 'mysql':
             df = main_db.get_df('USE superset; SELECT 1', None)
diff --git a/tests/sqllab_tests.py b/tests/sqllab_tests.py
index c3fd404..1159c4e 100644
--- a/tests/sqllab_tests.py
+++ b/tests/sqllab_tests.py
@@ -16,6 +16,7 @@ from superset.dataframe import SupersetDataFrame
 from superset.db_engine_specs import BaseEngineSpec
 from superset.models.sql_lab import Query
 from .base_tests import SupersetTestCase
+from .utils import get_main_database
 
 
 class SqlLabTests(SupersetTestCase):
@@ -62,7 +63,7 @@ class SqlLabTests(SupersetTestCase):
         self.assertLess(0, len(data['data']))
 
     def test_sql_json_has_access(self):
-        main_db = self.get_main_database(db.session)
+        main_db = get_main_database(db.session)
         security_manager.add_permission_view_menu('database_access', main_db.perm)
         db.session.commit()
         main_db_permission_view = (
diff --git a/tests/utils.py b/tests/utils.py
index d1a5adb..a0ab456 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -7,9 +7,20 @@ from __future__ import unicode_literals
 import json
 from os import path
 
+from superset import db
+from superset.models import core as models
+
 FIXTURES_DIR = 'tests/fixtures'
 
 
 def load_fixture(fixture_file_name):
     with open(path.join(FIXTURES_DIR, fixture_file_name)) as fixture_file:
         return json.load(fixture_file)
+
+
+def get_main_database(session):
+    return (
+        db.session.query(models.Database)
+        .filter_by(database_name='main')
+        .first()
+    )
diff --git a/tox.ini b/tox.ini
index b73d276..039075a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,8 @@
 [flake8]
 accept-encodings = utf-8
-application-import-names = superset
+application-import-names =
+    superset
+    tests
 exclude =
     .tox
     build
@@ -43,6 +45,17 @@ setenv =
 whitelist_externals =
     npm
 
+[testenv:cypress]
+commands =
+    {toxinidir}/superset/assets/cypress_build.sh
+setenv =
+    PYTHONPATH = {toxinidir}
+    SUPERSET_CONFIG = tests.superset_test_config
+    SUPERSET_HOME = {envtmpdir}
+deps =
+    -rrequirements.txt
+    -rrequirements-dev.txt
+
 [testenv:eslint]
 changedir = {toxinidir}/superset/assets
 commands =
@@ -70,6 +83,7 @@ deps =
 
 [tox]
 envlist =
+    cypress
     eslint
     flake8
     javascript