From 36189a421defb0002af15d2f497fa348aa9249cd Mon Sep 17 00:00:00 2001
From: Neha P <144308844+neha00290@users.noreply.github.com>
Date: Mon, 16 Jun 2025 21:37:00 +0530
Subject: [PATCH] test: search partition and pipeline dynamic name tests
(#7172)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
### **User description**
Added test cases to verify:
1. Search Partition:
- Verify search partition and search API calls for histogram query
2. Pipeline Dynamic Stream Names:
- Verify pipeline with dynamic destination name using {dynamicname}
- Verify pipeline with dynamic destination name using
test_{dynamicname}_test with underscores
- Verify pipeline with dynamic destination name using
test{dynamicname}test directly
___
### **PR Type**
Tests, Other
___
### **Description**
Add Playwright E2E tests for dynamic pipeline names
Add Playwright E2E test for search partition histogram query
Update CI workflow to include new tests
___
### **Changes walkthrough** 📝
| Relevant
files |
|---|
| Tests |
pipeline-dynamic.spec.jsAdd pipeline
dynamic stream name tests
tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js
Introduce E2E tests for dynamic pipeline stream names Add
helper functions: login, ingestion, navigation Cover three
dynamic naming scenarios Verify pipeline creation and deletion
|
+175/-0
|
searchparition.spec.jsAdd search
partition histogram tests
tests/ui-testing/playwright-tests/searchparition.spec.js
Add E2E test for search partition histogram query Implement
helpers: login, ingestion, applyQueryButton Validate
_search_partition API response fields Verify matching search
calls per partition
|
+151/-0
|
|
| Configuration
changes |
playwright.ymlInclude new tests in
CI workflow
.github/workflows/playwright.yml
- Include new test files in CI workflow job
|
+3/-1
|
|
___
> Need help?
Type /help how to
... in the comments thread for any questions about PR-Agent
usage.Check out the documentation
for more information.
---
.github/workflows/playwright.yml | 6 +-
tests/ui-testing/pages/logsPage.js | 147 +++++++++++++++++-
tests/ui-testing/pages/pipelinePage.js | 143 ++++++++++++++++-
.../playwright-tests/pipeline-dynamic.spec.js | 107 +++++++++++++
.../playwright-tests/searchpartition.spec.js | 105 +++++++++++++
5 files changed, 495 insertions(+), 13 deletions(-)
create mode 100644 tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js
create mode 100644 tests/ui-testing/playwright-tests/searchpartition.spec.js
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 9d64488191..ee32194889 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -142,8 +142,10 @@ jobs:
"usersOrg.spec.js",
"websocket.spec.js",
"alertsNew.spec.js",
- # "remotepipeline.spec.js",
-
+ "pipeline-dynamic.spec.js",
+ "searchpartition.spec.js"
+ # "remotepipeline.spec.js",
+
]
steps:
diff --git a/tests/ui-testing/pages/logsPage.js b/tests/ui-testing/pages/logsPage.js
index ea84595c82..4a89682479 100644
--- a/tests/ui-testing/pages/logsPage.js
+++ b/tests/ui-testing/pages/logsPage.js
@@ -2,6 +2,7 @@
import { expect } from '@playwright/test';
const { chromium } = require('playwright');
import { dateTimeButtonLocator, relative30SecondsButtonLocator, absoluteTabLocator, Past30SecondsValue } from '../pages/CommonLocator.js';
+import logData from '../cypress/fixtures/log.json';
export class LogsPage {
constructor(page) {
@@ -13,6 +14,8 @@ export class LogsPage {
this.logsMenuItem = page.locator('[data-test="menu-link-\\/logs-item"]');
this.indexDropDown = page.locator('[data-test="logs-search-index-list"]').getByText('arrow_drop_down');
this.streamToggle = page.locator('[data-test="log-search-index-list-stream-toggle-default"] div');
+ this.exploreButton = page.getByRole('button', { name: 'Explore' });
+ this.timestampColumnMenu = page.locator('[data-test="log-table-column-1-_timestamp"] [data-test="table-row-expand-menu"]');
this.filterMessage = page.locator('div:has-text("info Adjust filter parameters and click \'Run query\'")');
@@ -47,6 +50,15 @@ export class LogsPage {
this.profileButton = page.locator('[data-test="header-my-account-profile-icon"]');
this.signOutButton = page.getByText('Sign Out');
+ // Search Partition locators
+ this.searchPartitionButton = page.locator('[data-test="logs-search-partition-btn"]');
+ this.partitionDropdown = page.locator('[data-test="logs-search-partition-dropdown"]');
+ this.partitionOption = (option) => page.getByRole('option', { name: option });
+ this.partitionValueInput = page.locator('[data-test="logs-search-partition-value-input"]');
+ this.partitionApplyButton = page.locator('[data-test="logs-search-partition-apply-btn"]');
+ this.partitionClearButton = page.locator('[data-test="logs-search-partition-clear-btn"]');
+ this.partitionActiveIndicator = page.locator('[data-test="logs-search-partition-active"]');
+
}
async selectOrganization() {
@@ -205,15 +217,12 @@ export class LogsPage {
}
async applyQuery() {
- const search = this.page.waitForResponse("**/api/default/_search**");
+ const search = this.page.waitForResponse(logData.applyQuery);
await this.page.waitForTimeout(3000);
- await this.page.locator(this.queryButton).click({ force: true });
- try {
- const response = await search;
- await expect.poll(async () => response.status()).toBe(200);
- } catch (error) {
- throw new Error(`Failed to get response: ${error.message}`);
- }
+ await this.page.locator("[data-test='logs-search-bar-refresh-btn']").click({
+ force: true,
+ });
+ await expect.poll(async () => (await search).status()).toBe(200);
}
async applyQueryButton(expectedUrl) {
@@ -586,4 +595,126 @@ async executeQueryWithErrorHandling() {
await this.page.locator('[data-test="logs-search-result-detail-undefined"]').click();
}
+// New methods for histogram query test
+async executeHistogramQuery(query) {
+ await this.page.locator('[data-test="logs-search-bar-query-editor"] > .monaco-editor').click();
+ await this.page.keyboard.type(query);
+ await this.page.waitForTimeout(2000);
+}
+
+async toggleHistogramAndExecute() {
+ await this.page.locator('[data-test="logs-search-bar-show-histogram-toggle-btn"] div').nth(2).click();
+ await this.page.waitForTimeout(1000);
+ await this.page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
+}
+
+async verifyHistogramState() {
+ const isHistogramOff = await this.page.locator('[data-test="logs-search-bar-show-histogram-toggle-btn"]')
+ .evaluate(el => el.getAttribute('aria-checked') === 'false');
+ expect(isHistogramOff).toBeTruthy();
+}
+
+async verifySearchPartitionResponse() {
+ const searchPartitionPromise = this.page.waitForResponse(response =>
+ response.url().includes('/api/default/_search_partition') &&
+ response.request().method() === 'POST'
+ );
+
+ const searchPartitionResponse = await searchPartitionPromise;
+ const searchPartitionData = await searchPartitionResponse.json();
+
+ expect(searchPartitionData).toHaveProperty('partitions');
+ expect(searchPartitionData).toHaveProperty('histogram_interval');
+ expect(searchPartitionData).toHaveProperty('order_by', 'asc');
+
+ return searchPartitionData;
+}
+
+async captureSearchCalls() {
+ const searchCalls = [];
+ this.page.on('response', async response => {
+ if (response.url().includes('/api/default/_search') &&
+ response.request().method() === 'POST') {
+ const requestData = await response.request().postDataJSON();
+ searchCalls.push({
+ start_time: requestData.query.start_time,
+ end_time: requestData.query.end_time,
+ sql: requestData.query.sql
+ });
+ }
+ });
+ await this.page.waitForTimeout(2000);
+ return searchCalls;
+}
+
+async verifyStreamingModeResponse() {
+ const searchPromise = this.page.waitForResponse(response =>
+ response.url().includes('/api/default/_search') &&
+ response.request().method() === 'POST'
+ );
+
+ const searchResponse = await searchPromise;
+ expect(searchResponse.status()).toBe(200);
+
+ const searchData = await searchResponse.json();
+ expect(searchData).toBeDefined();
+ expect(searchData.hits).toBeDefined();
+}
+
+async clickExplore() {
+ try {
+ // Wait for the explore button to be visible and clickable
+ await this.exploreButton.first().waitFor({ state: 'visible', timeout: 10000 });
+ await this.exploreButton.first().waitFor({ state: 'attached', timeout: 10000 });
+
+ // Click the button and wait for any network requests to complete
+ await Promise.all([
+ this.page.waitForLoadState('networkidle'),
+ this.exploreButton.first().click()
+ ]);
+
+ // Wait for the results to load
+ await this.page.waitForTimeout(3000);
+ } catch (error) {
+ console.error('Error in clickExplore:', error);
+ throw error;
+ }
+}
+
+async openTimestampMenu() {
+ try {
+ // Wait for the table to be visible first
+ await this.page.waitForSelector('[data-test="logs-search-result-logs-table"]', { state: 'visible', timeout: 10000 });
+
+ // Wait for the timestamp column to be visible
+ await this.page.waitForSelector('[data-test="log-table-column-1-_timestamp"]', { state: 'visible', timeout: 10000 });
+
+ // Wait for the menu button to be visible and clickable
+ await this.timestampColumnMenu.waitFor({ state: 'visible', timeout: 10000 });
+
+ // Ensure the menu button is in viewport
+ await this.timestampColumnMenu.scrollIntoViewIfNeeded();
+
+ // Click the menu and wait for any network requests to complete
+ await Promise.all([
+ this.page.waitForLoadState('networkidle'),
+ this.timestampColumnMenu.click({ force: true })
+ ]);
+
+ // Wait for the menu to open
+ await this.page.waitForTimeout(1000);
+ } catch (error) {
+ console.error('Error in openTimestampMenu:', error);
+ // Try alternative approach if first attempt fails
+ try {
+ await this.page.waitForTimeout(2000); // Give more time for the table to load
+ await this.timestampColumnMenu.click({ force: true });
+ await this.page.waitForTimeout(1000);
+ } catch (retryError) {
+ console.error('Error in openTimestampMenu retry:', retryError);
+ throw retryError;
+ }
+ }
+}
+
}
diff --git a/tests/ui-testing/pages/pipelinePage.js b/tests/ui-testing/pages/pipelinePage.js
index 833b0d16eb..7c8657f9b4 100644
--- a/tests/ui-testing/pages/pipelinePage.js
+++ b/tests/ui-testing/pages/pipelinePage.js
@@ -84,9 +84,18 @@ class PipelinePage {
this.headerKeyInput = page.locator('[data-test="add-destination-header--key-input"]');
this.headerValueInput = page.locator('[data-test="add-destination-header-Authorization-value-input"]');
this.submitButton = page.locator('[data-test="add-destination-submit-btn"]');
-
-
-
+ this.streamsMenuItem = page.locator('[data-test="menu-link-\\/streams-item"]');
+ this.refreshStatsButton = page.locator('[data-test="log-stream-refresh-stats-btn"]');
+ this.searchStreamInput = page.getByPlaceholder('Search Stream');
+ this.exploreButton = page.getByRole('button', { name: 'Explore' });
+ this.timestampColumnMenu = page.locator('[data-test="log-table-column-1-_timestamp"] [data-test="table-row-expand-menu"]');
+ this.streamIcon = page.getByRole("img", { name: "Stream", exact: true });
+ this.outputStreamIcon = page.getByRole("img", { name: "Output Stream" });
+ this.containsOption = page.getByText("Contains", { exact: true });
+ this.valueInput = page.getByPlaceholder("Value");
+ this.kubernetesContainerNameOption = page.getByRole("option", { name: "kubernetes_container_name" });
+ this.conditionText = page.getByRole("button", { name: "kubernetes_container_name" });
+ this.pipelineSavedMessage = page.getByText('Pipeline saved successfully');
}
async openPipelineMenu() {
@@ -403,6 +412,134 @@ async createRemoteDestination(randomNodeName, AuthorizationToken) {
await this.submitButton.click();
}
+async navigateToStreams() {
+ await this.streamsMenuItem.click();
+ await this.page.waitForTimeout(1000);
+}
+
+async refreshStreamStats() {
+ await this.refreshStatsButton.click();
+ await this.page.waitForTimeout(1000);
+}
+
+async searchStream(streamName) {
+ await this.searchStreamInput.click();
+ await this.searchStreamInput.fill(streamName);
+ await this.page.waitForTimeout(1000);
+}
+
+async clickExplore() {
+ await this.exploreButton.first().click();
+ await this.page.waitForTimeout(3000);
+}
+
+async openTimestampMenu() {
+ await this.timestampColumnMenu.click();
+}
+
+async navigateToPipeline() {
+ await this.page.locator('[data-test="menu-link-\\/pipeline-item"]').click();
+}
+
+async setupContainerNameCondition() {
+ await this.editButton.hover();
+ await this.streamIcon.click();
+ await this.columnInput.click();
+ await this.columnInput.fill("container_name");
+ await this.page.waitForTimeout(1000);
+ await this.kubernetesContainerNameOption.click();
+ await this.page.locator("div:nth-child(2) > div:nth-child(2) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native").click();
+ await this.containsOption.click();
+ await this.valueInput.click();
+ await this.valueInput.fill("ziox");
+ await this.saveCondition();
+ await this.page.waitForTimeout(2000);
+ await this.conditionText.hover();
+}
+
+async setupDestinationStream(dynamicDestinationName) {
+ await this.outputStreamIcon.click();
+ await this.streamNameInput.click();
+ await this.page.waitForTimeout(1000);
+ await this.streamNameInput.fill(dynamicDestinationName);
+ await this.page.waitForTimeout(1000);
+ await this.clickInputNodeStreamSave();
+}
+
+async waitForPipelineSaved() {
+ await this.pipelineSavedMessage.waitFor({ state: "visible" });
+}
+
+async exploreStreamAndNavigateToPipeline(streamName) {
+ // Ingestion for the specific stream
+ const orgId = process.env["ORGNAME"];
+ const headers = {
+ "Authorization": `Basic ${Buffer.from(
+ `${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
+ ).toString('base64')}`,
+ "Content-Type": "application/json",
+ };
+
+ const url = `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`;
+ await this.page.evaluate(async ({ url, headers, logsdata }) => {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: headers,
+ body: JSON.stringify(logsdata)
+ });
+ return await response.json();
+ }, {
+ url: url,
+ headers: headers,
+ logsdata: require('../../test-data/logs_data.json')
+ });
+ await this.page.waitForTimeout(2000);
+
+ // Navigate and explore
+ await this.navigateToStreams();
+ await this.refreshStreamStats();
+ await this.searchStream(streamName);
+ await this.clickExplore();
+ await this.openTimestampMenu();
+ await this.navigateToPipeline();
+}
+
+async setupPipelineWithSourceStream(sourceStream) {
+ await this.openPipelineMenu();
+ await this.page.waitForTimeout(1000);
+ await this.addPipeline();
+ await this.selectStream();
+ await this.dragStreamToTarget(this.streamButton);
+ await this.selectLogs();
+ await this.enterStreamName(sourceStream);
+ await this.page.waitForTimeout(2000);
+ await this.page.getByRole("option", { name: sourceStream, exact: true }).click();
+ await this.saveInputNodeStream();
+ await this.page.waitForTimeout(2000);
+ await this.page.locator("button").filter({ hasText: "delete" }).nth(1).click();
+ await this.page.locator('[data-test="confirm-button"]').click();
+}
+
+async createAndVerifyPipeline(expectedStreamName, sourceStream) {
+ const pipelineName = `pipeline-${Math.random().toString(36).substring(7)}`;
+ await this.enterPipelineName(pipelineName);
+ await this.savePipeline();
+ await this.waitForPipelineSaved();
+
+ // Verify the dynamic destination stream exists
+ await this.exploreStreamAndNavigateToPipeline(expectedStreamName);
+
+ // Verify pipeline creation and cleanup
+ await this.searchPipeline(pipelineName);
+ await this.page.waitForTimeout(1000);
+ const deletePipelineButton = this.page.locator(
+ `[data-test="pipeline-list-${pipelineName}-delete-pipeline"]`
+ );
+ await deletePipelineButton.waitFor({ state: "visible" });
+ await deletePipelineButton.click();
+ await this.confirmDeletePipeline();
+ await this.verifyPipelineDeleted();
+}
}
export default PipelinePage;
\ No newline at end of file
diff --git a/tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js b/tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js
new file mode 100644
index 0000000000..aa6173d9f8
--- /dev/null
+++ b/tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js
@@ -0,0 +1,107 @@
+import { test, expect } from '@playwright/test';
+import PipelinePage from '../pages/pipelinePage.js';
+import logsdata from '../../test-data/logs_data.json';
+import { getHeaders, getIngestionUrl, sendRequest } from '../utils/apiUtils.js';
+
+test.describe('Pipeline Dynamic Stream Names', () => {
+ let page;
+ let pipelinePage;
+
+ test.beforeAll(async ({ browser }) => {
+ page = await browser.newPage();
+ pipelinePage = new PipelinePage(page);
+
+ // Login - happens only once before all tests
+ await page.goto(process.env["ZO_BASE_URL"]);
+ if (await page.getByText('Login as internal user').isVisible()) {
+ await page.getByText('Login as internal user').click();
+ }
+ await page.waitForTimeout(1000);
+ await page
+ .locator('[data-cy="login-user-id"]')
+ .fill(process.env["ZO_ROOT_USER_EMAIL"]);
+ await page
+ .locator('[data-cy="login-password"]')
+ .fill(process.env["ZO_ROOT_USER_PASSWORD"]);
+ await page.locator('[data-cy="login-sign-in"]').click();
+ // Wait for login to complete and page to be ready
+ await page.waitForTimeout(4000);
+ });
+
+ test.beforeEach(async () => {
+ // Ensure page is still open
+ if (page.isClosed()) {
+ throw new Error('Page was closed unexpectedly');
+ }
+
+ // Ingestion - happens before each test
+ const orgId = process.env["ORGNAME"];
+ const streamNames = ["e2e_automate", "e2e_automate1", "e2e_automate2", "e2e_automate3"];
+ const headers = getHeaders();
+
+ for(const streamName of streamNames) {
+ const url = getIngestionUrl(orgId, streamName);
+ const response = await sendRequest(page, url, logsdata, headers);
+ console.log(response);
+ }
+ await page.waitForTimeout(2000);
+ });
+
+ test.afterEach(async () => {
+ // Add a small wait after each test to ensure operations are complete
+ await page.waitForTimeout(1000);
+ });
+
+ test.afterAll(async () => {
+ // Add a wait before closing to ensure all operations are complete
+ await page.waitForTimeout(2000);
+ if (!page.isClosed()) {
+ await page.close();
+ }
+ });
+
+ test('Verify pipeline with dynamic destination name using kubernetes_container_name', async () => {
+ // Navigate to stream and pipeline
+ await pipelinePage.exploreStreamAndNavigateToPipeline('e2e_automate1');
+ await page.waitForLoadState('networkidle');
+
+ // Setup source stream
+ await pipelinePage.setupPipelineWithSourceStream('e2e_automate1');
+ await page.waitForLoadState('networkidle');
+
+ // Setup container name condition
+ await pipelinePage.setupContainerNameCondition();
+ await page.waitForLoadState('networkidle');
+
+ // Setup destination stream
+ await pipelinePage.setupDestinationStream('dynamic_ziox_dynamic');
+ await page.waitForLoadState('networkidle');
+
+ // Create and verify pipeline
+ await pipelinePage.createAndVerifyPipeline('dynamic_ziox_dynamic', 'e2e_automate1');
+ });
+
+ test('Verify pipeline with dynamic destination name using kubernetes_container_name with underscores', async () => {
+ await pipelinePage.exploreStreamAndNavigateToPipeline('e2e_automate2');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupPipelineWithSourceStream('e2e_automate2');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupContainerNameCondition();
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupDestinationStream('dynamic_ziox_dynamic');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.createAndVerifyPipeline('dynamic_ziox_dynamic', 'e2e_automate2');
+ });
+
+ test('Verify pipeline with dynamic destination name using kubernetes_container_name directly', async () => {
+ await pipelinePage.exploreStreamAndNavigateToPipeline('e2e_automate3');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupPipelineWithSourceStream('e2e_automate3');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupContainerNameCondition();
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.setupDestinationStream('ziox');
+ await page.waitForLoadState('networkidle');
+ await pipelinePage.createAndVerifyPipeline('ziox', 'e2e_automate3');
+ });
+});
\ No newline at end of file
diff --git a/tests/ui-testing/playwright-tests/searchpartition.spec.js b/tests/ui-testing/playwright-tests/searchpartition.spec.js
new file mode 100644
index 0000000000..4bcefaa125
--- /dev/null
+++ b/tests/ui-testing/playwright-tests/searchpartition.spec.js
@@ -0,0 +1,105 @@
+import { test, expect } from './baseFixtures.js';
+import logData from "../cypress/fixtures/log.json";
+import logsdata from "../../test-data/logs_data.json";
+import { LogsPage } from '../pages/logsPage.js';
+
+test.describe.configure({ mode: "parallel" });
+
+// Helper functions
+async function setupTest(page) {
+ const logsPage = new LogsPage(page);
+ await page.goto(process.env["ZO_BASE_URL"]);
+
+ // Login
+ if (await page.getByText('Login as internal user').isVisible()) {
+ await page.getByText('Login as internal user').click();
+ }
+ await page.waitForTimeout(1000);
+ await page.locator('[data-cy="login-user-id"]').fill(process.env["ZO_ROOT_USER_EMAIL"]);
+ await page.locator('[data-cy="login-password"]').fill(process.env["ZO_ROOT_USER_PASSWORD"]);
+ await page.locator('[data-cy="login-sign-in"]').click();
+ await page.waitForTimeout(4000);
+
+ return logsPage;
+}
+
+async function ingestTestData(page) {
+ const orgId = process.env["ORGNAME"];
+ const streamName = "e2e_automate";
+ const basicAuthCredentials = Buffer.from(
+ `${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
+ ).toString('base64');
+
+ const headers = {
+ "Authorization": `Basic ${basicAuthCredentials}`,
+ "Content-Type": "application/json",
+ };
+
+ await page.evaluate(async ({ url, headers, orgId, streamName, logsdata }) => {
+ const fetchResponse = await fetch(`${url}/api/${orgId}/${streamName}/_json`, {
+ method: 'POST',
+ headers: headers,
+ body: JSON.stringify(logsdata)
+ });
+ return await fetchResponse.json();
+ }, {
+ url: process.env.INGESTION_URL,
+ headers: headers,
+ orgId: orgId,
+ streamName: streamName,
+ logsdata: logsdata
+ });
+}
+
+async function applyQuery(page) {
+ const search = page.waitForResponse(logData.applyQuery);
+ await page.waitForTimeout(3000);
+ await page.locator("[data-test='logs-search-bar-refresh-btn']").click({ force: true });
+ await expect.poll(async () => (await search).status()).toBe(200);
+}
+
+test.describe("Search Partition Tests", () => {
+ let logsPage;
+
+ test.beforeEach(async ({ page }) => {
+ logsPage = await setupTest(page);
+ await ingestTestData(page);
+ await page.waitForTimeout(2000);
+ await page.goto(`${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`);
+ await logsPage.selectStreamAndStreamTypeForLogs("e2e_automate");
+ await applyQuery(page);
+ });
+
+ test("should verify search partition and search API calls for histogram query", async ({ page }) => {
+ const isStreamingEnabled = process.env["ZO_STREAMING_ENABLED"] === "true";
+ const histogramQuery = `SELECT histogram(_timestamp) as "x_axis_1", count(distinct(kubernetes_container_name)) as "y_axis_1" FROM "e2e_automate" GROUP BY x_axis_1 ORDER BY x_axis_1 ASC`;
+
+ // Setup and execute query
+ await logsPage.executeHistogramQuery(histogramQuery);
+ await logsPage.toggleHistogramAndExecute();
+
+ if (!isStreamingEnabled) {
+ // Verify search partition response
+ const searchPartitionData = await logsPage.verifySearchPartitionResponse();
+ const searchCalls = await logsPage.captureSearchCalls();
+
+ expect(searchCalls.length).toBe(searchPartitionData.partitions.length);
+
+ // Verify each partition has a matching search call
+ for (const partition of searchPartitionData.partitions) {
+ const matchingCall = searchCalls.find(call =>
+ call.start_time === partition[0] &&
+ call.end_time === partition[1]
+ );
+ expect(matchingCall).toBeTruthy();
+ expect(matchingCall.sql).toContain('SELECT histogram(_timestamp');
+ }
+ } else {
+ await logsPage.verifyStreamingModeResponse();
+ }
+
+ // Verify histogram state
+ await logsPage.verifyHistogramState();
+ });
+});
+