From fab9cabace901d027a0673dfa29b19933eed69fc Mon Sep 17 00:00:00 2001 From: Neha P <144308844+neha00290@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:12:38 +0000 Subject: [PATCH] test: regression coverage for 4 closed Needs-Automation issues (#14873) --- .../ci-matrix/ci_matrix_regression.json | 6 +- tests/ui-testing/pages/logsPages/logsPage.js | 93 +++++++++++++++++++ .../Logs/logs-field-actions.spec.js | 64 +++++++++++++ .../Logs/logs-result-display.spec.js | 55 +++++++++++ 4 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-field-actions.spec.js create mode 100644 tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-result-display.spec.js diff --git a/tests/ui-testing/ci-matrix/ci_matrix_regression.json b/tests/ui-testing/ci-matrix/ci_matrix_regression.json index 310c835e01..a8de3a13da 100644 --- a/tests/ui-testing/ci-matrix/ci_matrix_regression.json +++ b/tests/ui-testing/ci-matrix/ci_matrix_regression.json @@ -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": [] }, diff --git a/tests/ui-testing/pages/logsPages/logsPage.js b/tests/ui-testing/pages/logsPages/logsPage.js index 349c981101..72143d5730 100644 --- a/tests/ui-testing/pages/logsPages/logsPage.js +++ b/tests/ui-testing/pages/logsPages/logsPage.js @@ -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); diff --git a/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-field-actions.spec.js b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-field-actions.spec.js new file mode 100644 index 0000000000..9ac87eb905 --- /dev/null +++ b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-field-actions.spec.js @@ -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'); + }); +}); diff --git a/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-result-display.spec.js b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-result-display.spec.js new file mode 100644 index 0000000000..191ff86ad3 --- /dev/null +++ b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-result-display.spec.js @@ -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); + }); +});