You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by yj...@apache.org on 2020/06/25 22:24:46 UTC

[incubator-superset] branch master updated: build: upgrade Cypress and re-enable visualization tests (#10158)

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

yjc 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 ece4f6d  build: upgrade Cypress and re-enable visualization tests (#10158)
ece4f6d is described below

commit ece4f6d0b51d9e4a167c4baefd2785ecfb20f1b5
Author: Jesse Yang <je...@airbnb.com>
AuthorDate: Thu Jun 25 15:24:23 2020 -0700

    build: upgrade Cypress and re-enable visualization tests (#10158)
---
 .../cypress/integration/dashboard/controls.js      | 123 ----
 .../cypress/integration/dashboard/controls.test.js | 122 ++++
 .../cypress/integration/dashboard/edit_mode.js     |  78 ---
 .../integration/dashboard/edit_mode.test.js        |  75 +++
 .../cypress/integration/dashboard/fav_star.js      |  63 --
 .../cypress/integration/dashboard/fav_star.test.js |  62 ++
 .../cypress/integration/dashboard/filter.js        | 104 ---
 .../cypress/integration/dashboard/filter.test.js   | 103 +++
 .../cypress/integration/dashboard/load.js          |  60 --
 .../cypress/integration/dashboard/load.test.js     |  59 ++
 .../cypress/integration/dashboard/save.js          |  82 ---
 .../cypress/integration/dashboard/save.test.js     |  77 +++
 .../cypress/integration/dashboard/tabs.js          | 205 ------
 .../cypress/integration/dashboard/tabs.test.js     | 204 ++++++
 .../cypress/integration/dashboard/url_params.js    |  61 --
 .../integration/dashboard/url_params.test.js       |  60 ++
 .../index.test.js => explore/filter_box.test.js}   |  39 +-
 .../integration/explore/visualizations/area.js     | 115 ----
 .../explore/visualizations/area.test.js            | 114 ++++
 .../explore/visualizations/big_number.js           |  83 ---
 .../explore/visualizations/big_number.test.js      |  82 +++
 .../explore/visualizations/big_number_total.js     |  86 ---
 .../visualizations/big_number_total.test.js        |  85 +++
 .../integration/explore/visualizations/box_plot.js |  75 ---
 .../explore/visualizations/box_plot.test.js        |  68 ++
 .../integration/explore/visualizations/bubble.js   |  92 ---
 .../explore/visualizations/bubble.test.js          |  95 +++
 .../integration/explore/visualizations/compare.js  |  91 ---
 .../explore/visualizations/compare.test.js         |  90 +++
 .../integration/explore/visualizations/dist_bar.js |  84 ---
 .../explore/visualizations/dist_bar.test.js        |  81 +++
 .../explore/visualizations/dual_line.js            |  71 --
 .../explore/visualizations/dual_line.test.js       |  70 ++
 .../explore/visualizations/filter_box.js           |  43 --
 .../explore/visualizations/histogram.js            |  81 ---
 .../explore/visualizations/histogram.test.js       |  88 +++
 .../explore/visualizations/index.test.js           |  57 --
 .../integration/explore/visualizations/line.js     | 258 --------
 .../explore/visualizations/line.test.js            | 257 ++++++++
 .../integration/explore/visualizations/pie.js      |  79 ---
 .../integration/explore/visualizations/pie.test.js |  72 ++
 .../explore/visualizations/pivot_table.js          | 128 ----
 .../explore/visualizations/pivot_table.test.js     | 107 +++
 .../integration/explore/visualizations/sankey.js   |  78 ---
 .../explore/visualizations/sankey.test.js          |  77 +++
 .../integration/explore/visualizations/sunburst.js |  85 ---
 .../explore/visualizations/sunburst.test.js        |  84 +++
 .../integration/explore/visualizations/table.js    | 172 -----
 .../explore/visualizations/table.test.js           | 170 +++++
 .../explore/visualizations/time_table.js           | 203 +++---
 .../integration/explore/visualizations/treemap.js  |  79 ---
 .../explore/visualizations/treemap.test.js         |  84 +++
 .../explore/visualizations/world_map.js            |  85 ---
 .../explore/visualizations/world_map.test.js       |  84 +++
 .../cypress/integration/sqllab/index.test.js       |  27 -
 .../cypress/integration/sqllab/query.js            | 129 ----
 .../cypress/integration/sqllab/query.test.js       | 124 ++++
 .../integration/sqllab/sourcePanel.index.js        |  77 +++
 .../cypress/integration/sqllab/sourcePanel.js      |  79 ---
 .../cypress/integration/sqllab/tabs.js             |  58 --
 .../cypress/integration/sqllab/tabs.test.js        |  56 ++
 superset-frontend/cypress-base/package-lock.json   | 730 ++++++++-------------
 superset-frontend/cypress-base/package.json        |   4 +-
 63 files changed, 3123 insertions(+), 3491 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.js b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.js
deleted file mode 100644
index aa04410..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-import readResponseBlob from '../../utils/readResponseBlob';
-
-export default () =>
-  describe('top-level controls', () => {
-    const sliceRequests = [];
-    const forceRefreshRequests = [];
-    let mapId;
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const dashboard = bootstrapData.dashboard_data;
-        mapId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'world_map',
-        ).slice_id;
-
-        dashboard.slices.forEach(slice => {
-          const sliceRequest = `getJson_${slice.slice_id}`;
-          sliceRequests.push(`@${sliceRequest}`);
-          const formData = `{"slice_id":${slice.slice_id}}`;
-          cy.route('POST', `/superset/explore_json/?form_data=${formData}`).as(
-            sliceRequest,
-          );
-
-          const forceRefresh = `postJson_${slice.slice_id}_force`;
-          forceRefreshRequests.push(`@${forceRefresh}`);
-          cy.route(
-            'POST',
-            `/superset/explore_json/?form_data={"slice_id":${slice.slice_id}}&force=true`,
-          ).as(forceRefresh);
-        });
-      });
-    });
-    afterEach(() => {
-      sliceRequests.length = 0;
-      forceRefreshRequests.length = 0;
-    });
-
-    it.skip('should allow chart level refresh', () => {
-      cy.wait(sliceRequests);
-      cy.get('.grid-container .world_map').should('be.exist');
-      cy.get(`#slice_${mapId}-controls`).click();
-      cy.get(`#slice_${mapId}-controls`)
-        .next()
-        .find('.refresh-tooltip')
-        .trigger('click', { force: true });
-
-      // not allow dashboard level force refresh when any chart is loading
-      cy.get('#save-dash-split-button').trigger('click', { force: true });
-      cy.contains('Force refresh dashboard')
-        .parent()
-        .should('have.class', 'disabled');
-      // not allow chart level force refresh when it is loading
-      cy.get(`#slice_${mapId}-controls`)
-        .next()
-        .find('.refresh-tooltip')
-        .parent()
-        .parent()
-        .should('have.class', 'disabled');
-
-      cy.wait(`@postJson_${mapId}_force`);
-      cy.get('#save-dash-split-button').trigger('click');
-      cy.contains('Force refresh dashboard')
-        .parent()
-        .not('have.class', 'disabled');
-    });
-
-    it.skip('should allow dashboard level force refresh', () => {
-      // when charts are not start loading, for example, under a secondary tab,
-      // should allow force refresh
-      cy.get('#save-dash-split-button').trigger('click');
-      cy.contains('Force refresh dashboard')
-        .parent()
-        .not('have.class', 'disabled');
-
-      // wait the all dash finish loading.
-      cy.wait(sliceRequests);
-      cy.get('#save-dash-split-button').trigger('click');
-      cy.contains('Force refresh dashboard').trigger('click', { force: true });
-      cy.get('#save-dash-split-button').trigger('click');
-      cy.contains('Force refresh dashboard')
-        .parent()
-        .should('have.class', 'disabled');
-
-      // wait all charts force refreshed
-      cy.wait(forceRefreshRequests).then(xhrs => {
-        // is_cached in response should be false
-        xhrs.forEach(xhr => {
-          readResponseBlob(xhr.response.body).then(responseBody => {
-            expect(responseBody.is_cached).to.equal(false);
-          });
-        });
-      });
-
-      cy.get('#save-dash-split-button').trigger('click');
-      cy.contains('Force refresh dashboard')
-        .parent()
-        .not('have.class', 'disabled');
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.js
new file mode 100644
index 0000000..12f1b3a
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.js
@@ -0,0 +1,122 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+import readResponseBlob from '../../utils/readResponseBlob';
+
+describe('Dashboard top-level controls', () => {
+  const sliceRequests = [];
+  const forceRefreshRequests = [];
+  let mapId;
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      mapId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'world_map',
+      ).slice_id;
+
+      dashboard.slices.forEach(slice => {
+        const sliceRequest = `getJson_${slice.slice_id}`;
+        sliceRequests.push(`@${sliceRequest}`);
+        const formData = `{"slice_id":${slice.slice_id}}`;
+        cy.route('POST', `/superset/explore_json/?form_data=${formData}`).as(
+          sliceRequest,
+        );
+
+        const forceRefresh = `postJson_${slice.slice_id}_force`;
+        forceRefreshRequests.push(`@${forceRefresh}`);
+        cy.route(
+          'POST',
+          `/superset/explore_json/?form_data={"slice_id":${slice.slice_id}}&force=true`,
+        ).as(forceRefresh);
+      });
+    });
+  });
+  afterEach(() => {
+    sliceRequests.length = 0;
+    forceRefreshRequests.length = 0;
+  });
+
+  it.skip('should allow chart level refresh', () => {
+    cy.wait(sliceRequests);
+    cy.get('.grid-container .world_map').should('be.exist');
+    cy.get(`#slice_${mapId}-controls`).click();
+    cy.get(`#slice_${mapId}-controls`)
+      .next()
+      .find('.refresh-tooltip')
+      .trigger('click', { force: true });
+
+    // not allow dashboard level force refresh when any chart is loading
+    cy.get('#save-dash-split-button').trigger('click', { force: true });
+    cy.contains('Force refresh dashboard')
+      .parent()
+      .should('have.class', 'disabled');
+    // not allow chart level force refresh when it is loading
+    cy.get(`#slice_${mapId}-controls`)
+      .next()
+      .find('.refresh-tooltip')
+      .parent()
+      .parent()
+      .should('have.class', 'disabled');
+
+    cy.wait(`@postJson_${mapId}_force`);
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard')
+      .parent()
+      .not('have.class', 'disabled');
+  });
+
+  it.skip('should allow dashboard level force refresh', () => {
+    // when charts are not start loading, for example, under a secondary tab,
+    // should allow force refresh
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard')
+      .parent()
+      .not('have.class', 'disabled');
+
+    // wait the all dash finish loading.
+    cy.wait(sliceRequests);
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard').trigger('click', { force: true });
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard')
+      .parent()
+      .should('have.class', 'disabled');
+
+    // wait all charts force refreshed
+    cy.wait(forceRefreshRequests).then(xhrs => {
+      // is_cached in response should be false
+      xhrs.forEach(xhr => {
+        readResponseBlob(xhr.response.body).then(responseBody => {
+          expect(responseBody.is_cached).to.equal(false);
+        });
+      });
+    });
+
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard')
+      .parent()
+      .not('have.class', 'disabled');
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.js b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.js
deleted file mode 100644
index 821c94f..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('edit mode', () => {
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-      cy.get('.dashboard-header').contains('Edit dashboard').click();
-    });
-
-    it('remove, and add chart flow', () => {
-      // wait for box plot to appear
-      cy.get('.grid-container .box_plot');
-
-      cy.get('.fa.fa-trash')
-        .last()
-        .then($el => {
-          cy.wrap($el).invoke('show').click();
-          // box plot should be gone
-          cy.get('.grid-container .box_plot').should('not.exist');
-        });
-
-      // open charts list
-      cy.get('.component-layer').contains('Your charts & filters').click();
-
-      // find box plot is available from list
-      cy.get('.slices-layer')
-        .find('.chart-card-container')
-        .contains('Box plot');
-
-      // drag-n-drop
-      const dataTransfer = { data: {} };
-      cy.get('.dragdroppable')
-        .contains('Box plot')
-        .trigger('mousedown', { which: 1 })
-        .trigger('dragstart', { dataTransfer })
-        .trigger('drag', {});
-      cy.get('.grid-content div.grid-row.background--transparent')
-        .last()
-        .trigger('dragover', { dataTransfer })
-        .trigger('drop', { dataTransfer })
-        .trigger('dragend', { dataTransfer })
-        .trigger('mouseup', { which: 1 });
-
-      // add back to dashboard
-      cy.get('.grid-container .box_plot').should('be.exist');
-
-      // should show Save changes button
-      cy.get('.dashboard-header .button-container').contains('Save changes');
-
-      // undo 2 steps
-      cy.get('.dashboard-header .undo-action').click().click();
-
-      // no changes, can switch to view mode
-      cy.get('.dashboard-header .button-container')
-        .contains('Switch to view mode')
-        .click();
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
new file mode 100644
index 0000000..b725354
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard edit mode', () => {
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+    cy.get('.dashboard-header').contains('Edit dashboard').click();
+  });
+
+  it('remove, and add chart flow', () => {
+    // wait for box plot to appear
+    cy.get('.grid-container .box_plot');
+
+    cy.get('.fa.fa-trash')
+      .last()
+      .then($el => {
+        cy.wrap($el).invoke('show').click();
+        // box plot should be gone
+        cy.get('.grid-container .box_plot').should('not.exist');
+      });
+
+    // open charts list
+    cy.get('.component-layer').contains('Your charts & filters').click();
+
+    // find box plot is available from list
+    cy.get('.slices-layer').find('.chart-card-container').contains('Box plot');
+
+    // drag-n-drop
+    const dataTransfer = { data: {} };
+    cy.get('.dragdroppable')
+      .contains('Box plot')
+      .trigger('mousedown', { which: 1 })
+      .trigger('dragstart', { dataTransfer })
+      .trigger('drag', {});
+    cy.get('.grid-content div.grid-row.background--transparent')
+      .last()
+      .trigger('dragover', { dataTransfer })
+      .trigger('drop', { dataTransfer })
+      .trigger('dragend', { dataTransfer })
+      .trigger('mouseup', { which: 1 });
+
+    // add back to dashboard
+    cy.get('.grid-container .box_plot').should('be.exist');
+
+    // should show Save changes button
+    cy.get('.dashboard-header .button-container').contains('Save changes');
+
+    // undo 2 steps
+    cy.get('.dashboard-header .undo-action').click().click();
+
+    // no changes, can switch to view mode
+    cy.get('.dashboard-header .button-container')
+      .contains('Switch to view mode')
+      .click();
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.js b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.js
deleted file mode 100644
index 00dc19c..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import {
-  WORLD_HEALTH_DASHBOARD,
-  CHECK_DASHBOARD_FAVORITE_ENDPOINT,
-} from './dashboard.helper';
-
-export default () =>
-  describe('favorite dashboard', () => {
-    let isFavoriteDashboard = false;
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-
-      cy.route(CHECK_DASHBOARD_FAVORITE_ENDPOINT).as('countFavStar');
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-
-      cy.wait('@countFavStar').then(xhr => {
-        isFavoriteDashboard = xhr.response.body.count === 1;
-      });
-    });
-
-    it('should allow favor/unfavor', () => {
-      if (!isFavoriteDashboard) {
-        cy.get('.favstar').find('i').should('have.class', 'fa-star-o');
-        cy.get('.favstar').trigger('click');
-        cy.get('.favstar')
-          .find('i')
-          .should('have.class', 'fa-star')
-          .and('not.have.class', 'fa-star-o');
-      } else {
-        cy.get('.favstar')
-          .find('i')
-          .should('have.class', 'fa-star')
-          .and('not.have.class', 'fa-star-o');
-        cy.get('.favstar').trigger('click');
-        cy.get('.fave-unfave-icon')
-          .find('i')
-          .should('have.class', 'fa-star-o')
-          .and('not.have.class', 'fa-star');
-      }
-
-      // reset to original fav state
-      cy.get('.favstar').trigger('click');
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js
new file mode 100644
index 0000000..4b8b11f
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  WORLD_HEALTH_DASHBOARD,
+  CHECK_DASHBOARD_FAVORITE_ENDPOINT,
+} from './dashboard.helper';
+
+describe('Dashboard add to favorite', () => {
+  let isFavoriteDashboard = false;
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.route(CHECK_DASHBOARD_FAVORITE_ENDPOINT).as('countFavStar');
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.wait('@countFavStar').then(xhr => {
+      isFavoriteDashboard = xhr.response.body.count === 1;
+    });
+  });
+
+  it('should allow favor/unfavor', () => {
+    if (!isFavoriteDashboard) {
+      cy.get('.favstar').find('i').should('have.class', 'fa-star-o');
+      cy.get('.favstar').trigger('click');
+      cy.get('.favstar')
+        .find('i')
+        .should('have.class', 'fa-star')
+        .and('not.have.class', 'fa-star-o');
+    } else {
+      cy.get('.favstar')
+        .find('i')
+        .should('have.class', 'fa-star')
+        .and('not.have.class', 'fa-star-o');
+      cy.get('.favstar').trigger('click');
+      cy.get('.fave-unfave-icon')
+        .find('i')
+        .should('have.class', 'fa-star-o')
+        .and('not.have.class', 'fa-star');
+    }
+
+    // reset to original fav state
+    cy.get('.favstar').trigger('click');
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js
deleted file mode 100644
index 11edd86..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('dashboard filter', () => {
-    let filterId;
-    let aliases;
-
-    const getAlias = id => {
-      return `@slice_${id}`;
-    };
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const dashboard = bootstrapData.dashboard_data;
-        const sliceIds = dashboard.slices.map(slice => slice.slice_id);
-        filterId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'filter_box',
-        ).slice_id;
-        aliases = sliceIds.map(id => {
-          const alias = getAlias(id);
-          const url = `/superset/explore_json/?*{"slice_id":${id}}*`;
-          cy.route('POST', url).as(alias.slice(1));
-          return alias;
-        });
-
-        // wait the initial page load requests
-        cy.wait(aliases);
-      });
-    });
-
-    it('should apply filter', () => {
-      cy.get('.Select__control input[type=text]').first().focus();
-
-      // should open the filter indicator
-      cy.get('.filter-indicator.active')
-        .should('be.visible')
-        .should(nodes => {
-          expect(nodes).to.have.length(9);
-        });
-
-      cy.get('.Select__control input[type=text]').first().blur();
-
-      // should hide the filter indicator
-      cy.get('.filter-indicator')
-        .not('.active')
-        .should(nodes => {
-          expect(nodes).to.have.length(18);
-        });
-
-      cy.get('.Select__control input[type=text]')
-        .first()
-        .focus({ force: true })
-        .type('So', { force: true });
-
-      cy.get('.Select__menu').first().contains('Create "So"');
-
-      // Somehow Input loses focus after typing "So" while in Cypress, so
-      // we refocus the input again here. The is not happening in real life.
-      cy.get('.Select__control input[type=text]')
-        .first()
-        .focus({ force: true })
-        .type('uth Asia{enter}', { force: true });
-
-      // by default, need to click Apply button to apply filter
-      cy.get('.filter_box button').click({ force: true });
-
-      // wait again after applied filters
-      cy.wait(aliases.filter(x => x !== getAlias(filterId))).then(requests => {
-        requests.forEach(xhr => {
-          const requestFormData = xhr.request.body;
-          const requestParams = JSON.parse(requestFormData.get('form_data'));
-          expect(requestParams.extra_filters[0]).deep.eq({
-            col: 'region',
-            op: 'in',
-            val: ['South Asia'],
-          });
-        });
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js
new file mode 100644
index 0000000..daa4d76
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard filter', () => {
+  let filterId;
+  let aliases;
+
+  const getAlias = id => {
+    return `@slice_${id}`;
+  };
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      const sliceIds = dashboard.slices.map(slice => slice.slice_id);
+      filterId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'filter_box',
+      ).slice_id;
+      aliases = sliceIds.map(id => {
+        const alias = getAlias(id);
+        const url = `/superset/explore_json/?*{"slice_id":${id}}*`;
+        cy.route('POST', url).as(alias.slice(1));
+        return alias;
+      });
+
+      // wait the initial page load requests
+      cy.wait(aliases);
+    });
+  });
+
+  it('should apply filter', () => {
+    cy.get('.Select__control input[type=text]').first().focus();
+
+    // should open the filter indicator
+    cy.get('.filter-indicator.active')
+      .should('be.visible')
+      .should(nodes => {
+        expect(nodes).to.have.length(9);
+      });
+
+    cy.get('.Select__control input[type=text]').first().blur();
+
+    // should hide the filter indicator
+    cy.get('.filter-indicator')
+      .not('.active')
+      .should(nodes => {
+        expect(nodes).to.have.length(18);
+      });
+
+    cy.get('.Select__control input[type=text]')
+      .first()
+      .focus({ force: true })
+      .type('So', { force: true });
+
+    cy.get('.Select__menu').first().contains('Create "So"');
+
+    // Somehow Input loses focus after typing "So" while in Cypress, so
+    // we refocus the input again here. The is not happening in real life.
+    cy.get('.Select__control input[type=text]')
+      .first()
+      .focus({ force: true })
+      .type('uth Asia{enter}', { force: true });
+
+    // by default, need to click Apply button to apply filter
+    cy.get('.filter_box button').click({ force: true });
+
+    // wait again after applied filters
+    cy.wait(aliases.filter(x => x !== getAlias(filterId))).then(requests => {
+      requests.forEach(xhr => {
+        const requestFormData = xhr.request.body;
+        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        expect(requestParams.extra_filters[0]).deep.eq({
+          col: 'region',
+          op: 'in',
+          val: ['South Asia'],
+        });
+      });
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/load.js b/superset-frontend/cypress-base/cypress/integration/dashboard/load.js
deleted file mode 100644
index 2d48f84..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import readResponseBlob from '../../utils/readResponseBlob';
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('load', () => {
-    const aliases = [];
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const slices = bootstrapData.dashboard_data.slices;
-        // then define routes and create alias for each requests
-        slices.forEach(slice => {
-          const alias = `getJson_${slice.slice_id}`;
-          const formData = `{"slice_id":${slice.slice_id}}`;
-          cy.route('POST', `/superset/explore_json/?*${formData}*`).as(alias);
-          aliases.push(`@${alias}`);
-        });
-      });
-    });
-
-    it('should load dashboard', () => {
-      // wait and verify one-by-one
-      cy.wait(aliases).then(requests => {
-        return Promise.all(
-          requests.map(async xhr => {
-            expect(xhr.status).to.eq(200);
-            const responseBody = await readResponseBlob(xhr.response.body);
-            expect(responseBody).to.have.property('errors');
-            expect(responseBody.errors.length).to.eq(0);
-            const sliceId = responseBody.form_data.slice_id;
-            cy.get(`#chart-id-${sliceId}`).should('be.visible');
-          }),
-        );
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.js
new file mode 100644
index 0000000..3a50734
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.js
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import readResponseBlob from '../../utils/readResponseBlob';
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard load', () => {
+  const aliases = [];
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const slices = bootstrapData.dashboard_data.slices;
+      // then define routes and create alias for each requests
+      slices.forEach(slice => {
+        const alias = `getJson_${slice.slice_id}`;
+        const formData = `{"slice_id":${slice.slice_id}}`;
+        cy.route('POST', `/superset/explore_json/?*${formData}*`).as(alias);
+        aliases.push(`@${alias}`);
+      });
+    });
+  });
+
+  it('should load dashboard', () => {
+    // wait and verify one-by-one
+    cy.wait(aliases).then(requests => {
+      return Promise.all(
+        requests.map(async xhr => {
+          expect(xhr.status).to.eq(200);
+          const responseBody = await readResponseBlob(xhr.response.body);
+          expect(responseBody).to.have.property('errors');
+          expect(responseBody.errors.length).to.eq(0);
+          const sliceId = responseBody.form_data.slice_id;
+          cy.get(`#chart-id-${sliceId}`).should('be.visible');
+        }),
+      );
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.js
deleted file mode 100644
index a4611c7..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import readResponseBlob from '../../utils/readResponseBlob';
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('save', () => {
-    let dashboardId;
-    let boxplotChartId;
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.visit(WORLD_HEALTH_DASHBOARD);
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const dashboard = bootstrapData.dashboard_data;
-        dashboardId = dashboard.id;
-        boxplotChartId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'box_plot',
-        ).slice_id;
-
-        cy.route('POST', `/superset/copy_dash/${dashboardId}/`).as(
-          'copyRequest',
-        );
-      });
-
-      cy.get('#save-dash-split-button').trigger('click', { force: true });
-      cy.contains('Save as').trigger('click', { force: true });
-      cy.get('.modal-footer')
-        .contains('Save')
-        .trigger('click', { force: true });
-    });
-
-    it('should save as new dashboard', () => {
-      cy.wait('@copyRequest').then(xhr => {
-        expect(xhr.status).to.eq(200);
-        readResponseBlob(xhr.response.body).then(json => {
-          expect(json.id).to.be.gt(dashboardId);
-        });
-      });
-    });
-
-    it('should save/overwrite dashboard', () => {
-      // should have box_plot chart
-      cy.get('.grid-container .box_plot', { timeout: 5000 }); // wait for 5 secs
-
-      // remove box_plot chart from dashboard
-      cy.get('.dashboard-header')
-        .contains('Edit dashboard')
-        .trigger('click', { force: true });
-      cy.get('.fa.fa-trash').last().trigger('click', { force: true });
-      cy.get('.grid-container .box_plot').should('not.exist');
-
-      cy.route('POST', '/superset/save_dash/**/').as('saveRequest');
-      cy.get('.dashboard-header')
-        .contains('Save changes')
-        .trigger('click', { force: true });
-
-      // go back to view mode
-      cy.wait('@saveRequest');
-      cy.get('.dashboard-header').contains('Edit dashboard');
-      cy.get('.grid-container .box_plot').should('not.exist');
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
new file mode 100644
index 0000000..8f7069c
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import readResponseBlob from '../../utils/readResponseBlob';
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard save action', () => {
+  let dashboardId;
+  let boxplotChartId;
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      dashboardId = dashboard.id;
+      boxplotChartId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'box_plot',
+      ).slice_id;
+
+      cy.route('POST', `/superset/copy_dash/${dashboardId}/`).as('copyRequest');
+    });
+
+    cy.get('#save-dash-split-button').trigger('click', { force: true });
+    cy.contains('Save as').trigger('click', { force: true });
+    cy.get('.modal-footer').contains('Save').trigger('click', { force: true });
+  });
+
+  it('should save as new dashboard', () => {
+    cy.wait('@copyRequest').then(xhr => {
+      expect(xhr.status).to.eq(200);
+      readResponseBlob(xhr.response.body).then(json => {
+        expect(json.id).to.be.gt(dashboardId);
+      });
+    });
+  });
+
+  it('should save/overwrite dashboard', () => {
+    // should have box_plot chart
+    cy.get('.grid-container .box_plot', { timeout: 5000 }); // wait for 5 secs
+
+    // remove box_plot chart from dashboard
+    cy.get('.dashboard-header')
+      .contains('Edit dashboard')
+      .trigger('click', { force: true });
+    cy.get('.fa.fa-trash').last().trigger('click', { force: true });
+    cy.get('.grid-container .box_plot').should('not.exist');
+
+    cy.route('POST', '/superset/save_dash/**/').as('saveRequest');
+    cy.get('.dashboard-header')
+      .contains('Save changes')
+      .trigger('click', { force: true });
+
+    // go back to view mode
+    cy.wait('@saveRequest');
+    cy.get('.dashboard-header').contains('Edit dashboard');
+    cy.get('.grid-container .box_plot').should('not.exist');
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js
deleted file mode 100644
index 130609f..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { TABBED_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('tabs', () => {
-    let filterId;
-    let treemapId;
-    let linechartId;
-    let boxplotId;
-    let dashboardId;
-
-    // cypress can not handle window.scrollTo
-    // https://github.com/cypress-io/cypress/issues/2761
-    // add this exception handler to pass test
-    const handleException = () => {
-      // return false to prevent the error from
-      // failing this test
-      cy.on('uncaught:exception', () => false);
-    };
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-
-      cy.visit(TABBED_DASHBOARD);
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const dashboard = bootstrapData.dashboard_data;
-        dashboardId = dashboard.id;
-        filterId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'filter_box',
-        ).slice_id;
-        boxplotId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'box_plot',
-        ).slice_id;
-        treemapId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'treemap',
-        ).slice_id;
-        linechartId = dashboard.slices.find(
-          slice => slice.form_data.viz_type === 'line',
-        ).slice_id;
-
-        const filterFormdata = {
-          slice_id: filterId,
-        };
-        const filterRequest = `/superset/explore_json/?form_data=${JSON.stringify(
-          filterFormdata,
-        )}&dashboard_id=${dashboardId}`;
-        cy.route('POST', filterRequest).as('filterRequest');
-
-        const treemapFormdata = {
-          slice_id: treemapId,
-        };
-        const treemapRequest = `/superset/explore_json/?form_data=${JSON.stringify(
-          treemapFormdata,
-        )}&dashboard_id=${dashboardId}`;
-        cy.route('POST', treemapRequest).as('treemapRequest');
-
-        const linechartFormdata = {
-          slice_id: linechartId,
-        };
-        const linechartRequest = `/superset/explore_json/?form_data=${JSON.stringify(
-          linechartFormdata,
-        )}&dashboard_id=${dashboardId}`;
-        cy.route('POST', linechartRequest).as('linechartRequest');
-
-        const boxplotFormdata = {
-          slice_id: boxplotId,
-        };
-        const boxplotRequest = `/superset/explore_json/?form_data=${JSON.stringify(
-          boxplotFormdata,
-        )}&dashboard_id=${dashboardId}`;
-        cy.route('POST', boxplotRequest).as('boxplotRequest');
-      });
-    });
-
-    it('should load charts when tab is visible', () => {
-      // landing in first tab, should see 2 charts
-      cy.wait('@filterRequest');
-      cy.get('.grid-container .filter_box').should('be.exist');
-      cy.wait('@treemapRequest');
-      cy.get('.grid-container .treemap').should('be.exist');
-      cy.get('.grid-container .box_plot').should('not.be.exist');
-      cy.get('.grid-container .line').should('not.be.exist');
-
-      // click row level tab, see 1 more chart
-      cy.get('.tab-content ul.nav.nav-tabs li')
-        .last()
-        .find('.editable-title input')
-        .click();
-      cy.wait('@linechartRequest');
-      cy.get('.grid-container .line').should('be.exist');
-
-      // click top level tab, see 1 more chart
-      handleException();
-      cy.get('.dashboard-component-tabs')
-        .first()
-        .find('ul.nav.nav-tabs li')
-        .last()
-        .find('.editable-title input')
-        .click();
-
-      // should exist a visible box_plot element
-      cy.get('.grid-container .box_plot');
-    });
-
-    it('should send new queries when tab becomes visible', () => {
-      // landing in first tab
-      cy.wait('@filterRequest');
-      cy.wait('@treemapRequest');
-
-      // apply filter
-      cy.get('.Select__control').first().should('be.visible');
-      cy.get('.Select__control').first().click({ force: true });
-      cy.get('.Select__control input[type=text]')
-        .first()
-        .should('be.visible')
-        .type('South Asia{enter}', { force: true });
-
-      // send new query from same tab
-      cy.wait('@treemapRequest').then(xhr => {
-        const requestFormData = xhr.request.body;
-        const requestParams = JSON.parse(requestFormData.get('form_data'));
-        expect(requestParams.extra_filters[0]).deep.eq({
-          col: 'region',
-          op: 'in',
-          val: ['South Asia'],
-        });
-      });
-
-      // click row level tab, send 1 more query
-      cy.get('.tab-content ul.nav.nav-tabs li').last().click();
-      cy.wait('@linechartRequest').then(xhr => {
-        const requestFormData = xhr.request.body;
-        const requestParams = JSON.parse(requestFormData.get('form_data'));
-        expect(requestParams.extra_filters[0]).deep.eq({
-          col: 'region',
-          op: 'in',
-          val: ['South Asia'],
-        });
-      });
-
-      // click top level tab, send 1 more query
-      handleException();
-      cy.get('.dashboard-component-tabs')
-        .first()
-        .find('ul.nav.nav-tabs li')
-        .last()
-        .find('.editable-title input')
-        .click();
-
-      cy.wait('@boxplotRequest').then(xhr => {
-        const requestFormData = xhr.request.body;
-        const requestParams = JSON.parse(requestFormData.get('form_data'));
-        expect(requestParams.extra_filters[0]).deep.eq({
-          col: 'region',
-          op: 'in',
-          val: ['South Asia'],
-        });
-      });
-
-      // navigate to filter and clear filter
-      cy.get('.dashboard-component-tabs')
-        .first()
-        .find('ul.nav.nav-tabs li')
-        .first()
-        .click();
-      cy.get('.tab-content ul.nav.nav-tabs li')
-        .first()
-        .should('be.visible')
-        .click();
-      cy.get('.Select__clear-indicator').click();
-
-      // trigger 1 new query
-      cy.wait('@treemapRequest');
-
-      // make sure query API not requested multiple times
-      cy.on('fail', err => {
-        expect(err.message).to.include('timed out waiting');
-        return false;
-      });
-
-      cy.wait('@boxplotRequest', { timeout: 1000 }).then(() => {
-        throw new Error('Unexpected API call.');
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js
new file mode 100644
index 0000000..e4dd0d0
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js
@@ -0,0 +1,204 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { TABBED_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard tabs', () => {
+  let filterId;
+  let treemapId;
+  let linechartId;
+  let boxplotId;
+  let dashboardId;
+
+  // cypress can not handle window.scrollTo
+  // https://github.com/cypress-io/cypress/issues/2761
+  // add this exception handler to pass test
+  const handleException = () => {
+    // return false to prevent the error from
+    // failing this test
+    cy.on('uncaught:exception', () => false);
+  };
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.visit(TABBED_DASHBOARD);
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      dashboardId = dashboard.id;
+      filterId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'filter_box',
+      ).slice_id;
+      boxplotId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'box_plot',
+      ).slice_id;
+      treemapId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'treemap',
+      ).slice_id;
+      linechartId = dashboard.slices.find(
+        slice => slice.form_data.viz_type === 'line',
+      ).slice_id;
+
+      const filterFormdata = {
+        slice_id: filterId,
+      };
+      const filterRequest = `/superset/explore_json/?form_data=${JSON.stringify(
+        filterFormdata,
+      )}&dashboard_id=${dashboardId}`;
+      cy.route('POST', filterRequest).as('filterRequest');
+
+      const treemapFormdata = {
+        slice_id: treemapId,
+      };
+      const treemapRequest = `/superset/explore_json/?form_data=${JSON.stringify(
+        treemapFormdata,
+      )}&dashboard_id=${dashboardId}`;
+      cy.route('POST', treemapRequest).as('treemapRequest');
+
+      const linechartFormdata = {
+        slice_id: linechartId,
+      };
+      const linechartRequest = `/superset/explore_json/?form_data=${JSON.stringify(
+        linechartFormdata,
+      )}&dashboard_id=${dashboardId}`;
+      cy.route('POST', linechartRequest).as('linechartRequest');
+
+      const boxplotFormdata = {
+        slice_id: boxplotId,
+      };
+      const boxplotRequest = `/superset/explore_json/?form_data=${JSON.stringify(
+        boxplotFormdata,
+      )}&dashboard_id=${dashboardId}`;
+      cy.route('POST', boxplotRequest).as('boxplotRequest');
+    });
+  });
+
+  it('should load charts when tab is visible', () => {
+    // landing in first tab, should see 2 charts
+    cy.wait('@filterRequest');
+    cy.get('.grid-container .filter_box').should('be.exist');
+    cy.wait('@treemapRequest');
+    cy.get('.grid-container .treemap').should('be.exist');
+    cy.get('.grid-container .box_plot').should('not.be.exist');
+    cy.get('.grid-container .line').should('not.be.exist');
+
+    // click row level tab, see 1 more chart
+    cy.get('.tab-content ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+    cy.wait('@linechartRequest');
+    cy.get('.grid-container .line').should('be.exist');
+
+    // click top level tab, see 1 more chart
+    handleException();
+    cy.get('.dashboard-component-tabs')
+      .first()
+      .find('ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+
+    // should exist a visible box_plot element
+    cy.get('.grid-container .box_plot');
+  });
+
+  it('should send new queries when tab becomes visible', () => {
+    // landing in first tab
+    cy.wait('@filterRequest');
+    cy.wait('@treemapRequest');
+
+    // apply filter
+    cy.get('.Select__control').first().should('be.visible');
+    cy.get('.Select__control').first().click({ force: true });
+    cy.get('.Select__control input[type=text]')
+      .first()
+      .should('be.visible')
+      .type('South Asia{enter}', { force: true });
+
+    // send new query from same tab
+    cy.wait('@treemapRequest').then(xhr => {
+      const requestFormData = xhr.request.body;
+      const requestParams = JSON.parse(requestFormData.get('form_data'));
+      expect(requestParams.extra_filters[0]).deep.eq({
+        col: 'region',
+        op: 'in',
+        val: ['South Asia'],
+      });
+    });
+
+    // click row level tab, send 1 more query
+    cy.get('.tab-content ul.nav.nav-tabs li').last().click();
+    cy.wait('@linechartRequest').then(xhr => {
+      const requestFormData = xhr.request.body;
+      const requestParams = JSON.parse(requestFormData.get('form_data'));
+      expect(requestParams.extra_filters[0]).deep.eq({
+        col: 'region',
+        op: 'in',
+        val: ['South Asia'],
+      });
+    });
+
+    // click top level tab, send 1 more query
+    handleException();
+    cy.get('.dashboard-component-tabs')
+      .first()
+      .find('ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+
+    cy.wait('@boxplotRequest').then(xhr => {
+      const requestFormData = xhr.request.body;
+      const requestParams = JSON.parse(requestFormData.get('form_data'));
+      expect(requestParams.extra_filters[0]).deep.eq({
+        col: 'region',
+        op: 'in',
+        val: ['South Asia'],
+      });
+    });
+
+    // navigate to filter and clear filter
+    cy.get('.dashboard-component-tabs')
+      .first()
+      .find('ul.nav.nav-tabs li')
+      .first()
+      .click();
+    cy.get('.tab-content ul.nav.nav-tabs li')
+      .first()
+      .should('be.visible')
+      .click();
+    cy.get('.Select__clear-indicator').click();
+
+    // trigger 1 new query
+    cy.wait('@treemapRequest');
+
+    // make sure query API not requested multiple times
+    cy.on('fail', err => {
+      expect(err.message).to.include('timed out waiting');
+      return false;
+    });
+
+    cy.wait('@boxplotRequest', { timeout: 1000 }).then(() => {
+      throw new Error('Unexpected API call.');
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.js b/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.js
deleted file mode 100644
index 84b1a82..0000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
-
-export default () =>
-  describe('dashboard form data', () => {
-    const urlParams = { param1: '123', param2: 'abc' };
-    let sliceIds = [];
-    let dashboardId;
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-
-      cy.visit(WORLD_HEALTH_DASHBOARD, { qs: urlParams });
-
-      cy.get('#app').then(data => {
-        const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-        const dashboard = bootstrapData.dashboard_data;
-        dashboardId = dashboard.id;
-        sliceIds = dashboard.slices.map(slice => slice.slice_id);
-      });
-    });
-
-    it('should apply url params and queryFields to slice requests', () => {
-      const aliases = [];
-      sliceIds.forEach(id => {
-        const alias = `getJson_${id}`;
-        aliases.push(`@${alias}`);
-        cy.route(
-          'POST',
-          `/superset/explore_json/?form_data={"slice_id":${id}}&dashboard_id=${dashboardId}`,
-        ).as(alias);
-      });
-
-      cy.wait(aliases).then(requests => {
-        requests.forEach(xhr => {
-          const requestFormData = xhr.request.body;
-          const requestParams = JSON.parse(requestFormData.get('form_data'));
-          expect(requestParams).to.have.property('queryFields');
-          expect(requestParams.url_params).deep.eq(urlParams);
-        });
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.js
new file mode 100644
index 0000000..04eec1a
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.js
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
+
+describe('Dashboard form data', () => {
+  const urlParams = { param1: '123', param2: 'abc' };
+  let sliceIds = [];
+  let dashboardId;
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.visit(WORLD_HEALTH_DASHBOARD, { qs: urlParams });
+
+    cy.get('#app').then(data => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      dashboardId = dashboard.id;
+      sliceIds = dashboard.slices.map(slice => slice.slice_id);
+    });
+  });
+
+  it('should apply url params and queryFields to slice requests', () => {
+    const aliases = [];
+    sliceIds.forEach(id => {
+      const alias = `getJson_${id}`;
+      aliases.push(`@${alias}`);
+      cy.route(
+        'POST',
+        `/superset/explore_json/?form_data={"slice_id":${id}}&dashboard_id=${dashboardId}`,
+      ).as(alias);
+    });
+
+    cy.wait(aliases).then(requests => {
+      requests.forEach(xhr => {
+        const requestFormData = xhr.request.body;
+        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        expect(requestParams).to.have.property('queryFields');
+        expect(requestParams.url_params).deep.eq(urlParams);
+      });
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/index.test.js b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
similarity index 53%
rename from superset-frontend/cypress-base/cypress/integration/dashboard/index.test.js
rename to superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
index b4ee364..b9793b3 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/index.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
@@ -16,22 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import DashboardControlsTest from './controls';
-import DashboardEditModeTest from './edit_mode';
-import DashboardFavStarTest from './fav_star';
-import DashboardFilterTest from './filter';
-import DashboardLoadTest from './load';
-import DashboardSaveTest from './save';
-import DashboardTabsTest from './tabs';
-import DashboardFormDataTest from './url_params';
+import { FORM_DATA_DEFAULTS } from './visualizations/shared.helper';
 
-describe('Dashboard', () => {
-  DashboardControlsTest();
-  DashboardEditModeTest();
-  DashboardFavStarTest();
-  DashboardFilterTest();
-  DashboardLoadTest();
-  DashboardSaveTest();
-  DashboardTabsTest();
-  DashboardFormDataTest();
+describe('Edit FilterBox Chart', () => {
+  const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'filter_box' };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with default date filter', () => {
+    verify(VIZ_DEFAULTS);
+    // Filter box should default to having a date filter with no filter selected
+    cy.get('div.filter_box').within(() => {
+      cy.get('span').contains('No filter');
+    });
+  });
 });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js
deleted file mode 100644
index 229b05b..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import readResponseBlob from '../../../utils/readResponseBlob';
-
-export default () =>
-  describe('Area', () => {
-    const AREA_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'area',
-      slice_id: 48,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '1960-01-01 : now',
-      metrics: ['sum__SP_POP_TOTL'],
-      adhoc_filters: [],
-      groupby: [],
-      limit: '25',
-      order_desc: true,
-      contribution: false,
-      row_limit: 50000,
-      show_brush: 'auto',
-      show_legend: true,
-      line_interpolation: 'linear',
-      stacked_style: 'stack',
-      color_scheme: 'bnbColors',
-      rich_tooltip: true,
-      show_controls: false,
-      x_axis_label: '',
-      bottom_margin: 'auto',
-      x_ticks_layout: 'auto',
-      x_axis_format: 'smart_date',
-      x_axis_showminmax: false,
-      y_axis_format: '.3s',
-      y_log_scale: false,
-      rolling_type: 'None',
-      comparison_type: 'values',
-      annotation_layers: [],
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work without groupby', () => {
-      verify(AREA_FORM_DATA);
-      cy.get('.nv-area').should('have.length', 1);
-    });
-
-    it('should work with group by', () => {
-      verify({
-        ...AREA_FORM_DATA,
-        groupby: ['region'],
-      });
-
-      cy.get('.nv-area').should('have.length', 7);
-    });
-
-    it('should work with groupby and filter', () => {
-      cy.visitChartByParams(
-        JSON.stringify({
-          ...AREA_FORM_DATA,
-          groupby: ['region'],
-          adhoc_filters: [
-            {
-              expressionType: 'SIMPLE',
-              subject: 'region',
-              operator: 'in',
-              comparator: ['South Asia', 'North America'],
-              clause: 'WHERE',
-              sqlExpression: null,
-              filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
-            },
-          ],
-        }),
-      );
-
-      cy.wait('@getJson').then(async xhr => {
-        cy.verifyResponseCodes(xhr);
-
-        const responseBody = await readResponseBlob(xhr.response.body);
-
-        // Make sure data is sorted correctly
-        const firstRow = responseBody.data[0].values;
-        const secondRow = responseBody.data[1].values;
-        expect(firstRow[firstRow.length - 1].y).to.be.greaterThan(
-          secondRow[secondRow.length - 1].y,
-        );
-        cy.verifySliceContainer('svg');
-      });
-      cy.get('.nv-area').should('have.length', 2);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
new file mode 100644
index 0000000..b3c1b1a
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import readResponseBlob from '../../../utils/readResponseBlob';
+
+describe('Visualization > Area', () => {
+  const AREA_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'area',
+    slice_id: 48,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: '1960-01-01 : now',
+    metrics: ['sum__SP_POP_TOTL'],
+    adhoc_filters: [],
+    groupby: [],
+    limit: '25',
+    order_desc: true,
+    contribution: false,
+    row_limit: 50000,
+    show_brush: 'auto',
+    show_legend: true,
+    line_interpolation: 'linear',
+    stacked_style: 'stack',
+    color_scheme: 'bnbColors',
+    rich_tooltip: true,
+    show_controls: false,
+    x_axis_label: '',
+    bottom_margin: 'auto',
+    x_ticks_layout: 'auto',
+    x_axis_format: 'smart_date',
+    x_axis_showminmax: false,
+    y_axis_format: '.3s',
+    y_log_scale: false,
+    rolling_type: 'None',
+    comparison_type: 'values',
+    annotation_layers: [],
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work without groupby', () => {
+    verify(AREA_FORM_DATA);
+    cy.get('.nv-area').should('have.length', 1);
+  });
+
+  it('should work with group by', () => {
+    verify({
+      ...AREA_FORM_DATA,
+      groupby: ['region'],
+    });
+
+    cy.get('.nv-area').should('have.length', 7);
+  });
+
+  it('should work with groupby and filter', () => {
+    cy.visitChartByParams(
+      JSON.stringify({
+        ...AREA_FORM_DATA,
+        groupby: ['region'],
+        adhoc_filters: [
+          {
+            expressionType: 'SIMPLE',
+            subject: 'region',
+            operator: 'in',
+            comparator: ['South Asia', 'North America'],
+            clause: 'WHERE',
+            sqlExpression: null,
+            filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
+          },
+        ],
+      }),
+    );
+
+    cy.wait('@getJson').then(async xhr => {
+      cy.verifyResponseCodes(xhr);
+
+      const responseBody = await readResponseBlob(xhr.response.body);
+
+      // Make sure data is sorted correctly
+      const firstRow = responseBody.data[0].values;
+      const secondRow = responseBody.data[1].values;
+      expect(firstRow[firstRow.length - 1].y).to.be.greaterThan(
+        secondRow[secondRow.length - 1].y,
+      );
+      cy.verifySliceContainer('svg');
+    });
+    cy.get('.nv-area').should('have.length', 2);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.js
deleted file mode 100644
index ede0659..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Big Number', () => {
-    const BIG_NUMBER_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'big_number',
-      slice_id: 42,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '2000+:+2014-01-02',
-      metric: 'sum__SP_POP_TOTL',
-      adhoc_filters: [],
-      compare_lag: '10',
-      compare_suffix: 'over+10Y',
-      y_axis_format: '.3s',
-      show_trend_line: true,
-      start_y_axis_at_zero: true,
-      color_picker: {
-        r: 0,
-        g: 122,
-        b: 135,
-        a: 1,
-      },
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        chartSelector: '.big_number',
-      });
-    }
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(BIG_NUMBER_FORM_DATA);
-      cy.get('.chart-container .header-line');
-      cy.get('.chart-container .subheader-line');
-      cy.get('.chart-container svg path.vx-linepath');
-    });
-
-    it('should work without subheader', () => {
-      verify({
-        ...BIG_NUMBER_FORM_DATA,
-        compare_lag: null,
-      });
-      cy.get('.chart-container .header-line');
-      cy.get('.chart-container .subheader-line').should('not.exist');
-      cy.get('.chart-container svg path.vx-linepath');
-    });
-
-    it('should not render trendline when hidden', () => {
-      verify({
-        ...BIG_NUMBER_FORM_DATA,
-        show_trend_line: false,
-      });
-      cy.get('.chart-container .header-line');
-      cy.get('.chart-container .subheader-line');
-      cy.get('.chart-container svg').should('not.exist');
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
new file mode 100644
index 0000000..89ca8c4
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Big Number with Trendline', () => {
+  const BIG_NUMBER_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'big_number',
+    slice_id: 42,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: '2000+:+2014-01-02',
+    metric: 'sum__SP_POP_TOTL',
+    adhoc_filters: [],
+    compare_lag: '10',
+    compare_suffix: 'over+10Y',
+    y_axis_format: '.3s',
+    show_trend_line: true,
+    start_y_axis_at_zero: true,
+    color_picker: {
+      r: 0,
+      g: 122,
+      b: 135,
+      a: 1,
+    },
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      chartSelector: '.big_number',
+    });
+  }
+
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(BIG_NUMBER_FORM_DATA);
+    cy.get('.chart-container .header-line');
+    cy.get('.chart-container .subheader-line');
+    cy.get('.chart-container svg path.vx-linepath');
+  });
+
+  it('should work without subheader', () => {
+    verify({
+      ...BIG_NUMBER_FORM_DATA,
+      compare_lag: null,
+    });
+    cy.get('.chart-container .header-line');
+    cy.get('.chart-container .subheader-line').should('not.exist');
+    cy.get('.chart-container svg path.vx-linepath');
+  });
+
+  it('should not render trendline when hidden', () => {
+    verify({
+      ...BIG_NUMBER_FORM_DATA,
+      show_trend_line: false,
+    });
+    cy.get('.chart-container .header-line');
+    cy.get('.chart-container .subheader-line');
+    cy.get('.chart-container svg').should('not.exist');
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js
deleted file mode 100644
index fca8627..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
-import readResponseBlob from '../../../utils/readResponseBlob';
-
-// Big Number Total
-
-export default () =>
-  describe('Big Number Total', () => {
-    const BIG_NUMBER_DEFAULTS = {
-      ...FORM_DATA_DEFAULTS,
-      viz_type: 'big_number_total',
-    };
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('Test big number chart with adhoc metric', () => {
-      const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-      });
-    });
-
-    it('Test big number chart with simple filter', () => {
-      const filters = [
-        {
-          expressionType: 'SIMPLE',
-          subject: 'name',
-          operator: 'in',
-          comparator: ['Aaron', 'Amy', 'Andrea'],
-          clause: 'WHERE',
-          sqlExpression: null,
-          filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c',
-        },
-      ];
-
-      const formData = {
-        ...BIG_NUMBER_DEFAULTS,
-        metric: 'count',
-        adhoc_filters: filters,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson' });
-    });
-
-    it('Test big number chart ignores groupby', () => {
-      const formData = {
-        ...BIG_NUMBER_DEFAULTS,
-        metric: NUM_METRIC,
-        groupby: ['state'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.wait(['@getJson']).then(async xhr => {
-        cy.verifyResponseCodes(xhr);
-        cy.verifySliceContainer();
-
-        const responseBody = await readResponseBlob(xhr.response.body);
-        expect(responseBody.query).not.contains(formData.groupby[0]);
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.test.js
new file mode 100644
index 0000000..98aca2f
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.test.js
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
+import readResponseBlob from '../../../utils/readResponseBlob';
+
+// Big Number Total
+
+describe('Visualization > Big Number Total', () => {
+  const BIG_NUMBER_DEFAULTS = {
+    ...FORM_DATA_DEFAULTS,
+    viz_type: 'big_number_total',
+  };
+
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('Test big number chart with adhoc metric', () => {
+    const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+    });
+  });
+
+  it('Test big number chart with simple filter', () => {
+    const filters = [
+      {
+        expressionType: 'SIMPLE',
+        subject: 'name',
+        operator: 'in',
+        comparator: ['Aaron', 'Amy', 'Andrea'],
+        clause: 'WHERE',
+        sqlExpression: null,
+        filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c',
+      },
+    ];
+
+    const formData = {
+      ...BIG_NUMBER_DEFAULTS,
+      metric: 'count',
+      adhoc_filters: filters,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson' });
+  });
+
+  it('Test big number chart ignores groupby', () => {
+    const formData = {
+      ...BIG_NUMBER_DEFAULTS,
+      metric: NUM_METRIC,
+      groupby: ['state'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.wait(['@getJson']).then(async xhr => {
+      cy.verifyResponseCodes(xhr);
+      cy.verifySliceContainer();
+
+      const responseBody = await readResponseBlob(xhr.response.body);
+      expect(responseBody.query).not.contains(formData.groupby[0]);
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js
deleted file mode 100644
index 5d4b8f3..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Box Plot', () => {
-    const BOX_PLOT_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'box_plot',
-      slice_id: 49,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '1960-01-01+:+now',
-      metrics: ['sum__SP_POP_TOTL'],
-      adhoc_filters: [],
-      groupby: ['region'],
-      limit: '25',
-      color_scheme: 'bnbColors',
-      whisker_options: 'Min/max+(no+outliers)',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(BOX_PLOT_FORM_DATA);
-      cy.get('.chart-container svg rect.nv-boxplot-box').should(
-        'have.length',
-        7,
-      );
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...BOX_PLOT_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'region',
-            operator: '==',
-            comparator: 'South Asia',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
-          },
-        ],
-      });
-      cy.get('.chart-container svg rect.nv-boxplot-box').should(
-        'have.length',
-        1,
-      );
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
new file mode 100644
index 0000000..e58a7c0
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Box Plot', () => {
+  const BOX_PLOT_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'box_plot',
+    slice_id: 49,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: '1960-01-01+:+now',
+    metrics: ['sum__SP_POP_TOTL'],
+    adhoc_filters: [],
+    groupby: ['region'],
+    limit: '25',
+    color_scheme: 'bnbColors',
+    whisker_options: 'Min/max+(no+outliers)',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(BOX_PLOT_FORM_DATA);
+    cy.get('.chart-container svg rect.vx-boxplot-box').should('have.length', 7);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...BOX_PLOT_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'region',
+          operator: '==',
+          comparator: 'South Asia',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
+        },
+      ],
+    });
+    cy.get('.chart-container svg rect.vx-boxplot-box').should('have.length', 1);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js
deleted file mode 100644
index d9d735e..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Bubble', () => {
-    const BUBBLE_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'bubble',
-      slice_id: 46,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '2011-01-01+:+2011-01-02',
-      series: 'region',
-      entity: 'country_name',
-      x: 'sum__SP_RUR_TOTL_ZS',
-      y: 'sum__SP_DYN_LE00_IN',
-      size: 'sum__SP_POP_TOTL',
-      max_bubble_size: '50',
-      limit: 0,
-      color_scheme: 'bnbColors',
-      show_legend: true,
-      x_axis_label: '',
-      left_margin: 'auto',
-      x_axis_format: '.3s',
-      x_ticks_layout: 'auto',
-      x_log_scale: false,
-      x_axis_showminmax: false,
-      y_axis_label: '',
-      bottom_margin: 'auto',
-      y_axis_format: '.3s',
-      y_log_scale: false,
-      y_axis_showminmax: false,
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(BUBBLE_FORM_DATA);
-      cy.get('.chart-container svg circle').should('have.length', 208);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...BUBBLE_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'region',
-            operator: '==',
-            comparator: 'South+Asia',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_b2tfg1rs8y_8kmrcyxvsqd',
-          },
-        ],
-      });
-      cy.get('.chart-container svg circle')
-        .should('have.length', 9)
-        .then(nodeList => {
-          // Check that all circles have same color.
-          const color = nodeList[0].getAttribute('fill');
-          const circles = Array.prototype.slice.call(nodeList);
-          expect(circles.every(c => c.getAttribute('fill') === color)).to.equal(
-            true,
-          );
-        });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
new file mode 100644
index 0000000..1b44919
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Bubble', () => {
+  const BUBBLE_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'bubble',
+    slice_id: 46,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: '2011-01-01+:+2011-01-02',
+    series: 'region',
+    entity: 'country_name',
+    x: 'sum__SP_RUR_TOTL_ZS',
+    y: 'sum__SP_DYN_LE00_IN',
+    size: 'sum__SP_POP_TOTL',
+    max_bubble_size: '50',
+    limit: 0,
+    color_scheme: 'bnbColors',
+    show_legend: true,
+    x_axis_label: '',
+    left_margin: 'auto',
+    x_axis_format: '.3s',
+    x_ticks_layout: 'auto',
+    x_log_scale: false,
+    x_axis_showminmax: false,
+    y_axis_label: '',
+    bottom_margin: 'auto',
+    y_axis_format: '.3s',
+    y_log_scale: false,
+    y_axis_showminmax: false,
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(BUBBLE_FORM_DATA);
+    // number of circles = 214 rows
+    cy.get('.chart-container svg .nv-point-clips circle').should(
+      'have.length',
+      214,
+    );
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...BUBBLE_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'region',
+          operator: '==',
+          comparator: 'South+Asia',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_b2tfg1rs8y_8kmrcyxvsqd',
+        },
+      ],
+    });
+    cy.get('.chart-container svg .nv-point-clips circle')
+      .should('have.length', 8)
+      .then(nodeList => {
+        // Check that all circles have same color.
+        const color = nodeList[0].getAttribute('fill');
+        const circles = Array.prototype.slice.call(nodeList);
+        expect(circles.every(c => c.getAttribute('fill') === color)).to.equal(
+          true,
+        );
+      });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js
deleted file mode 100644
index c28ed9b..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Compare', () => {
-    const COMPARE_FORM_DATA = {
-      datasource: '3__table',
-      viz_type: 'compare',
-      slice_id: 60,
-      granularity_sqla: 'ds',
-      time_grain_sqla: 'P1D',
-      time_range: '100 years ago : now',
-      metrics: ['count'],
-      adhoc_filters: [],
-      groupby: [],
-      order_desc: true,
-      contribution: false,
-      row_limit: 50000,
-      color_scheme: 'bnbColors',
-      x_axis_label: 'Frequency',
-      bottom_margin: 'auto',
-      x_ticks_layout: 'auto',
-      x_axis_format: 'smart_date',
-      x_axis_showminmax: false,
-      y_axis_label: 'Num',
-      left_margin: 'auto',
-      y_axis_showminmax: false,
-      y_log_scale: false,
-      y_axis_format: '.3s',
-      rolling_type: 'None',
-      comparison_type: 'values',
-      annotation_layers: [],
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work without groupby', () => {
-      verify(COMPARE_FORM_DATA);
-      cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 1);
-    });
-
-    it('should with group by', () => {
-      verify({
-        ...COMPARE_FORM_DATA,
-        groupby: ['gender'],
-      });
-      cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 2);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...COMPARE_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'gender',
-            operator: '==',
-            comparator: 'boy',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
-          },
-        ],
-      });
-      cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 1);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
new file mode 100644
index 0000000..6b538f6
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Compare', () => {
+  const COMPARE_FORM_DATA = {
+    datasource: '3__table',
+    viz_type: 'compare',
+    slice_id: 60,
+    granularity_sqla: 'ds',
+    time_grain_sqla: 'P1D',
+    time_range: '100 years ago : now',
+    metrics: ['count'],
+    adhoc_filters: [],
+    groupby: [],
+    order_desc: true,
+    contribution: false,
+    row_limit: 50000,
+    color_scheme: 'bnbColors',
+    x_axis_label: 'Frequency',
+    bottom_margin: 'auto',
+    x_ticks_layout: 'auto',
+    x_axis_format: 'smart_date',
+    x_axis_showminmax: false,
+    y_axis_label: 'Num',
+    left_margin: 'auto',
+    y_axis_showminmax: false,
+    y_log_scale: false,
+    y_axis_format: '.3s',
+    rolling_type: 'None',
+    comparison_type: 'values',
+    annotation_layers: [],
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work without groupby', () => {
+    verify(COMPARE_FORM_DATA);
+    cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 1);
+  });
+
+  it('should with group by', () => {
+    verify({
+      ...COMPARE_FORM_DATA,
+      groupby: ['gender'],
+    });
+    cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 2);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...COMPARE_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'gender',
+          operator: '==',
+          comparator: 'boy',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
+        },
+      ],
+    });
+    cy.get('.chart-container .nvd3 path.nv-line').should('have.length', 1);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.js
deleted file mode 100644
index 7b3a8f0..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
-
-// Dist bar
-
-export default () =>
-  describe('Distribution bar chart', () => {
-    const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'dist_bar' };
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('Test dist bar with adhoc metric', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['state'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-        chartSelector: 'svg',
-      });
-    });
-
-    it('Test dist bar with series', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['state'],
-        columns: ['gender'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test dist bar with row limit', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['state'],
-        row_limit: 10,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test dist bar with contribution', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['state'],
-        columns: ['gender'],
-        contribution: true,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
new file mode 100644
index 0000000..b2a7114
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
+
+describe('Visualization > Distribution bar chart', () => {
+  const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'dist_bar' };
+
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with adhoc metric', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['state'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+      chartSelector: 'svg',
+    });
+  });
+
+  it('should work with series', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['state'],
+      columns: ['gender'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with row limit', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['state'],
+      row_limit: 10,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with contribution', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['state'],
+      columns: ['gender'],
+      contribution: true,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js
deleted file mode 100644
index da302d1..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Dual Line', () => {
-    const DUAL_LINE_FORM_DATA = {
-      datasource: '3__table',
-      viz_type: 'dual_line',
-      slice_id: 58,
-      granularity_sqla: 'ds',
-      time_grain_sqla: 'P1D',
-      time_range: '100+years+ago+:+now',
-      color_scheme: 'bnbColors',
-      x_axis_format: 'smart_date',
-      metric: 'avg__num',
-      y_axis_format: '.3s',
-      metric_2: 'sum__num',
-      y_axis_2_format: '.3s',
-      adhoc_filters: [],
-      annotation_layers: [],
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(DUAL_LINE_FORM_DATA);
-      cy.get('.chart-container svg path.nv-line').should('have.length', 2);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...DUAL_LINE_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'gender',
-            operator: '==',
-            comparator: 'girl',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_1ep6q50g8vk_48jj6qxdems',
-          },
-        ],
-      });
-      cy.get('.chart-container svg path.nv-line').should('have.length', 2);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
new file mode 100644
index 0000000..bfe8660
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Dual Line', () => {
+  const DUAL_LINE_FORM_DATA = {
+    datasource: '3__table',
+    viz_type: 'dual_line',
+    slice_id: 58,
+    granularity_sqla: 'ds',
+    time_grain_sqla: 'P1D',
+    time_range: '100+years+ago+:+now',
+    color_scheme: 'bnbColors',
+    x_axis_format: 'smart_date',
+    metric: 'sum__num',
+    y_axis_format: '.3s',
+    metric_2: 'count',
+    y_axis_2_format: '.3s',
+    adhoc_filters: [],
+    annotation_layers: [],
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(DUAL_LINE_FORM_DATA);
+    cy.get('.chart-container svg path.nv-line').should('have.length', 2);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...DUAL_LINE_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'gender',
+          operator: '==',
+          comparator: 'girl',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_1ep6q50g8vk_48jj6qxdems',
+        },
+      ],
+    });
+    cy.get('.chart-container svg path.nv-line').should('have.length', 2);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/filter_box.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/filter_box.js
deleted file mode 100644
index f62937d..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/filter_box.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { FORM_DATA_DEFAULTS } from './shared.helper';
-
-export default () =>
-  describe('FilterBox', () => {
-    const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'filter_box' };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work with default date filter', () => {
-      verify(VIZ_DEFAULTS);
-      // Filter box should default to having a date filter with no filter selected
-      cy.get('div.filter_box').within(() => {
-        cy.get('span').contains('No filter');
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js
deleted file mode 100644
index c732691..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Histogram', () => {
-    const HISTOGRAM_FORM_DATA = {
-      datasource: '3__table',
-      viz_type: 'histogram',
-      slice_id: 60,
-      granularity_sqla: 'ds',
-      time_grain_sqla: 'P1D',
-      time_range: '100 years ago : now',
-      all_columns_x: ['num'],
-      adhoc_filters: [],
-      row_limit: 50000,
-      groupby: [],
-      color_scheme: 'bnbColors',
-      link_length: 5,
-      x_axis_label: 'Frequency',
-      y_axis_label: 'Num',
-      global_opacity: 1,
-      normalized: false,
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work without groupby', () => {
-      verify(HISTOGRAM_FORM_DATA);
-      cy.get('.chart-container svg .vx-bar').should('have.length', 6);
-    });
-
-    it('should with group by', () => {
-      verify({
-        ...HISTOGRAM_FORM_DATA,
-        groupby: ['gender'],
-      });
-      cy.get('.chart-container svg .vx-bar').should('have.length', 12);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...HISTOGRAM_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'gender',
-            operator: '==',
-            comparator: 'boy',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
-          },
-        ],
-      });
-      cy.get('.chart-container svg .vx-bar').should('have.length', 5);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.js
new file mode 100644
index 0000000..78ec06e
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.js
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Histogram', () => {
+  const HISTOGRAM_FORM_DATA = {
+    datasource: '3__table',
+    viz_type: 'histogram',
+    slice_id: 60,
+    granularity_sqla: 'ds',
+    time_grain_sqla: 'P1D',
+    time_range: '100 years ago : now',
+    all_columns_x: ['num'],
+    adhoc_filters: [],
+    row_limit: 50000,
+    groupby: [],
+    color_scheme: 'bnbColors',
+    link_length: 5, // number of bins
+    x_axis_label: 'Frequency',
+    y_axis_label: 'Num',
+    global_opacity: 1,
+    normalized: false,
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work without groupby', () => {
+    verify(HISTOGRAM_FORM_DATA);
+    cy.get('.chart-container svg .vx-bar').should(
+      'have.length',
+      HISTOGRAM_FORM_DATA.link_length,
+    );
+  });
+
+  it('should work with group by', () => {
+    verify({
+      ...HISTOGRAM_FORM_DATA,
+      groupby: ['gender'],
+    });
+    cy.get('.chart-container svg .vx-bar').should(
+      'have.length',
+      HISTOGRAM_FORM_DATA.link_length * 2,
+    );
+  });
+
+  it('should work with filter and update num bins', () => {
+    const numBins = 2;
+    verify({
+      ...HISTOGRAM_FORM_DATA,
+      link_length: numBins,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'state',
+          operator: '==',
+          comparator: 'CA',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
+        },
+      ],
+    });
+    cy.get('.chart-container svg .vx-bar').should('have.length', numBins);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/index.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/index.test.js
deleted file mode 100644
index cd15587..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/index.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import AreaTest from './area';
-import BigNumberTest from './big_number';
-import BigNumberTotalTest from './big_number_total';
-import BubbleTest from './bubble';
-import CompareTest from './compare';
-import DistBarTest from './dist_bar';
-import DualLineTest from './dual_line';
-import FilterBox from './filter_box';
-import HistogramTest from './histogram';
-import LineTest from './line';
-import PieTest from './pie';
-import PivotTableTest from './pivot_table';
-import SankeyTest from './sankey';
-import SunburstTest from './sunburst';
-import TableTest from './table';
-import TimeTableTest from './time_table';
-import TreemapTest from './treemap';
-import WorldMapTest from './world_map';
-
-describe('All Visualizations', () => {
-  AreaTest();
-  BigNumberTest();
-  BigNumberTotalTest();
-  BubbleTest();
-  CompareTest();
-  DistBarTest();
-  DualLineTest();
-  FilterBox();
-  HistogramTest();
-  LineTest();
-  PieTest();
-  PivotTableTest();
-  SankeyTest();
-  SunburstTest();
-  TableTest();
-  TimeTableTest();
-  TreemapTest();
-  WorldMapTest();
-});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.js
deleted file mode 100644
index 1292072..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.js
+++ /dev/null
@@ -1,258 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { FORM_DATA_DEFAULTS, NUM_METRIC, SIMPLE_FILTER } from './shared.helper';
-
-export default () =>
-  describe('Line', () => {
-    const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' };
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('Test line chart with adhoc metric', () => {
-      const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with groupby', () => {
-      const metrics = ['count'];
-      const groupby = ['gender'];
-
-      const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with simple filter', () => {
-      const metrics = ['count'];
-      const filters = [SIMPLE_FILTER];
-
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics,
-        adhoc_filters: filters,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with series limit sort asc', () => {
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics: [NUM_METRIC],
-        limit: 10,
-        groupby: ['name'],
-        timeseries_limit_metric: NUM_METRIC,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with series limit sort desc', () => {
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics: [NUM_METRIC],
-        limit: 10,
-        groupby: ['name'],
-        timeseries_limit_metric: NUM_METRIC,
-        order_desc: true,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with rolling avg', () => {
-      const metrics = [NUM_METRIC];
-
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics,
-        rolling_type: 'mean',
-        rolling_periods: 10,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with time shift 1 year', () => {
-      const metrics = [NUM_METRIC];
-
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics,
-        time_compare: ['1+year'],
-        comparison_type: 'values',
-        groupby: ['gender'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-
-      // Offset color should match original line color
-      cy.get('.nv-legend-text')
-        .contains('boy')
-        .siblings()
-        .first()
-        .should('have.attr', 'style')
-        .then(style => {
-          cy.get('.nv-legend-text')
-            .contains('boy, 1 year offset')
-            .siblings()
-            .first()
-            .should('have.attr', 'style')
-            .and('eq', style);
-        });
-
-      cy.get('.nv-legend-text')
-        .contains('girl')
-        .siblings()
-        .first()
-        .should('have.attr', 'style')
-        .then(style => {
-          cy.get('.nv-legend-text')
-            .contains('girl, 1 year offset')
-            .siblings()
-            .first()
-            .should('have.attr', 'style')
-            .and('eq', style);
-        });
-    });
-
-    it('Test line chart with time shift yoy', () => {
-      const metrics = [NUM_METRIC];
-
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics,
-        time_compare: ['1+year'],
-        comparison_type: 'ratio',
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test line chart with time shift percentage change', () => {
-      const metrics = [NUM_METRIC];
-
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics,
-        time_compare: ['1+year'],
-        comparison_type: 'percentage',
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    });
-
-    it('Test verbose name shows up in legend', () => {
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics: ['count'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-      cy.get('text.nv-legend-text').contains('COUNT(*)');
-    });
-
-    it('Test hidden annotation', () => {
-      const formData = {
-        ...LINE_CHART_DEFAULTS,
-        metrics: ['count'],
-        annotation_layers: [
-          {
-            name: 'Goal+line',
-            annotationType: 'FORMULA',
-            sourceType: '',
-            value: 'y=140000',
-            overrides: { time_range: null },
-            show: false,
-            titleColumn: '',
-            descriptionColumns: [],
-            timeColumn: '',
-            intervalEndColumn: '',
-            color: null,
-            opacity: '',
-            style: 'solid',
-            width: 1,
-            showMarkers: false,
-            hideLine: false,
-          },
-        ],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-      cy.get('.slice_container').within(() => {
-        // Goal line annotation doesn't show up in legend
-        cy.get('.nv-legend-text').should('have.length', 1);
-      });
-    });
-
-    it('Test event annotation time override', () => {
-      cy.request('/chart/api/read?_flt_3_slice_name=Daily+Totals').then(
-        response => {
-          const value = response.body.pks[0];
-          const formData = {
-            ...LINE_CHART_DEFAULTS,
-            metrics: ['count'],
-            annotation_layers: [
-              {
-                name: 'Yearly date',
-                annotationType: 'EVENT',
-                sourceType: 'table',
-                value,
-                overrides: { time_range: null },
-                show: true,
-                titleColumn: 'ds',
-                descriptionColumns: ['ds'],
-                timeColumn: 'ds',
-                color: null,
-                opacity: '',
-                style: 'solid',
-                width: 1,
-                showMarkers: false,
-                hideLine: false,
-              },
-            ],
-          };
-          cy.visitChartByParams(JSON.stringify(formData));
-        },
-      );
-
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-      cy.get('.slice_container').within(() => {
-        cy.get('.nv-event-annotation-layer-0')
-          .children()
-          .should('have.length', 44);
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js
new file mode 100644
index 0000000..5314a01
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js
@@ -0,0 +1,257 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FORM_DATA_DEFAULTS, NUM_METRIC, SIMPLE_FILTER } from './shared.helper';
+
+describe('Visualization > Line', () => {
+  const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' };
+
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with adhoc metric', () => {
+    const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with groupby', () => {
+    const metrics = ['count'];
+    const groupby = ['gender'];
+
+    const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with simple filter', () => {
+    const metrics = ['count'];
+    const filters = [SIMPLE_FILTER];
+
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics,
+      adhoc_filters: filters,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with series limit sort asc', () => {
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics: [NUM_METRIC],
+      limit: 10,
+      groupby: ['name'],
+      timeseries_limit_metric: NUM_METRIC,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with series limit sort desc', () => {
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics: [NUM_METRIC],
+      limit: 10,
+      groupby: ['name'],
+      timeseries_limit_metric: NUM_METRIC,
+      order_desc: true,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with rolling avg', () => {
+    const metrics = [NUM_METRIC];
+
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics,
+      rolling_type: 'mean',
+      rolling_periods: 10,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with time shift 1 year', () => {
+    const metrics = [NUM_METRIC];
+
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics,
+      time_compare: ['1+year'],
+      comparison_type: 'values',
+      groupby: ['gender'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+
+    // Offset color should match original line color
+    cy.get('.nv-legend-text')
+      .contains('boy')
+      .siblings()
+      .first()
+      .should('have.attr', 'style')
+      .then(style => {
+        cy.get('.nv-legend-text')
+          .contains('boy, 1 year offset')
+          .siblings()
+          .first()
+          .should('have.attr', 'style')
+          .and('eq', style);
+      });
+
+    cy.get('.nv-legend-text')
+      .contains('girl')
+      .siblings()
+      .first()
+      .should('have.attr', 'style')
+      .then(style => {
+        cy.get('.nv-legend-text')
+          .contains('girl, 1 year offset')
+          .siblings()
+          .first()
+          .should('have.attr', 'style')
+          .and('eq', style);
+      });
+  });
+
+  it('should work with time shift yoy', () => {
+    const metrics = [NUM_METRIC];
+
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics,
+      time_compare: ['1+year'],
+      comparison_type: 'ratio',
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('should work with time shift percentage change', () => {
+    const metrics = [NUM_METRIC];
+
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics,
+      time_compare: ['1+year'],
+      comparison_type: 'percentage',
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  });
+
+  it('Test verbose name shows up in legend', () => {
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics: ['count'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+    cy.get('text.nv-legend-text').contains('COUNT(*)');
+  });
+
+  it('Test hidden annotation', () => {
+    const formData = {
+      ...LINE_CHART_DEFAULTS,
+      metrics: ['count'],
+      annotation_layers: [
+        {
+          name: 'Goal+line',
+          annotationType: 'FORMULA',
+          sourceType: '',
+          value: 'y=140000',
+          overrides: { time_range: null },
+          show: false,
+          titleColumn: '',
+          descriptionColumns: [],
+          timeColumn: '',
+          intervalEndColumn: '',
+          color: null,
+          opacity: '',
+          style: 'solid',
+          width: 1,
+          showMarkers: false,
+          hideLine: false,
+        },
+      ],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+    cy.get('.slice_container').within(() => {
+      // Goal line annotation doesn't show up in legend
+      cy.get('.nv-legend-text').should('have.length', 1);
+    });
+  });
+
+  it('Test event annotation time override', () => {
+    cy.request('/chart/api/read?_flt_3_slice_name=Daily+Totals').then(
+      response => {
+        const value = response.body.pks[0];
+        const formData = {
+          ...LINE_CHART_DEFAULTS,
+          metrics: ['count'],
+          annotation_layers: [
+            {
+              name: 'Yearly date',
+              annotationType: 'EVENT',
+              sourceType: 'table',
+              value,
+              overrides: { time_range: null },
+              show: true,
+              titleColumn: 'ds',
+              descriptionColumns: ['ds'],
+              timeColumn: 'ds',
+              color: null,
+              opacity: '',
+              style: 'solid',
+              width: 1,
+              showMarkers: false,
+              hideLine: false,
+            },
+          ],
+        };
+        cy.visitChartByParams(JSON.stringify(formData));
+      },
+    );
+
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+    cy.get('.slice_container').within(() => {
+      cy.get('.nv-event-annotation-layer-0')
+        .children()
+        .should('have.length', 44);
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js
deleted file mode 100644
index 39ae873..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Pie', () => {
-    const PIE_FORM_DATA = {
-      datasource: '3__table',
-      viz_type: 'pie',
-      slice_id: 55,
-      granularity_sqla: 'ds',
-      time_grain_sqla: 'P1D',
-      time_range: '100 years ago : now',
-      metric: 'sum__num',
-      adhoc_filters: [],
-      groupby: ['gender'],
-      row_limit: 50000,
-      pie_label_type: 'key',
-      donut: false,
-      show_legend: true,
-      show_labels: true,
-      labels_outside: true,
-      color_scheme: 'bnbColors',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work with ad-hoc metric', () => {
-      verify(PIE_FORM_DATA);
-      cy.get('.chart-container .nv-pie .nv-slice path').should(
-        'have.length',
-        2,
-      );
-    });
-
-    it('should work with simple filter', () => {
-      verify({
-        ...PIE_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'gender',
-            operator: '==',
-            comparator: 'boy',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
-          },
-        ],
-      });
-      cy.get('.chart-container .nv-pie .nv-slice path').should(
-        'have.length',
-        1,
-      );
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
new file mode 100644
index 0000000..6c2dc68
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Pie', () => {
+  const PIE_FORM_DATA = {
+    datasource: '3__table',
+    viz_type: 'pie',
+    slice_id: 55,
+    granularity_sqla: 'ds',
+    time_grain_sqla: 'P1D',
+    time_range: '100 years ago : now',
+    metric: 'sum__num',
+    adhoc_filters: [],
+    groupby: ['gender'],
+    row_limit: 50000,
+    pie_label_type: 'key',
+    donut: false,
+    show_legend: true,
+    show_labels: true,
+    labels_outside: true,
+    color_scheme: 'bnbColors',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with ad-hoc metric', () => {
+    verify(PIE_FORM_DATA);
+    cy.get('.chart-container .nv-pie .nv-slice path').should('have.length', 2);
+  });
+
+  it('should work with simple filter', () => {
+    verify({
+      ...PIE_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'gender',
+          operator: '==',
+          comparator: 'boy',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_tqx1en70hh_7nksse7nqic',
+        },
+      ],
+    });
+    cy.get('.chart-container .nv-pie .nv-slice path').should('have.length', 1);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js
deleted file mode 100644
index 7157d8a..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Pivot Table', () => {
-    const PIVOT_TABLE_FORM_DATA = {
-      datasource: '3__table',
-      viz_type: 'pivot_table',
-      slice_id: 61,
-      granularity_sqla: 'ds',
-      time_grain_sqla: 'P1D',
-      time_range: '100+years+ago+:+now',
-      metrics: ['sum__num'],
-      adhoc_filters: [],
-      groupby: ['name'],
-      columns: ['state'],
-      row_limit: 50000,
-      pandas_aggfunc: 'sum',
-      pivot_margins: true,
-      number_format: '.3s',
-      combine_metric: false,
-    };
-
-    const TEST_METRIC = {
-      expressionType: 'SIMPLE',
-      column: {
-        id: 338,
-        column_name: 'sum_boys',
-        expression: '',
-        filterable: false,
-        groupby: false,
-        is_dttm: false,
-        type: 'BIGINT',
-        optionName: '_col_sum_boys',
-      },
-      aggregate: 'SUM',
-      hasCustomLabel: false,
-      label: 'SUM(sum_boys)',
-      optionName: 'metric_gvpdjt0v2qf_6hkf56o012',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work with single groupby', () => {
-      verify(PIVOT_TABLE_FORM_DATA);
-      cy.get('.chart-container tr th').then(ths => {
-        expect(ths.find(th => th.innerText.trim() === 'name')).to.not.equal(
-          undefined,
-        );
-      });
-    });
-
-    it('should work with more than one groupby', () => {
-      verify({
-        ...PIVOT_TABLE_FORM_DATA,
-        groupby: ['name', 'gender'],
-      });
-      cy.get('.chart-container tr th').then(ths => {
-        expect(ths.find(th => th.innerText.trim() === 'name')).to.not.equal(
-          undefined,
-        );
-        expect(ths.find(th => th.innerText.trim() === 'gender')).to.not.equal(
-          undefined,
-        );
-      });
-    });
-
-    it('should work with multiple metrics', () => {
-      verify({
-        ...PIVOT_TABLE_FORM_DATA,
-        metrics: ['sum__num', TEST_METRIC],
-      });
-      cy.get('.chart-container tr th').then(ths => {
-        expect(ths.find(th => th.innerText.trim() === 'sum__num')).to.not.equal(
-          undefined,
-        );
-        expect(
-          ths.find(th => th.innerText.trim() === 'SUM(sum_boys)'),
-        ).to.not.equal(undefined);
-      });
-    });
-
-    it('should work with multiple groupby and multiple metrics', () => {
-      verify({
-        ...PIVOT_TABLE_FORM_DATA,
-        groupby: ['name', 'gender'],
-        metrics: ['sum__num', TEST_METRIC],
-      });
-      cy.get('.chart-container tr th').then(ths => {
-        expect(ths.find(th => th.innerText.trim() === 'name')).to.not.equal(
-          undefined,
-        );
-        expect(ths.find(th => th.innerText.trim() === 'gender')).to.not.equal(
-          undefined,
-        );
-        expect(ths.find(th => th.innerText.trim() === 'sum__num')).to.not.equal(
-          undefined,
-        );
-        expect(
-          ths.find(th => th.innerText.trim() === 'SUM(sum_boys)'),
-        ).to.not.equal(undefined);
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
new file mode 100644
index 0000000..32e9923
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Pivot Table', () => {
+  const PIVOT_TABLE_FORM_DATA = {
+    datasource: '3__table',
+    viz_type: 'pivot_table',
+    slice_id: 61,
+    granularity_sqla: 'ds',
+    time_grain_sqla: 'P1D',
+    time_range: '100+years+ago+:+now',
+    metrics: ['sum__num'],
+    adhoc_filters: [],
+    groupby: ['name'],
+    columns: ['state'],
+    row_limit: 50000,
+    pandas_aggfunc: 'sum',
+    pivot_margins: true,
+    number_format: '.3s',
+    combine_metric: false,
+  };
+
+  const TEST_METRIC = {
+    expressionType: 'SIMPLE',
+    column: {
+      id: 338,
+      column_name: 'sum_boys',
+      expression: '',
+      filterable: false,
+      groupby: false,
+      is_dttm: false,
+      type: 'BIGINT',
+      optionName: '_col_sum_boys',
+    },
+    aggregate: 'SUM',
+    hasCustomLabel: false,
+    label: 'SUM(sum_boys)',
+    optionName: 'metric_gvpdjt0v2qf_6hkf56o012',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with single groupby', () => {
+    verify(PIVOT_TABLE_FORM_DATA);
+    cy.get('.chart-container tr:eq(0) th:eq(1)').contains('sum__num');
+    cy.get('.chart-container tr:eq(1) th:eq(0)').contains('state');
+    cy.get('.chart-container tr:eq(2) th:eq(0)').contains('name');
+  });
+
+  it('should work with more than one groupby', () => {
+    verify({
+      ...PIVOT_TABLE_FORM_DATA,
+      groupby: ['name', 'gender'],
+    });
+    cy.get('.chart-container tr:eq(0) th:eq(2)').contains('sum__num');
+    cy.get('.chart-container tr:eq(1) th:eq(1)').contains('state');
+    cy.get('.chart-container tr:eq(2) th:eq(0)').contains('name');
+    cy.get('.chart-container tr:eq(2) th:eq(1)').contains('gender');
+  });
+
+  it('should work with multiple metrics', () => {
+    verify({
+      ...PIVOT_TABLE_FORM_DATA,
+      metrics: ['sum__num', TEST_METRIC],
+    });
+    cy.get('.chart-container tr:eq(0) th:eq(1)').contains('sum__num');
+    cy.get('.chart-container tr:eq(0) th:eq(2)').contains('SUM(sum_boys)');
+    cy.get('.chart-container tr:eq(1) th:eq(0)').contains('state');
+    cy.get('.chart-container tr:eq(2) th:eq(0)').contains('name');
+  });
+
+  it('should work with multiple groupby and multiple metrics', () => {
+    verify({
+      ...PIVOT_TABLE_FORM_DATA,
+      groupby: ['name', 'gender'],
+      metrics: ['sum__num', TEST_METRIC],
+    });
+    cy.get('.chart-container tr:eq(0) th:eq(2)').contains('sum__num');
+    cy.get('.chart-container tr:eq(0) th:eq(3)').contains('SUM(sum_boys)');
+    cy.get('.chart-container tr:eq(2) th:eq(0)').contains('name');
+    cy.get('.chart-container tr:eq(2) th:eq(1)').contains('gender');
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js
deleted file mode 100644
index 0d1de94..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Sankey', () => {
-    const SANKEY_FORM_DATA = {
-      datasource: '1__table',
-      viz_type: 'sankey',
-      slice_id: 1,
-      url_params: {},
-      granularity_sqla: null,
-      time_grain_sqla: 'P1D',
-      time_range: 'Last+week',
-      groupby: ['source', 'target'],
-      metric: 'sum__value',
-      adhoc_filters: [],
-      row_limit: '5000',
-      color_scheme: 'bnbColors',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(SANKEY_FORM_DATA);
-      cy.get('.chart-container svg g.node rect').should('have.length', 41);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...SANKEY_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SQL',
-            sqlExpression: 'SUM(value)+>+0',
-            clause: 'HAVING',
-            subject: null,
-            operator: null,
-            comparator: null,
-            filterOptionName: 'filter_jbdwe0hayaj_h9jfer8fy58',
-          },
-          {
-            expressionType: 'SIMPLE',
-            subject: 'source',
-            operator: '==',
-            comparator: 'Energy',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_8e0otka9uif_vmqri4gmbqc',
-          },
-        ],
-      });
-      cy.get('.chart-container svg g.node rect').should('have.length', 6);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
new file mode 100644
index 0000000..dc113df
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Sankey', () => {
+  const SANKEY_FORM_DATA = {
+    datasource: '1__table',
+    viz_type: 'sankey',
+    slice_id: 1,
+    url_params: {},
+    granularity_sqla: null,
+    time_grain_sqla: 'P1D',
+    time_range: 'Last+week',
+    groupby: ['source', 'target'],
+    metric: 'sum__value',
+    adhoc_filters: [],
+    row_limit: '5000',
+    color_scheme: 'bnbColors',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(SANKEY_FORM_DATA);
+    cy.get('.chart-container svg g.node rect').should('have.length', 41);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...SANKEY_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SQL',
+          sqlExpression: 'SUM(value)+>+0',
+          clause: 'HAVING',
+          subject: null,
+          operator: null,
+          comparator: null,
+          filterOptionName: 'filter_jbdwe0hayaj_h9jfer8fy58',
+        },
+        {
+          expressionType: 'SIMPLE',
+          subject: 'source',
+          operator: '==',
+          comparator: 'Energy',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_8e0otka9uif_vmqri4gmbqc',
+        },
+      ],
+    });
+    cy.get('.chart-container svg g.node rect').should('have.length', 6);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js
deleted file mode 100644
index adaaaf8..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Sunburst', () => {
-    const SUNBURST_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'sunburst',
-      slice_id: 47,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '2011-01-01+:+2011-01-01',
-      groupby: ['region'],
-      metric: 'sum__SP_POP_TOTL',
-      adhoc_filters: [],
-      row_limit: 50000,
-      color_scheme: 'bnbColors',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work without secondary metric', () => {
-      verify(SUNBURST_FORM_DATA);
-      // There should be 7 visible arcs + 1 hidden
-      cy.get('.chart-container svg g#arcs path').should('have.length', 8);
-    });
-
-    it('should work with secondary metric', () => {
-      verify({
-        ...SUNBURST_FORM_DATA,
-        secondary_metric: 'sum__SP_RUR_TOTL',
-      });
-      cy.get('.chart-container svg g#arcs path').should('have.length', 8);
-    });
-
-    it('should work with multiple groupbys', () => {
-      verify({
-        ...SUNBURST_FORM_DATA,
-        groupby: ['region', 'country_name'],
-      });
-      cy.get('.chart-container svg g#arcs path').should('have.length', 118);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...SUNBURST_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'region',
-            operator: 'in',
-            comparator: ['South Asia', 'North America'],
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
-          },
-        ],
-      });
-      cy.get('.chart-container svg g#arcs path').should('have.length', 3);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
new file mode 100644
index 0000000..d3b39e8
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Sunburst', () => {
+  const SUNBURST_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'sunburst',
+    slice_id: 47,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: 'No filter',
+    groupby: ['region'],
+    metric: 'sum__SP_POP_TOTL',
+    adhoc_filters: [],
+    row_limit: 50000,
+    color_scheme: 'bnbColors',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work without secondary metric', () => {
+    verify(SUNBURST_FORM_DATA);
+    // There should be 7 visible arcs + 1 hidden
+    cy.get('.chart-container svg g#arcs path').should('have.length', 8);
+  });
+
+  it('should work with secondary metric', () => {
+    verify({
+      ...SUNBURST_FORM_DATA,
+      secondary_metric: 'sum__SP_RUR_TOTL',
+    });
+    cy.get('.chart-container svg g#arcs path').should('have.length', 8);
+  });
+
+  it('should work with multiple groupbys', () => {
+    verify({
+      ...SUNBURST_FORM_DATA,
+      groupby: ['region', 'country_name'],
+    });
+    cy.get('.chart-container svg g#arcs path').should('have.length', 117);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...SUNBURST_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'region',
+          operator: 'in',
+          comparator: ['South Asia', 'North America'],
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
+        },
+      ],
+    });
+    cy.get('.chart-container svg g#arcs path').should('have.length', 3);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js
deleted file mode 100644
index c5e5b04..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { FORM_DATA_DEFAULTS, NUM_METRIC, SIMPLE_FILTER } from './shared.helper';
-import readResponseBlob from '../../../utils/readResponseBlob';
-
-// Table
-
-export default () =>
-  describe('Table chart', () => {
-    const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'table' };
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('Test table with adhoc metric', () => {
-      const formData = { ...VIZ_DEFAULTS, metrics: NUM_METRIC };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-        chartSelector: 'table',
-      });
-    });
-
-    it('Test table with groupby', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['name'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: formData.groupby[0],
-        chartSelector: 'table',
-      });
-    });
-
-    it('Test table with percent metrics and groupby', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        percent_metrics: NUM_METRIC,
-        metrics: [],
-        groupby: ['name'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
-    });
-
-    it('Test table with groupby order desc', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['name'],
-        order_desc: true,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
-    });
-
-    it('Test table with groupby and limit', () => {
-      const limit = 10;
-
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: NUM_METRIC,
-        groupby: ['name'],
-        row_limit: limit,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-
-      cy.wait('@getJson').then(async xhr => {
-        cy.verifyResponseCodes(xhr);
-        cy.verifySliceContainer('table');
-        const responseBody = await readResponseBlob(xhr.response.body);
-        expect(responseBody.data.records.length).to.eq(limit);
-      });
-      cy.get('span.label-danger').contains('10 rows');
-    });
-
-    it('Test table with columns and row limit', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        all_columns: ['name'],
-        metrics: [],
-        row_limit: 10,
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
-    });
-
-    it('Test table with columns, ordering, and row limit', () => {
-      const limit = 10;
-
-      const formData = {
-        ...VIZ_DEFAULTS,
-        all_columns: ['name', 'state', 'ds', 'num'],
-        metrics: [],
-        row_limit: limit,
-        order_by_cols: ['["num",+false]'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.wait('@getJson').then(async xhr => {
-        cy.verifyResponseCodes(xhr);
-        cy.verifySliceContainer('table');
-        const responseBody = await readResponseBlob(xhr.response.body);
-        const { records } = responseBody.data;
-        expect(records[0].num).greaterThan(records[records.length - 1].num);
-      });
-    });
-
-    it('Test table with simple filter', () => {
-      const metrics = ['count'];
-      const filters = [SIMPLE_FILTER];
-
-      const formData = { ...VIZ_DEFAULTS, metrics, adhoc_filters: filters };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
-    });
-
-    it('Tests table number formatting with % in metric name', () => {
-      const PERCENT_METRIC = {
-        expressionType: 'SQL',
-        sqlExpression: 'CAST(SUM(sum_girls)+AS+FLOAT)/SUM(num)',
-        column: null,
-        aggregate: null,
-        hasCustomLabel: true,
-        label: '%+Girls',
-        optionName: 'metric_6qwzgc8bh2v_zox7hil1mzs',
-      };
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: PERCENT_METRIC,
-        groupby: ['state'],
-      };
-
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: formData.groupby[0],
-        chartSelector: 'table',
-      });
-      cy.get('td').contains(/\d*%/);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js
new file mode 100644
index 0000000..d9c92d3
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js
@@ -0,0 +1,170 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FORM_DATA_DEFAULTS, NUM_METRIC, SIMPLE_FILTER } from './shared.helper';
+import readResponseBlob from '../../../utils/readResponseBlob';
+
+// Table
+describe('Visualization > Table', () => {
+  const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'table' };
+
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('Test table with adhoc metric', () => {
+    const formData = { ...VIZ_DEFAULTS, metrics: NUM_METRIC };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+      chartSelector: 'table',
+    });
+  });
+
+  it('Test table with groupby', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['name'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: formData.groupby[0],
+      chartSelector: 'table',
+    });
+  });
+
+  it('Test table with percent metrics and groupby', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      percent_metrics: NUM_METRIC,
+      metrics: [],
+      groupby: ['name'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
+  });
+
+  it('Test table with groupby order desc', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['name'],
+      order_desc: true,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
+  });
+
+  it('Test table with groupby and limit', () => {
+    const limit = 10;
+
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: NUM_METRIC,
+      groupby: ['name'],
+      row_limit: limit,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+
+    cy.wait('@getJson').then(async xhr => {
+      cy.verifyResponseCodes(xhr);
+      cy.verifySliceContainer('table');
+      const responseBody = await readResponseBlob(xhr.response.body);
+      expect(responseBody.data.records.length).to.eq(limit);
+    });
+    cy.get('span.label-danger').contains('10 rows');
+  });
+
+  it('Test table with columns and row limit', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      all_columns: ['name'],
+      metrics: [],
+      row_limit: 10,
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
+  });
+
+  it('Test table with columns, ordering, and row limit', () => {
+    const limit = 10;
+
+    const formData = {
+      ...VIZ_DEFAULTS,
+      all_columns: ['name', 'state', 'ds', 'num'],
+      metrics: [],
+      row_limit: limit,
+      order_by_cols: ['["num",+false]'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.wait('@getJson').then(async xhr => {
+      cy.verifyResponseCodes(xhr);
+      cy.verifySliceContainer('table');
+      const responseBody = await readResponseBlob(xhr.response.body);
+      const { records } = responseBody.data;
+      expect(records[0].num).greaterThan(records[records.length - 1].num);
+    });
+  });
+
+  it('Test table with simple filter', () => {
+    const metrics = ['count'];
+    const filters = [SIMPLE_FILTER];
+
+    const formData = { ...VIZ_DEFAULTS, metrics, adhoc_filters: filters };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
+  });
+
+  it('Tests table number formatting with % in metric name', () => {
+    const PERCENT_METRIC = {
+      expressionType: 'SQL',
+      sqlExpression: 'CAST(SUM(sum_girls)+AS+FLOAT)/SUM(num)',
+      column: null,
+      aggregate: null,
+      hasCustomLabel: true,
+      label: 'Girls',
+      optionName: 'metric_6qwzgc8bh2v_zox7hil1mzs',
+    };
+    const formData = {
+      ...VIZ_DEFAULTS,
+      percent_metrics: PERCENT_METRIC,
+      groupby: ['state'],
+    };
+
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: formData.groupby[0],
+      chartSelector: 'table',
+    });
+    cy.get('td').contains(/\d*%/);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
index 6cd4b87..1ccbc1b 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
@@ -18,118 +18,115 @@
  */
 import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
 
-// time table viz
+describe('Visualization > Time TableViz', () => {
+  const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'time_table' };
 
-export default () =>
-  describe('Time Table Viz', () => {
-    const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'time_table' };
-
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
 
-    it('Test time series table multiple metrics last year total', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: [NUM_METRIC, 'count'],
-        column_collection: [
-          {
-            key: '9g4K-B-YL',
-            label: 'Last+Year',
-            colType: 'time',
-            timeLag: '1',
-            comparisonType: 'value',
-          },
-        ],
-        url: '',
-      };
+  it('Test time series table multiple metrics last year total', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: [NUM_METRIC, 'count'],
+      column_collection: [
+        {
+          key: '9g4K-B-YL',
+          label: 'Last+Year',
+          colType: 'time',
+          timeLag: '1',
+          comparisonType: 'value',
+        },
+      ],
+      url: '',
+    };
 
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-      });
-      cy.get('.time-table').within(() => {
-        cy.get('span').contains('Sum(num)');
-        cy.get('span').contains('COUNT(*)');
-      });
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+    });
+    cy.get('.time-table').within(() => {
+      cy.get('span').contains('Sum(num)');
+      cy.get('span').contains('COUNT(*)');
     });
