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 <[email protected]> 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 <[email protected]> --- .../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(); +});
