You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2025/12/16 22:37:37 UTC
(superset) branch feat-additional-dataset-playwright-tests updated (d0361cb881 -> 92e9764a57)
This is an automated email from the ASF dual-hosted git repository.
jli pushed a change to branch feat-additional-dataset-playwright-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
from d0361cb881 test(playwright): convert and create new dataset list playwright tests (#36196)
new 7adc5de818 test(playwright): add export single dataset E2E test
new d90ec33556 test(playwright): add bulk export datasets E2E test
new 1f8f0fe99d test(playwright): add create dataset wizard E2E test
new 92e9764a57 test(playwright): add edit dataset E2E test
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../playwright/components/core/Checkbox.ts | 80 ++++++++++
.../playwright/components/core/Select.ts | 112 ++++++++++++++
.../playwright/components/core/Tabs.ts | 75 ++++++++++
.../playwright/components/core/Textarea.ts | 86 +++++++++++
.../playwright/components/core/index.ts | 4 +
.../components/modals/ConfirmDialog.ts} | 32 ++--
.../components/modals/EditDatasetModal.ts | 71 +++++++++
.../playwright/pages/CreateDatasetPage.ts | 140 ++++++++++++++++++
.../playwright/pages/DatasetListPage.ts | 81 +++++++++-
.../experimental/dataset/dataset-list.spec.ts | 163 ++++++++++++++++++++-
10 files changed, 831 insertions(+), 13 deletions(-)
create mode 100644 superset-frontend/playwright/components/core/Checkbox.ts
create mode 100644 superset-frontend/playwright/components/core/Select.ts
create mode 100644 superset-frontend/playwright/components/core/Tabs.ts
create mode 100644 superset-frontend/playwright/components/core/Textarea.ts
copy superset-frontend/{src/setup/setupCodeOverrides.ts => playwright/components/modals/ConfirmDialog.ts} (56%)
create mode 100644 superset-frontend/playwright/components/modals/EditDatasetModal.ts
create mode 100644 superset-frontend/playwright/pages/CreateDatasetPage.ts
(superset) 03/04: test(playwright): add create dataset wizard E2E test
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch feat-additional-dataset-playwright-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1f8f0fe99d843de5057b8930539001c6e9d7ab06
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Dec 16 14:10:06 2025 -0800
test(playwright): add create dataset wizard E2E test
Add E2E test for the create dataset wizard flow:
- Navigate to create dataset page via "+ Dataset" button
- Verify cascading select dropdowns (database → schema → table)
- Verify create and explore button availability
Add supporting components:
- Select component for Ant Design combobox interactions (with type-to-filter)
- CreateDatasetPage page object with component-based selectors
Refactor test file to initialize page objects only in tests that use them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
---
.../playwright/components/core/Select.ts | 112 +++++++++++++++++
.../playwright/components/core/index.ts | 1 +
.../playwright/pages/CreateDatasetPage.ts | 140 +++++++++++++++++++++
.../experimental/dataset/dataset-list.spec.ts | 49 +++++++-
4 files changed, 300 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/playwright/components/core/Select.ts b/superset-frontend/playwright/components/core/Select.ts
new file mode 100644
index 0000000000..34cfcff39e
--- /dev/null
+++ b/superset-frontend/playwright/components/core/Select.ts
@@ -0,0 +1,112 @@
+/**
+ * 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 { Locator, Page } from '@playwright/test';
+
+/**
+ * Select component for Ant Design Select/Combobox interactions.
+ */
+export class Select {
+ readonly page: Page;
+ private readonly locator: Locator;
+
+ constructor(page: Page, locator: Locator) {
+ this.page = page;
+ this.locator = locator;
+ }
+
+ /**
+ * Creates a Select from a combobox role with the given accessible name
+ * @param page - The Playwright page
+ * @param name - The accessible name (aria-label or placeholder text)
+ */
+ static fromRole(page: Page, name: string): Select {
+ const locator = page.getByRole('combobox', { name });
+ return new Select(page, locator);
+ }
+
+ /**
+ * Gets the select element locator
+ */
+ get element(): Locator {
+ return this.locator;
+ }
+
+ /**
+ * Opens the dropdown, types to filter, and selects an option.
+ * Handles cases where the option may not be initially visible in the dropdown.
+ * @param optionText - The text of the option to select
+ */
+ async selectOption(optionText: string): Promise<void> {
+ await this.open();
+ await this.type(optionText);
+ await this.clickOption(optionText);
+ }
+
+ /**
+ * Opens the dropdown
+ */
+ async open(): Promise<void> {
+ await this.locator.click();
+ }
+
+ /**
+ * Clicks an option in an already-open dropdown by its exact title/label
+ * @param optionText - The exact text of the option to click
+ */
+ async clickOption(optionText: string): Promise<void> {
+ await this.page.getByTitle(optionText, { exact: true }).click();
+ }
+
+ /**
+ * Closes the dropdown by pressing Escape
+ */
+ async close(): Promise<void> {
+ await this.page.keyboard.press('Escape');
+ }
+
+ /**
+ * Types into the select to filter options (assumes dropdown is open)
+ * @param text - The text to type
+ */
+ async type(text: string): Promise<void> {
+ await this.locator.fill(text);
+ }
+
+ /**
+ * Clears the current selection
+ */
+ async clear(): Promise<void> {
+ await this.locator.clear();
+ }
+
+ /**
+ * Checks if the select is visible
+ */
+ async isVisible(): Promise<boolean> {
+ return this.locator.isVisible();
+ }
+
+ /**
+ * Checks if the select is enabled
+ */
+ async isEnabled(): Promise<boolean> {
+ return this.locator.isEnabled();
+ }
+}
diff --git a/superset-frontend/playwright/components/core/index.ts b/superset-frontend/playwright/components/core/index.ts
index c0bec9f231..de85d30906 100644
--- a/superset-frontend/playwright/components/core/index.ts
+++ b/superset-frontend/playwright/components/core/index.ts
@@ -23,4 +23,5 @@ export { Checkbox } from './Checkbox';
export { Form } from './Form';
export { Input } from './Input';
export { Modal } from './Modal';
+export { Select } from './Select';
export { Table } from './Table';
diff --git a/superset-frontend/playwright/pages/CreateDatasetPage.ts b/superset-frontend/playwright/pages/CreateDatasetPage.ts
new file mode 100644
index 0000000000..cd950a6ba5
--- /dev/null
+++ b/superset-frontend/playwright/pages/CreateDatasetPage.ts
@@ -0,0 +1,140 @@
+/**
+ * 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 '../components/core';
+
+/**
+ * Create Dataset Page object for the dataset creation wizard.
+ */
+export class CreateDatasetPage {
+ readonly page: Page;
+
+ private static readonly PLACEHOLDERS = {
+ DATABASE: 'Select database or type to search databases',
+ CATALOG: 'Select catalog or type to search catalogs',
+ SCHEMA: 'Select schema or type to search schemas',
+ TABLE: 'Select table or type to search tables',
+ } as const;
+
+ constructor(page: Page) {
+ this.page = page;
+ }
+
+ /**
+ * Gets the database selector
+ */
+ getDatabaseSelect(): Select {
+ return Select.fromRole(this.page, CreateDatasetPage.PLACEHOLDERS.DATABASE);
+ }
+
+ /**
+ * Gets the catalog selector
+ */
+ getCatalogSelect(): Select {
+ return Select.fromRole(this.page, CreateDatasetPage.PLACEHOLDERS.CATALOG);
+ }
+
+ /**
+ * Gets the schema selector
+ */
+ getSchemaSelect(): Select {
+ return Select.fromRole(this.page, CreateDatasetPage.PLACEHOLDERS.SCHEMA);
+ }
+
+ /**
+ * Gets the table selector
+ */
+ getTableSelect(): Select {
+ return Select.fromRole(this.page, CreateDatasetPage.PLACEHOLDERS.TABLE);
+ }
+
+ /**
+ * Gets the create and explore button
+ */
+ getCreateAndExploreButton(): Button {
+ return new Button(
+ this.page,
+ this.page.getByRole('button', { name: /Create and explore dataset/i }),
+ );
+ }
+
+ /**
+ * Navigate to the create dataset page
+ */
+ async goto(): Promise<void> {
+ await this.page.goto('dataset/add/');
+ }
+
+ /**
+ * Select a database from the dropdown
+ * @param databaseName - The name of the database to select
+ */
+ async selectDatabase(databaseName: string): Promise<void> {
+ await this.getDatabaseSelect().selectOption(databaseName);
+ }
+
+ /**
+ * Select a schema from the dropdown
+ * @param schemaName - The name of the schema to select
+ */
+ async selectSchema(schemaName: string): Promise<void> {
+ await this.getSchemaSelect().selectOption(schemaName);
+ }
+
+ /**
+ * Select a table from the dropdown
+ * @param tableName - The name of the table to select
+ */
+ async selectTable(tableName: string): Promise<void> {
+ await this.getTableSelect().selectOption(tableName);
+ }
+
+ /**
+ * Click the "Create dataset" button (without exploring)
+ * Uses the dropdown menu to select "Create dataset" option
+ */
+ async clickCreateDataset(): Promise<void> {
+ // Click the dropdown arrow to open the menu
+ const dropdownButton = new Button(
+ this.page,
+ this.page.locator(
+ '.ant-dropdown-trigger, .ant-btn-group .ant-btn:last-child',
+ ),
+ );
+ await dropdownButton.click();
+
+ // Click "Create dataset" option from the dropdown menu
+ await this.page.getByText('Create dataset', { exact: true }).click();
+ }
+
+ /**
+ * Click the "Create and explore dataset" button
+ */
+ async clickCreateAndExploreDataset(): Promise<void> {
+ await this.getCreateAndExploreButton().click();
+ }
+
+ /**
+ * Wait for the page to load
+ */
+ async waitForPageLoad(): Promise<void> {
+ await this.getDatabaseSelect().element.waitFor({ state: 'visible' });
+ }
+}
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
index b4d9fb7e7e..d9dd1d23bb 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
+++ b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
@@ -20,6 +20,7 @@
import { test, expect } from '@playwright/test';
import { DatasetListPage } from '../../../pages/DatasetListPage';
import { ExplorePage } from '../../../pages/ExplorePage';
+import { CreateDatasetPage } from '../../../pages/CreateDatasetPage';
import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
import { DuplicateDatasetModal } from '../../../components/modals/DuplicateDatasetModal';
import { Toast } from '../../../components/core/Toast';
@@ -52,12 +53,10 @@ const TEST_DATASETS = {
// File-scope state (reset in beforeEach)
let datasetListPage: DatasetListPage;
-let explorePage: ExplorePage;
let testResources: { datasetIds: number[] } = { datasetIds: [] };
test.beforeEach(async ({ page }) => {
datasetListPage = new DatasetListPage(page);
- explorePage = new ExplorePage(page);
testResources = { datasetIds: [] }; // Reset for each test
// Navigate to dataset list page
@@ -87,6 +86,8 @@ test.afterEach(async ({ page }) => {
test('should navigate to Explore when dataset name is clicked', async ({
page,
}) => {
+ const explorePage = new ExplorePage(page);
+
// Use existing example dataset (hermetic - loaded in CI via --load-examples)
const datasetName = TEST_DATASETS.EXAMPLE_DATASET;
const dataset = await getDatasetByName(page, datasetName);
@@ -313,3 +314,47 @@ test('should bulk export multiple datasets', async ({ page }) => {
const failure = await download.failure();
expect(failure).toBeNull();
});
+
+test('should navigate the create dataset wizard', async ({ page }) => {
+ const createDatasetPage = new CreateDatasetPage(page);
+
+ // Click the "+ Dataset" button to navigate to create page
+ await page.getByRole('button', { name: 'Dataset' }).click();
+
+ // Wait for create dataset page to load
+ await createDatasetPage.waitForPageLoad();
+
+ // Verify we're on the create dataset page
+ await expect(page).toHaveURL(/dataset\/add/);
+
+ // Verify database select is visible and functional
+ const databaseSelect = createDatasetPage.getDatabaseSelect();
+ await expect(databaseSelect.element).toBeVisible();
+
+ // Select the examples database
+ await createDatasetPage.selectDatabase('examples');
+
+ // Verify schema select appears after database selection
+ const schemaSelect = createDatasetPage.getSchemaSelect();
+ await expect(schemaSelect.element).toBeVisible();
+
+ // Select the main schema
+ await createDatasetPage.selectSchema('main');
+
+ // Verify table select appears after schema selection
+ const tableSelect = createDatasetPage.getTableSelect();
+ await expect(tableSelect.element).toBeVisible();
+
+ // Open table dropdown and verify options are available
+ await tableSelect.open();
+ const tableOptions = page.locator('.ant-select-item-option');
+ await expect(tableOptions.first()).toBeVisible();
+
+ // Close dropdown
+ await tableSelect.close();
+
+ // Verify the create and explore button is visible
+ await expect(
+ createDatasetPage.getCreateAndExploreButton().element,
+ ).toBeVisible();
+});
(superset) 04/04: test(playwright): add edit dataset E2E test
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch feat-additional-dataset-playwright-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 92e9764a57b4a57964846fc6703df5f4666ee822
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Dec 16 14:19:44 2025 -0800
test(playwright): add edit dataset E2E test
Add E2E test for editing a dataset description:
- Click edit action to open DatasourceModal
- Update description field
- Save with confirmation dialog
- Verify toast and persisted change
Add supporting components:
- EditDatasetModal using Modal base class and component abstractions
- ConfirmDialog for reusable OK/Cancel confirmation dialogs
- Textarea component for multi-line text input
- Tabs component for tab navigation
- clickEditAction method in DatasetListPage
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
---
.../playwright/components/core/Tabs.ts | 75 +++++++++++++++++++
.../playwright/components/core/Textarea.ts | 86 ++++++++++++++++++++++
.../playwright/components/core/index.ts | 2 +
.../{core/index.ts => modals/ConfirmDialog.ts} | 35 +++++++--
.../components/modals/EditDatasetModal.ts | 71 ++++++++++++++++++
.../playwright/pages/DatasetListPage.ts | 12 +++
.../experimental/dataset/dataset-list.spec.ts | 53 +++++++++++++
7 files changed, 326 insertions(+), 8 deletions(-)
diff --git a/superset-frontend/playwright/components/core/Tabs.ts b/superset-frontend/playwright/components/core/Tabs.ts
new file mode 100644
index 0000000000..b2fe024911
--- /dev/null
+++ b/superset-frontend/playwright/components/core/Tabs.ts
@@ -0,0 +1,75 @@
+/**
+ * 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 { Locator, Page } from '@playwright/test';
+
+/**
+ * Tabs component for Ant Design tab navigation.
+ */
+export class Tabs {
+ readonly page: Page;
+ private readonly locator: Locator;
+
+ constructor(page: Page, locator?: Locator) {
+ this.page = page;
+ // Default to the tablist role if no specific locator provided
+ this.locator = locator ?? page.getByRole('tablist');
+ }
+
+ /**
+ * Gets the tablist element locator
+ */
+ get element(): Locator {
+ return this.locator;
+ }
+
+ /**
+ * Gets a tab by name
+ * @param tabName - The name/label of the tab
+ */
+ getTab(tabName: string): Locator {
+ return this.page.getByRole('tab', { name: tabName });
+ }
+
+ /**
+ * Clicks a tab by name
+ * @param tabName - The name/label of the tab to click
+ */
+ async clickTab(tabName: string): Promise<void> {
+ await this.getTab(tabName).click();
+ }
+
+ /**
+ * Gets the tab panel content for a given tab
+ * @param tabName - The name/label of the tab
+ */
+ getTabPanel(tabName: string): Locator {
+ return this.page.getByRole('tabpanel', { name: tabName });
+ }
+
+ /**
+ * Checks if a tab is selected
+ * @param tabName - The name/label of the tab
+ */
+ async isSelected(tabName: string): Promise<boolean> {
+ const tab = this.getTab(tabName);
+ const ariaSelected = await tab.getAttribute('aria-selected');
+ return ariaSelected === 'true';
+ }
+}
diff --git a/superset-frontend/playwright/components/core/Textarea.ts b/superset-frontend/playwright/components/core/Textarea.ts
new file mode 100644
index 0000000000..b33df32b8e
--- /dev/null
+++ b/superset-frontend/playwright/components/core/Textarea.ts
@@ -0,0 +1,86 @@
+/**
+ * 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 { Locator, Page } from '@playwright/test';
+
+/**
+ * Textarea component for multi-line text input interactions.
+ */
+export class Textarea {
+ readonly page: Page;
+ private readonly locator: Locator;
+
+ constructor(page: Page, locator: Locator) {
+ this.page = page;
+ this.locator = locator;
+ }
+
+ /**
+ * Creates a Textarea from a name attribute
+ * @param page - The Playwright page
+ * @param name - The name attribute value
+ */
+ static fromName(page: Page, name: string): Textarea {
+ const locator = page.locator(`textarea[name="${name}"]`);
+ return new Textarea(page, locator);
+ }
+
+ /**
+ * Gets the textarea element locator
+ */
+ get element(): Locator {
+ return this.locator;
+ }
+
+ /**
+ * Fills the textarea with text (clears existing content)
+ * @param text - The text to fill
+ */
+ async fill(text: string): Promise<void> {
+ await this.locator.fill(text);
+ }
+
+ /**
+ * Clears the textarea content
+ */
+ async clear(): Promise<void> {
+ await this.locator.clear();
+ }
+
+ /**
+ * Gets the current value of the textarea
+ */
+ async getValue(): Promise<string> {
+ return this.locator.inputValue();
+ }
+
+ /**
+ * Checks if the textarea is visible
+ */
+ async isVisible(): Promise<boolean> {
+ return this.locator.isVisible();
+ }
+
+ /**
+ * Checks if the textarea is enabled
+ */
+ async isEnabled(): Promise<boolean> {
+ return this.locator.isEnabled();
+ }
+}
diff --git a/superset-frontend/playwright/components/core/index.ts b/superset-frontend/playwright/components/core/index.ts
index de85d30906..27a55d20dd 100644
--- a/superset-frontend/playwright/components/core/index.ts
+++ b/superset-frontend/playwright/components/core/index.ts
@@ -25,3 +25,5 @@ export { Input } from './Input';
export { Modal } from './Modal';
export { Select } from './Select';
export { Table } from './Table';
+export { Tabs } from './Tabs';
+export { Textarea } from './Textarea';
diff --git a/superset-frontend/playwright/components/core/index.ts b/superset-frontend/playwright/components/modals/ConfirmDialog.ts
similarity index 55%
copy from superset-frontend/playwright/components/core/index.ts
copy to superset-frontend/playwright/components/modals/ConfirmDialog.ts
index de85d30906..77834123e9 100644
--- a/superset-frontend/playwright/components/core/index.ts
+++ b/superset-frontend/playwright/components/modals/ConfirmDialog.ts
@@ -17,11 +17,30 @@
* under the License.
*/
-// Core Playwright Components for Superset
-export { Button } from './Button';
-export { Checkbox } from './Checkbox';
-export { Form } from './Form';
-export { Input } from './Input';
-export { Modal } from './Modal';
-export { Select } from './Select';
-export { Table } from './Table';
+import { Page } from '@playwright/test';
+import { Modal } from '../core/Modal';
+
+/**
+ * Confirm Dialog component for Ant Design Modal.confirm dialogs.
+ * These are the "OK" / "Cancel" confirmation dialogs used throughout Superset.
+ */
+export class ConfirmDialog extends Modal {
+ constructor(page: Page) {
+ // Modal.confirm uses the same [role="dialog"] selector
+ super(page);
+ }
+
+ /**
+ * Clicks the OK button to confirm
+ */
+ async clickOk(): Promise<void> {
+ await this.clickFooterButton('OK');
+ }
+
+ /**
+ * Clicks the Cancel button to dismiss
+ */
+ async clickCancel(): Promise<void> {
+ await this.clickFooterButton('Cancel');
+ }
+}
diff --git a/superset-frontend/playwright/components/modals/EditDatasetModal.ts b/superset-frontend/playwright/components/modals/EditDatasetModal.ts
new file mode 100644
index 0000000000..a667378912
--- /dev/null
+++ b/superset-frontend/playwright/components/modals/EditDatasetModal.ts
@@ -0,0 +1,71 @@
+/**
+ * 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 { Modal, Tabs, Textarea } from '../core';
+
+/**
+ * Edit Dataset Modal component (DatasourceModal).
+ * Used for editing dataset properties like description, metrics, columns, etc.
+ */
+export class EditDatasetModal extends Modal {
+ private readonly tabs: Tabs;
+
+ constructor(page: Page) {
+ super(page, 'Edit Dataset');
+ this.tabs = new Tabs(page);
+ }
+
+ /**
+ * Click the Save button to save changes
+ */
+ async clickSave(): Promise<void> {
+ await this.clickFooterButton('Save');
+ }
+
+ /**
+ * Click the Cancel button to discard changes
+ */
+ async clickCancel(): Promise<void> {
+ await this.clickFooterButton('Cancel');
+ }
+
+ /**
+ * Get the description textarea component
+ */
+ getDescriptionTextarea(): Textarea {
+ return Textarea.fromName(this.page, 'description');
+ }
+
+ /**
+ * Fill in the description field
+ * @param description - The description text to enter
+ */
+ async fillDescription(description: string): Promise<void> {
+ await this.getDescriptionTextarea().fill(description);
+ }
+
+ /**
+ * Navigate to a specific tab in the modal
+ * @param tabName - The name of the tab (e.g., 'Source', 'Metrics', 'Columns')
+ */
+ async clickTab(tabName: string): Promise<void> {
+ await this.tabs.clickTab(tabName);
+ }
+}
diff --git a/superset-frontend/playwright/pages/DatasetListPage.ts b/superset-frontend/playwright/pages/DatasetListPage.ts
index 89662021be..86a2802461 100644
--- a/superset-frontend/playwright/pages/DatasetListPage.ts
+++ b/superset-frontend/playwright/pages/DatasetListPage.ts
@@ -31,6 +31,7 @@ export class DatasetListPage {
private static readonly SELECTORS = {
DATASET_LINK: '[data-test="internal-link"]',
DELETE_ACTION: '.action-button svg[data-icon="delete"]',
+ EDIT_ACTION: '.action-button svg[data-icon="edit"]',
EXPORT_ACTION: '.action-button svg[data-icon="upload"]',
DUPLICATE_ACTION: '.action-button svg[data-icon="copy"]',
BULK_SELECT_CONTROLS: '[data-test="bulk-select-controls"]',
@@ -93,6 +94,17 @@ export class DatasetListPage {
);
}
+ /**
+ * Clicks the edit action button for a dataset
+ * @param datasetName - The name of the dataset to edit
+ */
+ async clickEditAction(datasetName: string): Promise<void> {
+ await this.table.clickRowAction(
+ datasetName,
+ DatasetListPage.SELECTORS.EDIT_ACTION,
+ );
+ }
+
/**
* Clicks the export action button for a dataset
* @param datasetName - The name of the dataset to export
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
index d9dd1d23bb..5129403ad7 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
+++ b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
@@ -21,8 +21,10 @@ import { test, expect } from '@playwright/test';
import { DatasetListPage } from '../../../pages/DatasetListPage';
import { ExplorePage } from '../../../pages/ExplorePage';
import { CreateDatasetPage } from '../../../pages/CreateDatasetPage';
+import { ConfirmDialog } from '../../../components/modals/ConfirmDialog';
import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
import { DuplicateDatasetModal } from '../../../components/modals/DuplicateDatasetModal';
+import { EditDatasetModal } from '../../../components/modals/EditDatasetModal';
import { Toast } from '../../../components/core/Toast';
import {
apiDeleteDataset,
@@ -358,3 +360,54 @@ test('should navigate the create dataset wizard', async ({ page }) => {
createDatasetPage.getCreateAndExploreButton().element,
).toBeVisible();
});
+
+test('should edit a dataset description', async ({ page }) => {
+ const editModal = new EditDatasetModal(page);
+ const confirmDialog = new ConfirmDialog(page);
+ const toast = new Toast(page);
+
+ // Use existing example dataset
+ const datasetName = TEST_DATASETS.EXAMPLE_DATASET;
+
+ // Verify dataset is visible in list
+ await expect(datasetListPage.getDatasetRow(datasetName)).toBeVisible();
+
+ // Click edit action button
+ await datasetListPage.clickEditAction(datasetName);
+
+ // Edit modal should appear
+ await editModal.waitForVisible();
+
+ // Generate unique description to verify edit works
+ const newDescription = `Test description updated at ${Date.now()}`;
+
+ // Fill in new description
+ await editModal.fillDescription(newDescription);
+
+ // Click Save button
+ await editModal.clickSave();
+
+ // Confirm the save in the confirmation dialog
+ await confirmDialog.waitForVisible();
+ await confirmDialog.clickOk();
+
+ // Modal should close
+ await editModal.waitForHidden();
+
+ // Verify success toast appears
+ const successToast = toast.getSuccess();
+ await expect(successToast).toBeVisible();
+
+ // Verify the description was saved by re-opening the edit modal
+ await datasetListPage.clickEditAction(datasetName);
+ await editModal.waitForVisible();
+
+ // Verify description textarea contains our new description
+ await expect(editModal.getDescriptionTextarea().element).toHaveValue(
+ newDescription,
+ );
+
+ // Close modal without saving
+ await editModal.clickCancel();
+ await editModal.waitForHidden();
+});
(superset) 01/04: test(playwright): add export single dataset E2E test
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch feat-additional-dataset-playwright-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 7adc5de8185cfbff24de775eb1d1198c68e85e2b
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Dec 16 12:47:54 2025 -0800
test(playwright): add export single dataset E2E test
Add test for exporting a single dataset via the action button.
The test verifies:
- Download is triggered when clicking export action
- Downloaded file is a zip file
- Download completes without failure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
---
.../experimental/dataset/dataset-list.spec.ts | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
index 0eb9cc9d88..2bc8eeb389 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
+++ b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
@@ -252,3 +252,29 @@ test('should duplicate a dataset with new name', async ({ page }) => {
// Name should be different (the duplicate name)
expect(duplicateDataFull.result.table_name).toBe(duplicateName);
});
+
+test('should export a single dataset', async ({ page }) => {
+ // Use existing example dataset
+ const datasetName = TEST_DATASETS.EXAMPLE_DATASET;
+
+ // Verify dataset is visible in list
+ await expect(datasetListPage.getDatasetRow(datasetName)).toBeVisible();
+
+ // Set up download handler before triggering export
+ const downloadPromise = page.waitForEvent('download');
+
+ // Click export action button
+ await datasetListPage.clickExportAction(datasetName);
+
+ // Wait for download to complete
+ const download = await downloadPromise;
+
+ // Verify downloaded file is a zip
+ const fileName = download.suggestedFilename();
+ expect(fileName).toMatch(/\.zip$/);
+ expect(fileName).toContain('dataset');
+
+ // Verify download completed successfully (no failure)
+ const failure = await download.failure();
+ expect(failure).toBeNull();
+});
(superset) 02/04: test(playwright): add bulk export datasets E2E test
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch feat-additional-dataset-playwright-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
commit d90ec33556d6584b0a166130be75bbe650e7577c
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Dec 16 13:13:29 2025 -0800
test(playwright): add bulk export datasets E2E test
Add test for bulk exporting datasets via bulk select mode.
The test verifies:
- Bulk select mode can be enabled
- Datasets can be selected via checkboxes
- Export bulk action triggers download
- Downloaded file is a zip file
Add Checkbox component to components/core for checkbox interactions.
Add bulk select methods to DatasetListPage:
- getBulkSelectButton() / clickBulkSelectButton()
- getDatasetCheckbox() / selectDatasetCheckbox()
- getBulkActionButton() / clickBulkAction()
- getBulkSelectControls()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <no...@anthropic.com>
---
.../playwright/components/core/Checkbox.ts | 80 ++++++++++++++++++++++
.../playwright/components/core/index.ts | 1 +
.../playwright/pages/DatasetListPage.ts | 69 ++++++++++++++++++-
.../experimental/dataset/dataset-list.spec.ts | 35 ++++++++++
4 files changed, 184 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/playwright/components/core/Checkbox.ts b/superset-frontend/playwright/components/core/Checkbox.ts
new file mode 100644
index 0000000000..612b8867b1
--- /dev/null
+++ b/superset-frontend/playwright/components/core/Checkbox.ts
@@ -0,0 +1,80 @@
+/**
+ * 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 { Locator } from '@playwright/test';
+
+/**
+ * Checkbox component for checkbox interactions.
+ */
+export class Checkbox {
+ private readonly locator: Locator;
+
+ constructor(locator: Locator) {
+ this.locator = locator;
+ }
+
+ /**
+ * Gets the checkbox element locator
+ */
+ get element(): Locator {
+ return this.locator;
+ }
+
+ /**
+ * Checks the checkbox (ensures it's checked)
+ */
+ async check(): Promise<void> {
+ await this.locator.check();
+ }
+
+ /**
+ * Unchecks the checkbox (ensures it's unchecked)
+ */
+ async uncheck(): Promise<void> {
+ await this.locator.uncheck();
+ }
+
+ /**
+ * Toggles the checkbox state
+ */
+ async toggle(): Promise<void> {
+ await this.locator.click();
+ }
+
+ /**
+ * Checks if the checkbox is checked
+ */
+ async isChecked(): Promise<boolean> {
+ return this.locator.isChecked();
+ }
+
+ /**
+ * Checks if the checkbox is visible
+ */
+ async isVisible(): Promise<boolean> {
+ return this.locator.isVisible();
+ }
+
+ /**
+ * Checks if the checkbox is enabled
+ */
+ async isEnabled(): Promise<boolean> {
+ return this.locator.isEnabled();
+ }
+}
diff --git a/superset-frontend/playwright/components/core/index.ts b/superset-frontend/playwright/components/core/index.ts
index 82a26c2b69..c0bec9f231 100644
--- a/superset-frontend/playwright/components/core/index.ts
+++ b/superset-frontend/playwright/components/core/index.ts
@@ -19,6 +19,7 @@
// Core Playwright Components for Superset
export { Button } from './Button';
+export { Checkbox } from './Checkbox';
export { Form } from './Form';
export { Input } from './Input';
export { Modal } from './Modal';
diff --git a/superset-frontend/playwright/pages/DatasetListPage.ts b/superset-frontend/playwright/pages/DatasetListPage.ts
index a7b6af75a1..89662021be 100644
--- a/superset-frontend/playwright/pages/DatasetListPage.ts
+++ b/superset-frontend/playwright/pages/DatasetListPage.ts
@@ -18,7 +18,7 @@
*/
import { Page, Locator } from '@playwright/test';
-import { Table } from '../components/core';
+import { Button, Checkbox, Table } from '../components/core';
import { URL } from '../utils/urls';
/**
@@ -33,6 +33,8 @@ export class DatasetListPage {
DELETE_ACTION: '.action-button svg[data-icon="delete"]',
EXPORT_ACTION: '.action-button svg[data-icon="upload"]',
DUPLICATE_ACTION: '.action-button svg[data-icon="copy"]',
+ BULK_SELECT_CONTROLS: '[data-test="bulk-select-controls"]',
+ BULK_SELECT_ACTION: '[data-test="bulk-select-action"]',
} as const;
constructor(page: Page) {
@@ -112,4 +114,69 @@ export class DatasetListPage {
DatasetListPage.SELECTORS.DUPLICATE_ACTION,
);
}
+
+ /**
+ * Gets the "Bulk select" button
+ */
+ getBulkSelectButton(): Button {
+ return new Button(
+ this.page,
+ this.page.getByRole('button', { name: 'Bulk select' }),
+ );
+ }
+
+ /**
+ * Clicks the "Bulk select" button to enable bulk selection mode
+ */
+ async clickBulkSelectButton(): Promise<void> {
+ await this.getBulkSelectButton().click();
+ }
+
+ /**
+ * Gets the checkbox for a dataset row
+ * @param datasetName - The name of the dataset
+ */
+ getDatasetCheckbox(datasetName: string): Checkbox {
+ const row = this.table.getRow(datasetName);
+ return new Checkbox(row.getByRole('checkbox'));
+ }
+
+ /**
+ * Selects a dataset's checkbox in bulk select mode
+ * @param datasetName - The name of the dataset to select
+ */
+ async selectDatasetCheckbox(datasetName: string): Promise<void> {
+ await this.getDatasetCheckbox(datasetName).check();
+ }
+
+ /**
+ * Gets a bulk action button by name
+ * @param actionName - The name of the bulk action (e.g., "Export", "Delete")
+ */
+ getBulkActionButton(actionName: string): Button {
+ const bulkControls = this.page.locator(
+ DatasetListPage.SELECTORS.BULK_SELECT_CONTROLS,
+ );
+ return new Button(
+ this.page,
+ bulkControls.locator(DatasetListPage.SELECTORS.BULK_SELECT_ACTION, {
+ hasText: actionName,
+ }),
+ );
+ }
+
+ /**
+ * Clicks a bulk action button by name (e.g., "Export", "Delete")
+ * @param actionName - The name of the bulk action to click
+ */
+ async clickBulkAction(actionName: string): Promise<void> {
+ await this.getBulkActionButton(actionName).click();
+ }
+
+ /**
+ * Gets the bulk select controls locator (for assertions)
+ */
+ getBulkSelectControls(): Locator {
+ return this.page.locator(DatasetListPage.SELECTORS.BULK_SELECT_CONTROLS);
+ }
}
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
index 2bc8eeb389..b4d9fb7e7e 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
+++ b/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
@@ -278,3 +278,38 @@ test('should export a single dataset', async ({ page }) => {
const failure = await download.failure();
expect(failure).toBeNull();
});
+
+test('should bulk export multiple datasets', async ({ page }) => {
+ // Use existing example dataset for bulk export
+ const datasetName = TEST_DATASETS.EXAMPLE_DATASET;
+
+ // Verify dataset is visible in list
+ await expect(datasetListPage.getDatasetRow(datasetName)).toBeVisible();
+
+ // Enable bulk select mode
+ await datasetListPage.clickBulkSelectButton();
+
+ // Verify bulk select controls appear
+ await expect(datasetListPage.getBulkSelectControls()).toBeVisible();
+
+ // Select the dataset checkbox
+ await datasetListPage.selectDatasetCheckbox(datasetName);
+
+ // Set up download handler before triggering bulk export
+ const downloadPromise = page.waitForEvent('download');
+
+ // Click Export bulk action
+ await datasetListPage.clickBulkAction('Export');
+
+ // Wait for download to complete
+ const download = await downloadPromise;
+
+ // Verify downloaded file is a zip
+ const fileName = download.suggestedFilename();
+ expect(fileName).toMatch(/\.zip$/);
+ expect(fileName).toContain('dataset');
+
+ // Verify download completed successfully (no failure)
+ const failure = await download.failure();
+ expect(failure).toBeNull();
+});