From 2a75e4c3c37ec6dddab5ecdec8908a0f18ed391d Mon Sep 17 00:00:00 2001 From: Jinghuayao <81597121+jinghua-qa@users.noreply.github.com> Date: Fri, 1 Apr 2022 01:06:48 -0700 Subject: [PATCH] test(native filter): add new test for dependent filter (#19392) * add new test for dependent filter --- docker/docker-init.sh | 2 +- .../integration/dashboard/dashboard.helper.ts | 120 ++++++++++++++++++ .../dashboard/nativeFilters.test.ts | 97 ++++++++++---- .../cypress-base/cypress/support/index.d.ts | 14 ++ .../cypress-base/cypress/support/index.ts | 86 +++++++++++++ superset-frontend/package-lock.json | 2 +- 6 files changed, 292 insertions(+), 29 deletions(-) diff --git a/docker/docker-init.sh b/docker/docker-init.sh index 0783069404..c98f49881a 100755 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -41,7 +41,7 @@ ADMIN_PASSWORD="admin" # If Cypress run – overwrite the password for admin and export env variables if [ "$CYPRESS_CONFIG" == "true" ]; then ADMIN_PASSWORD="general" - export SUPERSET_CONFIG=tests.superset_test_config + export SUPERSET_CONFIG=tests.integration_tests.superset_test_config export SUPERSET_TESTENV=true export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset fi 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 1458fc7d59..9822f70359 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,9 @@ import { getChartAlias, Slice } from 'cypress/utils/vizPlugins'; +import { + dashboardView, + editDashboardView, + nativeFilters, +} from 'cypress/support/directories'; /** * Licensed to the Apache Software Foundation (ASF) under one @@ -25,7 +30,13 @@ export const testItems = { dashboard: 'Cypress Sales Dashboard', dataset: 'Vehicle Sales', chart: 'Cypress chart', + newChart: 'New Cypress Chart', + createdDashboard: 'New Dashboard', defaultNameDashboard: '[ untitled dashboard ]', + newDashboardTitle: `Test dashboard [NEW TEST]`, + bulkFirstNameDashboard: 'First Dash', + bulkSecondNameDashboard: 'Second Dash', + worldBanksDataCopy: `World Bank's Data [copy]`, }; export const CHECK_DASHBOARD_FAVORITE_ENDPOINT = @@ -133,3 +144,112 @@ export function resize(selector: string) { }, }; } + +export function cleanUp() { + cy.deleteDashboardByName(testItems.dashboard); + cy.deleteDashboardByName(testItems.defaultNameDashboard); + cy.deleteDashboardByName(''); + cy.deleteDashboardByName(testItems.newDashboardTitle); + cy.deleteDashboardByName(testItems.bulkFirstNameDashboard); + cy.deleteDashboardByName(testItems.bulkSecondNameDashboard); + cy.deleteDashboardByName(testItems.createdDashboard); + cy.deleteDashboardByName(testItems.worldBanksDataCopy); + cy.deleteChartByName(testItems.chart); + cy.deleteChartByName(testItems.newChart); +} + +/** ************************************************************************ + * Clicks on new filter button + * @returns {None} + * @summary helper for adding new filter + ************************************************************************* */ +export function clickOnAddFilterInModal() { + return cy + .get(nativeFilters.addFilterButton.button) + .first() + .click() + .then(() => { + cy.get(nativeFilters.addFilterButton.dropdownItem) + .contains('Filter') + .click({ force: true }); + }); +} + +/** ************************************************************************ + * Fills value native filter form with basic information + * @param {string} name name for filter + * @param {string} dataset which dataset should be used + * @param {string} filterColumn which column should be used + * @returns {None} + * @summary helper for filling value native filter form + ************************************************************************* */ +export function fillValueNativeFilterForm( + name: string, + dataset: string, + filterColumn: string, +) { + cy.get(nativeFilters.modal.container) + .find(nativeFilters.filtersPanel.filterName) + .last() + .click({ scrollBehavior: false }) + .type(name, { scrollBehavior: false }); + cy.get(nativeFilters.modal.container) + .find(nativeFilters.filtersPanel.datasetName) + .last() + .click({ scrollBehavior: false }) + .type(`${dataset}{enter}`, { scrollBehavior: false }); + cy.get(nativeFilters.silentLoading).should('not.exist'); + cy.get(nativeFilters.filtersPanel.filterInfoInput) + .last() + .should('be.visible') + .click({ force: true }); + cy.get(nativeFilters.filtersPanel.filterInfoInput).last().type(filterColumn); + cy.get(nativeFilters.filtersPanel.inputDropdown) + .should('be.visible', { timeout: 20000 }) + .last() + .click(); +} +/** ************************************************************************ + * Get native filter placeholder e.g 9 options + * @param {number} index which input it fills + * @returns cy object for assertions + * @summary helper for getting placeholder value + ************************************************************************* */ +export function getNativeFilterPlaceholderWithIndex(index: number) { + return cy.get(nativeFilters.filtersPanel.columnEmptyInput).eq(index); +} + +/** ************************************************************************ + * Apply native filter value from dashboard view + * @param {number} index which input it fills + * @param {string} value what is filter value + * @returns {null} + * @summary put value to nth native filter input in view + ************************************************************************* */ +export function applyNativeFilterValueWithIndex(index: number, value: string) { + cy.get(nativeFilters.filterFromDashboardView.filterValueInput) + .eq(index) + .parent() + .should('be.visible', { timeout: 10000 }) + .type(`${value}{enter}`); + // click the title to dismiss shown options + cy.get(nativeFilters.filterFromDashboardView.filterName).eq(index).click(); +} + +/** ************************************************************************ + * Fills parent filter input + * @param {number} index which input it fills + * @param {string} value on which filter it depends on + * @returns {null} + * @summary takes first or second input and modify the depends on filter value + ************************************************************************* */ +export function addParentFilterWithValue(index: number, value: string) { + return cy + .get(nativeFilters.filterConfigurationSections.displayedSection) + .within(() => { + cy.get('input[aria-label="Limit type"]') + .eq(index) + .click({ force: true }) + .type(`${value}{enter}`, { delay: 30, force: true }); + }); +} 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 433b4a65e3..2177c9c4fe 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts @@ -22,7 +22,17 @@ import { nativeFilters, exploreView, } from 'cypress/support/directories'; -import { testItems } from './dashboard.helper'; +import { + cleanUp, + testItems, + WORLD_HEALTH_CHARTS, + waitForChartLoad, + clickOnAddFilterInModal, + fillValueNativeFilterForm, + getNativeFilterPlaceholderWithIndex, + addParentFilterWithValue, + applyNativeFilterValueWithIndex, +} from './dashboard.helper'; import { DASHBOARD_LIST } from '../dashboard_list/dashboard_list.helper'; import { CHART_LIST } from '../chart_list/chart_list.helper'; import { FORM_DATA_DEFAULTS } from '../explore/visualizations/shared.helper'; @@ -39,21 +49,27 @@ const milliseconds = new Date().getTime(); const dashboard = `Test Dashboard${milliseconds}`; describe('Nativefilters Sanity test', () => { - before(() => { + beforeEach(() => { cy.login(); + cleanUp(); cy.intercept('/api/v1/dashboard/?q=**').as('dashboardsList'); cy.intercept('POST', '**/copy_dash/*').as('copy'); cy.intercept('/api/v1/dashboard/*').as('dashboard'); - cy.request( - 'api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:100)', - ).then(xhr => { - const dashboards = xhr.body.result; + cy.intercept('GET', '**/api/v1/dataset/**').as('datasetLoad'); + cy.intercept('**/api/v1/dashboard/?q=**').as('dashboardsList'); + cy.visit('dashboard/list/'); + cy.contains('Actions'); + cy.wait('@dashboardsList').then(xhr => { + const dashboards = xhr.response?.body.result; + /* eslint-disable no-unused-expressions */ + expect(dashboards).not.to.be.undefined; const worldBankDashboard = dashboards.find( (d: { dashboard_title: string }) => d.dashboard_title === "World Bank's Data", ); cy.visit(worldBankDashboard.url); }); + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); cy.get(dashboardView.threeDotsMenuIcon).should('be.visible').click(); cy.get(dashboardView.saveAsMenuOption).should('be.visible').click(); cy.get(dashboardView.saveModal.dashboardNameInput) @@ -65,19 +81,10 @@ describe('Nativefilters Sanity test', () => { .its('response.statusCode') .should('eq', 200); }); - beforeEach(() => { - cy.login(); - cy.request( - 'api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:100)', - ).then(xhr => { - const dashboards = xhr.body.result; - const testDashboard = dashboards.find( - (d: { dashboard_title: string }) => - d.dashboard_title === testItems.dashboard, - ); - cy.visit(testDashboard.url); - }); + afterEach(() => { + cleanUp(); }); + it('User can expand / retract native filter sidebar on a dashboard', () => { cy.get(nativeFilters.createFilterButton).should('not.exist'); cy.get(nativeFilters.filterFromDashboardView.expand) @@ -390,15 +397,6 @@ describe('Nativefilters Sanity test', () => { cy.get('.line').within(() => { cy.contains('United States').should('be.visible'); }); - - // clean up the default setting - cy.get(nativeFilters.filterFromDashboardView.expand).click({ force: true }); - cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click(); - cy.contains('Filter has default value').click(); - cy.get(nativeFilters.modal.footer) - .find(nativeFilters.modal.saveButton) - .should('be.visible') - .click({ force: true }); }); it('User can create a time grain filter', () => { @@ -565,6 +563,51 @@ describe('Nativefilters Sanity test', () => { .should('be.visible', { timeout: 40000 }) .contains('country_name'); }); + + it('User can create parent filters using "Values are dependent on other filters"', () => { + cy.get(nativeFilters.filterFromDashboardView.expand) + .should('be.visible') + .click({ force: true }); + cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click(); + // Create parent filter 'region'. + fillValueNativeFilterForm('region', 'wb_health_population', 'region'); + // Create filter 'country_name' depend on region filter. + clickOnAddFilterInModal(); + fillValueNativeFilterForm( + 'country_name', + 'wb_health_population', + 'country_name', + ); + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters') + .should('be.visible') + .click(); + }, + ); + addParentFilterWithValue(0, 'region'); + cy.wait(1000); + cy.get(nativeFilters.modal.footer) + .contains('Save') + .should('be.visible') + .click(); + // Validate both filter in dashboard view. + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); + ['region', 'country_name'].forEach(it => { + cy.get(nativeFilters.filterFromDashboardView.filterName) + .contains(it) + .should('be.visible'); + }); + getNativeFilterPlaceholderWithIndex(1) + .invoke('text') + .should('equal', '214 options', { timeout: 20000 }); + // apply first filter value and validate 2nd filter is depden on 1st filter. + applyNativeFilterValueWithIndex(0, 'East Asia & Pacific'); + + getNativeFilterPlaceholderWithIndex(0).should('have.text', '36 options', { + timeout: 20000, + }); + }); }); xdescribe('Nativefilters', () => { diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts index fdacf3232b..eca68a7ced 100644 --- a/superset-frontend/cypress-base/cypress/support/index.d.ts +++ b/superset-frontend/cypress-base/cypress/support/index.d.ts @@ -47,6 +47,20 @@ declare namespace Cypress { querySubstring?: string | RegExp; chartSelector?: JQuery.Selector; }): cy; + + /** + * Get + */ + getDashboards(): cy; + getCharts(): cy; + + /** + * Delete + */ + deleteDashboard(id: number): cy; + deleteDashboardByName(name: string): cy; + deleteChartByName(name: string): cy; + deleteChart(id: number): cy; } } diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts index e22f69975e..905d00df9f 100644 --- a/superset-frontend/cypress-base/cypress/support/index.ts +++ b/superset-frontend/cypress-base/cypress/support/index.ts @@ -19,6 +19,7 @@ import '@cypress/code-coverage/support'; const BASE_EXPLORE_URL = '/superset/explore/?form_data='; +const TokenName = Cypress.env('TOKEN_NAME'); /* eslint-disable consistent-return */ Cypress.on('uncaught:exception', err => { @@ -102,3 +103,88 @@ Cypress.Commands.add( return cy; }, ); + +Cypress.Commands.add('deleteDashboardByName', (name: string) => + cy.getDashboards().then((dashboards: any) => { + dashboards?.forEach((element: any) => { + if (element.dashboard_title === name) { + const elementId = element.id; + cy.deleteDashboard(elementId); + } + }); + }), +); + +Cypress.Commands.add('deleteDashboard', (id: number) => + cy + .request({ + method: 'DELETE', + url: `api/v1/dashboard/${id}`, + 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('getDashboards', () => + cy + .request({ + method: 'GET', + url: `api/v1/dashboard/`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + }, + }) + .then(resp => resp.body.result), +); + +Cypress.Commands.add('deleteChart', (id: number) => + cy + .request({ + method: 'DELETE', + url: `api/v1/chart/${id}`, + 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}/`, + }, + failOnStatusCode: false, + }) + .then(resp => resp), +); + +Cypress.Commands.add('getCharts', () => + cy + .request({ + method: 'GET', + url: `api/v1/chart/`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + }, + }) + .then(resp => resp.body.result), +); + +Cypress.Commands.add('deleteChartByName', (name: string) => + cy.getCharts().then((slices: any) => { + slices?.forEach((element: any) => { + if (element.slice_name === name) { + const elementId = element.id; + cy.deleteChart(elementId); + } + }); + }), +); diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 59582524ea..1518dc1647 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -275,7 +275,7 @@ }, "engines": { "node": "^16.9.1", - "npm": "^7.5.4" + "npm": "^7.5.4 || ^8.1.2" } }, "buildtools/eslint-plugin-theme-colors": {