+  });
 
-    it('Test time series table metric and group by last year total', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: [NUM_METRIC],
-        groupby: ['gender'],
-        column_collection: [
-          {
-            key: '9g4K-B-YL',
-            label: 'Last+Year',
-            colType: 'time',
-            timeLag: '1',
-            comparisonType: 'value',
-          },
-        ],
-        url: '',
-      };
+  it('Test time series table metric and group by last year total', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: [NUM_METRIC],
+      groupby: ['gender'],
+      column_collection: [
+        {
+          key: '9g4K-B-YL',
+          label: 'Last+Year',
+          colType: 'time',
+          timeLag: '1',
+          comparisonType: 'value',
+        },
+      ],
+      url: '',
+    };
 
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-      });
-      cy.get('.time-table').within(() => {
-        cy.get('td').contains('boy');
-        cy.get('td').contains('girl');
-      });
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+    });
+    cy.get('.time-table').within(() => {
+      cy.get('td').contains('boy');
+      cy.get('td').contains('girl');
     });
+  });
 
-    it('Test time series various time columns', () => {
-      const formData = {
-        ...VIZ_DEFAULTS,
-        metrics: [NUM_METRIC, 'count'],
-        column_collection: [
-          { key: 'LHHNPhamU', label: 'Current', colType: 'time', timeLag: 0 },
-          {
-            key: '9g4K-B-YL',
-            label: 'Last Year',
-            colType: 'time',
-            timeLag: '1',
-            comparisonType: 'value',
-          },
-          {
-            key: 'JVZXtNu7_',
-            label: 'YoY',
-            colType: 'time',
-            timeLag: 1,
-            comparisonType: 'perc',
-            d3format: '%',
-          },
-          { key: 'tN5Gba36u', label: 'Trend', colType: 'spark' },
-        ],
-        url: '',
-      };
+  it('Test time series various time columns', () => {
+    const formData = {
+      ...VIZ_DEFAULTS,
+      metrics: [NUM_METRIC, 'count'],
+      column_collection: [
+        { key: 'LHHNPhamU', label: 'Current', colType: 'time', timeLag: 0 },
+        {
+          key: '9g4K-B-YL',
+          label: 'Last Year',
+          colType: 'time',
+          timeLag: '1',
+          comparisonType: 'value',
+        },
+        {
+          key: 'JVZXtNu7_',
+          label: 'YoY',
+          colType: 'time',
+          timeLag: 1,
+          comparisonType: 'perc',
+          d3format: '%',
+        },
+        { key: 'tN5Gba36u', label: 'Trend', colType: 'spark' },
+      ],
+      url: '',
+    };
 
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({
-        waitAlias: '@getJson',
-        querySubstring: NUM_METRIC.label,
-      });
-      cy.get('.time-table').within(() => {
-        cy.get('th').contains('Current');
-        cy.get('th').contains('Last Year');
-        cy.get('th').contains('YoY');
-        cy.get('th').contains('Trend');
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({
+      waitAlias: '@getJson',
+      querySubstring: NUM_METRIC.label,
+    });
+    cy.get('.time-table').within(() => {
+      cy.get('th').contains('Current');
+      cy.get('th').contains('Last Year');
+      cy.get('th').contains('YoY');
+      cy.get('th').contains('Trend');
 
-        cy.get('span').contains('%');
-        cy.get('svg')
-          .first()
-          .then(charts => {
-            const firstChart = charts[0];
-            expect(firstChart.clientWidth).greaterThan(0);
-            expect(firstChart.clientHeight).greaterThan(0);
-          });
-      });
+      cy.get('span').contains('%');
+      cy.get('svg')
+        .first()
+        .then(charts => {
+          const firstChart = charts[0];
+          expect(firstChart.clientWidth).greaterThan(0);
+          expect(firstChart.clientHeight).greaterThan(0);
+        });
     });
   });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js
deleted file mode 100644
index c865262..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('Treemap', () => {
-    const TREEMAP_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'treemap',
-      slice_id: 50,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '1960-01-01+:+now',
-      metrics: ['sum__SP_POP_TOTL'],
-      adhoc_filters: [],
-      groupby: ['country_code'],
-      row_limit: 50000,
-      color_scheme: 'bnbColors',
-      treemap_ratio: 1.618033988749895,
-      number_format: '.3s',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work', () => {
-      verify(TREEMAP_FORM_DATA);
-      cy.get('.chart-container svg rect.child').should('have.length', 214);
-    });
-
-    it('should work with multiple groupby', () => {
-      verify({
-        ...TREEMAP_FORM_DATA,
-        groupby: ['region', 'country_code'],
-      });
-      cy.get('.chart-container svg rect.parent').should('have.length', 7);
-      cy.get('.chart-container svg rect.child').should('have.length', 214);
-    });
-
-    it('should work with filter', () => {
-      verify({
-        ...TREEMAP_FORM_DATA,
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'region',
-            operator: '==',
-            comparator: 'South Asia',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
-          },
-        ],
-      });
-      cy.get('.chart-container svg rect.child').should('have.length', 8);
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
new file mode 100644
index 0000000..3fbd7de
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > Treemap', () => {
+  const TREEMAP_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'treemap',
+    slice_id: 50,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: 'No filter',
+    metrics: ['sum__SP_POP_TOTL'],
+    adhoc_filters: [],
+    groupby: ['country_code'],
+    row_limit: 50000,
+    color_scheme: 'bnbColors',
+    treemap_ratio: 1.618033988749895,
+    number_format: '.3s',
+  };
+
+  const level0 = '.chart-container rect[style="fill: rgb(255, 90, 95);"]';
+  const level1 = '.chart-container rect[style="fill: rgb(123, 0, 81);"]';
+  const level2 = '.chart-container rect[style="fill: rgb(0, 122, 135);"]';
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work', () => {
+    verify(TREEMAP_FORM_DATA);
+    cy.get(level0).should('have.length', 1);
+    cy.get(level1).should('have.length', 214);
+  });
+
+  it('should work with multiple groupby', () => {
+    verify({
+      ...TREEMAP_FORM_DATA,
+      groupby: ['region', 'country_code'],
+    });
+    cy.get(level0).should('have.length', 1);
+    cy.get(level1).should('have.length', 7);
+    cy.get(level2).should('have.length', 214);
+  });
+
+  it('should work with filter', () => {
+    verify({
+      ...TREEMAP_FORM_DATA,
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'region',
+          operator: '==',
+          comparator: 'South Asia',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
+        },
+      ],
+    });
+    cy.get(level1).should('have.length', 8);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js
deleted file mode 100644
index 0b60857..0000000
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () =>
-  describe('World Map', () => {
-    const WORLD_MAP_FORM_DATA = {
-      datasource: '2__table',
-      viz_type: 'world_map',
-      slice_id: 45,
-      granularity_sqla: 'year',
-      time_grain_sqla: 'P1D',
-      time_range: '2014-01-01 : 2014-01-02',
-      entity: 'country_code',
-      country_fieldtype: 'cca3',
-      metric: 'sum__SP_RUR_TOTL_ZS',
-      adhoc_filters: [],
-      row_limit: 50000,
-      show_bubbles: true,
-      secondary_metric: 'sum__SP_POP_TOTL',
-      max_bubble_size: '25',
-    };
-
-    function verify(formData) {
-      cy.visitChartByParams(JSON.stringify(formData));
-      cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
-    }
-
-    beforeEach(() => {
-      cy.server();
-      cy.login();
-      cy.route('POST', '/superset/explore_json/**').as('getJson');
-    });
-
-    it('should work with ad-hoc metric', () => {
-      verify(WORLD_MAP_FORM_DATA);
-      cy.get('.bubbles circle.datamaps-bubble').should('have.length', 206);
-    });
-
-    it('should work with simple filter', () => {
-      verify({
-        ...WORLD_MAP_FORM_DATA,
-        metric: 'count',
-        adhoc_filters: [
-          {
-            expressionType: 'SIMPLE',
-            subject: 'region',
-            operator: '==',
-            comparator: 'South Asia',
-            clause: 'WHERE',
-            sqlExpression: null,
-            filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
-          },
-        ],
-      });
-      cy.get('.bubbles circle.datamaps-bubble').should('have.length', 8);
-    });
-
-    it('should hide bubbles when told so', () => {
-      verify({
-        ...WORLD_MAP_FORM_DATA,
-        show_bubbles: false,
-      });
-      cy.get('.slice_container').then(containers => {
-        expect(
-          containers[0].querySelectorAll('.bubbles circle.datamaps-bubble')
-            .length,
-        ).to.equal(0);
-      });
-    });
-  });
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
new file mode 100644
index 0000000..2b44c2e
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('Visualization > World Map', () => {
+  const WORLD_MAP_FORM_DATA = {
+    datasource: '2__table',
+    viz_type: 'world_map',
+    slice_id: 45,
+    granularity_sqla: 'year',
+    time_grain_sqla: 'P1D',
+    time_range: '2014-01-01 : 2014-01-02',
+    entity: 'country_code',
+    country_fieldtype: 'cca3',
+    metric: 'sum__SP_RUR_TOTL_ZS',
+    adhoc_filters: [],
+    row_limit: 50000,
+    show_bubbles: true,
+    secondary_metric: 'sum__SP_POP_TOTL',
+    max_bubble_size: '25',
+  };
+
+  function verify(formData) {
+    cy.visitChartByParams(JSON.stringify(formData));
+    cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
+  }
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+    cy.route('POST', '/superset/explore_json/**').as('getJson');
+  });
+
+  it('should work with ad-hoc metric', () => {
+    verify(WORLD_MAP_FORM_DATA);
+    cy.get('.bubbles circle.datamaps-bubble').should('have.length', 206);
+  });
+
+  it('should work with simple filter', () => {
+    verify({
+      ...WORLD_MAP_FORM_DATA,
+      metric: 'count',
+      adhoc_filters: [
+        {
+          expressionType: 'SIMPLE',
+          subject: 'region',
+          operator: '==',
+          comparator: 'South Asia',
+          clause: 'WHERE',
+          sqlExpression: null,
+          filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l',
+        },
+      ],
+    });
+    cy.get('.bubbles circle.datamaps-bubble').should('have.length', 8);
+  });
+
+  it('should hide bubbles when told so', () => {
+    verify({
+      ...WORLD_MAP_FORM_DATA,
+      show_bubbles: false,
+    });
+    cy.get('.slice_container').then(containers => {
+      expect(
+        containers[0].querySelectorAll('.bubbles circle.datamaps-bubble')
+          .length,
+      ).to.equal(0);
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/index.test.js b/superset-frontend/cypress-base/cypress/integration/sqllab/index.test.js
deleted file mode 100644
index d2273f3..0000000
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/index.test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import queryTests from './query';
-import sourcePanelTests from './sourcePanel';
-import tabsTests from './tabs';
-
-describe('All SqlLab tests', () => {
-  queryTests();
-  sourcePanelTests();
-  tabsTests();
-});
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/query.js b/superset-frontend/cypress-base/cypress/integration/sqllab/query.js
deleted file mode 100644
index 71dfe14..0000000
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/query.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import shortid from 'shortid';
-import { selectResultsTab, assertSQLLabResultsAreEqual } from './sqllab.helper';
-
-export default () => {
-  describe('SqlLab query panel', () => {
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.visit('/superset/sqllab');
-
-      cy.route('POST', '/superset/sql_json/').as('sqlLabQuery');
-    });
-
-    it.skip('supports entering and running a query', () => {
-      // row limit has to be < ~10 for us to be able to determine how many rows
-      // are fetched below (because React _Virtualized_ does not render all rows)
-      const rowLimit = 3;
-
-      cy.get('#brace-editor textarea')
-        .clear({ force: true })
-        .type(
-          `{selectall}{backspace}SELECT ds, gender, name, num FROM main.birth_names LIMIT ${rowLimit}`,
-          { force: true },
-        );
-      cy.get('#js-sql-toolbar button').eq(0).click();
-
-      cy.wait('@sqlLabQuery');
-
-      selectResultsTab()
-        .eq(0) // ensures results tab in case preview tab exists
-        .then(tableNodes => {
-          const [header, bodyWrapper] = tableNodes[0].childNodes;
-          const body = bodyWrapper.childNodes[0];
-          const expectedColCount = header.childNodes.length;
-          const expectedRowCount = body.childNodes.length;
-          expect(expectedColCount).to.equal(4);
-          expect(expectedRowCount).to.equal(rowLimit);
-        });
-    });
-
-    it.skip('successfully saves a query', () => {
-      cy.route('savedqueryviewapi/**').as('getSavedQuery');
-      cy.route('superset/tables/**').as('getTables');
-
-      const query =
-        'SELECT ds, gender, name, num FROM main.birth_names ORDER BY name LIMIT 3';
-      const savedQueryTitle = `CYPRESS TEST QUERY ${shortid.generate()}`;
-
-      // we will assert that the results of the query we save, and the saved query are the same
-      let initialResultsTable = null;
-      let savedQueryResultsTable = null;
-
-      cy.get('#brace-editor textarea')
-        .clear({ force: true })
-        .type(`{selectall}{backspace}${query}`, { force: true })
-        .focus() // focus => blur is required for updating the query that is to be saved
-        .blur();
-
-      // ctrl + r also runs query
-      cy.get('#brace-editor textarea').type('{ctrl}r', { force: true });
-
-      cy.wait('@sqlLabQuery');
-
-      // Save results to check agains below
-      selectResultsTab().then(resultsA => {
-        initialResultsTable = resultsA[0];
-      });
-
-      cy.get('#js-sql-toolbar button')
-        .eq(1) // save query
-        .click();
-
-      // Enter name + save into modal
-      cy.get('.modal-sm input')
-        .clear({ force: true })
-        .type(`{selectall}{backspace}${savedQueryTitle}`, {
-          force: true,
-        });
-
-      cy.get('.modal-sm .modal-body button')
-        .eq(0) // save
-        .click();
-
-      // visit saved queries
-      cy.visit('/sqllab/my_queries/');
-
-      // first row contains most recent link, follow back to SqlLab
-      cy.get('table tr:first-child a[href*="savedQueryId"').click();
-
-      // will timeout without explicitly waiting here
-      cy.wait(['@getSavedQuery', '@getTables']);
-
-      // run the saved query
-      cy.get('#js-sql-toolbar button')
-        .eq(0) // run query
-        .click();
-
-      cy.wait('@sqlLabQuery');
-
-      // assert the results of the saved query match the initial results
-      selectResultsTab().then(resultsB => {
-        savedQueryResultsTable = resultsB[0];
-
-        assertSQLLabResultsAreEqual(
-          initialResultsTable,
-          savedQueryResultsTable,
-        );
-      });
-    });
-  });
-};
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.js b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.js
new file mode 100644
index 0000000..e4061a3
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.js
@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import shortid from 'shortid';
+import { selectResultsTab, assertSQLLabResultsAreEqual } from './sqllab.helper';
+
+describe('SqlLab query panel', () => {
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.visit('/superset/sqllab');
+
+    cy.route('POST', '/superset/sql_json/').as('sqlLabQuery');
+  });
+
+  it.skip('supports entering and running a query', () => {
+    // row limit has to be < ~10 for us to be able to determine how many rows
+    // are fetched below (because React _Virtualized_ does not render all rows)
+    const rowLimit = 3;
+
+    cy.get('#brace-editor textarea')
+      .clear({ force: true })
+      .type(
+        `{selectall}{backspace}SELECT ds, gender, name, num FROM main.birth_names LIMIT ${rowLimit}`,
+        { force: true },
+      );
+    cy.get('#js-sql-toolbar button').eq(0).click();
+
+    cy.wait('@sqlLabQuery');
+
+    selectResultsTab()
+      .eq(0) // ensures results tab in case preview tab exists
+      .then(tableNodes => {
+        const [header, bodyWrapper] = tableNodes[0].childNodes;
+        const body = bodyWrapper.childNodes[0];
+        const expectedColCount = header.childNodes.length;
+        const expectedRowCount = body.childNodes.length;
+        expect(expectedColCount).to.equal(4);
+        expect(expectedRowCount).to.equal(rowLimit);
+      });
+  });
+
+  it.skip('successfully saves a query', () => {
+    cy.route('savedqueryviewapi/**').as('getSavedQuery');
+    cy.route('superset/tables/**').as('getTables');
+
+    const query =
+      'SELECT ds, gender, name, num FROM main.birth_names ORDER BY name LIMIT 3';
+    const savedQueryTitle = `CYPRESS TEST QUERY ${shortid.generate()}`;
+
+    // we will assert that the results of the query we save, and the saved query are the same
+    let initialResultsTable = null;
+    let savedQueryResultsTable = null;
+
+    cy.get('#brace-editor textarea')
+      .clear({ force: true })
+      .type(`{selectall}{backspace}${query}`, { force: true })
+      .focus() // focus => blur is required for updating the query that is to be saved
+      .blur();
+
+    // ctrl + r also runs query
+    cy.get('#brace-editor textarea').type('{ctrl}r', { force: true });
+
+    cy.wait('@sqlLabQuery');
+
+    // Save results to check agains below
+    selectResultsTab().then(resultsA => {
+      initialResultsTable = resultsA[0];
+    });
+
+    cy.get('#js-sql-toolbar button')
+      .eq(1) // save query
+      .click();
+
+    // Enter name + save into modal
+    cy.get('.modal-sm input')
+      .clear({ force: true })
+      .type(`{selectall}{backspace}${savedQueryTitle}`, {
+        force: true,
+      });
+
+    cy.get('.modal-sm .modal-body button')
+      .eq(0) // save
+      .click();
+
+    // visit saved queries
+    cy.visit('/sqllab/my_queries/');
+
+    // first row contains most recent link, follow back to SqlLab
+    cy.get('table tr:first-child a[href*="savedQueryId"').click();
+
+    // will timeout without explicitly waiting here
+    cy.wait(['@getSavedQuery', '@getTables']);
+
+    // run the saved query
+    cy.get('#js-sql-toolbar button')
+      .eq(0) // run query
+      .click();
+
+    cy.wait('@sqlLabQuery');
+
+    // assert the results of the saved query match the initial results
+    selectResultsTab().then(resultsB => {
+      savedQueryResultsTable = resultsB[0];
+
+      assertSQLLabResultsAreEqual(initialResultsTable, savedQueryResultsTable);
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.js b/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.js
new file mode 100644
index 0000000..011930f
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.js
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { selectResultsTab } from './sqllab.helper';
+
+describe('SqlLab datasource panel', () => {
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.visit('/superset/sqllab');
+  });
+
+  // TODO the test bellow is flaky, and has been disabled for the time being
+  // (notice the `it.skip`)
+  it.skip('creates a table preview when a database, schema, and table are selected', () => {
+    cy.route('/superset/table/**').as('tableMetadata');
+
+    // it should have dropdowns to select database, schema, and table
+    cy.get('.sql-toolbar .Select').should('have.length', 3);
+
+    cy.get('.sql-toolbar .table-schema').should('not.exist');
+    cy.get('.SouthPane .tab-content .filterable-table-container').should(
+      'not.exist',
+    );
+
+    cy.get('.sql-toolbar .Select')
+      .eq(0) // database select
+      .within(() => {
+        // note: we have to set force: true because the input is invisible / cypress throws
+        cy.get('input').type('main{enter}', { force: true });
+      });
+
+    cy.get('.sql-toolbar .Select')
+      .eq(1) // schema select
+      .within(() => {
+        cy.get('input').type('main{enter}', { force: true });
+      });
+
+    cy.get('.sql-toolbar .Select')
+      .eq(2) // table select
+      .within(() => {
+        cy.get('input').type('birth_names{enter}', { force: true });
+      });
+
+    cy.wait('@tableMetadata');
+
+    cy.get('.sql-toolbar .table-schema').should('have.length', 1);
+    selectResultsTab().should('have.length', 1);
+
+    // add another table and check for added schema + preview
+    cy.get('.sql-toolbar .Select')
+      .eq(2)
+      .within(() => {
+        cy.get('input').type('logs{enter}', { force: true });
+      });
+
+    cy.wait('@tableMetadata');
+
+    cy.get('.sql-toolbar .table-schema').should('have.length', 2);
+    selectResultsTab().should('have.length', 2);
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.js b/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.js
deleted file mode 100644
index ef977b2..0000000
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { selectResultsTab } from './sqllab.helper';
-
-export default () => {
-  describe('SqlLab datasource panel', () => {
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.visit('/superset/sqllab');
-    });
-
-    // TODO the test bellow is flaky, and has been disabled for the time being
-    // (notice the `it.skip`)
-    it.skip('creates a table preview when a database, schema, and table are selected', () => {
-      cy.route('/superset/table/**').as('tableMetadata');
-
-      // it should have dropdowns to select database, schema, and table
-      cy.get('.sql-toolbar .Select').should('have.length', 3);
-
-      cy.get('.sql-toolbar .table-schema').should('not.exist');
-      cy.get('.SouthPane .tab-content .filterable-table-container').should(
-        'not.exist',
-      );
-
-      cy.get('.sql-toolbar .Select')
-        .eq(0) // database select
-        .within(() => {
-          // note: we have to set force: true because the input is invisible / cypress throws
-          cy.get('input').type('main{enter}', { force: true });
-        });
-
-      cy.get('.sql-toolbar .Select')
-        .eq(1) // schema select
-        .within(() => {
-          cy.get('input').type('main{enter}', { force: true });
-        });
-
-      cy.get('.sql-toolbar .Select')
-        .eq(2) // table select
-        .within(() => {
-          cy.get('input').type('birth_names{enter}', { force: true });
-        });
-
-      cy.wait('@tableMetadata');
-
-      cy.get('.sql-toolbar .table-schema').should('have.length', 1);
-      selectResultsTab().should('have.length', 1);
-
-      // add another table and check for added schema + preview
-      cy.get('.sql-toolbar .Select')
-        .eq(2)
-        .within(() => {
-          cy.get('input').type('logs{enter}', { force: true });
-        });
-
-      cy.wait('@tableMetadata');
-
-      cy.get('.sql-toolbar .table-schema').should('have.length', 2);
-      selectResultsTab().should('have.length', 2);
-    });
-  });
-};
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.js b/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.js
deleted file mode 100644
index f4a1f93..0000000
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-export default () => {
-  describe('SqlLab query tabs', () => {
-    beforeEach(() => {
-      cy.login();
-      cy.server();
-      cy.visit('/superset/sqllab');
-    });
-
-    it('allows you to create a tab', () => {
-      cy.get('#a11y-query-editor-tabs > ul > li').then(tabList => {
-        const initialTabCount = tabList.length;
-
-        // add tab
-        cy.get('#a11y-query-editor-tabs > ul > li').last().click();
-
-        cy.get('#a11y-query-editor-tabs > ul > li').should(
-          'have.length',
-          initialTabCount + 1,
-        );
-      });
-    });
-
-    it('allows you to close a tab', () => {
-      cy.get('#a11y-query-editor-tabs > ul > li').then(tabListA => {
-        const initialTabCount = tabListA.length;
-
-        // open the tab dropdown to remove
-        cy.get('#a11y-query-editor-tabs > ul > li .dropdown-toggle').click();
-
-        // first item is close
-        cy.get('#a11y-query-editor-tabs .close-btn a').click();
-
-        cy.get('#a11y-query-editor-tabs > ul > li').should(
-          'have.length',
-          initialTabCount - 1,
-        );
-      });
-    });
-  });
-};
diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js b/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js
new file mode 100644
index 0000000..43e9706
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+describe('SqlLab query tabs', () => {
+  beforeEach(() => {
+    cy.login();
+    cy.server();
+    cy.visit('/superset/sqllab');
+  });
+
+  it('allows you to create a tab', () => {
+    cy.get('#a11y-query-editor-tabs > ul > li').then(tabList => {
+      const initialTabCount = tabList.length;
+
+      // add tab
+      cy.get('#a11y-query-editor-tabs > ul > li').last().click();
+
+      cy.get('#a11y-query-editor-tabs > ul > li').should(
+        'have.length',
+        initialTabCount + 1,
+      );
+    });
+  });
+
+  it('allows you to close a tab', () => {
+    cy.get('#a11y-query-editor-tabs > ul > li').then(tabListA => {
+      const initialTabCount = tabListA.length;
+
+      // open the tab dropdown to remove
+      cy.get('#a11y-query-editor-tabs > ul > li .dropdown-toggle').click();
+
+      // first item is close
+      cy.get('#a11y-query-editor-tabs .close-btn a').click();
+
+      cy.get('#a11y-query-editor-tabs > ul > li').should(
+        'have.length',
+        initialTabCount - 1,
+      );
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/package-lock.json b/superset-frontend/cypress-base/package-lock.json
index 8f09a65..25a2aa9 100644
--- a/superset-frontend/cypress-base/package-lock.json
+++ b/superset-frontend/cypress-base/package-lock.json
@@ -5,11 +5,11 @@
   "requires": true,
   "dependencies": {
     "@babel/code-frame": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
-      "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz",
+      "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==",
       "requires": {
-        "@babel/highlight": "^7.10.1"
+        "@babel/highlight": "^7.10.3"
       }
     },
     "@babel/core": {
@@ -34,11 +34,11 @@
       }
     },
     "@babel/generator": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.1.tgz",
-      "integrity": "sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.3.tgz",
+      "integrity": "sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA==",
       "requires": {
-        "@babel/types": "^7.10.1",
+        "@babel/types": "^7.10.3",
         "jsesc": "^2.5.1",
         "lodash": "^4.17.13",
         "source-map": "^0.5.0"
@@ -53,21 +53,21 @@
       }
     },
     "@babel/helper-builder-binary-assignment-operator-visitor": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz",
-      "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz",
+      "integrity": "sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A==",
       "requires": {
-        "@babel/helper-explode-assignable-expression": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/helper-explode-assignable-expression": "^7.10.3",
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-builder-react-jsx": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.1.tgz",
-      "integrity": "sha512-KXzzpyWhXgzjXIlJU1ZjIXzUPdej1suE6vzqgImZ/cpAsR/CC8gUcX4EWRmDfWz/cs6HOCPMBIJ3nKoXt3BFuw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.3.tgz",
+      "integrity": "sha512-vkxmuFvmovtqTZknyMGj9+uQAZzz5Z9mrbnkJnPkaYGfKTaSsYcjQdXP0lgrWLVh8wU6bCjOmXOpx+kqUi+S5Q==",
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-builder-react-jsx-experimental": {
@@ -81,14 +81,14 @@
       }
     },
     "@babel/helper-create-class-features-plugin": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.1.tgz",
-      "integrity": "sha512-bwhdehBJZt84HuPUcP1HaTLuc/EywVS8rc3FgsEPDcivg+DCW+SHuLHVkYOmcBA1ZfI+Z/oZjQc/+bPmIO7uAA==",
-      "requires": {
-        "@babel/helper-function-name": "^7.10.1",
-        "@babel/helper-member-expression-to-functions": "^7.10.1",
-        "@babel/helper-optimise-call-expression": "^7.10.1",
-        "@babel/helper-plugin-utils": "^7.10.1",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz",
+      "integrity": "sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ==",
+      "requires": {
+        "@babel/helper-function-name": "^7.10.3",
+        "@babel/helper-member-expression-to-functions": "^7.10.3",
+        "@babel/helper-optimise-call-expression": "^7.10.3",
+        "@babel/helper-plugin-utils": "^7.10.3",
         "@babel/helper-replace-supers": "^7.10.1",
         "@babel/helper-split-export-declaration": "^7.10.1"
       }
@@ -104,64 +104,64 @@
       }
     },
     "@babel/helper-define-map": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz",
-      "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz",
+      "integrity": "sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ==",
       "requires": {
-        "@babel/helper-function-name": "^7.10.1",
-        "@babel/types": "^7.10.1",
+        "@babel/helper-function-name": "^7.10.3",
+        "@babel/types": "^7.10.3",
         "lodash": "^4.17.13"
       }
     },
     "@babel/helper-explode-assignable-expression": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz",
-      "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz",
+      "integrity": "sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw==",
       "requires": {
-        "@babel/traverse": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/traverse": "^7.10.3",
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-function-name": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz",
-      "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz",
+      "integrity": "sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw==",
       "requires": {
-        "@babel/helper-get-function-arity": "^7.10.1",
-        "@babel/template": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/helper-get-function-arity": "^7.10.3",
+        "@babel/template": "^7.10.3",
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-get-function-arity": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
-      "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz",
+      "integrity": "sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg==",
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-hoist-variables": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz",
-      "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz",
+      "integrity": "sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg==",
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-member-expression-to-functions": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
-      "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz",
+      "integrity": "sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw==",
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-module-imports": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz",
-      "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz",
+      "integrity": "sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w==",
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-module-transforms": {
@@ -179,17 +179,17 @@
       }
     },
     "@babel/helper-optimise-call-expression": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
-      "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz",
+      "integrity": "sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg==",
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-plugin-utils": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
-      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA=="
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz",
+      "integrity": "sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g=="
     },
     "@babel/helper-regex": {
       "version": "7.10.1",
@@ -200,15 +200,15 @@
       }
     },
     "@babel/helper-remap-async-to-generator": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz",
-      "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz",
+      "integrity": "sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA==",
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.10.1",
         "@babel/helper-wrap-function": "^7.10.1",
-        "@babel/template": "^7.10.1",
-        "@babel/traverse": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/template": "^7.10.3",
+        "@babel/traverse": "^7.10.3",
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/helper-replace-supers": {
@@ -240,9 +240,9 @@
       }
     },
     "@babel/helper-validator-identifier": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
-      "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw=="
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz",
+      "integrity": "sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw=="
     },
     "@babel/helper-wrap-function": {
       "version": "7.10.1",
@@ -266,27 +266,27 @@
       }
     },
     "@babel/highlight": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
-      "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz",
+      "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==",
       "requires": {
-        "@babel/helper-validator-identifier": "^7.10.1",
+        "@babel/helper-validator-identifier": "^7.10.3",
         "chalk": "^2.0.0",
         "js-tokens": "^4.0.0"
       }
     },
     "@babel/parser": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz",
