Skip to content

Commit 19d7f56

Browse files
astandrikAnton Standrik
and
Anton Standrik
authoredNov 18, 2024··
fix: change trace and svg mutations to lazy query (#1640)
Co-authored-by: Anton Standrik <astandrik@Antons-MacBook-Air.local>
1 parent 71956ec commit 19d7f56

File tree

14 files changed

+555
-177
lines changed

14 files changed

+555
-177
lines changed
 

‎src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface PlanToSvgButtonProps {
2323
export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
2424
const [error, setError] = React.useState<string | null>(null);
2525
const [blobUrl, setBlobUrl] = React.useState<string | null>(null);
26-
const [getPlanToSvg, {isLoading}] = planToSvgApi.usePlanToSvgQueryMutation();
26+
const [getPlanToSvg, {isLoading}] = planToSvgApi.useLazyPlanToSvgQueryQuery();
2727

2828
const handleClick = React.useCallback(() => {
2929
getPlanToSvg({plan, database})

‎src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) {
2020
const checkTraceUrl = traceCheck?.url ? replaceParams(traceCheck.url, {traceId}) : '';
2121
const traceUrl = traceView?.url ? replaceParams(traceView.url, {traceId}) : '';
2222

23-
const [checkTrace, {isLoading, isUninitialized}] = traceApi.useCheckTraceMutation();
23+
const [checkTrace, {isLoading, isUninitialized}] = traceApi.useLazyCheckTraceQuery();
2424

2525
React.useEffect(() => {
2626
let checkTraceMutation: {abort: () => void} | null;

‎src/store/reducers/planToSvg.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface PlanToSvgQueryParams {
99

1010
export const planToSvgApi = api.injectEndpoints({
1111
endpoints: (build) => ({
12-
planToSvgQuery: build.mutation<string, PlanToSvgQueryParams>({
12+
planToSvgQuery: build.query<string, PlanToSvgQueryParams>({
1313
queryFn: async ({plan, database}, {signal}) => {
1414
try {
1515
const response = await window.api.planToSvg(

‎src/store/reducers/trace.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface CheckTraceParams {
77

88
export const traceApi = api.injectEndpoints({
99
endpoints: (build) => ({
10-
checkTrace: build.mutation({
10+
checkTrace: build.query({
1111
queryFn: async ({url}: CheckTraceParams, {signal, dispatch}) => {
1212
try {
1313
const response = await window.api.checkTrace({url}, {signal});

‎tests/suites/nodes/nodes.test.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {expect, test} from '@playwright/test';
22

3+
import {toggleExperiment} from '../../utils/toggleExperiment';
34
import {NodesPage} from '../nodes/NodesPage';
45
import {PaginatedTable} from '../paginatedTable/paginatedTable';
56

@@ -28,12 +29,7 @@ test.describe('Test Nodes Paginated Table', async () => {
2829
expect(response?.ok()).toBe(true);
2930

3031
// Wil be removed since it's an experiment
31-
await page.evaluate(() => {
32-
localStorage.setItem('useBackendParamsForTables', 'true');
33-
location.reload();
34-
});
35-
36-
await page.waitForLoadState('networkidle');
32+
await toggleExperiment(page, 'on', 'Use paginated tables');
3733
});
3834

3935
test('Table loads and displays data', async ({page}) => {

‎tests/suites/sidebar/Sidebar.ts

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import type {Locator, Page} from '@playwright/test';
2+
3+
export class Sidebar {
4+
private sidebarContainer: Locator;
5+
private logoButton: Locator;
6+
private footer: Locator;
7+
private settingsButton: Locator;
8+
private documentationButton: Locator;
9+
private accountButton: Locator;
10+
private collapseButton: Locator;
11+
private drawer: Locator;
12+
private drawerMenu: Locator;
13+
private experimentsSection: Locator;
14+
15+
constructor(page: Page) {
16+
this.sidebarContainer = page.locator('.gn-aside-header__aside-content');
17+
this.logoButton = this.sidebarContainer.locator('.gn-logo__btn-logo');
18+
this.footer = this.sidebarContainer.locator('.gn-aside-header__footer');
19+
this.drawer = page.locator('.gn-drawer');
20+
this.drawerMenu = page.locator('.gn-settings-menu');
21+
this.experimentsSection = this.drawerMenu
22+
.locator('.gn-settings-menu__item')
23+
.filter({hasText: 'Experiments'});
24+
25+
// Footer buttons with specific icons
26+
const footerItems = this.sidebarContainer.locator('.gn-footer-item');
27+
this.documentationButton = footerItems.filter({hasText: 'Documentation'});
28+
this.settingsButton = footerItems
29+
.filter({hasText: 'Settings'})
30+
.locator('.gn-composite-bar-item__btn-icon');
31+
this.accountButton = footerItems.filter({hasText: 'Account'});
32+
33+
this.collapseButton = this.sidebarContainer.locator('.gn-collapse-button');
34+
}
35+
36+
async waitForSidebarToLoad() {
37+
await this.sidebarContainer.waitFor({state: 'visible'});
38+
}
39+
40+
async isSidebarVisible() {
41+
return this.sidebarContainer.isVisible();
42+
}
43+
44+
async isLogoButtonVisible() {
45+
return this.logoButton.isVisible();
46+
}
47+
48+
async isSettingsButtonVisible() {
49+
return this.settingsButton.isVisible();
50+
}
51+
52+
async isDocumentationButtonVisible() {
53+
return this.documentationButton.isVisible();
54+
}
55+
56+
async isAccountButtonVisible() {
57+
return this.accountButton.isVisible();
58+
}
59+
60+
async clickLogoButton() {
61+
await this.logoButton.click();
62+
}
63+
64+
async clickSettings() {
65+
await this.settingsButton.click();
66+
}
67+
68+
async clickDocumentation() {
69+
await this.documentationButton.click();
70+
}
71+
72+
async clickAccount() {
73+
await this.accountButton.click();
74+
}
75+
76+
async toggleCollapse() {
77+
await this.collapseButton.click();
78+
}
79+
80+
async isCollapsed() {
81+
const button = await this.collapseButton;
82+
const title = await button.getAttribute('title');
83+
return title === 'Expand';
84+
}
85+
86+
async getFooterItemsCount(): Promise<number> {
87+
return this.footer.locator('.gn-composite-bar-item').count();
88+
}
89+
90+
async isFooterItemVisible(index: number) {
91+
const items = this.footer.locator('.gn-composite-bar-item');
92+
return items.nth(index).isVisible();
93+
}
94+
95+
async clickFooterItem(index: number) {
96+
const items = this.footer.locator('.gn-composite-bar-item');
97+
await items.nth(index).click();
98+
}
99+
100+
async getFooterItemText(index: number): Promise<string> {
101+
const items = this.footer.locator('.gn-composite-bar-item');
102+
const item = items.nth(index);
103+
return item.locator('.gn-composite-bar-item__title-text').innerText();
104+
}
105+
106+
async isDrawerVisible() {
107+
return this.drawer.isVisible();
108+
}
109+
110+
async getDrawerMenuItems(): Promise<string[]> {
111+
const items = this.drawerMenu.locator('.gn-settings-menu__item >> span');
112+
const count = await items.count();
113+
const texts: string[] = [];
114+
for (let i = 0; i < count; i++) {
115+
texts.push(await items.nth(i).innerText());
116+
}
117+
return texts;
118+
}
119+
120+
async clickExperimentsSection() {
121+
await this.experimentsSection.click();
122+
}
123+
124+
async toggleExperimentByTitle(title: string) {
125+
const experimentItem = this.drawer
126+
.locator('.gn-settings__item-title')
127+
.filter({hasText: title});
128+
// Click the label element which wraps the switch, avoiding the slider that intercepts events
129+
const switchLabel = experimentItem.locator(
130+
'xpath=../../..//label[contains(@class, "g-control-label")]',
131+
);
132+
await switchLabel.click();
133+
}
134+
135+
async getFirstExperimentTitle(): Promise<string> {
136+
const experimentItem = this.drawer.locator('.gn-settings__item-title').first();
137+
return experimentItem.innerText();
138+
}
139+
140+
async isExperimentEnabled(title: string): Promise<boolean> {
141+
const experimentItem = this.drawer
142+
.locator('.gn-settings__item-title')
143+
.filter({hasText: title});
144+
const switchControl = experimentItem.locator('xpath=../../..//input[@type="checkbox"]');
145+
return switchControl.isChecked();
146+
}
147+
}

‎tests/suites/sidebar/sidebar.test.ts

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {PageModel} from '../../models/PageModel';
4+
import {toggleExperiment} from '../../utils/toggleExperiment';
5+
6+
import {Sidebar} from './Sidebar';
7+
8+
test.describe('Test Sidebar', async () => {
9+
test.beforeEach(async ({page}) => {
10+
const basePage = new PageModel(page);
11+
const response = await basePage.goto();
12+
expect(response?.ok()).toBe(true);
13+
});
14+
15+
test('Sidebar is visible and loads correctly', async ({page}) => {
16+
const sidebar = new Sidebar(page);
17+
await sidebar.waitForSidebarToLoad();
18+
await expect(sidebar.isSidebarVisible()).resolves.toBe(true);
19+
});
20+
21+
test('Logo button is visible and clickable', async ({page}) => {
22+
const sidebar = new Sidebar(page);
23+
await sidebar.waitForSidebarToLoad();
24+
await expect(sidebar.isLogoButtonVisible()).resolves.toBe(true);
25+
await sidebar.clickLogoButton();
26+
});
27+
28+
test('Settings button is visible and clickable', async ({page}) => {
29+
const sidebar = new Sidebar(page);
30+
await sidebar.waitForSidebarToLoad();
31+
await expect(sidebar.isSettingsButtonVisible()).resolves.toBe(true);
32+
await sidebar.clickSettings();
33+
});
34+
35+
test('Settings button click opens drawer with correct sections', async ({page}) => {
36+
const sidebar = new Sidebar(page);
37+
await sidebar.waitForSidebarToLoad();
38+
39+
// Initially drawer should not be visible
40+
await expect(sidebar.isDrawerVisible()).resolves.toBe(false);
41+
42+
// Click settings button
43+
await sidebar.clickSettings();
44+
await page.waitForTimeout(500); // Wait for animation
45+
46+
// Drawer should become visible
47+
await expect(sidebar.isDrawerVisible()).resolves.toBe(true);
48+
49+
// Verify drawer menu items
50+
const menuItems = await sidebar.getDrawerMenuItems();
51+
expect(menuItems).toEqual(['General', 'Editor', 'Experiments', 'About']);
52+
});
53+
54+
test('Documentation button is visible and clickable', async ({page}) => {
55+
const sidebar = new Sidebar(page);
56+
await sidebar.waitForSidebarToLoad();
57+
await expect(sidebar.isDocumentationButtonVisible()).resolves.toBe(true);
58+
await sidebar.clickDocumentation();
59+
});
60+
61+
test('Account button is visible and clickable', async ({page}) => {
62+
const sidebar = new Sidebar(page);
63+
await sidebar.waitForSidebarToLoad();
64+
await expect(sidebar.isAccountButtonVisible()).resolves.toBe(true);
65+
await sidebar.clickAccount();
66+
});
67+
68+
test('Sidebar can be collapsed and expanded', async ({page}) => {
69+
const sidebar = new Sidebar(page);
70+
await sidebar.waitForSidebarToLoad();
71+
72+
// Initially collapsed
73+
await expect(sidebar.isCollapsed()).resolves.toBe(true);
74+
75+
// Expand
76+
await sidebar.toggleCollapse();
77+
await page.waitForTimeout(500); // Wait for animation
78+
await expect(sidebar.isCollapsed()).resolves.toBe(false);
79+
80+
// Collapse
81+
await sidebar.toggleCollapse();
82+
await page.waitForTimeout(500); // Wait for animation
83+
await expect(sidebar.isCollapsed()).resolves.toBe(true);
84+
});
85+
86+
test('Footer items are visible', async ({page}) => {
87+
const sidebar = new Sidebar(page);
88+
await sidebar.waitForSidebarToLoad();
89+
90+
const itemsCount = await sidebar.getFooterItemsCount();
91+
expect(itemsCount).toBeGreaterThan(0);
92+
});
93+
94+
test('Can toggle experiments in settings', async ({page}) => {
95+
const sidebar = new Sidebar(page);
96+
await sidebar.clickSettings();
97+
await page.waitForTimeout(500); // Wait for animation
98+
await sidebar.clickExperimentsSection();
99+
const experimentTitle = await sidebar.getFirstExperimentTitle();
100+
101+
await toggleExperiment(page, 'on', experimentTitle);
102+
await sidebar.clickSettings();
103+
await page.waitForTimeout(500); // Wait for animation
104+
await sidebar.clickExperimentsSection();
105+
const newState = await sidebar.isExperimentEnabled(experimentTitle);
106+
expect(newState).toBe(true);
107+
108+
await toggleExperiment(page, 'off', experimentTitle);
109+
await sidebar.clickSettings();
110+
await page.waitForTimeout(500); // Wait for animation
111+
await sidebar.clickExperimentsSection();
112+
const finalState = await sidebar.isExperimentEnabled(experimentTitle);
113+
expect(finalState).toBe(false);
114+
});
115+
});

‎tests/suites/tenant/TenantPage.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {Locator, Page} from '@playwright/test';
33
import {PageModel} from '../../models/PageModel';
44
import {tenantPage} from '../../utils/constants';
55

6-
export const VISIBILITY_TIMEOUT = 5000;
6+
export const VISIBILITY_TIMEOUT = 10000;
77

88
export enum NavigationTabs {
99
Query = 'Query',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {tenantName} from '../../../utils/constants';
4+
import {toggleExperiment} from '../../../utils/toggleExperiment';
5+
import {TenantPage} from '../TenantPage';
6+
7+
import {ButtonNames, QueryEditor} from './QueryEditor';
8+
9+
test.describe('Test Plan to SVG functionality', async () => {
10+
const testQuery = 'SELECT 1;'; // Simple query that will generate a plan
11+
12+
test.beforeEach(async ({page}) => {
13+
const pageQueryParams = {
14+
schema: tenantName,
15+
database: tenantName,
16+
general: 'query',
17+
};
18+
19+
const tenantPage = new TenantPage(page);
20+
await tenantPage.goto(pageQueryParams);
21+
});
22+
23+
test('Plan to SVG experiment shows execution plan in new tab', async ({page}) => {
24+
const queryEditor = new QueryEditor(page);
25+
26+
// 1. Turn on Plan to SVG experiment
27+
await toggleExperiment(page, 'on', 'Plan to SVG');
28+
29+
// 2. Set stats level to Full
30+
await queryEditor.clickGearButton();
31+
await queryEditor.settingsDialog.changeStatsLevel('Full');
32+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
33+
34+
// 3. Set query and run it
35+
await queryEditor.setQuery(testQuery);
36+
await queryEditor.clickRunButton();
37+
38+
// 4. Wait for query execution to complete
39+
await expect(async () => {
40+
const status = await queryEditor.getExecutionStatus();
41+
expect(status).toBe('Completed');
42+
}).toPass();
43+
44+
// 5. Check if Execution Plan button appears and click it
45+
const executionPlanButton = page.locator('button:has-text("Execution plan")');
46+
await expect(executionPlanButton).toBeVisible();
47+
await executionPlanButton.click();
48+
await page.waitForTimeout(1000); // Wait for new tab to open
49+
50+
// 6. Verify we're taken to a new tab with SVG content
51+
const svgElement = page.locator('svg').first();
52+
await expect(svgElement).toBeVisible();
53+
});
54+
});

‎tests/suites/tenant/queryEditor/queryEditor.test.ts

-163
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,6 @@ test.describe('Test Query Editor', async () => {
2727
await tenantPage.goto(pageQueryParams);
2828
});
2929

30-
test('Settings dialog opens on Gear click and closes on Cancel', async ({page}) => {
31-
const queryEditor = new QueryEditor(page);
32-
await queryEditor.clickGearButton();
33-
34-
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
35-
36-
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
37-
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
38-
});
39-
40-
test('Settings dialog saves changes and updates Gear button', async ({page}) => {
41-
const queryEditor = new QueryEditor(page);
42-
await queryEditor.clickGearButton();
43-
44-
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
45-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
46-
47-
await expect(async () => {
48-
const text = await queryEditor.gearButtonText();
49-
expect(text).toContain('(1)');
50-
}).toPass({timeout: VISIBILITY_TIMEOUT});
51-
});
52-
5330
test('Run button executes YQL script', async ({page}) => {
5431
const queryEditor = new QueryEditor(page);
5532
await queryEditor.run(testQuery, QueryMode.YQLScript);
@@ -103,92 +80,6 @@ test.describe('Test Query Editor', async () => {
10380
await expect(errorMessage).toContain('Column references are not allowed without FROM');
10481
});
10582

106-
test('Banner appears after executing script with changed settings', async ({page}) => {
107-
const queryEditor = new QueryEditor(page);
108-
109-
// Change a setting
110-
await queryEditor.clickGearButton();
111-
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
112-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
113-
114-
// Execute a script
115-
await queryEditor.setQuery(testQuery);
116-
await queryEditor.clickRunButton();
117-
118-
// Check if banner appears
119-
await expect(queryEditor.isBannerVisible()).resolves.toBe(true);
120-
});
121-
122-
test('Banner not appears for running query', async ({page}) => {
123-
const queryEditor = new QueryEditor(page);
124-
125-
// Change a setting
126-
await queryEditor.clickGearButton();
127-
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
128-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
129-
130-
// Execute a script
131-
await queryEditor.setQuery(longRunningQuery);
132-
await queryEditor.clickRunButton();
133-
await page.waitForTimeout(500);
134-
135-
// Check if banner appears
136-
await expect(queryEditor.isBannerHidden()).resolves.toBe(true);
137-
});
138-
139-
test('Indicator icon appears after closing banner', async ({page}) => {
140-
const queryEditor = new QueryEditor(page);
141-
142-
// Change a setting
143-
await queryEditor.clickGearButton();
144-
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
145-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
146-
147-
// Execute a script to make the banner appear
148-
await queryEditor.setQuery(testQuery);
149-
await queryEditor.clickRunButton();
150-
151-
// Close the banner
152-
await queryEditor.closeBanner();
153-
154-
await expect(queryEditor.isIndicatorIconVisible()).resolves.toBe(true);
155-
});
156-
157-
test('Indicator not appears for running query', async ({page}) => {
158-
const queryEditor = new QueryEditor(page);
159-
160-
// Change a setting
161-
await queryEditor.clickGearButton();
162-
await queryEditor.settingsDialog.changeTransactionMode('Snapshot');
163-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
164-
165-
// Execute a script to make the banner appear
166-
await queryEditor.setQuery(testQuery);
167-
await queryEditor.clickRunButton();
168-
169-
// Close the banner
170-
await queryEditor.closeBanner();
171-
await queryEditor.setQuery(longRunningQuery);
172-
await queryEditor.clickRunButton();
173-
await page.waitForTimeout(500);
174-
175-
await expect(queryEditor.isIndicatorIconHidden()).resolves.toBe(true);
176-
});
177-
178-
test('Gear button shows number of changed settings', async ({page}) => {
179-
const queryEditor = new QueryEditor(page);
180-
await queryEditor.clickGearButton();
181-
182-
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
183-
await queryEditor.settingsDialog.changeTransactionMode('Snapshot');
184-
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
185-
186-
await expect(async () => {
187-
const text = await queryEditor.gearButtonText();
188-
expect(text).toContain('(2)');
189-
}).toPass({timeout: VISIBILITY_TIMEOUT});
190-
});
191-
19283
test('Run and Explain buttons are disabled when query is empty', async ({page}) => {
19384
const queryEditor = new QueryEditor(page);
19485

@@ -201,15 +92,6 @@ test.describe('Test Query Editor', async () => {
20192
await expect(queryEditor.isExplainButtonEnabled()).resolves.toBe(true);
20293
});
20394

204-
test('Banner does not appear when executing script with default settings', async ({page}) => {
205-
const queryEditor = new QueryEditor(page);
206-
207-
await queryEditor.setQuery(testQuery);
208-
await queryEditor.clickRunButton();
209-
210-
await expect(queryEditor.isBannerHidden()).resolves.toBe(true);
211-
});
212-
21395
test('Stop button and elapsed time label appears when query is running', async ({page}) => {
21496
const queryEditor = new QueryEditor(page);
21597

@@ -290,51 +172,6 @@ test.describe('Test Query Editor', async () => {
290172
await expect(queryEditor.isStopButtonHidden()).resolves.toBe(true);
291173
});
292174

293-
test('No query status when no query was executed', async ({page}) => {
294-
const queryEditor = new QueryEditor(page);
295-
296-
// Ensure page is loaded
297-
await queryEditor.setQuery(longRunningQuery);
298-
await queryEditor.clickGearButton();
299-
await queryEditor.settingsDialog.changeStatsLevel('Profile');
300-
301-
await expect(queryEditor.isResultsControlsHidden()).resolves.toBe(true);
302-
});
303-
304-
test('Running query status for running query', async ({page}) => {
305-
const queryEditor = new QueryEditor(page);
306-
307-
await queryEditor.setQuery(longRunningQuery);
308-
await queryEditor.clickRunButton();
309-
await page.waitForTimeout(500);
310-
311-
const statusElement = await queryEditor.getExecutionStatus();
312-
await expect(statusElement).toBe('Running');
313-
});
314-
315-
test('Completed query status for completed query', async ({page}) => {
316-
const queryEditor = new QueryEditor(page);
317-
318-
await queryEditor.setQuery(testQuery);
319-
await queryEditor.clickRunButton();
320-
await page.waitForTimeout(1000);
321-
322-
const statusElement = await queryEditor.getExecutionStatus();
323-
await expect(statusElement).toBe('Completed');
324-
});
325-
326-
test('Failed query status for failed query', async ({page}) => {
327-
const queryEditor = new QueryEditor(page);
328-
329-
const invalidQuery = 'Select d';
330-
await queryEditor.setQuery(invalidQuery);
331-
await queryEditor.clickRunButton();
332-
await page.waitForTimeout(1000);
333-
334-
const statusElement = await queryEditor.getExecutionStatus();
335-
await expect(statusElement).toBe('Failed');
336-
});
337-
338175
test('Changing tab inside results pane doesnt change results view', async ({page}) => {
339176
const queryEditor = new QueryEditor(page);
340177
await queryEditor.setQuery(testQuery);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {tenantName} from '../../../utils/constants';
4+
import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage';
5+
import {longRunningQuery} from '../constants';
6+
7+
import {ButtonNames, QueryEditor, QueryMode} from './QueryEditor';
8+
9+
test.describe('Test Query Settings', async () => {
10+
const testQuery = 'SELECT 1, 2, 3, 4, 5;';
11+
12+
test.beforeEach(async ({page}) => {
13+
const pageQueryParams = {
14+
schema: tenantName,
15+
database: tenantName,
16+
general: 'query',
17+
};
18+
19+
const tenantPage = new TenantPage(page);
20+
await tenantPage.goto(pageQueryParams);
21+
});
22+
23+
test('Settings dialog opens on Gear click and closes on Cancel', async ({page}) => {
24+
const queryEditor = new QueryEditor(page);
25+
await queryEditor.clickGearButton();
26+
27+
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
28+
29+
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
30+
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
31+
});
32+
33+
test('Settings dialog saves changes and updates Gear button', async ({page}) => {
34+
const queryEditor = new QueryEditor(page);
35+
await queryEditor.clickGearButton();
36+
37+
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
38+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
39+
40+
await expect(async () => {
41+
const text = await queryEditor.gearButtonText();
42+
expect(text).toContain('(1)');
43+
}).toPass({timeout: VISIBILITY_TIMEOUT});
44+
});
45+
46+
test('Banner appears after executing script with changed settings', async ({page}) => {
47+
const queryEditor = new QueryEditor(page);
48+
49+
// Change a setting
50+
await queryEditor.clickGearButton();
51+
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
52+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
53+
54+
// Execute a script
55+
await queryEditor.setQuery(testQuery);
56+
await queryEditor.clickRunButton();
57+
58+
// Check if banner appears
59+
await expect(queryEditor.isBannerVisible()).resolves.toBe(true);
60+
});
61+
62+
test('Banner not appears for running query', async ({page}) => {
63+
const queryEditor = new QueryEditor(page);
64+
65+
// Change a setting
66+
await queryEditor.clickGearButton();
67+
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
68+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
69+
70+
// Execute a script
71+
await queryEditor.setQuery(longRunningQuery);
72+
await queryEditor.clickRunButton();
73+
await page.waitForTimeout(500);
74+
75+
// Check if banner appears
76+
await expect(queryEditor.isBannerHidden()).resolves.toBe(true);
77+
});
78+
79+
test('Indicator icon appears after closing banner', async ({page}) => {
80+
const queryEditor = new QueryEditor(page);
81+
82+
// Change a setting
83+
await queryEditor.clickGearButton();
84+
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
85+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
86+
87+
// Execute a script to make the banner appear
88+
await queryEditor.setQuery(testQuery);
89+
await queryEditor.clickRunButton();
90+
91+
// Close the banner
92+
await queryEditor.closeBanner();
93+
94+
await expect(queryEditor.isIndicatorIconVisible()).resolves.toBe(true);
95+
});
96+
97+
test('Indicator not appears for running query', async ({page}) => {
98+
const queryEditor = new QueryEditor(page);
99+
100+
// Change a setting
101+
await queryEditor.clickGearButton();
102+
await queryEditor.settingsDialog.changeTransactionMode('Snapshot');
103+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
104+
105+
// Execute a script to make the banner appear
106+
await queryEditor.setQuery(testQuery);
107+
await queryEditor.clickRunButton();
108+
109+
// Close the banner
110+
await queryEditor.closeBanner();
111+
await queryEditor.setQuery(longRunningQuery);
112+
await queryEditor.clickRunButton();
113+
await page.waitForTimeout(500);
114+
115+
await expect(queryEditor.isIndicatorIconHidden()).resolves.toBe(true);
116+
});
117+
118+
test('Gear button shows number of changed settings', async ({page}) => {
119+
const queryEditor = new QueryEditor(page);
120+
await queryEditor.clickGearButton();
121+
122+
await queryEditor.settingsDialog.changeQueryMode(QueryMode.Scan);
123+
await queryEditor.settingsDialog.changeTransactionMode('Snapshot');
124+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
125+
126+
await expect(async () => {
127+
const text = await queryEditor.gearButtonText();
128+
expect(text).toContain('(2)');
129+
}).toPass({timeout: VISIBILITY_TIMEOUT});
130+
});
131+
132+
test('Banner does not appear when executing script with default settings', async ({page}) => {
133+
const queryEditor = new QueryEditor(page);
134+
135+
await queryEditor.setQuery(testQuery);
136+
await queryEditor.clickRunButton();
137+
138+
await expect(queryEditor.isBannerHidden()).resolves.toBe(true);
139+
});
140+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {tenantName} from '../../../utils/constants';
4+
import {TenantPage} from '../TenantPage';
5+
import {longRunningQuery} from '../constants';
6+
7+
import {QueryEditor} from './QueryEditor';
8+
9+
test.describe('Test Query Execution Status', async () => {
10+
const testQuery = 'SELECT 1;'; // Simple query that will generate a plan
11+
12+
test.beforeEach(async ({page}) => {
13+
const pageQueryParams = {
14+
schema: tenantName,
15+
database: tenantName,
16+
general: 'query',
17+
};
18+
19+
const tenantPage = new TenantPage(page);
20+
await tenantPage.goto(pageQueryParams);
21+
});
22+
23+
test('No query status when no query was executed', async ({page}) => {
24+
const queryEditor = new QueryEditor(page);
25+
26+
// Ensure page is loaded
27+
await queryEditor.setQuery(longRunningQuery);
28+
await queryEditor.clickGearButton();
29+
await queryEditor.settingsDialog.changeStatsLevel('Profile');
30+
31+
await expect(queryEditor.isResultsControlsHidden()).resolves.toBe(true);
32+
});
33+
34+
test('Running query status for running query', async ({page}) => {
35+
const queryEditor = new QueryEditor(page);
36+
37+
await queryEditor.setQuery(longRunningQuery);
38+
await queryEditor.clickRunButton();
39+
await page.waitForTimeout(500);
40+
41+
const statusElement = await queryEditor.getExecutionStatus();
42+
await expect(statusElement).toBe('Running');
43+
});
44+
45+
test('Completed query status for completed query', async ({page}) => {
46+
const queryEditor = new QueryEditor(page);
47+
48+
await queryEditor.setQuery(testQuery);
49+
await queryEditor.clickRunButton();
50+
await page.waitForTimeout(1000);
51+
52+
const statusElement = await queryEditor.getExecutionStatus();
53+
await expect(statusElement).toBe('Completed');
54+
});
55+
56+
test('Failed query status for failed query', async ({page}) => {
57+
const queryEditor = new QueryEditor(page);
58+
59+
const invalidQuery = 'Select d';
60+
await queryEditor.setQuery(invalidQuery);
61+
await queryEditor.clickRunButton();
62+
await page.waitForTimeout(1000);
63+
64+
const statusElement = await queryEditor.getExecutionStatus();
65+
await expect(statusElement).toBe('Failed');
66+
});
67+
});

‎tests/suites/tenant/queryHistory/queryHistory.test.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import {expect, test} from '@playwright/test';
22

33
import {tenantName} from '../../../utils/constants';
4-
import {TenantPage} from '../TenantPage';
4+
import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage';
55
import {QueryEditor, QueryMode} from '../queryEditor/QueryEditor';
66

77
import executeQueryWithKeybinding from './utils';
88

9-
export const VISIBILITY_TIMEOUT = 5000;
10-
119
test.describe('Query History', () => {
1210
let tenantPage: TenantPage;
1311
let queryEditor: QueryEditor;

‎tests/utils/toggleExperiment.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type {Page} from '@playwright/test';
2+
3+
import {Sidebar} from '../suites/sidebar/Sidebar';
4+
5+
export const toggleExperiment = async (page: Page, state: 'on' | 'off', title: string) => {
6+
const sidebar = new Sidebar(page);
7+
await sidebar.waitForSidebarToLoad();
8+
if (!(await sidebar.isDrawerVisible())) {
9+
await sidebar.clickSettings();
10+
await page.waitForTimeout(500); // Wait for animation
11+
}
12+
await sidebar.clickExperimentsSection();
13+
const currentState = await sidebar.isExperimentEnabled(title);
14+
const desiredState = state === 'on';
15+
16+
if (currentState !== desiredState) {
17+
await sidebar.toggleExperimentByTitle(title);
18+
}
19+
20+
if (await sidebar.isDrawerVisible()) {
21+
await sidebar.clickSettings();
22+
await page.waitForTimeout(500); // Wait for animation
23+
}
24+
};

0 commit comments

Comments
 (0)
Please sign in to comment.