You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "sadpandajoe (via GitHub)" <gi...@apache.org> on 2026/03/04 01:37:04 UTC
[PR] test(playwright): add dashboard list E2E tests [superset]
sadpandajoe opened a new pull request, #38377:
URL: https://github.com/apache/superset/pull/38377
### SUMMARY
Add Playwright E2E tests for the Dashboard List page, mirroring the existing chart list test patterns. Covers five operations: single delete, single export, bulk delete, bulk export, and import.
**New files:**
- `playwright/helpers/api/dashboard.ts` — CRUD + export/search API helpers with proper Rison encoding
- `playwright/pages/DashboardListPage.ts` — Page object composing `Table` + `BulkSelect` + `Button` components
- `playwright/tests/experimental/dashboard/dashboard-test-helpers.ts` — `createTestDashboard()` factory
- `playwright/tests/experimental/dashboard/dashboard-list.spec.ts` — 5 test cases
**Modified:**
- `playwright/helpers/fixtures/testAssets.ts` — Added `trackDashboard()` with FK-ordered cleanup (dashboards → charts → datasets → databases)
**Import test approach:** Export-then-reimport at test time (no static fixture needed, no environment coupling). Uses the shared `ImportModelsModal` UI via `ImportDatasetModal` page object.
### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — test-only change
### TESTING INSTRUCTIONS
```bash
cd superset-frontend
INCLUDE_EXPERIMENTAL=true npx playwright test tests/experimental/dashboard/dashboard-list.spec.ts --project=chromium
```
Requires Superset running with `--load-examples` and admin user authenticated via global-setup.
### ADDITIONAL INFORMATION
- [ ] Has associated issue:
- [ ] Required feature flags:
- [ ] Changes UI
- [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
- [ ] Migration is atomic, supports rollback & is backwards-compatible
- [ ] Confirm DB migration upgrade and downgrade tested
- [ ] Runtime estimates and downtime expectations provided
- [ ] Introduces new feature or API
- [ ] Removes existing feature or API
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas merged PR #38377:
URL: https://github.com/apache/superset/pull/38377
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #38377:
URL: https://github.com/apache/superset/pull/38377#discussion_r2881353697
##########
superset-frontend/playwright/pages/DashboardListPage.ts:
##########
@@ -0,0 +1,139 @@
+/**
+ * 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, Locator } from '@playwright/test';
+import { Button, Table } from '../components/core';
+import { BulkSelect } from '../components/ListView';
+import { URL } from '../utils/urls';
+
+/**
+ * Dashboard List Page object.
+ */
+export class DashboardListPage {
+ private readonly page: Page;
+ private readonly table: Table;
+ readonly bulkSelect: BulkSelect;
+
+ /**
+ * Action button names for getByRole('button', { name })
+ * DashboardList uses Icons.DeleteOutlined, Icons.UploadOutlined, Icons.EditOutlined
+ */
+ private static readonly ACTION_BUTTONS = {
+ DELETE: 'delete',
+ EDIT: 'edit',
+ EXPORT: 'upload',
+ } as const;
Review Comment:
**Suggestion:** The row-level export action button is selected using the accessible name `'upload'`, but in the Dashboard list UI the export button is labeled as an export action (e.g., "Export"), so `getByRole('button', { name: 'upload' })` is unlikely to match any button and will cause the single-dashboard export test to fail by timing out when trying to click a non-existent locator. [possible bug]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
- ❌ Dashboard single-export E2E test fails locating button.
- ⚠️ Dashboard export behavior lacks reliable end-to-end coverage.
- ⚠️ CI Playwright run may show persistent failures for dashboards.
```
</details>
```suggestion
private static readonly ACTION_BUTTONS = {
DELETE: 'delete',
EDIT: 'edit',
EXPORT: 'Export',
} as const;
```
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Run the new Playwright spec
`superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts` (file
found via Grep) using the command from the PR description: `INCLUDE_EXPERIMENTAL=true npx
playwright test tests/experimental/dashboard/dashboard-list.spec.ts --project=chromium`.
2. In that spec, the Dashboard list tests construct a `DashboardListPage` from
`superset-frontend/playwright/pages/DashboardListPage.ts` and navigate via `goto()` before
exercising the single-dashboard export flow (file presence and usage confirmed via Grep).
3. The test calls `DashboardListPage.clickExportAction(dashboardName)`, which resolves to
the implementation at `superset-frontend/playwright/pages/DashboardListPage.ts:103-107`,
where the row locator calls `.getByRole('button', { name:
DashboardListPage.ACTION_BUTTONS.EXPORT })`.
4. Because `ACTION_BUTTONS.EXPORT` is defined as `'upload'` at
`superset-frontend/playwright/pages/DashboardListPage.ts:37-41`, Playwright searches for a
button whose accessible name is `"upload"`. In the actual Dashboard list UI
(`superset-frontend/src/pages/DashboardList/index.tsx`, identified via Grep as using
`UploadOutlined` for the export action), the export action is labeled as an "Export"
button (tooltip/aria-label), so no button with name `"upload"` exists. The locator never
resolves, causing the single-dashboard export test to time out or fail with a "locator not
found" error when trying to click the non-existent button.
```
</details>
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** superset-frontend/playwright/pages/DashboardListPage.ts
**Line:** 37:41
**Comment:**
*Possible Bug: The row-level export action button is selected using the accessible name `'upload'`, but in the Dashboard list UI the export button is labeled as an export action (e.g., "Export"), so `getByRole('button', { name: 'upload' })` is unlikely to match any button and will cause the single-dashboard export test to fail by timing out when trying to click a non-existent locator.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
```
</details>
<a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38377&comment_hash=d39cef278c68d1c78be8040545866ff2fcb943ae8541bd277df9d5e153504f19&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38377&comment_hash=d39cef278c68d1c78be8040545866ff2fcb943ae8541bd277df9d5e153504f19&reaction=dislike'>👎</a>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #38377:
URL: https://github.com/apache/superset/pull/38377#issuecomment-3994685021
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #38377:
URL: https://github.com/apache/superset/pull/38377#discussion_r2881427097
##########
superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts:
##########
@@ -0,0 +1,403 @@
+/**
+ * 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 {
+ test as testWithAssets,
+ expect,
+} from '../../../helpers/fixtures/testAssets';
+import { DashboardListPage } from '../../../pages/DashboardListPage';
+import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
+import { ImportDatasetModal } from '../../../components/modals/ImportDatasetModal';
+import { Toast } from '../../../components/core/Toast';
+import {
+ apiGetDashboard,
+ apiDeleteDashboard,
+ apiExportDashboards,
+ getDashboardByName,
+ ENDPOINTS,
+} from '../../../helpers/api/dashboard';
+import { createTestDashboard } from './dashboard-test-helpers';
+import { waitForGet, waitForPost } from '../../../helpers/api/intercepts';
+import {
+ expectStatusOneOf,
+ expectValidExportZip,
+} from '../../../helpers/api/assertions';
+import { TIMEOUT } from '../../../utils/constants';
+
+/**
+ * Extend testWithAssets with dashboardListPage navigation (beforeEach equivalent).
+ */
+const test = testWithAssets.extend<{ dashboardListPage: DashboardListPage }>({
+ dashboardListPage: async ({ page }, use) => {
+ const dashboardListPage = new DashboardListPage(page);
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+ await use(dashboardListPage);
+ },
+});
+
+test('should delete a dashboard with confirmation', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create throwaway dashboard for deletion
+ const { id: dashboardId, name: dashboardName } = await createTestDashboard(
+ page,
+ testAssets,
+ test.info(),
+ { prefix: 'test_delete' },
+ );
+
+ // Refresh to see the new dashboard (created via API)
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify dashboard is visible in list
+ await expect(dashboardListPage.getDashboardRow(dashboardName)).toBeVisible();
+
+ // Click delete action button
+ await dashboardListPage.clickDeleteAction(dashboardName);
+
+ // Delete confirmation modal should appear
+ const deleteModal = new DeleteConfirmationModal(page);
+ await deleteModal.waitForVisible();
+
+ // Type "DELETE" to confirm
+ await deleteModal.fillConfirmationInput('DELETE');
+
+ // Click the Delete button
+ await deleteModal.clickDelete();
+
+ // Modal should close
+ await deleteModal.waitForHidden();
+
+ // Verify success toast appears
+ const toast = new Toast(page);
+ await expect(toast.getSuccess()).toBeVisible();
+
+ // Verify dashboard is removed from list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboardName),
+ ).not.toBeVisible();
+
+ // Backend verification: API returns 404
+ await expect
+ .poll(
+ async () => {
+ const response = await apiGetDashboard(page, dashboardId, {
+ failOnStatusCode: false,
+ });
+ return response.status();
+ },
+ { timeout: 10000, message: `Dashboard ${dashboardId} should return 404` },
+ )
+ .toBe(404);
+});
+
+test('should export a dashboard as a zip file', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create throwaway dashboard for export
+ const { name: dashboardName } = await createTestDashboard(
+ page,
+ testAssets,
+ test.info(),
+ { prefix: 'test_export' },
+ );
+
+ // Refresh to see the new dashboard
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify dashboard is visible in list
+ await expect(dashboardListPage.getDashboardRow(dashboardName)).toBeVisible();
+
+ // Set up API response intercept for export endpoint
+ const exportResponsePromise = waitForGet(page, ENDPOINTS.DASHBOARD_EXPORT);
+
+ // Click export action button
+ await dashboardListPage.clickExportAction(dashboardName);
+
+ // Wait for export API response and validate zip contents
+ const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
+ await expectValidExportZip(exportResponse, {
+ resourceDir: 'dashboards',
+ expectedNames: [dashboardName],
+ });
+});
+
+test('should bulk delete multiple dashboards', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ test.setTimeout(60_000);
+
+ // Create 2 throwaway dashboards for bulk delete
+ const [dashboard1, dashboard2] = await Promise.all([
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_delete_1',
+ }),
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_delete_2',
+ }),
+ ]);
+
+ // Refresh to see new dashboards
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify both dashboards are visible in list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).toBeVisible();
+
+ // Enable bulk select mode
+ await dashboardListPage.clickBulkSelectButton();
+
+ // Select both dashboards
+ await dashboardListPage.selectDashboardCheckbox(dashboard1.name);
+ await dashboardListPage.selectDashboardCheckbox(dashboard2.name);
+
+ // Click bulk delete action
+ await dashboardListPage.clickBulkAction('Delete');
+
+ // Delete confirmation modal should appear
+ const deleteModal = new DeleteConfirmationModal(page);
+ await deleteModal.waitForVisible();
+
+ // Type "DELETE" to confirm
+ await deleteModal.fillConfirmationInput('DELETE');
+
+ // Click the Delete button
+ await deleteModal.clickDelete();
+
+ // Modal should close
+ await deleteModal.waitForHidden();
+
+ // Verify success toast appears
+ const toast = new Toast(page);
+ await expect(toast.getSuccess()).toBeVisible();
+
+ // Verify both dashboards are removed from list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).not.toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).not.toBeVisible();
+
+ // Backend verification: Both return 404
+ for (const dashboard of [dashboard1, dashboard2]) {
+ await expect
+ .poll(
+ async () => {
+ const response = await apiGetDashboard(page, dashboard.id, {
+ failOnStatusCode: false,
+ });
+ return response.status();
+ },
+ {
+ timeout: 10000,
+ message: `Dashboard ${dashboard.id} should return 404`,
+ },
+ )
+ .toBe(404);
+ }
+});
+
+test('should bulk export multiple dashboards', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create 2 throwaway dashboards for bulk export
+ const [dashboard1, dashboard2] = await Promise.all([
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_export_1',
+ }),
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_export_2',
+ }),
+ ]);
+
+ // Refresh to see new dashboards
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify both dashboards are visible in list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).toBeVisible();
+
+ // Enable bulk select mode
+ await dashboardListPage.clickBulkSelectButton();
+
+ // Select both dashboards
+ await dashboardListPage.selectDashboardCheckbox(dashboard1.name);
+ await dashboardListPage.selectDashboardCheckbox(dashboard2.name);
+
+ // Set up API response intercept for export endpoint
+ const exportResponsePromise = waitForGet(page, ENDPOINTS.DASHBOARD_EXPORT);
+
+ // Click bulk export action
+ await dashboardListPage.clickBulkAction('Export');
+
+ // Wait for export API response and validate zip contains both dashboards
+ const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
+ await expectValidExportZip(exportResponse, {
+ resourceDir: 'dashboards',
+ minCount: 2,
+ expectedNames: [dashboard1.name, dashboard2.name],
+ });
+});
+
+// Import test uses export-then-reimport approach (no static fixture needed).
+// Uses test.describe only because Playwright's serial mode API requires it -
+// this prevents race conditions when parallel workers import the same dashboard.
+// (Deviation from "avoid describe" guideline is necessary for functional reasons)
+test.describe('import dashboard', () => {
+ test.describe.configure({ mode: 'serial' });
+ test('should import a dashboard from a zip file', async ({
Review Comment:
Copilot is right that serial mode has no effect with a single test. However, it's there as an intentional guard — if someone adds a second import test later, they'd need serial mode to avoid race conditions. The comment already explains the reasoning. Removing it would save 3 lines but lose the safety net.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #38377:
URL: https://github.com/apache/superset/pull/38377#discussion_r2881425712
##########
superset-frontend/playwright/pages/DashboardListPage.ts:
##########
@@ -0,0 +1,139 @@
+/**
+ * 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, Locator } from '@playwright/test';
+import { Button, Table } from '../components/core';
+import { BulkSelect } from '../components/ListView';
+import { URL } from '../utils/urls';
+
+/**
+ * Dashboard List Page object.
+ */
+export class DashboardListPage {
+ private readonly page: Page;
+ private readonly table: Table;
+ readonly bulkSelect: BulkSelect;
+
+ /**
+ * Action button names for getByRole('button', { name })
+ * DashboardList uses Icons.DeleteOutlined, Icons.UploadOutlined, Icons.EditOutlined
+ */
+ private static readonly ACTION_BUTTONS = {
+ DELETE: 'delete',
+ EDIT: 'edit',
+ EXPORT: 'upload',
+ } as const;
Review Comment:
This would actually break the tests as there is no "export" there.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] test(playwright): add dashboard list E2E tests [superset]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #38377:
URL: https://github.com/apache/superset/pull/38377#discussion_r2881381782
##########
superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts:
##########
@@ -0,0 +1,403 @@
+/**
+ * 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 {
+ test as testWithAssets,
+ expect,
+} from '../../../helpers/fixtures/testAssets';
+import { DashboardListPage } from '../../../pages/DashboardListPage';
+import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
+import { ImportDatasetModal } from '../../../components/modals/ImportDatasetModal';
+import { Toast } from '../../../components/core/Toast';
+import {
+ apiGetDashboard,
+ apiDeleteDashboard,
+ apiExportDashboards,
+ getDashboardByName,
+ ENDPOINTS,
+} from '../../../helpers/api/dashboard';
+import { createTestDashboard } from './dashboard-test-helpers';
+import { waitForGet, waitForPost } from '../../../helpers/api/intercepts';
+import {
+ expectStatusOneOf,
+ expectValidExportZip,
+} from '../../../helpers/api/assertions';
+import { TIMEOUT } from '../../../utils/constants';
+
+/**
+ * Extend testWithAssets with dashboardListPage navigation (beforeEach equivalent).
+ */
+const test = testWithAssets.extend<{ dashboardListPage: DashboardListPage }>({
+ dashboardListPage: async ({ page }, use) => {
+ const dashboardListPage = new DashboardListPage(page);
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+ await use(dashboardListPage);
+ },
+});
+
+test('should delete a dashboard with confirmation', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create throwaway dashboard for deletion
+ const { id: dashboardId, name: dashboardName } = await createTestDashboard(
+ page,
+ testAssets,
+ test.info(),
+ { prefix: 'test_delete' },
+ );
+
+ // Refresh to see the new dashboard (created via API)
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify dashboard is visible in list
+ await expect(dashboardListPage.getDashboardRow(dashboardName)).toBeVisible();
+
+ // Click delete action button
+ await dashboardListPage.clickDeleteAction(dashboardName);
+
+ // Delete confirmation modal should appear
+ const deleteModal = new DeleteConfirmationModal(page);
+ await deleteModal.waitForVisible();
+
+ // Type "DELETE" to confirm
+ await deleteModal.fillConfirmationInput('DELETE');
+
+ // Click the Delete button
+ await deleteModal.clickDelete();
+
+ // Modal should close
+ await deleteModal.waitForHidden();
+
+ // Verify success toast appears
+ const toast = new Toast(page);
+ await expect(toast.getSuccess()).toBeVisible();
+
+ // Verify dashboard is removed from list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboardName),
+ ).not.toBeVisible();
+
+ // Backend verification: API returns 404
+ await expect
+ .poll(
+ async () => {
+ const response = await apiGetDashboard(page, dashboardId, {
+ failOnStatusCode: false,
+ });
+ return response.status();
+ },
+ { timeout: 10000, message: `Dashboard ${dashboardId} should return 404` },
+ )
+ .toBe(404);
+});
+
+test('should export a dashboard as a zip file', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create throwaway dashboard for export
+ const { name: dashboardName } = await createTestDashboard(
+ page,
+ testAssets,
+ test.info(),
+ { prefix: 'test_export' },
+ );
+
+ // Refresh to see the new dashboard
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify dashboard is visible in list
+ await expect(dashboardListPage.getDashboardRow(dashboardName)).toBeVisible();
+
+ // Set up API response intercept for export endpoint
+ const exportResponsePromise = waitForGet(page, ENDPOINTS.DASHBOARD_EXPORT);
+
+ // Click export action button
+ await dashboardListPage.clickExportAction(dashboardName);
+
+ // Wait for export API response and validate zip contents
+ const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
+ await expectValidExportZip(exportResponse, {
+ resourceDir: 'dashboards',
+ expectedNames: [dashboardName],
+ });
+});
+
+test('should bulk delete multiple dashboards', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ test.setTimeout(60_000);
+
+ // Create 2 throwaway dashboards for bulk delete
+ const [dashboard1, dashboard2] = await Promise.all([
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_delete_1',
+ }),
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_delete_2',
+ }),
+ ]);
+
+ // Refresh to see new dashboards
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify both dashboards are visible in list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).toBeVisible();
+
+ // Enable bulk select mode
+ await dashboardListPage.clickBulkSelectButton();
+
+ // Select both dashboards
+ await dashboardListPage.selectDashboardCheckbox(dashboard1.name);
+ await dashboardListPage.selectDashboardCheckbox(dashboard2.name);
+
+ // Click bulk delete action
+ await dashboardListPage.clickBulkAction('Delete');
+
+ // Delete confirmation modal should appear
+ const deleteModal = new DeleteConfirmationModal(page);
+ await deleteModal.waitForVisible();
+
+ // Type "DELETE" to confirm
+ await deleteModal.fillConfirmationInput('DELETE');
+
+ // Click the Delete button
+ await deleteModal.clickDelete();
+
+ // Modal should close
+ await deleteModal.waitForHidden();
+
+ // Verify success toast appears
+ const toast = new Toast(page);
+ await expect(toast.getSuccess()).toBeVisible();
+
+ // Verify both dashboards are removed from list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).not.toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).not.toBeVisible();
+
+ // Backend verification: Both return 404
+ for (const dashboard of [dashboard1, dashboard2]) {
+ await expect
+ .poll(
+ async () => {
+ const response = await apiGetDashboard(page, dashboard.id, {
+ failOnStatusCode: false,
+ });
+ return response.status();
+ },
+ {
+ timeout: 10000,
+ message: `Dashboard ${dashboard.id} should return 404`,
+ },
+ )
+ .toBe(404);
+ }
+});
+
+test('should bulk export multiple dashboards', async ({
+ page,
+ dashboardListPage,
+ testAssets,
+}) => {
+ // Create 2 throwaway dashboards for bulk export
+ const [dashboard1, dashboard2] = await Promise.all([
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_export_1',
+ }),
+ createTestDashboard(page, testAssets, test.info(), {
+ prefix: 'bulk_export_2',
+ }),
+ ]);
+
+ // Refresh to see new dashboards
+ await dashboardListPage.goto();
+ await dashboardListPage.waitForTableLoad();
+
+ // Verify both dashboards are visible in list
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard1.name),
+ ).toBeVisible();
+ await expect(
+ dashboardListPage.getDashboardRow(dashboard2.name),
+ ).toBeVisible();
+
+ // Enable bulk select mode
+ await dashboardListPage.clickBulkSelectButton();
+
+ // Select both dashboards
+ await dashboardListPage.selectDashboardCheckbox(dashboard1.name);
+ await dashboardListPage.selectDashboardCheckbox(dashboard2.name);
+
+ // Set up API response intercept for export endpoint
+ const exportResponsePromise = waitForGet(page, ENDPOINTS.DASHBOARD_EXPORT);
+
+ // Click bulk export action
+ await dashboardListPage.clickBulkAction('Export');
+
+ // Wait for export API response and validate zip contains both dashboards
+ const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
+ await expectValidExportZip(exportResponse, {
+ resourceDir: 'dashboards',
+ minCount: 2,
+ expectedNames: [dashboard1.name, dashboard2.name],
+ });
+});
+
+// Import test uses export-then-reimport approach (no static fixture needed).
+// Uses test.describe only because Playwright's serial mode API requires it -
+// this prevents race conditions when parallel workers import the same dashboard.
+// (Deviation from "avoid describe" guideline is necessary for functional reasons)
+test.describe('import dashboard', () => {
+ test.describe.configure({ mode: 'serial' });
+ test('should import a dashboard from a zip file', async ({
Review Comment:
The `test.describe(...).configure({ mode: 'serial' })` wrapper doesn't provide any benefit here since there's only a single test in the block, and it also conflicts with the repo guidance to prefer `test()` over `describe()` for Playwright tests. Consider removing the `describe`/`serial` wrapper (and the related comment) and keeping this as a top-level `test()`; if you later add multiple import tests that truly need serial execution, reintroduce `test.describe.configure({ mode: 'serial' })` then.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org