-      "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg=="
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.3.tgz",
+      "integrity": "sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA=="
     },
     "@babel/plugin-proposal-async-generator-functions": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz",
-      "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz",
+      "integrity": "sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1",
-        "@babel/helper-remap-async-to-generator": "^7.10.1",
+        "@babel/helper-plugin-utils": "^7.10.3",
+        "@babel/helper-remap-async-to-generator": "^7.10.3",
         "@babel/plugin-syntax-async-generators": "^7.8.0"
       }
     },
@@ -411,26 +411,26 @@
       }
     },
     "@babel/plugin-transform-classes": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz",
-      "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz",
+      "integrity": "sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw==",
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.10.1",
-        "@babel/helper-define-map": "^7.10.1",
-        "@babel/helper-function-name": "^7.10.1",
-        "@babel/helper-optimise-call-expression": "^7.10.1",
-        "@babel/helper-plugin-utils": "^7.10.1",
+        "@babel/helper-define-map": "^7.10.3",
+        "@babel/helper-function-name": "^7.10.3",
+        "@babel/helper-optimise-call-expression": "^7.10.3",
+        "@babel/helper-plugin-utils": "^7.10.3",
         "@babel/helper-replace-supers": "^7.10.1",
         "@babel/helper-split-export-declaration": "^7.10.1",
         "globals": "^11.1.0"
       }
     },
     "@babel/plugin-transform-computed-properties": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz",
