Compare commits

..

20 Commits

Author SHA1 Message Date
Sindhu Balasubramanian
1f1ab078c6 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into users/sindhuba/sharedThroughput 2026-01-21 13:14:44 -08:00
sakshigupta12feb
31385950dd removed NotebookViewer file (#2281)
Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2026-01-22 00:07:06 +05:30
Laurent Nguyen
6dce2632c8 fix: prevent race condition by initializing scenario tracking after config is set (#2339)
Co-authored-by: Laurent Nguyen <languye@microsoft.com>
2026-01-21 17:28:08 +01:00
Sindhu Balasubramanian
abd41485e8 Address comments 2026-01-14 22:22:17 -08:00
Sindhu Balasubramanian
151ce0ac3a Fix tests 2026-01-13 16:52:49 -08:00
Sindhu Balasubramanian
c89c878636 Minor fix 2026-01-13 14:11:48 -08:00
Sindhu Balasubramanian
1a2d2b0e4e Fix errors 2026-01-13 14:08:06 -08:00
Sindhu Balasubramanian
36e8fd3519 Minor fixes 2026-01-13 12:15:21 -08:00
Sindhu Balasubramanian
5c83b36fd2 Revert "Add Mongo Pagination tests"
This reverts commit 50a244e6f9.
2026-01-13 11:57:04 -08:00
Sindhu Balasubramanian
79dbdbbe7f Merge branch 'users/sindhuba/playwrit-tests' of https://github.com/Azure/cosmos-explorer into users/sindhuba/sharedThroughput 2026-01-13 11:34:54 -08:00
Sindhu Balasubramanian
3f977df00d Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2026-01-13 10:25:55 -08:00
Sindhu Balasubramanian
865e9c906b Run npm format 2026-01-08 13:16:22 -08:00
Sindhu Balasubramanian
1c34425dd8 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2026-01-08 13:00:47 -08:00
Sindhu Balasubramanian
50a244e6f9 Add Mongo Pagination tests 2026-01-08 12:55:24 -08:00
Sindhu Balasubramanian
9dad75c2f9 Remove localhost 2025-12-30 23:21:56 -08:00
Sindhu Balasubramanian
876b531248 Add changes for Load more option to work 2025-12-30 22:54:42 -08:00
Sindhu Balasubramanian
28fe5846b3 Fix error with adding container to shared db test 2025-12-22 11:22:36 -08:00
Sindhu Balasubramanian
f8533abb64 Run npm format 2025-12-22 07:08:30 -08:00
Sindhu Balasubramanian
2921294a3d Run npm commands 2025-12-22 06:58:56 -08:00
Sindhu Balasubramanian
a03c289da0 Add sharedThroughput db tests 2025-12-21 22:03:12 -08:00
7 changed files with 677 additions and 507 deletions

View File

@@ -105,9 +105,12 @@ const App = (): JSX.Element => {
// Scenario-based health tracking: start ApplicationLoad and complete phases.
const { startScenario, completePhase } = useMetricScenario();
React.useEffect(() => {
startScenario(MetricScenario.ApplicationLoad);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Only start scenario after config is initialized to avoid race conditions
// with message handlers that depend on configContext.platform
if (config) {
startScenario(MetricScenario.ApplicationLoad);
}
}, [config, startScenario]);
React.useEffect(() => {
if (explorer) {

View File

@@ -1,88 +0,0 @@
import { initializeIcons } from "@fluentui/react";
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import * as ReactDOM from "react-dom";
import { configContext, initializeConfiguration } from "../ConfigContext";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
import { GalleryTab } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import {
NotebookViewerComponent,
NotebookViewerComponentProps,
} from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent";
import * as FileSystemUtil from "../Explorer/Notebook/FileSystemUtil";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "../Utils/GalleryUtils";
const onInit = async () => {
initializeIcons();
await initializeConfiguration();
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
let backNavigationText: string;
let onBackClick: () => void;
if (galleryViewerProps.selectedTab !== undefined) {
backNavigationText = GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
onBackClick = () =>
(window.location.href = `${configContext.hostedExplorerURL}gallery.html?tab=${
GalleryTab[galleryViewerProps.selectedTab]
}`);
}
const hideInputs = notebookViewerProps.hideInputs;
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
const galleryItemId = notebookViewerProps.galleryItemId;
let galleryItem: IGalleryItem;
if (galleryItemId) {
const junoClient = new JunoClient();
const galleryItemJunoResponse = await junoClient.getNotebookInfo(galleryItemId);
galleryItem = galleryItemJunoResponse.data;
}
// The main purpose of hiding the prompt is to hide everything when hiding inputs.
// It is generally not very useful to just hide the prompt.
const hidePrompts = hideInputs;
render(notebookUrl, backNavigationText, hideInputs, hidePrompts, galleryItem, onBackClick);
};
const render = (
notebookUrl: string,
backNavigationText: string,
hideInputs?: boolean,
hidePrompts?: boolean,
galleryItem?: IGalleryItem,
onBackClick?: () => void,
) => {
const props: NotebookViewerComponentProps = {
junoClient: galleryItem ? new JunoClient() : undefined,
notebookUrl,
galleryItem,
backNavigationText,
hideInputs,
hidePrompts,
onBackClick: onBackClick,
onTagClick: undefined,
};
if (galleryItem) {
document.title = FileSystemUtil.stripExtension(galleryItem.name, "ipynb");
}
const element = (
<>
<header>
<GalleryHeaderComponent />
</header>
<div style={{ marginLeft: 120, marginRight: 120 }}>
<NotebookViewerComponent {...props} />
</div>
</>
);
ReactDOM.render(element, document.getElementById("notebookContent"));
};
// Entry point
window.addEventListener("load", onInit);

View File

@@ -58,7 +58,9 @@ export const defaultAccounts: Record<TestAccount, string> = {
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000;
export const TEST_MANUAL_THROUGHPUT_RU = 800;
export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K = 2000;
export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_4K = 4000;
export const TEST_MANUAL_THROUGHPUT_RU_2K = 2000;
export const ONE_MINUTE_MS: number = 60 * 1000;
@@ -515,14 +517,14 @@ export class DataExplorer {
const containerNode = await this.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand();
// // refresh tree to remove deleted database
// const consoleMessages = await this.getNotificationConsoleMessages();
// const refreshButton = this.frame.getByTestId("Sidebar/RefreshButton");
// await refreshButton.click();
// await expect(consoleMessages).toContainText("Successfully refreshed databases", {
// timeout: ONE_MINUTE_MS,
// });
// await this.collapseNotificationConsole();
// refresh tree to remove deleted database
const consoleMessages = await this.getNotificationConsoleMessages();
const refreshButton = this.frame.getByTestId("Sidebar/RefreshButton");
await refreshButton.click();
await expect(consoleMessages).toContainText("Successfully refreshed databases", {
timeout: ONE_MINUTE_MS,
});
await this.collapseNotificationConsole();
const scaleAndSettingsButton = this.frame.getByTestId(
`TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`,

View File

@@ -1,258 +1,258 @@
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { expect, Frame, Locator, Page, test } from "@playwright/test";
// import { truncateName } from "../../../src/Explorer/ContainerCopy/CopyJobUtils";
// import {
// ContainerCopy,
// getAccountName,
// getDropdownItemByNameOrPosition,
// interceptAndInspectApiRequest,
// TestAccount,
// waitForApiResponse,
// } from "../../fx";
// import { createMultipleTestContainers } from "../../testData";
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect, Frame, Locator, Page, test } from "@playwright/test";
import { truncateName } from "../../../src/Explorer/ContainerCopy/CopyJobUtils";
import {
ContainerCopy,
getAccountName,
getDropdownItemByNameOrPosition,
interceptAndInspectApiRequest,
TestAccount,
waitForApiResponse,
} from "../../fx";
import { createMultipleTestContainers } from "../../testData";
// test.describe("Container Copy - Offline Migration", () => {
// let page: Page;
// let wrapper: Locator;
// let panel: Locator;
// let frame: Frame;
// let expectedJobName: string;
// let targetAccountName: string;
// let expectedSubscriptionName: string;
// let expectedCopyJobNameInitial: string;
test.describe("Container Copy - Offline Migration", () => {
let page: Page;
let wrapper: Locator;
let panel: Locator;
let frame: Frame;
let expectedJobName: string;
let targetAccountName: string;
let expectedSubscriptionName: string;
let expectedCopyJobNameInitial: string;
// test.beforeEach("Setup for offline migration test", async ({ browser }) => {
// await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 2 });
test.beforeEach("Setup for offline migration test", async ({ browser }) => {
await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 2 });
// page = await browser.newPage();
// ({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
// expectedJobName = `offline_test_job_${Date.now()}`;
// targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
// });
page = await browser.newPage();
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
expectedJobName = `offline_test_job_${Date.now()}`;
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
});
// test.afterEach("Cleanup after offline migration test", async () => {
// await page.unroute(/.*/, (route) => route.continue());
// await page.close();
// });
test.afterEach("Cleanup after offline migration test", async () => {
await page.unroute(/.*/, (route) => route.continue());
await page.close();
});
// test("Successfully create and manage offline migration copy job", async () => {
// expect(wrapper).not.toBeNull();
// await wrapper.locator(".commandBarContainer").waitFor({ state: "visible" });
test("Successfully create and manage offline migration copy job", async () => {
expect(wrapper).not.toBeNull();
await wrapper.locator(".commandBarContainer").waitFor({ state: "visible" });
// // Open Create Copy Job panel
// const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
// await expect(createCopyJobButton).toBeVisible();
// await createCopyJobButton.click();
// panel = frame.getByTestId("Panel:Create copy job");
// await expect(panel).toBeVisible();
// Open Create Copy Job panel
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
await expect(createCopyJobButton).toBeVisible();
await createCopyJobButton.click();
panel = frame.getByTestId("Panel:Create copy job");
await expect(panel).toBeVisible();
// // Reduced wait time for better performance
// await page.waitForTimeout(2000);
// Reduced wait time for better performance
await page.waitForTimeout(2000);
// // Setup subscription and account
// const subscriptionDropdown = panel.getByTestId("subscription-dropdown");
// const expectedAccountName = targetAccountName;
// expectedSubscriptionName = await subscriptionDropdown.locator("span.ms-Dropdown-title").innerText();
// Setup subscription and account
const subscriptionDropdown = panel.getByTestId("subscription-dropdown");
const expectedAccountName = targetAccountName;
expectedSubscriptionName = await subscriptionDropdown.locator("span.ms-Dropdown-title").innerText();
// await subscriptionDropdown.click();
// const subscriptionItem = await getDropdownItemByNameOrPosition(
// frame,
// { name: expectedSubscriptionName },
// { ariaLabel: "Subscription" },
// );
// await subscriptionItem.click();
await subscriptionDropdown.click();
const subscriptionItem = await getDropdownItemByNameOrPosition(
frame,
{ name: expectedSubscriptionName },
{ ariaLabel: "Subscription" },
);
await subscriptionItem.click();
// // Select account
// const accountDropdown = panel.getByTestId("account-dropdown");
// await expect(accountDropdown).toHaveText(new RegExp(expectedAccountName));
// await accountDropdown.click();
// Select account
const accountDropdown = panel.getByTestId("account-dropdown");
await expect(accountDropdown).toHaveText(new RegExp(expectedAccountName));
await accountDropdown.click();
// const accountItem = await getDropdownItemByNameOrPosition(
// frame,
// { name: expectedAccountName },
// { ariaLabel: "Account" },
// );
// await accountItem.click();
const accountItem = await getDropdownItemByNameOrPosition(
frame,
{ name: expectedAccountName },
{ ariaLabel: "Account" },
);
await accountItem.click();
// // Test offline migration mode toggle functionality
// const migrationTypeContainer = panel.getByTestId("migration-type");
// Test offline migration mode toggle functionality
const migrationTypeContainer = panel.getByTestId("migration-type");
// // First test online mode (should show permissions screen)
// const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
// await onlineCopyRadioButton.click({ force: true });
// await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
// First test online mode (should show permissions screen)
const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
await onlineCopyRadioButton.click({ force: true });
await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
// await panel.getByRole("button", { name: "Next" }).click();
// await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).toBeVisible();
// await expect(panel.getByText("Online container copy", { exact: true })).toBeVisible();
await panel.getByRole("button", { name: "Next" }).click();
await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).toBeVisible();
await expect(panel.getByText("Online container copy", { exact: true })).toBeVisible();
// // Go back and switch to offline mode
// await panel.getByRole("button", { name: "Previous" }).click();
// Go back and switch to offline mode
await panel.getByRole("button", { name: "Previous" }).click();
// const offlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Offline mode/i });
// await offlineCopyRadioButton.click({ force: true });
// await expect(migrationTypeContainer.getByTestId("migration-type-description-offline")).toBeVisible();
const offlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Offline mode/i });
await offlineCopyRadioButton.click({ force: true });
await expect(migrationTypeContainer.getByTestId("migration-type-description-offline")).toBeVisible();
// await panel.getByRole("button", { name: "Next" }).click();
await panel.getByRole("button", { name: "Next" }).click();
// // Verify we skip permissions screen in offline mode
// await expect(panel.getByTestId("Panel:SelectSourceAndTargetContainers")).toBeVisible();
// await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).not.toBeVisible();
// Verify we skip permissions screen in offline mode
await expect(panel.getByTestId("Panel:SelectSourceAndTargetContainers")).toBeVisible();
await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).not.toBeVisible();
// // Test source and target container selection with validation
// const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
// expect(sourceContainerDropdown).toBeVisible();
// await expect(sourceContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// Test source and target container selection with validation
const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
expect(sourceContainerDropdown).toBeVisible();
await expect(sourceContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// // Select source database first (containers are disabled until database is selected)
// const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
// await sourceDatabaseDropdown.click();
// const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Database" },
// );
// await sourceDbDropdownItem.click();
// Select source database first (containers are disabled until database is selected)
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
await sourceDatabaseDropdown.click();
const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await sourceDbDropdownItem.click();
// // Now container dropdown should be enabled
// await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
// await sourceContainerDropdown.click();
// const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Container" },
// );
// await sourceContainerDropdownItem.click();
// Now container dropdown should be enabled
await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
await sourceContainerDropdown.click();
const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Container" },
);
await sourceContainerDropdownItem.click();
// // Test target container selection
// const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
// expect(targetContainerDropdown).toBeVisible();
// await expect(targetContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// Test target container selection
const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
expect(targetContainerDropdown).toBeVisible();
await expect(targetContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
// await targetDatabaseDropdown.click();
// const targetDbDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Database" },
// );
// await targetDbDropdownItem.click();
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
await targetDatabaseDropdown.click();
const targetDbDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await targetDbDropdownItem.click();
// await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
// await targetContainerDropdown.click();
await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
await targetContainerDropdown.click();
// // First try selecting the same container (should show error)
// const targetContainerDropdownItem1 = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Container" },
// );
// await targetContainerDropdownItem1.click();
// First try selecting the same container (should show error)
const targetContainerDropdownItem1 = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Container" },
);
await targetContainerDropdownItem1.click();
// await panel.getByRole("button", { name: "Next" }).click();
await panel.getByRole("button", { name: "Next" }).click();
// // Verify validation error for same source and target containers
// const errorContainer = panel.getByTestId("Panel:ErrorContainer");
// await expect(errorContainer).toBeVisible();
// await expect(errorContainer).toHaveText(/Source and destination containers cannot be the same/i);
// Verify validation error for same source and target containers
const errorContainer = panel.getByTestId("Panel:ErrorContainer");
await expect(errorContainer).toBeVisible();
await expect(errorContainer).toHaveText(/Source and destination containers cannot be the same/i);
// // Select different target container
// await targetContainerDropdown.click();
// const targetContainerDropdownItem2 = await getDropdownItemByNameOrPosition(
// frame,
// { position: 1 },
// { ariaLabel: "Container" },
// );
// await targetContainerDropdownItem2.click();
// Select different target container
await targetContainerDropdown.click();
const targetContainerDropdownItem2 = await getDropdownItemByNameOrPosition(
frame,
{ position: 1 },
{ ariaLabel: "Container" },
);
await targetContainerDropdownItem2.click();
// // Generate expected job name based on selections
// const selectedSourceDatabase = await sourceDatabaseDropdown.innerText();
// const selectedSourceContainer = await sourceContainerDropdown.innerText();
// const selectedTargetDatabase = await targetDatabaseDropdown.innerText();
// const selectedTargetContainer = await targetContainerDropdown.innerText();
// expectedCopyJobNameInitial = `${truncateName(selectedSourceDatabase)}.${truncateName(
// selectedSourceContainer,
// )}_${truncateName(selectedTargetDatabase)}.${truncateName(selectedTargetContainer)}`;
// Generate expected job name based on selections
const selectedSourceDatabase = await sourceDatabaseDropdown.innerText();
const selectedSourceContainer = await sourceContainerDropdown.innerText();
const selectedTargetDatabase = await targetDatabaseDropdown.innerText();
const selectedTargetContainer = await targetContainerDropdown.innerText();
expectedCopyJobNameInitial = `${truncateName(selectedSourceDatabase)}.${truncateName(
selectedSourceContainer,
)}_${truncateName(selectedTargetDatabase)}.${truncateName(selectedTargetContainer)}`;
// await panel.getByRole("button", { name: "Next" }).click();
await panel.getByRole("button", { name: "Next" }).click();
// // Error should disappear and preview should be visible
// await expect(errorContainer).not.toBeVisible();
// await expect(panel.getByTestId("Panel:PreviewCopyJob")).toBeVisible();
// Error should disappear and preview should be visible
await expect(errorContainer).not.toBeVisible();
await expect(panel.getByTestId("Panel:PreviewCopyJob")).toBeVisible();
// // Verify job preview details
// const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
// await expect(previewContainer).toBeVisible();
// await expect(previewContainer.getByTestId("source-subscription-name")).toHaveText(expectedSubscriptionName);
// await expect(previewContainer.getByTestId("source-account-name")).toHaveText(expectedAccountName);
// Verify job preview details
const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
await expect(previewContainer).toBeVisible();
await expect(previewContainer.getByTestId("source-subscription-name")).toHaveText(expectedSubscriptionName);
await expect(previewContainer.getByTestId("source-account-name")).toHaveText(expectedAccountName);
// const jobNameInput = previewContainer.getByTestId("job-name-textfield");
// await expect(jobNameInput).toHaveValue(new RegExp(expectedCopyJobNameInitial));
const jobNameInput = previewContainer.getByTestId("job-name-textfield");
await expect(jobNameInput).toHaveValue(new RegExp(expectedCopyJobNameInitial));
// const primaryBtn = panel.getByRole("button", { name: "Copy", exact: true });
// await expect(primaryBtn).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
const primaryBtn = panel.getByRole("button", { name: "Copy", exact: true });
await expect(primaryBtn).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
// // Test invalid job name validation (spaces not allowed)
// await jobNameInput.fill("test job name");
// await expect(primaryBtn).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// Test invalid job name validation (spaces not allowed)
await jobNameInput.fill("test job name");
await expect(primaryBtn).toHaveClass(/(^|\s)is-disabled(\s|$)/);
// // Test duplicate job name error handling
// const duplicateJobName = "test-job-name-1";
// await jobNameInput.fill(duplicateJobName);
// Test duplicate job name error handling
const duplicateJobName = "test-job-name-1";
await jobNameInput.fill(duplicateJobName);
// const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
// const expectedErrorMessage = `Duplicate job name '${duplicateJobName}'`;
const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
const expectedErrorMessage = `Duplicate job name '${duplicateJobName}'`;
// await interceptAndInspectApiRequest(
// page,
// `${expectedAccountName}/dataTransferJobs/${duplicateJobName}`,
// "PUT",
// new Error(expectedErrorMessage),
// (url?: string) => url?.includes(duplicateJobName) ?? false,
// );
await interceptAndInspectApiRequest(
page,
`${expectedAccountName}/dataTransferJobs/${duplicateJobName}`,
"PUT",
new Error(expectedErrorMessage),
(url?: string) => url?.includes(duplicateJobName) ?? false,
);
// let errorThrown = false;
// try {
// await copyButton.click();
// await page.waitForTimeout(2000);
// } catch (error: any) {
// errorThrown = true;
// expect(error.message).toContain("not allowed");
// }
let errorThrown = false;
try {
await copyButton.click();
await page.waitForTimeout(2000);
} catch (error: any) {
errorThrown = true;
expect(error.message).toContain("not allowed");
}
// if (!errorThrown) {
// const errorContainer = panel.getByTestId("Panel:ErrorContainer");
// await expect(errorContainer).toBeVisible();
// await expect(errorContainer).toHaveText(new RegExp(expectedErrorMessage, "i"));
// }
if (!errorThrown) {
const errorContainer = panel.getByTestId("Panel:ErrorContainer");
await expect(errorContainer).toBeVisible();
await expect(errorContainer).toHaveText(new RegExp(expectedErrorMessage, "i"));
}
// await expect(panel).toBeVisible();
await expect(panel).toBeVisible();
// // Test successful job creation with valid job name
// const validJobName = expectedJobName;
// Test successful job creation with valid job name
const validJobName = expectedJobName;
// const copyJobCreationPromise = waitForApiResponse(
// page,
// `${expectedAccountName}/dataTransferJobs/${validJobName}`,
// "PUT",
// );
const copyJobCreationPromise = waitForApiResponse(
page,
`${expectedAccountName}/dataTransferJobs/${validJobName}`,
"PUT",
);
// await jobNameInput.fill(validJobName);
// await expect(copyButton).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
await jobNameInput.fill(validJobName);
await expect(copyButton).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
// await copyButton.click();
await copyButton.click();
// const response = await copyJobCreationPromise;
// expect(response.ok()).toBe(true);
const response = await copyJobCreationPromise;
expect(response.ok()).toBe(true);
// // Verify panel closes and job appears in the list
// await expect(panel).not.toBeVisible({ timeout: 5000 });
// Verify panel closes and job appears in the list
await expect(panel).not.toBeVisible({ timeout: 5000 });
// const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
// await jobsListContainer.waitFor({ state: "visible", timeout: 5000 });
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
await jobsListContainer.waitFor({ state: "visible", timeout: 5000 });
// const jobItem = jobsListContainer.getByText(validJobName);
// await jobItem.waitFor({ state: "visible", timeout: 5000 });
// await expect(jobItem).toBeVisible();
// });
// });
const jobItem = jobsListContainer.getByText(validJobName);
await jobItem.waitFor({ state: "visible", timeout: 5000 });
await expect(jobItem).toBeVisible();
});
});

View File

@@ -1,185 +1,185 @@
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { expect, Frame, Locator, Page, test } from "@playwright/test";
// import {
// ContainerCopy,
// getAccountName,
// getDropdownItemByNameOrPosition,
// TestAccount,
// waitForApiResponse,
// } from "../../fx";
// import { createMultipleTestContainers } from "../../testData";
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect, Frame, Locator, Page, test } from "@playwright/test";
import {
ContainerCopy,
getAccountName,
getDropdownItemByNameOrPosition,
TestAccount,
waitForApiResponse,
} from "../../fx";
import { createMultipleTestContainers } from "../../testData";
// test.describe("Container Copy - Online Migration", () => {
// let page: Page;
// let wrapper: Locator;
// let panel: Locator;
// let frame: Frame;
// let targetAccountName: string;
test.describe("Container Copy - Online Migration", () => {
let page: Page;
let wrapper: Locator;
let panel: Locator;
let frame: Frame;
let targetAccountName: string;
// test.beforeEach("Setup for online migration test", async ({ browser }) => {
// await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 2 });
test.beforeEach("Setup for online migration test", async ({ browser }) => {
await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 2 });
// page = await browser.newPage();
// ({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
// targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
// });
page = await browser.newPage();
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
});
// test.afterEach("Cleanup after online migration test", async () => {
// await page.unroute(/.*/, (route) => route.continue());
// await page.close();
// });
test.afterEach("Cleanup after online migration test", async () => {
await page.unroute(/.*/, (route) => route.continue());
await page.close();
});
// test("Successfully create and manage online migration copy job", async () => {
// expect(wrapper).not.toBeNull();
// await wrapper.locator(".commandBarContainer").waitFor({ state: "visible" });
test("Successfully create and manage online migration copy job", async () => {
expect(wrapper).not.toBeNull();
await wrapper.locator(".commandBarContainer").waitFor({ state: "visible" });
// // Open Create Copy Job panel
// const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
// await expect(createCopyJobButton).toBeVisible();
// await createCopyJobButton.click();
// panel = frame.getByTestId("Panel:Create copy job");
// await expect(panel).toBeVisible();
// Open Create Copy Job panel
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
await expect(createCopyJobButton).toBeVisible();
await createCopyJobButton.click();
panel = frame.getByTestId("Panel:Create copy job");
await expect(panel).toBeVisible();
// // Reduced wait time for better performance
// await page.waitForTimeout(1000);
// Reduced wait time for better performance
await page.waitForTimeout(1000);
// // Enable online migration mode
// const migrationTypeContainer = panel.getByTestId("migration-type");
// const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
// await onlineCopyRadioButton.click({ force: true });
// Enable online migration mode
const migrationTypeContainer = panel.getByTestId("migration-type");
const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
await onlineCopyRadioButton.click({ force: true });
// await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
// await panel.getByRole("button", { name: "Next" }).click();
await panel.getByRole("button", { name: "Next" }).click();
// // Verify permissions screen is shown for online migration
// const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
// await expect(permissionScreen).toBeVisible();
// await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
// Verify permissions screen is shown for online migration
const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
await expect(permissionScreen).toBeVisible();
await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
// // Skip permissions setup and proceed to container selection
// await panel.getByRole("button", { name: "Next" }).click();
// Skip permissions setup and proceed to container selection
await panel.getByRole("button", { name: "Next" }).click();
// // Configure source and target containers for online migration
// const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
// await sourceDatabaseDropdown.click();
// const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Database" },
// );
// await sourceDbDropdownItem.click();
// Configure source and target containers for online migration
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
await sourceDatabaseDropdown.click();
const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await sourceDbDropdownItem.click();
// const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
// await sourceContainerDropdown.click();
// const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Container" },
// );
// await sourceContainerDropdownItem.click();
const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
await sourceContainerDropdown.click();
const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Container" },
);
await sourceContainerDropdownItem.click();
// const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
// await targetDatabaseDropdown.click();
// const targetDbDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 0 },
// { ariaLabel: "Database" },
// );
// await targetDbDropdownItem.click();
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
await targetDatabaseDropdown.click();
const targetDbDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await targetDbDropdownItem.click();
// const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
// await targetContainerDropdown.click();
// const targetContainerDropdownItem = await getDropdownItemByNameOrPosition(
// frame,
// { position: 1 },
// { ariaLabel: "Container" },
// );
// await targetContainerDropdownItem.click();
const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
await targetContainerDropdown.click();
const targetContainerDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 1 },
{ ariaLabel: "Container" },
);
await targetContainerDropdownItem.click();
// await panel.getByRole("button", { name: "Next" }).click();
await panel.getByRole("button", { name: "Next" }).click();
// // Verify job preview and create the online migration job
// const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
// await expect(previewContainer.getByTestId("source-account-name")).toHaveText(targetAccountName);
// Verify job preview and create the online migration job
const previewContainer = panel.getByTestId("Panel:PreviewCopyJob");
await expect(previewContainer.getByTestId("source-account-name")).toHaveText(targetAccountName);
// const jobNameInput = previewContainer.getByTestId("job-name-textfield");
// const onlineMigrationJobName = await jobNameInput.inputValue();
const jobNameInput = previewContainer.getByTestId("job-name-textfield");
const onlineMigrationJobName = await jobNameInput.inputValue();
// const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
// const copyJobCreationPromise = waitForApiResponse(
// page,
// `${targetAccountName}/dataTransferJobs/${onlineMigrationJobName}`,
// "PUT",
// );
// await copyButton.click();
// await page.waitForTimeout(1000); // Reduced wait time
const copyJobCreationPromise = waitForApiResponse(
page,
`${targetAccountName}/dataTransferJobs/${onlineMigrationJobName}`,
"PUT",
);
await copyButton.click();
await page.waitForTimeout(1000); // Reduced wait time
// const response = await copyJobCreationPromise;
// expect(response.ok()).toBe(true);
const response = await copyJobCreationPromise;
expect(response.ok()).toBe(true);
// // Verify panel closes and job appears in the list
// await expect(panel).not.toBeVisible({ timeout: 5000 });
// Verify panel closes and job appears in the list
await expect(panel).not.toBeVisible({ timeout: 5000 });
// const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
// await jobsListContainer.waitFor({ state: "visible", timeout: 5000 });
const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
await jobsListContainer.waitFor({ state: "visible", timeout: 5000 });
// let jobRow, statusCell, actionMenuButton;
// jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
// statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
// await jobRow.waitFor({ state: "visible", timeout: 5000 });
let jobRow, statusCell, actionMenuButton;
jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
await jobRow.waitFor({ state: "visible", timeout: 5000 });
// // Verify job status changes to queued state
// await expect(statusCell).toContainText(/running|queued|pending/i, { timeout: 5000 });
// Verify job status changes to queued state
await expect(statusCell).toContainText(/running|queued|pending/i, { timeout: 5000 });
// // Test job lifecycle management through action menu
// actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
// await actionMenuButton.click();
// Test job lifecycle management through action menu
actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
await actionMenuButton.click();
// // Test pause functionality
// const pauseAction = frame.locator(".ms-ContextualMenu-list button:has-text('Pause')");
// await pauseAction.click();
// Test pause functionality
const pauseAction = frame.locator(".ms-ContextualMenu-list button:has-text('Pause')");
await pauseAction.click();
// const pauseResponse = await waitForApiResponse(
// page,
// `${targetAccountName}/dataTransferJobs/${onlineMigrationJobName}/pause`,
// "POST",
// );
// expect(pauseResponse.ok()).toBe(true);
const pauseResponse = await waitForApiResponse(
page,
`${targetAccountName}/dataTransferJobs/${onlineMigrationJobName}/pause`,
"POST",
);
expect(pauseResponse.ok()).toBe(true);
// // Verify job status changes to paused
// jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
// await jobRow.waitFor({ state: "visible", timeout: 5000 });
// statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
// await expect(statusCell).toContainText(/paused/i, { timeout: 5000 });
// await page.waitForTimeout(1000);
// Verify job status changes to paused
jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
await jobRow.waitFor({ state: "visible", timeout: 5000 });
statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
await expect(statusCell).toContainText(/paused/i, { timeout: 5000 });
await page.waitForTimeout(1000);
// // Test cancel job functionality
// actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
// await actionMenuButton.click();
// await frame.locator(".ms-ContextualMenu-list button:has-text('Cancel')").click();
// Test cancel job functionality
actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
await actionMenuButton.click();
await frame.locator(".ms-ContextualMenu-list button:has-text('Cancel')").click();
// // Verify cancellation confirmation dialog
// await expect(frame.locator(".ms-Dialog-main")).toBeVisible({ timeout: 2000 });
// await expect(frame.locator(".ms-Dialog-main")).toContainText(onlineMigrationJobName);
// Verify cancellation confirmation dialog
await expect(frame.locator(".ms-Dialog-main")).toBeVisible({ timeout: 2000 });
await expect(frame.locator(".ms-Dialog-main")).toContainText(onlineMigrationJobName);
// const cancelDialogButton = frame.locator(".ms-Dialog-main").getByTestId("DialogButton:Cancel");
// await expect(cancelDialogButton).toBeVisible();
// await cancelDialogButton.click();
// await expect(frame.locator(".ms-Dialog-main")).not.toBeVisible();
const cancelDialogButton = frame.locator(".ms-Dialog-main").getByTestId("DialogButton:Cancel");
await expect(cancelDialogButton).toBeVisible();
await cancelDialogButton.click();
await expect(frame.locator(".ms-Dialog-main")).not.toBeVisible();
// actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
// await actionMenuButton.click();
// await frame.locator(".ms-ContextualMenu-list button:has-text('Cancel')").click();
actionMenuButton = wrapper.getByTestId(`CopyJobActionMenu/Button:${onlineMigrationJobName}`);
await actionMenuButton.click();
await frame.locator(".ms-ContextualMenu-list button:has-text('Cancel')").click();
// const confirmDialogButton = frame.locator(".ms-Dialog-main").getByTestId("DialogButton:Confirm");
// await expect(confirmDialogButton).toBeVisible();
// await confirmDialogButton.click();
const confirmDialogButton = frame.locator(".ms-Dialog-main").getByTestId("DialogButton:Confirm");
await expect(confirmDialogButton).toBeVisible();
await confirmDialogButton.click();
// // Verify final job status is cancelled
// jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
// statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
// await expect(statusCell).toContainText(/cancelled/i, { timeout: 5000 });
// });
// });
// Verify final job status is cancelled
jobRow = jobsListContainer.locator(".ms-DetailsRow", { hasText: onlineMigrationJobName });
statusCell = jobRow.locator("[data-automationid='DetailsRowCell'][data-automation-key='CopyJobStatus']");
await expect(statusCell).toContainText(/cancelled/i, { timeout: 5000 });
});
});

