From 69338f2bb3b8234c625bd78c84e5f40cb4b7412d Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 18:00:50 +0300 Subject: [PATCH 1/9] chore: change trace and svg mutations to lazy query --- tests/suites/sidebar/Sidebar.ts | 97 ++++++++++++++++++++++++++++ tests/suites/sidebar/sidebar.test.ts | 73 +++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 tests/suites/sidebar/Sidebar.ts create mode 100644 tests/suites/sidebar/sidebar.test.ts diff --git a/tests/suites/sidebar/Sidebar.ts b/tests/suites/sidebar/Sidebar.ts new file mode 100644 index 000000000..9418cc955 --- /dev/null +++ b/tests/suites/sidebar/Sidebar.ts @@ -0,0 +1,97 @@ +import type {Locator, Page} from '@playwright/test'; + +export class Sidebar { + private sidebarContainer: Locator; + private logoButton: Locator; + private footer: Locator; + private settingsButton: Locator; + private documentationButton: Locator; + private accountButton: Locator; + private collapseButton: Locator; + + constructor(page: Page) { + this.sidebarContainer = page.locator('.gn-aside-header__aside-content'); + this.logoButton = this.sidebarContainer.locator('.gn-logo__btn-logo'); + this.footer = this.sidebarContainer.locator('.gn-aside-header__footer'); + + // Footer buttons with specific icons + const footerItems = this.sidebarContainer.locator('.gn-footer-item'); + this.documentationButton = footerItems.filter({hasText: 'Documentation'}); + this.settingsButton = footerItems + .filter({hasText: 'Settings'}) + .locator('.gn-composite-bar-item__btn-icon'); + this.accountButton = footerItems.filter({hasText: 'Account'}); + + this.collapseButton = this.sidebarContainer.locator('.gn-collapse-button'); + } + + async waitForSidebarToLoad() { + await this.sidebarContainer.waitFor({state: 'visible'}); + } + + async isSidebarVisible() { + return this.sidebarContainer.isVisible(); + } + + async isLogoButtonVisible() { + return this.logoButton.isVisible(); + } + + async isSettingsButtonVisible() { + return this.settingsButton.isVisible(); + } + + async isDocumentationButtonVisible() { + return this.documentationButton.isVisible(); + } + + async isAccountButtonVisible() { + return this.accountButton.isVisible(); + } + + async clickLogoButton() { + await this.logoButton.click(); + } + + async clickSettings() { + await this.settingsButton.click(); + } + + async clickDocumentation() { + await this.documentationButton.click(); + } + + async clickAccount() { + await this.accountButton.click(); + } + + async toggleCollapse() { + await this.collapseButton.click(); + } + + async isCollapsed() { + const button = await this.collapseButton; + const title = await button.getAttribute('title'); + return title === 'Expand'; + } + + async getFooterItemsCount(): Promise { + return this.footer.locator('.gn-composite-bar-item').count(); + } + + async isFooterItemVisible(index: number) { + const items = this.footer.locator('.gn-composite-bar-item'); + return items.nth(index).isVisible(); + } + + async clickFooterItem(index: number) { + const items = this.footer.locator('.gn-composite-bar-item'); + await items.nth(index).click(); + } + + async getFooterItemText(index: number): Promise { + const items = this.footer.locator('.gn-composite-bar-item'); + const item = items.nth(index); + return item.locator('.gn-composite-bar-item__title-text').innerText(); + } +} diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts new file mode 100644 index 000000000..b28a40a96 --- /dev/null +++ b/tests/suites/sidebar/sidebar.test.ts @@ -0,0 +1,73 @@ +import {expect, test} from '@playwright/test'; + +import {PageModel} from '../../models/PageModel'; + +import {Sidebar} from './Sidebar'; + +test.describe('Test Sidebar', async () => { + test.beforeEach(async ({page}) => { + const basePage = new PageModel(page); + const response = await basePage.goto(); + expect(response?.ok()).toBe(true); + }); + + test('Sidebar is visible and loads correctly', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + await expect(sidebar.isSidebarVisible()).resolves.toBe(true); + }); + + test('Logo button is visible and clickable', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + await expect(sidebar.isLogoButtonVisible()).resolves.toBe(true); + await sidebar.clickLogoButton(); + }); + + test('Settings button is visible and clickable', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + await expect(sidebar.isSettingsButtonVisible()).resolves.toBe(true); + await sidebar.clickSettings(); + }); + + test('Documentation button is visible and clickable', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + await expect(sidebar.isDocumentationButtonVisible()).resolves.toBe(true); + await sidebar.clickDocumentation(); + }); + + test('Account button is visible and clickable', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + await expect(sidebar.isAccountButtonVisible()).resolves.toBe(true); + await sidebar.clickAccount(); + }); + + test('Sidebar can be collapsed and expanded', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + + // Initially collapsed + await expect(sidebar.isCollapsed()).resolves.toBe(true); + + // Expand + await sidebar.toggleCollapse(); + await page.waitForTimeout(500); // Wait for animation + await expect(sidebar.isCollapsed()).resolves.toBe(false); + + // Collapse + await sidebar.toggleCollapse(); + await page.waitForTimeout(500); // Wait for animation + await expect(sidebar.isCollapsed()).resolves.toBe(true); + }); + + test('Footer items are visible', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + + const itemsCount = await sidebar.getFooterItemsCount(); + expect(itemsCount).toBeGreaterThan(0); + }); +}); From aeddd303bae41fa08db786ebd1f859e72d21e560 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 18:23:22 +0300 Subject: [PATCH 2/9] fix: turn experiments on --- tests/suites/sidebar/Sidebar.ts | 45 +++++++++++++++++++++++++ tests/suites/sidebar/sidebar.test.ts | 50 ++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/tests/suites/sidebar/Sidebar.ts b/tests/suites/sidebar/Sidebar.ts index 9418cc955..aa19cad6e 100644 --- a/tests/suites/sidebar/Sidebar.ts +++ b/tests/suites/sidebar/Sidebar.ts @@ -8,11 +8,19 @@ export class Sidebar { private documentationButton: Locator; private accountButton: Locator; private collapseButton: Locator; + private drawer: Locator; + private drawerMenu: Locator; + private experimentsSection: Locator; constructor(page: Page) { this.sidebarContainer = page.locator('.gn-aside-header__aside-content'); this.logoButton = this.sidebarContainer.locator('.gn-logo__btn-logo'); this.footer = this.sidebarContainer.locator('.gn-aside-header__footer'); + this.drawer = page.locator('.gn-drawer'); + this.drawerMenu = page.locator('.gn-settings-menu'); + this.experimentsSection = this.drawerMenu + .locator('.gn-settings-menu__item') + .filter({hasText: 'Experiments'}); // Footer buttons with specific icons const footerItems = this.sidebarContainer.locator('.gn-footer-item'); @@ -94,4 +102,41 @@ export class Sidebar { const item = items.nth(index); return item.locator('.gn-composite-bar-item__title-text').innerText(); } + + async isDrawerVisible() { + return this.drawer.isVisible(); + } + + async getDrawerMenuItems(): Promise { + const items = this.drawerMenu.locator('.gn-settings-menu__item >> span'); + const count = await items.count(); + const texts: string[] = []; + for (let i = 0; i < count; i++) { + texts.push(await items.nth(i).innerText()); + } + return texts; + } + + async clickExperimentsSection() { + await this.experimentsSection.click(); + } + + async toggleExperimentByTitle(title: string) { + const experimentItem = this.drawer + .locator('.gn-settings__item-title') + .filter({hasText: title}); + // Click the label element which wraps the switch, avoiding the slider that intercepts events + const switchLabel = experimentItem.locator( + 'xpath=../../..//label[contains(@class, "g-control-label")]', + ); + await switchLabel.click(); + } + + async isExperimentEnabled(title: string): Promise { + const experimentItem = this.drawer + .locator('.gn-settings__item-title') + .filter({hasText: title}); + const switchControl = experimentItem.locator('xpath=../../..//input[@type="checkbox"]'); + return switchControl.isChecked(); + } } diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index b28a40a96..3d036ae5a 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -31,6 +31,25 @@ test.describe('Test Sidebar', async () => { await sidebar.clickSettings(); }); + test('Settings button click opens drawer with correct sections', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + + // Initially drawer should not be visible + await expect(sidebar.isDrawerVisible()).resolves.toBe(false); + + // Click settings button + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + + // Drawer should become visible + await expect(sidebar.isDrawerVisible()).resolves.toBe(true); + + // Verify drawer menu items + const menuItems = await sidebar.getDrawerMenuItems(); + expect(menuItems).toEqual(['General', 'Editor', 'Experiments', 'About']); + }); + test('Documentation button is visible and clickable', async ({page}) => { const sidebar = new Sidebar(page); await sidebar.waitForSidebarToLoad(); @@ -70,4 +89,35 @@ test.describe('Test Sidebar', async () => { const itemsCount = await sidebar.getFooterItemsCount(); expect(itemsCount).toBeGreaterThan(0); }); + + test('Can toggle experiments in settings', async ({page}) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + + // Open settings + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + + // Click experiments section + await sidebar.clickExperimentsSection(); + await page.waitForTimeout(500); // Wait for animation + + // Toggle "Use paginated tables" experiment + const experimentTitle = 'Use paginated tables'; + const initialState = await sidebar.isExperimentEnabled(experimentTitle); + await sidebar.toggleExperimentByTitle(experimentTitle); + await page.waitForTimeout(500); // Wait for animation + + // Verify the state has changed + const newState = await sidebar.isExperimentEnabled(experimentTitle); + expect(newState).not.toBe(initialState); + + // Toggle back + await sidebar.toggleExperimentByTitle(experimentTitle); + await page.waitForTimeout(500); // Wait for animation + + // Verify it's back to initial state + const finalState = await sidebar.isExperimentEnabled(experimentTitle); + expect(finalState).toBe(initialState); + }); }); From 422e6fe6be92d0c7cff028bf31f8e22cba455e50 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 18:52:38 +0300 Subject: [PATCH 3/9] . --- .../Query/ExecuteResult/PlanToSvgButton.tsx | 2 +- .../Query/ExecuteResult/TraceButton.tsx | 2 +- src/store/reducers/planToSvg.ts | 2 +- src/store/reducers/trace.ts | 2 +- tests/suites/nodes/nodes.test.ts | 8 +-- tests/suites/sidebar/sidebar.test.ts | 30 +++-------- .../suites/tenant/planToSvg/planToSvg.test.ts | 53 +++++++++++++++++++ tests/utils/toggleExperiment.ts | 26 +++++++++ 8 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 tests/suites/tenant/planToSvg/planToSvg.test.ts create mode 100644 tests/utils/toggleExperiment.ts diff --git a/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx b/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx index ae7ac15de..8d17c5106 100644 --- a/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx +++ b/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx @@ -23,7 +23,7 @@ interface PlanToSvgButtonProps { export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) { const [error, setError] = React.useState(null); const [blobUrl, setBlobUrl] = React.useState(null); - const [getPlanToSvg, {isLoading}] = planToSvgApi.usePlanToSvgQueryMutation(); + const [getPlanToSvg, {isLoading}] = planToSvgApi.useLazyPlanToSvgQueryQuery(); const handleClick = React.useCallback(() => { getPlanToSvg({plan, database}) diff --git a/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx b/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx index 32449207f..a42a768ab 100644 --- a/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx +++ b/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx @@ -20,7 +20,7 @@ export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) { const checkTraceUrl = traceCheck?.url ? replaceParams(traceCheck.url, {traceId}) : ''; const traceUrl = traceView?.url ? replaceParams(traceView.url, {traceId}) : ''; - const [checkTrace, {isLoading, isUninitialized}] = traceApi.useCheckTraceMutation(); + const [checkTrace, {isLoading, isUninitialized}] = traceApi.useLazyCheckTraceQuery(); React.useEffect(() => { let checkTraceMutation: {abort: () => void} | null; diff --git a/src/store/reducers/planToSvg.ts b/src/store/reducers/planToSvg.ts index 2c34d78fb..921987f93 100644 --- a/src/store/reducers/planToSvg.ts +++ b/src/store/reducers/planToSvg.ts @@ -9,7 +9,7 @@ export interface PlanToSvgQueryParams { export const planToSvgApi = api.injectEndpoints({ endpoints: (build) => ({ - planToSvgQuery: build.mutation({ + planToSvgQuery: build.query({ queryFn: async ({plan, database}, {signal}) => { try { const response = await window.api.planToSvg( diff --git a/src/store/reducers/trace.ts b/src/store/reducers/trace.ts index 466afcb2b..fbfad100a 100644 --- a/src/store/reducers/trace.ts +++ b/src/store/reducers/trace.ts @@ -7,7 +7,7 @@ interface CheckTraceParams { export const traceApi = api.injectEndpoints({ endpoints: (build) => ({ - checkTrace: build.mutation({ + checkTrace: build.query({ queryFn: async ({url}: CheckTraceParams, {signal, dispatch}) => { try { const response = await window.api.checkTrace({url}, {signal}); diff --git a/tests/suites/nodes/nodes.test.ts b/tests/suites/nodes/nodes.test.ts index c8a8756c7..66fc51541 100644 --- a/tests/suites/nodes/nodes.test.ts +++ b/tests/suites/nodes/nodes.test.ts @@ -1,5 +1,6 @@ import {expect, test} from '@playwright/test'; +import {toggleExperiment} from '../../utils/toggleExperiment'; import {NodesPage} from '../nodes/NodesPage'; import {PaginatedTable} from '../paginatedTable/paginatedTable'; @@ -28,12 +29,7 @@ test.describe('Test Nodes Paginated Table', async () => { expect(response?.ok()).toBe(true); // Wil be removed since it's an experiment - await page.evaluate(() => { - localStorage.setItem('useBackendParamsForTables', 'true'); - location.reload(); - }); - - await page.waitForLoadState('networkidle'); + await toggleExperiment(page, 'on', 'Use paginated tables'); }); test('Table loads and displays data', async ({page}) => { diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index 3d036ae5a..d80f15db2 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -1,6 +1,7 @@ import {expect, test} from '@playwright/test'; import {PageModel} from '../../models/PageModel'; +import {toggleExperiment} from '../../utils/toggleExperiment'; import {Sidebar} from './Sidebar'; @@ -92,32 +93,13 @@ test.describe('Test Sidebar', async () => { test('Can toggle experiments in settings', async ({page}) => { const sidebar = new Sidebar(page); - await sidebar.waitForSidebarToLoad(); - - // Open settings - await sidebar.clickSettings(); - await page.waitForTimeout(500); // Wait for animation - - // Click experiments section - await sidebar.clickExperimentsSection(); - await page.waitForTimeout(500); // Wait for animation - - // Toggle "Use paginated tables" experiment - const experimentTitle = 'Use paginated tables'; - const initialState = await sidebar.isExperimentEnabled(experimentTitle); - await sidebar.toggleExperimentByTitle(experimentTitle); - await page.waitForTimeout(500); // Wait for animation - - // Verify the state has changed + const experimentTitle = 'Plan to SVG'; + await toggleExperiment(page, 'on', experimentTitle); const newState = await sidebar.isExperimentEnabled(experimentTitle); - expect(newState).not.toBe(initialState); - - // Toggle back - await sidebar.toggleExperimentByTitle(experimentTitle); - await page.waitForTimeout(500); // Wait for animation + expect(newState).toBe(true); - // Verify it's back to initial state + await toggleExperiment(page, 'off', experimentTitle); const finalState = await sidebar.isExperimentEnabled(experimentTitle); - expect(finalState).toBe(initialState); + expect(finalState).toBe(false); }); }); diff --git a/tests/suites/tenant/planToSvg/planToSvg.test.ts b/tests/suites/tenant/planToSvg/planToSvg.test.ts new file mode 100644 index 000000000..ff3824d30 --- /dev/null +++ b/tests/suites/tenant/planToSvg/planToSvg.test.ts @@ -0,0 +1,53 @@ +import {expect, test} from '@playwright/test'; + +import {tenantName} from '../../../utils/constants'; +import {toggleExperiment} from '../../../utils/toggleExperiment'; +import {TenantPage} from '../TenantPage'; +import {ButtonNames, QueryEditor} from '../queryEditor/QueryEditor'; + +test.describe('Test Plan to SVG functionality', async () => { + const testQuery = 'SELECT 1;'; // Simple query that will generate a plan + + test.beforeEach(async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + general: 'query', + }; + + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + }); + + test('Plan to SVG experiment shows execution plan in new tab', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // 1. Turn on Plan to SVG experiment + await toggleExperiment(page, 'on', 'Plan to SVG'); + + // 2. Set stats level to Full + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeStatsLevel('Full'); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + // 3. Set query and run it + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + + // 4. Wait for query execution to complete + await expect(async () => { + const status = await queryEditor.getExecutionStatus(); + expect(status).toBe('Completed'); + }).toPass(); + + // 5. Check if Execution Plan button appears and click it + const executionPlanButton = page.locator('button:has-text("Execution plan")'); + await expect(executionPlanButton).toBeVisible(); + await executionPlanButton.click(); + await page.waitForTimeout(1000); // Wait for new tab to open + + // 6. Verify we're taken to a new tab with SVG content + const svgElement = page.locator('svg').first(); + await expect(svgElement).toBeVisible(); + }); +}); diff --git a/tests/utils/toggleExperiment.ts b/tests/utils/toggleExperiment.ts new file mode 100644 index 000000000..85fae200f --- /dev/null +++ b/tests/utils/toggleExperiment.ts @@ -0,0 +1,26 @@ +import type {Page} from '@playwright/test'; + +import {Sidebar} from '../suites/sidebar/Sidebar'; + +export const toggleExperiment = async (page: Page, state: 'on' | 'off', title: string) => { + const sidebar = new Sidebar(page); + await sidebar.waitForSidebarToLoad(); + if (!(await sidebar.isDrawerVisible())) { + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + } + await sidebar.clickExperimentsSection(); + await page.waitForTimeout(500); // Wait for animation + const currentState = await sidebar.isExperimentEnabled(title); + const desiredState = state === 'on'; + + if (currentState !== desiredState) { + await sidebar.toggleExperimentByTitle(title); + await page.waitForTimeout(500); // Wait for animation + } + + if (await sidebar.isDrawerVisible()) { + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + } +}; From 7ac1936b039dfdd10c3cacdbc9c6552cf7e88857 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 19:13:50 +0300 Subject: [PATCH 4/9] fix: test --- tests/suites/sidebar/sidebar.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index d80f15db2..17ea075ff 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -94,11 +94,20 @@ test.describe('Test Sidebar', async () => { test('Can toggle experiments in settings', async ({page}) => { const sidebar = new Sidebar(page); const experimentTitle = 'Plan to SVG'; + await toggleExperiment(page, 'on', experimentTitle); + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + await sidebar.clickExperimentsSection(); + await page.waitForTimeout(500); // Wait for animation const newState = await sidebar.isExperimentEnabled(experimentTitle); expect(newState).toBe(true); await toggleExperiment(page, 'off', experimentTitle); + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + await sidebar.clickExperimentsSection(); + await page.waitForTimeout(500); // Wait for animation const finalState = await sidebar.isExperimentEnabled(experimentTitle); expect(finalState).toBe(false); }); From 271973905ce2e7cdcb9ed2be5ddf68a7676040d3 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 19:21:23 +0300 Subject: [PATCH 5/9] fix: better code --- tests/suites/sidebar/Sidebar.ts | 5 +++++ tests/suites/sidebar/sidebar.test.ts | 9 +++++---- tests/utils/toggleExperiment.ts | 2 -- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/suites/sidebar/Sidebar.ts b/tests/suites/sidebar/Sidebar.ts index aa19cad6e..16dc95859 100644 --- a/tests/suites/sidebar/Sidebar.ts +++ b/tests/suites/sidebar/Sidebar.ts @@ -132,6 +132,11 @@ export class Sidebar { await switchLabel.click(); } + async getFirstExperimentTitle(): Promise { + const experimentItem = this.drawer.locator('.gn-settings__item-title').first(); + return experimentItem.innerText(); + } + async isExperimentEnabled(title: string): Promise { const experimentItem = this.drawer .locator('.gn-settings__item-title') diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index 17ea075ff..383642f08 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -91,15 +91,17 @@ test.describe('Test Sidebar', async () => { expect(itemsCount).toBeGreaterThan(0); }); - test('Can toggle experiments in settings', async ({page}) => { + test.only('Can toggle experiments in settings', async ({page}) => { const sidebar = new Sidebar(page); - const experimentTitle = 'Plan to SVG'; + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + await sidebar.clickExperimentsSection(); + const experimentTitle = await sidebar.getFirstExperimentTitle(); await toggleExperiment(page, 'on', experimentTitle); await sidebar.clickSettings(); await page.waitForTimeout(500); // Wait for animation await sidebar.clickExperimentsSection(); - await page.waitForTimeout(500); // Wait for animation const newState = await sidebar.isExperimentEnabled(experimentTitle); expect(newState).toBe(true); @@ -107,7 +109,6 @@ test.describe('Test Sidebar', async () => { await sidebar.clickSettings(); await page.waitForTimeout(500); // Wait for animation await sidebar.clickExperimentsSection(); - await page.waitForTimeout(500); // Wait for animation const finalState = await sidebar.isExperimentEnabled(experimentTitle); expect(finalState).toBe(false); }); diff --git a/tests/utils/toggleExperiment.ts b/tests/utils/toggleExperiment.ts index 85fae200f..0ed92f88c 100644 --- a/tests/utils/toggleExperiment.ts +++ b/tests/utils/toggleExperiment.ts @@ -10,13 +10,11 @@ export const toggleExperiment = async (page: Page, state: 'on' | 'off', title: s await page.waitForTimeout(500); // Wait for animation } await sidebar.clickExperimentsSection(); - await page.waitForTimeout(500); // Wait for animation const currentState = await sidebar.isExperimentEnabled(title); const desiredState = state === 'on'; if (currentState !== desiredState) { await sidebar.toggleExperimentByTitle(title); - await page.waitForTimeout(500); // Wait for animation } if (await sidebar.isDrawerVisible()) { From f49f49e0fee48b40eb1cb529797a632262b36649 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sat, 16 Nov 2024 19:40:20 +0300 Subject: [PATCH 6/9] fix: rm only --- tests/suites/sidebar/sidebar.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/suites/sidebar/sidebar.test.ts b/tests/suites/sidebar/sidebar.test.ts index 383642f08..0103d9511 100644 --- a/tests/suites/sidebar/sidebar.test.ts +++ b/tests/suites/sidebar/sidebar.test.ts @@ -91,7 +91,7 @@ test.describe('Test Sidebar', async () => { expect(itemsCount).toBeGreaterThan(0); }); - test.only('Can toggle experiments in settings', async ({page}) => { + test('Can toggle experiments in settings', async ({page}) => { const sidebar = new Sidebar(page); await sidebar.clickSettings(); await page.waitForTimeout(500); // Wait for animation From 306a27d4e474f3a3c3a841f3be98cafe30bacfff Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sun, 17 Nov 2024 00:59:08 +0300 Subject: [PATCH 7/9] fix: split test files --- .../planToSvg.test.ts | 3 +- .../tenant/queryEditor/queryEditor.test.ts | 163 ------------------ .../tenant/queryEditor/querySettings.test.ts | 140 +++++++++++++++ .../tenant/queryEditor/queryStatus.test.ts | 67 +++++++ 4 files changed, 209 insertions(+), 164 deletions(-) rename tests/suites/tenant/{planToSvg => queryEditor}/planToSvg.test.ts (96%) create mode 100644 tests/suites/tenant/queryEditor/querySettings.test.ts create mode 100644 tests/suites/tenant/queryEditor/queryStatus.test.ts diff --git a/tests/suites/tenant/planToSvg/planToSvg.test.ts b/tests/suites/tenant/queryEditor/planToSvg.test.ts similarity index 96% rename from tests/suites/tenant/planToSvg/planToSvg.test.ts rename to tests/suites/tenant/queryEditor/planToSvg.test.ts index ff3824d30..d32238454 100644 --- a/tests/suites/tenant/planToSvg/planToSvg.test.ts +++ b/tests/suites/tenant/queryEditor/planToSvg.test.ts @@ -3,7 +3,8 @@ import {expect, test} from '@playwright/test'; import {tenantName} from '../../../utils/constants'; import {toggleExperiment} from '../../../utils/toggleExperiment'; import {TenantPage} from '../TenantPage'; -import {ButtonNames, QueryEditor} from '../queryEditor/QueryEditor'; + +import {ButtonNames, QueryEditor} from './QueryEditor'; test.describe('Test Plan to SVG functionality', async () => { const testQuery = 'SELECT 1;'; // Simple query that will generate a plan diff --git a/tests/suites/tenant/queryEditor/queryEditor.test.ts b/tests/suites/tenant/queryEditor/queryEditor.test.ts index e9cd270d4..4e5022fa6 100644 --- a/tests/suites/tenant/queryEditor/queryEditor.test.ts +++ b/tests/suites/tenant/queryEditor/queryEditor.test.ts @@ -27,29 +27,6 @@ test.describe('Test Query Editor', async () => { await tenantPage.goto(pageQueryParams); }); - test('Settings dialog opens on Gear click and closes on Cancel', async ({page}) => { - const queryEditor = new QueryEditor(page); - await queryEditor.clickGearButton(); - - await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true); - - await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel); - await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true); - }); - - test('Settings dialog saves changes and updates Gear button', async ({page}) => { - const queryEditor = new QueryEditor(page); - await queryEditor.clickGearButton(); - - await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - await expect(async () => { - const text = await queryEditor.gearButtonText(); - expect(text).toContain('(1)'); - }).toPass({timeout: VISIBILITY_TIMEOUT}); - }); - test('Run button executes YQL script', async ({page}) => { const queryEditor = new QueryEditor(page); await queryEditor.run(testQuery, QueryMode.YQLScript); @@ -103,92 +80,6 @@ test.describe('Test Query Editor', async () => { await expect(errorMessage).toContain('Column references are not allowed without FROM'); }); - test('Banner appears after executing script with changed settings', async ({page}) => { - const queryEditor = new QueryEditor(page); - - // Change a setting - await queryEditor.clickGearButton(); - await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - // Execute a script - await queryEditor.setQuery(testQuery); - await queryEditor.clickRunButton(); - - // Check if banner appears - await expect(queryEditor.isBannerVisible()).resolves.toBe(true); - }); - - test('Banner not appears for running query', async ({page}) => { - const queryEditor = new QueryEditor(page); - - // Change a setting - await queryEditor.clickGearButton(); - await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - // Execute a script - await queryEditor.setQuery(longRunningQuery); - await queryEditor.clickRunButton(); - await page.waitForTimeout(500); - - // Check if banner appears - await expect(queryEditor.isBannerHidden()).resolves.toBe(true); - }); - - test('Indicator icon appears after closing banner', async ({page}) => { - const queryEditor = new QueryEditor(page); - - // Change a setting - await queryEditor.clickGearButton(); - await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - // Execute a script to make the banner appear - await queryEditor.setQuery(testQuery); - await queryEditor.clickRunButton(); - - // Close the banner - await queryEditor.closeBanner(); - - await expect(queryEditor.isIndicatorIconVisible()).resolves.toBe(true); - }); - - test('Indicator not appears for running query', async ({page}) => { - const queryEditor = new QueryEditor(page); - - // Change a setting - await queryEditor.clickGearButton(); - await queryEditor.settingsDialog.changeTransactionMode('Snapshot'); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - // Execute a script to make the banner appear - await queryEditor.setQuery(testQuery); - await queryEditor.clickRunButton(); - - // Close the banner - await queryEditor.closeBanner(); - await queryEditor.setQuery(longRunningQuery); - await queryEditor.clickRunButton(); - await page.waitForTimeout(500); - - await expect(queryEditor.isIndicatorIconHidden()).resolves.toBe(true); - }); - - test('Gear button shows number of changed settings', async ({page}) => { - const queryEditor = new QueryEditor(page); - await queryEditor.clickGearButton(); - - await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); - await queryEditor.settingsDialog.changeTransactionMode('Snapshot'); - await queryEditor.settingsDialog.clickButton(ButtonNames.Save); - - await expect(async () => { - const text = await queryEditor.gearButtonText(); - expect(text).toContain('(2)'); - }).toPass({timeout: VISIBILITY_TIMEOUT}); - }); - test('Run and Explain buttons are disabled when query is empty', async ({page}) => { const queryEditor = new QueryEditor(page); @@ -201,15 +92,6 @@ test.describe('Test Query Editor', async () => { await expect(queryEditor.isExplainButtonEnabled()).resolves.toBe(true); }); - test('Banner does not appear when executing script with default settings', async ({page}) => { - const queryEditor = new QueryEditor(page); - - await queryEditor.setQuery(testQuery); - await queryEditor.clickRunButton(); - - await expect(queryEditor.isBannerHidden()).resolves.toBe(true); - }); - test('Stop button and elapsed time label appears when query is running', async ({page}) => { const queryEditor = new QueryEditor(page); @@ -290,51 +172,6 @@ test.describe('Test Query Editor', async () => { await expect(queryEditor.isStopButtonHidden()).resolves.toBe(true); }); - test('No query status when no query was executed', async ({page}) => { - const queryEditor = new QueryEditor(page); - - // Ensure page is loaded - await queryEditor.setQuery(longRunningQuery); - await queryEditor.clickGearButton(); - await queryEditor.settingsDialog.changeStatsLevel('Profile'); - - await expect(queryEditor.isResultsControlsHidden()).resolves.toBe(true); - }); - - test('Running query status for running query', async ({page}) => { - const queryEditor = new QueryEditor(page); - - await queryEditor.setQuery(longRunningQuery); - await queryEditor.clickRunButton(); - await page.waitForTimeout(500); - - const statusElement = await queryEditor.getExecutionStatus(); - await expect(statusElement).toBe('Running'); - }); - - test('Completed query status for completed query', async ({page}) => { - const queryEditor = new QueryEditor(page); - - await queryEditor.setQuery(testQuery); - await queryEditor.clickRunButton(); - await page.waitForTimeout(1000); - - const statusElement = await queryEditor.getExecutionStatus(); - await expect(statusElement).toBe('Completed'); - }); - - test('Failed query status for failed query', async ({page}) => { - const queryEditor = new QueryEditor(page); - - const invalidQuery = 'Select d'; - await queryEditor.setQuery(invalidQuery); - await queryEditor.clickRunButton(); - await page.waitForTimeout(1000); - - const statusElement = await queryEditor.getExecutionStatus(); - await expect(statusElement).toBe('Failed'); - }); - test('Changing tab inside results pane doesnt change results view', async ({page}) => { const queryEditor = new QueryEditor(page); await queryEditor.setQuery(testQuery); diff --git a/tests/suites/tenant/queryEditor/querySettings.test.ts b/tests/suites/tenant/queryEditor/querySettings.test.ts new file mode 100644 index 000000000..62f9e9b9f --- /dev/null +++ b/tests/suites/tenant/queryEditor/querySettings.test.ts @@ -0,0 +1,140 @@ +import {expect, test} from '@playwright/test'; + +import {tenantName} from '../../../utils/constants'; +import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage'; +import {longRunningQuery} from '../constants'; + +import {ButtonNames, QueryEditor, QueryMode} from './QueryEditor'; + +test.describe('Test Query Settings', async () => { + const testQuery = 'SELECT 1, 2, 3, 4, 5;'; + + test.beforeEach(async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + general: 'query', + }; + + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + }); + + test('Settings dialog opens on Gear click and closes on Cancel', async ({page}) => { + const queryEditor = new QueryEditor(page); + await queryEditor.clickGearButton(); + + await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true); + + await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel); + await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true); + }); + + test('Settings dialog saves changes and updates Gear button', async ({page}) => { + const queryEditor = new QueryEditor(page); + await queryEditor.clickGearButton(); + + await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + await expect(async () => { + const text = await queryEditor.gearButtonText(); + expect(text).toContain('(1)'); + }).toPass({timeout: VISIBILITY_TIMEOUT}); + }); + + test('Banner appears after executing script with changed settings', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // Change a setting + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + // Execute a script + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + + // Check if banner appears + await expect(queryEditor.isBannerVisible()).resolves.toBe(true); + }); + + test('Banner not appears for running query', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // Change a setting + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + // Execute a script + await queryEditor.setQuery(longRunningQuery); + await queryEditor.clickRunButton(); + await page.waitForTimeout(500); + + // Check if banner appears + await expect(queryEditor.isBannerHidden()).resolves.toBe(true); + }); + + test('Indicator icon appears after closing banner', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // Change a setting + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + // Execute a script to make the banner appear + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + + // Close the banner + await queryEditor.closeBanner(); + + await expect(queryEditor.isIndicatorIconVisible()).resolves.toBe(true); + }); + + test('Indicator not appears for running query', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // Change a setting + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeTransactionMode('Snapshot'); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + // Execute a script to make the banner appear + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + + // Close the banner + await queryEditor.closeBanner(); + await queryEditor.setQuery(longRunningQuery); + await queryEditor.clickRunButton(); + await page.waitForTimeout(500); + + await expect(queryEditor.isIndicatorIconHidden()).resolves.toBe(true); + }); + + test('Gear button shows number of changed settings', async ({page}) => { + const queryEditor = new QueryEditor(page); + await queryEditor.clickGearButton(); + + await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan); + await queryEditor.settingsDialog.changeTransactionMode('Snapshot'); + await queryEditor.settingsDialog.clickButton(ButtonNames.Save); + + await expect(async () => { + const text = await queryEditor.gearButtonText(); + expect(text).toContain('(2)'); + }).toPass({timeout: VISIBILITY_TIMEOUT}); + }); + + test('Banner does not appear when executing script with default settings', async ({page}) => { + const queryEditor = new QueryEditor(page); + + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + + await expect(queryEditor.isBannerHidden()).resolves.toBe(true); + }); +}); diff --git a/tests/suites/tenant/queryEditor/queryStatus.test.ts b/tests/suites/tenant/queryEditor/queryStatus.test.ts new file mode 100644 index 000000000..d53cac8c0 --- /dev/null +++ b/tests/suites/tenant/queryEditor/queryStatus.test.ts @@ -0,0 +1,67 @@ +import {expect, test} from '@playwright/test'; + +import {tenantName} from '../../../utils/constants'; +import {TenantPage} from '../TenantPage'; +import {longRunningQuery} from '../constants'; + +import {QueryEditor} from './QueryEditor'; + +test.describe('Test Plan to SVG functionality', async () => { + const testQuery = 'SELECT 1;'; // Simple query that will generate a plan + + test.beforeEach(async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + general: 'query', + }; + + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + }); + + test('No query status when no query was executed', async ({page}) => { + const queryEditor = new QueryEditor(page); + + // Ensure page is loaded + await queryEditor.setQuery(longRunningQuery); + await queryEditor.clickGearButton(); + await queryEditor.settingsDialog.changeStatsLevel('Profile'); + + await expect(queryEditor.isResultsControlsHidden()).resolves.toBe(true); + }); + + test('Running query status for running query', async ({page}) => { + const queryEditor = new QueryEditor(page); + + await queryEditor.setQuery(longRunningQuery); + await queryEditor.clickRunButton(); + await page.waitForTimeout(500); + + const statusElement = await queryEditor.getExecutionStatus(); + await expect(statusElement).toBe('Running'); + }); + + test('Completed query status for completed query', async ({page}) => { + const queryEditor = new QueryEditor(page); + + await queryEditor.setQuery(testQuery); + await queryEditor.clickRunButton(); + await page.waitForTimeout(1000); + + const statusElement = await queryEditor.getExecutionStatus(); + await expect(statusElement).toBe('Completed'); + }); + + test('Failed query status for failed query', async ({page}) => { + const queryEditor = new QueryEditor(page); + + const invalidQuery = 'Select d'; + await queryEditor.setQuery(invalidQuery); + await queryEditor.clickRunButton(); + await page.waitForTimeout(1000); + + const statusElement = await queryEditor.getExecutionStatus(); + await expect(statusElement).toBe('Failed'); + }); +}); From cf0a7ae201178e5568d4af264ecb85155adea907 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sun, 17 Nov 2024 01:29:33 +0300 Subject: [PATCH 8/9] fix: naming --- tests/suites/tenant/queryEditor/queryStatus.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/suites/tenant/queryEditor/queryStatus.test.ts b/tests/suites/tenant/queryEditor/queryStatus.test.ts index d53cac8c0..251fceca6 100644 --- a/tests/suites/tenant/queryEditor/queryStatus.test.ts +++ b/tests/suites/tenant/queryEditor/queryStatus.test.ts @@ -6,7 +6,7 @@ import {longRunningQuery} from '../constants'; import {QueryEditor} from './QueryEditor'; -test.describe('Test Plan to SVG functionality', async () => { +test.describe('Test Query Execution Status', async () => { const testQuery = 'SELECT 1;'; // Simple query that will generate a plan test.beforeEach(async ({page}) => { From 45e249a50d0ff2d2e16a9f7045aa7ac8926d8b38 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Sun, 17 Nov 2024 01:45:31 +0300 Subject: [PATCH 9/9] fix: flaky tests --- tests/suites/tenant/TenantPage.ts | 2 +- tests/suites/tenant/queryHistory/queryHistory.test.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/suites/tenant/TenantPage.ts b/tests/suites/tenant/TenantPage.ts index c1aff228d..15a82111c 100644 --- a/tests/suites/tenant/TenantPage.ts +++ b/tests/suites/tenant/TenantPage.ts @@ -3,7 +3,7 @@ import type {Locator, Page} from '@playwright/test'; import {PageModel} from '../../models/PageModel'; import {tenantPage} from '../../utils/constants'; -export const VISIBILITY_TIMEOUT = 5000; +export const VISIBILITY_TIMEOUT = 10000; export enum NavigationTabs { Query = 'Query', diff --git a/tests/suites/tenant/queryHistory/queryHistory.test.ts b/tests/suites/tenant/queryHistory/queryHistory.test.ts index 0b2db63b1..19301b5d7 100644 --- a/tests/suites/tenant/queryHistory/queryHistory.test.ts +++ b/tests/suites/tenant/queryHistory/queryHistory.test.ts @@ -1,13 +1,11 @@ import {expect, test} from '@playwright/test'; import {tenantName} from '../../../utils/constants'; -import {TenantPage} from '../TenantPage'; +import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage'; import {QueryEditor, QueryMode} from '../queryEditor/QueryEditor'; import executeQueryWithKeybinding from './utils'; -export const VISIBILITY_TIMEOUT = 5000; - test.describe('Query History', () => { let tenantPage: TenantPage; let queryEditor: QueryEditor;