From a45a5e10600980df7cbacd1decd2f6fc3cf901f3 Mon Sep 17 00:00:00 2001 From: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com> Date: Fri, 26 Mar 2021 09:20:13 -0700 Subject: [PATCH] chore(cypress): Make the e2e tests more behavior-driven (#13784) * chore(cypress): make the load dashboard test behavior driven * remove bootstrap usage from controls test, + new utils * fix save test to not use bootstrap dat * remove bootstrap usage from the filter test * fix race condition * remove bootstrap from url params test * fix lint * remove unused const * add chart specs for the tab test * attempt removing bootstrap data from tabs tests * fix cypress async/sync confusion * remove redundant assertions * attempt fixing tab test * attempt fixing tabs test * cleanup commented code * better aliases so you can tell what they are in the logs * remove unused imports * get the line chart alias before clicking the tab * simplify getChartGridComponent * wait for all the charts before force refresh * fix tabs test * one more time with the tabs test * another edit to tabs test * fix flaky test Co-authored-by: Phillip Kelley-Dotson --- .../integration/dashboard/controls.test.ts | 113 +++++++-------- .../integration/dashboard/dashboard.helper.ts | 69 +++++++++ .../integration/dashboard/filter.test.ts | 92 ++++-------- .../integration/dashboard/load.test.ts | 47 +----- .../integration/dashboard/save.test.js | 42 +++--- .../integration/dashboard/tabs.test.ts | 134 ++++++++---------- .../integration/dashboard/url_params.test.ts | 57 +++----- .../cypress-base/cypress/utils/vizPlugins.ts | 37 ++--- .../src/dashboard/components/Header.jsx | 1 + .../components/gridComponents/Chart.jsx | 8 +- 10 files changed, 290 insertions(+), 310 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 1be8f518c..73587ee3a 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts @@ -16,79 +16,80 @@ * specific language governing permissions and limitations * under the License. */ -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; import { - getChartAliases, - isLegacyResponse, - DASHBOARD_CHART_ALIAS_PREFIX, -} from '../../utils/vizPlugins'; + WORLD_HEALTH_CHARTS, + WORLD_HEALTH_DASHBOARD, + waitForChartLoad, + ChartSpec, + getChartAliasesBySpec, +} from './dashboard.helper'; +import { isLegacyResponse } from '../../utils/vizPlugins'; describe('Dashboard top-level controls', () => { - let mapId: string; - let aliases: string[]; - beforeEach(() => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); - - cy.get('#app').then(data => { - const bootstrapData = JSON.parse(data[0].dataset.bootstrap || ''); - const dashboard = bootstrapData.dashboard_data; - mapId = dashboard.slices.find( - (slice: { form_data: { viz_type: string }; slice_id: number }) => - slice.form_data.viz_type === 'world_map', - ).slice_id; - aliases = getChartAliases(dashboard.slices); - }); }); // flaky test xit('should allow chart level refresh', () => { - cy.wait(aliases); - cy.get('[data-test="grid-container"]').find('.world_map').should('exist'); - cy.get(`#slice_${mapId}-controls`).click(); - cy.get(`[data-test="slice_${mapId}-menu"]`) - .find('[data-test="refresh-chart-menu-item"]') - .click({ force: true }); - cy.get('[data-test="refresh-chart-menu-item"]').should( - 'have.class', - 'ant-dropdown-menu-item-disabled', - ); - - cy.wait(`@${DASHBOARD_CHART_ALIAS_PREFIX}${mapId}`); - cy.get('[data-test="refresh-chart-menu-item"]').should( - 'not.have.class', - 'ant-dropdown-menu-item-disabled', - ); + const mapSpec = WORLD_HEALTH_CHARTS.find( + ({ viz }) => viz === 'world_map', + ) as ChartSpec; + waitForChartLoad(mapSpec).then(gridComponent => { + const mapId = gridComponent.attr('data-test-chart-id'); + cy.get('[data-test="grid-container"]').find('.world_map').should('exist'); + cy.get(`#slice_${mapId}-controls`).click(); + cy.get(`[data-test="slice_${mapId}-menu"]`) + .find('[data-test="refresh-chart-menu-item"]') + .click({ force: true }); + // likely cause for flakiness: + // The query completes before this assertion happens. + // Solution: pause the network before clicking, assert, then unpause network. + cy.get('[data-test="refresh-chart-menu-item"]').should( + 'have.class', + 'ant-dropdown-menu-item-disabled', + ); + waitForChartLoad(mapSpec); + cy.get('[data-test="refresh-chart-menu-item"]').should( + 'not.have.class', + 'ant-dropdown-menu-item-disabled', + ); + }); }); it('should allow dashboard level force refresh', () => { - cy.wait(aliases); // when charts are not start loading, for example, under a secondary tab, // should allow force refresh - cy.get('[data-test="more-horiz"]').click(); - cy.get('[data-test="refresh-dashboard-menu-item"]').should( - 'not.have.class', - 'ant-dropdown-menu-item-disabled', - ); + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); + getChartAliasesBySpec(WORLD_HEALTH_CHARTS).then(aliases => { + cy.get('[data-test="more-horiz"]').click(); + cy.get('[data-test="refresh-dashboard-menu-item"]').should( + 'not.have.class', + 'ant-dropdown-menu-item-disabled', + ); - cy.get('[data-test="refresh-dashboard-menu-item"]').click({ force: true }); - cy.get('[data-test="refresh-dashboard-menu-item"]').should( - 'have.class', - 'ant-dropdown-menu-item-disabled', - ); + cy.get('[data-test="refresh-dashboard-menu-item"]').click({ + force: true, + }); + cy.get('[data-test="refresh-dashboard-menu-item"]').should( + 'have.class', + 'ant-dropdown-menu-item-disabled', + ); - // wait all charts force refreshed. - cy.wait(aliases).then(xhrs => { - xhrs.forEach(async ({ response, request }) => { - const responseBody = response?.body; - const isCached = isLegacyResponse(responseBody) - ? responseBody.is_cached - : responseBody.result[0].is_cached; - // request url should indicate force-refresh operation - expect(request.url).to.have.string('force=true'); - // is_cached in response should be false - expect(isCached).to.equal(false); + // wait all charts force refreshed. + + cy.wait(aliases).then(xhrs => { + xhrs.forEach(async ({ response, request }) => { + const responseBody = response?.body; + const isCached = isLegacyResponse(responseBody) + ? responseBody.is_cached + : responseBody.result[0].is_cached; + // request url should indicate force-refresh operation + expect(request.url).to.have.string('force=true'); + // is_cached in response should be false + expect(isCached).to.equal(false); + }); }); }); cy.get('[data-test="more-horiz"]').click(); 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 1ad51368b..459c943a0 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts @@ -1,3 +1,5 @@ +import { getChartAlias, Slice } from 'cypress/utils/vizPlugins'; + /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -22,6 +24,73 @@ export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/'; 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; + +/** 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-grid-component"][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 + .wrap(gridComponent) + .find(`#chart-id-${chartId}`, { timeout: 30000 }) + .should('be.visible') + // return the chart grid component + .then(() => gridComponent) + ); + }); +} + +const toSlicelike = ($chart: JQuery): 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/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts index dd378f160..6afe2c619 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts @@ -16,77 +16,47 @@ * specific language governing permissions and limitations * under the License. */ +import { isLegacyResponse, parsePostForm } from 'cypress/utils'; import { - getChartAliases, - DASHBOARD_CHART_ALIAS_PREFIX, - isLegacyResponse, - parsePostForm, -} from 'cypress/utils'; -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; - -interface Slice { - slice_id: number; - form_data: { - viz_type: string; - [key: string]: JSONValue; - }; -} - -interface DashboardData { - slices: Slice[]; -} + WORLD_HEALTH_CHARTS, + WORLD_HEALTH_DASHBOARD, + getChartAliasesBySpec, + waitForChartLoad, +} from './dashboard.helper'; describe('Dashboard filter', () => { - let filterId: number; - let aliases: string[]; - - const getAlias = (id: number) => `@${DASHBOARD_CHART_ALIAS_PREFIX}${id}`; - - beforeEach(() => { + before(() => { cy.login(); - cy.visit(WORLD_HEALTH_DASHBOARD); - - cy.get('#app').then(app => { - const bootstrapData = app.data('bootstrap'); - const dashboard = bootstrapData.dashboard_data as DashboardData; - const { slices } = dashboard; - filterId = - dashboard.slices.find( - slice => slice.form_data.viz_type === 'filter_box', - )?.slice_id || 0; - - aliases = getChartAliases(slices); - - // wait the initial page load requests - cy.wait(aliases); - }); }); it('should apply filter', () => { - cy.get('.Select__placeholder:first').click(); + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); + getChartAliasesBySpec( + WORLD_HEALTH_CHARTS.filter(({ viz }) => viz !== 'filter_box'), + ).then(nonFilterChartAliases => { + cy.get('.Select__placeholder:first').click(); - // should show the filter indicator - cy.get('svg[data-test="filter"]:visible').should(nodes => { - expect(nodes.length).to.least(9); - }); + // should show the filter indicator + cy.get('svg[data-test="filter"]:visible').should(nodes => { + expect(nodes.length).to.least(9); + }); - cy.get('.Select__control:first input[type=text]').type('So', { - force: true, - delay: 100, - }); + cy.get('.Select__control:first input[type=text]').type('So', { + force: true, + delay: 100, + }); - cy.get('.Select__menu').first().contains('South Asia').click(); + cy.get('.Select__menu').first().contains('South Asia').click(); - // should still have all filter indicators - cy.get('svg[data-test="filter"]:visible').should(nodes => { - expect(nodes.length).to.least(9); - }); + // should still have all filter indicators + cy.get('svg[data-test="filter"]:visible').should(nodes => { + expect(nodes.length).to.least(9); + }); - cy.get('.filter_box button').click({ force: true }); - cy.wait(aliases.filter(x => x !== getAlias(filterId))).then(requests => - Promise.all( - requests.map(async ({ response, request }) => { + cy.get('.filter_box button').click({ force: true }); + cy.wait(nonFilterChartAliases).then(requests => { + requests.forEach(({ response, request }) => { const responseBody = response?.body; let requestFilter; if (isLegacyResponse(responseBody)) { @@ -103,9 +73,9 @@ describe('Dashboard filter', () => { op: '==', val: 'South Asia', }); - }), - ), - ); + }); + }); + }); // TODO add test with South Asia{enter} type action to select filter }); 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 0b38a8378..b03cdd279 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts @@ -17,53 +17,18 @@ * under the License. */ import { - getChartAliases, - isLegacyResponse, - getSliceIdFromRequestUrl, - JsonObject, -} from '../../utils/vizPlugins'; -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; + waitForChartLoad, + WORLD_HEALTH_CHARTS, + WORLD_HEALTH_DASHBOARD, +} from './dashboard.helper'; describe('Dashboard load', () => { - let dashboard; - let aliases: string[]; - beforeEach(() => { + before(() => { cy.login(); - cy.visit(WORLD_HEALTH_DASHBOARD); - - cy.get('#app').then(nodes => { - const bootstrapData = JSON.parse(nodes[0].dataset.bootstrap || ''); - dashboard = bootstrapData.dashboard_data; - const { slices } = dashboard; - // then define routes and create alias for each requests - aliases = getChartAliases(slices); - }); }); it('should load dashboard', () => { - // wait and verify one-by-one - cy.wait(aliases).then(requests => - Promise.all( - requests.map(async ({ response, request }) => { - const responseBody = response?.body; - let sliceId; - if (isLegacyResponse(responseBody)) { - expect(responseBody).to.have.property('errors'); - expect(responseBody.errors.length).to.eq(0); - sliceId = responseBody.form_data.slice_id; - } else { - sliceId = getSliceIdFromRequestUrl(request.url); - responseBody.result.forEach((element: JsonObject) => { - expect(element).to.have.property('error', null); - expect(element).to.have.property('status', 'success'); - }); - } - cy.get('[data-test="grid-content"]') - .find(`#chart-id-${sliceId}`) - .should('be.visible'); - }), - ), - ); + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); }); }); 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 b0909ce1b..b56da45b8 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js @@ -18,7 +18,11 @@ */ import shortid from 'shortid'; -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; +import { + waitForChartLoad, + WORLD_HEALTH_CHARTS, + WORLD_HEALTH_DASHBOARD, +} from './dashboard.helper'; function openDashboardEditProperties() { cy.get('.dashboard-header [data-test=edit-alt]').click(); @@ -31,35 +35,37 @@ describe('Dashboard save action', () => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); cy.get('#app').then(data => { - const bootstrapData = JSON.parse(data[0].dataset.bootstrap); - const dashboard = bootstrapData.dashboard_data; - const dashboardId = dashboard.id; - cy.intercept('POST', `/superset/copy_dash/${dashboardId}/`).as( - 'copyRequest', - ); + cy.get('[data-test="dashboard-header"]').then(headerElement => { + const dashboardId = headerElement.attr('data-test-id'); - cy.get('[data-test="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, + cy.intercept('POST', `/superset/copy_dash/${dashboardId}/`).as( + 'copyRequest', + ); + + cy.get('[data-test="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(xhr => { - expect(xhr.response.body.dashboard_title).to.not.equal( - `World Bank's Data`, - ); + cy.get('[data-test="editable-title-input"]').then(element => { + const dashboardTitle = element.attr('title'); + expect(dashboardTitle).to.not.equal(`World Bank's Data`); + }); }); }); it('should save/overwrite dashboard', () => { // should load chart - cy.get('.dashboard-grid', { timeout: 30000 }); - cy.get('.box_plot').should('be.visible'); + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); // remove box_plot chart from dashboard cy.get('[data-test="edit-alt"]').click({ timeout: 5000 }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts index 2ceed512f..d4ec42e0d 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts @@ -16,15 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { interceptChart, parsePostForm, Slice } from 'cypress/utils'; -import { TABBED_DASHBOARD } from './dashboard.helper'; +import { parsePostForm } from 'cypress/utils'; +import { + TABBED_DASHBOARD, + waitForChartLoad, + getChartAliasBySpec, +} from './dashboard.helper'; + +const TREEMAP = { name: 'Treemap', viz: 'treemap' }; +const FILTER_BOX = { name: 'Region Filter', viz: 'filter_box' }; +const LINE_CHART = { name: 'Growth Rate', viz: 'line' }; +const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' }; describe('Dashboard tabs', () => { - let filterId; - let treemapId; - let linechartId; - let boxplotId; - // cypress can not handle window.scrollTo // https://github.com/cypress-io/cypress/issues/2761 // add this exception handler to pass test @@ -38,36 +42,11 @@ describe('Dashboard tabs', () => { cy.login(); cy.visit(TABBED_DASHBOARD); - - cy.get('#app').then(data => { - const bootstrapData = JSON.parse(data[0].dataset.bootstrap || ''); - const dashboard = bootstrapData.dashboard_data as { slices: Slice[] }; - filterId = dashboard.slices.find( - slice => slice.form_data.viz_type === 'filter_box', - )?.slice_id; - boxplotId = dashboard.slices.find( - slice => slice.form_data.viz_type === 'box_plot', - )?.slice_id; - treemapId = dashboard.slices.find( - slice => slice.form_data.viz_type === 'treemap', - )?.slice_id; - linechartId = dashboard.slices.find( - slice => slice.form_data.viz_type === 'line', - )?.slice_id; - interceptChart({ sliceId: filterId, legacy: true }).as('filterRequest'); - interceptChart({ sliceId: treemapId, legacy: true }).as('treemapRequest'); - interceptChart({ sliceId: linechartId, legacy: true }).as( - 'linechartRequest', - ); - interceptChart({ sliceId: boxplotId, legacy: false }).as( - 'boxplotRequest', - ); - }); }); it('should switch active tab on click', () => { - cy.wait('@filterRequest'); - cy.wait('@treemapRequest'); + waitForChartLoad(FILTER_BOX); + waitForChartLoad(TREEMAP); cy.get('[data-test="dashboard-component-tabs"]') .first() @@ -93,14 +72,8 @@ describe('Dashboard tabs', () => { it('should load charts when tab is visible', () => { // landing in first tab, should see 2 charts - cy.wait('@filterRequest'); - cy.get('[data-test="grid-container"]') - .find('.filter_box') - .should('be.visible'); - cy.wait('@treemapRequest'); - cy.get('[data-test="grid-container"]') - .find('.treemap') - .should('be.visible'); + waitForChartLoad(FILTER_BOX); + waitForChartLoad(TREEMAP); cy.get('[data-test="grid-container"]') .find('.box_plot') .should('not.exist'); @@ -114,7 +87,7 @@ describe('Dashboard tabs', () => { cy.get('@row-level-tabs').last().click(); - cy.wait('@linechartRequest'); + waitForChartLoad(LINE_CHART); cy.get('[data-test="grid-container"]').find('.line').should('be.visible'); // click top level tab, see 1 more chart @@ -132,30 +105,33 @@ describe('Dashboard tabs', () => { it('should send new queries when tab becomes visible', () => { // landing in first tab - cy.wait('@filterRequest'); - cy.wait('@treemapRequest'); + waitForChartLoad(FILTER_BOX); + waitForChartLoad(TREEMAP); - // apply filter - cy.get('.Select__control').first().should('be.visible').click(); - cy.get('.Select__control input[type=text]').first().focus().type('South'); - cy.get('.Select__option').contains('South Asia').click(); - cy.get('.filter_box button:not(:disabled)').contains('Apply').click(); + getChartAliasBySpec(TREEMAP).then(treemapAlias => { + // apply filter + cy.get('.Select__control').first().should('be.visible').click(); + cy.get('.Select__control input[type=text]').first().focus().type('South'); + cy.get('.Select__option').contains('South Asia').click(); + cy.get('.filter_box button:not(:disabled)').contains('Apply').click(); - // send new query from same tab - cy.wait('@treemapRequest').then(({ request }) => { - const requestBody = parsePostForm(request.body); - const requestParams = JSON.parse(requestBody.form_data as string); - expect(requestParams.extra_filters[0]).deep.eq({ - col: 'region', - op: '==', - val: 'South Asia', + // send new query from same tab + cy.wait(treemapAlias).then(({ request }) => { + const requestBody = parsePostForm(request.body); + const requestParams = JSON.parse(requestBody.form_data as string); + expect(requestParams.extra_filters[0]).deep.eq({ + col: 'region', + op: '==', + val: 'South Asia', + }); }); }); + cy.intercept('/superset/explore_json/?*').as('legacyChartData'); // click row level tab, send 1 more query cy.get('.ant-tabs-tab').contains('row tab 2').click(); - cy.wait('@linechartRequest').then(({ request }) => { + cy.wait('@legacyChartData').then(({ request }) => { const requestBody = parsePostForm(request.body); const requestParams = JSON.parse(requestBody.form_data as string); expect(requestParams.extra_filters[0]).deep.eq({ @@ -163,39 +139,41 @@ describe('Dashboard tabs', () => { op: '==', val: 'South Asia', }); + expect(requestParams.viz_type).eq(LINE_CHART.viz); }); + cy.intercept('POST', '/api/v1/chart/data?*').as('v1ChartData'); + // click top level tab, send 1 more query cy.get('.ant-tabs-tab').contains('Tab B').click(); - cy.wait('@boxplotRequest').then(({ request }) => { - const requestBody = request.body; - const requestParams = requestBody.queries[0]; - expect(requestParams.filters[0]).deep.eq({ + cy.wait('@v1ChartData').then(({ request }) => { + expect(request.body.queries[0].filters[0]).deep.eq({ col: 'region', op: '==', val: 'South Asia', }); }); - // navigate to filter and clear filter - cy.get('.ant-tabs-tab').contains('Tab A').click(); - cy.get('.ant-tabs-tab').contains('row tab 1').click(); + getChartAliasBySpec(BOX_PLOT).then(boxPlotAlias => { + // navigate to filter and clear filter + cy.get('.ant-tabs-tab').contains('Tab A').click(); + cy.get('.ant-tabs-tab').contains('row tab 1').click(); - cy.get('.Select__clear-indicator').click(); - cy.get('.filter_box button:not(:disabled)').contains('Apply').click(); + cy.get('.Select__clear-indicator').click(); + cy.get('.filter_box button:not(:disabled)').contains('Apply').click(); - // trigger 1 new query - cy.wait('@treemapRequest'); + // trigger 1 new query + waitForChartLoad(TREEMAP); + // make sure query API not requested multiple times + cy.on('fail', err => { + expect(err.message).to.include('timed out waiting'); + return false; + }); - // make sure query API not requested multiple times - cy.on('fail', err => { - expect(err.message).to.include('timed out waiting'); - return false; - }); - - cy.wait('@boxplotRequest', { timeout: 1000 }).then(() => { - throw new Error('Unexpected API call.'); + cy.wait(boxPlotAlias, { timeout: 1000 }).then(() => { + throw new Error('Unexpected API call.'); + }); }); }); }); 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 72ce9b4b1..0f4b839f1 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 @@ -16,52 +16,35 @@ * specific language governing permissions and limitations * under the License. */ +import { parsePostForm, JsonObject } from 'cypress/utils'; import { - isLegacyResponse, - getChartAliases, - parsePostForm, - Dashboard, - JsonObject, -} from 'cypress/utils'; -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; + WORLD_HEALTH_DASHBOARD, + WORLD_HEALTH_CHARTS, + waitForChartLoad, +} from './dashboard.helper'; describe('Dashboard form data', () => { const urlParams = { param1: '123', param2: 'abc' }; - let dashboard: Dashboard; - - beforeEach(() => { + before(() => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD, { qs: urlParams }); - - cy.get('#app').then(data => { - const bootstrapData = JSON.parse(data[0].dataset.bootstrap || ''); - dashboard = bootstrapData.dashboard_data; - }); }); it('should apply url params to slice requests', () => { - const aliases = getChartAliases(dashboard.slices); - // wait and verify one-by-one - cy.wait(aliases, { timeout: 18000 }).then(requests => - Promise.all( - requests.map(async ({ response, request }) => { - const responseBody = response?.body; - if (isLegacyResponse(responseBody)) { - const requestParams = JSON.parse( - parsePostForm(request.body).form_data as string, - ); - expect(requestParams.url_params).deep.eq(urlParams); - } else { - // TODO: export url params to chart data API - request.body.queries.forEach( - (query: { url_params: JsonObject }) => { - expect(query.url_params).deep.eq(urlParams); - }, - ); - } - }), - ), - ); + cy.intercept('/superset/explore_json/*', request => { + const requestParams = JSON.parse( + parsePostForm(request.body).form_data as string, + ); + expect(requestParams.url_params).deep.eq(urlParams); + }); + cy.intercept('/api/v1/chart/data?*', request => { + // TODO: export url params to chart data API + request.body.queries.forEach((query: { url_params: JsonObject }) => { + expect(query.url_params).deep.eq(urlParams); + }); + }); + + WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); }); }); diff --git a/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts b/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts index 9450a303b..850cc0987 100644 --- a/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts +++ b/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts @@ -41,7 +41,7 @@ const V1_PLUGINS = [ 'pie', 'table', ]; -export const DASHBOARD_CHART_ALIAS_PREFIX = 'getJson_'; +export const DASHBOARD_CHART_ALIAS_PREFIX = 'getChartData_'; export function isLegacyChart(vizType: string): boolean { return !V1_PLUGINS.includes(vizType); @@ -57,24 +57,25 @@ export function getSliceIdFromRequestUrl(url: string) { return query?.match(/\d+/)?.[0]; } +export function getChartDataRouteForSlice(slice: Slice) { + const vizType = slice.form_data.viz_type; + const isLegacy = isLegacyChart(vizType); + const formData = encodeURIComponent(`{"slice_id":${slice.slice_id}}`); + if (isLegacy) { + return `/superset/explore_json/?*${formData}*`; + } + return `/api/v1/chart/data?*${formData}*`; +} + +export function getChartAlias(slice: Slice): string { + const alias = `${DASHBOARD_CHART_ALIAS_PREFIX}${slice.slice_id}_${slice.form_data.viz_type}`; + const route = getChartDataRouteForSlice(slice); + cy.intercept('POST', route).as(alias); + return `@${alias}`; +} + export function getChartAliases(slices: Slice[]): string[] { - const aliases: string[] = []; - Array.from(slices).forEach(slice => { - const vizType = slice.form_data.viz_type; - const isLegacy = isLegacyChart(vizType); - const alias = `${DASHBOARD_CHART_ALIAS_PREFIX}${slice.slice_id}`; - const formData = encodeURIComponent(`{"slice_id":${slice.slice_id}}`); - if (isLegacy) { - const route = `/superset/explore_json/?*${formData}*`; - cy.intercept('POST', `${route}`).as(alias); - aliases.push(`@${alias}`); - } else { - const route = `/api/v1/chart/data?*${formData}*`; - cy.intercept('POST', `${route}`).as(alias); - aliases.push(`@${alias}`); - } - }); - return aliases; + return Array.from(slices).map(getChartAlias); } export function interceptChart({ diff --git a/superset-frontend/src/dashboard/components/Header.jsx b/superset-frontend/src/dashboard/components/Header.jsx index 0d1826bb8..8ad6c57e6 100644 --- a/superset-frontend/src/dashboard/components/Header.jsx +++ b/superset-frontend/src/dashboard/components/Header.jsx @@ -387,6 +387,7 @@ class Header extends React.PureComponent {
+