View File

@@ -0,0 +1,232 @@
import { Locator, expect, test } from "@playwright/test";
import {
CommandBarButton,
DataExplorer,
ONE_MINUTE_MS,
TEST_AUTOSCALE_MAX_THROUGHPUT_RU_4K,
TEST_MANUAL_THROUGHPUT_RU,
TestAccount,
} from "../../fx";
import { TestDatabaseContext, createTestDB } from "../../testData";
test.describe("Database with Shared Throughput", () => {
let dbContext: TestDatabaseContext = null!;
let explorer: DataExplorer = null!;
const containerId = "sharedcontainer";
// Helper methods
const getThroughputInput = (type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input`);
};
// Delete database only if not running in CI
if (!process.env.CI) {
test.afterEach("Delete Test Database", async () => {
await dbContext?.dispose();
});
}
test.describe("Manual Throughput Tests", () => {
test.beforeEach(async ({ page }) => {
explorer = await DataExplorer.open(page, TestAccount.SQL);
});
test("Create database with shared manual throughput and verify Scale node in UI", async () => {
test.setTimeout(120000); // 2 minutes timeout
// Create database with shared manual throughput (400 RU/s)
dbContext = await createTestDB({ throughput: 400 });
// Verify database node appears in the tree
const databaseNode = await explorer.waitForNode(dbContext.database.id);
expect(databaseNode).toBeDefined();
// Expand the database node to see child nodes
await databaseNode.expand();
// Verify that "Scale" node appears under the database
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
expect(scaleNode).toBeDefined();
await expect(scaleNode.element).toBeVisible();
});
test("Add container to shared database without dedicated throughput", async () => {
// Create database with shared manual throughput
dbContext = await createTestDB({ throughput: 400 });
// Wait for the database to appear in the tree
await explorer.waitForNode(dbContext.database.id);
// Add a container to the shared database via UI
const newContainerButton = await explorer.globalCommandButton("New Container");
await newContainerButton.click();
await explorer.whilePanelOpen(
"New Container",
async (panel, okButton) => {
// Select "Use existing" database
const useExistingRadio = panel.getByRole("radio", { name: /Use existing/i });
await useExistingRadio.click();
// Select the database from dropdown using the new data-testid
const databaseDropdown = panel.getByRole("combobox", { name: "Choose an existing database" });
await databaseDropdown.click();
await explorer.frame.getByRole("option", { name: dbContext.database.id }).click();
// Now you can target the specific database option by its data-testid
//await panel.getByTestId(`database-option-${dbContext.database.id}`).click();
// Fill container id
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
// Fill partition key
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
// Ensure "Provision dedicated throughput" is NOT checked
const dedicatedThroughputCheckbox = panel.getByRole("checkbox", {
name: /Provision dedicated throughput for this container/i,
});
if (await dedicatedThroughputCheckbox.isVisible()) {
const isChecked = await dedicatedThroughputCheckbox.isChecked();
if (isChecked) {
await dedicatedThroughputCheckbox.uncheck();
}
}
await okButton.click();
},
{ closeTimeout: 5 * ONE_MINUTE_MS },
);
// Verify container was created under the database
const containerNode = await explorer.waitForContainerNode(dbContext.database.id, containerId);
expect(containerNode).toBeDefined();
});
test("Scale shared database manual throughput", async () => {
// Create database with shared manual throughput (400 RU/s)
dbContext = await createTestDB({ throughput: 400 });
// Navigate to the scale settings by clicking the "Scale" node in the tree
const databaseNode = await explorer.waitForNode(dbContext.database.id);
await databaseNode.expand();
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
await scaleNode.element.click();
// Update manual throughput from 400 to 800
await getThroughputInput("manual").fill(TEST_MANUAL_THROUGHPUT_RU.toString());
// Save changes
await explorer.commandBarButton(CommandBarButton.Save).click();
// Verify success message
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for database ${dbContext.database.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
);
});
test("Scale shared database from manual to autoscale", async () => {
// Create database with shared manual throughput (400 RU/s)
dbContext = await createTestDB({ throughput: 400 });
// Open database settings by clicking the "Scale" node
const databaseNode = await explorer.waitForNode(dbContext.database.id);
await databaseNode.expand();
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
await scaleNode.element.click();
// Switch to Autoscale
const autoscaleRadio = explorer.frame.getByText("Autoscale", { exact: true });
await autoscaleRadio.click();
// Set autoscale max throughput to 1000
//await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
// Save changes
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for database ${dbContext.database.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
);
});
});
test.describe("Autoscale Throughput Tests", () => {
test.beforeEach(async ({ page }) => {
explorer = await DataExplorer.open(page, TestAccount.SQL);
});
test("Create database with shared autoscale throughput and verify Scale node in UI", async () => {
test.setTimeout(120000); // 2 minutes timeout
// Create database with shared autoscale throughput (max 1000 RU/s)
dbContext = await createTestDB({ maxThroughput: 1000 });
// Verify database node appears
const databaseNode = await explorer.waitForNode(dbContext.database.id);
expect(databaseNode).toBeDefined();
// Expand the database node to see child nodes
await databaseNode.expand();
// Verify that "Scale" node appears under the database
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
expect(scaleNode).toBeDefined();
await expect(scaleNode.element).toBeVisible();
});
test("Scale shared database autoscale throughput", async () => {
// Create database with shared autoscale throughput (max 1000 RU/s)
dbContext = await createTestDB({ maxThroughput: 1000 });
// Open database settings
const databaseNode = await explorer.waitForNode(dbContext.database.id);
await databaseNode.expand();
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
await scaleNode.element.click();
// Update autoscale max throughput from 1000 to 4000
await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_4K.toString());
// Save changes
await explorer.commandBarButton(CommandBarButton.Save).click();
// Verify success message
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for database ${dbContext.database.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
);
});
test("Scale shared database from autoscale to manual", async () => {
// Create database with shared autoscale throughput (max 1000 RU/s)
dbContext = await createTestDB({ maxThroughput: 1000 });
// Open database settings
const databaseNode = await explorer.waitForNode(dbContext.database.id);
await databaseNode.expand();
const scaleNode = await explorer.waitForNode(`${dbContext.database.id}/Scale`);
await scaleNode.element.click();
// Switch to Manual
const manualRadio = explorer.frame.getByText("Manual", { exact: true });
await manualRadio.click();
// Save changes
await explorer.commandBarButton(CommandBarButton.Save).click();
// Verify success message
await expect(explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for database ${dbContext.database.id}`,
{ timeout: 2 * ONE_MINUTE_MS },
);
});
});
});

