test: regression coverage for 4 closed Needs-Automation issues (#14873)

This commit is contained in:
Neha P
2026-09-24 16:12:38 +00:00
committed by GitHub
parent 99cab83c91
commit fab9cabace
4 changed files with 216 additions and 2 deletions
@@ -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);
});
});