mirror of
https://github.com/apache/superset.git
synced 2026-05-07 08:54:23 +00:00
217 lines
7.6 KiB
TypeScript
217 lines
7.6 KiB
TypeScript
/**
|
|
* 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 { 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 { getDatabaseByName } from '../../helpers/api/database';
|
|
import { apiExecuteSql } from '../../helpers/api/sqllab';
|
|
|
|
interface ExamplesSetupResult {
|
|
tableName: string;
|
|
dbId: number;
|
|
createDatasetPage: CreateDatasetPage;
|
|
}
|
|
|
|
/**
|
|
* Creates a temporary table in the examples database via SQL Lab,
|
|
* then navigates to the create dataset wizard with it pre-selected.
|
|
*
|
|
* Requires `allow_dml=True` on the examples database (configured in CI setup).
|
|
*
|
|
* @param page - Playwright page instance
|
|
* @param testAssets - Test assets tracker for cleanup
|
|
* @param testInfo - Test info for parallelIndex to avoid name collisions
|
|
* @returns Setup result with table name, database ID, and page object
|
|
*/
|
|
async function setupExamplesDataset(
|
|
page: Page,
|
|
_testAssets: TestAssets,
|
|
testInfo: TestInfo,
|
|
): Promise<ExamplesSetupResult> {
|
|
// Look up the examples database (always available in CI via load_examples)
|
|
const examplesDb = await getDatabaseByName(page, 'examples');
|
|
if (!examplesDb) {
|
|
throw new Error(
|
|
'Examples database not found. Ensure "superset load_examples" has run.',
|
|
);
|
|
}
|
|
const dbId = examplesDb.id;
|
|
|
|
// Create a uniquely-named temporary table via SQL Lab
|
|
const uniqueSuffix = `${Date.now()}_${testInfo.parallelIndex}`;
|
|
const tableName = `test_pw_${uniqueSuffix}`;
|
|
|
|
// CI examples DB is always PostgreSQL, so 'public' is the correct schema.
|
|
const createTableRes = await apiExecuteSql(
|
|
page,
|
|
dbId,
|
|
`CREATE TABLE ${tableName} AS SELECT 1 AS id, 'test' AS name`,
|
|
'public',
|
|
);
|
|
if (!createTableRes.ok()) {
|
|
const errorBody = await createTableRes.json().catch(() => ({}));
|
|
throw new Error(
|
|
`Failed to create temp table "${tableName}": ${JSON.stringify(errorBody)}`,
|
|
);
|
|
}
|
|
|
|
// Navigate to create dataset page
|
|
const createDatasetPage = new CreateDatasetPage(page);
|
|
await createDatasetPage.goto();
|
|
await createDatasetPage.waitForPageLoad();
|
|
|
|
// Select the examples database, public schema, and temp table.
|
|
// Schema is 'public' because the CI examples DB is always PostgreSQL.
|
|
await createDatasetPage.selectDatabase('examples');
|
|
await createDatasetPage.selectSchema('public');
|
|
await createDatasetPage.selectTable(tableName);
|
|
|
|
return { tableName, dbId, createDatasetPage };
|
|
}
|
|
|
|
/**
|
|
* Drop a temporary table created during test setup.
|
|
* Uses failOnStatusCode: false so cleanup doesn't throw if the table was already removed.
|
|
*/
|
|
async function dropTempTable(
|
|
page: Page,
|
|
dbId: number,
|
|
tableName: string,
|
|
): Promise<void> {
|
|
// Schema matches 'public' used in setupExamplesDataset (CI examples DB is PostgreSQL).
|
|
await apiExecuteSql(
|
|
page,
|
|
dbId,
|
|
`DROP TABLE IF EXISTS ${tableName}`,
|
|
'public',
|
|
{ failOnStatusCode: false },
|
|
);
|
|
}
|
|
|
|
// Both tests create a temp table and use the dataset wizard, so they must run serially.
|
|
// Uses test.describe only because Playwright's serial mode API requires it -
|
|
// (Deviation from "avoid describe" guideline is necessary for functional reasons)
|
|
test.describe('create dataset wizard', () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
test('should create a dataset via wizard', async ({ page, testAssets }) => {
|
|
const { tableName, dbId, createDatasetPage } = await setupExamplesDataset(
|
|
page,
|
|
testAssets,
|
|
test.info(),
|
|
);
|
|
|
|
// Set up response intercept to capture new dataset ID
|
|
const createResponsePromise = waitForPost(page, ENDPOINTS.DATASET, {
|
|
pathMatch: true,
|
|
});
|
|
|
|
// Click "Create and explore dataset" button
|
|
await createDatasetPage.clickCreateAndExploreDataset();
|
|
|
|
// Wait for dataset creation and capture ID for cleanup
|
|
const createResponse = expectStatusOneOf(
|
|
await createResponsePromise,
|
|
[200, 201],
|
|
);
|
|
const createBody = await createResponse.json();
|
|
const newDatasetId = createBody.result?.id ?? createBody.id;
|
|
|
|
if (newDatasetId) {
|
|
testAssets.trackDataset(newDatasetId);
|
|
}
|
|
|
|
// Verify we navigated to Chart Creation page with dataset pre-selected
|
|
await page.waitForURL(/.*\/chart\/add.*/);
|
|
const chartCreationPage = new ChartCreationPage(page);
|
|
await chartCreationPage.waitForPageLoad();
|
|
|
|
// Verify the dataset is pre-selected
|
|
await chartCreationPage.expectDatasetSelected(tableName);
|
|
|
|
// Select a visualization type and create chart
|
|
await chartCreationPage.selectVizType('Table');
|
|
|
|
// Click "Create new chart" to go to Explore
|
|
await chartCreationPage.clickCreateNewChart();
|
|
|
|
// Verify we navigated to Explore page
|
|
await page.waitForURL(/.*\/explore\/.*/);
|
|
const explorePage = new ExplorePage(page);
|
|
await explorePage.waitForPageLoad();
|
|
|
|
// Verify the dataset name is shown in Explore
|
|
const loadedDatasetName = await explorePage.getDatasetName();
|
|
expect(loadedDatasetName).toContain(tableName);
|
|
|
|
// Clean up temp table (dataset cleanup handled by testAssets)
|
|
await dropTempTable(page, dbId, tableName);
|
|
});
|
|
|
|
test('should create a dataset without exploring', async ({
|
|
page,
|
|
testAssets,
|
|
}) => {
|
|
const { tableName, dbId, createDatasetPage } = await setupExamplesDataset(
|
|
page,
|
|
testAssets,
|
|
test.info(),
|
|
);
|
|
|
|
// Set up response intercept to capture dataset ID
|
|
const createResponsePromise = waitForPost(page, ENDPOINTS.DATASET, {
|
|
pathMatch: true,
|
|
});
|
|
|
|
// Click "Create dataset" (not explore)
|
|
await createDatasetPage.clickCreateDataset();
|
|
|
|
// Capture dataset ID from response for cleanup
|
|
const createResponse = expectStatusOneOf(
|
|
await createResponsePromise,
|
|
[200, 201],
|
|
);
|
|
const createBody = await createResponse.json();
|
|
const datasetId = createBody.result?.id ?? createBody.id;
|
|
if (datasetId) {
|
|
testAssets.trackDataset(datasetId);
|
|
}
|
|
|
|
// Verify redirect to dataset list (not chart creation)
|
|
// Note: "Create dataset" action does not show a toast
|
|
await page.waitForURL(/.*tablemodelview\/list.*/);
|
|
|
|
// Wait for table load, verify row visible
|
|
const datasetListPage = new DatasetListPage(page);
|
|
await datasetListPage.waitForTableLoad();
|
|
await expect(datasetListPage.getDatasetRow(tableName)).toBeVisible();
|
|
|
|
// Clean up temp table (dataset cleanup handled by testAssets)
|
|
await dropTempTable(page, dbId, tableName);
|
|
});
|
|
});
|