This is an automated email from the ASF dual-hosted git repository. suddjian pushed a commit to branch behavior-driven-cypress in repository https://gitbox.apache.org/repos/asf/superset.git
commit cda0f2405ff7f1f85aec7f3959bf390f31618d4a Author: David Aaron Suddjian <[email protected]> AuthorDate: Wed Mar 24 11:36:54 2021 -0700 remove bootstrap usage from controls test, + new utils --- .../cypress/integration/dashboard/controls.test.ts | 110 ++++++++++----------- .../integration/dashboard/dashboard.helper.ts | 69 +++++++++++++ .../cypress/integration/dashboard/load.test.ts | 20 +--- .../cypress-base/cypress/utils/vizPlugins.ts | 33 +++---- 4 files changed, 143 insertions(+), 89 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 1be8f51..0727a07 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,79 @@ * specific language governing permissions and limitations * under the License. */ -import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper'; import { + WORLD_HEALTH_CHARTS, + WORLD_HEALTH_DASHBOARD, + waitForChartLoad, + ChartSpec, getChartAliases, - isLegacyResponse, - DASHBOARD_CHART_ALIAS_PREFIX, -} from '../../utils/vizPlugins'; +} 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', - ); + getChartAliases(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. - // 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.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 e9d8cd0..7bd2be5 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 @@ -35,6 +37,73 @@ export const WORLD_HEALTH_CHARTS = [ { name: 'Box plot', viz: 'box_plot' }, ] as const; +export const BIRTH_NAMES_CHARTS = [ + { name: 'Participants', viz: 'blah' }, + { name: 'Genders', viz: 'blah' }, + { name: 'Trends', viz: 'blah' }, + { name: 'Genders by State', viz: 'blah' }, + { name: 'Girls', viz: 'blah' }, + { name: 'Girl Name Cloud', viz: 'blah' }, + { name: 'Top 10 Girl Name Share', viz: 'blah' }, + { name: 'Boys', viz: 'blah' }, + { name: 'Boy Name Cloud', viz: 'blah' }, + { name: 'Top 10 Boy Name Share', viz: 'blah' }, +] 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="grid-content"] [data-test="editable-title"]') + .contains(name) + // parentsUntil returns the child of the element matching the selector + .parentsUntil('[data-test="chart-grid-component"]') + .parent() + .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<HTMLElement>): Slice => ({ + slice_id: parseInt($chart.attr('data-test-chart-id')!, 10), + form_data: { + viz_type: $chart.attr('data-test-viz-type')!, + }, +}); + +export function getChartAliases(charts: readonly ChartSpec[]) { + const aliases: string[] = []; + charts.forEach(chart => + getChartGridComponent(chart).then($chart => { + aliases.push(getChartAlias(toSlicelike($chart))); + }), + ); + // 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/load.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts index 4522494..b03cdd2 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts @@ -17,32 +17,18 @@ * under the License. */ import { + waitForChartLoad, WORLD_HEALTH_CHARTS, WORLD_HEALTH_DASHBOARD, } from './dashboard.helper'; describe('Dashboard load', () => { - beforeEach(() => { + before(() => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); }); it('should load dashboard', () => { - // wait and verify one-by-one - WORLD_HEALTH_CHARTS.forEach(({ name, viz }) => { - // prettier-ignore - cy.get('[data-test="grid-content"] [data-test="editable-title"]').contains(name) - // use the chart title to find the chart grid component, - // which has the chart id and viz type info - .parentsUntil('[data-test="chart-grid-component"]').parent() - .should('have.attr', 'data-test-viz-type', viz) - .then(chartElement => { - const chartId = chartElement.attr('data-test-chart-id'); - // the chart should load in under a minute - // (big timeout so that it works in CI) - cy.wrap(chartElement).find(`#chart-id-${chartId}`, { timeout: 30000 }) - .should('be.visible'); - }); - }); + 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 9450a30..04075e9 100644 --- a/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts +++ b/superset-frontend/cypress-base/cypress/utils/vizPlugins.ts @@ -57,24 +57,23 @@ export function getSliceIdFromRequestUrl(url: string) { return query?.match(/\d+/)?.[0]; } +export function getChartAlias(slice: Slice): string { + 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); + return `@${alias}`; + } + const route = `/api/v1/chart/data?*${formData}*`; + 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({
