From a7408545b08989544c947154faf3a23b10798680 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 21 Jan 2025 16:42:23 +0300 Subject: [PATCH] test(ObjectSummary): properly find virtualized tree element --- tests/suites/tenant/summary/ObjectSummary.ts | 73 ++++++++++++++++++- .../tenant/summary/objectSummary.test.ts | 11 ++- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/tests/suites/tenant/summary/ObjectSummary.ts b/tests/suites/tenant/summary/ObjectSummary.ts index 0745082137..7e3b02da5c 100644 --- a/tests/suites/tenant/summary/ObjectSummary.ts +++ b/tests/suites/tenant/summary/ObjectSummary.ts @@ -12,10 +12,12 @@ export enum ObjectSummaryTab { Schema = 'Schema', } export class ObjectSummary { + private page: Page; private tabs: Locator; private schemaViewer: Locator; private tree: Locator; private treeRows: Locator; + private treeLoaders: Locator; private primaryKeys: Locator; private actionsMenu: ActionsMenu; private aclWrapper: Locator; @@ -34,8 +36,10 @@ export class ObjectSummary { private overviewWrapper: Locator; constructor(page: Page) { + this.page = page; this.tree = page.locator('.ydb-object-summary__tree'); this.treeRows = page.locator('.ydb-tree-view'); + this.treeLoaders = page.locator('.ydb-navigation-tree-view-loader'); this.tabs = page.locator('.ydb-object-summary__tabs'); this.schemaViewer = page.locator('.schema-viewer'); this.primaryKeys = page.locator('.schema-viewer__keys_type_primary'); @@ -166,6 +170,16 @@ export class ObjectSummary { return true; } + async isTreeLoaded() { + const loaders = await this.treeLoaders.all(); + + for (const loader of loaders) { + await loader.waitFor({state: 'hidden', timeout: VISIBILITY_TIMEOUT}); + } + + return true; + } + async isTreeHidden() { await this.tree.waitFor({state: 'hidden', timeout: VISIBILITY_TIMEOUT}); return true; @@ -181,8 +195,61 @@ export class ObjectSummary { return true; } + async getTreeItem(text: string) { + // Ensure tree is ready for the search + await this.isTreeVisible(); + await this.isTreeLoaded(); + + const itemLocator = this.treeRows.filter({hasText: text}).first(); + + // Default timeout is too big for such case + const timeout = 500; + + if (await itemLocator.isVisible({timeout})) { + return itemLocator; + } + + // Element could be in not rendered (virtualized) part of the tree + // Such element cannot be found by playwright + // Scroll 200px * 10 from top to bottom to find element + // Firstly scroll to top in case tree was already scrolled down + await this.tree.evaluate((e) => { + e.scrollTo({top: 0, behavior: 'instant'}); + }); + + // Wait after scroll for elements to become stable + await this.page.waitForTimeout(50); + + if (await itemLocator.isVisible({timeout})) { + return itemLocator; + } + + // Hover element so page.mouse.wheel work for it + await this.tree.hover(); + + // Start scrolling from top to bottom untill desired element is found + let i = 0; + while (i < 10) { + i++; + + await this.page.mouse.wheel(0, 200); + + // Wait after scroll for elements to become stable + await this.page.waitForTimeout(50); + + // Some nested nodes could be loading + await this.isTreeLoaded(); + + if (await itemLocator.isVisible({timeout})) { + return itemLocator; + } + } + + throw new Error(`Tree item ${text} was not found`); + } + async isOpenPreviewIconVisibleOnHover(text: string): Promise { - const treeItem = this.treeRows.filter({hasText: text}).first(); + const treeItem = await this.getTreeItem(text); await treeItem.hover(); const openPreviewIcon = treeItem.locator('button[title="Open preview"]'); @@ -196,7 +263,7 @@ export class ObjectSummary { } async clickPreviewButton(text: string): Promise { - const treeItem = this.treeRows.filter({hasText: text}).first(); + const treeItem = await this.getTreeItem(text); await treeItem.hover(); const openPreviewIcon = treeItem.locator('button[title="Open preview"]'); @@ -204,7 +271,7 @@ export class ObjectSummary { } async clickActionsButton(text: string): Promise { - const treeItem = this.treeRows.filter({hasText: text}).first(); + const treeItem = await this.getTreeItem(text); await treeItem.hover(); const actionsIcon = treeItem.locator('.g-dropdown-menu__switcher-button'); diff --git a/tests/suites/tenant/summary/objectSummary.test.ts b/tests/suites/tenant/summary/objectSummary.test.ts index 5c426a932b..051690845f 100644 --- a/tests/suites/tenant/summary/objectSummary.test.ts +++ b/tests/suites/tenant/summary/objectSummary.test.ts @@ -251,7 +251,7 @@ test.describe('Object Summary', async () => { await objectSummary.createDirectory(directoryName); // Verify the new directory appears in the tree - const treeItem = page.locator('.ydb-tree-view').filter({hasText: directoryName}); + const treeItem = await objectSummary.getTreeItem(directoryName); await expect(treeItem).toBeVisible(); }); @@ -276,14 +276,17 @@ test.describe('Object Summary', async () => { await queryEditor.waitForStatus('Completed'); // Verify table is not visible before refresh - const treeItemBeforeRefresh = page.locator('.ydb-tree-view').filter({hasText: tableName}); - await expect(treeItemBeforeRefresh).not.toBeVisible(); + try { + await objectSummary.getTreeItem(tableName); + } catch (error) { + expect(error).toBeTruthy(); + } // Click refresh button to update tree view await objectSummary.clickRefreshButton(); // Verify table appears in tree - const treeItemAfterRefresh = page.locator('.ydb-tree-view').filter({hasText: tableName}); + const treeItemAfterRefresh = await objectSummary.getTreeItem(tableName); await expect(treeItemAfterRefresh).toBeVisible(); });