-      "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz",
+      "integrity": "sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.3"
       }
     },
     "@babel/plugin-transform-destructuring": {
@@ -522,13 +522,13 @@
       }
     },
     "@babel/plugin-transform-modules-systemjs": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz",
-      "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz",
+      "integrity": "sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A==",
       "requires": {
-        "@babel/helper-hoist-variables": "^7.10.1",
+        "@babel/helper-hoist-variables": "^7.10.3",
         "@babel/helper-module-transforms": "^7.10.1",
-        "@babel/helper-plugin-utils": "^7.10.1",
+        "@babel/helper-plugin-utils": "^7.10.3",
         "babel-plugin-dynamic-import-node": "^2.3.3"
       }
     },
@@ -542,9 +542,9 @@
       }
     },
     "@babel/plugin-transform-named-capturing-groups-regex": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
-      "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz",
+      "integrity": "sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA==",
       "requires": {
         "@babel/helper-create-regexp-features-plugin": "^7.8.3"
       }
@@ -584,21 +584,21 @@
       }
     },
     "@babel/plugin-transform-react-display-name": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.1.tgz",
-      "integrity": "sha512-rBjKcVwjk26H3VX8pavMxGf33LNlbocMHdSeldIEswtQ/hrjyTG8fKKILW1cSkODyRovckN/uZlGb2+sAV9JUQ==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.3.tgz",
+      "integrity": "sha512-dOV44bnSW5KZ6kYF6xSHBth7TFiHHZReYXH/JH3XnFNV+soEL1F5d8JT7AJ3ZBncd19Qul7SN4YpBnyWOnQ8KA==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.3"
       }
     },
     "@babel/plugin-transform-react-jsx": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.1.tgz",
