mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-08 03:57:31 +00:00
Refactor dropdown utilities and fix test helper functions
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|||||||
189
test/fx.ts
189
test/fx.ts
@@ -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();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user