mirror of
https://github.com/apache/superset.git
synced 2026-07-17 04:05:37 +00:00
Compare commits
2 Commits
fix-tile-w
...
codex/refa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8785ac794f | ||
|
|
0a200e6114 |
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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 { Page } from '@playwright/test';
|
||||
import { Button, Select } from '../core';
|
||||
|
||||
/**
|
||||
* Dashboard native-filter bar component.
|
||||
*/
|
||||
export class DashboardFilterBar {
|
||||
private static readonly SELECTORS = {
|
||||
FILTER_VALUE: '[data-test="form-item-value"]',
|
||||
APPLY_BUTTON:
|
||||
'[data-test="filter-bar__apply-button"], [data-test="filterbar-action-buttons"] button[type="submit"]',
|
||||
CLEAR_BUTTON: '[data-test="filter-bar__clear-button"]',
|
||||
} as const;
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
/**
|
||||
* Selects an option in a native filter by its zero-based position.
|
||||
*/
|
||||
async selectOption(optionText: string, index = 0): Promise<void> {
|
||||
const select = new Select(
|
||||
this.page,
|
||||
this.page
|
||||
.locator(DashboardFilterBar.SELECTORS.FILTER_VALUE)
|
||||
.nth(index)
|
||||
.getByRole('combobox'),
|
||||
);
|
||||
await select.open();
|
||||
await select.clickOption(optionText);
|
||||
await select.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies pending native-filter changes.
|
||||
*/
|
||||
async apply(): Promise<void> {
|
||||
await this.getApplyButton().click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies pending native-filter changes when the Apply button is enabled.
|
||||
*/
|
||||
async applyIfEnabled(): Promise<void> {
|
||||
const applyButton = this.getApplyButton();
|
||||
if (!(await applyButton.isEnabled().catch(() => false))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await applyButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all native-filter values without applying the pending changes.
|
||||
*/
|
||||
async clearAll(): Promise<void> {
|
||||
await new Button(
|
||||
this.page,
|
||||
DashboardFilterBar.SELECTORS.CLEAR_BUTTON,
|
||||
).click();
|
||||
}
|
||||
|
||||
private getApplyButton(): Button {
|
||||
return new Button(
|
||||
this.page,
|
||||
this.page.locator(DashboardFilterBar.SELECTORS.APPLY_BUTTON).first(),
|
||||
);
|
||||
}
|
||||
}
|
||||
20
superset-frontend/playwright/components/dashboard/index.ts
Normal file
20
superset-frontend/playwright/components/dashboard/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { DashboardFilterBar } from './DashboardFilterBar';
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { Page, Download, Locator } from '@playwright/test';
|
||||
import { Menu } from '../components/core';
|
||||
import { DashboardFilterBar } from '../components/dashboard';
|
||||
import { NativeFiltersConfigModal } from '../components/modals';
|
||||
import { gotoWithRetry } from '../helpers/navigation';
|
||||
import { TIMEOUT } from '../utils/constants';
|
||||
@@ -28,6 +29,7 @@ import { TIMEOUT } from '../utils/constants';
|
||||
*/
|
||||
export class DashboardPage {
|
||||
private readonly page: Page;
|
||||
private readonly filterBar: DashboardFilterBar;
|
||||
|
||||
private static readonly SELECTORS = {
|
||||
DASHBOARD_HEADER: '[data-test="dashboard-header-container"]',
|
||||
@@ -35,12 +37,11 @@ export class DashboardPage {
|
||||
// The header-actions-menu is the data-test for the dropdown menu content
|
||||
HEADER_ACTIONS_MENU: '[data-test="header-actions-menu"]',
|
||||
FILTER_BAR_SETTINGS: '[data-test="filterbar-orientation-icon"]',
|
||||
APPLY_FILTERS_BUTTON:
|
||||
'[data-test="filter-bar__apply-button"], [data-test="filterbar-action-buttons"] button[type="submit"]',
|
||||
} as const;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.filterBar = new DashboardFilterBar(page);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +117,13 @@ export class DashboardPage {
|
||||
return this.page.getByRole('heading', { name, exact: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dashboard native-filter bar component.
|
||||
*/
|
||||
getFilterBar(): DashboardFilterBar {
|
||||
return this.filterBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the native filters and Display Controls configuration modal.
|
||||
*/
|
||||
@@ -134,14 +142,7 @@ export class DashboardPage {
|
||||
* Applies pending native filter changes when the Apply button is enabled.
|
||||
*/
|
||||
async applyFiltersIfEnabled(): Promise<void> {
|
||||
const applyButton = this.page
|
||||
.locator(DashboardPage.SELECTORS.APPLY_FILTERS_BUTTON)
|
||||
.first();
|
||||
if (!(await applyButton.isEnabled().catch(() => false))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await applyButton.click();
|
||||
await this.filterBar.applyIfEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import type { Request } from '@playwright/test';
|
||||
import { testWithAssets, expect } from '../../helpers/fixtures';
|
||||
import { apiPost, apiPut } from '../../helpers/api/requests';
|
||||
import { apiPostDashboard } from '../../helpers/api/dashboard';
|
||||
@@ -148,23 +149,10 @@ testWithAssets(
|
||||
await dashboardPage.gotoById(dashboardId);
|
||||
await dashboardPage.waitForLoad({ timeout: TIMEOUT.SLOW_TEST });
|
||||
await dashboardPage.waitForChartsToLoad();
|
||||
const filterBar = dashboardPage.getFilterBar();
|
||||
|
||||
// The Gender select should be visible in the filter bar
|
||||
const filterCombobox = page
|
||||
.locator('[data-test="form-item-value"]')
|
||||
.first()
|
||||
.locator('[role="combobox"]');
|
||||
await filterCombobox.click();
|
||||
await page
|
||||
.locator('.ant-select-item-option', { hasText: /^boy$/ })
|
||||
.first()
|
||||
.click();
|
||||
// Close the dropdown
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const applyBtn = page.locator(
|
||||
'[data-test="filter-bar__apply-button"], [data-test="filterbar-action-buttons"] button[type="submit"]',
|
||||
);
|
||||
await filterBar.selectOption('boy');
|
||||
|
||||
// Wait for chart data to come back after Apply
|
||||
const firstApplyResponse = page.waitForResponse(
|
||||
@@ -173,21 +161,20 @@ testWithAssets(
|
||||
r.request().method() === 'POST',
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
await applyBtn.first().click();
|
||||
await filterBar.apply();
|
||||
await firstApplyResponse;
|
||||
await dashboardPage.waitForChartsToLoad();
|
||||
|
||||
// Now track POST /api/v1/chart/data requests around Clear All
|
||||
const postsAfterClearAll: string[] = [];
|
||||
const handler = (req: any) => {
|
||||
const handler = (req: Request) => {
|
||||
if (req.url().includes('/api/v1/chart/data') && req.method() === 'POST') {
|
||||
postsAfterClearAll.push(req.url());
|
||||
}
|
||||
};
|
||||
page.on('request', handler);
|
||||
|
||||
const clearBtn = page.locator('[data-test="filter-bar__clear-button"]');
|
||||
await clearBtn.click();
|
||||
await filterBar.clearAll();
|
||||
|
||||
// Allow time for any debounced reload to fire if the bug is present
|
||||
await page.waitForTimeout(2000);
|
||||
@@ -209,7 +196,7 @@ testWithAssets(
|
||||
r.request().method() === 'POST',
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
await applyBtn.first().click();
|
||||
await filterBar.apply();
|
||||
await applyAfterClearPromise;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe('Dashboard Export', () => {
|
||||
await dashboardPage.gotoBySlug('world_health');
|
||||
await dashboardPage.waitForLoad({ timeout: TIMEOUT.PAGE_LOAD });
|
||||
// Wait for charts to finish loading - Download menu may be disabled while loading
|
||||
await dashboardPage.waitForChartsToLoad();
|
||||
await dashboardPage.waitForChartsToLoad({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
|
||||
@@ -39,6 +39,7 @@ import { apiPostChart, apiPutChart } from '../../helpers/api/chart';
|
||||
import { apiPostDashboard } from '../../helpers/api/dashboard';
|
||||
import { getDatasetByName } from '../../helpers/api/dataset';
|
||||
import { DashboardPage } from '../../pages/DashboardPage';
|
||||
import { TIMEOUT } from '../../utils/constants';
|
||||
|
||||
const DATASET_NAME = 'birth_names';
|
||||
|
||||
@@ -51,6 +52,8 @@ const COLOR_UNUSED_3: [number, number, number] = [90, 193, 137];
|
||||
testWithAssets(
|
||||
'Gauge renders configured interval colors on a dashboard (#28766)',
|
||||
async ({ page, testAssets }) => {
|
||||
testWithAssets.setTimeout(TIMEOUT.SLOW_TEST);
|
||||
|
||||
const dataset = await getDatasetByName(page, DATASET_NAME);
|
||||
if (!dataset) {
|
||||
throw new Error(`Dataset ${DATASET_NAME} not found`);
|
||||
|
||||
@@ -79,7 +79,7 @@ test('should navigate to Explore when dataset name is clicked', async ({
|
||||
await datasetListPage.clickDatasetName(datasetName);
|
||||
|
||||
// Wait for Explore page to load (validates URL + datasource control)
|
||||
await explorePage.waitForPageLoad();
|
||||
await explorePage.waitForPageLoad({ timeout: TIMEOUT.API_RESPONSE });
|
||||
|
||||
// Verify correct dataset is loaded in datasource control
|
||||
const loadedDatasetName = await explorePage.getDatasetName();
|
||||
|
||||
Reference in New Issue
Block a user