-      "integrity": "sha512-MBVworWiSRBap3Vs39eHt+6pJuLUAaK4oxGc8g+wY+vuSJvLiEQjW1LSTqKb8OUPtDvHCkdPhk7d6sjC19xyFw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.3.tgz",
+      "integrity": "sha512-Y21E3rZmWICRJnvbGVmDLDZ8HfNDIwjGF3DXYHx1le0v0mIHCs0Gv5SavyW5Z/jgAHLaAoJPiwt+Dr7/zZKcOQ==",
       "requires": {
-        "@babel/helper-builder-react-jsx": "^7.10.1",
+        "@babel/helper-builder-react-jsx": "^7.10.3",
         "@babel/helper-builder-react-jsx-experimental": "^7.10.1",
-        "@babel/helper-plugin-utils": "^7.10.1",
+        "@babel/helper-plugin-utils": "^7.10.3",
         "@babel/plugin-syntax-jsx": "^7.10.1"
       }
     },
@@ -621,9 +621,9 @@
       }
     },
     "@babel/plugin-transform-regenerator": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz",
-      "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz",
+      "integrity": "sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw==",
       "requires": {
         "regenerator-transform": "^0.14.2"
       }
@@ -673,12 +673,12 @@
       }
     },
     "@babel/plugin-transform-template-literals": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz",
