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 2026/04/02 19:00:14 UTC

(superset) branch master updated: chore(tests): promote Playwright experimental tests to stable (#38924)

This is an automated email from the ASF dual-hosted git repository.

jli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 5662ecab154 chore(tests): promote Playwright experimental tests to stable (#38924)
5662ecab154 is described below

commit 5662ecab15458071383e51d8b74918cf822e6180
Author: Joe Li <jo...@preset.io>
AuthorDate: Thu Apr 2 12:00:06 2026 -0700

    chore(tests): promote Playwright experimental tests to stable (#38924)
    
    Co-authored-by: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
 .../cypress/e2e/chart_list/list.test.ts            | 171 ---------------------
 .../cypress/e2e/dataset/dataset_list.test.ts       |  42 -----
 .../cypress-base/cypress/e2e/explore/utils.ts      |  33 +---
 .../cypress-base/cypress/utils/index.ts            |  22 ---
 .../cypress-base/cypress/utils/urls.ts             |   5 -
 .../playwright/components/modals/index.ts          |   3 +
 .../playwright/pages/ChartListPage.ts              |  39 ++++-
 .../{experimental => }/chart/chart-list.spec.ts    |  73 +++++++--
 .../{experimental => }/chart/chart-test-helpers.ts |   6 +-
 .../dashboard/dashboard-list.spec.ts               |  21 ++-
 .../dashboard/dashboard-test-helpers.ts            |   4 +-
 .../{experimental => }/dashboard/export.spec.ts    |   6 +-
 .../{experimental => }/dashboard/theme.spec.ts     |  12 +-
 .../dataset/create-dataset.spec.ts                 |  20 +--
 .../dataset/dataset-list.spec.ts                   |  57 +++----
 .../dataset/dataset-test-helpers.ts                |   4 +-
 16 files changed, 163 insertions(+), 355 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
deleted file mode 100644
index 9f507bca4ab..00000000000
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-/**
- * 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 { CHART_LIST } from 'cypress/utils/urls';
-import { setGridMode, toggleBulkSelect } from 'cypress/utils';
-import {
-  setFilter,
-  interceptBulkDelete,
-  interceptUpdate,
-  interceptDelete,
-  interceptFiltering,
-  interceptFavoriteStatus,
-} from '../explore/utils';
-
-function orderAlphabetical() {
-  setFilter('Sort', 'Alphabetical');
-}
-
-function openProperties() {
-  cy.get('[aria-label="more"]').eq(0).click();
-  cy.getBySel('chart-list-edit-option').click();
-}
-
-function openMenu() {
-  cy.get('[aria-label="more"]').eq(0).click();
-}
-
-function confirmDelete() {
-  cy.getBySel('delete-modal-input').type('DELETE');
-  cy.getBySel('modal-confirm-button').click();
-}
-
-function visitChartList() {
-  interceptFiltering();
-  interceptFavoriteStatus();
-  cy.visit(CHART_LIST);
-  cy.wait('@filtering');
-  cy.wait('@favoriteStatus');
-}
-
-describe('Charts list', () => {
-  describe('common actions', () => {
-    beforeEach(() => {
-      visitChartList();
-    });
-
-    it('should bulk delete correctly', () => {
-      cy.createSampleCharts([0, 1, 2, 3]);
-
-      interceptBulkDelete();
-      toggleBulkSelect();
-
-      // bulk deletes in card-view
-      setGridMode('card');
-      orderAlphabetical();
-
-      cy.getBySel('skeleton-card').should('not.exist');
-      cy.getBySel('styled-card').contains('1 - Sample chart').click();
-      cy.getBySel('styled-card').contains('2 - Sample chart').click();
-      cy.getBySel('bulk-select-action').contains('Delete').click();
-      confirmDelete();
-      cy.wait('@bulkDelete');
-      cy.getBySel('styled-card')
-        .eq(1)
-        .should('not.contain', '1 - Sample chart');
-      cy.getBySel('styled-card')
-        .eq(2)
-        .should('not.contain', '2 - Sample chart');
-
-      // bulk deletes in list-view
-      setGridMode('list');
-      cy.get('.loading').should('not.exist');
-      cy.getBySel('table-row').contains('3 - Sample chart').should('exist');
-      cy.getBySel('table-row').contains('4 - Sample chart').should('exist');
-      cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click();
-      cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
-      cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
-      confirmDelete();
-      cy.wait('@bulkDelete');
-      cy.get('.loading').should('exist');
-      cy.get('.loading').should('not.exist');
-      cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
-      cy.getBySel('table-row').eq(1).should('not.contain', '4 - Sample chart');
-    });
-
-    it('should delete correctly in card mode', () => {
-      cy.createSampleCharts([0, 1]);
-      interceptDelete();
-
-      // deletes in card-view
-      setGridMode('card');
-      orderAlphabetical();
-
-      cy.getBySel('styled-card').contains('1 - Sample chart');
-      openMenu();
-      cy.getBySel('chart-list-delete-option').click();
-      confirmDelete();
-      cy.wait('@delete');
-      cy.getBySel('styled-card')
-        .contains('1 - Sample chart')
-        .should('not.exist');
-    });
-
-    it('should delete correctly in list mode', () => {
-      cy.createSampleCharts([2, 3]);
-      interceptDelete();
-      cy.getBySel('sort-header').contains('Name').click();
-
-      // Modal closes immediately without this
-      cy.wait(2000);
-
-      cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
-      cy.getBySel('delete').eq(0).click();
-      confirmDelete();
-      cy.wait('@delete');
-      cy.get('.loading').should('exist');
-      cy.get('.loading').should('not.exist');
-      cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
-    });
-
-    it('should edit correctly', () => {
-      cy.createSampleCharts([0]);
-      interceptUpdate();
-
-      // edits in card-view
-      setGridMode('card');
-      orderAlphabetical();
-      cy.getBySel('skeleton-card').should('not.exist');
-      cy.getBySel('styled-card').eq(0).contains('1 - Sample chart');
-
-      // change title
-      openProperties();
-      cy.getBySel('properties-modal-name-input').type(' | EDITED');
-      cy.get('button:contains("Save")').click();
-      cy.wait('@update');
-      cy.getBySel('styled-card').eq(0).contains('1 - Sample chart | EDITED');
-
-      // edits in list-view
-      setGridMode('list');
-      // Wait for list view to fully render after mode change
-      cy.get('.loading').should('not.exist');
-      cy.getBySel('table-row').should('be.visible');
-      // Target the specific row by chart title to avoid flakiness from row ordering
-      cy.getBySel('table-row')
-        .contains('1 - Sample chart | EDITED')
-        .parents('[data-test="table-row"]')
-        .find('[data-test="edit-alt"]')
-        .click();
-      cy.getBySel('properties-modal-name-input').clear();
-      cy.getBySel('properties-modal-name-input').type('1 - Sample chart');
-      cy.get('button:contains("Save")').click();
-      cy.wait('@update');
-      cy.getBySel('table-row').contains('1 - Sample chart').should('exist');
-    });
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts
deleted file mode 100644
index 6bf0419cdda..00000000000
--- a/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * 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 { DATASET_LIST_PATH } from 'cypress/utils/urls';
-
-describe('Dataset list', () => {
-  before(() => {
-    cy.visit(DATASET_LIST_PATH);
-  });
-
-  xit('should open Explore on dataset name click', () => {
-    cy.intercept('**/api/v1/explore/**').as('explore');
-    cy.get('[data-test="listview-table"] [data-test="internal-link"]')
-      .contains('birth_names')
-      .click();
-    cy.wait('@explore');
-    cy.get('[data-test="datasource-control"] .title-select').contains(
-      'birth_names',
-    );
-    cy.get('.metric-option-label').first().contains('COUNT(*)');
-    cy.get('.column-option-label').first().contains('ds');
-    cy.get('[data-test="fast-viz-switcher"] > div:not([role="button"]')
-      .contains('Table')
-      .should('be.visible');
-  });
-});
diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts
index 93dbe99baa2..5a4df482ec0 100644
--- a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts
@@ -23,18 +23,6 @@ export function interceptFiltering() {
   cy.intercept('GET', `**/api/v1/chart/?q=*`).as('filtering');
 }
 
-export function interceptBulkDelete() {
-  cy.intercept('DELETE', `**/api/v1/chart/?q=*`).as('bulkDelete');
-}
-
-export function interceptDelete() {
-  cy.intercept('DELETE', `**/api/v1/chart/*`).as('delete');
-}
-
-export function interceptFavoriteStatus() {
-  cy.intercept('GET', '**/api/v1/chart/favorite_status/*').as('favoriteStatus');
-}
-
 export function interceptUpdate() {
   cy.intercept('PUT', `**/api/v1/chart/*`).as('update');
 }
@@ -43,32 +31,13 @@ export const interceptV1ChartData = (alias = 'v1Data') => {
   cy.intercept('**/api/v1/chart/data*').as(alias);
 };
 
-export function interceptExploreJson(alias = 'getJson') {
-  cy.intercept('POST', `**/superset/explore_json/**`).as(alias);
-}
-
-export const interceptFormDataKey = () => {
-  cy.intercept('POST', '**/api/v1/explore/form_data').as('formDataKey');
-};
-
-export function interceptExploreGet() {
+function interceptExploreGet() {
   cy.intercept({
     method: 'GET',
     url: /.*\/api\/v1\/explore\/\?(form_data_key|dashboard_page_id|slice_id)=.*/,
   }).as('getExplore');
 }
 
-export function setFilter(filter: string, option: string) {
-  interceptFiltering();
-
-  cy.get(`[aria-label^="${filter}"]`).first().click();
-  cy.get(`.ant-select-item-option[title="${option}"]`).first().click({
-    force: true,
-  });
-
-  cy.wait('@filtering');
-}
-
 export function saveChartToDashboard(chartName: string, dashboardName: string) {
   interceptDashboardGet();
   interceptUpdate();
diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts
index 00ff58ab9aa..0c12c23aba7 100644
--- a/superset-frontend/cypress-base/cypress/utils/index.ts
+++ b/superset-frontend/cypress-base/cypress/utils/index.ts
@@ -25,28 +25,6 @@ export interface ChartSpec {
   viz: string;
 }
 
-const viewTypeIcons = {
-  card: 'appstore',
-  list: 'unordered-list',
-};
-
-export function setGridMode(type: 'card' | 'list') {
-  const icon = viewTypeIcons[type];
-  cy.get(`[aria-label="${icon}"]`).click();
-}
-
-export function toggleBulkSelect() {
-  cy.getBySel('bulk-select').click();
-}
-
-export function clearAllInputs() {
-  cy.get('body').then($body => {
-    if ($body.find('.ant-select-clear').length) {
-      cy.get('.ant-select-clear').click({ multiple: true, force: true });
-    }
-  });
-}
-
 const toSlicelike = ($chart: JQuery<HTMLElement>): Slice => {
   const chartId = $chart.attr('data-test-chart-id');
   const vizType = $chart.attr('data-test-viz-type');
diff --git a/superset-frontend/cypress-base/cypress/utils/urls.ts b/superset-frontend/cypress-base/cypress/utils/urls.ts
index 1a585f40479..66110bcc64b 100644
--- a/superset-frontend/cypress-base/cypress/utils/urls.ts
+++ b/superset-frontend/cypress-base/cypress/utils/urls.ts
@@ -25,8 +25,3 @@ export const SUPPORTED_CHARTS_DASHBOARD =
   '/superset/dashboard/supported_charts_dash/';
 export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/';
 export const DATABASE_LIST = '/databaseview/list';
-export const DATASET_LIST_PATH = 'tablemodelview/list';
-export const ALERT_LIST = '/alert/list/';
-export const REPORT_LIST = '/report/list/';
-export const LOGIN = '/login/';
-export const REGISTER = '/register/';
diff --git a/superset-frontend/playwright/components/modals/index.ts b/superset-frontend/playwright/components/modals/index.ts
index a8f8f1576f3..41074401a7d 100644
--- a/superset-frontend/playwright/components/modals/index.ts
+++ b/superset-frontend/playwright/components/modals/index.ts
@@ -18,6 +18,9 @@
  */
 
 // Specific modal implementations
+export { ChartPropertiesModal } from './ChartPropertiesModal';
+export { ConfirmDialog } from './ConfirmDialog';
 export { DeleteConfirmationModal } from './DeleteConfirmationModal';
 export { DuplicateDatasetModal } from './DuplicateDatasetModal';
+export { EditDatasetModal } from './EditDatasetModal';
 export { ImportDatasetModal } from './ImportDatasetModal';
diff --git a/superset-frontend/playwright/pages/ChartListPage.ts b/superset-frontend/playwright/pages/ChartListPage.ts
index 74f75181d24..49c3578cfb1 100644
--- a/superset-frontend/playwright/pages/ChartListPage.ts
+++ b/superset-frontend/playwright/pages/ChartListPage.ts
@@ -47,7 +47,7 @@ export class ChartListPage {
   }
 
   /**
-   * Navigate to the chart list page.
+   * Navigate to the chart list page in table view.
    * Forces table view via URL parameter to avoid card view default
    * (ListviewsDefaultCardView feature flag may enable card view).
    */
@@ -55,6 +55,13 @@ export class ChartListPage {
     await this.page.goto(`${URL.CHART_LIST}?viewMode=table`);
   }
 
+  /**
+   * Navigate to the chart list page in card view.
+   */
+  async gotoCardView(): Promise<void> {
+    await this.page.goto(`${URL.CHART_LIST}?viewMode=card`);
+  }
+
   /**
    * Wait for the table to load
    * @param options - Optional wait options
@@ -63,6 +70,16 @@ export class ChartListPage {
     await this.table.waitForVisible(options);
   }
 
+  /**
+   * Wait for card view to finish loading.
+   */
+  async waitForCardLoad(options?: { timeout?: number }): Promise<void> {
+    await this.page
+      .locator('[data-test="styled-card"]')
+      .first()
+      .waitFor({ state: 'visible', ...options });
+  }
+
   /**
    * Gets a chart row locator by name.
    * Returns a Locator that tests can use with expect().toBeVisible(), etc.
@@ -129,4 +146,24 @@ export class ChartListPage {
   async clickBulkAction(actionName: string): Promise<void> {
     await this.bulkSelect.clickAction(actionName);
   }
+
+  // --- Card view methods ---
+
+  /**
+   * Gets a chart card locator by name (card view).
+   */
+  getChartCard(chartName: string): Locator {
+    return this.page
+      .locator('[data-test="styled-card"]')
+      .filter({ hasText: chartName });
+  }
+
+  /**
+   * Clicks the edit option in a chart card's dropdown menu (card view).
+   */
+  async clickCardEditAction(chartName: string): Promise<void> {
+    const card = this.getChartCard(chartName);
+    await card.locator('[aria-label="more"]').click();
+    await this.page.locator('[data-test="chart-list-edit-option"]').click();
+  }
 }
diff --git a/superset-frontend/playwright/tests/experimental/chart/chart-list.spec.ts b/superset-frontend/playwright/tests/chart/chart-list.spec.ts
similarity index 79%
rename from superset-frontend/playwright/tests/experimental/chart/chart-list.spec.ts
rename to superset-frontend/playwright/tests/chart/chart-list.spec.ts
index b1476db4f8f..57f7b2dd2ea 100644
--- a/superset-frontend/playwright/tests/experimental/chart/chart-list.spec.ts
+++ b/superset-frontend/playwright/tests/chart/chart-list.spec.ts
@@ -17,21 +17,20 @@
  * under the License.
  */
 
+import { testWithAssets, expect } from '../../helpers/fixtures';
+import { ChartListPage } from '../../pages/ChartListPage';
 import {
-  test as testWithAssets,
-  expect,
-} from '../../../helpers/fixtures/testAssets';
-import { ChartListPage } from '../../../pages/ChartListPage';
-import { ChartPropertiesModal } from '../../../components/modals/ChartPropertiesModal';
-import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
-import { Toast } from '../../../components/core/Toast';
-import { apiGetChart, ENDPOINTS } from '../../../helpers/api/chart';
+  ChartPropertiesModal,
+  DeleteConfirmationModal,
+} from '../../components/modals';
+import { Toast } from '../../components/core';
+import { apiGetChart, ENDPOINTS } from '../../helpers/api/chart';
 import { createTestChart } from './chart-test-helpers';
-import { waitForGet, waitForPut } from '../../../helpers/api/intercepts';
+import { waitForGet, waitForPut } from '../../helpers/api/intercepts';
 import {
   expectStatusOneOf,
   expectValidExportZip,
-} from '../../../helpers/api/assertions';
+} from '../../helpers/api/assertions';
 
 /**
  * Extend testWithAssets with chartListPage navigation (beforeEach equivalent).
@@ -261,6 +260,60 @@ test('should bulk delete multiple charts', async ({
   }
 });
 
+test('should edit chart name from card view', async ({ page, testAssets }) => {
+  // Create throwaway chart for editing
+  const { id: chartId, name: chartName } = await createTestChart(
+    page,
+    testAssets,
+    test.info(),
+    { prefix: 'test_card_edit' },
+  );
+
+  // Navigate to card view (not table view)
+  const cardListPage = new ChartListPage(page);
+  await cardListPage.gotoCardView();
+  await cardListPage.waitForCardLoad();
+
+  // Verify chart card is visible
+  await expect(cardListPage.getChartCard(chartName)).toBeVisible();
+
+  // Open card dropdown and click edit
+  await cardListPage.clickCardEditAction(chartName);
+
+  // Wait for properties modal to be ready
+  const propertiesModal = new ChartPropertiesModal(page);
+  await propertiesModal.waitForReady();
+
+  // Edit the chart name
+  const newName = `card_renamed_${Date.now()}_${test.info().parallelIndex}`;
+  await propertiesModal.fillName(newName);
+
+  // Set up response intercept for save
+  const saveResponsePromise = waitForPut(page, `${ENDPOINTS.CHART}${chartId}`);
+
+  // Click Save button
+  await propertiesModal.clickSave();
+
+  // Wait for save to complete and verify success
+  expectStatusOneOf(await saveResponsePromise, [200, 201]);
+
+  // Modal should close
+  await propertiesModal.waitForHidden();
+
+  // Verify success toast appears
+  const toast = new Toast(page);
+  await expect(toast.getSuccess()).toBeVisible();
+
+  // Verify the renamed card appears in card view and old name is gone
+  await expect(cardListPage.getChartCard(newName)).toBeVisible();
+  await expect(cardListPage.getChartCard(chartName)).not.toBeVisible();
+
+  // Backend verification: API returns updated name
+  const response = await apiGetChart(page, chartId);
+  const chart = (await response.json()).result;
+  expect(chart.slice_name).toBe(newName);
+});
+
 test('should bulk export multiple charts', async ({
   page,
   chartListPage,
diff --git a/superset-frontend/playwright/tests/experimental/chart/chart-test-helpers.ts b/superset-frontend/playwright/tests/chart/chart-test-helpers.ts
similarity index 92%
rename from superset-frontend/playwright/tests/experimental/chart/chart-test-helpers.ts
rename to superset-frontend/playwright/tests/chart/chart-test-helpers.ts
index fa0dc55e036..5cae0d5f58f 100644
--- a/superset-frontend/playwright/tests/experimental/chart/chart-test-helpers.ts
+++ b/superset-frontend/playwright/tests/chart/chart-test-helpers.ts
@@ -18,9 +18,9 @@
  */
 
 import type { Page, TestInfo } from '@playwright/test';
-import type { TestAssets } from '../../../helpers/fixtures/testAssets';
-import { apiPostChart } from '../../../helpers/api/chart';
-import { getDatasetByName } from '../../../helpers/api/dataset';
+import type { TestAssets } from '../../helpers/fixtures';
+import { apiPostChart } from '../../helpers/api/chart';
+import { getDatasetByName } from '../../helpers/api/dataset';
 
 interface TestChartResult {
   id: number;
diff --git a/superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts b/superset-frontend/playwright/tests/dashboard/dashboard-list.spec.ts
similarity index 95%
rename from superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts
rename to superset-frontend/playwright/tests/dashboard/dashboard-list.spec.ts
index 0ed0c49769d..a305b65ffa0 100644
--- a/superset-frontend/playwright/tests/experimental/dashboard/dashboard-list.spec.ts
+++ b/superset-frontend/playwright/tests/dashboard/dashboard-list.spec.ts
@@ -17,28 +17,27 @@
  * under the License.
  */
 
+import { testWithAssets, expect } from '../../helpers/fixtures';
+import { DashboardListPage } from '../../pages/DashboardListPage';
 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';
+  DeleteConfirmationModal,
+  ImportDatasetModal,
+} from '../../components/modals';
+import { Toast } from '../../components/core';
 import {
   apiGetDashboard,
   apiDeleteDashboard,
   apiExportDashboards,
   getDashboardByName,
   ENDPOINTS,
-} from '../../../helpers/api/dashboard';
+} from '../../helpers/api/dashboard';
 import { createTestDashboard } from './dashboard-test-helpers';
-import { waitForGet, waitForPost } from '../../../helpers/api/intercepts';
+import { waitForGet, waitForPost } from '../../helpers/api/intercepts';
 import {
   expectStatusOneOf,
   expectValidExportZip,
-} from '../../../helpers/api/assertions';
-import { TIMEOUT } from '../../../utils/constants';
+} from '../../helpers/api/assertions';
+import { TIMEOUT } from '../../utils/constants';
 
 /**
  * Extend testWithAssets with dashboardListPage navigation (beforeEach equivalent).
diff --git a/superset-frontend/playwright/tests/experimental/dashboard/dashboard-test-helpers.ts b/superset-frontend/playwright/tests/dashboard/dashboard-test-helpers.ts
similarity index 93%
rename from superset-frontend/playwright/tests/experimental/dashboard/dashboard-test-helpers.ts
rename to superset-frontend/playwright/tests/dashboard/dashboard-test-helpers.ts
index fcbb07601eb..9f45661342e 100644
--- a/superset-frontend/playwright/tests/experimental/dashboard/dashboard-test-helpers.ts
+++ b/superset-frontend/playwright/tests/dashboard/dashboard-test-helpers.ts
@@ -18,8 +18,8 @@
  */
 
 import type { Page, TestInfo } from '@playwright/test';
