mirror of
https://github.com/apache/superset.git
synced 2026-04-16 22:55:52 +00:00
308 lines
9.2 KiB
TypeScript
308 lines
9.2 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 {
|
|
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';
|
|
import { createTestChart } from './chart-test-helpers';
|
|
import { waitForGet, waitForPut } from '../../../helpers/api/intercepts';
|
|
import {
|
|
expectStatusOneOf,
|
|
expectValidExportZip,
|
|
} from '../../../helpers/api/assertions';
|
|
|
|
/**
|
|
* Extend testWithAssets with chartListPage navigation (beforeEach equivalent).
|
|
*/
|
|
const test = testWithAssets.extend<{ chartListPage: ChartListPage }>({
|
|
chartListPage: async ({ page }, use) => {
|
|
const chartListPage = new ChartListPage(page);
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
await use(chartListPage);
|
|
},
|
|
});
|
|
|
|
test('should delete a chart with confirmation', async ({
|
|
page,
|
|
chartListPage,
|
|
testAssets,
|
|
}) => {
|
|
// Create throwaway chart for deletion
|
|
const { id: chartId, name: chartName } = await createTestChart(
|
|
page,
|
|
testAssets,
|
|
test.info(),
|
|
{ prefix: 'test_delete' },
|
|
);
|
|
|
|
// Refresh to see the new chart (created via API)
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
|
|
// Verify chart is visible in list
|
|
await expect(chartListPage.getChartRow(chartName)).toBeVisible();
|
|
|
|
// Click delete action button
|
|
await chartListPage.clickDeleteAction(chartName);
|
|
|
|
// Delete confirmation modal should appear
|
|
const deleteModal = new DeleteConfirmationModal(page);
|
|
await deleteModal.waitForVisible();
|
|
|
|
// Type "DELETE" to confirm
|
|
await deleteModal.fillConfirmationInput('DELETE');
|
|
|
|
// Click the Delete button
|
|
await deleteModal.clickDelete();
|
|
|
|
// Modal should close
|
|
await deleteModal.waitForHidden();
|
|
|
|
// Verify success toast appears
|
|
const toast = new Toast(page);
|
|
await expect(toast.getSuccess()).toBeVisible();
|
|
|
|
// Verify chart is removed from list
|
|
await expect(chartListPage.getChartRow(chartName)).not.toBeVisible();
|
|
|
|
// Backend verification: API returns 404
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const response = await apiGetChart(page, chartId, {
|
|
failOnStatusCode: false,
|
|
});
|
|
return response.status();
|
|
},
|
|
{ timeout: 10000, message: `Chart ${chartId} should return 404` },
|
|
)
|
|
.toBe(404);
|
|
});
|
|
|
|
test('should edit chart name via properties modal', async ({
|
|
page,
|
|
chartListPage,
|
|
testAssets,
|
|
}) => {
|
|
// Create throwaway chart for editing
|
|
const { id: chartId, name: chartName } = await createTestChart(
|
|
page,
|
|
testAssets,
|
|
test.info(),
|
|
{ prefix: 'test_edit' },
|
|
);
|
|
|
|
// Refresh to see the new chart
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
|
|
// Verify chart is visible in list
|
|
await expect(chartListPage.getChartRow(chartName)).toBeVisible();
|
|
|
|
// Click edit action to open properties modal
|
|
await chartListPage.clickEditAction(chartName);
|
|
|
|
// Wait for properties modal to be ready
|
|
const propertiesModal = new ChartPropertiesModal(page);
|
|
await propertiesModal.waitForReady();
|
|
|
|
// Edit the chart name
|
|
const newName = `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();
|
|
|
|
// 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 export a chart as a zip file', async ({
|
|
page,
|
|
chartListPage,
|
|
testAssets,
|
|
}) => {
|
|
// Create throwaway chart for export
|
|
const { name: chartName } = await createTestChart(
|
|
page,
|
|
testAssets,
|
|
test.info(),
|
|
{ prefix: 'test_export' },
|
|
);
|
|
|
|
// Refresh to see the new chart
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
|
|
// Verify chart is visible in list
|
|
await expect(chartListPage.getChartRow(chartName)).toBeVisible();
|
|
|
|
// Set up API response intercept for export endpoint
|
|
const exportResponsePromise = waitForGet(page, ENDPOINTS.CHART_EXPORT);
|
|
|
|
// Click export action button
|
|
await chartListPage.clickExportAction(chartName);
|
|
|
|
// Wait for export API response and validate zip contents
|
|
const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
|
|
await expectValidExportZip(exportResponse, {
|
|
resourceDir: 'charts',
|
|
expectedNames: [chartName],
|
|
});
|
|
});
|
|
|
|
test('should bulk delete multiple charts', async ({
|
|
page,
|
|
chartListPage,
|
|
testAssets,
|
|
}) => {
|
|
test.setTimeout(60_000);
|
|
|
|
// Create 2 throwaway charts for bulk delete
|
|
const [chart1, chart2] = await Promise.all([
|
|
createTestChart(page, testAssets, test.info(), {
|
|
prefix: 'bulk_delete_1',
|
|
}),
|
|
createTestChart(page, testAssets, test.info(), {
|
|
prefix: 'bulk_delete_2',
|
|
}),
|
|
]);
|
|
|
|
// Refresh to see new charts
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
|
|
// Verify both charts are visible in list
|
|
await expect(chartListPage.getChartRow(chart1.name)).toBeVisible();
|
|
await expect(chartListPage.getChartRow(chart2.name)).toBeVisible();
|
|
|
|
// Enable bulk select mode
|
|
await chartListPage.clickBulkSelectButton();
|
|
|
|
// Select both charts
|
|
await chartListPage.selectChartCheckbox(chart1.name);
|
|
await chartListPage.selectChartCheckbox(chart2.name);
|
|
|
|
// Click bulk delete action
|
|
await chartListPage.clickBulkAction('Delete');
|
|
|
|
// Delete confirmation modal should appear
|
|
const deleteModal = new DeleteConfirmationModal(page);
|
|
await deleteModal.waitForVisible();
|
|
|
|
// Type "DELETE" to confirm
|
|
await deleteModal.fillConfirmationInput('DELETE');
|
|
|
|
// Click the Delete button
|
|
await deleteModal.clickDelete();
|
|
|
|
// Modal should close
|
|
await deleteModal.waitForHidden();
|
|
|
|
// Verify success toast appears
|
|
const toast = new Toast(page);
|
|
await expect(toast.getSuccess()).toBeVisible();
|
|
|
|
// Verify both charts are removed from list
|
|
await expect(chartListPage.getChartRow(chart1.name)).not.toBeVisible();
|
|
await expect(chartListPage.getChartRow(chart2.name)).not.toBeVisible();
|
|
|
|
// Backend verification: Both return 404
|
|
for (const chart of [chart1, chart2]) {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const response = await apiGetChart(page, chart.id, {
|
|
failOnStatusCode: false,
|
|
});
|
|
return response.status();
|
|
},
|
|
{ timeout: 10000, message: `Chart ${chart.id} should return 404` },
|
|
)
|
|
.toBe(404);
|
|
}
|
|
});
|
|
|
|
test('should bulk export multiple charts', async ({
|
|
page,
|
|
chartListPage,
|
|
testAssets,
|
|
}) => {
|
|
// Create 2 throwaway charts for bulk export
|
|
const [chart1, chart2] = await Promise.all([
|
|
createTestChart(page, testAssets, test.info(), {
|
|
prefix: 'bulk_export_1',
|
|
}),
|
|
createTestChart(page, testAssets, test.info(), {
|
|
prefix: 'bulk_export_2',
|
|
}),
|
|
]);
|
|
|
|
// Refresh to see new charts
|
|
await chartListPage.goto();
|
|
await chartListPage.waitForTableLoad();
|
|
|
|
// Verify both charts are visible in list
|
|
await expect(chartListPage.getChartRow(chart1.name)).toBeVisible();
|
|
await expect(chartListPage.getChartRow(chart2.name)).toBeVisible();
|
|
|
|
// Enable bulk select mode
|
|
await chartListPage.clickBulkSelectButton();
|
|
|
|
// Select both charts
|
|
await chartListPage.selectChartCheckbox(chart1.name);
|
|
await chartListPage.selectChartCheckbox(chart2.name);
|
|
|
|
// Set up API response intercept for export endpoint
|
|
const exportResponsePromise = waitForGet(page, ENDPOINTS.CHART_EXPORT);
|
|
|
|
// Click bulk export action
|
|
await chartListPage.clickBulkAction('Export');
|
|
|
|
// Wait for export API response and validate zip contains both charts
|
|
const exportResponse = expectStatusOneOf(await exportResponsePromise, [200]);
|
|
await expectValidExportZip(exportResponse, {
|
|
resourceDir: 'charts',
|
|
minCount: 2,
|
|
expectedNames: [chart1.name, chart2.name],
|
|
});
|
|
});
|