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();
+});

Reply via email to