From d78f3eae289998214f6b63436b399cee0a4c6b64 Mon Sep 17 00:00:00 2001 From: Bikram Choudhury Date: Fri, 22 May 2026 18:09:04 +0530 Subject: [PATCH] added playwright tests --- src/Explorer/Tabs/Tabs.tsx | 1 + test/fx.ts | 10 +++ test/sql/duplicateTab.spec.ts | 126 ++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 test/sql/duplicateTab.spec.ts diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index 6c97c7cbf..186a5b5bd 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -91,6 +91,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
  • setHovering(true)} onMouseLeave={() => setHovering(false)} className={active ? "active tabList" : "tabList"} diff --git a/test/fx.ts b/test/fx.ts index d65d776b1..7c8dcbd3f 100644 --- a/test/fx.ts +++ b/test/fx.ts @@ -616,6 +616,16 @@ export class DataExplorer { await page.goto(url); return DataExplorer.waitForExplorer(page); } + + /** Returns the tab navigation
  • element for the given tab ID or React tab name (e.g. "tab0", "Home") */ + tabNavHeader(tabId: string): Locator { + return this.frame.getByTestId(`TabNav:${tabId}`); + } + + /** Returns a context menu item in the tab right-click menu by visible label */ + tabContextMenuItem(label: string): Locator { + return this.frame.getByRole("menuitem", { name: label }); + } } export async function waitForApiResponse( diff --git a/test/sql/duplicateTab.spec.ts b/test/sql/duplicateTab.spec.ts new file mode 100644 index 000000000..d1de09f80 --- /dev/null +++ b/test/sql/duplicateTab.spec.ts @@ -0,0 +1,126 @@ +import { expect, test } from "@playwright/test"; + +import { DataExplorer, TestAccount } from "../fx"; +import { createTestSQLContainer, TestContainerContext } from "../testData"; + +let context: TestContainerContext = null!; +let explorer: DataExplorer = null!; + +test.beforeAll("Create Test Database", async () => { + context = await createTestSQLContainer({ includeTestData: false }); +}); + +test.afterAll("Delete Test Database", async () => { + await context?.dispose(); +}); + +test.beforeEach("Open Data Explorer", async ({ page }) => { + explorer = await DataExplorer.open(page, TestAccount.SQL); +}); + +test("Duplicate Items tab opens a second Items tab", async () => { + // Open Items tab + const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id); + await containerNode.expand(); + const itemsNode = await explorer.waitForContainerItemsNode(context.database.id, context.container.id); + await itemsNode.element.click(); + + const documentsTab = explorer.documentsTab("tab0"); + await documentsTab.documentsFilter.waitFor({ timeout: 30_000 }); + + // Right-click the tab nav header + await explorer.tabNavHeader("tab0").click({ button: "right" }); + + // "Duplicate tab" should be visible in the context menu + const duplicateMenuItem = explorer.tabContextMenuItem("Duplicate tab"); + await expect(duplicateMenuItem).toBeVisible(); + await duplicateMenuItem.click(); + + // A second tab should appear + const tab1 = explorer.tab("tab1"); + await expect(tab1).toBeAttached({ timeout: 30_000 }); + + // The duplicated tab should also show the Documents content + const duplicatedTab = explorer.documentsTab("tab1"); + await duplicatedTab.documentsFilter.waitFor({ timeout: 30_000 }); +}); + +test("Duplicate Query tab preserves query text in new tab", async () => { + // Open a new SQL query tab via container context menu + const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id); + await containerNode.openContextMenu(); + await containerNode.contextMenuItem("New SQL Query").click(); + + const queryTab = explorer.queryTab("tab0"); + const editor = queryTab.editor(); + await editor.locator.waitFor({ timeout: 30_000 }); + + // Type a custom query + const customQuery = 'SELECT * FROM c WHERE c.id = "duplicate-query-test"'; + await editor.setText(customQuery); + + // Right-click the tab nav header + await explorer.tabNavHeader("tab0").click({ button: "right" }); + + const duplicateMenuItem = explorer.tabContextMenuItem("Duplicate tab"); + await expect(duplicateMenuItem).toBeVisible(); + await duplicateMenuItem.click(); + + // Second query tab should appear + const tab1 = explorer.tab("tab1"); + await expect(tab1).toBeAttached({ timeout: 30_000 }); + + // The duplicated tab should contain the same query text + const duplicatedQueryTab = explorer.queryTab("tab1"); + await duplicatedQueryTab.editor().locator.waitFor({ timeout: 30_000 }); + const editorText = await duplicatedQueryTab.editor().text(); + expect(editorText).toContain("duplicate-query-test"); +}); + +test("Duplicate Scale & Settings tab opens a second settings tab", async () => { + await explorer.openScaleAndSettings(context); + + // Wait for the settings tab pane to be attached + const tab0 = explorer.tab("tab0"); + await expect(tab0).toBeAttached({ timeout: 30_000 }); + + // Right-click the tab nav header + await explorer.tabNavHeader("tab0").click({ button: "right" }); + + const duplicateMenuItem = explorer.tabContextMenuItem("Duplicate tab"); + await expect(duplicateMenuItem).toBeVisible(); + await duplicateMenuItem.click(); + + // A second settings tab should appear + const tab1 = explorer.tab("tab1"); + await expect(tab1).toBeAttached({ timeout: 30_000 }); +}); + +test("Duplicate tab menu item is not shown for the Home tab", async () => { + // The Home tab (ReactTabKind) is never duplicable + await explorer.tabNavHeader("Home").click({ button: "right" }); + + // "Close tab" should always appear + await expect(explorer.tabContextMenuItem("Close tab")).toBeVisible(); + + // "Duplicate tab" must NOT appear + await expect(explorer.tabContextMenuItem("Duplicate tab")).not.toBeVisible(); +}); + +test("Close tab from right-click menu closes the tab", async () => { + // Open Items tab + const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id); + await containerNode.expand(); + const itemsNode = await explorer.waitForContainerItemsNode(context.database.id, context.container.id); + await itemsNode.element.click(); + + const documentsTab = explorer.documentsTab("tab0"); + await documentsTab.documentsFilter.waitFor({ timeout: 30_000 }); + + // Right-click the tab nav header and close the tab + await explorer.tabNavHeader("tab0").click({ button: "right" }); + await explorer.tabContextMenuItem("Close tab").click(); + + // The tab pane should be removed + await expect(explorer.tab("tab0")).not.toBeAttached({ timeout: 15_000 }); +});