-      "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz",
+      "integrity": "sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA==",
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.10.1",
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.3"
       }
     },
     "@babel/plugin-transform-typeof-symbol": {
@@ -754,11 +754,11 @@
       },
       "dependencies": {
         "@babel/plugin-proposal-object-rest-spread": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz",
-          "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==",
+          "version": "7.10.3",
+          "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz",
+          "integrity": "sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ==",
           "requires": {
-            "@babel/helper-plugin-utils": "^7.10.1",
+            "@babel/helper-plugin-utils": "^7.10.3",
             "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
             "@babel/plugin-transform-parameters": "^7.10.1"
           }
@@ -793,37 +793,37 @@
       }
     },
     "@babel/template": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz",
-      "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.3.tgz",
+      "integrity": "sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA==",
       "requires": {
-        "@babel/code-frame": "^7.10.1",
-        "@babel/parser": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/code-frame": "^7.10.3",
+        "@babel/parser": "^7.10.3",
+        "@babel/types": "^7.10.3"
       }
     },
     "@babel/traverse": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz",
-      "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.3.tgz",
+      "integrity": "sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug==",
       "requires": {
-        "@babel/code-frame": "^7.10.1",
-        "@babel/generator": "^7.10.1",
-        "@babel/helper-function-name": "^7.10.1",
+        "@babel/code-frame": "^7.10.3",
+        "@babel/generator": "^7.10.3",
+        "@babel/helper-function-name": "^7.10.3",
         "@babel/helper-split-export-declaration": "^7.10.1",
-        "@babel/parser": "^7.10.1",
-        "@babel/types": "^7.10.1",
+        "@babel/parser": "^7.10.3",
+        "@babel/types": "^7.10.3",
         "debug": "^4.1.0",
         "globals": "^11.1.0",
         "lodash": "^4.17.13"
       }
     },
     "@babel/types": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
