Refactor dropdown utilities and fix test helper functions

This commit is contained in:
Bikram Choudhury
2025-12-30 18:26:10 +05:30
parent fda6b59bc1
commit 8c67026760
6 changed files with 242 additions and 154 deletions

View File

@@ -19,9 +19,21 @@ const NavigationControls: React.FC<NavigationControlsProps> = ({
isPreviousDisabled, isPreviousDisabled,
}) => ( }) => (
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20 }}>
<PrimaryButton text={primaryBtnText} onClick={onPrimary} allowDisabledFocus disabled={isPrimaryDisabled} /> <PrimaryButton
<DefaultButton text="Previous" onClick={onPrevious} allowDisabledFocus disabled={isPreviousDisabled} /> data-test="copy-job-primary"
<DefaultButton text="Cancel" onClick={onCancel} /> text={primaryBtnText}
onClick={onPrimary}
allowDisabledFocus
disabled={isPrimaryDisabled}
/>
<DefaultButton
data-test="copy-job-previous"
text="Previous"
onClick={onPrevious}
allowDisabledFocus
disabled={isPreviousDisabled}
/>
<DefaultButton data-test="copy-job-cancel" text="Cancel" onClick={onCancel} />
</Stack> </Stack>
); );

View File

@@ -9,7 +9,7 @@ import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../../Context/CopyJobContext"; import { CopyJobContext } from "../../../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums"; import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes"; import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes";
import { AccountDropdown } from "./AccountDropdown"; import { AccountDropdown, normalizeAccountId } from "./AccountDropdown";
jest.mock("../../../../../../hooks/useDatabaseAccounts"); jest.mock("../../../../../../hooks/useDatabaseAccounts");
jest.mock("../../../../../../UserContext", () => ({ jest.mock("../../../../../../UserContext", () => ({
@@ -202,13 +202,16 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState); const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toBe(mockDatabaseAccount1); expect(newState.source.account).toEqual({
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
});
}); });
it("should auto-select predefined account from userContext if available", async () => { it("should auto-select predefined account from userContext if available", async () => {
const userContextAccount = { const userContextAccount = {
...mockDatabaseAccount2, ...mockDatabaseAccount2,
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account2", id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account2",
}; };
(userContext as any).databaseAccount = userContextAccount; (userContext as any).databaseAccount = userContextAccount;
@@ -223,7 +226,10 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState); const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toBe(mockDatabaseAccount2); expect(newState.source.account).toEqual({
...mockDatabaseAccount2,
id: normalizeAccountId(mockDatabaseAccount2.id),
});
}); });
it("should keep current account if it exists in the filtered list", async () => { it("should keep current account if it exists in the filtered list", async () => {
@@ -248,7 +254,16 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState); const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
expect(newState).toBe(contextWithSelectedAccount.copyJobState); expect(newState).toEqual({
...contextWithSelectedAccount.copyJobState,
source: {
...contextWithSelectedAccount.copyJobState.source,
account: {
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
},
},
});
}); });
it("should handle account change when user selects different account", async () => { it("should handle account change when user selects different account", async () => {
@@ -272,7 +287,7 @@ describe("AccountDropdown", () => {
it("should normalize account ID for Portal platform", () => { it("should normalize account ID for Portal platform", () => {
const portalAccount = { const portalAccount = {
...mockDatabaseAccount1, ...mockDatabaseAccount1,
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account1", id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account1",
}; };
(configContext as any).platform = Platform.Portal; (configContext as any).platform = Platform.Portal;

View File

@@ -12,7 +12,7 @@ import FieldRow from "../../Components/FieldRow";
interface AccountDropdownProps {} interface AccountDropdownProps {}
const normalizeAccountId = (id: string) => { export const normalizeAccountId = (id: string = "") => {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/"); return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/");
} else if (configContext.platform === Platform.Hosted) { } else if (configContext.platform === Platform.Hosted) {
@@ -27,7 +27,12 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId; const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId); const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
const sqlApiOnlyAccounts: DatabaseAccount[] = (allAccounts || []).filter((account) => apiType(account) === "SQL"); const sqlApiOnlyAccounts = (allAccounts || [])
.filter((account) => apiType(account) === "SQL")
.map((account) => ({
...account,
id: normalizeAccountId(account.id),
}));
const updateCopyJobState = (newAccount: DatabaseAccount) => { const updateCopyJobState = (newAccount: DatabaseAccount) => {
setCopyJobState((prevState) => { setCopyJobState((prevState) => {
@@ -47,9 +52,8 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
useEffect(() => { useEffect(() => {
if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) { if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) {
const currentAccountId = copyJobState?.source?.account?.id; const currentAccountId = copyJobState?.source?.account?.id;
const predefinedAccountId = userContext.databaseAccount?.id; const predefinedAccountId = normalizeAccountId(userContext.databaseAccount?.id);
const selectedAccountId = currentAccountId || predefinedAccountId; const selectedAccountId = currentAccountId || predefinedAccountId;
const targetAccount: DatabaseAccount | null = const targetAccount: DatabaseAccount | null =
sqlApiOnlyAccounts.find((account) => account.id === selectedAccountId) || null; sqlApiOnlyAccounts.find((account) => account.id === selectedAccountId) || null;
updateCopyJobState(targetAccount || sqlApiOnlyAccounts[0]); updateCopyJobState(targetAccount || sqlApiOnlyAccounts[0]);
@@ -58,7 +62,7 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const accountOptions = const accountOptions =
sqlApiOnlyAccounts?.map((account) => ({ sqlApiOnlyAccounts?.map((account) => ({
key: normalizeAccountId(account.id), key: account.id,
text: account.name, text: account.name,
data: account, data: account,
})) || []; })) || [];

View File

@@ -185,6 +185,39 @@ export async function getTestExplorerUrl(accountType: TestAccount, options?: Tes
return `https://localhost:1234/testExplorer.html?${params.toString()}`; return `https://localhost:1234/testExplorer.html?${params.toString()}`;
} }
type DropdownItemExpectations = {
ariaLabel?: string;
itemCount?: number;
};
type DropdownItemMatcher = {
name?: string;
position?: number;
};
export async function getDropdownItemByNameOrPosition(
frame: Frame,
matcher?: DropdownItemMatcher,
expectedOptions?: DropdownItemExpectations,
): Promise<Locator> {
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
if (expectedOptions?.ariaLabel) {
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual(expectedOptions.ariaLabel);
}
if (expectedOptions?.itemCount) {
const items = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
await expect(items).toHaveCount(expectedOptions.itemCount);
}
const containerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
if (matcher?.name) {
return containerDropdownItems.filter({ hasText: matcher.name });
} else if (matcher?.position !== undefined) {
return containerDropdownItems.nth(matcher.position);
}
// Return first item if no matcher is provided
return containerDropdownItems.first();
}
/** Helper class that provides locator methods for TreeNode elements, on top of a Locator */ /** Helper class that provides locator methods for TreeNode elements, on top of a Locator */
class TreeNode { class TreeNode {
constructor( constructor(
@@ -490,15 +523,6 @@ export class DataExplorer {
return this.frame.getByTestId("notification-console/header-status"); return this.frame.getByTestId("notification-console/header-status");
} }
async getDropdownItemByName(name: string, ariaLabel?: string): Promise<Locator> {
const dropdownItemsWrapper = this.frame.locator("div.ms-Dropdown-items");
if (ariaLabel) {
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual(ariaLabel);
}
const containerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
return containerDropdownItems.filter({ hasText: name });
}
/** Waits for the Data Explorer app to load */ /** Waits for the Data Explorer app to load */
static async waitForExplorer(page: Page, options?: TestExplorerUrlOptions): Promise<DataExplorer> { static async waitForExplorer(page: Page, options?: TestExplorerUrlOptions): Promise<DataExplorer> {
const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle(); const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle();
@@ -527,6 +551,80 @@ export class DataExplorer {
} }
} }
export async function waitForApiResponse(
page: Page,
urlPattern: string,
method?: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payloadValidator?: (payload: any) => boolean,
) {
return page.waitForResponse(async (response) => {
const request = response.request();
if (!request.url().includes(urlPattern)) {
return false;
}
if (method && request.method() !== method) {
return false;
}
if (payloadValidator && (request.method() === "POST" || request.method() === "PUT")) {
const postData = request.postData();
if (postData) {
try {
const payload = JSON.parse(postData);
return payloadValidator(payload);
} catch {
return false;
}
}
}
return true;
});
}
export async function interceptAndInspectApiRequest(
page: Page,
urlPattern: string,
method: string = "POST",
error: Error,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errorValidator: (url?: string, payload?: any) => boolean,
): Promise<void> {
await page.route(
(url) => url.pathname.includes(urlPattern),
async (route, request) => {
if (request.method() !== method) {
await route.continue();
return;
}
const postData = request.postData();
if (postData) {
try {
const payload = JSON.parse(postData);
if (errorValidator && errorValidator(request.url(), payload)) {
await route.fulfill({
status: 409,
contentType: "application/json",
body: JSON.stringify({
code: "Conflict",
message: error.message,
}),
});
return;
}
} catch (err) {
if (err instanceof Error && err.message.includes("not allowed")) {
throw err;
}
}
}
await route.continue();
},
);
}
export class ContainerCopy { export class ContainerCopy {
constructor( constructor(
public frame: Frame, public frame: Frame,
@@ -544,77 +642,4 @@ export class ContainerCopy {
await page.goto(url); await page.goto(url);
return ContainerCopy.waitForContainerCopy(page); return ContainerCopy.waitForContainerCopy(page);
} }
static async waitForApiResponse(
page: Page,
urlPattern: string,
method?: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payloadValidator?: (payload: any) => boolean,
) {
return page.waitForResponse(async (response) => {
const request = response.request();
if (!request.url().includes(urlPattern)) {
return false;
}
if (method && request.method() !== method) {
return false;
}
if (payloadValidator && (request.method() === "POST" || request.method() === "PUT")) {
const postData = request.postData();
if (postData) {
try {
const payload = JSON.parse(postData);
return payloadValidator(payload);
} catch {
return false;
}
}
}
return true;
});
}
static async interceptAndInspectApiRequest(
page: Page,
urlPattern: string,
method: string = "POST",
error: Error,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errorValidator: (url?: string, payload?: any) => boolean,
): Promise<void> {
await page.route(
(url) => url.pathname.includes(urlPattern),
async (route, request) => {
if (request.method() !== method) {
await route.continue();
return;
}
const postData = request.postData();
if (postData) {
try {
const payload = JSON.parse(postData);
if (errorValidator && errorValidator(request.url(), payload)) {
await route.fulfill({
status: 409,
contentType: "application/json",
body: JSON.stringify({
code: "Conflict",
message: error.message,
}),
});
return;
}
} catch (err) {
if (err instanceof Error && err.message.includes("not allowed")) {
throw err;
}
}
}
await route.continue();
},
);
}
} }

View File

@@ -3,7 +3,14 @@ import { expect, Frame, Locator, Page, test } from "@playwright/test";
import { set } from "lodash"; import { set } from "lodash";
import { DatabaseAccount, Subscription } from "../../src/Contracts/DataModels"; import { DatabaseAccount, Subscription } from "../../src/Contracts/DataModels";
import { truncateName } from "../../src/Explorer/ContainerCopy/CopyJobUtils"; import { truncateName } from "../../src/Explorer/ContainerCopy/CopyJobUtils";
import { ContainerCopy, getAccountName, TestAccount } from "../fx"; import {
ContainerCopy,
getAccountName,
getDropdownItemByNameOrPosition,
interceptAndInspectApiRequest,
TestAccount,
waitForApiResponse,
} from "../fx";
test.describe.configure({ mode: "serial" }); test.describe.configure({ mode: "serial" });
let page: Page; let page: Page;
@@ -138,9 +145,10 @@ test("Verify Point-in-Time Restore timer and refresh button workflow", async ()
await pitrBtn.click(); await pitrBtn.click();
page.context().on("page", async (newPage) => { page.context().on("page", async (newPage) => {
expect(newPage.url()).toMatch( const expectedUrlEndPattern = new RegExp(
new RegExp(`/providers/Microsoft\\.Document[DB][Db]/databaseAccounts/${expectedSourceAccountName}/backupRestore`), `/providers/Microsoft.(DocumentDB|DocumentDb)/databaseAccounts/${expectedSourceAccountName}/backupRestore`,
); );
expect(newPage.url()).toMatch(expectedUrlEndPattern);
await newPage.close(); await newPage.close();
}); });
@@ -279,26 +287,16 @@ test("Loading and verifying subscription & account dropdown", async () => {
panel = frame.getByTestId("Panel:Create copy job"); panel = frame.getByTestId("Panel:Create copy job");
await expect(panel).toBeVisible(); await expect(panel).toBeVisible();
const subscriptionPromise = ContainerCopy.waitForApiResponse( const subscriptionPromise = waitForApiResponse(page, "/Microsoft.ResourceGraph/resources", "POST", (payload: any) => {
page, return (
"/Microsoft.ResourceGraph/resources", payload.query.includes("resources | where type == 'microsoft.documentdb/databaseaccounts'") &&
"POST", payload.query.includes("| where type == 'microsoft.resources/subscriptions'")
(payload: any) => { );
return ( });
payload.query.includes("resources | where type == 'microsoft.documentdb/databaseaccounts'") &&
payload.query.includes("| where type == 'microsoft.resources/subscriptions'")
);
},
);
const accountPromise = ContainerCopy.waitForApiResponse( const accountPromise = waitForApiResponse(page, "/Microsoft.ResourceGraph/resources", "POST", (payload: any) => {
page, return payload.query.includes("resources | where type =~ 'microsoft.documentdb/databaseaccounts'");
"/Microsoft.ResourceGraph/resources", });
"POST",
(payload: any) => {
return payload.query.includes("resources | where type =~ 'microsoft.documentdb/databaseaccounts'");
},
);
const subscriptionResponse = await subscriptionPromise; const subscriptionResponse = await subscriptionPromise;
const data = await subscriptionResponse.json(); const data = await subscriptionResponse.json();
@@ -316,11 +314,11 @@ test("Loading and verifying subscription & account dropdown", async () => {
await expect(subscriptionDropdown).toHaveText(new RegExp(selectedSubscription.subscriptionName)); await expect(subscriptionDropdown).toHaveText(new RegExp(selectedSubscription.subscriptionName));
await subscriptionDropdown.click(); await subscriptionDropdown.click();
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items"); const subscriptionItem = await getDropdownItemByNameOrPosition(
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Subscription"); frame,
const subscriptionDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']"); { name: selectedSubscription.subscriptionName },
expect(subscriptionDropdownItems).toHaveCount(data.count); { ariaLabel: "Subscription", itemCount: data.count },
const subscriptionItem = subscriptionDropdownItems.filter({ hasText: selectedSubscription.subscriptionName }); );
await subscriptionItem.click(); await subscriptionItem.click();
const expectedAccountName = getAccountName(TestAccount.SQLContainerCopyOnly); const expectedAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
@@ -330,13 +328,12 @@ test("Loading and verifying subscription & account dropdown", async () => {
await expect(accountDropdown).toHaveText(new RegExp(expectedAccountName)); await expect(accountDropdown).toHaveText(new RegExp(expectedAccountName));
await accountDropdown.click(); await accountDropdown.click();
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account"); const accountItem = await getDropdownItemByNameOrPosition(
const accountDropdownItemCount = await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").count(); frame,
expect(accountDropdownItemCount).toBeLessThanOrEqual(accountData.count); { name: expectedAccountName },
{ ariaLabel: "Account" },
await dropdownItemsWrapper );
.locator("button.ms-Dropdown-item[role='option']", { hasText: expectedAccountName }) await accountItem.click();
.click();
expectedSourceSubscription = selectedSubscription; expectedSourceSubscription = selectedSubscription;
expectedSourceAccount = selectedAccount; expectedSourceAccount = selectedAccount;
@@ -369,15 +366,18 @@ test("Verifying source and target container selection", async () => {
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown"); const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
await sourceDatabaseDropdown.click(); await sourceDatabaseDropdown.click();
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Database"); const sourceDbDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 }, { ariaLabel: "Database" });
const sourceDbDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']"); await sourceDbDropdownItem.click();
await sourceDbDropdownItems.first().click();
await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/); await expect(sourceContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
await sourceContainerDropdown.click(); await sourceContainerDropdown.click();
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Container"); const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
const sourceContainerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']"); frame,
await sourceContainerDropdownItems.first().click(); { position: 0 },
{ ariaLabel: "Container" },
);
await sourceContainerDropdownItem.click();
const targetContainerDropdown = panel.getByTestId("target-containerDropdown"); const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
expect(targetContainerDropdown).toBeVisible(); expect(targetContainerDropdown).toBeVisible();
@@ -385,22 +385,32 @@ test("Verifying source and target container selection", async () => {
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown"); const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
await targetDatabaseDropdown.click(); await targetDatabaseDropdown.click();
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Database"); const targetDbDropdownItem = await getDropdownItemByNameOrPosition(frame, { position: 0 }, { ariaLabel: "Database" });
const targetDbDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']"); await targetDbDropdownItem.click();
await targetDbDropdownItems.first().click();
await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/); await expect(targetContainerDropdown).not.toHaveClass(/(^|\s)is-disabled(\s|$)/);
await targetContainerDropdown.click(); await targetContainerDropdown.click();
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Container"); const targetContainerDropdownItem1 = await getDropdownItemByNameOrPosition(
const targetContainerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']"); frame,
await targetContainerDropdownItems.first().click(); { position: 0 },
{ ariaLabel: "Container" },
);
await targetContainerDropdownItem1.click();
await panel.getByRole("button", { name: "Next" }).click(); await panel.getByRole("button", { name: "Next" }).click();
const errorContainer = panel.getByTestId("Panel:ErrorContainer"); const errorContainer = panel.getByTestId("Panel:ErrorContainer");
await expect(errorContainer).toBeVisible(); await expect(errorContainer).toBeVisible();
await expect(errorContainer).toHaveText(/Source and destination containers cannot be the same/i); 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(); await targetContainerDropdown.click();
await targetContainerDropdownItems.nth(1).click(); const targetContainerDropdownItem2 = await getDropdownItemByNameOrPosition(
frame,
{ position: 1 },
{ ariaLabel: "Container" },
);
await targetContainerDropdownItem2.click();
const selectedSourceDatabase = await sourceDatabaseDropdown.innerText(); const selectedSourceDatabase = await sourceDatabaseDropdown.innerText();
const selectedSourceContainer = await sourceContainerDropdown.innerText(); const selectedSourceContainer = await sourceContainerDropdown.innerText();
@@ -440,7 +450,7 @@ test("Testing API request interception with duplicate job name", async () => {
const copyButton = panel.getByRole("button", { name: "Copy", exact: true }); const copyButton = panel.getByRole("button", { name: "Copy", exact: true });
const expectedErrorMessage = `Duplicate job name '${duplicateJobName}'`; const expectedErrorMessage = `Duplicate job name '${duplicateJobName}'`;
await ContainerCopy.interceptAndInspectApiRequest( await interceptAndInspectApiRequest(
page, page,
`${expectedSourceAccount.name}/dataTransferJobs/${duplicateJobName}`, `${expectedSourceAccount.name}/dataTransferJobs/${duplicateJobName}`,
"PUT", "PUT",
@@ -472,7 +482,7 @@ test("Testing API request success with valid job name and verifying copy job cre
const validJobName = expectedJobName; const validJobName = expectedJobName;
const copyJobCreationPromise = ContainerCopy.waitForApiResponse( const copyJobCreationPromise = waitForApiResponse(
page, page,
`${expectedSourceAccount.name}/dataTransferJobs/${validJobName}`, `${expectedSourceAccount.name}/dataTransferJobs/${validJobName}`,
"PUT", "PUT",
@@ -551,23 +561,41 @@ test("Cancel a copy job", async () => {
// Select source containers quickly // Select source containers quickly
const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown"); const sourceDatabaseDropdown = panel.getByTestId("source-databaseDropdown");
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
await sourceDatabaseDropdown.click(); await sourceDatabaseDropdown.click();
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click(); const sourceDatabaseDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await sourceDatabaseDropdownItem.click();
const sourceContainerDropdown = panel.getByTestId("source-containerDropdown"); const sourceContainerDropdown = panel.getByTestId("source-containerDropdown");
await sourceContainerDropdown.click(); await sourceContainerDropdown.click();
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click(); const sourceContainerDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Container" },
);
await sourceContainerDropdownItem.click();
// Select target containers // Select target containers
const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown"); const targetDatabaseDropdown = panel.getByTestId("target-databaseDropdown");
await targetDatabaseDropdown.click(); await targetDatabaseDropdown.click();
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").first().click(); const targetDatabaseDropdownItem = await getDropdownItemByNameOrPosition(
frame,
{ position: 0 },
{ ariaLabel: "Database" },
);
await targetDatabaseDropdownItem.click();
const targetContainerDropdown = panel.getByTestId("target-containerDropdown"); const targetContainerDropdown = panel.getByTestId("target-containerDropdown");
await targetContainerDropdown.click(); await targetContainerDropdown.click();
await dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']").nth(1).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();

View File

@@ -1,5 +1,5 @@
import { expect, Page, test } from "@playwright/test"; import { expect, Page, test } from "@playwright/test";
import { DataExplorer, TestAccount } from "../../fx"; import { DataExplorer, getDropdownItemByNameOrPosition, TestAccount } from "../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../testData"; import { createTestSQLContainer, TestContainerContext } from "../../testData";
test.describe("Change Partition Key", () => { test.describe("Change Partition Key", () => {
@@ -81,7 +81,11 @@ test.describe("Change Partition Key", () => {
await changePkPanel.getByLabel("Use existing container").check(); await changePkPanel.getByLabel("Use existing container").check();
await changePkPanel.getByText("Choose an existing container").click(); await changePkPanel.getByText("Choose an existing container").click();
const containerDropdownItem = await explorer.getDropdownItemByName(newContainerId, "Existing Containers"); const containerDropdownItem = await getDropdownItemByNameOrPosition(
explorer.frame,
{ name: newContainerId },
{ ariaLabel: "Existing Containers" },
);
await containerDropdownItem.click(); await containerDropdownItem.click();
await changePkPanel.getByTestId("Panel/OkButton").click(); await changePkPanel.getByTestId("Panel/OkButton").click();