You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2022/09/02 16:55:13 UTC

[superset] branch chore/cypress-runtime-enhancements updated (f5a74cdc0d -> 1c4d440794)

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

diegopucci pushed a change to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git


    from f5a74cdc0d Enhance charts list tests
     new b429773cb4 Enhance charts filter test
     new c95faa2ad2 Enhance dashboard load tests
     new 984baf6bf9 Refactor edit/save dashboard tests
     new 3ea671e4c3 Improve cleanup samples data
     new 1c4d440794 Additional sample data management enhancements

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../cypress-base/cypress/fixtures/dashboards.json  |  12 +-
 .../integration/chart_list/card_view.test.ts       | 129 ----------
 .../integration/chart_list/chart_list.helper.ts    |  19 --
 .../chart_list/chartlist.applitools.test.ts        |   2 +-
 .../cypress/integration/chart_list/filter.test.ts  | 168 +++++--------
 .../cypress/integration/chart_list/list.test.ts    |   4 -
 .../cypress/integration/dashboard/controls.test.ts |   6 +-
 .../dashboard/dashboard.applitools.test.ts         |   8 +-
 .../integration/dashboard/dashboard.helper.ts      |  94 -------
 .../integration/dashboard/edit_mode.test.js        |  97 --------
 .../integration/dashboard/edit_properties.test.ts  | 263 --------------------
 .../cypress/integration/dashboard/editsave.test.ts | 274 +++++++++++++++++++++
 .../cypress/integration/dashboard/fav_star.test.js |   3 +-
 .../cypress/integration/dashboard/filter.test.ts   |   6 +-
 .../integration/dashboard/key_value.test.ts        |   6 +-
 .../cypress/integration/dashboard/load.test.ts     |  21 +-
 .../cypress/integration/dashboard/markdown.test.ts |   4 +-
 .../integration/dashboard/nativeFilters.test.ts    |  11 +-
 .../cypress/integration/dashboard/save.test.js     | 163 ------------
 .../integration/dashboard/url_params.test.ts       |   7 +-
 .../cypress/integration/dashboard/utils.ts         |  22 ++
 .../integration/dashboard_list/filter.test.ts      |  11 +-
 .../integration/dashboard_list/list.test.ts        |   4 -
 .../cypress-base/cypress/support/index.d.ts        |   4 +
 .../cypress-base/cypress/support/index.ts          | 222 ++++++++++-------
 .../cypress-base/cypress/utils/index.ts            |  83 +++++++
 .../cypress-base/cypress/utils/urls.ts             |   2 +
 .../src/components/PageHeaderWithActions/index.tsx |   1 +
 .../components/BuilderComponentPane/index.tsx      |   1 +
 .../src/dashboard/components/Header/index.jsx      |   2 +-
 .../dashboard/components/PropertiesModal/index.tsx |   1 +
 31 files changed, 639 insertions(+), 1011 deletions(-)
 delete mode 100644 superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts
 delete mode 100644 superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts
 delete mode 100644 superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
 delete mode 100644 superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
 create mode 100644 superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
 delete mode 100644 superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js


[superset] 01/05: Enhance charts filter test

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git

commit b429773cb4b9e66a5c4699fe0161b29f41dd6436
Author: geido <di...@gmail.com>
AuthorDate: Thu Sep 1 15:33:05 2022 +0300

    Enhance charts filter test
---
 .../integration/chart_list/card_view.test.ts       | 129 ----------------
 .../integration/chart_list/chart_list.helper.ts    |  19 ---
 .../chart_list/chartlist.applitools.test.ts        |   2 +-
 .../cypress/integration/chart_list/filter.test.ts  | 167 +++++++++------------
 .../integration/dashboard/nativeFilters.test.ts    |   2 +-
 .../integration/dashboard_list/filter.test.ts      |  10 +-
 .../cypress-base/cypress/utils/index.ts            |   4 +
 7 files changed, 76 insertions(+), 257 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts
deleted file mode 100644
index 1335fcb422..0000000000
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts
+++ /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 { CHART_LIST } from './chart_list.helper';
-
-describe('chart card view', () => {
-  beforeEach(() => {
-    cy.login();
-    cy.visit(CHART_LIST);
-    cy.get('[aria-label="card-view"]').click();
-  });
-
-  it('should load cards', () => {
-    cy.get('[data-test="chart-list-view"]');
-    cy.get('[data-test="styled-card"]').should('be.visible');
-    cy.get('[data-test="styled-card"]').should('have.length', 25);
-  });
-
-  it('should allow to favorite/unfavorite chart card', () => {
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-selected']")
-      .should('not.exist');
-    cy.get("[data-test='card-actions']")
-      .find("[aria-label='favorite-unselected']")
-      .first()
-      .click();
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-selected']")
-      .should('be.visible');
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-unselected']")
-      .should('not.exist');
-
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-unselected']")
-      .should('not.exist');
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-selected']")
-      .click();
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-unselected']")
-      .should('be.visible');
-    cy.get("[data-test='card-actions']")
-      .first()
-      .find("[aria-label='favorite-selected']")
-      .should('not.exist');
-  });
-
-  xit('should sort correctly', () => {
-    // sort Alphabetical
-    cy.get('.Select__control').last().should('be.visible');
-    cy.get('.Select__control').last().click();
-    cy.get('.Select__menu').contains('Alphabetical').click();
-    cy.get('[data-test="chart-list-view"]').should('be.visible');
-    cy.get('[data-test="styled-card"]').first().contains('% Rural');
-
-    // sort Recently Modified
-    cy.get('.Select__control').last().should('be.visible');
-    cy.get('.Select__control').last().click();
-    cy.get('.Select__menu').contains('Recently Modified').click();
-    cy.get('[data-test="chart-list-view"]').should('be.visible');
-    // TODO - next line is/was flaky
-    cy.get('[data-test="styled-card"]').first().contains('Unicode Cloud');
-    cy.get('[data-test="styled-card"]')
-      .last()
-      .contains('Life Expectancy VS Rural %');
-  });
-
-  // flaky
-  xit('should delete correctly', () => {
-    // show delete modal
-    cy.get('[data-test="more-horiz"]').last().trigger('mouseover');
-    cy.get('[data-test="chart-list-delete-option"]')
-      .last()
-      .should('be.visible');
-    cy.get('[data-test="chart-list-delete-option"]')
-      .last()
-      .contains('Delete')
-      .click();
-    cy.get('[data-test="Please Confirm-modal"]').should('be.visible');
-    cy.get('[data-test="modal-confirm-button"]').should(
-      'have.attr',
-      'disabled',
-    );
-    cy.get('[data-test="Please Confirm-modal"]').should('be.visible');
-    cy.get("[data-test='delete-modal-input']").type('DELETE');
-    cy.get('[data-test="modal-confirm-button"]').should(
-      'not.have.attr',
-      'disabled',
-    );
-    cy.get('[data-test="modal-cancel-button"]').click();
-  });
-
-  // flaky
-  xit('should edit correctly', () => {
-    // show edit modal
-    cy.get('[data-test="more-horiz"]').last().trigger('mouseover');
-    cy.get('[data-test="chart-list-edit-option"]').last().should('be.visible');
-    cy.get('[data-test="chart-list-edit-option"]').last().click();
-    cy.get('[data-test="properties-edit-modal"]').should('be.visible');
-    cy.get('[data-test="properties-modal-name-input"]').should(
-      'not.have.value',
-    );
-    cy.get('[data-test="properties-modal-cancel-button"]')
-      .contains('Cancel')
-      .click();
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts
deleted file mode 100644
index 0d66010cf4..0000000000
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts
+++ /dev/null
@@ -1,19 +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 const CHART_LIST = '/chart/list/';
diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts
index ddbbead4a2..045a8b809a 100644
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { CHART_LIST } from './chart_list.helper';
+ import { CHART_LIST } from 'cypress/utils/urls';
 
 describe('charts list view', () => {
   beforeEach(() => {
diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
index 4466cc2ad5..c5308a87f9 100644
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
@@ -16,119 +16,86 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { CHART_LIST } from './chart_list.helper';
+ import { CHART_LIST } from 'cypress/utils/urls';
+ import { setGridMode, clearAllInputs } from 'cypress/utils';
+ import { setFilter } from '../explore/utils';
 
-describe('chart card view filters', () => {
-  beforeEach(() => {
+ describe('Charts filters', () => {
+  before(() => {
     cy.login();
     cy.visit(CHART_LIST);
-    cy.get('[aria-label="card-view"]').click();
   });
 
-  it('should filter by owners correctly', () => {
-    // filter by owners
-    cy.get('[data-test="filters-select"]').first().click();
-    cy.get('.rc-virtual-list').contains('alpha user').click();
-    cy.get('[data-test="styled-card"]').should('not.exist');
-    cy.get('[data-test="filters-select"]').first().click();
-    cy.get('.rc-virtual-list').contains('gamma user').click();
-    cy.get('[data-test="styled-card"]').should('not.exist');
+  beforeEach(() => {
+    cy.preserveLogin();
+    clearAllInputs();
   });
 
-  it('should filter by created by correctly', () => {
-    // filter by created by
-    cy.get('[data-test="filters-select"]').eq(1).click();
-    cy.get('.rc-virtual-list').contains('alpha user').click();
-    cy.get('.ant-card').should('not.exist');
-    cy.get('[data-test="filters-select"]').eq(1).click();
-    cy.get('.rc-virtual-list').contains('gamma user').click();
-    cy.get('[data-test="styled-card"]').should('not.exist');
-  });
+  describe('card-view', () => {
+    before(() => {
+      setGridMode('card');
+    });
 
-  xit('should filter by viz type correctly', () => {
-    // filter by viz type
-    cy.get('[data-test="filters-select"]').eq(2).click();
-    cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 });
-    cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
-    cy.get('[data-test="styled-card"]')
-      .contains("World's Pop Growth")
-      .should('be.visible');
-    cy.get('[data-test="filters-select"]').eq(2).click();
-    cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}');
-    cy.get('[data-test="styled-card"]').should('have.length', 1);
-    cy.get('[data-test="styled-card"]')
-      .contains('% Rural')
-      .should('be.visible');
-  });
+    it('should filter by owners correctly', () => {
+      setFilter('Owner', 'alpha user');
+      cy.getBySel('styled-card').should('not.exist');
+      setFilter('Owner', 'admin user');
+      cy.getBySel('styled-card').should('have.length', 20);
+    });
 
-  it('should filter by datasource correctly', () => {
-    // filter by datasource
-    cy.get('[data-test="filters-select"]').eq(3).click();
-    cy.get('.rc-virtual-list').contains('unicode_test').click();
-    cy.get('[data-test="styled-card"]').should('have.length', 1);
-    cy.get('[data-test="styled-card"]')
-      .contains('Unicode Cloud')
-      .should('be.visible');
-    cy.get('[data-test="filters-select"]').eq(2).click();
-    cy.get('[data-test="filters-select"]')
-      .eq(2)
-      .type('energy_usage{enter}{enter}');
-    cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
-  });
-});
+    it('should filter by created by correctly', () => {
+      setFilter('Created by', 'alpha user');
+      cy.getBySel('styled-card').should('not.exist');
+      setFilter('Created by', 'admin user');
+      cy.getBySel('styled-card').should('have.length', 20);
+    });
 
-describe('chart list view filters', () => {
-  beforeEach(() => {
-    cy.login();
-    cy.visit(CHART_LIST);
-    cy.get('[aria-label="list-view"]').click();
-  });
+    it('should filter by viz type correctly', () => {
+      setFilter('Chart type', 'Area Chart');
+      cy.getBySel('styled-card').should('have.length', 3);
+      setFilter('Chart type', 'Bubble Chart');
+      cy.getBySel('styled-card').should('have.length', 1);
+    });
 
-  it('should filter by owners correctly', () => {
-    // filter by owners
-    cy.get('[data-test="filters-select"]').first().click();
-    cy.get('.rc-virtual-list').contains('alpha user').click();
-    cy.get('[data-test="table-row"]').should('not.exist');
-    cy.get('[data-test="filters-select"]').first().click();
-    cy.get('.rc-virtual-list').contains('gamma user').click();
-    cy.get('[data-test="table-row"]').should('not.exist');
+    it('should filter by datasource correctly', () => {
+      setFilter('Dataset', 'energy_usage');
+      cy.getBySel('styled-card').should('have.length', 3);
+      setFilter('Dataset', 'unicode_test');
+      cy.getBySel('styled-card').should('have.length', 1);
+    });
   });
 
-  it('should filter by created by correctly', () => {
-    // filter by created by
-    cy.get('[data-test="filters-select"]').eq(1).click();
-    cy.get('.rc-virtual-list').contains('alpha user').click();
-    cy.get('[data-test="table-row"]').should('not.exist');
-    cy.get('[data-test="filters-select"]').eq(1).click();
-    cy.get('.rc-virtual-list').contains('gamma user').click();
-    cy.get('[data-test="table-row"]').should('not.exist');
-  });
+  describe('list-view', () => {
+    before(() => {
+      setGridMode('list');
+    });
 
-  // this is flaky, but seems to fail along with the card view test of the same name
-  xit('should filter by viz type correctly', () => {
-    // filter by viz type
-    cy.get('[data-test="filters-select"]').eq(2).click();
-    cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 });
-    cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
-    cy.get('[data-test="table-row"]')
-      .contains("World's Pop Growth")
-      .should('exist');
-    cy.get('[data-test="filters-select"]').eq(2).click();
-    cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}');
-    cy.get('[data-test="table-row"]').should('have.length', 1);
-    cy.get('[data-test="table-row"]').contains('% Rural').should('exist');
-  });
+    it('should filter by owners correctly', () => {
+      setFilter('Owner', 'alpha user');
+      cy.getBySel('table-row').should('not.exist');
+      setFilter('Owner', 'admin user');
+      cy.getBySel('table-row').should('have.length', 20);
+    });
+
+    it('should filter by created by correctly', () => {
+      setFilter('Created by', 'alpha user');
+      cy.getBySel('table-row').should('not.exist');
+      setFilter('Created by', 'admin user');
+      cy.getBySel('table-row').should('have.length', 20);
+    });
+
+    it('should filter by viz type correctly', () => {
+      setFilter('Chart type', 'Area Chart');
+      cy.getBySel('table-row').should('have.length', 3);
+      setFilter('Chart type', 'Bubble Chart');
+      cy.getBySel('table-row').should('have.length', 1);
+    });
 
-  it('should filter by datasource correctly', () => {
-    // filter by datasource
-    cy.get('[data-test="filters-select"]').eq(3).click();
-    cy.get('.rc-virtual-list').contains('unicode_test').click();
-    cy.get('[data-test="table-row"]').should('have.length', 1);
-    cy.get('[data-test="table-row"]').contains('Unicode Cloud').should('exist');
-    cy.get('[data-test="filters-select"]').eq(3).click();
-    cy.get('[data-test="filters-select"]')
-      .eq(3)
-      .type('energy_usage{enter}{enter}');
-    cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
+    it('should filter by datasource correctly', () => {
+      setFilter('Dataset', 'energy_usage');
+      cy.getBySel('table-row').should('have.length', 3);
+      setFilter('Dataset', 'unicode_test');
+      cy.getBySel('table-row').should('have.length', 1);
+    });
   });
-});
+});
\ No newline at end of file
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index b97694ca09..d63478ca6b 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -57,7 +57,7 @@ import {
   validateFilterNameOnDashboard,
 } from './nativeFilter.helper';
 import { DASHBOARD_LIST } from 'cypress/utils/urls';
-import { CHART_LIST } from '../chart_list/chart_list.helper';
+import { CHART_LIST } from 'cypress/utils/urls';
 
 // TODO: fix flaky init logic and re-enable
 const milliseconds = new Date().getTime();
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
index b2d7cf6e87..c33a8fe948 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
@@ -17,13 +17,9 @@
  * under the License.
  */
 import { DASHBOARD_LIST } from 'cypress/utils/urls';
-import { setGridMode } from 'cypress/utils';
+import { setGridMode, clearAllInputs } from 'cypress/utils';
 import { setFilter } from '../dashboard/utils';
 
-function resetFilters() {
-  cy.get('[aria-label="close-circle"]').click({ multiple: true, force: true });
-}
-
 describe('Dashboards filters', () => {
   before(() => {
     cy.login();
@@ -32,7 +28,7 @@ describe('Dashboards filters', () => {
 
   beforeEach(() => {
     cy.preserveLogin();
-    resetFilters();
+    clearAllInputs();
   });
 
   describe('card-view', () => {
@@ -58,7 +54,7 @@ describe('Dashboards filters', () => {
       setFilter('Status', 'Published');
       cy.getBySel('styled-card').should('have.length', 3);
       setFilter('Status', 'Draft');
-      cy.get('[data-test="styled-card"]').should('have.length', 2);
+      cy.getBySel('styled-card').should('have.length', 2);
     });
   });
 
diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts
index 6c22829345..796f32a765 100644
--- a/superset-frontend/cypress-base/cypress/utils/index.ts
+++ b/superset-frontend/cypress-base/cypress/utils/index.ts
@@ -26,3 +26,7 @@ export function setGridMode(type: 'card' | 'list') {
 export function toggleBulkSelect() {
   cy.getBySel('bulk-select').click();
 }
+
+export function clearAllInputs() {
+    cy.get('[aria-label="close-circle"]').click({ multiple: true, force: true });
+}
\ No newline at end of file


[superset] 04/05: Improve cleanup samples data

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3ea671e4c343e7741cd63df7465a052bec4fa6a3
Author: geido <di...@gmail.com>
AuthorDate: Fri Sep 2 19:10:35 2022 +0300

    Improve cleanup samples data
---
 .../cypress/integration/chart_list/filter.test.ts  |   1 -
 .../cypress/integration/chart_list/list.test.ts    |   4 -
 .../cypress/integration/dashboard/editsave.test.ts |  28 +++---
 .../cypress/integration/dashboard/utils.ts         |   4 +
 .../integration/dashboard_list/filter.test.ts      |   1 -
 .../integration/dashboard_list/list.test.ts        |   4 -
 .../cypress-base/cypress/support/index.d.ts        |   6 +-
 .../cypress-base/cypress/support/index.ts          | 100 ++++++++++++++++-----
 8 files changed, 101 insertions(+), 47 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
index c5308a87f9..0418afd3ff 100644
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts
@@ -22,7 +22,6 @@
 
  describe('Charts filters', () => {
   before(() => {
-    cy.login();
     cy.visit(CHART_LIST);
   });
 
diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts
index 966725e921..e3837445d9 100644
--- a/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts
@@ -44,10 +44,6 @@ function confirmDelete() {
 }
 
 describe('Charts list', () => {
-  before(() => {
-    cy.login();
-  });
-
   beforeEach(() => {
     cy.preserveLogin();
   });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
index f456d9473a..1fd94413c6 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
@@ -19,6 +19,7 @@
 import { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls';
 import { drag } from 'cypress/utils';
 import {
+  interceptGet,
   interceptUpdate,
 } from './utils';
 import * as ace from 'brace';
@@ -64,9 +65,13 @@ function discardChanges() {
 
 function visitEdit() {
   interceptCharts();
+  interceptGet();
+
   cy.visit(SAMPLE_DASHBOARD_1);
+  cy.wait('@get');
   editDashboard();
   cy.wait('@filtering');
+  cy.wait(500);
 }
 
 function selectColorScheme(color: string) {
@@ -98,7 +103,8 @@ function assertMetadata(text: string) {
     });
 }
 function clearAll(input: string) {
-  return cy.get(input).type('{selectall}{backspace}');
+  cy.get(input).type('{selectall}{backspace}');
+  return cy.wait(500);
 }
 
 describe('Dashboard edit', () => {
@@ -133,6 +139,13 @@ describe('Dashboard edit', () => {
       cy.getBySel('dashboard-charts-filter-search-input').clear();
     });
 
+    it('should disable the Save button when undoing', () => {
+      dragChart();
+      cy.getBySel('header-save-button').should('be.enabled');
+      discardChanges();
+      cy.getBySel('header-save-button').should('be.disabled');
+    });
+
   });
 
   describe('Components', () => {
@@ -245,22 +258,11 @@ describe('Dashboard edit', () => {
   });
 
   describe('Save', () => {
-    before(() => {
+    beforeEach(() => {
       cy.createSampleDashboards();
       visitEdit();
     });
 
-    beforeEach(() => {
-      discardChanges();
-    })
-
-    it('should disable saving when undoing', () => {
-      dragChart();
-      cy.getBySel('header-save-button').should('be.enabled');
-      discardChanges();
-      cy.getBySel('header-save-button').should('be.disabled');
-    });
-
     it('should save', () => {
       dragChart();
       cy.getBySel('header-save-button').should('be.enabled');
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
index b4d2dd3100..072a190314 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
@@ -30,6 +30,10 @@
     { name: 'Box plot', viz: 'box_plot' },
   ] as const;
 
+export function interceptGet() {
+cy.intercept('/api/v1/dashboard/*').as('get');
+}
+
 export function interceptFiltering() {
   cy.intercept('GET', `/api/v1/dashboard/?q=*`).as('filtering');
 }
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
index c33a8fe948..8c70ff7a88 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
@@ -22,7 +22,6 @@ import { setFilter } from '../dashboard/utils';
 
 describe('Dashboards filters', () => {
   before(() => {
-    cy.login();
     cy.visit(DASHBOARD_LIST);
   });
 
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts
index adfb0ea13d..da9d7a4bfc 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts
@@ -44,10 +44,6 @@ function confirmDelete() {
 }
 
 describe('Dashboards list', () => {
-  before(() => {
-    cy.login();
-  });
-
   beforeEach(() => {
     cy.preserveLogin();
   });
diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts
index a5011090ee..a7e0f6836a 100644
--- a/superset-frontend/cypress-base/cypress/support/index.d.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.d.ts
@@ -39,8 +39,10 @@ declare namespace Cypress {
     getBySel(selector: string): cy;
     getBySelLike(selector: string): cy;
     getSampleData(): void;
-    cleanCharts(): void;
-    cleanDashboards(): void;
+    cleanCharts(): cy;
+    cleanDashboards(): cy;
+    loadChartFixtures(): cy;
+    loadDashboardFixtures(): cy;
 
     visitChartByParams(params: string | Record<string, unknown>): cy;
     visitChartByName(name: string): cy;
diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts
index 3c24d167c3..6116feef9c 100644
--- a/superset-frontend/cypress-base/cypress/support/index.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -25,37 +25,91 @@ const BASE_EXPLORE_URL = '/explore/?form_data=';
 const TokenName = Cypress.env('TOKEN_NAME');
 let SAMPLE_DASHBOARDS: Record<string, any>[] = [];
 let SAMPLE_CHARTS: Record<string, any>[] = [];
+let DASHBOARD_FIXTURES: Record<string, any>[] = [];
+let CHART_FIXTURES: Record<string, any>[] = [];
 
-Cypress.Commands.add('getSampleData', () =>
-  cy.getCharts().then((slices: any) => {
-    SAMPLE_CHARTS = slices;
-    cy.getDashboards().then((dashboards: any) => {
-      SAMPLE_DASHBOARDS = dashboards;
-    });
+Cypress.Commands.add('loadChartFixtures', () =>
+  cy.fixture('charts.json').then(charts => {
+    CHART_FIXTURES = charts
   })
 );
 
-Cypress.Commands.add('cleanDashboards', () =>
+Cypress.Commands.add('loadDashboardFixtures', () =>
   cy.fixture('dashboards.json').then(dashboards => {
-    for (let i = 0; i < dashboards.length; i += 1) {
-      cy.deleteDashboardByName(dashboards[i].dashboard_title, false)
-    }
-  })
-);
-
-Cypress.Commands.add('cleanCharts', () =>
-  cy.fixture('charts.json').then(charts => {
-    for (let i = 0; i < charts.length; i += 1) {
-      cy.deleteChartByName(charts[i].slice_name, false)
-    }
+    DASHBOARD_FIXTURES = dashboards
   })
 );
 
 before(() => {
   cy.login();
+  cy.loadChartFixtures();
+  cy.loadDashboardFixtures();
+  cy.getSampleData();
   cy.cleanDashboards();
   cy.cleanCharts();
-  cy.getSampleData();
+});
+
+Cypress.Commands.add('getSampleData', () => {
+  cy.getCharts().then((slices: any) => {
+    SAMPLE_CHARTS = slices;
+  });
+  cy.getDashboards().then((dashboards: any) => {
+    SAMPLE_DASHBOARDS = dashboards;
+  });
+});
+
+Cypress.Commands.add('cleanDashboards', () =>
+{
+  const deletableDashboards = [];
+  for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) {
+    const isInDb = SAMPLE_DASHBOARDS.find(d => d.dashboard_title === DASHBOARD_FIXTURES[i].dashboard_title);
+    if (isInDb) {
+      deletableDashboards.push(isInDb.id);
+    }
+  }
+  return cy
+    .request({
+      failOnStatusCode: false,
+      method: 'DELETE',
+      url: `api/v1/dashboard/?q=!(${deletableDashboards.join(',')})`,
+      headers: {
+        Cookie: `csrf_access_token=${window.localStorage.getItem(
+          'access_token',
+        )}`,
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+        Referer: `${Cypress.config().baseUrl}/`,
+      },
+    })
+    .then(resp => resp)
+});
+
+Cypress.Commands.add('cleanCharts', () =>
+{
+  const deletableCharts = [];
+  for (let i = 0; i < CHART_FIXTURES.length; i += 1) {
+    const isInDb = SAMPLE_CHARTS.find(c => c.slice_name === CHART_FIXTURES[i].slice_name);
+    if (isInDb) {
+      deletableCharts.push(isInDb.id);
+    }
+  }
+  return cy
+    .request({
+      failOnStatusCode: false,
+      method: 'DELETE',
+      url: `api/v1/chart/?q=!(${deletableCharts.join(',')})`,
+      headers: {
+        Cookie: `csrf_access_token=${window.localStorage.getItem(
+          'access_token',
+        )}`,
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+        Referer: `${Cypress.config().baseUrl}/`,
+      },
+    })
+    .then(resp => resp)
 });
 
 Cypress.Commands.add('getBySel', (selector, ...args) =>
@@ -197,6 +251,7 @@ Cypress.Commands.add('createSampleDashboards', () => {
           method: 'POST',
           url: `/api/v1/dashboard/`,
           body: dashboards[i],
+          failOnStatusCode: false,
           headers: {
             Cookie: `csrf_access_token=${window.localStorage.getItem(
               'access_token',
@@ -214,8 +269,8 @@ Cypress.Commands.add('createSampleDashboards', () => {
 });
 
 Cypress.Commands.add('createSampleCharts', () => {
-  cy.cleanCharts();
   const requests: any = [];
+  cy.cleanCharts();
   return cy.fixture('charts.json').then(charts => {
     for (let i = 0; i < charts.length; i += 1) {
       requests.push(
@@ -223,6 +278,7 @@ Cypress.Commands.add('createSampleCharts', () => {
           method: 'POST',
           url: `/api/v1/chart/`,
           body: charts[i],
+          failOnStatusCode: false,
           headers: {
             Cookie: `csrf_access_token=${window.localStorage.getItem(
               'access_token',
@@ -251,7 +307,7 @@ Cypress.Commands.add(
   },
 );
 
-Cypress.Commands.add('deleteDashboard', (id: number, failOnStatusCode = true) =>
+Cypress.Commands.add('deleteDashboard', (id: number, failOnStatusCode = false) =>
   cy
     .request({
       failOnStatusCode,
@@ -283,7 +339,7 @@ Cypress.Commands.add('getDashboards', () =>
     .then(resp => resp.body.result),
 );
 
-Cypress.Commands.add('deleteChart', (id: number, failOnStatusCode = true) =>
+Cypress.Commands.add('deleteChart', (id: number, failOnStatusCode = false) =>
   cy
     .request({
       failOnStatusCode,


[superset] 03/05: Refactor edit/save dashboard tests

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 984baf6bf9af0715619e6ad32c46c15ef5b81596
Author: geido <di...@gmail.com>
AuthorDate: Fri Sep 2 14:52:36 2022 +0300

    Refactor edit/save dashboard tests
---
 .../cypress-base/cypress/fixtures/dashboards.json  |  12 +-
 .../integration/dashboard/dashboard.helper.ts      |  24 --
 .../integration/dashboard/edit_mode.test.js        |  98 --------
 .../integration/dashboard/edit_properties.test.ts  | 263 --------------------
 .../cypress/integration/dashboard/editsave.test.ts | 272 +++++++++++++++++++++
 .../cypress/integration/dashboard/markdown.test.ts |   4 +-
 .../integration/dashboard/nativeFilters.test.ts    |   2 +-
 .../cypress/integration/dashboard/save.test.js     | 163 ------------
 .../cypress-base/cypress/support/index.d.ts        |   3 +
 .../cypress-base/cypress/support/index.ts          |  58 ++---
 .../cypress-base/cypress/utils/index.ts            |  23 ++
 .../cypress-base/cypress/utils/urls.ts             |   3 +-
 .../src/components/PageHeaderWithActions/index.tsx |   1 +
 .../components/BuilderComponentPane/index.tsx      |   1 +
 .../src/dashboard/components/Header/index.jsx      |   2 +-
 .../dashboard/components/PropertiesModal/index.tsx |   1 +
 16 files changed, 345 insertions(+), 585 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/fixtures/dashboards.json b/superset-frontend/cypress-base/cypress/fixtures/dashboards.json
index 6972cb0552..68a9984e41 100644
--- a/superset-frontend/cypress-base/cypress/fixtures/dashboards.json
+++ b/superset-frontend/cypress-base/cypress/fixtures/dashboards.json
@@ -1,14 +1,18 @@
 [
     {
-        "dashboard_title": "1 - Sample dashboard"
+        "dashboard_title": "1 - Sample dashboard",
+        "slug": "1-sample-dashboard"
     },
     {
-        "dashboard_title": "2 - Sample dashboard"
+        "dashboard_title": "2 - Sample dashboard",
+        "slug": "2-sample-dashboard"
     },
     {
-        "dashboard_title": "3 - Sample dashboard"
+        "dashboard_title": "3 - Sample dashboard",
+        "slug": "3-sample-dashboard"
     },
     {
-        "dashboard_title": "4 - Sample dashboard"
+        "dashboard_title": "4 - Sample dashboard",
+        "slug": "4-sample-dashboard"
     }
 ]
\ No newline at end of file
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
index 39b7fc40c2..9868eb5b5e 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
@@ -67,30 +67,6 @@ export const ECHARTS_CHARTS = [
   { name: 'Energy Force Layout', viz: 'graph_chart' },
 ] as const;
 
-/**
- * Drag an element and drop it to another element.
- * Usage:
- *    drag(source).to(target);
- */
-export function drag(selector: string, content: string | number | RegExp) {
-  const dataTransfer = { data: {} };
-  return {
-    to(target: string | Cypress.Chainable) {
-      cy.get('.dragdroppable')
-        .contains(selector, content)
-        .trigger('mousedown', { which: 1 })
-        .trigger('dragstart', { dataTransfer })
-        .trigger('drag', {});
-
-      (typeof target === 'string' ? cy.get(target) : target)
-        .trigger('dragover', { dataTransfer })
-        .trigger('drop', { dataTransfer })
-        .trigger('dragend', { dataTransfer })
-        .trigger('mouseup', { which: 1 });
-    },
-  };
-}
-
 export function resize(selector: string) {
   return {
     to(cordX: number, cordY: number) {
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
deleted file mode 100644
index 0f646cce3d..0000000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
+++ /dev/null
@@ -1,98 +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 { drag } from './dashboard.helper';
-import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
-
-describe('Dashboard edit mode', () => {
-  beforeEach(() => {
-    cy.login();
-    cy.visit(WORLD_HEALTH_DASHBOARD);
-    cy.get('.header-with-actions')
-      .find('[aria-label="Edit dashboard"]')
-      .click();
-  });
-
-  it('remove, and add chart flow', () => {
-    // wait for box plot to appear
-    cy.get('[data-test="grid-container"]').find('.box_plot', {
-      timeout: 10000,
-    });
-    const elementsCount = 10;
-
-    cy.get('[data-test="dashboard-component-chart-holder"]')
-      .find('[data-test="dashboard-delete-component-button"]')
-      .last()
-      .then($el => {
-        cy.wrap($el).invoke('show').click();
-        // box plot should be gone
-        cy.get('[data-test="grid-container"]')
-          .find('.box_plot')
-          .should('not.exist');
-      });
-
-    // find box plot is available from list
-    cy.get('[data-test="dashboard-charts-filter-search-input"]').type(
-      'Box plot',
-    );
-    cy.get('[data-test="card-title"]').should('have.length', 1);
-
-    drag('[data-test="card-title"]', 'Box plot').to(
-      '.grid-row.background--transparent:last',
-    );
-
-    // add back to dashboard
-    cy.get('[data-test="grid-container"]')
-      .find('.box_plot')
-      .should('be.visible');
-
-    // should show Save changes button
-    cy.get('[data-test="header-save-button"]').should('be.visible');
-
-    // undo first step and expect deleted item
-    cy.get('[data-test="undo-action"]').click();
-    cy.get('[data-test="grid-container"]')
-      .find('[data-test="chart-container"]')
-      .should('have.length', elementsCount - 1);
-
-    // Box plot chart should be gone
-    cy.get('[data-test="grid-container"]')
-      .find('.box_plot')
-      .should('not.exist');
-
-    // undo second step and expect initial items count
-    cy.get('[data-test="undo-action"]').click();
-    cy.get('[data-test="grid-container"]')
-      .find('[data-test="chart-container"]')
-      .should('have.length', elementsCount);
-    cy.get('[data-test="card-title"]').contains('Box plot', { timeout: 5000 });
-
-    // save changes button should be disabled
-    cy.get('[data-test="header-save-button"]').should('be.disabled');
-
-    // no changes, can switch to view mode
-    cy.get('[data-test="dashboard-edit-actions"]')
-      .find('[data-test="discard-changes-button"]')
-      .should('be.visible')
-      .click();
-    cy.get('.header-with-actions').within(() => {
-      cy.get('[data-test="dashboard-edit-actions"]').should('not.be.visible');
-      cy.get('[aria-label="Edit dashboard"]').should('be.visible');
-    });
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
deleted file mode 100644
index 57839ebc2c..0000000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
+++ /dev/null
@@ -1,263 +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.
- */
-
-// eslint-disable-next-line import/no-extraneous-dependencies
-import * as ace from 'brace';
-import * as shortid from 'shortid';
-import { USA_BIRTH_NAMES_DASHBOARD } from './dashboard.helper';
-
-function selectColorScheme(color: string) {
-  // open color scheme dropdown
-  cy.get('.ant-modal-body')
-    .contains('Color scheme')
-    .parents('.ControlHeader')
-    .next('.ant-select')
-    .click()
-    .then($colorSelect => {
-      // select a new color scheme
-      cy.wrap($colorSelect).find(`[data-test="${color}"]`).click();
-    });
-}
-
-function assertMetadata(text: string) {
-  const regex = new RegExp(text);
-  cy.get('.ant-modal-body')
-    .find('#json_metadata')
-    .should('be.visible')
-    .then(() => {
-      const metadata = cy.$$('#json_metadata')[0];
-
-      // cypress can read this locally, but not in ci
-      // so we have to use the ace module directly to fetch the value
-      expect(ace.edit(metadata).getValue()).to.match(regex);
-    });
-}
-function clear(input: string) {
-  cy.get(input).type('{selectall}{backspace}');
-}
-function type(input: string, text: string) {
-  cy.get(input).type(text, { parseSpecialCharSequences: false });
-}
-
-function openAdvancedProperties() {
-  return cy
-    .get('.ant-modal-body')
-    .contains('Advanced')
-    .should('be.visible')
-    .click();
-}
-
-function openDashboardEditProperties() {
-  // open dashboard properties edit modal
-  cy.get(
-    '.header-with-actions .right-button-panel .ant-dropdown-trigger',
-  ).trigger('click', {
-    force: true,
-  });
-  cy.get('[data-test=header-actions-menu]')
-    .contains('Edit properties')
-    .click({ force: true });
-}
-
-describe('Dashboard edit action', () => {
-  beforeEach(() => {
-    cy.login();
-    cy.visit(USA_BIRTH_NAMES_DASHBOARD);
-    cy.intercept(`/api/v1/dashboard/births`).as('dashboardGet');
-    cy.get('.dashboard-grid', { timeout: 50000 })
-      .should('be.visible') // wait for 50 secs to load dashboard
-      .then(() => {
-        cy.get('.header-with-actions [aria-label="Edit dashboard"]')
-          .should('be.visible')
-          .click();
-        openDashboardEditProperties();
-      });
-  });
-
-  it('should update the title', () => {
-    const dashboardTitle = `Test dashboard [${shortid.generate()}]`;
-
-    // update title
-    cy.get('.ant-modal-body')
-      .should('be.visible')
-      .contains('Title')
-      .get('[data-test="dashboard-title-input"]')
-      .type(`{selectall}{backspace}${dashboardTitle}`);
-
-    // save edit changes
-    cy.get('.ant-modal-footer')
-      .contains('Apply')
-      .click()
-      .then(() => {
-        // assert that modal edit window has closed
-        cy.get('.ant-modal-body').should('not.exist');
-
-        // assert title has been updated
-        cy.get('[data-test="editable-title-input"]').should(
-          'have.value',
-          dashboardTitle,
-        );
-      });
-  });
-  describe('the color picker is changed', () => {
-    describe('the metadata has a color scheme', () => {
-      describe('the advanced tab is open', () => {
-        it('should overwrite the color scheme', () => {
-          openAdvancedProperties();
-          selectColorScheme('d3Category20b');
-          assertMetadata('d3Category20b');
-        });
-      });
-      describe('the advanced tab is not open', () => {
-        it('should overwrite the color scheme', () => {
-          selectColorScheme('bnbColors');
-          openAdvancedProperties();
-          assertMetadata('bnbColors');
-        });
-      });
-    });
-  });
-  describe('a valid colorScheme is entered', () => {
-    it('should save json metadata color change to dropdown', () => {
-      // edit json metadata
-      openAdvancedProperties().then(() => {
-        clear('#json_metadata');
-        type('#json_metadata', '{"color_scheme":"d3Category20"}');
-      });
-
-      // save edit changes
-      cy.get('.ant-modal-footer')
-        .contains('Apply')
-        .click()
-        .then(() => {
-          // assert that modal edit window has closed
-          cy.get('.ant-modal-body').should('not.exist');
-
-          // assert color has been updated
-          openDashboardEditProperties();
-          openAdvancedProperties().then(() => {
-            assertMetadata('d3Category20');
-          });
-          cy.get('.ant-select-selection-item .color-scheme-option').should(
-            'have.attr',
-            'data-test',
-            'd3Category20',
-          );
-        });
-    });
-  });
-  describe('an invalid colorScheme is entered', () => {
-    it('should throw an error', () => {
-      // edit json metadata
-      openAdvancedProperties().then(() => {
-        clear('#json_metadata');
-        type('#json_metadata', '{"color_scheme":"THIS_DOES_NOT_WORK"}');
-      });
-
-      // save edit changes
-      cy.get('.ant-modal-footer')
-        .contains('Apply')
-        .click()
-        .then(() => {
-          // assert that modal edit window has closed
-          cy.get('.ant-modal-body')
-            .contains('A valid color scheme is required')
-            .should('be.visible');
-        });
-
-      cy.on('uncaught:exception', err => {
-        expect(err.message).to.include('something about the error');
-
-        // return false to prevent the error from
-        // failing this test
-        return false;
-      });
-    });
-  });
-  describe.skip('the color scheme affects the chart colors', () => {
-    it('should change the chart colors', () => {
-      openAdvancedProperties().then(() => {
-        clear('#json_metadata');
-        type(
-          '#json_metadata',
-          '{"color_scheme":"lyftColors", "label_colors": {}}',
-        );
-      });
-
-      cy.get('.ant-modal-footer')
-        .contains('Apply')
-        .click()
-        .then(() => {
-          cy.get('.ant-modal-body').should('not.exist');
-          // assert that the chart has changed colors
-          cy.get('.line .nv-legend-symbol')
-            .first()
-            .should('have.css', 'fill', 'rgb(117, 96, 170)');
-        });
-    });
-    it('the label colors should take precedence over the scheme', () => {
-      openAdvancedProperties().then(() => {
-        clear('#json_metadata');
-        type(
-          '#json_metadata',
-          '{"color_scheme":"lyftColors","label_colors":{"Amanda":"red"}}',
-        );
-      });
-
-      cy.get('.ant-modal-footer')
-        .contains('Apply')
-        .click()
-        .then(() => {
-          cy.get('.ant-modal-body').should('not.exist');
-          // assert that the chart has changed colors
-          cy.get('.line .nv-legend-symbol')
-            .first()
-            .should('have.css', 'fill', 'rgb(255, 0, 0)');
-        });
-    });
-    it('the shared label colors and label colors are applied correctly', () => {
-      openAdvancedProperties().then(() => {
-        clear('#json_metadata');
-        type(
-          '#json_metadata',
-          '{"color_scheme":"lyftColors","label_colors":{"Amanda":"red"}}',
-        );
-      });
-
-      cy.get('.ant-modal-footer')
-        .contains('Apply')
-        .click()
-        .then(() => {
-          cy.get('.ant-modal-body').should('not.exist');
-          // assert that the chart has changed colors
-          cy.get('.line .nv-legend-symbol')
-            .first()
-            .should('have.css', 'fill', 'rgb(255, 0, 0)'); // label: amanda
-          cy.get('.line .nv-legend-symbol')
-            .eq(11)
-            .should('have.css', 'fill', 'rgb(234, 11, 140)'); // label: jennifer
-          cy.get('.word_cloud')
-            .first()
-            .find('svg text')
-            .first()
-            .should('have.css', 'fill', 'rgb(234, 11, 140)'); // label: jennifer
-        });
-    });
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
new file mode 100644
index 0000000000..f456d9473a
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/editsave.test.ts
@@ -0,0 +1,272 @@
+/**
+ * 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 { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls';
+import { drag } from 'cypress/utils';
+import {
+  interceptUpdate,
+} from './utils';
+import * as ace from 'brace';
+import {interceptFiltering as interceptCharts} from '../explore/utils';
+
+function editDashboard() {
+  cy.getBySel('edit-dashboard-button').click();
+}
+
+function closeModal() {
+  cy.getBySel('properties-modal-cancel-button').click({force: true});
+}
+
+function openProperties() {
+  cy.get('body')
+  .then($body => {
+    if ($body.find('[data-test="properties-modal-cancel-button"]').length) {
+      closeModal();
+    }
+  });
+  cy.getBySel('actions-trigger').click({ force: true} );
+  cy.getBySel('header-actions-menu').contains('Edit properties').click({ force: true} );
+  cy.wait(500);
+}
+
+function openAdvancedProperties() {
+  return cy
+    .get('.ant-modal-body')
+    .contains('Advanced')
+    .should('be.visible')
+    .click({ force: true });
+}
+
+function dragChart(chart = 'Unicode Cloud') {
+  drag('[data-test="card-title"]', chart).to(
+    '[data-test="grid-content"] [data-test="dragdroppable-object"]',
+  );
+}
+
+function discardChanges() {
+  cy.getBySel('undo-action').click({ force: true });
+}
+
+function visitEdit() {
+  interceptCharts();
+  cy.visit(SAMPLE_DASHBOARD_1);
+  editDashboard();
+  cy.wait('@filtering');
+}
+
+function selectColorScheme(color: string) {
+    cy.get('[data-test="dashboard-edit-properties-form"] [aria-label="Select color scheme"]').first().click();
+    cy.getBySel(color).click();
+}
+
+function applyChanges() {
+  cy.getBySel('properties-modal-apply-button').click();
+}
+
+function saveChanges() {
+  interceptUpdate();
+  cy.getBySel('header-save-button').click();
+  cy.wait('@update');
+}
+
+function assertMetadata(text: string) {
+  const regex = new RegExp(text);
+  cy.get('.ant-modal-body')
+    .find('#json_metadata')
+    .should('be.visible')
+    .then(() => {
+      const metadata = cy.$$('#json_metadata')[0];
+
+      // cypress can read this locally, but not in ci
+      // so we have to use the ace module directly to fetch the value
+      expect(ace.edit(metadata).getValue()).to.match(regex);
+    });
+}
+function clearAll(input: string) {
+  return cy.get(input).type('{selectall}{backspace}');
+}
+
+describe('Dashboard edit', () => {
+  beforeEach(() => {
+    cy.preserveLogin();
+  });
+
+  describe('Edit mode', () => {
+    before(() => {
+      cy.createSampleDashboards();
+      visitEdit();
+    });
+
+    beforeEach(() => {
+      discardChanges();
+    });
+
+    it('should enable edit mode', () => {
+      cy.getBySel('dashboard-builder-sidepane').should('be.visible');
+    });
+
+    it('should edit the title inline', () => {
+      cy.getBySel('editable-title-input').clear().type('Edited title{enter}');
+      cy.getBySel('header-save-button').should('be.enabled');
+    });
+
+    it('should filter charts', () => {
+      interceptCharts();
+      cy.getBySel('dashboard-charts-filter-search-input').type('Unicode');
+      cy.wait('@filtering');
+      cy.getBySel('chart-card').should('have.length', 1).contains('Unicode Cloud');
+      cy.getBySel('dashboard-charts-filter-search-input').clear();
+    });
+
+  });
+
+  describe('Components', () => {
+    before(() => {
+      cy.createSampleDashboards();
+    });
+
+    beforeEach(() => {
+      visitEdit();
+    });
+
+    it('should add charts', () => {
+      dragChart();
+      cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
+    });
+
+    it('should remove added charts', () => {
+      dragChart('% Rural');
+      cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
+      cy.getBySel('dashboard-delete-component-button').click();
+      cy.getBySel('dashboard-component-chart-holder').should('have.length', 0);
+    });
+  });
+
+  describe('Edit properties', () => {
+    before(() => {
+      cy.createSampleDashboards();
+      visitEdit();
+    });
+
+    beforeEach(() => {
+      openProperties();
+    });
+
+    it('should overwrite the color scheme when advanced is closed', () => {
+      selectColorScheme('d3Category20b');
+      openAdvancedProperties();
+      assertMetadata('d3Category20b');
+      applyChanges();
+    });
+
+    it('should overwrite the color scheme when advanced is open', () => {
+      openAdvancedProperties();
+      selectColorScheme('googleCategory10c');
+      assertMetadata('googleCategory10c');
+      applyChanges();
+    });
+
+    it('should accept a valid color scheme', () => {
+      openAdvancedProperties();
+      clearAll('#json_metadata').then(() => {
+        cy.get('#json_metadata').type('{"color_scheme":"lyftColors"}', { parseSpecialCharSequences: false })
+        applyChanges();
+        openProperties();
+        openAdvancedProperties();
+        assertMetadata('lyftColors');
+        applyChanges();
+      })
+
+    });
+
+    it('should not accept an invalid color scheme', () => {
+      openAdvancedProperties();
+      clearAll('#json_metadata').then(() => {
+        cy.get('#json_metadata').type('{"color_scheme":"wrongcolorscheme"}', { parseSpecialCharSequences: false })
+        applyChanges();
+        cy.get('.ant-modal-body')
+          .contains('A valid color scheme is required')
+          .should('be.visible');
+      })
+    });
+
+    it('should edit the title', () => {
+      cy.getBySel('dashboard-title-input').clear().type('Edited title');
+      applyChanges();
+      cy.getBySel('editable-title-input').should('have.value', 'Edited title');
+    });
+  });
+
+  describe('Color schemes', () => {
+    beforeEach(() => {
+      cy.createSampleDashboards();
+      visitEdit();
+    });
+
+    it('should apply a valid color scheme', () => {
+      dragChart('Top 10 California Names Timeseries');
+      openProperties();
+      selectColorScheme('lyftColors');
+      applyChanges();
+      saveChanges();
+      cy.get('.line .nv-legend-symbol')
+        .first()
+        .should('have.css', 'fill', 'rgb(234, 11, 140)');
+    });
+
+    it('label colors should take the precedence', () => {
+      dragChart('Top 10 California Names Timeseries');
+      openProperties();
+      openAdvancedProperties();
+      clearAll('#json_metadata').then(() => {
+        cy.get('#json_metadata').type('{"color_scheme":"lyftColors","label_colors":{"Anthony":"red"}}', { parseSpecialCharSequences: false })
+        applyChanges();
+        saveChanges();
+        cy.get('.line .nv-legend-symbol')
+          .first()
+          .should('have.css', 'fill', 'rgb(255, 0, 0)');
+      });
+    });
+  });
+
+  describe('Save', () => {
+    before(() => {
+      cy.createSampleDashboards();
+      visitEdit();
+    });
+
+    beforeEach(() => {
+      discardChanges();
+    })
+
+    it('should disable saving when undoing', () => {
+      dragChart();
+      cy.getBySel('header-save-button').should('be.enabled');
+      discardChanges();
+      cy.getBySel('header-save-button').should('be.disabled');
+    });
+
+    it('should save', () => {
+      dragChart();
+      cy.getBySel('header-save-button').should('be.enabled');
+      saveChanges();
+      cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
+      cy.getBySel('edit-dashboard-button').should('be.visible');
+    });
+  });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts
index a27382933b..5d7d818508 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { TABBED_DASHBOARD, drag, resize } from './dashboard.helper';
-
+import { TABBED_DASHBOARD, resize } from './dashboard.helper';
+import { drag } from 'cypress/utils';
 describe('Dashboard edit markdown', () => {
   beforeEach(() => {
     cy.login();
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index 024f7b6fa3..8b8c048862 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -553,7 +553,7 @@ xdescribe('Nativefilters', () => {
     cy.get('[data-test="Treemap-list-chart-title"]')
       .should('be.visible', { timeout: 5000 })
       .click();
-    cy.get('[data-test="query-save-button"]').click();
+    cy.get('[data-test="edit-dashboard-button"]').click();
     cy.get('[data-test="save-chart-modal-select-dashboard-form"]')
       .find('input[aria-label="Select a dashboard"]')
       .type(`${dashboard}`, { force: true });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
deleted file mode 100644
index 07be4ef626..0000000000
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
+++ /dev/null
@@ -1,163 +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 {
-  waitForChartLoad,
-} from 'cypress/utils';
-import { WORLD_HEALTH_CHARTS } from './utils';
-import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
-
-function openDashboardEditProperties() {
-  // open dashboard properties edit modal
-  cy.get('.header-with-actions [aria-label="Edit dashboard"]')
-    .should('be.visible')
-    .click();
-  cy.get(
-    '.header-with-actions .right-button-panel .ant-dropdown-trigger',
-  ).trigger('click', {
-    force: true,
-  });
-  cy.get('[data-test=header-actions-menu]')
-    .contains('Edit properties')
-    .click({ force: true });
-}
-
-describe('Dashboard save action', () => {
-  beforeEach(() => {
-    cy.login();
-    cy.visit(WORLD_HEALTH_DASHBOARD);
-    cy.get('#app').then(() => {
-      cy.get('.dashboard-header-container').then(headerContainerElement => {
-        const dashboardId = headerContainerElement.attr('data-test-id');
-
-        cy.intercept('POST', `/superset/copy_dash/${dashboardId}/`).as(
-          'copyRequest',
-        );
-
-        cy.get('[aria-label="more-horiz"]').trigger('click', { force: true });
-        cy.get('[data-test="save-as-menu-item"]').trigger('click', {
-          force: true,
-        });
-        cy.get('[data-test="modal-save-dashboard-button"]').trigger('click', {
-          force: true,
-        });
-      });
-    });
-  });
-
-  // change to what the title should be
-  it('should save as new dashboard', () => {
-    cy.wait('@copyRequest').then(() => {
-      cy.get('[data-test="editable-title"]').then(element => {
-        const dashboardTitle = element.attr('title');
-        expect(dashboardTitle).to.not.equal(`World Bank's Data`);
-      });
-    });
-  });
-
-  it('should save/overwrite dashboard', () => {
-    // should load chart
-    WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
-
-    // remove box_plot chart from dashboard
-    cy.get('[aria-label="Edit dashboard"]').click({ timeout: 5000 });
-    cy.get('[data-test="dashboard-delete-component-button"]')
-      .last()
-      .trigger('mouseenter')
-      .click();
-
-    cy.get('[data-test="grid-container"]')
-      .find('.box_plot')
-      .should('not.exist');
-
-    cy.intercept('PUT', '/api/v1/dashboard/**').as('putDashboardRequest');
-    cy.get('.header-with-actions')
-      .find('[data-test="header-save-button"]')
-      .contains('Save')
-      .click();
-
-    // go back to view mode
-    cy.wait('@putDashboardRequest');
-    cy.get('.header-with-actions')
-      .find('[aria-label="Edit dashboard"]')
-      .click();
-
-    // deleted boxplot should still not exist
-    cy.get('[data-test="grid-container"]')
-      .find('.box_plot', { timeout: 20000 })
-      .should('not.exist');
-  });
-
-  it('should save after edit', () => {
-    cy.get('.dashboard-grid', { timeout: 50000 }) // wait for 50 secs to load dashboard
-      .then(() => {
-        const dashboardTitle = `Test dashboard [${shortid.generate()}]`;
-
-        openDashboardEditProperties();
-
-        // open color scheme dropdown
-        cy.get('.ant-modal-body')
-          .contains('Color scheme')
-          .parents('.ControlHeader')
-          .next('.ant-select')
-          .click()
-          .then(() => {
-            // select a new color scheme
-            cy.get('.ant-modal-body')
-              .find('.ant-select-item-option-active')
-              .first()
-              .click();
-          });
-
-        // remove json metadata
-        cy.get('.ant-modal-body')
-          .contains('Advanced')
-          .click()
-          .then(() => {
-            cy.get('#json_metadata').type('{selectall}{backspace}');
-          });
-
-        // update title
-        cy.get('[data-test="dashboard-title-input"]').type(
-          `{selectall}{backspace}${dashboardTitle}`,
-        );
-
-        // save edit changes
-        cy.get('.ant-modal-footer')
-          .contains('Apply')
-          .click()
-          .then(() => {
-            // assert that modal edit window has closed
-            cy.get('.ant-modal-body').should('not.exist');
-
-            // save dashboard changes
-            cy.get('.header-with-actions').contains('Save').click();
-
-            // assert success flash
-            cy.contains('saved successfully').should('be.visible');
-
-            // assert title has been updated
-            cy.get(
-              '.header-with-actions .title-panel [data-test="editable-title"]',
-            ).should('have.text', dashboardTitle);
-          });
-      });
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts
index fc065563d3..a5011090ee 100644
--- a/superset-frontend/cypress-base/cypress/support/index.d.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.d.ts
@@ -38,6 +38,9 @@ declare namespace Cypress {
 
     getBySel(selector: string): cy;
     getBySelLike(selector: string): cy;
+    getSampleData(): void;
+    cleanCharts(): void;
+    cleanDashboards(): void;
 
     visitChartByParams(params: string | Record<string, unknown>): cy;
     visitChartByName(name: string): cy;
diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts
index 5f201200b5..3c24d167c3 100644
--- a/superset-frontend/cypress-base/cypress/support/index.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -26,36 +26,36 @@ const TokenName = Cypress.env('TOKEN_NAME');
 let SAMPLE_DASHBOARDS: Record<string, any>[] = [];
 let SAMPLE_CHARTS: Record<string, any>[] = [];
 
-function resetSamples() {
-  cy.login();
-  cy.fixture('dashboards.json').then(dashboards => {
-    dashboards.forEach((d: { dashboard_title: string }) => {
-      cy.deleteDashboardByName(d.dashboard_title, false);
-    });
-  });
-  cy.fixture('charts.json').then(charts => {
-    charts.forEach((c: { slice_name: string }) => {
-      cy.deleteChartByName(c.slice_name, false);
-    });
-  });
-}
-
-function loadSampleData() {
-  cy.login();
+Cypress.Commands.add('getSampleData', () =>
   cy.getCharts().then((slices: any) => {
     SAMPLE_CHARTS = slices;
-  });
-  cy.getDashboards().then((dashboards: any) => {
-    SAMPLE_DASHBOARDS = dashboards;
-  });
-}
+    cy.getDashboards().then((dashboards: any) => {
+      SAMPLE_DASHBOARDS = dashboards;
+    });
+  })
+);
 
-before(() => {
-  loadSampleData();
-});
+Cypress.Commands.add('cleanDashboards', () =>
+  cy.fixture('dashboards.json').then(dashboards => {
+    for (let i = 0; i < dashboards.length; i += 1) {
+      cy.deleteDashboardByName(dashboards[i].dashboard_title, false)
+    }
+  })
+);
 
-beforeEach(() => {
-  resetSamples();
+Cypress.Commands.add('cleanCharts', () =>
+  cy.fixture('charts.json').then(charts => {
+    for (let i = 0; i < charts.length; i += 1) {
+      cy.deleteChartByName(charts[i].slice_name, false)
+    }
+  })
+);
+
+before(() => {
+  cy.login();
+  cy.cleanDashboards();
+  cy.cleanCharts();
+  cy.getSampleData();
 });
 
 Cypress.Commands.add('getBySel', (selector, ...args) =>
@@ -189,6 +189,7 @@ Cypress.Commands.add(
 
 Cypress.Commands.add('createSampleDashboards', () => {
   const requests: any = [];
+  cy.cleanDashboards();
   cy.fixture('dashboards.json').then(dashboards => {
     for (let i = 0; i < dashboards.length; i += 1) {
       requests.push(
@@ -208,11 +209,12 @@ Cypress.Commands.add('createSampleDashboards', () => {
         }),
       );
     }
-    return Promise.all(requests).then(() => loadSampleData());
+    return Promise.all(requests).then(() => cy.getSampleData());
   });
 });
 
 Cypress.Commands.add('createSampleCharts', () => {
+  cy.cleanCharts();
   const requests: any = [];
   return cy.fixture('charts.json').then(charts => {
     for (let i = 0; i < charts.length; i += 1) {
@@ -233,7 +235,7 @@ Cypress.Commands.add('createSampleCharts', () => {
         }),
       );
     }
-    return Promise.all(requests).then(() => loadSampleData());
+    return Promise.all(requests).then(() => cy.getSampleData());
   });
 });
 
diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts
index 3bdbb0f122..949817c84a 100644
--- a/superset-frontend/cypress-base/cypress/utils/index.ts
+++ b/superset-frontend/cypress-base/cypress/utils/index.ts
@@ -86,3 +86,26 @@ export function waitForChartLoad(chart: ChartSpec) {
     });
   }
 
+/**
+ * Drag an element and drop it to another element.
+ * Usage:
+ *    drag(source).to(target);
+ */
+ export function drag(selector: string, content: string | number | RegExp) {
+    const dataTransfer = { data: {} };
+    return {
+      to(target: string | Cypress.Chainable) {
+        cy.get('.dragdroppable')
+          .contains(selector, content)
+          .trigger('mousedown', { which: 1, force: true })
+          .trigger('dragstart', { dataTransfer, force: true })
+          .trigger('drag', {force: true});
+
+        (typeof target === 'string' ? cy.get(target) : target)
+          .trigger('dragover', { dataTransfer, force: true })
+          .trigger('drop', { dataTransfer, force: true })
+          .trigger('dragend', { dataTransfer, force: true })
+          .trigger('mouseup', { which: 1, force: true });
+      },
+    };
+  }
\ No newline at end of file
diff --git a/superset-frontend/cypress-base/cypress/utils/urls.ts b/superset-frontend/cypress-base/cypress/utils/urls.ts
index 2f3cc4d56c..40eb51a170 100644
--- a/superset-frontend/cypress-base/cypress/utils/urls.ts
+++ b/superset-frontend/cypress-base/cypress/utils/urls.ts
@@ -19,4 +19,5 @@
 
 export const DASHBOARD_LIST = '/dashboard/list/';
 export const CHART_LIST = '/chart/list/';
-export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
\ No newline at end of file
+export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
+export const SAMPLE_DASHBOARD_1 = '/superset/dashboard/1-sample-dashboard/';
\ No newline at end of file
diff --git a/superset-frontend/src/components/PageHeaderWithActions/index.tsx b/superset-frontend/src/components/PageHeaderWithActions/index.tsx
index e85ccdfc82..19f1ded4a7 100644
--- a/superset-frontend/src/components/PageHeaderWithActions/index.tsx
+++ b/superset-frontend/src/components/PageHeaderWithActions/index.tsx
@@ -152,6 +152,7 @@ export const PageHeaderWithActions = ({
               css={menuTriggerStyles}
               buttonStyle="tertiary"
               aria-label={t('Menu actions trigger')}
+              data-test="actions-trigger"
             >
               <Icons.MoreHoriz
                 iconColor={theme.colors.primary.dark2}
diff --git a/superset-frontend/src/dashboard/components/BuilderComponentPane/index.tsx b/superset-frontend/src/dashboard/components/BuilderComponentPane/index.tsx
index 7a1019a0e2..39e7fd4bc4 100644
--- a/superset-frontend/src/dashboard/components/BuilderComponentPane/index.tsx
+++ b/superset-frontend/src/dashboard/components/BuilderComponentPane/index.tsx
@@ -64,6 +64,7 @@ const BuilderComponentPane: React.FC<BCPProps> = ({
   <DashboardBuilderSidepane
     topOffset={topOffset}
     className="dashboard-builder-sidepane"
+    data-test="dashboard-builder-sidepane"
   >
     <ParentSize>
       {({ height }) => (
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx
index 19b7c21ce2..354ee81bbc 100644
--- a/superset-frontend/src/dashboard/components/Header/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/index.jsx
@@ -618,7 +618,7 @@ class Header extends React.PureComponent {
                     <Button
                       buttonStyle="secondary"
                       onClick={this.toggleEditMode}
-                      data-test="query-save-button"
+                      data-test="edit-dashboard-button"
                       className="action-button"
                       css={editButtonStyle}
                       aria-label={t('Edit dashboard')}
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index ebdc753226..f05b4081a5 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -538,6 +538,7 @@ const PropertiesModal = ({
             {t('Cancel')}
           </Button>
           <Button
+            data-test="properties-modal-apply-button"
             onClick={form.submit}
             buttonSize="small"
             buttonStyle="primary"


[superset] 05/05: Additional sample data management enhancements

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 1c4d440794623d2584f3da770e5e26eba250612b
Author: geido <di...@gmail.com>
AuthorDate: Fri Sep 2 19:54:55 2022 +0300

    Additional sample data management enhancements
---
 .../cypress-base/cypress/support/index.d.ts        |   1 -
 .../cypress-base/cypress/support/index.ts          | 234 ++++++++++-----------
 2 files changed, 110 insertions(+), 125 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts
index a7e0f6836a..b884c1c430 100644
--- a/superset-frontend/cypress-base/cypress/support/index.d.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.d.ts
@@ -38,7 +38,6 @@ declare namespace Cypress {
 
     getBySel(selector: string): cy;
     getBySelLike(selector: string): cy;
-    getSampleData(): void;
     cleanCharts(): cy;
     cleanDashboards(): cy;
     loadChartFixtures(): cy;
diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts
index 6116feef9c..1c374eafc6 100644
--- a/superset-frontend/cypress-base/cypress/support/index.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -23,8 +23,6 @@ require('cy-verify-downloads').addCustomCommand();
 
 const BASE_EXPLORE_URL = '/explore/?form_data=';
 const TokenName = Cypress.env('TOKEN_NAME');
-let SAMPLE_DASHBOARDS: Record<string, any>[] = [];
-let SAMPLE_CHARTS: Record<string, any>[] = [];
 let DASHBOARD_FIXTURES: Record<string, any>[] = [];
 let CHART_FIXTURES: Record<string, any>[] = [];
 
@@ -44,73 +42,69 @@ before(() => {
   cy.login();
   cy.loadChartFixtures();
   cy.loadDashboardFixtures();
-  cy.getSampleData();
   cy.cleanDashboards();
   cy.cleanCharts();
 });
 
-Cypress.Commands.add('getSampleData', () => {
-  cy.getCharts().then((slices: any) => {
-    SAMPLE_CHARTS = slices;
-  });
-  cy.getDashboards().then((dashboards: any) => {
-    SAMPLE_DASHBOARDS = dashboards;
-  });
-});
-
 Cypress.Commands.add('cleanDashboards', () =>
-{
-  const deletableDashboards = [];
-  for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) {
-    const isInDb = SAMPLE_DASHBOARDS.find(d => d.dashboard_title === DASHBOARD_FIXTURES[i].dashboard_title);
-    if (isInDb) {
-      deletableDashboards.push(isInDb.id);
+  cy.getDashboards().then((sampleDashboards: any) => {
+    const deletableDashboards = [];
+    for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) {
+      const isInDb = sampleDashboards.find((d: any) => d.dashboard_title === DASHBOARD_FIXTURES[i].dashboard_title);
+      if (isInDb) {
+        deletableDashboards.push(isInDb.id);
+      }
     }
-  }
-  return cy
-    .request({
-      failOnStatusCode: false,
-      method: 'DELETE',
-      url: `api/v1/dashboard/?q=!(${deletableDashboards.join(',')})`,
-      headers: {
-        Cookie: `csrf_access_token=${window.localStorage.getItem(
-          'access_token',
-        )}`,
-        'Content-Type': 'application/json',
-        Authorization: `Bearer ${TokenName}`,
-        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
-        Referer: `${Cypress.config().baseUrl}/`,
-      },
-    })
-    .then(resp => resp)
-});
+    if (deletableDashboards.length) {
+      return cy
+      .request({
+        failOnStatusCode: false,
+        method: 'DELETE',
+        url: `api/v1/dashboard/?q=!(${deletableDashboards.join(',')})`,
+        headers: {
+          Cookie: `csrf_access_token=${window.localStorage.getItem(
+            'access_token',
+          )}`,
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${TokenName}`,
+          'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+          Referer: `${Cypress.config().baseUrl}/`,
+        },
+      })
+      .then(resp => resp)
+    }
+  })
+);
 
 Cypress.Commands.add('cleanCharts', () =>
-{
-  const deletableCharts = [];
-  for (let i = 0; i < CHART_FIXTURES.length; i += 1) {
-    const isInDb = SAMPLE_CHARTS.find(c => c.slice_name === CHART_FIXTURES[i].slice_name);
-    if (isInDb) {
-      deletableCharts.push(isInDb.id);
+  cy.getCharts().then((sampleCharts: any) => {
+    const deletableCharts = [];
+    for (let i = 0; i < CHART_FIXTURES.length; i += 1) {
+      const isInDb = sampleCharts.find((c: any) => c.slice_name === CHART_FIXTURES[i].slice_name);
+      if (isInDb) {
+        deletableCharts.push(isInDb.id);
+      }
     }
-  }
-  return cy
-    .request({
-      failOnStatusCode: false,
-      method: 'DELETE',
-      url: `api/v1/chart/?q=!(${deletableCharts.join(',')})`,
-      headers: {
-        Cookie: `csrf_access_token=${window.localStorage.getItem(
-          'access_token',
-        )}`,
-        'Content-Type': 'application/json',
-        Authorization: `Bearer ${TokenName}`,
-        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
-        Referer: `${Cypress.config().baseUrl}/`,
-      },
-    })
-    .then(resp => resp)
-});
+    if (deletableCharts) {
+      return cy
+      .request({
+        failOnStatusCode: false,
+        method: 'DELETE',
+        url: `api/v1/chart/?q=!(${deletableCharts.join(',')})`,
+        headers: {
+          Cookie: `csrf_access_token=${window.localStorage.getItem(
+            'access_token',
+          )}`,
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${TokenName}`,
+          'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+          Referer: `${Cypress.config().baseUrl}/`,
+        },
+      })
+      .then(resp => resp)
+    }
+  })
+);
 
 Cypress.Commands.add('getBySel', (selector, ...args) =>
   cy.get(`[data-test=${selector}]`, ...args),
@@ -241,70 +235,61 @@ Cypress.Commands.add(
   },
 );
 
-Cypress.Commands.add('createSampleDashboards', () => {
-  const requests: any = [];
-  cy.cleanDashboards();
-  cy.fixture('dashboards.json').then(dashboards => {
-    for (let i = 0; i < dashboards.length; i += 1) {
-      requests.push(
-        cy.request({
-          method: 'POST',
-          url: `/api/v1/dashboard/`,
-          body: dashboards[i],
-          failOnStatusCode: false,
-          headers: {
-            Cookie: `csrf_access_token=${window.localStorage.getItem(
-              'access_token',
-            )}`,
-            'Content-Type': 'application/json',
-            Authorization: `Bearer ${TokenName}`,
-            'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
-            Referer: `${Cypress.config().baseUrl}/`,
-          },
-        }),
-      );
+Cypress.Commands.add('createSampleDashboards', () =>
+  cy.cleanDashboards().then(() => {
+    for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) {
+      cy.request({
+        method: 'POST',
+        url: `/api/v1/dashboard/`,
+        body: DASHBOARD_FIXTURES[i],
+        failOnStatusCode: false,
+        headers: {
+          Cookie: `csrf_access_token=${window.localStorage.getItem(
+            'access_token',
+          )}`,
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${TokenName}`,
+          'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+          Referer: `${Cypress.config().baseUrl}/`,
+        },
+      })
     }
-    return Promise.all(requests).then(() => cy.getSampleData());
-  });
-});
+  })
+);
 
-Cypress.Commands.add('createSampleCharts', () => {
-  const requests: any = [];
-  cy.cleanCharts();
-  return cy.fixture('charts.json').then(charts => {
-    for (let i = 0; i < charts.length; i += 1) {
-      requests.push(
-        cy.request({
-          method: 'POST',
-          url: `/api/v1/chart/`,
-          body: charts[i],
-          failOnStatusCode: false,
-          headers: {
-            Cookie: `csrf_access_token=${window.localStorage.getItem(
-              'access_token',
-            )}`,
-            'Content-Type': 'application/json',
-            Authorization: `Bearer ${TokenName}`,
-            'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
-            Referer: `${Cypress.config().baseUrl}/`,
-          },
-        }),
-      );
+Cypress.Commands.add('createSampleCharts', () =>
+  cy.cleanCharts().then(() => {
+    for (let i = 0; i < CHART_FIXTURES.length; i += 1) {
+      cy.request({
+        method: 'POST',
+        url: `/api/v1/chart/`,
+        body: CHART_FIXTURES[i],
+        failOnStatusCode: false,
+        headers: {
+          Cookie: `csrf_access_token=${window.localStorage.getItem(
+            'access_token',
+          )}`,
+          'Content-Type': 'application/json',
+          Authorization: `Bearer ${TokenName}`,
+          'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+          Referer: `${Cypress.config().baseUrl}/`,
+        },
+      })
     }
-    return Promise.all(requests).then(() => cy.getSampleData());
-  });
-});
+  })
+);
 
 Cypress.Commands.add(
   'deleteDashboardByName',
-  (dashboardName: string, failOnStatusCode: boolean) => {
-    const dashboard = SAMPLE_DASHBOARDS.find(
-      d => d.dashboard_title === dashboardName,
-    );
-    if (dashboard) {
-      cy.deleteDashboard(dashboard.id, failOnStatusCode);
-    }
-  },
+  (dashboardName: string, failOnStatusCode: boolean) =>
+    cy.getDashboards().then((sampleDashboards: any) => {
+      const dashboard = sampleDashboards.find(
+        (d: any) => d.dashboard_title === dashboardName,
+      );
+      if (dashboard) {
+        cy.deleteDashboard(dashboard.id, failOnStatusCode);
+      }
+    })
 );
 
 Cypress.Commands.add('deleteDashboard', (id: number, failOnStatusCode = false) =>
@@ -373,10 +358,11 @@ Cypress.Commands.add('getCharts', () =>
 
 Cypress.Commands.add(
   'deleteChartByName',
-  (sliceName: string, failOnStatusCode: boolean) => {
-    const chart = SAMPLE_CHARTS.find(c => c.slice_name === sliceName);
-    if (chart) {
-      cy.deleteChart(chart.id, failOnStatusCode);
-    }
-  },
+  (sliceName: string, failOnStatusCode: boolean) =>
+    cy.getCharts().then((sampleCharts: any) => {
+      const chart = sampleCharts.find((c: any) => c.slice_name === sliceName);
+      if (chart) {
+        cy.deleteChart(chart.id, failOnStatusCode);
+      }
+    })
 );


[superset] 02/05: Enhance dashboard load tests

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/cypress-runtime-enhancements
in repository https://gitbox.apache.org/repos/asf/superset.git

commit c95faa2ad2ab4783098e6fab864d0bee5b27821a
Author: geido <di...@gmail.com>
AuthorDate: Thu Sep 1 15:54:37 2022 +0300

    Enhance dashboard load tests
---
 .../cypress/integration/dashboard/controls.test.ts |  6 +-
 .../dashboard/dashboard.applitools.test.ts         |  8 +--
 .../integration/dashboard/dashboard.helper.ts      | 70 ----------------------
 .../integration/dashboard/edit_mode.test.js        |  3 +-
 .../cypress/integration/dashboard/fav_star.test.js |  3 +-
 .../cypress/integration/dashboard/filter.test.ts   |  6 +-
 .../integration/dashboard/key_value.test.ts        |  6 +-
 .../cypress/integration/dashboard/load.test.ts     | 21 ++++---
 .../integration/dashboard/nativeFilters.test.ts    |  7 ++-
 .../cypress/integration/dashboard/save.test.js     |  6 +-
 .../integration/dashboard/url_params.test.ts       |  7 ++-
 .../cypress/integration/dashboard/utils.ts         | 18 ++++++
 .../cypress-base/cypress/utils/index.ts            | 58 +++++++++++++++++-
 .../cypress-base/cypress/utils/urls.ts             |  1 +
 14 files changed, 115 insertions(+), 105 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts
index 961e71bfd6..3c772fdca9 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts
@@ -17,12 +17,12 @@
  * under the License.
  */
 import {
-  WORLD_HEALTH_CHARTS,
-  WORLD_HEALTH_DASHBOARD,
   waitForChartLoad,
   ChartSpec,
   getChartAliasesBySpec,
-} from './dashboard.helper';
+} from 'cypress/utils';
+import { WORLD_HEALTH_CHARTS } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 import { isLegacyResponse } from '../../utils/vizPlugins';
 
 describe('Dashboard top-level controls', () => {
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts
index d492175a5e..a604ada58a 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts
@@ -16,11 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import {
-  waitForChartLoad,
-  WORLD_HEALTH_CHARTS,
-  WORLD_HEALTH_DASHBOARD,
-} from './dashboard.helper';
+import { WORLD_HEALTH_CHARTS } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
+import { waitForChartLoad } from 'cypress/utils';
 
 describe('Dashboard load', () => {
   beforeEach(() => {
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
index 486833c72b..39b7fc40c2 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
@@ -1,4 +1,3 @@
-import { getChartAlias, Slice } from 'cypress/utils/vizPlugins';
 import { dashboardView } from 'cypress/support/directories';
 
 /**
@@ -19,7 +18,6 @@ import { dashboardView } from 'cypress/support/directories';
  * specific language governing permissions and limitations
  * under the License.
  */
-export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
 export const USA_BIRTH_NAMES_DASHBOARD = '/superset/dashboard/births/';
 export const testDashboard = '/superset/dashboard/538/';
 export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/';
@@ -61,19 +59,6 @@ export const testItems = {
 export const CHECK_DASHBOARD_FAVORITE_ENDPOINT =
   '/superset/favstar/Dashboard/*/count';
 
-export const WORLD_HEALTH_CHARTS = [
-  { name: '% Rural', viz: 'world_map' },
-  { name: 'Most Populated Countries', viz: 'table' },
-  { name: 'Region Filter', viz: 'filter_box' },
-  { name: "World's Population", viz: 'big_number' },
-  { name: 'Growth Rate', viz: 'line' },
-  { name: 'Rural Breakdown', viz: 'sunburst' },
-  { name: "World's Pop Growth", viz: 'area' },
-  { name: 'Life Expectancy VS Rural %', viz: 'bubble' },
-  { name: 'Treemap', viz: 'treemap' },
-  { name: 'Box plot', viz: 'box_plot' },
-] as const;
-
 export const ECHARTS_CHARTS = [
   { name: 'Number of Girls', viz: 'big_number_total' },
   { name: 'Participants', viz: 'big_number' },
@@ -82,61 +67,6 @@ export const ECHARTS_CHARTS = [
   { name: 'Energy Force Layout', viz: 'graph_chart' },
 ] as const;
 
-/** Used to specify charts expected by the test suite */
-export interface ChartSpec {
-  name: string;
-  viz: string;
-}
-
-export function getChartGridComponent({ name, viz }: ChartSpec) {
-  return cy
-    .get(`[data-test-chart-name="${name}"]`)
-    .should('have.attr', 'data-test-viz-type', viz);
-}
-
-export function waitForChartLoad(chart: ChartSpec) {
-  return getChartGridComponent(chart).then(gridComponent => {
-    const chartId = gridComponent.attr('data-test-chart-id');
-    // the chart should load in under half a minute
-    return (
-      cy
-        // this id only becomes visible when the chart is loaded
-        .get(`#chart-id-${chartId}`, {
-          timeout: 30000,
-        })
-        .should('be.visible')
-        // return the chart grid component
-        .then(() => gridComponent)
-    );
-  });
-}
-
-const toSlicelike = ($chart: JQuery<HTMLElement>): Slice => ({
-  slice_id: parseInt($chart.attr('data-test-chart-id')!, 10),
-  form_data: {
-    viz_type: $chart.attr('data-test-viz-type')!,
-  },
-});
-
-export function getChartAliasBySpec(chart: ChartSpec) {
-  return getChartGridComponent(chart).then($chart =>
-    cy.wrap(getChartAlias(toSlicelike($chart))),
-  );
-}
-
-export function getChartAliasesBySpec(charts: readonly ChartSpec[]) {
-  const aliases: string[] = [];
-  charts.forEach(chart =>
-    getChartAliasBySpec(chart).then(alias => {
-      aliases.push(alias);
-    }),
-  );
-  // Wrapping the aliases is key.
-  // That way callers can chain off this function
-  // and actually get the list of aliases.
-  return cy.wrap(aliases);
-}
-
 /**
  * Drag an element and drop it to another element.
  * Usage:
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
index 10b8a4a40d..0f646cce3d 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js
@@ -16,7 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { WORLD_HEALTH_DASHBOARD, drag } from './dashboard.helper';
+import { drag } from './dashboard.helper';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 
 describe('Dashboard edit mode', () => {
   beforeEach(() => {
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
index a20b1eb3f5..9b527a4c7a 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js
@@ -17,10 +17,11 @@
  * under the License.
  */
 import {
-  WORLD_HEALTH_DASHBOARD,
   CHECK_DASHBOARD_FAVORITE_ENDPOINT,
 } from './dashboard.helper';
 
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
+
 describe('Dashboard add to favorite', () => {
   let isFavoriteDashboard = false;
 
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
index e1dd45cf3c..389c181cba 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
@@ -18,11 +18,11 @@
  */
 import { isLegacyResponse, parsePostForm } from 'cypress/utils';
 import {
-  WORLD_HEALTH_CHARTS,
-  WORLD_HEALTH_DASHBOARD,
   getChartAliasesBySpec,
   waitForChartLoad,
-} from './dashboard.helper';
+} from 'cypress/utils';
+import {WORLD_HEALTH_CHARTS } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 
 describe('Dashboard filter', () => {
   before(() => {
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts
index 1738ea5bd3..42ac6cf7c7 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts
@@ -18,10 +18,10 @@
  */
 import qs from 'querystringify';
 import {
-  WORLD_HEALTH_DASHBOARD,
-  WORLD_HEALTH_CHARTS,
   waitForChartLoad,
-} from './dashboard.helper';
+} from 'cypress/utils';
+import {WORLD_HEALTH_CHARTS} from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 
 interface QueryString {
   native_filters_key: string;
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
index 9fb84f70c9..f1c374b158 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
@@ -16,17 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import {
-  waitForChartLoad,
-  WORLD_HEALTH_CHARTS,
-  WORLD_HEALTH_DASHBOARD,
-} from './dashboard.helper';
+import { WORLD_HEALTH_CHARTS, interceptLog } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
+import { waitForChartLoad } from 'cypress/utils';
 
 describe('Dashboard load', () => {
-  beforeEach(() => {
+  before(() => {
     cy.login();
   });
 
+  beforeEach(() => {
+    cy.preserveLogin();
+  });
+
   it('should load dashboard', () => {
     cy.visit(WORLD_HEALTH_DASHBOARD);
     WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
@@ -34,7 +36,7 @@ describe('Dashboard load', () => {
 
   it('should load in edit mode', () => {
     cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`);
-    cy.get('[data-test="discard-changes-button"]').should('be.visible');
+    cy.getBySel('discard-changes-button').should('be.visible');
   });
 
   it('should load in standalone mode', () => {
@@ -44,12 +46,13 @@ describe('Dashboard load', () => {
 
   it('should load in edit/standalone mode', () => {
     cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`);
-    cy.get('[data-test="discard-changes-button"]').should('be.visible');
+    cy.getBySel('discard-changes-button').should('be.visible');
     cy.get('#app-menu').should('not.exist');
   });
 
   it('should send log data', () => {
+    interceptLog();
     cy.visit(WORLD_HEALTH_DASHBOARD);
-    cy.intercept('/superset/log/?explode=events&dashboard_id=*');
+    cy.wait('@logs');
   });
 });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index d63478ca6b..024f7b6fa3 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -27,10 +27,11 @@ import {
   cleanUp,
   copyTestDashboard,
   testItems,
-  waitForChartLoad,
-  WORLD_HEALTH_DASHBOARD,
-  WORLD_HEALTH_CHARTS,
 } from './dashboard.helper';
+import { waitForChartLoad } from 'cypress/utils';
+import {WORLD_HEALTH_CHARTS} from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
+
 import {
   addCountryCodeFilter,
   addCountryNameFilter,
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
index 419346cf98..07be4ef626 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
@@ -20,9 +20,9 @@
 import shortid from 'shortid';
 import {
   waitForChartLoad,
-  WORLD_HEALTH_CHARTS,
-  WORLD_HEALTH_DASHBOARD,
-} from './dashboard.helper';
+} from 'cypress/utils';
+import { WORLD_HEALTH_CHARTS } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 
 function openDashboardEditProperties() {
   // open dashboard properties edit modal
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts
index 5f9ad7382e..c5b4c6655f 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts
@@ -18,10 +18,11 @@
  */
 import { parsePostForm, JsonObject } from 'cypress/utils';
 import {
-  WORLD_HEALTH_DASHBOARD,
-  WORLD_HEALTH_CHARTS,
   waitForChartLoad,
-} from './dashboard.helper';
+} from 'cypress/utils';
+
+import { WORLD_HEALTH_CHARTS } from './utils';
+import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls';
 
 describe('Dashboard form data', () => {
   const urlParams = { param1: '123', param2: 'abc' };
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
index 645c24dff1..b4d2dd3100 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts
@@ -17,6 +17,19 @@
  * under the License.
  */
 
+ export const WORLD_HEALTH_CHARTS = [
+    { name: '% Rural', viz: 'world_map' },
+    { name: 'Most Populated Countries', viz: 'table' },
+    { name: 'Region Filter', viz: 'filter_box' },
+    { name: "World's Population", viz: 'big_number' },
+    { name: 'Growth Rate', viz: 'line' },
+    { name: 'Rural Breakdown', viz: 'sunburst' },
+    { name: "World's Pop Growth", viz: 'area' },
+    { name: 'Life Expectancy VS Rural %', viz: 'bubble' },
+    { name: 'Treemap', viz: 'treemap' },
+    { name: 'Box plot', viz: 'box_plot' },
+  ] as const;
+
 export function interceptFiltering() {
   cy.intercept('GET', `/api/v1/dashboard/?q=*`).as('filtering');
 }
@@ -37,6 +50,10 @@ export function interceptPost() {
   cy.intercept('POST', `/api/v1/dashboard/`).as('post');
 }
 
+export function interceptLog() {
+  cy.intercept('/superset/log/?explode=events&dashboard_id=*').as('logs');
+}
+
 export function setFilter(filter: string, option: string) {
   interceptFiltering();
 
@@ -45,3 +62,4 @@ export function setFilter(filter: string, option: string) {
 
   cy.wait('@filtering');
 }
+
diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts
index 796f32a765..3bdbb0f122 100644
--- a/superset-frontend/cypress-base/cypress/utils/index.ts
+++ b/superset-frontend/cypress-base/cypress/utils/index.ts
@@ -16,8 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { getChartAlias, Slice } from 'cypress/utils/vizPlugins';
+
 export * from './vizPlugins';
 export { default as parsePostForm } from './parsePostForm';
+export interface ChartSpec {
+    name: string;
+    viz: string;
+  }
 
 export function setGridMode(type: 'card' | 'list') {
   cy.get(`[aria-label="${type}-view"]`).click();
@@ -29,4 +35,54 @@ export function toggleBulkSelect() {
 
 export function clearAllInputs() {
     cy.get('[aria-label="close-circle"]').click({ multiple: true, force: true });
-}
\ No newline at end of file
+}
+
+  const toSlicelike = ($chart: JQuery<HTMLElement>): Slice => ({
+    slice_id: parseInt($chart.attr('data-test-chart-id')!, 10),
+    form_data: {
+      viz_type: $chart.attr('data-test-viz-type')!,
+    },
+  });
+
+  export function getChartAliasBySpec(chart: ChartSpec) {
+    return getChartGridComponent(chart).then($chart =>
+      cy.wrap(getChartAlias(toSlicelike($chart))),
+    );
+  }
+
+  export function getChartAliasesBySpec(charts: readonly ChartSpec[]) {
+    const aliases: string[] = [];
+    charts.forEach(chart =>
+      getChartAliasBySpec(chart).then(alias => {
+        aliases.push(alias);
+      }),
+    );
+    // Wrapping the aliases is key.
+    // That way callers can chain off this function
+    // and actually get the list of aliases.
+    return cy.wrap(aliases);
+  }
+
+export function getChartGridComponent({ name, viz }: ChartSpec) {
+    return cy
+      .get(`[data-test-chart-name="${name}"]`)
+      .should('have.attr', 'data-test-viz-type', viz);
+  }
+
+export function waitForChartLoad(chart: ChartSpec) {
+    return getChartGridComponent(chart).then(gridComponent => {
+      const chartId = gridComponent.attr('data-test-chart-id');
+      // the chart should load in under half a minute
+      return (
+        cy
+          // this id only becomes visible when the chart is loaded
+          .get(`#chart-id-${chartId}`, {
+            timeout: 30000,
+          })
+          .should('be.visible')
+          // return the chart grid component
+          .then(() => gridComponent)
+      );
+    });
+  }
+
diff --git a/superset-frontend/cypress-base/cypress/utils/urls.ts b/superset-frontend/cypress-base/cypress/utils/urls.ts
index 124046b826..2f3cc4d56c 100644
--- a/superset-frontend/cypress-base/cypress/utils/urls.ts
+++ b/superset-frontend/cypress-base/cypress/utils/urls.ts
@@ -19,3 +19,4 @@
 
 export const DASHBOARD_LIST = '/dashboard/list/';
 export const CHART_LIST = '/chart/list/';
+export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
\ No newline at end of file