View File

@@ -82,6 +82,75 @@ export class TestContainerContext {
}
}
export class TestDatabaseContext {
constructor(
public armClient: CosmosDBManagementClient,
public client: CosmosClient,
public database: Database,
) {}
async dispose() {
await this.database.delete();
}
}
export interface CreateTestDBOptions {
throughput?: number;
maxThroughput?: number; // For autoscale
}
// Helper function to create ARM client and Cosmos client for SQL account
async function createCosmosClientForSQLAccount(
accountType: TestAccount.SQL | TestAccount.SQLContainerCopyOnly = TestAccount.SQL,
): Promise<{ armClient: CosmosDBManagementClient; client: CosmosClient }> {
const credentials = getAzureCLICredentials();
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
const armClient = new CosmosDBManagementClient(adaptedCredentials, subscriptionId);
const accountName = getAccountName(accountType);
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
const clientOptions: CosmosClientOptions = {
endpoint: account.documentEndpoint!,
};
const rbacToken =
accountType === TestAccount.SQL
? process.env.NOSQL_TESTACCOUNT_TOKEN
: accountType === TestAccount.SQLContainerCopyOnly
? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN
: "";
if (rbacToken) {
clientOptions.tokenProvider = async (): Promise<string> => {
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${rbacToken}`;
return authorizationToken;
};
} else {
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName);
clientOptions.key = keys.primaryMasterKey;
}
const client = new CosmosClient(clientOptions);
return { armClient, client };
}
export async function createTestDB(options?: CreateTestDBOptions): Promise<TestDatabaseContext> {
const databaseId = generateUniqueName("db");
const { armClient, client } = await createCosmosClientForSQLAccount();
// Create database with provisioned throughput (shared throughput)
// This checks the "Provision database throughput" option
const { database } = await client.databases.create({
id: databaseId,
throughput: options?.throughput, // Manual throughput (e.g., 400)
maxThroughput: options?.maxThroughput, // Autoscale max throughput (e.g., 1000)
});
return new TestDatabaseContext(armClient, client, database);
}
type createTestSqlContainerConfig = {
includeTestData?: boolean;
partitionKey?: string;
@@ -104,34 +173,7 @@ export async function createMultipleTestContainers({
const creationPromises: Promise<TestContainerContext>[] = [];
const databaseId = databaseName ? databaseName : generateUniqueName("db");
const credentials = getAzureCLICredentials();
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
const armClient = new CosmosDBManagementClient(adaptedCredentials, subscriptionId);
const accountName = getAccountName(accountType);
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
const clientOptions: CosmosClientOptions = {
endpoint: account.documentEndpoint!,
};
const rbacToken =
accountType === TestAccount.SQL
? process.env.NOSQL_TESTACCOUNT_TOKEN
: accountType === TestAccount.SQLContainerCopyOnly
? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN
: "";
if (rbacToken) {
clientOptions.tokenProvider = async (): Promise<string> => {
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${rbacToken}`;
return authorizationToken;
};
} else {
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName);
clientOptions.key = keys.primaryMasterKey;
}
const client = new CosmosClient(clientOptions);
const { armClient, client } = await createCosmosClientForSQLAccount(accountType);
const { database } = await client.databases.createIfNotExists({ id: databaseId });
try {
@@ -158,29 +200,8 @@ export async function createTestSQLContainer({
}: createTestSqlContainerConfig = {}) {
const databaseId = databaseName ? databaseName : generateUniqueName("db");
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
const credentials = getAzureCLICredentials();
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
const armClient = new CosmosDBManagementClient(adaptedCredentials, subscriptionId);
const accountName = getAccountName(TestAccount.SQL);
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
const { armClient, client } = await createCosmosClientForSQLAccount();
const clientOptions: CosmosClientOptions = {
endpoint: account.documentEndpoint!,
};
const nosqlAccountRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN;
if (nosqlAccountRbacToken) {
clientOptions.tokenProvider = async (): Promise<string> => {
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${nosqlAccountRbacToken}`;
return authorizationToken;
};
} else {
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName);
clientOptions.key = keys.primaryMasterKey;
}
const client = new CosmosClient(clientOptions);
const { database } = await client.databases.createIfNotExists({ id: databaseId });
try {
const { container } = await database.containers.createIfNotExists({