-      "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
+      "version": "7.10.3",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.3.tgz",
+      "integrity": "sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA==",
       "requires": {
-        "@babel/helper-validator-identifier": "^7.10.1",
+        "@babel/helper-validator-identifier": "^7.10.3",
         "lodash": "^4.17.13",
         "to-fast-properties": "^2.0.0"
       }
@@ -872,16 +872,17 @@
       }
     },
     "@cypress/code-coverage": {
-      "version": "3.7.6",
-      "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.7.6.tgz",
-      "integrity": "sha512-uJiI+DmNO8JjoXpXXt0V9nCcVZdb9JfwedQ2tPN8q7g1oyGQ/Cyn52/4GiDwUuny832i8kKhOSxTD/gpDJjy8Q==",
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.8.1.tgz",
+      "integrity": "sha512-XkecqM/4xHZdAPUMOxOUi5yf2TDWUycqIi6Z6zdGiO9j04CxkRoVTOJYsE14i7uG7orudYSLcLFJEeJv237qXQ==",
       "requires": {
         "@cypress/browserify-preprocessor": "3.0.0",
         "debug": "4.1.1",
         "execa": "4.0.2",
         "globby": "11.0.0",
         "istanbul-lib-coverage": "3.0.0",
-        "nyc": "15.0.1"
+        "js-yaml": "3.14.0",
+        "nyc": "15.1.0"
       },
       "dependencies": {
         "cross-spawn": {
@@ -1064,51 +1065,6 @@
         "get-package-type": "^0.1.0",
         "js-yaml": "^3.13.1",
         "resolve-from": "^5.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-          "requires": {
-            "locate-path": "^5.0.0",
-            "path-exists": "^4.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-          "requires": {
-            "p-locate": "^4.1.0"
-          }
-        },
-        "p-limit": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-          "requires": {
-            "p-try": "^2.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-          "requires": {
-            "p-limit": "^2.2.0"
-          }
-        },
-        "p-try": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
-        },
-        "path-exists": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-        }
       }
     },
     "@istanbuljs/schema": {
@@ -1175,9 +1131,9 @@
       }
     },
     "acorn": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
