Added different container creation playwirhgt tests

This commit is contained in:
Sung-Hyun Kang
2025-12-09 19:07:54 -06:00
parent 5b7d1a74af
commit 945c457bd5
22 changed files with 1088 additions and 71 deletions

View File

@@ -23,7 +23,7 @@ const ShimmerTree = ({ indentLevels, style = {} }: ShimmerTreeProps) => {
);
return (
<Stack tokens={{ childrenGap: 8 }} style={{ width: "50%", ...style }} data-testid="shimmer-stack">
<Stack tokens={{ childrenGap: 8 }} style={{ width: "50%", ...style }} data-test="shimmer-stack">
{indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel))}
</Stack>
);

View File

@@ -32,7 +32,7 @@ describe("CopyJobContext", () => {
it("should render children correctly", () => {
render(
<CopyJobContextProvider explorer={mockExplorer}>
<div data-testid="test-child">Test Child</div>
<div data-test="test-child">Test Child</div>
</CopyJobContextProvider>,
);
@@ -552,7 +552,7 @@ describe("CopyJobContext", () => {
const TestComponent2 = (): JSX.Element => {
const context = useCopyJobContext();
contextValue2 = context;
return <div data-testid="component-2">Component 2</div>;
return <div data-test="component-2">Component 2</div>;
};
render(

View File

@@ -20,7 +20,7 @@ jest.mock("../../../CopyJobUtils", () => ({
jest.mock("../Components/InfoTooltip", () => {
const MockInfoTooltip = ({ content }: { content: React.ReactNode }) => {
return <div data-testid="info-tooltip">{content}</div>;
return <div data-test="info-tooltip">{content}</div>;
};
MockInfoTooltip.displayName = "MockInfoTooltip";
return MockInfoTooltip;
@@ -46,13 +46,13 @@ jest.mock("../Components/PopoverContainer", () => {
return null;
}
return (
<div data-testid="popover-message" data-loading={isLoading}>
<div data-testid="popover-title">{title}</div>
<div data-testid="popover-content">{children}</div>
<button onClick={onCancel} data-testid="popover-cancel">
<div data-test="popover-message" data-loading={isLoading}>
<div data-test="popover-title">{title}</div>
<div data-test="popover-content">{children}</div>
<button onClick={onCancel} data-test="popover-cancel">
Cancel
</button>
<button onClick={onPrimary} data-testid="popover-primary">
<button onClick={onPrimary} data-test="popover-primary">
Primary
</button>
</div>

View File

@@ -26,7 +26,7 @@ jest.mock("./hooks/usePermissionsSection", () => ({
jest.mock("../../../../../Common/ShimmerTree/ShimmerTree", () => {
const MockShimmerTree = (props: any) => {
return (
<div data-testid="shimmer-tree" {...props}>
<div data-test="shimmer-tree" {...props}>
Loading...
</div>
);
@@ -37,7 +37,7 @@ jest.mock("../../../../../Common/ShimmerTree/ShimmerTree", () => {
jest.mock("./AddManagedIdentity", () => {
const MockAddManagedIdentity = () => {
return <div data-testid="add-managed-identity">Add Managed Identity Component</div>;
return <div data-test="add-managed-identity">Add Managed Identity Component</div>;
};
MockAddManagedIdentity.displayName = "MockAddManagedIdentity";
return MockAddManagedIdentity;
@@ -45,7 +45,7 @@ jest.mock("./AddManagedIdentity", () => {
jest.mock("./AddReadPermissionToDefaultIdentity", () => {
const MockAddReadPermissionToDefaultIdentity = () => {
return <div data-testid="add-read-permission">Add Read Permission Component</div>;
return <div data-test="add-read-permission">Add Read Permission Component</div>;
};
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
return MockAddReadPermissionToDefaultIdentity;
@@ -53,7 +53,7 @@ jest.mock("./AddReadPermissionToDefaultIdentity", () => {
jest.mock("./DefaultManagedIdentity", () => {
const MockDefaultManagedIdentity = () => {
return <div data-testid="default-managed-identity">Default Managed Identity Component</div>;
return <div data-test="default-managed-identity">Default Managed Identity Component</div>;
};
MockDefaultManagedIdentity.displayName = "MockDefaultManagedIdentity";
return MockDefaultManagedIdentity;
@@ -61,7 +61,7 @@ jest.mock("./DefaultManagedIdentity", () => {
jest.mock("./OnlineCopyEnabled", () => {
const MockOnlineCopyEnabled = () => {
return <div data-testid="online-copy-enabled">Online Copy Enabled Component</div>;
return <div data-test="online-copy-enabled">Online Copy Enabled Component</div>;
};
MockOnlineCopyEnabled.displayName = "MockOnlineCopyEnabled";
return MockOnlineCopyEnabled;
@@ -69,7 +69,7 @@ jest.mock("./OnlineCopyEnabled", () => {
jest.mock("./PointInTimeRestore", () => {
const MockPointInTimeRestore = () => {
return <div data-testid="point-in-time-restore">Point In Time Restore Component</div>;
return <div data-test="point-in-time-restore">Point In Time Restore Component</div>;
};
MockPointInTimeRestore.displayName = "MockPointInTimeRestore";
return MockPointInTimeRestore;
@@ -196,14 +196,14 @@ describe("AssignPermissions Component", () => {
{
id: "addManagedIdentity",
title: "Add Managed Identity",
Component: () => <div data-testid="add-managed-identity">Add Managed Identity Component</div>,
Component: () => <div data-test="add-managed-identity">Add Managed Identity Component</div>,
disabled: false,
completed: true,
},
{
id: "readPermissionAssigned",
title: "Read Permission Assigned",
Component: () => <div data-testid="add-read-permission">Add Read Permission Component</div>,
Component: () => <div data-test="add-read-permission">Add Read Permission Component</div>,
disabled: false,
completed: false,
},
@@ -228,14 +228,14 @@ describe("AssignPermissions Component", () => {
{
id: "pointInTimeRestore",
title: "Point In Time Restore",
Component: () => <div data-testid="point-in-time-restore">Point In Time Restore Component</div>,
Component: () => <div data-test="point-in-time-restore">Point In Time Restore Component</div>,
disabled: false,
completed: true,
},
{
id: "onlineCopyEnabled",
title: "Online Copy Enabled",
Component: () => <div data-testid="online-copy-enabled">Online Copy Enabled Component</div>,
Component: () => <div data-test="online-copy-enabled">Online Copy Enabled Component</div>,
disabled: false,
completed: false,
},
@@ -262,7 +262,7 @@ describe("AssignPermissions Component", () => {
{
id: "addManagedIdentity",
title: "Add Managed Identity",
Component: () => <div data-testid="add-managed-identity">Add Managed Identity Component</div>,
Component: () => <div data-test="add-managed-identity">Add Managed Identity Component</div>,
disabled: false,
completed: true,
},
@@ -276,7 +276,7 @@ describe("AssignPermissions Component", () => {
{
id: "onlineCopyEnabled",
title: "Online Copy Enabled",
Component: () => <div data-testid="online-copy-enabled">Online Copy Enabled Component</div>,
Component: () => <div data-test="online-copy-enabled">Online Copy Enabled Component</div>,
disabled: false,
completed: false,
},

View File

@@ -15,7 +15,7 @@ jest.mock("../../../../../Utils/arm/identityUtils", () => ({
jest.mock("../Components/InfoTooltip", () => {
const MockInfoTooltip = ({ content }: { content: React.ReactNode }) => {
return <div data-testid="info-tooltip">{content}</div>;
return <div data-test="info-tooltip">{content}</div>;
};
MockInfoTooltip.displayName = "MockInfoTooltip";
return MockInfoTooltip;
@@ -41,14 +41,14 @@ jest.mock("../Components/PopoverContainer", () => {
return null;
}
return (
<div data-testid="popover-message">
<div data-testid="popover-title">{title}</div>
<div data-testid="popover-content">{children}</div>
<div data-testid="popover-loading">{isLoading ? "Loading" : "Not Loading"}</div>
<button data-testid="popover-cancel" onClick={onCancel}>
<div data-test="popover-message">
<div data-test="popover-title">{title}</div>
<div data-test="popover-content">{children}</div>
<div data-test="popover-loading">{isLoading ? "Loading" : "Not Loading"}</div>
<button data-test="popover-cancel" onClick={onCancel}>
Cancel
</button>
<button data-testid="popover-primary" onClick={onPrimary}>
<button data-test="popover-primary" onClick={onPrimary}>
Primary
</button>
</div>

View File

@@ -25,7 +25,7 @@ jest.mock("../../../../../Common/Logger", () => ({
jest.mock("../../../../../Common/LoadingOverlay", () => {
const MockLoadingOverlay = ({ isLoading, label }: { isLoading: boolean; label: string }) => {
return isLoading ? <div data-testid="loading-overlay">{label}</div> : null;
return isLoading ? <div data-test="loading-overlay">{label}</div> : null;
};
MockLoadingOverlay.displayName = "MockLoadingOverlay";
return MockLoadingOverlay;

View File

@@ -52,11 +52,11 @@ const TestComponent: React.FC<TestComponentProps> = ({ updateIdentityFn, onError
return (
<div>
<button onClick={handleClick} disabled={loading} data-testid="add-identity-button">
<button onClick={handleClick} disabled={loading} data-test="add-identity-button">
{loading ? "Loading..." : "Add System Identity"}
</button>
<div data-testid="loading-status">{loading ? "true" : "false"}</div>
{contextError && <div data-testid="error-message">{contextError}</div>}
<div data-test="loading-status">{loading ? "true" : "false"}</div>
{contextError && <div data-test="error-message">{contextError}</div>}
</div>
);
};

View File

@@ -34,7 +34,7 @@ jest.mock("../../../../CopyJobUtils", () => ({
jest.mock("../AddManagedIdentity", () => {
const MockAddManagedIdentity = () => {
return <div data-testid="add-managed-identity">AddManagedIdentity</div>;
return <div data-test="add-managed-identity">AddManagedIdentity</div>;
};
MockAddManagedIdentity.displayName = "MockAddManagedIdentity";
return MockAddManagedIdentity;
@@ -42,7 +42,7 @@ jest.mock("../AddManagedIdentity", () => {
jest.mock("../AddReadPermissionToDefaultIdentity", () => {
const MockAddReadPermissionToDefaultIdentity = () => {
return <div data-testid="add-read-permission">AddReadPermissionToDefaultIdentity</div>;
return <div data-test="add-read-permission">AddReadPermissionToDefaultIdentity</div>;
};
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
return MockAddReadPermissionToDefaultIdentity;
@@ -50,7 +50,7 @@ jest.mock("../AddReadPermissionToDefaultIdentity", () => {
jest.mock("../DefaultManagedIdentity", () => {
const MockDefaultManagedIdentity = () => {
return <div data-testid="default-managed-identity">DefaultManagedIdentity</div>;
return <div data-test="default-managed-identity">DefaultManagedIdentity</div>;
};
MockDefaultManagedIdentity.displayName = "MockDefaultManagedIdentity";
return MockDefaultManagedIdentity;
@@ -58,7 +58,7 @@ jest.mock("../DefaultManagedIdentity", () => {
jest.mock("../OnlineCopyEnabled", () => {
const MockOnlineCopyEnabled = () => {
return <div data-testid="online-copy-enabled">OnlineCopyEnabled</div>;
return <div data-test="online-copy-enabled">OnlineCopyEnabled</div>;
};
MockOnlineCopyEnabled.displayName = "MockOnlineCopyEnabled";
return MockOnlineCopyEnabled;
@@ -66,7 +66,7 @@ jest.mock("../OnlineCopyEnabled", () => {
jest.mock("../PointInTimeRestore", () => {
const MockPointInTimeRestore = () => {
return <div data-testid="point-in-time-restore">PointInTimeRestore</div>;
return <div data-test="point-in-time-restore">PointInTimeRestore</div>;
};
MockPointInTimeRestore.displayName = "MockPointInTimeRestore";
return MockPointInTimeRestore;
@@ -92,18 +92,18 @@ const TestWrapper: React.FC<TestWrapperProps> = ({ state, onResult }) => {
}, [result, onResult]);
return (
<div data-testid="test-wrapper">
<div data-testid="groups-count">{result.length}</div>
<div data-test="test-wrapper">
<div data-test="groups-count">{result.length}</div>
{result.map((group) => (
<div key={group.id} data-testid={`group-${group.id}`}>
<div key={group.id} data-test={`group-${group.id}`}>
<h3>{group.title}</h3>
<p>{group.description}</p>
{group.sections.map((section) => (
<div key={section.id} data-testid={`section-${section.id}`}>
<span data-testid={`section-${section.id}-completed`}>
<div key={section.id} data-test={`section-${section.id}`}>
<span data-test={`section-${section.id}-completed`}>
{section.completed?.toString() || "undefined"}
</span>
<span data-testid={`section-${section.id}-disabled`}>{section.disabled.toString()}</span>
<span data-test={`section-${section.id}-disabled`}>{section.disabled.toString()}</span>
</div>
))}
</div>

View File

@@ -7,14 +7,14 @@ const TestToggleComponent: React.FC<{ initialState?: boolean }> = ({ initialStat
return (
<div>
<span data-testid="toggle-state">{state ? "true" : "false"}</span>
<button data-testid="toggle-button" onClick={() => onToggle(null, !state)}>
<span data-test="toggle-state">{state ? "true" : "false"}</span>
<button data-test="toggle-button" onClick={() => onToggle(null, !state)}>
Toggle
</button>
<button data-testid="set-true-button" onClick={() => onToggle(null, true)}>
<button data-test="set-true-button" onClick={() => onToggle(null, true)}>
Set True
</button>
<button data-testid="set-false-button" onClick={() => onToggle(null, false)}>
<button data-test="set-false-button" onClick={() => onToggle(null, false)}>
Set False
</button>
</div>
@@ -57,8 +57,8 @@ describe("useToggle hook", () => {
return (
<div>
<span data-testid="toggle-state">{state ? "true" : "false"}</span>
<button data-testid="undefined-button" onClick={() => onToggle(null, undefined)}>
<span data-test="toggle-state">{state ? "true" : "false"}</span>
<button data-test="undefined-button" onClick={() => onToggle(null, undefined)}>
Set Undefined
</button>
</div>

View File

@@ -24,8 +24,8 @@ describe("FieldRow", () => {
it("renders children content correctly", () => {
render(
<FieldRow label={testLabel}>
<input type="text" data-testid="test-input" />
<button data-testid="test-button">Click me</button>
<input type="text" data-test="test-input" />
<button data-test="test-button">Click me</button>
</FieldRow>,
);
@@ -123,7 +123,7 @@ describe("FieldRow", () => {
it("positions children in grow container with center alignment", () => {
const { container } = render(
<FieldRow label={testLabel}>
<div data-testid="child-content">{mockChildContent}</div>
<div data-test="child-content">{mockChildContent}</div>
</FieldRow>,
);
@@ -135,7 +135,7 @@ describe("FieldRow", () => {
it("maintains layout when no label is provided", () => {
const { container } = render(
<FieldRow>
<div data-testid="child-content">{mockChildContent}</div>
<div data-test="child-content">{mockChildContent}</div>
</FieldRow>,
);

View File

@@ -209,6 +209,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
checked={isAutoscaleSelected}
type="radio"
role="radio"
data-test="ThroughputInput/ThroughputMode:Autoscale"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/>
@@ -224,6 +225,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
type="radio"
aria-required={true}
role="radio"
data-test="ThroughputInput/ThroughputMode:Manual"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Manual")}
/>
@@ -286,7 +288,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack>
<TextField
id="autoscaleRUValueField"
data-test="autoscaleRUInput"
data-test="ThroughputInput/AutoscaleRUInput"
type="number"
styles={{
fieldGroup: { width: 100, height: 27, flexShrink: 0 },
@@ -352,6 +354,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
}
>
<TextField
data-test="ThroughputInput/ManualThroughputInput"
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },

View File

@@ -301,6 +301,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
type="radio"
role="radio"
id="databaseCreateNew"
data-test="AddCollectionPanel/DatabaseRadio:CreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
@@ -314,6 +315,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
name="databaseType"
type="radio"
role="radio"
data-test="AddCollectionPanel/DatabaseRadio:UseExisting"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
@@ -337,6 +339,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40}
className="panelTextField"
aria-label="New database id, Type a new database id"
data-test="AddCollectionPanel/DatabaseId"
tabIndex={0}
value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -346,18 +349,20 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!isServerlessAccount() && (
<Stack horizontal>
<Checkbox
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
checked={this.state.isSharedThroughputChecked}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ isSharedThroughputChecked: isChecked })
}
/>
<div data-test="AddCollectionPanel/SharedThroughputCheckbox">
<Checkbox
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
checked={this.state.isSharedThroughputChecked}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ isSharedThroughputChecked: isChecked })
}
/>
</div>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
@@ -396,6 +401,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.state.createNewDatabase && (
<Dropdown
ariaLabel="Choose an existing database"
data-test="AddCollectionPanel/ExistingDatabaseDropdown"
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
style={{ width: 300, fontSize: 12 }}
placeholder="Choose an existing database"
@@ -443,6 +449,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
placeholder={`e.g., ${getCollectionName()}1`}
size={40}
className="panelTextField"
data-test="AddCollectionPanel/CollectionId"
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
value={this.state.collectionId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -576,6 +583,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<input
type="text"
id="addCollection-partitionKeyValue"
data-test="AddCollectionPanel/PartitionKey"
aria-required
required
size={40}
@@ -612,6 +620,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<input
type="text"
id="addCollection-partitionKeyValue"
data-test="AddCollectionPanel/PartitionKey"
key={`addCollection-partitionKeyValue_${index}`}
aria-required
required
@@ -729,7 +738,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)}
{!isFabricNative() && userContext.apiType === "SQL" && (
<Stack style={{ marginTop: -2, marginBottom: -4 }}>
<Stack style={{ marginTop: -2, marginBottom: -4 }} data-test="AddCollectionPanel/UniqueKeysSection">
{UniqueKeysHeader()}
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
return (
@@ -743,6 +752,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode"
}
className="panelTextField"
data-test="AddCollectionPanel/UniqueKey"
value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -769,6 +779,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<ActionButton
iconProps={{ iconName: "Add" }}
data-test="AddCollectionPanel/AddUniqueKeyButton"
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
onClick={() => this.setState({ uniqueKeys: [...this.state.uniqueKeys, ""] })}
>

View File

@@ -120,6 +120,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
<TextField
id="confirmCollectionId"
data-test="DeleteCollectionConfirmationPane/ConfirmInput"
autoFocus
value={inputCollectionName}
styles={{ fieldGroup: { width: 300 } }}

View File

@@ -135,7 +135,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
<Text variant="small">{confirmDatabase}</Text>
<TextField
id="confirmDatabaseId"
data-test="Input:confirmDatabaseId"
data-test="DeleteDatabaseConfirmationPanel/ConfirmInput"
autoFocus
styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => {

View File

@@ -244,7 +244,12 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
return (
<div className={styles.globalCommandsContainer} data-test="GlobalCommands">
{actions.length === 1 ? (
<Button icon={primaryAction.icon} onClick={onPrimaryActionClick} ref={primaryFocusableRef}>
<Button
data-test={`GlobalCommands/Button:${primaryAction.label}`}
icon={primaryAction.icon}
onClick={onPrimaryActionClick}
ref={primaryFocusableRef}
>
{primaryAction.label}
</Button>
) : (
@@ -253,8 +258,12 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
{(triggerProps: MenuButtonProps) => (
<div ref={setGlobalCommandButton}>
<SplitButton
data-test={`GlobalCommands/Button:${primaryAction.label}`}
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
primaryActionButton={{ onClick: onPrimaryActionClick, ref: primaryFocusableRef }}
primaryActionButton={{
onClick: onPrimaryActionClick,
ref: primaryFocusableRef,
}}
className={styles.globalCommandsSplitButton}
icon={primaryAction.icon}
>

View File

@@ -0,0 +1,138 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import { deleteKeyspace, fillCassandraTableDetails, setThroughput } from "../helpers/containerCreationHelpers";
test.describe("Cassandra API - Keyspace and Table Creation", () => {
test("Create table in new keyspace with non-shared throughput", async ({ page }) => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
await fillCassandraTableDetails(panel, keyspaceId, tableId);
await setThroughput(panel, true, TEST_AUTOSCALE_THROUGHPUT_RU);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const keyspaceNode = await explorer.waitForNode(keyspaceId);
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup
await deleteKeyspace(explorer, keyspaceId);
await expect(keyspaceNode.element).not.toBeAttached();
});
test("Create table in new keyspace with shared throughput", async ({ page }) => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
await fillCassandraTableDetails(panel, keyspaceId, tableId);
await panel
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
.getByRole("checkbox")
.check({ force: true });
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const keyspaceNode = await explorer.waitForNode(keyspaceId);
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup
await deleteKeyspace(explorer, keyspaceId);
await expect(keyspaceNode.element).not.toBeAttached();
});
test("Create table with autoscale throughput", async ({ page }) => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
await fillCassandraTableDetails(panel, keyspaceId, tableId);
await setThroughput(panel, true, TEST_AUTOSCALE_THROUGHPUT_RU);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup
await deleteKeyspace(explorer, keyspaceId);
});
test("Create table with manual throughput", async ({ page }) => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
const manualThroughput = 400;
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
await fillCassandraTableDetails(panel, keyspaceId, tableId);
await setThroughput(panel, false, manualThroughput);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup
await deleteKeyspace(explorer, keyspaceId);
});
test("Create multiple tables in keyspace", async ({ page }) => {
const keyspaceId = generateUniqueName("keyspace");
const table1Id = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
// Create first table
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
await fillCassandraTableDetails(panel, keyspaceId, table1Id);
await setThroughput(panel, true, TEST_AUTOSCALE_THROUGHPUT_RU);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const keyspaceNode = await explorer.waitForNode(keyspaceId);
await explorer.waitForContainerNode(keyspaceId, table1Id);
// Cleanup
await deleteKeyspace(explorer, keyspaceId);
await expect(keyspaceNode.element).not.toBeAttached();
});
});

View File

@@ -344,7 +344,7 @@ export class DataExplorer {
* There's only a single "primary" button, but we still require you to pass the label to confirm you're selecting the right button.
*/
globalCommandButton(label: string): Locator {
return this.frame.getByTestId("GlobalCommands").getByText(label);
return this.frame.getByTestId(`GlobalCommands/Button:${label}`);
}
/** Select the command bar button with the specified label */

View File

@@ -0,0 +1,135 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import { GREMLIN_CONFIG, deleteDatabase, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
test.describe("Gremlin API - Graph Creation", () => {
test("Create graph in new database with non-shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const graphId = generateUniqueName("graph");
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
databaseId,
containerId: graphId,
partitionKey: "/pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
await expect(graphNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Create graph in new database with shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const graphId = generateUniqueName("graph");
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
databaseId,
containerId: graphId,
partitionKey: "/pk",
useSharedThroughput: true,
});
const databaseNode = await explorer.waitForNode(databaseId);
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
await expect(graphNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Create graph with autoscale throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const graphId = generateUniqueName("graph");
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
databaseId,
containerId: graphId,
partitionKey: "/pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
await expect(graphNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Create graph with manual throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const graphId = generateUniqueName("graph");
const manualThroughput = 400;
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
databaseId,
containerId: graphId,
partitionKey: "/pk",
isAutoscale: false,
throughputValue: manualThroughput,
});
const databaseNode = await explorer.waitForNode(databaseId);
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
await expect(graphNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Create graph - no unique keys support", async ({ page }) => {
// Gremlin doesn't support unique keys, verify panel doesn't show unique key UI
const databaseId = generateUniqueName("db");
const graphId = generateUniqueName("graph");
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
await explorer.globalCommandButton("New Graph").click();
await explorer.whilePanelOpen(
"New Graph",
async (panel, okButton) => {
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(graphId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
const uniqueKeyButton = panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton");
await expect(uniqueKeyButton).not.toBeVisible();
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
const databaseNode = await explorer.waitForNode(databaseId);
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
await expect(graphNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
});

View File

@@ -0,0 +1,323 @@
import { Locator } from "@playwright/test";
import { DataExplorer, TestAccount } from "../fx";
/**
* Container creation test API configuration
* Defines labels and selectors specific to each Cosmos DB API
*/
export interface ApiConfig {
account: TestAccount;
commandLabel: string; // "New Container", "New Collection", "New Graph", "New Table"
containerIdLabel: string; // "Container id", "Collection id", "Graph id", "Table id"
panelTitle: string; // "New Container", "New Collection", "New Graph", "Add Table"
databaseIdPlaceholder: string; // "Type a new keyspace id" for Cassandra, etc.
containerIdPlaceholder: string;
partitionKeyLabel?: string; // "Partition key", "Shard key", or undefined for Tables
partitionKeyPlaceholder?: string;
confirmDeleteLabel: string; // "Confirm by typing the [container/collection/table/graph] id"
databaseName?: string; // "TablesDB" for Tables, undefined for others
supportsUniqueKeys: boolean;
}
export const SQL_CONFIG: ApiConfig = {
account: TestAccount.SQL,
commandLabel: "New Container",
containerIdLabel: "Container id, Example Container1",
panelTitle: "New Container",
databaseIdPlaceholder: "Type a new database id",
containerIdPlaceholder: "e.g., Container1",
partitionKeyLabel: "Partition key",
partitionKeyPlaceholder: "/pk",
confirmDeleteLabel: "Confirm by typing the container id",
supportsUniqueKeys: true,
};
export const MONGO_CONFIG: ApiConfig = {
account: TestAccount.Mongo,
commandLabel: "New Collection",
containerIdLabel: "Collection id, Example Collection1",
panelTitle: "New Collection",
databaseIdPlaceholder: "Type a new database id",
containerIdPlaceholder: "e.g., Collection1",
partitionKeyLabel: "Shard key",
partitionKeyPlaceholder: "pk",
confirmDeleteLabel: "Confirm by typing the collection id",
supportsUniqueKeys: true,
};
export const MONGO32_CONFIG: ApiConfig = {
...MONGO_CONFIG,
account: TestAccount.Mongo32,
};
export const GREMLIN_CONFIG: ApiConfig = {
account: TestAccount.Gremlin,
commandLabel: "New Graph",
containerIdLabel: "Graph id, Example Graph1",
panelTitle: "New Graph",
databaseIdPlaceholder: "Type a new database id",
containerIdPlaceholder: "e.g., Graph1",
partitionKeyLabel: "Partition key",
partitionKeyPlaceholder: "/pk",
confirmDeleteLabel: "Confirm by typing the graph id",
supportsUniqueKeys: false,
};
export const TABLES_CONFIG: ApiConfig = {
account: TestAccount.Tables,
commandLabel: "New Table",
containerIdLabel: "Table id, Example Table1",
panelTitle: "New Table",
databaseIdPlaceholder: "", // Not used
containerIdPlaceholder: "e.g., Table1",
confirmDeleteLabel: "Confirm by typing the table id",
databaseName: "TablesDB",
supportsUniqueKeys: false,
};
export const CASSANDRA_CONFIG: ApiConfig = {
account: TestAccount.Cassandra,
commandLabel: "New Table",
containerIdLabel: "Enter table Id",
panelTitle: "Add Table",
databaseIdPlaceholder: "Type a new keyspace id",
containerIdPlaceholder: "Enter table Id",
confirmDeleteLabel: "Confirm by typing the table id",
supportsUniqueKeys: false,
};
/**
* Fills database selection in the panel
* Automatically selects "Create new" and fills the database ID
*/
export async function fillDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
// Wait for the radio button to be visible and click it (more reliable than check for custom styled radios)
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").waitFor({ state: "visible" });
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").click();
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
}
/**
* Fills existing database selection
* Selects "Use existing" and clicks the dropdown to select the database
*/
export async function fillExistingDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").waitFor({ state: "visible" });
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").click();
await panel.getByTestId("AddCollectionPanel/ExistingDatabaseDropdown").click();
await panel.locator(`text=${databaseId}`).click();
}
/**
* Fills container/collection/graph/table details
*/
export async function fillContainerDetails(
panel: Locator,
containerId: string,
partitionKey: string | undefined,
): Promise<void> {
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(containerId);
if (partitionKey) {
await panel.getByTestId("AddCollectionPanel/PartitionKey").first().fill(partitionKey);
}
}
/**
* Fills Cassandra-specific table details
* (keyspace and table IDs are separate for Cassandra)
*/
export async function fillCassandraTableDetails(panel: Locator, keyspaceId: string, tableId: string): Promise<void> {
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(keyspaceId);
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(tableId);
}
/**
* Sets throughput mode and value
* @param isAutoscale - if true, sets autoscale mode; if false, sets manual mode
*/
export async function setThroughput(panel: Locator, isAutoscale: boolean, throughputValue: number): Promise<void> {
const testId = isAutoscale ? "ThroughputInput/ThroughputMode:Autoscale" : "ThroughputInput/ThroughputMode:Manual";
await panel.getByTestId(testId).check();
if (isAutoscale) {
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(throughputValue.toString());
} else {
await panel.getByTestId("ThroughputInput/ManualThroughputInput").fill(throughputValue.toString());
}
}
/**
* Adds a unique key to the container (SQL/Mongo only)
*/
export async function addUniqueKey(panel: Locator, uniqueKeyValue: string): Promise<void> {
// Scroll to find the unique key section
await panel.getByTestId("AddCollectionPanel/UniqueKeysSection").scrollIntoViewIfNeeded();
// Click the "Add unique key" button
await panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton").click();
// Fill in the unique key value
const uniqueKeyInput = panel.getByTestId("AddCollectionPanel/UniqueKey").first();
await uniqueKeyInput.fill(uniqueKeyValue);
}
/**
* Deletes a database and waits for it to disappear from the tree
*/
export async function deleteDatabase(
explorer: DataExplorer,
databaseId: string,
databaseNodeName: string = databaseId,
): Promise<void> {
const databaseNode = await explorer.waitForNode(databaseNodeName);
await databaseNode.openContextMenu();
await databaseNode.contextMenuItem("Delete Database").click();
await explorer.whilePanelOpen(
"Delete Database",
async (panel: Locator, okButton: Locator) => {
await panel.getByTestId("DeleteDatabaseConfirmationPanel/ConfirmInput").fill(databaseId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
}
/**
* Deletes a keyspace (Cassandra only)
*/
export async function deleteKeyspace(explorer: DataExplorer, keyspaceId: string): Promise<void> {
const keyspaceNode = await explorer.waitForNode(keyspaceId);
await keyspaceNode.openContextMenu();
await keyspaceNode.contextMenuItem("Delete Keyspace").click();
await explorer.whilePanelOpen(
"Delete Keyspace",
async (panel: Locator, okButton: Locator) => {
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(keyspaceId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
}
/**
* Deletes a container/collection/graph/table
*/
export async function deleteContainer(
explorer: DataExplorer,
databaseId: string,
containerId: string,
deleteLabel: string, // "Delete Container", "Delete Collection", etc.
): Promise<void> {
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await containerNode.openContextMenu();
await containerNode.contextMenuItem(deleteLabel).click();
await explorer.whilePanelOpen(
deleteLabel,
async (panel: Locator, okButton: Locator) => {
// All container/collection/graph/table deletes use same panel with test ID
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(containerId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
}
/**
* Opens the create container dialog and fills in the form based on scenario
*/
export async function openAndFillCreateContainerPanel(
explorer: DataExplorer,
config: ApiConfig,
options: {
databaseId: string;
containerId: string;
partitionKey?: string;
useExistingDatabase?: boolean;
isAutoscale?: boolean;
throughputValue?: number;
uniqueKey?: string;
useSharedThroughput?: boolean;
},
): Promise<void> {
await explorer.globalCommandButton(config.commandLabel).click();
await explorer.whilePanelOpen(
config.panelTitle,
async (panel, okButton) => {
// Database selection
if (options.useExistingDatabase) {
await fillExistingDatabaseSelection(panel, options.databaseId);
} else {
await fillDatabaseSelection(panel, options.databaseId);
}
// Shared throughput checkbox (if applicable)
if (options.useSharedThroughput) {
await panel
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
.getByRole("checkbox")
.check({ force: true });
}
// Container details
await fillContainerDetails(panel, options.containerId, options.partitionKey);
// Throughput (only if not using shared throughput)
if (!options.useSharedThroughput) {
const isAutoscale = options.isAutoscale !== false;
const throughputValue = options.throughputValue || 1000;
await setThroughput(panel, isAutoscale, throughputValue);
}
// Unique keys (if applicable)
if (options.uniqueKey && config.supportsUniqueKeys) {
await addUniqueKey(panel, options.uniqueKey);
}
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
}
/**
* Opens the create table dialog for Cassandra and fills in the form
* Cassandra has a different UI pattern than other APIs
*/
export async function openAndFillCreateCassandraTablePanel(
explorer: DataExplorer,
options: {
keyspaceId: string;
tableId: string;
isAutoscale?: boolean;
throughputValue?: number;
useSharedThroughput?: boolean;
},
): Promise<void> {
await explorer.globalCommandButton("New Table").click();
await explorer.whilePanelOpen(
"Add Table",
async (panel, okButton) => {
// Fill Cassandra-specific table details
await fillCassandraTableDetails(panel, options.keyspaceId, options.tableId);
// Shared throughput checkbox (if applicable)
if (options.useSharedThroughput) {
await panel
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
.getByRole("checkbox")
.check({ force: true });
}
// Throughput (only if not using shared throughput)
if (!options.useSharedThroughput) {
const isAutoscale = options.isAutoscale !== false;
const throughputValue = options.throughputValue || 1000;
await setThroughput(panel, isAutoscale, throughputValue);
}
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
}

View File

@@ -0,0 +1,125 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import { MONGO_CONFIG, deleteDatabase, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
test("Mongo: New database non-shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const collectionId = generateUniqueName("collection");
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
databaseId,
containerId: collectionId,
partitionKey: "pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
await expect(collectionNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Mongo: New database shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const collectionId = generateUniqueName("collection");
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
databaseId,
containerId: collectionId,
partitionKey: "pk",
useSharedThroughput: true,
});
const databaseNode = await explorer.waitForNode(databaseId);
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
await expect(collectionNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Mongo: Unique keys", async ({ page }) => {
const databaseId = generateUniqueName("db");
const collectionId = generateUniqueName("collection");
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
databaseId,
containerId: collectionId,
partitionKey: "pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
uniqueKey: "email",
});
const databaseNode = await explorer.waitForNode(databaseId);
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
await expect(collectionNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Mongo: Autoscale throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const collectionId = generateUniqueName("collection");
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
databaseId,
containerId: collectionId,
partitionKey: "pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
await expect(collectionNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("Mongo: Manual throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const collectionId = generateUniqueName("collection");
const manualThroughput = 400;
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
databaseId,
containerId: collectionId,
partitionKey: "pk",
isAutoscale: false,
throughputValue: manualThroughput,
});
const databaseNode = await explorer.waitForNode(databaseId);
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
await expect(collectionNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});

View File

@@ -0,0 +1,125 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import { SQL_CONFIG, deleteDatabase, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
test("SQL: New database non-shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
databaseId,
containerId,
partitionKey: "/pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeAttached();
// Cleanup - delete database which cascades container deletion
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("SQL: New database shared throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
databaseId,
containerId,
partitionKey: "/pk",
useSharedThroughput: true,
});
const databaseNode = await explorer.waitForNode(databaseId);
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("SQL: Unique keys", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
databaseId,
containerId,
partitionKey: "/pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
uniqueKey: "/email,/username",
});
const databaseNode = await explorer.waitForNode(databaseId);
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("SQL: Autoscale throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
databaseId,
containerId,
partitionKey: "/pk",
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const databaseNode = await explorer.waitForNode(databaseId);
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});
test("SQL: Manual throughput", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
const manualThroughput = 400;
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
databaseId,
containerId,
partitionKey: "/pk",
isAutoscale: false,
throughputValue: manualThroughput,
});
const databaseNode = await explorer.waitForNode(databaseId);
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeAttached();
// Cleanup
await deleteDatabase(explorer, databaseId);
await expect(databaseNode.element).not.toBeAttached();
});

View File

@@ -0,0 +1,147 @@
import { expect, test } from "@playwright/test";
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import { TABLES_CONFIG, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
test.describe("Tables API - Table Creation", () => {
test("Create table in TablesDB with non-shared throughput", async ({ page }) => {
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Tables);
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
databaseId: "TablesDB", // Tables uses a fixed database
containerId: tableId,
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup - delete table
await tableNode.openContextMenu();
await tableNode.contextMenuItem("Delete Table").click();
await explorer.whilePanelOpen(
"Delete Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
await expect(tableNode.element).not.toBeAttached();
});
test("Create multiple tables in TablesDB", async ({ page }) => {
const table1Id = generateUniqueName("table");
const table2Id = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Tables);
// Create first table
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
databaseId: "TablesDB",
containerId: table1Id,
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
await explorer.waitForContainerNode("TablesDB", table1Id);
// Create second table
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
databaseId: "TablesDB",
containerId: table2Id,
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const table2Node = await explorer.waitForContainerNode("TablesDB", table2Id);
await expect(table2Node.element).toBeAttached();
// Cleanup - delete both tables
const table1Node = await explorer.waitForContainerNode("TablesDB", table1Id);
await table1Node.openContextMenu();
await table1Node.contextMenuItem("Delete Table").click();
await explorer.whilePanelOpen(
"Delete Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(table1Id);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
await table2Node.openContextMenu();
await table2Node.contextMenuItem("Delete Table").click();
await explorer.whilePanelOpen(
"Delete Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(table2Id);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
});
test("Create table with shared throughput", async ({ page }) => {
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Tables);
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
databaseId: "TablesDB",
containerId: tableId,
useSharedThroughput: true,
});
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
await expect(tableNode.element).toBeAttached();
// Cleanup
await tableNode.openContextMenu();
await tableNode.contextMenuItem("Delete Table").click();
await explorer.whilePanelOpen(
"Delete Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
});
test("Create table - no partition key support", async ({ page }) => {
// Tables don't use partition keys, verify they're not shown in UI
const tableId = generateUniqueName("table");
const explorer = await DataExplorer.open(page, TestAccount.Tables);
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
databaseId: "TablesDB",
containerId: tableId,
isAutoscale: true,
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
});
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
await expect(tableNode.element).toBeAttached();
// Verify partition key field is not present (Tables don't use partition keys)
// This would need to be checked during panel open, so we keep the inline test for this validation
// Cleanup
await tableNode.openContextMenu();
await tableNode.contextMenuItem("Delete Table").click();
await explorer.whilePanelOpen(
"Delete Table",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
});
});