mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +00:00
test: search partition and pipeline dynamic name tests (#7172)
### **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** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><table>
<tr>
<td>
<details>
<summary><strong>pipeline-dynamic.spec.js</strong><dd><code>Add pipeline
dynamic stream name tests</code>
</dd></summary>
<hr>
tests/ui-testing/playwright-tests/pipeline-dynamic.spec.js
<li>Introduce E2E tests for dynamic pipeline stream names<br> <li> Add
helper functions: login, ingestion, navigation<br> <li> Cover three
dynamic naming scenarios<br> <li> Verify pipeline creation and deletion
</details>
</td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7172/files#diff-a661fdc01b51aa8d44953a9b3452cc4407b0e3770309d6541355d251a58c1ace">+175/-0</a>
</td>
</tr>
<tr>
<td>
<details>
<summary><strong>searchparition.spec.js</strong><dd><code>Add search
partition histogram tests</code>
</dd></summary>
<hr>
tests/ui-testing/playwright-tests/searchparition.spec.js
<li>Add E2E test for search partition histogram query<br> <li> Implement
helpers: login, ingestion, applyQueryButton<br> <li> Validate
_search_partition API response fields<br> <li> Verify matching search
calls per partition
</details>
</td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7172/files#diff-d6855518e0bc277da8b3696ac75727cc1fb40b96012b270f12b85f17f5a47434">+151/-0</a>
</td>
</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
<td>
<details>
<summary><strong>playwright.yml</strong><dd><code>Include new tests in
CI workflow</code>
</dd></summary>
<hr>
.github/workflows/playwright.yml
- Include new test files in CI workflow job
</details>
</td>
<td><a
href="https://github.com/openobserve/openobserve/pull/7172/files#diff-7afcd2d8f7b49bda74843f209eefb7b2da45f7e7803bf2e4bd636699b76aa2d3">+3/-1</a>
</td>
</tr>
</table></td></tr></tr></tbody></table>
___
> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user