mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
209 lines
7.6 KiB
JavaScript
209 lines
7.6 KiB
JavaScript
/**
|
|
* MonacoEditorHelper - Utility class for Monaco editor interactions
|
|
*
|
|
* Encapsulates Monaco-specific selectors and interactions to:
|
|
* 1. Keep spec files clean (POM compliance)
|
|
* 2. Centralize Monaco selector changes if Monaco version updates
|
|
* 3. Provide consistent interaction patterns across tests
|
|
*
|
|
* Note: Monaco editor is a third-party component. These selectors
|
|
* may need updates when Monaco version changes.
|
|
*/
|
|
|
|
const { expect } = require('@playwright/test');
|
|
|
|
class MonacoEditorHelper {
|
|
/**
|
|
* @param {import('@playwright/test').Page} page - Playwright page object
|
|
*/
|
|
constructor(page) {
|
|
this.page = page;
|
|
|
|
// Monaco editor selectors (third-party component)
|
|
this.selectors = {
|
|
editor: '.monaco-editor',
|
|
viewLines: '.monaco-editor .view-lines',
|
|
inputArea: '.monaco-editor textarea.inputarea',
|
|
suggestWidget: '.monaco-editor .suggest-widget',
|
|
suggestionRows: '.monaco-editor .suggest-widget .monaco-list-row',
|
|
suggestionLabel: '.label-name, .monaco-icon-label-container',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the Monaco editor within a container
|
|
* @param {import('@playwright/test').Locator} container - Container locator with data-test attribute
|
|
* @returns {import('@playwright/test').Locator}
|
|
*/
|
|
getEditor(container) {
|
|
return container.locator(this.selectors.viewLines).first();
|
|
}
|
|
|
|
/**
|
|
* Get the input area (textarea) within a container
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
* @returns {import('@playwright/test').Locator}
|
|
*/
|
|
getInputArea(container) {
|
|
return container.locator(this.selectors.inputArea);
|
|
}
|
|
|
|
/**
|
|
* Click into the Monaco editor to focus it
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
*/
|
|
async focus(container) {
|
|
const inputArea = this.getInputArea(container);
|
|
// The textarea lags the container when the editor is revealed via v-if; wait for it to attach so a one-shot count() can't miss it and skip focusing (→ typed text lands nowhere).
|
|
await inputArea.waitFor({ state: 'attached', timeout: 10000 }).catch(() => {});
|
|
if (await inputArea.count() > 0) {
|
|
await inputArea.focus();
|
|
} else {
|
|
const editor = this.getEditor(container);
|
|
await editor.click({ force: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all content from the Monaco editor
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
*/
|
|
async clear(container) {
|
|
await this.focus(container);
|
|
// ControlOrMeta, not Control: on macOS Control+a is "move to line start",
|
|
// so a plain Control+a leaves the editor untouched and the next type()
|
|
// appends to the existing query instead of replacing it.
|
|
await this.page.keyboard.press('ControlOrMeta+a');
|
|
await this.page.keyboard.press('Backspace');
|
|
}
|
|
|
|
/**
|
|
* Type text into the Monaco editor
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
* @param {string} text - Text to type
|
|
*/
|
|
async type(container, text) {
|
|
await this.focus(container);
|
|
await this.page.keyboard.type(text);
|
|
}
|
|
|
|
/**
|
|
* Set content in the Monaco editor (clears first, then types)
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
* @param {string} content - Content to set
|
|
*/
|
|
async setContent(container, content) {
|
|
await this.clear(container);
|
|
await this.page.keyboard.type(content);
|
|
}
|
|
|
|
/**
|
|
* Set content and confirm the editor model holds exactly it, retyping if the text did not replace what was there.
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
* @param {string} content - Content to set
|
|
* @param {number} timeout - Timeout in milliseconds (default 15000)
|
|
*/
|
|
async setContentVerified(container, content, timeout = 15000) {
|
|
const editorCall = (action) => container.evaluate((node, act) => {
|
|
const editor = window.monaco?.editor?.getEditors?.().find((e) => node.contains(e.getDomNode()));
|
|
if (!editor) return null;
|
|
if (act === 'select-all') {
|
|
editor.focus();
|
|
editor.setSelection(editor.getModel().getFullModelRange());
|
|
}
|
|
return editor.getValue();
|
|
}, action);
|
|
// The keyboard select-all can miss the editor's input, so the selection goes through the editor; the text is still typed.
|
|
await expect.poll(async () => {
|
|
if ((await editorCall('read')) === content) return true;
|
|
await editorCall('select-all');
|
|
await this.page.keyboard.press('Backspace');
|
|
await this.page.keyboard.type(content);
|
|
return (await editorCall('read')) === content;
|
|
}, { timeout, intervals: [500, 1000, 2000] }).toBe(true);
|
|
}
|
|
|
|
/**
|
|
* Get the text content of the Monaco editor
|
|
* @param {import('@playwright/test').Locator} container - Container locator
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async getContent(container) {
|
|
const viewLines = container.locator(this.selectors.viewLines);
|
|
return await viewLines.textContent();
|
|
}
|
|
|
|
/**
|
|
* Trigger autocomplete suggestions (Ctrl+Space)
|
|
*/
|
|
async triggerSuggestions() {
|
|
await this.page.keyboard.press('Control+Space');
|
|
}
|
|
|
|
/**
|
|
* Wait for the suggestions widget to be visible
|
|
* @param {number} timeout - Timeout in milliseconds (default 5000)
|
|
* @returns {Promise<import('@playwright/test').Locator>}
|
|
*/
|
|
async waitForSuggestions(timeout = 5000) {
|
|
const suggestWidget = this.page.locator(this.selectors.suggestWidget);
|
|
await suggestWidget.waitFor({ state: 'visible', timeout });
|
|
return suggestWidget;
|
|
}
|
|
|
|
/**
|
|
* Check if suggestions widget is visible
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async isSuggestionsVisible() {
|
|
const suggestWidget = this.page.locator(this.selectors.suggestWidget);
|
|
return await suggestWidget.isVisible();
|
|
}
|
|
|
|
/**
|
|
* Get all suggestion labels from the autocomplete widget
|
|
* @param {number} timeout - Timeout to wait for suggestions (default 5000)
|
|
* @returns {Promise<string[]>}
|
|
*/
|
|
async getSuggestionLabels(timeout = 5000) {
|
|
const suggestionRows = this.page.locator(this.selectors.suggestionRows);
|
|
await suggestionRows.first().waitFor({ state: 'visible', timeout });
|
|
|
|
const labels = await suggestionRows
|
|
.locator(this.selectors.suggestionLabel)
|
|
.allTextContents();
|
|
|
|
return labels.map(l => l.trim()).filter(l => l.length > 0);
|
|
}
|
|
|
|
/**
|
|
* Check if suggestions contain a specific value
|
|
* @param {string} value - Value to search for
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async suggestionsContain(value) {
|
|
const labels = await this.getSuggestionLabels();
|
|
return labels.some(label => label.includes(value));
|
|
}
|
|
|
|
/**
|
|
* Select a suggestion by text
|
|
* @param {string} text - Text of the suggestion to select
|
|
*/
|
|
async selectSuggestion(text) {
|
|
const suggestionRow = this.page.locator(
|
|
`${this.selectors.suggestionRows}:has-text("${text}")`
|
|
).first();
|
|
await suggestionRow.click();
|
|
}
|
|
|
|
/**
|
|
* Dismiss the suggestions widget by clicking outside the editor
|
|
*/
|
|
async dismissSuggestions() {
|
|
await this.page.locator('body').click({ position: { x: 10, y: 10 } });
|
|
}
|
|
}
|
|
|
|
module.exports = MonacoEditorHelper;
|