From 811f3fb71955850a8f22cfdf3a160b1a0e0a2089 Mon Sep 17 00:00:00 2001 From: Sung-Hyun Kang Date: Wed, 27 May 2026 16:06:28 -0500 Subject: [PATCH] Added quantizer type playwright test --- .../CollapsibleSectionComponent.tsx | 2 + .../VectorEmbeddingPoliciesComponent.tsx | 1 + .../AddCollectionPanel/AddCollectionPanel.tsx | 1 + test/locale-override.js | 4 + test/sql/container.spec.ts | 174 +++++++++++++++++- .../containerPolicies/vectorPolicy.spec.ts | 123 ++++++++++++- 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 test/locale-override.js diff --git a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx index 3dcea02ac..261df751d 100644 --- a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx +++ b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx @@ -13,6 +13,7 @@ export interface CollapsibleSectionProps { onDelete?: () => void; disabled?: boolean; disableDelete?: boolean; + testId?: string; } export interface CollapsibleSectionState { @@ -57,6 +58,7 @@ export class CollapsibleSectionComponent extends React.Component diff --git a/src/Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent.tsx b/src/Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent.tsx index 88a6f021b..11c040b4d 100644 --- a/src/Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent.tsx +++ b/src/Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent.tsx @@ -426,6 +426,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent diff --git a/test/locale-override.js b/test/locale-override.js new file mode 100644 index 000000000..005beebf9 --- /dev/null +++ b/test/locale-override.js @@ -0,0 +1,4 @@ +// Temporary init script for Playwright MCP to override browser locale +// This runs before any page scripts, so i18next will detect 'ja' from navigator +Object.defineProperty(navigator, "language", { get: () => "ja" }); +Object.defineProperty(navigator, "languages", { get: () => ["ja", "ja-JP"] }); diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts index ea02b2105..9ff0e0f47 100644 --- a/test/sql/container.spec.ts +++ b/test/sql/container.spec.ts @@ -1,6 +1,13 @@ import { expect, test } from "@playwright/test"; -import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx"; +import { + DataExplorer, + ONE_MINUTE_MS, + TEST_AUTOSCALE_THROUGHPUT_RU, + TestAccount, + generateUniqueName, + getDropdownItemByNameOrPosition, +} from "../fx"; test("SQL database and container CRUD", async ({ page }) => { const databaseId = generateUniqueName("db"); @@ -50,3 +57,168 @@ test("SQL database and container CRUD", async ({ page }) => { await expect(databaseNode.element).not.toBeAttached(); }); + +test.describe("Vector embedding quantizer type in New Container panel", () => { + /** + * Opens the New Container panel and expands the Container Vector Policy section. + * Returns the panel locator and the scoped vector policy content locator. + */ + const openPanelWithVectorPolicy = async (explorer: DataExplorer) => { + const newContainerButton = await explorer.globalCommandButton("New Container"); + await newContainerButton.click(); + + const panel = explorer.panel("New Container"); + await panel.waitFor(); + + // Expand section via its stable data-test id (avoids matching localized title text) + await explorer.frame.getByTestId("container-vector-policy-section").click(); + + const vectorSection = explorer.frame.locator("#collapsibleVectorPolicySectionContent"); + await vectorSection.locator("#add-vector-policy").waitFor(); + + return { panel, vectorSection }; + }; + + /** Closes the New Container panel without submitting via the Fluent UI Panel close button. */ + const closePanel = async (explorer: DataExplorer) => { + await explorer.frame.locator("button.ms-Panel-closeButton").click(); + await explorer.panel("New Container").waitFor({ state: "detached" }); + }; + + test("Quantizer type dropdown is disabled by default when index type is none", async ({ page }) => { + const explorer = await DataExplorer.open(page, TestAccount.SQL); + const { vectorSection } = await openPanelWithVectorPolicy(explorer); + + await vectorSection.locator("#add-vector-policy").click(); + + // Index type defaults to "none" — quantizer type must be disabled + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await expect(quantizerDropdownBtn).toBeDisabled(); + + await closePanel(explorer); + }); + + test("Quantizer type dropdown is disabled for flat index type", async ({ page }) => { + const explorer = await DataExplorer.open(page, TestAccount.SQL); + const { vectorSection } = await openPanelWithVectorPolicy(explorer); + + await vectorSection.locator("#add-vector-policy").click(); + + // Select "flat" index type (does not support quantization). + // Use exact match because "flat" is also a substring of "quantizedFlat". + await vectorSection.locator("#vector-policy-indexType-1").click(); + await explorer.frame.getByRole("option", { name: "flat", exact: true }).click(); + + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await expect(quantizerDropdownBtn).toBeDisabled(); + + await closePanel(explorer); + }); + + test("Quantizer type dropdown becomes enabled with diskANN index type and defaults to Product", async ({ page }) => { + const explorer = await DataExplorer.open(page, TestAccount.SQL); + const { vectorSection } = await openPanelWithVectorPolicy(explorer); + + await vectorSection.locator("#add-vector-policy").click(); + + await vectorSection.locator("#vector-policy-indexType-1").click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await expect(quantizerDropdownBtn).toBeEnabled(); + await expect(quantizerDropdownBtn).toContainText("Product"); + + await closePanel(explorer); + }); + + test("Quantizer type dropdown becomes enabled with quantizedFlat index type and defaults to Product", async ({ + page, + }) => { + const explorer = await DataExplorer.open(page, TestAccount.SQL); + const { vectorSection } = await openPanelWithVectorPolicy(explorer); + + await vectorSection.locator("#add-vector-policy").click(); + + // Select "quantizedFlat" index type — supports quantization + await vectorSection.locator("#vector-policy-indexType-1").click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "quantizedFlat" })).click(); + + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await expect(quantizerDropdownBtn).toBeEnabled(); + await expect(quantizerDropdownBtn).toContainText("Product"); + + await closePanel(explorer); + }); + + test("Quantizer type can be changed to Spherical (Preview)", async ({ page }) => { + const explorer = await DataExplorer.open(page, TestAccount.SQL); + const { vectorSection } = await openPanelWithVectorPolicy(explorer); + + await vectorSection.locator("#add-vector-policy").click(); + + await vectorSection.locator("#vector-policy-indexType-1").click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await quantizerDropdownBtn.click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click(); + + await expect(quantizerDropdownBtn).not.toContainText("Product"); + + await closePanel(explorer); + }); + + test("Creating a container with diskANN index type and Spherical quantizer type succeeds", async ({ page }) => { + const databaseId = generateUniqueName("db"); + const containerId = "testvecquantizer"; + const explorer = await DataExplorer.open(page, TestAccount.SQL); + + await (await explorer.globalCommandButton("New Container")).click(); + + await explorer.whilePanelOpen( + "New Container", + async (panel, okButton) => { + await panel.getByPlaceholder("Type a new database id").fill(databaseId); + await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId); + await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk"); + await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString()); + + await explorer.frame.getByTestId("container-vector-policy-section").click(); + const vectorSection = explorer.frame.locator("#collapsibleVectorPolicySectionContent"); + await vectorSection.locator("#add-vector-policy").waitFor(); + + await vectorSection.locator("#add-vector-policy").click(); + await vectorSection.locator("#vector-policy-path-1").fill("/embedding"); + await vectorSection.locator("#vector-policy-dimension-1").fill("1536"); + + await vectorSection.locator("#vector-policy-indexType-1").click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1"); + await expect(quantizerDropdownBtn).toBeEnabled(); + await quantizerDropdownBtn.click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click(); + + await okButton.click(); + }, + { closeTimeout: 5 * 60 * 1000 }, + ); + + const containerNode = await explorer.waitForContainerNode(databaseId, containerId); + await expect(containerNode.element).toBeVisible(); + + // Cleanup + const databaseNode = await explorer.waitForNode(databaseId); + await databaseNode.openContextMenu(); + await databaseNode.contextMenuItem("Delete Database").click(); + await explorer.whilePanelOpen( + "Delete Database", + async (panel, okButton) => { + await panel.getByRole("textbox", { name: "Confirm by typing the database id" }).fill(databaseId); + await okButton.click(); + }, + { closeTimeout: 5 * 60 * 1000 }, + ); + await expect(databaseNode.element).not.toBeAttached({ timeout: ONE_MINUTE_MS }); + }); +}); diff --git a/test/sql/scaleAndSettings/containerPolicies/vectorPolicy.spec.ts b/test/sql/scaleAndSettings/containerPolicies/vectorPolicy.spec.ts index f1fd4a39a..ec18a9b28 100644 --- a/test/sql/scaleAndSettings/containerPolicies/vectorPolicy.spec.ts +++ b/test/sql/scaleAndSettings/containerPolicies/vectorPolicy.spec.ts @@ -1,5 +1,11 @@ import { expect, test } from "@playwright/test"; -import { CommandBarButton, DataExplorer, ONE_MINUTE_MS, TestAccount } from "../../../fx"; +import { + CommandBarButton, + DataExplorer, + getDropdownItemByNameOrPosition, + ONE_MINUTE_MS, + TestAccount, +} from "../../../fx"; import { createTestSQLContainer, TestContainerContext } from "../../../testData"; test.describe("Vector Policy under Scale & Settings", () => { @@ -190,4 +196,119 @@ test.describe("Vector Policy under Scale & Settings", () => { const saveButton = explorer.commandBarButton(CommandBarButton.Save); await expect(saveButton).toBeDisabled(); }); + + test("Quantizer type dropdown is disabled when index type is none", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await expect(quantizerDropdownBtn).toBeDisabled(); + }); + + test("Quantizer type dropdown is disabled for flat index type", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click(); + // Use exact match because "flat" is also a substring of "quantizedFlat". + await explorer.frame.getByRole("option", { name: "flat", exact: true }).click(); + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await expect(quantizerDropdownBtn).toBeDisabled(); + }); + + test("Quantizer type dropdown becomes enabled and defaults to Product for diskANN index type", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await expect(quantizerDropdownBtn).toBeEnabled(); + await expect(quantizerDropdownBtn).toContainText("Product"); + }); + + test("Quantizer type dropdown becomes enabled and defaults to Product for quantizedFlat index type", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "quantizedFlat" })).click(); + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await expect(quantizerDropdownBtn).toBeEnabled(); + await expect(quantizerDropdownBtn).toContainText("Product"); + }); + + test("Quantizer type can be changed to Spherical (Preview)", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + await explorer.frame.locator(`#vector-policy-path-${newIndex}`).fill("/embedding"); + await explorer.frame.locator(`#vector-policy-dimension-${newIndex}`).fill("1536"); + + await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await quantizerDropdownBtn.click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click(); + + await expect(quantizerDropdownBtn).not.toContainText("Product"); + }); + + test("Saving a vector policy with diskANN and Spherical quantizer type persists", async () => { + const existingCount = await getPolicyCount(); + + const addButton = explorer.frame.locator("#add-vector-policy"); + await addButton.click(); + + const newIndex = existingCount + 1; + + await explorer.frame.locator(`#vector-policy-path-${newIndex}`).fill("/vecQuantizer"); + await explorer.frame.locator(`#vector-policy-dimension-${newIndex}`).fill("1536"); + + await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click(); + + const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await quantizerDropdownBtn.click(); + await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click(); + + // Save + const saveButton = explorer.commandBarButton(CommandBarButton.Save); + await expect(saveButton).toBeEnabled(); + await saveButton.click(); + await expect(explorer.getConsoleHeaderStatus()).toContainText(`${context.container.id}`, { + timeout: 2 * ONE_MINUTE_MS, + }); + + await explorer.openScaleAndSettings(context); + await explorer.frame.getByTestId("settings-tab-header/ContainerVectorPolicyTab").click(); + await explorer.frame.getByRole("tab", { name: "Vector Policy" }).click(); + + const savedQuantizerBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`); + await expect(savedQuantizerBtn).toContainText("Spherical"); + }); });