Merge branch 'master' of https://github.com/Azure/cosmos-explorer into users/aisayas/playwright

This commit is contained in:
Asier Isayas
2025-12-04 11:43:17 -08:00
18 changed files with 243 additions and 83 deletions

View File

@@ -0,0 +1,31 @@
import { Overlay, Spinner, SpinnerSize } from "@fluentui/react";
import React from "react";
interface LoadingOverlayProps {
isLoading: boolean;
label: string;
}
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) => {
if (!isLoading) {
return null;
}
return (
<Overlay
styles={{
root: {
backgroundColor: "rgba(255,255,255,0.9)",
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}}
>
<Spinner size={SpinnerSize.large} label={label} styles={{ label: { fontWeight: 600 } }} />
</Overlay>
);
};
export default LoadingOverlay;

View File

@@ -55,11 +55,20 @@ export default {
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.",
intraAccountOnlineDescription: (accountName: string) =>
`Follow the steps below to enable online copy on your "${accountName}" account.`,
commonConfiguration: {
title: "Common configuration",
description: "Basic permissions required for copy operations",
},
onlineConfiguration: {
title: "Online copy configuration",
description: "Additional permissions required for online copy operations",
},
},
toggleBtn: {
onText: "On",
offText: "Off",
},
popoverOverlaySpinnerLabel: "Please wait while we process your request...",
addManagedIdentity: {
title: "System-assigned managed identity enabled.",
description:

View File

@@ -1,5 +1,5 @@
import { DatabaseAccount } from "Contracts/DataModels";
import { CopyJobErrorType, CopyJobType } from "./Types/CopyJobTypes";
import { CopyJobContextState, CopyJobErrorType, CopyJobType } from "./Types/CopyJobTypes";
const azurePortalMpacEndpoint = "https://ms.portal.azure.com/";
@@ -115,6 +115,14 @@ export function getAccountDetailsFromResourceId(accountId: string | undefined) {
return { subscriptionId, resourceGroup, accountName };
}
export function getContainerIdentifiers(container: CopyJobContextState["source"] | CopyJobContextState["target"]) {
return {
accountId: container?.account?.id || "",
databaseId: container?.databaseId || "",
containerId: container?.containerId || "",
};
}
export function isIntraAccountCopy(sourceAccountId: string | undefined, targetAccountId: string | undefined): boolean {
const sourceAccountDetails = getAccountDetailsFromResourceId(sourceAccountId);
const targetAccountDetails = getAccountDetailsFromResourceId(targetAccountId);

View File

@@ -8,7 +8,8 @@ import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { isIntraAccountCopy } from "../../../CopyJobUtils";
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
import { useCopyJobPrerequisitesCache } from "../../Utils/useCopyJobPrerequisitesCache";
import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection";
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
<AccordionItem key={id} value={id} disabled={disabled}>
@@ -30,43 +31,91 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Compo
</AccordionItem>
);
const AssignPermissions = () => {
const { copyJobState } = useCopyJobContext();
const permissionSections = usePermissionSections(copyJobState);
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description, sections }) => {
const [openItems, setOpenItems] = React.useState<string[]>([]);
useEffect(() => {
const firstIncompleteSection = sections.find((section) => !section.completed);
const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : [];
if (JSON.stringify(openItems) !== JSON.stringify(nextOpenItems)) {
setOpenItems(nextOpenItems);
}
}, [sections]);
return (
<Stack
tokens={{ childrenGap: 15 }}
styles={{
root: {
background: "#fafafa",
border: "1px solid #e1e1e1",
borderRadius: 8,
padding: 16,
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
},
}}
>
<Stack tokens={{ childrenGap: 5 }}>
<Text variant="medium" style={{ fontWeight: 600 }}>
{title}
</Text>
{description && (
<Text variant="small" styles={{ root: { color: "#605E5C" } }}>
{description}
</Text>
)}
</Stack>
<Accordion className="permissionsAccordion" collapsible openItems={openItems}>
{sections.map((section) => (
<PermissionSection key={section.id} {...section} />
))}
</Accordion>
</Stack>
);
};
const AssignPermissions = () => {
const { setValidationCache } = useCopyJobPrerequisitesCache();
const { copyJobState } = useCopyJobContext();
const permissionGroups = usePermissionSections(copyJobState);
const totalSectionsCount = React.useMemo(
() => permissionGroups.reduce((total, group) => total + group.sections.length, 0),
[permissionGroups],
);
const indentLevels = React.useMemo<IndentLevel[]>(
() => Array(copyJobState.migrationType === CopyJobMigrationType.Online ? 5 : 3).fill({ level: 0, width: "100%" }),
[],
[copyJobState.migrationType],
);
const isSameAccount = isIntraAccountCopy(copyJobState?.source?.account?.id, copyJobState?.target?.account?.id);
useEffect(() => {
const firstIncompleteSection = permissionSections.find((section) => !section.completed);
const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : [];
if (JSON.stringify(openItems) !== JSON.stringify(nextOpenItems)) {
setOpenItems(nextOpenItems);
}
}, [permissionSections]);
return () => {
setValidationCache(new Map<string, boolean>());
};
}, []);
return (
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
<span>
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}>
<Text variant="medium">
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
copyJobState?.source?.account?.name || "",
)
: ContainerCopyMessages.assignPermissions.crossAccountDescription}
</span>
{permissionSections?.length === 0 ? (
</Text>
{totalSectionsCount === 0 ? (
<ShimmerTree indentLevels={indentLevels} style={{ width: "100%" }} />
) : (
<Accordion className="permissionsAccordion" collapsible openItems={openItems}>
{permissionSections.map((section) => (
<PermissionSection key={section.id} {...section} />
<Stack tokens={{ childrenGap: 25 }}>
{permissionGroups.map((group) => (
<PermissionGroup key={group.id} {...group} />
))}
</Accordion>
</Stack>
)}
</Stack>
);

View File

@@ -1,8 +1,9 @@
import { Link, PrimaryButton, Stack } from "@fluentui/react";
import { CapabilityNames } from "Common/Constants";
import { DatabaseAccount } from "Contracts/DataModels";
import React from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { CapabilityNames } from "../../../../../Common/Constants";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import { logError } from "../../../../../Common/Logger";
import { update as updateDatabaseAccount } from "../../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
@@ -119,6 +120,7 @@ const OnlineCopyEnabled: React.FC = () => {
return (
<Stack className="onlineCopyContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<LoadingOverlay isLoading={loading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<Stack.Item className="info-message">
{ContainerCopyMessages.onlineCopyEnabled.description(source?.account?.name || "")}&ensp;
<Link href={ContainerCopyMessages.onlineCopyEnabled.href} target="_blank" rel="noopener noreferrer">

View File

@@ -2,6 +2,7 @@ import { Link, PrimaryButton, Stack, Text } from "@fluentui/react";
import { DatabaseAccount } from "Contracts/DataModels";
import React, { useEffect, useRef, useState } from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import { logError } from "../../../../../Common/Logger";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
@@ -109,6 +110,7 @@ const PointInTimeRestore: React.FC = () => {
return (
<Stack className="pointInTimeRestoreContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<LoadingOverlay isLoading={loading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<Stack.Item className="toggle-label">
{ContainerCopyMessages.pointInTimeRestore.description(source.account?.name ?? "")}
{tooltipContent && (

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { CapabilityNames } from "../../../../../../Common/Constants";
import { fetchRoleAssignments, fetchRoleDefinitions, RoleDefinitionType } from "../../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { getAccountDetailsFromResourceId, isIntraAccountCopy } from "../../../../CopyJobUtils";
import { getAccountDetailsFromResourceId, getContainerIdentifiers, isIntraAccountCopy } from "../../../../CopyJobUtils";
import {
BackupPolicyType,
CopyJobMigrationType,
@@ -26,6 +26,13 @@ export interface PermissionSectionConfig {
validate?: (state: CopyJobContextState) => boolean | Promise<boolean>;
}
export interface PermissionGroupConfig {
id: string;
title: string;
description: string;
sections: PermissionSectionConfig[];
}
export const SECTION_IDS = {
addManagedIdentity: "addManagedIdentity",
defaultManagedIdentity: "defaultManagedIdentity",
@@ -127,26 +134,81 @@ export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinition
}
/**
* Returns the permission sections configuration for the Assign Permissions screen.
* Memoizes derived values for performance and decouples logic for testability.
* Validates sections within a group sequentially.
*/
const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => {
const sourceAccountId = state?.source?.account?.id || "";
const targetAccountId = state?.target?.account?.id || "";
const validateSectionsInGroup = async (
sections: PermissionSectionConfig[],
state: CopyJobContextState,
validationCache: Map<string, boolean>,
): Promise<PermissionSectionConfig[]> => {
const result: PermissionSectionConfig[] = [];
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
if (validationCache.has(section.id) && validationCache.get(section.id) === true) {
result.push({ ...section, completed: true });
continue;
}
if (section.validate) {
const isValid = await section.validate(state);
validationCache.set(section.id, isValid);
result.push({ ...section, completed: isValid });
if (!isValid) {
// Mark remaining sections in this group as incomplete
for (let j = i + 1; j < sections.length; j++) {
result.push({ ...sections[j], completed: false });
}
break;
}
} else {
validationCache.set(section.id, false);
result.push({ ...section, completed: false });
}
}
return result;
};
/**
* Returns the permission groups configuration for the Assign Permissions screen.
* Groups validate independently but sections within each group validate sequentially.
*/
const usePermissionSections = (state: CopyJobContextState): PermissionGroupConfig[] => {
const sourceAccount = getContainerIdentifiers(state.source);
const targetAccount = getContainerIdentifiers(state.target);
const { validationCache, setValidationCache } = useCopyJobPrerequisitesCache();
const [permissionSections, setPermissionSections] = useState<PermissionSectionConfig[] | null>(null);
const [permissionGroups, setPermissionGroups] = useState<PermissionGroupConfig[] | null>(null);
const isValidatingRef = useRef(false);
const sectionToValidate = useMemo(() => {
const isSameAccount = isIntraAccountCopy(sourceAccountId, targetAccountId);
const groupsToValidate = useMemo(() => {
const isSameAccount = isIntraAccountCopy(sourceAccount.accountId, targetAccount.accountId);
const commonSections = isSameAccount ? [] : [...PERMISSION_SECTIONS_CONFIG];
const groups: PermissionGroupConfig[] = [];
const baseSections = isSameAccount ? [] : [...PERMISSION_SECTIONS_CONFIG];
if (state.migrationType === CopyJobMigrationType.Online) {
return [...baseSections, ...PERMISSION_SECTIONS_FOR_ONLINE_JOBS];
if (commonSections.length > 0) {
groups.push({
id: "commonConfigs",
title: ContainerCopyMessages.assignPermissions.commonConfiguration.title,
description: ContainerCopyMessages.assignPermissions.commonConfiguration.description,
sections: commonSections,
});
}
return baseSections;
}, [sourceAccountId, targetAccountId, state.migrationType]);
if (state.migrationType === CopyJobMigrationType.Online) {
groups.push({
id: "onlineConfigs",
title: ContainerCopyMessages.assignPermissions.onlineConfiguration.title,
description: ContainerCopyMessages.assignPermissions.onlineConfiguration.description,
sections: [...PERMISSION_SECTIONS_FOR_ONLINE_JOBS],
});
}
return groups;
}, [sourceAccount.accountId, targetAccount.accountId, state.migrationType]);
const memoizedValidationCache = useMemo(() => {
if (state.migrationType === CopyJobMigrationType.Offline) {
@@ -157,52 +219,39 @@ const usePermissionSections = (state: CopyJobContextState): PermissionSectionCon
}, [state.migrationType]);
useEffect(() => {
const validateSections = async () => {
const validateGroups = async () => {
if (isValidatingRef.current) {
return;
}
isValidatingRef.current = true;
const result: PermissionSectionConfig[] = [];
const newValidationCache = new Map(memoizedValidationCache);
for (let i = 0; i < sectionToValidate.length; i++) {
const section = sectionToValidate[i];
// Validate all groups independently (in parallel)
const validatedGroups = await Promise.all(
groupsToValidate.map(async (group) => {
const validatedSections = await validateSectionsInGroup(group.sections, state, newValidationCache);
if (newValidationCache.has(section.id) && newValidationCache.get(section.id) === true) {
result.push({ ...section, completed: true });
continue;
}
if (section.validate) {
const isValid = await section.validate(state);
newValidationCache.set(section.id, isValid);
result.push({ ...section, completed: isValid });
if (!isValid) {
for (let j = i + 1; j < sectionToValidate.length; j++) {
result.push({ ...sectionToValidate[j], completed: false });
}
break;
}
} else {
newValidationCache.set(section.id, false);
result.push({ ...section, completed: false });
}
}
return {
...group,
sections: validatedSections,
};
}),
);
setValidationCache(newValidationCache);
setPermissionSections(result);
setPermissionGroups(validatedGroups);
isValidatingRef.current = false;
};
validateSections();
validateGroups();
return () => {
isValidatingRef.current = false;
};
}, [state, sectionToValidate]);
}, [state, groupsToValidate]);
return permissionSections ?? [];
return permissionGroups ?? [];
};
export default usePermissionSections;

View File

@@ -2,6 +2,8 @@
/* eslint-disable react/display-name */
import { DefaultButton, PrimaryButton, Stack, Text } from "@fluentui/react";
import React from "react";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
interface PopoverContainerProps {
isLoading?: boolean;
@@ -19,17 +21,13 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
tokens={{ childrenGap: 20 }}
style={{ maxWidth: 450 }}
>
<LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<Text variant="mediumPlus" style={{ fontWeight: 600 }}>
{title}
</Text>
<Text>{children}</Text>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<PrimaryButton
text={isLoading ? "" : "Yes"}
{...(isLoading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
onClick={onPrimary}
disabled={isLoading}
/>
<PrimaryButton text={"Yes"} onClick={onPrimary} disabled={isLoading} />
<DefaultButton text="No" onClick={onCancel} disabled={isLoading} />
</Stack>
</Stack>

View File

@@ -2,7 +2,7 @@ import { useCallback, useMemo, useReducer, useState } from "react";
import { useSidePanel } from "../../../../hooks/useSidePanel";
import { submitCreateCopyJob } from "../../Actions/CopyJobActions";
import { useCopyJobContext } from "../../Context/CopyJobContext";
import { isIntraAccountCopy } from "../../CopyJobUtils";
import { getContainerIdentifiers, isIntraAccountCopy } from "../../CopyJobUtils";
import { CopyJobMigrationType } from "../../Enums/CopyJobEnums";
import { useCopyJobPrerequisitesCache } from "./useCopyJobPrerequisitesCache";
import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList";
@@ -71,12 +71,6 @@ export function useCopyJobNavigation() {
useSidePanel.getState().closeSidePanel();
}, []);
const getContainerIdentifiers = (container: typeof copyJobState.source | typeof copyJobState.target) => ({
accountId: container?.account?.id || "",
databaseId: container?.databaseId || "",
containerId: container?.containerId || "",
});
const areContainersIdentical = () => {
const { source, target } = copyJobState;
const sourceIds = getContainerIdentifiers(source);

View File

@@ -19,6 +19,10 @@
.createCopyJobScreensContainer {
height: 100%;
padding: 1em 1.5em;
.pointInTimeRestoreContainer, .onlineCopyContainer {
position: relative;
}
label {
padding: 0;
@@ -59,6 +63,7 @@
}
}
.popover-container {
border-radius: 6px;
button[disabled] {
cursor: not-allowed;
opacity: 0.8;
@@ -66,7 +71,7 @@
}
.foreground {
z-index: 10;
background-color: white;
background-color: #f9f9f9;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: translate(0%, -9%);
@@ -89,6 +94,10 @@
.panelFormWrapper .panelMainContent {
padding: 0;
}
.createCopyJobScreensFooter {
margin-top: 50px;
}
}
.monitorCopyJobs {

View File

@@ -286,6 +286,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack>
<TextField
id="autoscaleRUValueField"
data-test="autoscaleRUInput"
type="number"
styles={{
fieldGroup: { width: 100, height: 27, flexShrink: 0 },

View File

@@ -2144,6 +2144,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</Stack>
<StyledTextFieldBase
ariaLabel="Container max RU/s"
data-test="autoscaleRUInput"
errorMessage=""
id="autoscaleRUValueField"
key=".0:$.$.1"
@@ -2170,6 +2171,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<TextFieldBase
ariaLabel="Container max RU/s"
data-test="autoscaleRUInput"
deferredValidationTime={200}
errorMessage=""
id="autoscaleRUValueField"
@@ -2470,6 +2472,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-invalid={false}
aria-label="Container max RU/s"
className="ms-TextField-field field-124"
data-test="autoscaleRUInput"
id="autoscaleRUValueField"
max="9007199254740991"
min={1000}

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
test("Cassandra keyspace and table CRUD", async ({ page }) => {
const keyspaceId = generateUniqueName("db");
@@ -14,6 +14,7 @@ test("Cassandra keyspace and table CRUD", async ({ page }) => {
async (panel, okButton) => {
await panel.getByPlaceholder("Type a new keyspace id").fill(keyspaceId);
await panel.getByPlaceholder("Enter table Id").fill(tableId);
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },

View File

@@ -55,6 +55,7 @@ export const defaultAccounts: Record<TestAccount, string> = {
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000;
export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K = 2000;
export const TEST_MANUAL_THROUGHPUT_RU_2K = 2000;
export const ONE_MINUTE_MS: number = 60 * 1000;

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
test("Gremlin graph CRUD", async ({ page }) => {
const databaseId = generateUniqueName("db");
@@ -16,6 +16,7 @@ test("Gremlin graph CRUD", async ({ page }) => {
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Graph id, Example Graph1" }).fill(graphId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
(
[
@@ -21,6 +21,7 @@ import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Collection id, Example Collection1" }).fill(collectionId);
await panel.getByRole("textbox", { name: "Shard key" }).fill("pk");
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
test("SQL database and container CRUD", async ({ page }) => {
const databaseId = generateUniqueName("db");
@@ -15,6 +15,7 @@ test("SQL database and container CRUD", async ({ page }) => {
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },

View File

@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
test("Tables CRUD", async ({ page }) => {
const tableId = generateUniqueName("table"); // A unique table name IS needed because the database is shared when using Table Storage.
@@ -12,7 +12,7 @@ test("Tables CRUD", async ({ page }) => {
"New Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Table id, Example Table1" }).fill(tableId);
await panel.getByLabel("Table Max RU/s").fill("1000");
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },