diff --git a/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx b/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx index ae7ac15de4..8d17c51060 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 32449207f2..a42a768aba 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 2c34d78fb9..921987f931 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 466afcb2bb..fbfad100a0 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 c8a8756c70..66fc51541a 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.ts b/tests/suites/sidebar/Sidebar.ts new file mode 100644 index 0000000000..16dc958593 --- /dev/null +++ b/tests/suites/sidebar/Sidebar.ts @@ -0,0 +1,147 @@ +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; + 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'); + 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(); + } + + 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 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') + .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 new file mode 100644 index 0000000000..0103d95116 --- /dev/null +++ b/tests/suites/sidebar/sidebar.test.ts @@ -0,0 +1,115 @@ +import {expect, test} from '@playwright/test'; + +import {PageModel} from '../../models/PageModel'; +import {toggleExperiment} from '../../utils/toggleExperiment'; + +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('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(); + 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); + }); + + test('Can toggle experiments in settings', async ({page}) => { + const sidebar = new Sidebar(page); + 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(); + 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(); + const finalState = await sidebar.isExperimentEnabled(experimentTitle); + expect(finalState).toBe(false); + }); +}); diff --git a/tests/suites/tenant/TenantPage.ts b/tests/suites/tenant/TenantPage.ts index c1aff228d6..15a82111c6 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/queryEditor/planToSvg.test.ts b/tests/suites/tenant/queryEditor/planToSvg.test.ts new file mode 100644 index 0000000000..d322384546 --- /dev/null +++ b/tests/suites/tenant/queryEditor/planToSvg.test.ts @@ -0,0 +1,54 @@ +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'; + +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/suites/tenant/queryEditor/queryEditor.test.ts b/tests/suites/tenant/queryEditor/queryEditor.test.ts index e9cd270d4e..4e5022fa6e 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 0000000000..62f9e9b9f9 --- /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 0000000000..251fceca62 --- /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 Query Execution Status', 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'); + }); +}); diff --git a/tests/suites/tenant/queryHistory/queryHistory.test.ts b/tests/suites/tenant/queryHistory/queryHistory.test.ts index 0b2db63b16..19301b5d7e 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; diff --git a/tests/utils/toggleExperiment.ts b/tests/utils/toggleExperiment.ts new file mode 100644 index 0000000000..0ed92f88c3 --- /dev/null +++ b/tests/utils/toggleExperiment.ts @@ -0,0 +1,24 @@ +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(); + const currentState = await sidebar.isExperimentEnabled(title); + const desiredState = state === 'on'; + + if (currentState !== desiredState) { + await sidebar.toggleExperimentByTitle(title); + } + + if (await sidebar.isDrawerVisible()) { + await sidebar.clickSettings(); + await page.waitForTimeout(500); // Wait for animation + } +};