-import type { TestAssets } from '../../../helpers/fixtures/testAssets';
-import { apiPostDashboard } from '../../../helpers/api/dashboard';
+import type { TestAssets } from '../../helpers/fixtures';
+import { apiPostDashboard } from '../../helpers/api/dashboard';
 
 interface TestDashboardResult {
   id: number;
diff --git a/superset-frontend/playwright/tests/experimental/dashboard/export.spec.ts b/superset-frontend/playwright/tests/dashboard/export.spec.ts
similarity index 94%
rename from superset-frontend/playwright/tests/experimental/dashboard/export.spec.ts
rename to superset-frontend/playwright/tests/dashboard/export.spec.ts
index 992aaca5097..1687bf2ecac 100644
--- a/superset-frontend/playwright/tests/experimental/dashboard/export.spec.ts
+++ b/superset-frontend/playwright/tests/dashboard/export.spec.ts
@@ -18,9 +18,9 @@
  */
 
 import { test, expect } from '@playwright/test';
-import { DashboardPage } from '../../../pages/DashboardPage';
-import { Toast } from '../../../components/core';
-import { TIMEOUT } from '../../../utils/constants';
+import { DashboardPage } from '../../pages/DashboardPage';
+import { Toast } from '../../components/core';
+import { TIMEOUT } from '../../utils/constants';
 
 /**
  * Dashboard Export E2E tests.
diff --git a/superset-frontend/playwright/tests/experimental/dashboard/theme.spec.ts b/superset-frontend/playwright/tests/dashboard/theme.spec.ts
similarity index 93%
rename from superset-frontend/playwright/tests/experimental/dashboard/theme.spec.ts
rename to superset-frontend/playwright/tests/dashboard/theme.spec.ts
index edec236e6ca..79d431cb85c 100644
--- a/superset-frontend/playwright/tests/experimental/dashboard/theme.spec.ts
+++ b/superset-frontend/playwright/tests/dashboard/theme.spec.ts
@@ -18,16 +18,16 @@
  */
 
 import { test, expect } from '@playwright/test';
-import { AuthPage } from '../../../pages/AuthPage';
-import { DashboardPage } from '../../../pages/DashboardPage';
-import { apiPostTheme, apiDeleteTheme } from '../../../helpers/api/theme';
+import { AuthPage } from '../../pages/AuthPage';
+import { DashboardPage } from '../../pages/DashboardPage';
+import { apiPostTheme, apiDeleteTheme } from '../../helpers/api/theme';
 import {
   apiPostDashboard,
   apiPutDashboard,
   apiDeleteDashboard,
-} from '../../../helpers/api/dashboard';
-import { apiGet } from '../../../helpers/api/requests';
-import { TIMEOUT } from '../../../utils/constants';
+} from '../../helpers/api/dashboard';
+import { apiGet } from '../../helpers/api/requests';
+import { TIMEOUT } from '../../utils/constants';
 
 /**
  * Dashboard Theme E2E tests.
diff --git a/superset-frontend/playwright/tests/experimental/dataset/create-dataset.spec.ts b/superset-frontend/playwright/tests/dataset/create-dataset.spec.ts
similarity index 91%
rename from superset-frontend/playwright/tests/experimental/dataset/create-dataset.spec.ts
rename to superset-frontend/playwright/tests/dataset/create-dataset.spec.ts
index 4250ddcb674..a1a69e54a93 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/create-dataset.spec.ts
+++ b/superset-frontend/playwright/tests/dataset/create-dataset.spec.ts
@@ -17,17 +17,17 @@
  * under the License.
  */
 