-      "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ=="
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
+      "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA=="
     },
     "acorn-node": {
       "version": "1.8.2",
@@ -1190,9 +1146,9 @@
       }
     },
     "acorn-walk": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
-      "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ=="
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+      "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
     },
     "aggregate-error": {
       "version": "3.0.1",
@@ -1274,9 +1230,9 @@
       }
     },
     "arch": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
-      "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==",
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz",
+      "integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==",
       "dev": true
     },
     "archy": {
@@ -1755,14 +1711,14 @@
       }
     },
     "browserslist": {
-      "version": "4.12.0",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
-      "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+      "version": "4.12.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.1.tgz",
+      "integrity": "sha512-WMjXwFtPskSW1pQUDJRxvRKRkeCr7usN0O/Za76N+F4oadaTdQHotSGcX9jT/Hs7mSKPkyMFNvqawB/1HzYDKQ==",
       "requires": {
-        "caniuse-lite": "^1.0.30001043",
-        "electron-to-chromium": "^1.3.413",
-        "node-releases": "^1.1.53",
-        "pkg-up": "^2.0.0"
+        "caniuse-lite": "^1.0.30001088",
+        "electron-to-chromium": "^1.3.481",
+        "escalade": "^3.0.1",
+        "node-releases": "^1.1.58"
       }
     },
     "buffer": {
@@ -1839,9 +1795,9 @@
       "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
     },
     "caniuse-lite": {
-      "version": "1.0.30001066",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz",
-      "integrity": "sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw=="
+      "version": "1.0.30001088",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001088.tgz",
+      "integrity": "sha512-6eYUrlShRYveyqKG58HcyOfPgh3zb2xqs7NvT2VVtP3hEUeeWvc3lqhpeMTxYWBBeeaT9A4bKsrtjATm66BTHg=="
     },
     "caseless": {
       "version": "0.12.0",
@@ -2136,9 +2092,9 @@
       }
     },
     "commander": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz",
-      "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
       "dev": true
     },
     "common-tags": {
@@ -2290,9 +2246,9 @@
       }
     },
     "cypress": {
-      "version": "4.7.0",
-      "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.7.0.tgz",
-      "integrity": "sha512-Vav6wUFhPRlImIND/2lOQlUnAWzgCC/iXyJlJjX9nJOJul5LC1vUpf/m8Oiae870PFPyT0ZLLwPHKTXZNdXmHw==",
+      "version": "4.9.0",
+      "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.9.0.tgz",
+      "integrity": "sha512-qGxT5E0j21FPryzhb0OBjCdhoR/n1jXtumpFFSBPYWsaZZhNaBvc3XlBUDEZKkkXPsqUFYiyhWdHN/zo0t5FcA==",
       "dev": true,
       "requires": {
         "@cypress/listr-verbose-renderer": "0.4.1",
@@ -2300,29 +2256,29 @@
         "@cypress/xvfb": "1.2.4",
         "@types/sinonjs__fake-timers": "6.0.1",
         "@types/sizzle": "2.3.2",
-        "arch": "2.1.1",
+        "arch": "2.1.2",
         "bluebird": "3.7.2",
         "cachedir": "2.3.0",
         "chalk": "2.4.2",
         "check-more-types": "2.24.0",
         "cli-table3": "0.5.1",
-        "commander": "4.1.0",
+        "commander": "4.1.1",
         "common-tags": "1.8.0",
         "debug": "4.1.1",
-        "eventemitter2": "4.1.2",
+        "eventemitter2": "6.4.2",
         "execa": "1.0.0",
         "executable": "4.1.1",
         "extract-zip": "1.7.0",
         "fs-extra": "8.1.0",
-        "getos": "3.1.4",
+        "getos": "3.2.1",
         "is-ci": "2.0.0",
-        "is-installed-globally": "0.1.0",
+        "is-installed-globally": "0.3.2",
         "lazy-ass": "1.6.0",
         "listr": "0.14.3",
         "lodash": "4.17.15",
         "log-symbols": "3.0.0",
         "minimist": "1.2.5",
-        "moment": "2.24.0",
+        "moment": "2.26.0",
         "ospath": "1.2.2",
         "pretty-bytes": "5.3.0",
         "ramda": "0.26.1",
@@ -2532,9 +2488,9 @@
       }
     },
     "electron-to-chromium": {
-      "version": "1.3.453",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.453.tgz",
-      "integrity": "sha512-IQbCfjJR0NDDn/+vojTlq7fPSREcALtF8M1n01gw7nQghCtfFYrJ2dfhsp8APr8bANoFC8vRTFVXMOGpT0eetw=="
+      "version": "1.3.481",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.481.tgz",
+      "integrity": "sha512-q2PeCP2PQXSYadDo9uNY+uHXjdB9PcsUpCVoGlY8TZOPHGlXdevlqW9PkKeqCxn2QBkGB8b6AcMO++gh8X82bA=="
     },
     "elegant-spinner": {
       "version": "1.0.1",
@@ -2543,9 +2499,9 @@
       "dev": true
     },
     "elliptic": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
-      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+      "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
       "requires": {
         "bn.js": "^4.4.0",
         "brorand": "^1.0.1",
@@ -2581,6 +2537,11 @@
       "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
       "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
     },
+    "escalade": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.1.tgz",
+      "integrity": "sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA=="
+    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -2601,9 +2562,9 @@
       "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
     },
     "eventemitter2": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
-      "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=",
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.2.tgz",
+      "integrity": "sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw==",
       "dev": true
     },
     "events": {
@@ -2815,15 +2776,15 @@
       "dev": true
     },
     "fast-deep-equal": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
-      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
     "fast-glob": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz",
-      "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==",
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
+      "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==",
       "requires": {
         "@nodelib/fs.stat": "^2.0.2",
         "@nodelib/fs.walk": "^1.2.3",
@@ -2957,11 +2918,12 @@
       }
     },
     "find-up": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
-      "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
       "requires": {
-        "locate-path": "^2.0.0"
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
       }
     },
     "for-in": {
@@ -3129,12 +3091,12 @@
       "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
     },
     "getos": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz",
-      "integrity": "sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==",
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
+      "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
       "dev": true,
       "requires": {
-        "async": "^3.1.0"
+        "async": "^3.2.0"
       }
     },
     "getpass": {
@@ -3179,12 +3141,12 @@
       }
     },
     "global-dirs": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
-      "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz",
+      "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==",
       "dev": true,
       "requires": {
-        "ini": "^1.3.4"
+        "ini": "^1.3.5"
       }
     },
     "globals": {
@@ -3381,9 +3343,9 @@
       "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
     },
     "ignore": {
-      "version": "5.1.6",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz",
-      "integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA=="
+      "version": "5.1.8",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+      "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
     },
     "imurmurhash": {
       "version": "0.1.4",
@@ -3549,13 +3511,13 @@
       }
     },
     "is-installed-globally": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
-      "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
+      "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
       "dev": true,
       "requires": {
-        "global-dirs": "^0.1.0",
-        "is-path-inside": "^1.0.0"
+        "global-dirs": "^2.0.1",
+        "is-path-inside": "^3.0.1"
       }
     },
     "is-number": {
@@ -3586,13 +3548,10 @@
       }
     },
     "is-path-inside": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
-      "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
-      "dev": true,
-      "requires": {
-        "path-is-inside": "^1.0.1"
-      }
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
+      "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==",
+      "dev": true
     },
     "is-plain-object": {
       "version": "2.0.4",
@@ -3670,18 +3629,18 @@
       },
       "dependencies": {
         "@babel/core": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.1.tgz",
-          "integrity": "sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg==",
+          "version": "7.10.3",
+          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.3.tgz",
+          "integrity": "sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w==",
           "requires": {
-            "@babel/code-frame": "^7.10.1",
-            "@babel/generator": "^7.10.1",
+            "@babel/code-frame": "^7.10.3",
+            "@babel/generator": "^7.10.3",
             "@babel/helper-module-transforms": "^7.10.1",
             "@babel/helpers": "^7.10.1",
-            "@babel/parser": "^7.10.1",
-            "@babel/template": "^7.10.1",
-            "@babel/traverse": "^7.10.1",
-            "@babel/types": "^7.10.1",
+            "@babel/parser": "^7.10.3",
+            "@babel/template": "^7.10.3",
+            "@babel/traverse": "^7.10.3",
+            "@babel/types": "^7.10.3",
             "convert-source-map": "^1.7.0",
             "debug": "^4.1.0",
             "gensync": "^1.0.0-beta.1",
@@ -4061,12 +4020,11 @@
       }
     },
     "locate-path": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
-      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
       "requires": {
-        "p-locate": "^2.0.0",
-        "path-exists": "^3.0.0"
+        "p-locate": "^4.1.0"
       }
     },
     "lodash": {
@@ -4197,9 +4155,9 @@
       "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
     },
     "merge2": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
-      "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
     },
     "micromatch": {
       "version": "3.1.10",
@@ -4331,9 +4289,9 @@
       }
     },
     "moment": {
-      "version": "2.24.0",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
-      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
+      "version": "2.26.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
+      "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==",
       "dev": true
     },
     "ms": {
@@ -4385,9 +4343,9 @@
       }
     },
     "node-releases": {
-      "version": "1.1.57",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.57.tgz",
-      "integrity": "sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw=="
+      "version": "1.1.58",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz",
+      "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg=="
     },
     "normalize-path": {
       "version": "3.0.0",
@@ -4410,9 +4368,9 @@
       "dev": true
     },
     "nyc": {
-      "version": "15.0.1",
-      "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.1.tgz",
-      "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==",
+      "version": "15.1.0",
+      "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz",
+      "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==",
       "requires": {
         "@istanbuljs/load-nyc-config": "^1.0.0",
         "@istanbuljs/schema": "^0.1.2",
@@ -4422,6 +4380,7 @@
         "find-cache-dir": "^3.2.0",
         "find-up": "^4.1.0",
         "foreground-child": "^2.0.0",
+        "get-package-type": "^0.1.0",
         "glob": "^7.1.6",
         "istanbul-lib-coverage": "^3.0.0",
         "istanbul-lib-hook": "^3.0.0",
@@ -4442,39 +4401,6 @@
         "yargs": "^15.0.2"
       },
       "dependencies": {
-        "find-up": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-          "requires": {
-            "locate-path": "^5.0.0",
-            "path-exists": "^4.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-          "requires": {
-            "p-locate": "^4.1.0"
-          }
-        },
-        "p-limit": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-          "requires": {
-            "p-try": "^2.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-          "requires": {
-            "p-limit": "^2.2.0"
-          }
-        },
         "p-map": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
@@ -4483,16 +4409,6 @@
             "aggregate-error": "^3.0.0"
           }
         },
-        "p-try": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
-        },
-        "path-exists": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-        },
         "rimraf": {
           "version": "3.0.2",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -4614,19 +4530,19 @@
       "dev": true
     },
     "p-limit": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
-      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
       "requires": {
-        "p-try": "^1.0.0"
+        "p-try": "^2.0.0"
       }
     },
     "p-locate": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
-      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
       "requires": {
-        "p-limit": "^1.1.0"
+        "p-limit": "^2.2.0"
       }
     },
     "p-map": {
@@ -4636,9 +4552,9 @@
       "dev": true
     },
     "p-try": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
-      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
     },
     "package-hash": {
       "version": "4.0.0",
@@ -4693,21 +4609,15 @@
       "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
     },
     "path-exists": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
     },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
     },
-    "path-is-inside": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
-      "dev": true
-    },
     "path-key": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
@@ -4730,9 +4640,9 @@
       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
     },
     "pbkdf2": {
-      "version": "3.0.17",
-      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
-      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+      "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
       "requires": {
         "create-hash": "^1.1.2",
         "create-hmac": "^1.1.4",
@@ -4770,59 +4680,6 @@
       "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
       "requires": {
         "find-up": "^4.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-          "requires": {
-            "locate-path": "^5.0.0",
-            "path-exists": "^4.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-          "requires": {
-            "p-locate": "^4.1.0"
-          }
-        },
-        "p-limit": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-          "requires": {
-            "p-try": "^2.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-          "requires": {
-            "p-limit": "^2.2.0"
-          }
-        },
-        "p-try": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
-        },
-        "path-exists": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-        }
-      }
-    },
-    "pkg-up": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
-      "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
-      "requires": {
-        "find-up": "^2.1.0"
       }
     },
     "posix-character-classes": {
@@ -4972,9 +4829,9 @@
       }
     },
     "regenerate": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
-      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
+      "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A=="
     },
     "regenerate-unicode-properties": {
       "version": "8.2.0",
@@ -4999,9 +4856,9 @@
       },
       "dependencies": {
         "@babel/runtime": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.1.tgz",
-          "integrity": "sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA==",
+          "version": "7.10.3",
+          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
+          "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==",
           "requires": {
             "regenerator-runtime": "^0.13.4"
           }
@@ -6061,54 +5918,11 @@
           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
           "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
         },
-        "find-up": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-          "requires": {
-            "locate-path": "^5.0.0",
-            "path-exists": "^4.0.0"
-          }
-        },
         "is-fullwidth-code-point": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
           "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
         },
-        "locate-path": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-          "requires": {
-            "p-locate": "^4.1.0"
-          }
-        },
-        "p-limit": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-          "requires": {
-            "p-try": "^2.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-          "requires": {
-            "p-limit": "^2.2.0"
-          }
-        },
-        "p-try": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
-        },
-        "path-exists": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-        },
         "string-width": {
           "version": "4.2.0",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
diff --git a/superset-frontend/cypress-base/package.json b/superset-frontend/cypress-base/package.json
index 54d3810..d021910 100644
--- a/superset-frontend/cypress-base/package.json
+++ b/superset-frontend/cypress-base/package.json
@@ -10,10 +10,10 @@
   "license": "Apache-2.0",
   "dependencies": {
     "shortid": "^2.2.15",
-    "@cypress/code-coverage": "^3.7.6"
+    "@cypress/code-coverage": "^3.8.1"
   },
   "devDependencies": {
-    "cypress": "4.7.0",
+    "cypress": "4.9.0",
     "eslint-plugin-cypress": "^2.11.1"
   }
 }