Skip to content

Commit e5be605

Browse files
authored
chore: more tests (#1805)
1 parent 9390a3a commit e5be605

File tree

14 files changed

+570
-27
lines changed

14 files changed

+570
-27
lines changed

src/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function PaneVisibilityToggleButtons({
9191
className={b(
9292
{
9393
hidden: isCollapsed,
94+
type: 'collapse',
9495
},
9596
className,
9697
)}
@@ -106,6 +107,7 @@ export function PaneVisibilityToggleButtons({
106107
className={b(
107108
{
108109
hidden: !isCollapsed,
110+
type: 'expand',
109111
},
110112
className,
111113
)}

tests/suites/tenant/TenantPage.ts

Lines changed: 1 addition & 1 deletion
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 = 10000;
6+
export const VISIBILITY_TIMEOUT = 10 * 1000;
77

88
export enum NavigationTabs {
99
Query = 'Query',

tests/suites/tenant/queryEditor/models/QueryEditor.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import type {Locator, Page} from '@playwright/test';
22

33
import type {QUERY_MODES} from '../../../../../src/utils/query';
44
import {VISIBILITY_TIMEOUT} from '../../TenantPage';
5+
import {QueriesHistoryTable} from '../../queryHistory/models/QueriesHistoryTable';
6+
import {SavedQueriesTable} from '../../savedQueries/models/SavedQueriesTable';
57

68
import {QueryTabsNavigation} from './QueryTabsNavigation';
79
import {PaneWrapper, ResultTable} from './ResultTable';
8-
import {SavedQueriesTable} from './SavedQueriesTable';
910
import {SettingsDialog} from './SettingsDialog';
1011

1112
export enum ExplainResultType {
@@ -41,13 +42,15 @@ export class QueryEditor {
4142
queryTabs: QueryTabsNavigation;
4243
resultTable: ResultTable;
4344
savedQueries: SavedQueriesTable;
45+
historyQueries: QueriesHistoryTable;
4446
editorTextArea: Locator;
4547

4648
private page: Page;
4749
private selector: Locator;
4850
private runButton: Locator;
4951
private explainButton: Locator;
5052
private stopButton: Locator;
53+
private saveButton: Locator;
5154
private gearButton: Locator;
5255
private indicatorIcon: Locator;
5356
private banner: Locator;
@@ -63,6 +66,7 @@ export class QueryEditor {
6366
this.runButton = this.selector.getByRole('button', {name: ButtonNames.Run});
6467
this.stopButton = this.selector.getByRole('button', {name: ButtonNames.Stop});
6568
this.explainButton = this.selector.getByRole('button', {name: ButtonNames.Explain});
69+
this.saveButton = this.selector.getByRole('button', {name: ButtonNames.Save});
6670
this.gearButton = this.selector.locator('.ydb-query-editor-controls__gear-button');
6771
this.executionStatus = this.selector.locator('.kv-query-execution-status');
6872
this.resultsControls = this.selector.locator('.ydb-query-result__controls');
@@ -78,6 +82,7 @@ export class QueryEditor {
7882
this.paneWrapper = new PaneWrapper(page);
7983
this.queryTabs = new QueryTabsNavigation(page);
8084
this.savedQueries = new SavedQueriesTable(page);
85+
this.historyQueries = new QueriesHistoryTable(page);
8186
}
8287

8388
async run(query: string, mode: keyof typeof QUERY_MODES) {
@@ -116,6 +121,11 @@ export class QueryEditor {
116121
await this.explainButton.click();
117122
}
118123

124+
async clickSaveButton() {
125+
await this.saveButton.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
126+
await this.saveButton.click();
127+
}
128+
119129
async getExplainResult(type: ExplainResultType) {
120130
await this.selectResultTypeRadio(type);
121131
const resultArea = this.selector.locator('.ydb-query-result__result');
@@ -144,6 +154,37 @@ export class QueryEditor {
144154
await this.editorTextArea.focus();
145155
}
146156

157+
async selectText(startLine: number, startColumn: number, endLine: number, endColumn: number) {
158+
await this.editorTextArea.evaluate(
159+
(_, coords) => {
160+
const editor = window.ydbEditor;
161+
if (editor) {
162+
editor.setSelection({
163+
startLineNumber: coords.startLine,
164+
startColumn: coords.startColumn,
165+
endLineNumber: coords.endLine,
166+
endColumn: coords.endColumn,
167+
});
168+
}
169+
},
170+
{startLine, startColumn, endLine, endColumn},
171+
);
172+
}
173+
174+
async pressKeys(key: string) {
175+
await this.editorTextArea.press(key);
176+
}
177+
178+
async runSelectedQueryViaContextMenu() {
179+
await this.editorTextArea.evaluate(() => {
180+
const editor = window.ydbEditor;
181+
if (editor) {
182+
// Trigger the sendSelectedQuery action directly
183+
editor.trigger('contextMenu', 'sendSelectedQuery', null);
184+
}
185+
});
186+
}
187+
147188
async closeSettingsDialog() {
148189
await this.settingsDialog.clickButton(ButtonNames.Cancel);
149190
}
@@ -166,6 +207,7 @@ export class QueryEditor {
166207

167208
async setQuery(query: string) {
168209
await this.editorTextArea.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
210+
await this.editorTextArea.clear();
169211
await this.editorTextArea.fill(query);
170212
}
171213

@@ -205,6 +247,36 @@ export class QueryEditor {
205247
return true;
206248
}
207249

250+
async collapseResultsControls() {
251+
const collapseButton = this.resultsControls.locator(
252+
'.kv-pane-visibility-button_type_collapse',
253+
);
254+
await collapseButton.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
255+
await collapseButton.click();
256+
}
257+
258+
async expandResultsControls() {
259+
const expandButton = this.resultsControls.locator('.kv-pane-visibility-button_type_expand');
260+
await expandButton.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
261+
await expandButton.click();
262+
}
263+
264+
async isResultsControlsCollapsed() {
265+
const expandButton = this.resultsControls.locator('.kv-pane-visibility-button_type_expand');
266+
try {
267+
await expandButton.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
268+
return true;
269+
} catch {
270+
return false;
271+
}
272+
}
273+
274+
async clickCopyResultButton() {
275+
const copyButton = this.resultsControls.locator('button[title="Copy result"]');
276+
await copyButton.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
277+
await copyButton.click();
278+
}
279+
208280
async isRunButtonEnabled() {
209281
return this.runButton.isEnabled({timeout: VISIBILITY_TIMEOUT});
210282
}

tests/suites/tenant/queryEditor/models/ResultTable.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ export class ResultTable {
2424
private table: Locator;
2525
private preview: Locator;
2626
private resultHead: Locator;
27+
private resultWrapper: Locator;
2728

2829
constructor(selector: Locator) {
2930
this.table = selector.locator('.ydb-query-result-sets-viewer__result');
3031
this.preview = selector.locator('.kv-preview__result');
3132
this.resultHead = selector.locator('.ydb-query-result-sets-viewer__head');
33+
this.resultWrapper = selector.locator('.ydb-query-result-sets-viewer__result-wrapper');
3234
}
3335

3436
async isVisible() {
@@ -70,4 +72,34 @@ export class ResultTable {
7072
await this.resultHead.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
7173
return this.resultHead.innerText();
7274
}
75+
76+
async getResultTabs() {
77+
const tabs = this.resultWrapper.locator(
78+
'.ydb-query-result-sets-viewer__tabs .g-tabs__item',
79+
);
80+
await tabs.first().waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
81+
return tabs;
82+
}
83+
84+
async getResultTabsCount() {
85+
const tabs = await this.getResultTabs();
86+
return tabs.count();
87+
}
88+
89+
async getResultTabTitle(index: number) {
90+
const tabs = await this.getResultTabs();
91+
const tab = tabs.nth(index);
92+
await tab.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
93+
return tab.getAttribute('title');
94+
}
95+
96+
async hasMultipleResultTabs() {
97+
const tabs = this.resultWrapper.locator('.ydb-query-result-sets-viewer__tabs');
98+
try {
99+
await tabs.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
100+
return true;
101+
} catch {
102+
return false;
103+
}
104+
}
73105
}

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {expect, test} from '@playwright/test';
22

33
import {QUERY_MODES, STATISTICS_MODES} from '../../../../src/utils/query';
4+
import {getClipboardContent} from '../../../utils/clipboard';
45
import {tenantName} from '../../../utils/constants';
56
import {NavigationTabs, TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage';
67
import {createTableQuery, longRunningQuery, longTableSelect} from '../constants';
@@ -12,6 +13,7 @@ import {
1213
QueryTabs,
1314
ResultTabNames,
1415
} from './models/QueryEditor';
16+
import {executeSelectedQueryWithKeybinding} from './utils';
1517

1618
test.describe('Test Query Editor', async () => {
1719
const testQuery = 'SELECT 1, 2, 3, 4, 5;';
@@ -241,4 +243,108 @@ test.describe('Test Query Editor', async () => {
241243

242244
await expect(queryEditor.waitForStatus('Completed')).resolves.toBe(true);
243245
});
246+
247+
test('Running selected query via keyboard shortcut executes only selected part', async ({
248+
page,
249+
}) => {
250+
const queryEditor = new QueryEditor(page);
251+
const multiQuery = 'SELECT 1;\nSELECT 2;';
252+
253+
// First verify running the entire query produces two results
254+
await queryEditor.setQuery(multiQuery);
255+
await queryEditor.clickRunButton();
256+
await expect(queryEditor.waitForStatus('Completed')).resolves.toBe(true);
257+
258+
// Verify there are two result tabs
259+
await expect(queryEditor.resultTable.getResultTabsCount()).resolves.toBe(2);
260+
await expect(queryEditor.resultTable.getResultTabTitle(0)).resolves.toBe('Result #1');
261+
await expect(queryEditor.resultTable.getResultTabTitle(1)).resolves.toBe('Result #2');
262+
263+
// Then verify running only selected part produces one result
264+
await queryEditor.focusEditor();
265+
await queryEditor.selectText(1, 1, 1, 9);
266+
267+
// Use keyboard shortcut to run selected query
268+
await executeSelectedQueryWithKeybinding(page);
269+
270+
await expect(queryEditor.waitForStatus('Completed')).resolves.toBe(true);
271+
await expect(queryEditor.resultTable.hasMultipleResultTabs()).resolves.toBe(false);
272+
await expect(queryEditor.resultTable.getResultHeadText()).resolves.toBe('Result(1)');
273+
});
274+
275+
test('Running selected query via context menu executes only selected part', async ({page}) => {
276+
const queryEditor = new QueryEditor(page);
277+
const multiQuery = 'SELECT 1;\nSELECT 2;';
278+
279+
// First verify running the entire query produces two results with tabs
280+
await queryEditor.setQuery(multiQuery);
281+
await queryEditor.clickRunButton();
282+
await expect(queryEditor.waitForStatus('Completed')).resolves.toBe(true);
283+
284+
// Verify there are two result tabs
285+
await expect(queryEditor.resultTable.getResultTabsCount()).resolves.toBe(2);
286+
await expect(queryEditor.resultTable.getResultTabTitle(0)).resolves.toBe('Result #1');
287+
await expect(queryEditor.resultTable.getResultTabTitle(1)).resolves.toBe('Result #2');
288+
289+
// Then verify running only selected part produces one result without tabs
290+
await queryEditor.focusEditor();
291+
await queryEditor.selectText(1, 1, 1, 9);
292+
293+
// Use context menu to run selected query
294+
await queryEditor.runSelectedQueryViaContextMenu();
295+
296+
await expect(queryEditor.waitForStatus('Completed')).resolves.toBe(true);
297+
await expect(queryEditor.resultTable.hasMultipleResultTabs()).resolves.toBe(false);
298+
await expect(queryEditor.resultTable.getResultHeadText()).resolves.toBe('Result(1)');
299+
});
300+
301+
test('Results controls collapse and expand functionality', async ({page}) => {
302+
const queryEditor = new QueryEditor(page);
303+
304+
// Run a query to show results
305+
await queryEditor.setQuery('SELECT 1;');
306+
await queryEditor.clickRunButton();
307+
await queryEditor.waitForStatus('Completed');
308+
309+
// Verify controls are initially visible
310+
await expect(queryEditor.isResultsControlsVisible()).resolves.toBe(true);
311+
await expect(queryEditor.isResultsControlsCollapsed()).resolves.toBe(false);
312+
313+
// Test collapse
314+
await queryEditor.collapseResultsControls();
315+
await expect(queryEditor.isResultsControlsCollapsed()).resolves.toBe(true);
316+
317+
// Test expand
318+
await queryEditor.expandResultsControls();
319+
await expect(queryEditor.isResultsControlsCollapsed()).resolves.toBe(false);
320+
});
321+
322+
test('Copy result button copies to clipboard', async ({page}) => {
323+
const queryEditor = new QueryEditor(page);
324+
const query = 'SELECT 42 as answer;';
325+
326+
// Run query to get results
327+
await queryEditor.setQuery(query);
328+
await queryEditor.clickRunButton();
329+
await queryEditor.waitForStatus('Completed');
330+
331+
// Click copy button
332+
await queryEditor.clickCopyResultButton();
333+
334+
// Wait for clipboard operation to complete
335+
await page.waitForTimeout(2000);
336+
337+
// Retry clipboard read a few times if needed
338+
let clipboardContent = '';
339+
for (let i = 0; i < 3; i++) {
340+
clipboardContent = await getClipboardContent(page);
341+
if (clipboardContent) {
342+
break;
343+
}
344+
await page.waitForTimeout(500);
345+
}
346+
347+
// Verify clipboard contains the query result
348+
expect(clipboardContent).toContain('42');
349+
});
244350
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {expect, test} from '@playwright/test';
22

33
import {dsVslotsSchema, dsVslotsTableName, tenantName} from '../../../utils/constants';
44
import {TenantPage} from '../TenantPage';
5+
import {SavedQueriesTable} from '../savedQueries/models/SavedQueriesTable';
56
import {ObjectSummary} from '../summary/ObjectSummary';
67
import {RowTableAction} from '../summary/types';
78

@@ -13,7 +14,6 @@ import {
1314
} from './models/NewSqlDropdownMenu';
1415
import {QueryEditor, QueryTabs} from './models/QueryEditor';
1516
import {SaveQueryDialog} from './models/SaveQueryDialog';
16-
import {SavedQueriesTable} from './models/SavedQueriesTable';
1717
import {UnsavedChangesModal} from './models/UnsavedChangesModal';
1818

1919
test.describe('Query Templates', () => {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type {Page} from '@playwright/test';
2+
3+
export const executeSelectedQueryWithKeybinding = async (page: Page) => {
4+
const isMac = process.platform === 'darwin';
5+
const browserName = page.context().browser()?.browserType().name() ?? 'chromium';
6+
const modifierKey = browserName === 'webkit' ? 'Meta' : 'Control';
7+
8+
if (browserName !== 'webkit' || isMac) {
9+
await page.keyboard.down(modifierKey);
10+
await page.keyboard.down('Shift');
11+
await page.keyboard.press('Enter');
12+
await page.keyboard.up('Shift');
13+
await page.keyboard.up(modifierKey);
14+
} else {
15+
await page.keyboard.press('Meta+Shift+Enter');
16+
}
17+
18+
// Add a small delay to ensure the event is processed
19+
await page.waitForTimeout(1000);
20+
};

0 commit comments

Comments
 (0)