-import { test, expect } from '../../../helpers/fixtures/testAssets';
-import type { TestAssets } from '../../../helpers/fixtures/testAssets';
+import { testWithAssets as test, expect } from '../../helpers/fixtures';
+import type { TestAssets } from '../../helpers/fixtures';
 import type { Page, TestInfo } from '@playwright/test';
-import { ExplorePage } from '../../../pages/ExplorePage';
-import { CreateDatasetPage } from '../../../pages/CreateDatasetPage';
-import { DatasetListPage } from '../../../pages/DatasetListPage';
-import { ChartCreationPage } from '../../../pages/ChartCreationPage';
-import { ENDPOINTS } from '../../../helpers/api/dataset';
-import { waitForPost } from '../../../helpers/api/intercepts';
-import { expectStatusOneOf } from '../../../helpers/api/assertions';
-import { apiPostDatabase } from '../../../helpers/api/database';
+import { ExplorePage } from '../../pages/ExplorePage';
+import { CreateDatasetPage } from '../../pages/CreateDatasetPage';
+import { DatasetListPage } from '../../pages/DatasetListPage';
+import { ChartCreationPage } from '../../pages/ChartCreationPage';
+import { ENDPOINTS } from '../../helpers/api/dataset';
+import { waitForPost } from '../../helpers/api/intercepts';
+import { expectStatusOneOf } from '../../helpers/api/assertions';
+import { apiPostDatabase } from '../../helpers/api/database';
 
 interface GsheetsSetupResult {
   sheetName: string;
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts b/superset-frontend/playwright/tests/dataset/dataset-list.spec.ts
similarity index 92%
rename from superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
rename to superset-frontend/playwright/tests/dataset/dataset-list.spec.ts
index a31e6db4e98..d9b781507ea 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-list.spec.ts
+++ b/superset-frontend/playwright/tests/dataset/dataset-list.spec.ts
@@ -17,37 +17,36 @@
  * under the License.
  */
 
-import {
-  test as testWithAssets,
-  expect,
-} from '../../../helpers/fixtures/testAssets';
+import { testWithAssets, expect } from '../../helpers/fixtures';
 import path from 'path';
-import { DatasetListPage } from '../../../pages/DatasetListPage';
-import { ExplorePage } from '../../../pages/ExplorePage';
-import { ConfirmDialog } from '../../../components/modals/ConfirmDialog';
-import { DeleteConfirmationModal } from '../../../components/modals/DeleteConfirmationModal';
-import { ImportDatasetModal } from '../../../components/modals/ImportDatasetModal';
-import { DuplicateDatasetModal } from '../../../components/modals/DuplicateDatasetModal';
-import { EditDatasetModal } from '../../../components/modals/EditDatasetModal';
-import { Toast } from '../../../components/core/Toast';
+import { DatasetListPage } from '../../pages/DatasetListPage';
+import { ExplorePage } from '../../pages/ExplorePage';
+import {
+  ConfirmDialog,
+  DeleteConfirmationModal,
+  DuplicateDatasetModal,
+  EditDatasetModal,
+  ImportDatasetModal,
+} from '../../components/modals';
+import { Toast } from '../../components/core';
 import {
   apiDeleteDataset,
   apiGetDataset,
   apiPostVirtualDataset,
   getDatasetByName,
   ENDPOINTS,
-} from '../../../helpers/api/dataset';
+} from '../../helpers/api/dataset';
 import { createTestDataset } from './dataset-test-helpers';
 import {
   waitForGet,
   waitForPost,
   waitForPut,
-} from '../../../helpers/api/intercepts';
+} from '../../helpers/api/intercepts';
 import {
   expectStatusOneOf,
   expectValidExportZip,
-} from '../../../helpers/api/assertions';
-import { TIMEOUT } from '../../../utils/constants';
+} from '../../helpers/api/assertions';
+import { TIMEOUT } from '../../utils/constants';
 
 /**
  * Extend testWithAssets with datasetListPage navigation (beforeEach equivalent).
@@ -458,11 +457,12 @@ test.describe('import dataset', () => {
     testAssets,
   }) => {
     // Dataset name from fixture (test_netflix_1768502050965)
-    // Note: Fixture contains a Google Sheets dataset - test will skip if gsheets connector unavailable
+    // Note: Fixture contains a Google Sheets dataset backed by shillelagh[gsheetsapi],
+    // which is a base dependency — import failure fails the test hard (no skip).
     const importedDatasetName = 'test_netflix_1768502050965';
     const fixturePath = path.resolve(
       __dirname,
-      '../../../fixtures/dataset_export.zip',
+      '../../fixtures/dataset_export.zip',
     );
 
     // Cleanup: Delete any existing dataset with the same name from previous runs
@@ -518,25 +518,12 @@ test.describe('import dataset', () => {
       importResponse = await importResponsePromise;
     }
 
-    // Check final import response for gsheets connector errors
+    // Fail hard if dataset import fails.
+    // The fixture contains a gsheets dataset; shillelagh[gsheetsapi] is a base
+    // dependency (pyproject.toml), so the engine is always available in CI.
     if (!importResponse.ok()) {
       const errorBody = await importResponse.json().catch(() => ({}));
-      const errorText = JSON.stringify(errorBody);
-      // Skip test if gsheets connector not installed
-      if (
-        errorText.includes('gsheets') ||
-        errorText.includes('No such DB engine') ||
-        errorText.includes('Could not load database driver')
-      ) {
-        await test.info().attach('skip-reason', {
-          body: `Import failed due to missing gsheets connector: ${errorText}`,
-          contentType: 'text/plain',
-        });
-        test.skip();
-        return;
-      }
-      // Re-throw other errors
-      throw new Error(`Import failed: ${errorText}`);
+      throw new Error(`Import failed: ${JSON.stringify(errorBody)}`);
     }
 
     // Modal should close on success
diff --git a/superset-frontend/playwright/tests/experimental/dataset/dataset-test-helpers.ts b/superset-frontend/playwright/tests/dataset/dataset-test-helpers.ts
similarity index 93%
rename from superset-frontend/playwright/tests/experimental/dataset/dataset-test-helpers.ts
rename to superset-frontend/playwright/tests/dataset/dataset-test-helpers.ts
index 8f619580ae8..2bff43f8b28 100644
--- a/superset-frontend/playwright/tests/experimental/dataset/dataset-test-helpers.ts
+++ b/superset-frontend/playwright/tests/dataset/dataset-test-helpers.ts
@@ -18,8 +18,8 @@
  */
 
 import type { Page, TestInfo } from '@playwright/test';
-import type { TestAssets } from '../../../helpers/fixtures/testAssets';
-import { createTestVirtualDataset } from '../../../helpers/api/dataset';
+import type { TestAssets } from '../../helpers/fixtures';
+import { createTestVirtualDataset } from '../../helpers/api/dataset';
 
 interface TestDatasetResult {
   id: number;