mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 00:08:55 +00:00
test: regression coverage for 4 closed Needs-Automation issues (#14873)
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"browser": "chrome",
|
||||
"run_files": [
|
||||
"logs-bugs.spec.js",
|
||||
"logs-query-editor-resize-bounds.spec.js"
|
||||
"logs-query-editor-resize-bounds.spec.js",
|
||||
"logs-result-display.spec.js"
|
||||
],
|
||||
"disabled": []
|
||||
},
|
||||
@@ -134,7 +135,8 @@
|
||||
"logs-vrl-column-cell-actions.spec.js",
|
||||
"logs-reset-filters.spec.js",
|
||||
"logs-stream-name-case.spec.js",
|
||||
"logs-no-stream-prompt.spec.js"
|
||||
"logs-no-stream-prompt.spec.js",
|
||||
"logs-field-actions.spec.js"
|
||||
],
|
||||
"disabled": []
|
||||
},
|
||||
|
||||
@@ -506,6 +506,16 @@ export class LogsPage {
|
||||
this.resultsProgressBar = '[data-test="logs-results-progress"]';
|
||||
this.queryPlanDialog = '[data-test="query-plan-dialog"]';
|
||||
this.queryEditorSplitter = '[role="separator"][aria-orientation="horizontal"]';
|
||||
// useLogsHighlighter marks FTS matches with .log-highlighted inside the result cell.
|
||||
this.highlightedMatch = '[data-test="logs-search-result-logs-table"] .log-highlighted';
|
||||
// The "=" icon is revealed on hover over its sidebar field row.
|
||||
this.fieldListItemPrefix = 'logs-field-list-item-';
|
||||
this.logDetailRowPrefix = 'log-detail-row-';
|
||||
this.fieldEqualsButton = (field) => `[data-test="log-search-index-list-filter-${field}-field-btn"]`;
|
||||
this.logDetailRow = (field) => `[data-test="log-detail-row-${field}"]`;
|
||||
this.logDetailFieldMenuTrigger = '[data-test="log-details-include-exclude-field-btn"]';
|
||||
this.logDetailAddFieldItem = '[data-test="log-details-add-field-btn"]';
|
||||
this.tableExpandCell = '[data-test="logs-search-result-logs-table"] tbody tr[data-test="o2-table-row-0"] td[data-test="o2-table-expand-cell"]';
|
||||
this.tableRowExpandMenu = '[data-test^="o2-table-expand-"]';
|
||||
this.logDetailsIncludeExcludeBtn = '[data-test="log-details-include-exclude-field-btn"]';
|
||||
this.timestampCells = '[data-test="o2-table-cell-_timestamp"]';
|
||||
@@ -2023,6 +2033,89 @@ export class LogsPage {
|
||||
await expect(searchResult).toContainText(expectedPattern, { timeout: 15000 });
|
||||
}
|
||||
|
||||
/** Choose a page size without asserting the banner, for tests that assert rows instead. */
|
||||
async selectRecordsPerPage(size) {
|
||||
const dropdown = this.page.locator(this.recordsPerPageDropdown);
|
||||
await dropdown.waitFor({ state: 'visible', timeout: 15000 });
|
||||
await dropdown.click({ force: true });
|
||||
const option = this.page.locator(this.recordsPerPageOption(size)).first();
|
||||
await option.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await option.click({ force: true });
|
||||
await expect(this.page.locator(this.paginationRowCountTitle))
|
||||
.toContainText(`1 to ${size}`, { timeout: 30000 });
|
||||
}
|
||||
|
||||
/** The "1 to N out of M events" banner, used to compare the claim against rendered rows. */
|
||||
async getResultTitleText() {
|
||||
return (await this.page.locator(this.paginationRowCountTitle).first().innerText()).trim();
|
||||
}
|
||||
|
||||
/** #9542: match_all matches must render highlighted, not plain. */
|
||||
async expectHighlightedMatchesRendered() {
|
||||
await expect(this.page.locator(this.highlightedMatch).first()).toBeVisible({ timeout: 20000 });
|
||||
}
|
||||
|
||||
async getHighlightedMatchCount() {
|
||||
return await this.page.locator(this.highlightedMatch).count();
|
||||
}
|
||||
|
||||
/** Sidebar fields that offer an "=" filter, in render order; the set varies by dataset. */
|
||||
async getFilterableSidebarFields(count = 2) {
|
||||
const fields = await this.page
|
||||
.locator(`[data-test^="${this.fieldListItemPrefix}"]`)
|
||||
.evaluateAll((items, prefix) =>
|
||||
items.map((i) => (i.getAttribute('data-test') || '').replace(prefix, '')),
|
||||
this.fieldListItemPrefix);
|
||||
const usable = fields.filter((f) => f && f !== '_timestamp');
|
||||
if (usable.length < count) {
|
||||
throw new Error(`need ${count} filterable sidebar fields, saw: ${fields.join(', ')}`);
|
||||
}
|
||||
return usable.slice(0, count);
|
||||
}
|
||||
|
||||
/** Click a sidebar field's "=" icon; it only renders while its row is hovered. */
|
||||
async addEqualsFilterForField(field) {
|
||||
await this.page.locator(this.fieldListItem(field)).first().hover();
|
||||
const button = this.page.locator(this.fieldEqualsButton(field)).first();
|
||||
await button.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await button.click();
|
||||
}
|
||||
|
||||
async expandFirstResultRow() {
|
||||
await this.page.locator(this.tableExpandCell).click();
|
||||
await expect(this.page.locator(this.logDetailRow('_timestamp'))).toBeVisible({ timeout: 15000 });
|
||||
}
|
||||
|
||||
/** First expanded-row field that offers the add/remove action; timestamp never does. */
|
||||
async getFirstActionableDetailField() {
|
||||
const fields = await this.page
|
||||
.locator(`[data-test^="${this.logDetailRowPrefix}"]`)
|
||||
.evaluateAll((rows, prefix) =>
|
||||
rows.map((r) => (r.getAttribute('data-test') || '').replace(prefix, '')),
|
||||
this.logDetailRowPrefix);
|
||||
const field = fields.find((f) => f && f !== '_timestamp');
|
||||
if (!field) throw new Error(`no actionable field in the expanded row, saw: ${fields.join(', ')}`);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open one expanded-row field's action menu and read its add/remove label.
|
||||
* The menu is scoped to its own log-detail-row: the timestamp column renders no
|
||||
* add item at all, so an unscoped lookup finds nothing.
|
||||
*/
|
||||
async getAddOrRemoveFieldLabel(field) {
|
||||
const row = this.page.locator(this.logDetailRow(field));
|
||||
await row.hover();
|
||||
await row.locator(this.logDetailFieldMenuTrigger).first().click();
|
||||
const item = this.page.locator(this.logDetailAddFieldItem).first();
|
||||
await item.waitFor({ state: 'visible', timeout: 10000 });
|
||||
return (await item.innerText()).trim();
|
||||
}
|
||||
|
||||
async clickAddOrRemoveFieldItem() {
|
||||
await this.page.locator(this.logDetailAddFieldItem).first().click();
|
||||
}
|
||||
|
||||
async pageNotVisible() {
|
||||
// When LIMIT caps results below per-page floor, OSelect records-per-page dropdown hides.
|
||||
const recordsDropdown = this.page.locator(this.recordsPerPageDropdown);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
const { test, expect, navigateToBase } = require('../../utils/enhanced-baseFixtures.js');
|
||||
const testLogger = require('../../utils/test-logger.js');
|
||||
const PageManager = require('../../../pages/page-manager.js');
|
||||
const { ingestTestData } = require('../../utils/data-ingestion.js');
|
||||
|
||||
const STREAM = 'e2e_automate';
|
||||
|
||||
test.describe("Logs Field Actions Regression", () => {
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
let pm;
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
await navigateToBase(page);
|
||||
pm = new PageManager(page);
|
||||
try {
|
||||
await ingestTestData(page);
|
||||
} catch (error) {
|
||||
testLogger.warn(`Data ingestion skipped: ${error.message}`);
|
||||
}
|
||||
await pm.logsPage.selectStream(STREAM);
|
||||
await pm.logsPage.clickRefreshButton();
|
||||
await pm.logsPage.expectResultsGridSettledWithRows();
|
||||
});
|
||||
|
||||
test("a second field filter is ANDed onto the query, not substituted @bug-11988 @P1 @regression @logsRegression", async () => {
|
||||
// Which fields the sidebar offers varies by dataset, so take them as rendered.
|
||||
const [firstField, secondField] = await pm.logsPage.getFilterableSidebarFields(2);
|
||||
testLogger.info(`Using sidebar fields: ${firstField}, ${secondField}`);
|
||||
|
||||
await pm.logsPage.addEqualsFilterForField(firstField);
|
||||
const afterFirst = await pm.logsPage.getQueryEditorText();
|
||||
testLogger.info(`After first filter: ${afterFirst}`);
|
||||
expect(afterFirst, 'first field filter must reach the editor').toContain(firstField);
|
||||
|
||||
await pm.logsPage.addEqualsFilterForField(secondField);
|
||||
const afterSecond = await pm.logsPage.getQueryEditorText();
|
||||
testLogger.info(`After second filter: ${afterSecond}`);
|
||||
|
||||
// The defect replaced the first field, so the first surviving is the contract.
|
||||
expect(afterSecond, 'the first field must survive the second').toContain(firstField);
|
||||
expect(afterSecond, 'the second field must be added').toContain(secondField);
|
||||
expect(afterSecond.toLowerCase(), 'the two filters must be combined').toContain(' and ');
|
||||
});
|
||||
|
||||
test("the field menu label reflects whether the field is already in the table @bug-3347 @P3 @regression @logsRegression", async () => {
|
||||
await pm.logsPage.expandFirstResultRow();
|
||||
// Which fields a record carries varies, so pick one the row actually renders.
|
||||
const field = await pm.logsPage.getFirstActionableDetailField();
|
||||
testLogger.info(`Using expanded-row field: ${field}`);
|
||||
|
||||
const beforeAdd = await pm.logsPage.getAddOrRemoveFieldLabel(field);
|
||||
testLogger.info(`Label before adding: ${beforeAdd}`);
|
||||
expect(beforeAdd.toLowerCase(), 'menu should offer to add the field').toContain('add');
|
||||
|
||||
await pm.logsPage.clickAddOrRemoveFieldItem();
|
||||
|
||||
const afterAdd = await pm.logsPage.getAddOrRemoveFieldLabel(field);
|
||||
testLogger.info(`Label after adding: ${afterAdd}`);
|
||||
// The defect was a static label, so the flip to remove is what must be asserted.
|
||||
expect(afterAdd.toLowerCase(), 'menu should offer to remove an already-added field')
|
||||
.toContain('remove');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
const { test, expect, navigateToBase } = require('../../utils/enhanced-baseFixtures.js');
|
||||
const testLogger = require('../../utils/test-logger.js');
|
||||
const PageManager = require('../../../pages/page-manager.js');
|
||||
const { ingestTestData } = require('../../utils/data-ingestion.js');
|
||||
|
||||
const STREAM = 'e2e_automate';
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
test.describe("Logs Result Display Regression", () => {
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
let pm;
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
await navigateToBase(page);
|
||||
pm = new PageManager(page);
|
||||
try {
|
||||
await ingestTestData(page);
|
||||
} catch (error) {
|
||||
testLogger.warn(`Data ingestion skipped: ${error.message}`);
|
||||
}
|
||||
await pm.logsPage.selectStream(STREAM);
|
||||
});
|
||||
|
||||
test("match_all matches render highlighted @bug-9542 @P0 @regression @logsRegression", async () => {
|
||||
await pm.logsPage.enableSqlModeIfNeeded();
|
||||
await pm.logsPage.setQueryEditorContent(
|
||||
`SELECT * FROM "${STREAM}" WHERE match_all('ingester')`,
|
||||
);
|
||||
await pm.logsPage.clickRefreshButton();
|
||||
await pm.logsPage.expectResultsGridSettledWithRows();
|
||||
|
||||
// The defect was matches rendering plain, so the highlight markup is the contract.
|
||||
await pm.logsPage.expectHighlightedMatchesRendered();
|
||||
const highlights = await pm.logsPage.getHighlightedMatchCount();
|
||||
testLogger.info(`Highlighted match nodes: ${highlights}`);
|
||||
expect(highlights, 'match_all results must carry highlight markup').toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("first page renders a full page of results @bug-10643 @P1 @regression @logsRegression", async () => {
|
||||
await pm.logsPage.clickRefreshButton();
|
||||
await pm.logsPage.expectResultsGridSettledWithRows();
|
||||
|
||||
await pm.logsPage.selectRecordsPerPage(PAGE_SIZE);
|
||||
|
||||
const title = await pm.logsPage.getResultTitleText();
|
||||
const rendered = await pm.logsPage.getLogRowCount();
|
||||
testLogger.info(`Banner: ${title.replace(/\n/g, ' | ')} | rendered rows: ${rendered}`);
|
||||
|
||||
// The defect was page 1 rendering fewer rows than the banner claimed (47 of 100).
|
||||
expect(title, 'banner must claim a full first page').toContain(`1 to ${PAGE_SIZE}`);
|
||||
expect(rendered, 'rendered rows must match the page size the banner claims')
|
||||
.toBe(PAGE_SIZE);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user