-
- Welcome to Azure Cosmos DB
+
+ {title}
+
-
Globally distributed, multi-model database service for any scale
+ {subtitle}
{getSplashScreenButtons()}
{useCarousel.getState().showCoachMark && (
{
<>
+
>
) : (
diff --git a/test/sql/containercopy.spec.ts b/test/sql/containercopy.spec.ts
deleted file mode 100644
index eff5faca1..000000000
--- a/test/sql/containercopy.spec.ts
+++ /dev/null
@@ -1,505 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { expect, Frame, Locator, Page, test } from "@playwright/test";
-import { set } from "lodash";
-import { truncateName } from "../../src/Explorer/ContainerCopy/CopyJobUtils";
-import {
- ContainerCopy,
- getAccountName,
- getDropdownItemByNameOrPosition,
- interceptAndInspectApiRequest,
- TestAccount,
- waitForApiResponse,
-} from "../fx";
-import { createMultipleTestContainers } from "../testData";
-
-let page: Page;
-let wrapper: Locator = null!;
-let panel: Locator = null!;
-let frame: Frame = null!;
-let expectedCopyJobNameInitial: string = null!;
-let expectedJobName: string = "";
-let targetAccountName: string = "";
-let expectedSourceAccountName: string = "";
-let expectedSubscriptionName: string = "";
-const VISIBLE_TIMEOUT_MS = 30 * 1000;
-
-test.describe.configure({ mode: "serial" });
-
-test.describe("Container Copy", () => {
- test.beforeAll("Container Copy - Before All", async ({ browser }) => {
- await createMultipleTestContainers({ accountType: TestAccount.SQLContainerCopyOnly, containerCount: 3 });
-
- page = await browser.newPage();
- ({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
- expectedJobName = `test_job_${Date.now()}`;
- targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
- });
-
- test.afterEach("Container Copy - After Each", async () => {
- await page.unroute(/.*/, (route) => route.continue());
- });
-
- test("Loading and verifying the content of the page", async () => {
- expect(wrapper).not.toBeNull();
- await expect(wrapper.getByTestId("CommandBar/Button:Create Copy Job")).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
- await expect(wrapper.getByTestId("CommandBar/Button:Refresh")).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
- await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).toBeVisible({ timeout: VISIBLE_TIMEOUT_MS });
- });
-
- test("Successfully create a copy job for offline migration", async () => {
- expect(wrapper).not.toBeNull();
- // Loading and verifying subscription & account dropdown
-
- const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
- await createCopyJobButton.click();
- panel = frame.getByTestId("Panel:Create copy job");
- await expect(panel).toBeVisible();
-
- await page.waitForTimeout(10 * 1000);
-
- 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();
-
- // Load account dropdown based on selected subscription
-
- 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();
-
- // Verifying online or offline migration functionality
- /**
- * This test verifies the functionality of the migration type radio that toggles between
- * online and offline container copy modes. It ensures that:
- * 1. When online mode is selected, the user is directed to a permissions screen
- * 2. When offline mode is selected, the user bypasses the permissions screen
- * 3. The UI correctly reflects the selected migration type throughout the workflow
- */
- 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 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: "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();
-
- await panel.getByRole("button", { name: "Next" }).click();
-
- await expect(panel.getByTestId("Panel:SelectSourceAndTargetContainers")).toBeVisible();
- await expect(panel.getByTestId("Panel:AssignPermissionsContainer")).not.toBeVisible();
-
- // Verifying source and target container selection
-
- const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
- expect(sourceContainerDropdown).toBeVisible();
- await expect(sourceContainerDropdown).toHaveClass(/(^|\s)is-disabled(\s|$)/);
-
- const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
- await sourceDatabaseDropdown.click();
-
- const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(
- frame,
- { position: 0 },
- { ariaLabel: "Database" },
- );
- await sourceDbDropdownItem.click();
-
- await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
- await sourceContainerDropdown.click();
- const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
- frame,
- { position: 0 },
- { ariaLabel: "Container" },
- );
- await sourceContainerDropdownItem.click();
-
- 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();
-
- await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
- await targetContainerDropdown.click();
- const targetContainerDropdownItem1 = await getDropdownItemByNameOrPosition(
- frame,
- { position: 0 },
- { ariaLabel: "Container" },
- );
- await targetContainerDropdownItem1.click();
-
- await panel.getByRole("button", { name: "Next" }).click();
-
- const errorContainer = panel.getByTestId("Panel:ErrorContainer");
- await expect(errorContainer).toBeVisible();
- await expect(errorContainer).toHaveText(/Source and destination containers cannot be the same/i);
-
- // Reselect target container to be different from source container
- await targetContainerDropdown.click();
- const targetContainerDropdownItem2 = await getDropdownItemByNameOrPosition(
- frame,
- { position: 1 },
- { ariaLabel: "Container" },
- );
- await targetContainerDropdownItem2.click();
-
- 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 expect(errorContainer).not.toBeVisible();
- await expect(panel.getByTestId("Panel:PreviewCopyJob")).toBeVisible();
-
- // Verifying the preview of the copy job
- 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 primaryBtn = panel.getByRole("button", { name: "Copy", exact: true });
- await expect(primaryBtn).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
-
- await jobNameInput.fill("test job name");
- await expect(primaryBtn).toHaveClass(/(^|\s)is-disabled(\s|$)/);
-
- // Testing API request interception with duplicate job name
- 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}'`;
- 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");
- }
- if (!errorThrown) {
- const errorContainer = panel.getByTestId("Panel:ErrorContainer");
- await expect(errorContainer).toBeVisible();
- await expect(errorContainer).toHaveText(new RegExp(expectedErrorMessage, "i"));
- }
-
- await expect(panel).toBeVisible();
-
- // Testing API request success with valid job name and verifying copy job creation
-
- const validJobName = expectedJobName;
-
- const copyJobCreationPromise = waitForApiResponse(
- page,
- `${expectedAccountName}/dataTransferJobs/${validJobName}`,
- "PUT",
- );
-
- await jobNameInput.fill(validJobName);
- await expect(copyButton).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
-
- await copyButton.click();
-
- const response = await copyJobCreationPromise;
- expect(response.ok()).toBe(true);
-
- await expect(panel).not.toBeVisible({ timeout: 10000 });
-
- const jobsListContainer = wrapper.locator(".CopyJobListContainer .ms-DetailsList-contentWrapper .ms-List-page");
- await jobsListContainer.waitFor({ state: "visible" });
-
- const jobItem = jobsListContainer.getByText(validJobName);
- await jobItem.waitFor({ state: "visible" });
- await expect(jobItem).toBeVisible();
- });
-
- test("Verify Online or Offline Container Copy Permissions Panel", async () => {
- expect(wrapper).not.toBeNull();
-
- // Opening the Create Copy Job panel again to verify initial state
- const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
- await createCopyJobButton.click();
- panel = frame.getByTestId("Panel:Create copy job");
- await expect(panel).toBeVisible();
- await expect(panel.getByRole("heading", { name: "Create copy job" })).toBeVisible();
-
- // select different account dropdown
-
- const accountDropdown = panel.getByTestId("account-dropdown");
- await accountDropdown.click();
-
- const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
- expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account");
-
- const allDropdownItems = await dropdownItemsWrapper.locator(`button.ms-Dropdown-item[role='option']`).all();
-
- const filteredItems = [];
- for (const item of allDropdownItems) {
- const testContent = (await item.textContent()) ?? "";
- if (testContent.trim() !== targetAccountName.trim()) {
- filteredItems.push(item);
- }
- }
-
- if (filteredItems.length > 0) {
- const firstDropdownItem = filteredItems[0];
- expectedSourceAccountName = (await firstDropdownItem.textContent()) ?? "";
- await firstDropdownItem.click();
- } else {
- throw new Error("No dropdown items available after filtering");
- }
-
- const migrationTypeContainer = panel.getByTestId("migration-type");
- const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
- await onlineCopyRadioButton.click({ force: true });
-
- await panel.getByRole("button", { name: "Next" }).click();
-
- // Verifying Assign Permissions panel for online copy
-
- const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
- await expect(permissionScreen).toBeVisible();
-
- await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
- await expect(permissionScreen.getByText("Cross-account container copy", { exact: true })).toBeVisible();
-
- // Verify Point-in-Time Restore timer and refresh button workflow
-
- await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}**`, async (route) => {
- const mockData = {
- identity: {
- type: "SystemAssigned",
- principalId: "00-11-22-33",
- },
- properties: {
- defaultIdentity: "SystemAssignedIdentity",
- backupPolicy: {
- type: "Continuous",
- },
- capabilities: [{ name: "EnableOnlineContainerCopy" }],
- },
- };
- if (route.request().method() === "GET") {
- const response = await route.fetch();
- const actualData = await response.json();
- const mergedData = { ...actualData };
-
- set(mergedData, "identity", mockData.identity);
- set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
- set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
- set(mergedData, "properties.capabilities", mockData.properties.capabilities);
-
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify(mergedData),
- });
- } else {
- await route.continue();
- }
- });
-
- await expect(permissionScreen).toBeVisible();
-
- const expandedOnlineAccordionHeader = permissionScreen
- .getByTestId("permission-group-container-onlineConfigs")
- .locator("button[aria-expanded='true']");
- await expect(expandedOnlineAccordionHeader).toBeVisible();
-
- const accordionItem = expandedOnlineAccordionHeader
- .locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
- .first();
-
- const accordionPanel = accordionItem
- .locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
- .first();
-
- await page.clock.install({ time: new Date("2024-01-01T10:00:00Z") });
-
- const pitrBtn = accordionPanel.getByTestId("pointInTimeRestore:PrimaryBtn");
- await expect(pitrBtn).toBeVisible();
- await pitrBtn.click();
-
- page.context().on("page", async (newPage) => {
- const expectedUrlEndPattern = new RegExp(
- `/providers/Microsoft.(DocumentDB|DocumentDb)/databaseAccounts/${expectedSourceAccountName}/backupRestore`,
- );
- expect(newPage.url()).toMatch(expectedUrlEndPattern);
- await newPage.close();
- });
-
- const loadingOverlay = frame.locator("[data-test='loading-overlay']");
- await expect(loadingOverlay).toBeVisible();
-
- const refreshBtn = accordionPanel.getByTestId("pointInTimeRestore:RefreshBtn");
- await expect(refreshBtn).not.toBeVisible();
-
- // Fast forward time by 11 minutes (11 * 60 * 1000ms = 660000ms)
- await page.clock.fastForward(11 * 60 * 1000);
-
- await expect(refreshBtn).toBeVisible();
- await expect(pitrBtn).not.toBeVisible();
-
- // Veify Popover & Loading Overlay on permission screen with API mocks and accordion interactions
-
- await page.route(
- `**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/sqlRoleAssignments*`,
- async (route) => {
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify({
- value: [
- {
- principalId: "00-11-22-33",
- roleDefinitionId: `Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/77-88-99`,
- },
- ],
- }),
- });
- },
- );
-
- await page.route("**/Microsoft.DocumentDB/databaseAccounts/*/77-88-99**", async (route) => {
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify({
- value: [
- {
- name: "00000000-0000-0000-0000-000000000001",
- },
- ],
- }),
- });
- });
-
- await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${targetAccountName}**`, async (route) => {
- const mockData = {
- identity: {
- type: "SystemAssigned",
- principalId: "00-11-22-33",
- },
- properties: {
- defaultIdentity: "SystemAssignedIdentity",
- backupPolicy: {
- type: "Continuous",
- },
- capabilities: [{ name: "EnableOnlineContainerCopy" }],
- },
- };
-
- if (route.request().method() === "PATCH") {
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify({ status: "Succeeded" }),
- });
- } else if (route.request().method() === "GET") {
- // Get the actual response and merge with mock data
- const response = await route.fetch();
- const actualData = await response.json();
- const mergedData = { ...actualData };
- set(mergedData, "identity", mockData.identity);
- set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
- set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
- set(mergedData, "properties.capabilities", mockData.properties.capabilities);
-
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify(mergedData),
- });
- } else {
- await route.continue();
- }
- });
-
- await expect(permissionScreen).toBeVisible();
-
- const expandedCrossAccordionHeader = permissionScreen
- .getByTestId("permission-group-container-crossAccountConfigs")
- .locator("button[aria-expanded='true']");
- await expect(expandedCrossAccordionHeader).toBeVisible();
-
- const crossAccordionItem = expandedCrossAccordionHeader
- .locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
- .first();
-
- const crossAccordionPanel = crossAccordionItem
- .locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
- .first();
-
- const toggleButton = crossAccordionPanel.getByTestId("btn-toggle");
- await expect(toggleButton).toBeVisible();
- await toggleButton.click();
-
- const popover = frame.locator("[data-test='popover-container']");
- await expect(popover).toBeVisible();
-
- const yesButton = popover.getByRole("button", { name: /Yes/i });
- const noButton = popover.getByRole("button", { name: /No/i });
- await expect(yesButton).toBeVisible();
- await expect(noButton).toBeVisible();
-
- await yesButton.click();
-
- await expect(loadingOverlay).toBeVisible();
-
- await expect(loadingOverlay).toBeHidden({ timeout: 10 * 1000 });
- await expect(popover).toBeHidden({ timeout: 10 * 1000 });
-
- await panel.getByRole("button", { name: "Cancel" }).click();
- });
-
- test.afterAll("Container Copy - After All", async () => {
- await page.unroute(/.*/, (route) => route.continue());
- await page.close();
- });
-});
diff --git a/test/sql/containercopy/offlineMigration.spec.ts b/test/sql/containercopy/offlineMigration.spec.ts
new file mode 100644
index 000000000..de9597d49
--- /dev/null
+++ b/test/sql/containercopy/offlineMigration.spec.ts
@@ -0,0 +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";
+
+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 });
+
+ 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("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();
+
+ // 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();
+
+ 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();
+
+ const accountItem = await getDropdownItemByNameOrPosition(
+ frame,
+ { name: expectedAccountName },
+ { ariaLabel: "Account" },
+ );
+ await accountItem.click();
+
+ // 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();
+
+ 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();
+
+ 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();
+
+ // 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|$)/);
+
+ // 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();
+
+ // 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();
+
+ 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();
+
+ 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);
+
+ // 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)}`;
+
+ 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();
+
+ // 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 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 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}'`;
+
+ 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");
+ }
+
+ if (!errorThrown) {
+ const errorContainer = panel.getByTestId("Panel:ErrorContainer");
+ await expect(errorContainer).toBeVisible();
+ await expect(errorContainer).toHaveText(new RegExp(expectedErrorMessage, "i"));
+ }
+
+ await expect(panel).toBeVisible();
+
+ // Test successful job creation with valid job name
+ const validJobName = expectedJobName;
+
+ const copyJobCreationPromise = waitForApiResponse(
+ page,
+ `${expectedAccountName}/dataTransferJobs/${validJobName}`,
+ "PUT",
+ );
+
+ await jobNameInput.fill(validJobName);
+ await expect(copyButton).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
+
+ await copyButton.click();
+
+ 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 });
+
+ 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();
+ });
+});
diff --git a/test/sql/containercopy/onlineMigration.spec.ts b/test/sql/containercopy/onlineMigration.spec.ts
new file mode 100644
index 000000000..3914f6002
--- /dev/null
+++ b/test/sql/containercopy/onlineMigration.spec.ts
@@ -0,0 +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";
+
+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 });
+
+ 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("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();
+
+ // 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 });
+
+ await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
+
+ 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();
+
+ // 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();
+
+ 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 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();
+
+ // 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 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 response = await copyJobCreationPromise;
+ expect(response.ok()).toBe(true);
+
+ // 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 });
+
+ 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 });
+
+ // 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();
+
+ 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);
+
+ // 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);
+
+ 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();
+
+ 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 });
+ });
+});
diff --git a/test/sql/containercopy/permissionsScreen.spec.ts b/test/sql/containercopy/permissionsScreen.spec.ts
new file mode 100644
index 000000000..fa7d3e199
--- /dev/null
+++ b/test/sql/containercopy/permissionsScreen.spec.ts
@@ -0,0 +1,270 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { expect, Frame, Locator, Page, test } from "@playwright/test";
+import { set } from "lodash";
+import { ContainerCopy, getAccountName, TestAccount } from "../../fx";
+
+const VISIBLE_TIMEOUT_MS = 30 * 1000;
+
+test.describe("Container Copy - Permission Screen Verification", () => {
+ let page: Page;
+ let wrapper: Locator;
+ let panel: Locator;
+ let frame: Frame;
+ let targetAccountName: string;
+ let expectedSourceAccountName: string;
+
+ test.beforeEach("Setup for each test", async ({ browser }) => {
+ page = await browser.newPage();
+ ({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQLContainerCopyOnly));
+ targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
+ });
+
+ test.afterEach("Cleanup after each test", async () => {
+ await page.unroute(/.*/, (route) => route.continue());
+ await page.close();
+ });
+
+ test("Verify online container copy permissions panel functionality", async () => {
+ expect(wrapper).not.toBeNull();
+
+ // Verify all command bar buttons are visible
+ await wrapper.locator(".commandBarContainer").waitFor({ state: "visible", timeout: VISIBLE_TIMEOUT_MS });
+
+ const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
+ await expect(createCopyJobButton).toBeVisible();
+ await expect(wrapper.getByTestId("CommandBar/Button:Refresh")).toBeVisible();
+ await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).toBeVisible();
+
+ // Open the Create Copy Job panel
+ await createCopyJobButton.click();
+ panel = frame.getByTestId("Panel:Create copy job");
+ await expect(panel).toBeVisible();
+ await expect(panel.getByRole("heading", { name: "Create copy job" })).toBeVisible();
+
+ // Select a different account for cross-account testing
+ const accountDropdown = panel.getByTestId("account-dropdown");
+ await accountDropdown.click();
+
+ const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
+ expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account");
+
+ const allDropdownItems = await dropdownItemsWrapper.locator(`button.ms-Dropdown-item[role='option']`).all();
+
+ const filteredItems = [];
+ for (const item of allDropdownItems) {
+ const testContent = (await item.textContent()) ?? "";
+ if (testContent.trim() !== targetAccountName.trim()) {
+ filteredItems.push(item);
+ }
+ }
+
+ if (filteredItems.length > 0) {
+ const firstDropdownItem = filteredItems[0];
+ expectedSourceAccountName = (await firstDropdownItem.textContent()) ?? "";
+ await firstDropdownItem.click();
+ } else {
+ throw new Error("No dropdown items available after filtering");
+ }
+
+ // 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 panel.getByRole("button", { name: "Next" }).click();
+
+ // Verify Assign Permissions panel for online copy
+ const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
+ await expect(permissionScreen).toBeVisible();
+ await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
+ await expect(permissionScreen.getByText("Cross-account container copy", { exact: true })).toBeVisible();
+
+ // Setup API mocking for the source account
+ await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}**`, async (route) => {
+ const mockData = {
+ identity: {
+ type: "SystemAssigned",
+ principalId: "00-11-22-33",
+ },
+ properties: {
+ defaultIdentity: "SystemAssignedIdentity",
+ backupPolicy: {
+ type: "Continuous",
+ },
+ capabilities: [{ name: "EnableOnlineContainerCopy" }],
+ },
+ };
+ if (route.request().method() === "GET") {
+ const response = await route.fetch();
+ const actualData = await response.json();
+ const mergedData = { ...actualData };
+
+ set(mergedData, "identity", mockData.identity);
+ set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
+ set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
+ set(mergedData, "properties.capabilities", mockData.properties.capabilities);
+
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(mergedData),
+ });
+ } else {
+ await route.continue();
+ }
+ });
+
+ // Verify Point-in-Time Restore functionality
+ const expandedOnlineAccordionHeader = permissionScreen
+ .getByTestId("permission-group-container-onlineConfigs")
+ .locator("button[aria-expanded='true']");
+ await expect(expandedOnlineAccordionHeader).toBeVisible();
+
+ const accordionItem = expandedOnlineAccordionHeader
+ .locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
+ .first();
+
+ const accordionPanel = accordionItem
+ .locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
+ .first();
+
+ // Install clock mock and test PITR functionality
+ await page.clock.install({ time: new Date("2024-01-01T10:00:00Z") });
+
+ const pitrBtn = accordionPanel.getByTestId("pointInTimeRestore:PrimaryBtn");
+ await expect(pitrBtn).toBeVisible();
+ await pitrBtn.click();
+
+ // Verify new page opens with correct URL pattern
+ page.context().on("page", async (newPage) => {
+ const expectedUrlEndPattern = new RegExp(
+ `/providers/Microsoft.(DocumentDB|DocumentDb)/databaseAccounts/${expectedSourceAccountName}/backupRestore`,
+ );
+ expect(newPage.url()).toMatch(expectedUrlEndPattern);
+ await newPage.close();
+ });
+
+ const loadingOverlay = frame.locator("[data-test='loading-overlay']");
+ await expect(loadingOverlay).toBeVisible();
+
+ const refreshBtn = accordionPanel.getByTestId("pointInTimeRestore:RefreshBtn");
+ await expect(refreshBtn).not.toBeVisible();
+
+ // Fast forward time by 11 minutes
+ await page.clock.fastForward(11 * 60 * 1000);
+
+ await expect(refreshBtn).toBeVisible({ timeout: 5000 });
+ await expect(pitrBtn).not.toBeVisible();
+
+ // Setup additional API mocks for role assignments and permissions
+ await page.route(
+ `**/Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/sqlRoleAssignments*`,
+ async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ value: [
+ {
+ principalId: "00-11-22-33",
+ roleDefinitionId: `Microsoft.DocumentDB/databaseAccounts/${expectedSourceAccountName}/77-88-99`,
+ },
+ ],
+ }),
+ });
+ },
+ );
+
+ await page.route("**/Microsoft.DocumentDB/databaseAccounts/*/77-88-99**", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ value: [
+ {
+ name: "00000000-0000-0000-0000-000000000001",
+ },
+ ],
+ }),
+ });
+ });
+
+ await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${targetAccountName}**`, async (route) => {
+ const mockData = {
+ identity: {
+ type: "SystemAssigned",
+ principalId: "00-11-22-33",
+ },
+ properties: {
+ defaultIdentity: "SystemAssignedIdentity",
+ backupPolicy: {
+ type: "Continuous",
+ },
+ capabilities: [{ name: "EnableOnlineContainerCopy" }],
+ },
+ };
+
+ if (route.request().method() === "PATCH") {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({ status: "Succeeded" }),
+ });
+ } else if (route.request().method() === "GET") {
+ const response = await route.fetch();
+ const actualData = await response.json();
+ const mergedData = { ...actualData };
+ set(mergedData, "identity", mockData.identity);
+ set(mergedData, "properties.defaultIdentity", mockData.properties.defaultIdentity);
+ set(mergedData, "properties.backupPolicy", mockData.properties.backupPolicy);
+ set(mergedData, "properties.capabilities", mockData.properties.capabilities);
+
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(mergedData),
+ });
+ } else {
+ await route.continue();
+ }
+ });
+
+ // Verify cross-account permissions functionality
+ const expandedCrossAccordionHeader = permissionScreen
+ .getByTestId("permission-group-container-crossAccountConfigs")
+ .locator("button[aria-expanded='true']");
+ await expect(expandedCrossAccordionHeader).toBeVisible();
+
+ const crossAccordionItem = expandedCrossAccordionHeader
+ .locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
+ .first();
+
+ const crossAccordionPanel = crossAccordionItem
+ .locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
+ .first();
+
+ const toggleButton = crossAccordionPanel.getByTestId("btn-toggle");
+ await expect(toggleButton).toBeVisible();
+ await toggleButton.click();
+
+ // Verify popover functionality
+ const popover = frame.locator("[data-test='popover-container']");
+ await expect(popover).toBeVisible();
+
+ const yesButton = popover.getByRole("button", { name: /Yes/i });
+ const noButton = popover.getByRole("button", { name: /No/i });
+ await expect(yesButton).toBeVisible();
+ await expect(noButton).toBeVisible();
+
+ await yesButton.click();
+
+ // Verify loading states
+ await expect(loadingOverlay).toBeVisible();
+ await expect(loadingOverlay).toBeHidden({ timeout: 10 * 1000 });
+ await expect(popover).toBeHidden({ timeout: 10 * 1000 });
+
+ // Cancel the panel to clean up
+ await panel.getByRole("button", { name: "Cancel" }).click();
+ });
+});