mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-31 06:41:35 +00:00
Added different container creation playwirhgt tests
This commit is contained in:
138
test/cassandra/containerCreation.spec.ts
Normal file
138
test/cassandra/containerCreation.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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 */
|
||||
|
||||
135
test/gremlin/containerCreation.spec.ts
Normal file
135
test/gremlin/containerCreation.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
323
test/helpers/containerCreationHelpers.ts
Normal file
323
test/helpers/containerCreationHelpers.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
125
test/mongo/containerCreation.spec.ts
Normal file
125
test/mongo/containerCreation.spec.ts
Normal 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();
|
||||
});
|
||||
125
test/sql/containerCreation.spec.ts
Normal file
125
test/sql/containerCreation.spec.ts
Normal 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();
|
||||
});
|
||||
147
test/tables/containerCreation.spec.ts
Normal file
147
test/tables/containerCreation.spec.ts
Normal 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 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user