mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
test(e2e): refactor pipeline condition tests and fix data ingestion timeout (#9528)
## Summary
Refactored pipeline condition tests into 2 new spec files for better
organization and maintainability, fixed data ingestion timeout issue,
and added comprehensive test coverage for pipeline conditions.
## Changes
- Refactored pipeline condition tests into 2 new spec files for better
organization
- Added 14 new comprehensive test cases (7 UI-based + 7 API-based
validation tests)
- Fixed beforeEach timeout issue by reducing data ingestion from 3,800+
to 10 records per stream
- Added cleanup patterns for `e2e_conditions_*` streams to prevent data
accumulation
- Updated apiCleanup.js to include 7 new stream names in pipeline
cleanup
- Removed outdated comments referencing conditional wait logic
- Added page object methods for condition testing in pipelinesPage.js,
logsPage.js, and streamsPage.js
## Test Coverage
### pipeline-conditions.spec.js (7 tests - all passing ✅)
- Basic condition operations with multiple operators (=, !=, Contains,
NotContains, >=)
- Condition groups, nesting, and reordering
- Field validation and error messages
- Complex operator precedence (A OR B AND C)
- Multiple conditions (A, B, C, D)
- Condition node deletion
- Comparison operators validation
### pipeline-conditions-validation.spec.js (7 tests - API-based)
- Operator precedence validation
- Nested groups with AND/OR logic
- Numeric comparisons
- String operators (case-insensitive Contains)
## Performance
- Reduced test execution time from 180s timeout to ~4.6 minutes for full
suite
- 100% pass rate for active UI tests (7/7)
## Files Changed
- `.github/workflows/playwright.yml` - Updated test configuration
- `tests/test-data/data_100.json` - Added test data for validation tests
- `tests/test-data/sample_pipeline_template.json` - Added pipeline
template
- `tests/ui-testing/pages/apiCleanup.js` - Added stream cleanup patterns
- `tests/ui-testing/pages/pipelinesPages/pipelinesPage.js` - Added
condition testing methods
- `tests/ui-testing/pages/logsPages/logsPage.js` - Added data ingestion
helpers
- `tests/ui-testing/pages/streamsPages/streamsPage.js` - Added stream
management helpers
-
`tests/ui-testing/playwright-tests/Pipelines/pipeline-conditions.spec.js`
- Refactored UI tests
-
`tests/ui-testing/playwright-tests/Pipelines/pipeline-conditions-validation.spec.js`
- New API validation tests
- `tests/ui-testing/playwright-tests/cleanup.spec.js` - Added
e2e_conditions_* pattern
## Testing
All 7 UI-based tests passing with 100% pass rate.
This commit is contained in:
@@ -194,6 +194,7 @@ jobs:
|
||||
browser: "chrome"
|
||||
run_files: [
|
||||
"pipeline-conditions.spec.js",
|
||||
"pipeline-conditions-validation.spec.js",
|
||||
"pipelines.spec.js",
|
||||
"pipeline-dynamic.spec.js",
|
||||
"pipeline-core.spec.js",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"pipeline_id": "7402696020770947072",
|
||||
"version": 0,
|
||||
"enabled": true,
|
||||
"org": "default",
|
||||
"name": "test",
|
||||
"description": "",
|
||||
"source": {
|
||||
"source_type": "realtime",
|
||||
"org_id": "default",
|
||||
"stream_name": "default",
|
||||
"stream_type": "logs"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "9aab959a-0d22-42a4-be84-f15488862bfa",
|
||||
"data": {
|
||||
"node_type": "stream",
|
||||
"org_id": "default",
|
||||
"stream_name": "default",
|
||||
"stream_type": "logs"
|
||||
},
|
||||
"position": {
|
||||
"x": 597,
|
||||
"y": 222.5
|
||||
},
|
||||
"io_type": "input"
|
||||
},
|
||||
{
|
||||
"id": "95c8ef15-2b44-4ba7-87c8-efc28b0d7b59",
|
||||
"data": {
|
||||
"node_type": "stream",
|
||||
"org_id": "default",
|
||||
"stream_name": "default",
|
||||
"stream_type": "logs"
|
||||
},
|
||||
"position": {
|
||||
"x": 602.5,
|
||||
"y": 537.5
|
||||
},
|
||||
"io_type": "output"
|
||||
},
|
||||
{
|
||||
"id": "c160107d-ba19-427d-8e9b-95cca5973373",
|
||||
"data": {
|
||||
"node_type": "condition",
|
||||
"version": 2,
|
||||
"conditions": {
|
||||
"filterType": "group",
|
||||
"logicalOperator": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": ">=",
|
||||
"value": "test",
|
||||
"logicalOperator": "OR"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": "contains",
|
||||
"value": "test",
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "",
|
||||
"operator": "=",
|
||||
"value": "",
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "field",
|
||||
"operator": "=",
|
||||
"value": "rrewt",
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
{
|
||||
"filterType": "group",
|
||||
"logicalOperator": "OR",
|
||||
"conditions": [
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": "<=",
|
||||
"value": "test",
|
||||
"logicalOperator": "OR"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": ">",
|
||||
"value": "test",
|
||||
"logicalOperator": "OR"
|
||||
},
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": "<",
|
||||
"value": "test",
|
||||
"logicalOperator": "AND"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"filterType": "group",
|
||||
"logicalOperator": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"filterType": "condition",
|
||||
"column": "code",
|
||||
"operator": "not_contains",
|
||||
"value": "test",
|
||||
"logicalOperator": "OR"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"x": 206.75,
|
||||
"y": 392.5
|
||||
},
|
||||
"io_type": "default"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e9aab959a-0d22-42a4-be84-f15488862bfa-c160107d-ba19-427d-8e9b-95cca5973373",
|
||||
"source": "9aab959a-0d22-42a4-be84-f15488862bfa",
|
||||
"target": "c160107d-ba19-427d-8e9b-95cca5973373",
|
||||
"markerEnd": {
|
||||
"type": "arrowclosed",
|
||||
"width": 20,
|
||||
"height": 20
|
||||
},
|
||||
"type": "custom",
|
||||
"style": {
|
||||
"strokeWidth": 2
|
||||
},
|
||||
"animated": true,
|
||||
"updatable": true
|
||||
},
|
||||
{
|
||||
"id": "ec160107d-ba19-427d-8e9b-95cca5973373-95c8ef15-2b44-4ba7-87c8-efc28b0d7b59",
|
||||
"source": "c160107d-ba19-427d-8e9b-95cca5973373",
|
||||
"target": "95c8ef15-2b44-4ba7-87c8-efc28b0d7b59",
|
||||
"markerEnd": {
|
||||
"type": "arrowclosed",
|
||||
"width": 20,
|
||||
"height": 20
|
||||
},
|
||||
"type": "custom",
|
||||
"style": {
|
||||
"strokeWidth": 2
|
||||
},
|
||||
"animated": true,
|
||||
"updatable": true
|
||||
}
|
||||
],
|
||||
"paused_at": null,
|
||||
"type": "realtime",
|
||||
"stream_name": "default",
|
||||
"stream_type": "logs",
|
||||
"frequency": "--",
|
||||
"period": "--",
|
||||
"cron": "--",
|
||||
"sql_query": "--",
|
||||
"#": 1
|
||||
}
|
||||
@@ -573,7 +573,25 @@ class APICleanup {
|
||||
* Deletes pipelines where source.stream_name matches any of the target streams
|
||||
* @param {Array<string>} streamNames - Array of stream names to match (default: ['e2e_automate', 'e2e_automate1', 'e2e_automate2', 'e2e_automate3'])
|
||||
*/
|
||||
async cleanupPipelines(streamNames = ['e2e_automate', 'e2e_automate1', 'e2e_automate2', 'e2e_automate3']) {
|
||||
async cleanupPipelines(streamNames = [
|
||||
'e2e_automate',
|
||||
'e2e_automate1',
|
||||
'e2e_automate2',
|
||||
'e2e_automate3',
|
||||
'e2e_conditions_validation_precedence_src',
|
||||
'e2e_conditions_validation_nested_and_src',
|
||||
'e2e_conditions_validation_nested_or_src',
|
||||
'e2e_conditions_validation_numeric_src',
|
||||
'e2e_conditions_validation_contains_src',
|
||||
'e2e_conditions_validation_deep_src',
|
||||
'e2e_conditions_basic',
|
||||
'e2e_conditions_groups',
|
||||
'e2e_conditions_validation',
|
||||
'e2e_conditions_precedence',
|
||||
'e2e_conditions_multiple',
|
||||
'e2e_conditions_delete',
|
||||
'e2e_conditions_operators'
|
||||
]) {
|
||||
testLogger.info('Starting pipeline cleanup', { streams: streamNames });
|
||||
|
||||
try {
|
||||
@@ -581,10 +599,61 @@ class APICleanup {
|
||||
const pipelines = await this.fetchPipelines();
|
||||
testLogger.info('Fetched pipelines', { total: pipelines.length });
|
||||
|
||||
// Filter pipelines by source stream name
|
||||
const matchingPipelines = pipelines.filter(p =>
|
||||
p.source && streamNames.includes(p.source.stream_name)
|
||||
);
|
||||
// Filter pipelines by source stream name OR by pattern matching
|
||||
const matchingPipelines = pipelines.filter(p => {
|
||||
if (!p.source) return false;
|
||||
|
||||
// Exact match for stream names
|
||||
if (streamNames.includes(p.source.stream_name)) return true;
|
||||
|
||||
// Pattern match for validation tests (old naming - static names)
|
||||
if (p.source.stream_name && p.source.stream_name.startsWith('e2e_conditions_validation_')) return true;
|
||||
|
||||
// Pattern match for timestamp-based test pipelines (new naming)
|
||||
const sourceStreamPatterns = [
|
||||
/^e2e_precedence_src_\d+$/,
|
||||
/^e2e_multiple_or_src_\d+$/,
|
||||
/^e2e_nested_or_src_\d+$/,
|
||||
/^e2e_numeric_src_\d+$/,
|
||||
/^e2e_multiple_and_src_\d+$/,
|
||||
/^e2e_deep_src_\d+$/,
|
||||
/^e2e_not_operator_src_\d+$/,
|
||||
/^e2e_impossible_src_\d+$/,
|
||||
/^e2e_universal_src_\d+$/,
|
||||
/^e2e_4level_src_\d+$/,
|
||||
/^simple_src_\d+$/,
|
||||
/^manual_debug_src_/,
|
||||
/^manual_verify_src_/
|
||||
];
|
||||
|
||||
if (p.source.stream_name && sourceStreamPatterns.some(pattern => pattern.test(p.source.stream_name))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also match by pipeline name patterns for comprehensive cleanup
|
||||
const pipelineNamePatterns = [
|
||||
/^validation-precedence-\d+$/,
|
||||
/^validation-multiple-or-\d+$/,
|
||||
/^validation-nested-or-\d+$/,
|
||||
/^validation-numeric-\d+$/,
|
||||
/^validation-multiple-and-\d+$/,
|
||||
/^validation-deep-nested-\d+$/,
|
||||
/^validation-not-operator-\d+$/,
|
||||
/^validation-impossible-\d+$/,
|
||||
/^validation-universal-\d+$/,
|
||||
/^validation-4level-\d+$/,
|
||||
/^simple-test-\d+$/,
|
||||
/^or-root-test-\d+$/,
|
||||
/^manual-debug-pipeline-/,
|
||||
/^manual-verify-pipeline-/
|
||||
];
|
||||
|
||||
if (p.name && pipelineNamePatterns.some(pattern => pattern.test(p.name))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
testLogger.info('Found pipelines matching target streams', { count: matchingPipelines.length });
|
||||
|
||||
if (matchingPipelines.length === 0) {
|
||||
@@ -1827,6 +1896,354 @@ class APICleanup {
|
||||
testLogger.error('Complete cascade cleanup failed', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ==========================================
|
||||
* PIPELINE-SPECIFIC API METHODS
|
||||
* ==========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clean conditions for API (removes UI-only fields)
|
||||
* This removes fields like 'id' and 'groupId' that are only used in UI
|
||||
* @param {object} conditions - Condition configuration object with UI fields
|
||||
* @returns {object} Cleaned conditions for API
|
||||
*/
|
||||
cleanConditionsForAPI(conditions) {
|
||||
if (conditions.filterType === "condition") {
|
||||
// Remove UI-only fields: id, groupId
|
||||
// KEEP values field as it's required by the backend
|
||||
const { id, groupId, ...cleaned } = conditions;
|
||||
return cleaned;
|
||||
} else if (conditions.filterType === "group") {
|
||||
// Recursively clean nested conditions
|
||||
const { id, groupId, ...cleaned } = conditions;
|
||||
cleaned.conditions = conditions.conditions.map(c => this.cleanConditionsForAPI(c));
|
||||
return cleaned;
|
||||
}
|
||||
return conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stream via API
|
||||
* @param {string} streamName - Name of the stream to create
|
||||
* @param {string} streamType - Type of stream (logs, metrics, traces)
|
||||
* @returns {Promise<object>} API response
|
||||
*/
|
||||
async createStream(streamName, streamType = 'logs') {
|
||||
const payload = {
|
||||
fields: [],
|
||||
settings: {
|
||||
partition_keys: [],
|
||||
index_fields: [],
|
||||
full_text_search_keys: [],
|
||||
bloom_filter_fields: [],
|
||||
defined_schema_fields: [],
|
||||
data_retention: 14
|
||||
}
|
||||
};
|
||||
|
||||
testLogger.info('Creating stream via API', { streamName, streamType });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/${this.org}/streams/${streamName}?type=${streamType}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 200 && data.code === 200) {
|
||||
testLogger.info('Stream created successfully', { streamName });
|
||||
} else {
|
||||
testLogger.warn('Stream creation returned non-200 or already exists', { streamName, status: response.status, data });
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to create stream', { streamName, error: error.message });
|
||||
return { status: 500, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pipeline via API
|
||||
* @param {string} pipelineName - Name of the pipeline
|
||||
* @param {string} sourceStream - Source stream name
|
||||
* @param {string} destStream - Destination stream name
|
||||
* @param {object} conditions - Condition configuration object
|
||||
* @returns {Promise<object>} API response
|
||||
*/
|
||||
async createPipeline(pipelineName, sourceStream, destStream, conditions) {
|
||||
// Clean conditions to remove UI-only fields
|
||||
const cleanedConditions = this.cleanConditionsForAPI(conditions);
|
||||
testLogger.info('Cleaned conditions', { cleaned: JSON.stringify(cleanedConditions, null, 2) });
|
||||
|
||||
// Generate unique node IDs
|
||||
const inputNodeId = `input-${Date.now()}`;
|
||||
const conditionNodeId = `condition-${Date.now()}`;
|
||||
const outputNodeId = `output-${Date.now()}`;
|
||||
|
||||
const pipelinePayload = {
|
||||
pipeline_id: "",
|
||||
version: 0,
|
||||
enabled: true,
|
||||
org: this.org,
|
||||
name: pipelineName,
|
||||
description: `Validation test pipeline for ${pipelineName}`,
|
||||
source: {
|
||||
source_type: "realtime"
|
||||
},
|
||||
paused_at: null,
|
||||
nodes: [
|
||||
{
|
||||
id: inputNodeId,
|
||||
position: { x: 100, y: 100 },
|
||||
data: {
|
||||
node_type: "stream",
|
||||
stream_type: "logs",
|
||||
stream_name: sourceStream,
|
||||
org_id: this.org
|
||||
},
|
||||
io_type: "input"
|
||||
},
|
||||
{
|
||||
id: conditionNodeId,
|
||||
position: { x: 300, y: 200 },
|
||||
data: {
|
||||
node_type: "condition",
|
||||
version: 2,
|
||||
conditions: cleanedConditions
|
||||
},
|
||||
io_type: "default"
|
||||
},
|
||||
{
|
||||
id: outputNodeId,
|
||||
position: { x: 500, y: 300 },
|
||||
data: {
|
||||
node_type: "stream",
|
||||
stream_type: "logs",
|
||||
stream_name: destStream,
|
||||
org_id: this.org
|
||||
},
|
||||
io_type: "output"
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: `e-${inputNodeId}-${conditionNodeId}`,
|
||||
source: inputNodeId,
|
||||
target: conditionNodeId,
|
||||
markerEnd: {
|
||||
type: "arrowclosed",
|
||||
width: 20,
|
||||
height: 20
|
||||
},
|
||||
type: "custom",
|
||||
style: {
|
||||
strokeWidth: 2
|
||||
},
|
||||
animated: true,
|
||||
updatable: true
|
||||
},
|
||||
{
|
||||
id: `e-${conditionNodeId}-${outputNodeId}`,
|
||||
source: conditionNodeId,
|
||||
target: outputNodeId,
|
||||
markerEnd: {
|
||||
type: "arrowclosed",
|
||||
width: 20,
|
||||
height: 20
|
||||
},
|
||||
type: "custom",
|
||||
style: {
|
||||
strokeWidth: 2
|
||||
},
|
||||
animated: true,
|
||||
updatable: true
|
||||
}
|
||||
],
|
||||
type: "realtime",
|
||||
stream_name: sourceStream,
|
||||
stream_type: "logs"
|
||||
};
|
||||
|
||||
testLogger.info('Creating pipeline via API', { pipelineName, sourceStream, destStream });
|
||||
testLogger.info('Pipeline payload', { payload: JSON.stringify(pipelinePayload, null, 2) });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/${this.org}/pipelines`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(pipelinePayload)
|
||||
});
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
let data;
|
||||
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
data = await response.json();
|
||||
} else {
|
||||
const text = await response.text();
|
||||
data = { error: text };
|
||||
}
|
||||
|
||||
testLogger.info('Pipeline creation response', { pipelineName, status: response.status, data });
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Pipeline creation failed: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to create pipeline', { pipelineName, error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ingest data to a stream
|
||||
* Sends records individually to ensure uniqueness
|
||||
* @param {string} streamName - Stream name
|
||||
* @param {array} data - Array of log objects
|
||||
* @returns {Promise<object>} Result with success/fail counts
|
||||
*/
|
||||
async ingestData(streamName, data) {
|
||||
testLogger.info('Ingesting data', { streamName, recordCount: data.length });
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
// Send records one by one to ensure each is treated as unique
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const record = data[i];
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${this.org}/${streamName}/_json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify([record]) // Send as single-element array
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
if (response.status === 200 && responseData.code === 200) {
|
||||
successCount++;
|
||||
testLogger.debug(`Ingested record ${i+1}/${data.length}`, { name: record.name, test_id: record.test_id || 'no_id', unique_id: record.unique_id });
|
||||
} else {
|
||||
failCount++;
|
||||
testLogger.error(`Failed to ingest record ${i+1}/${data.length}`, { response: responseData, test_id: record.test_id || record.name });
|
||||
}
|
||||
|
||||
// Small delay between records
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
testLogger.error(`Error ingesting record ${i+1}/${data.length}`, { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
testLogger.info('Ingestion complete', { streamName, total: data.length, success: successCount, failed: failCount });
|
||||
|
||||
if (failCount > 0) {
|
||||
throw new Error(`Failed to ingest ${failCount} out of ${data.length} records`);
|
||||
}
|
||||
|
||||
return { total: data.length, success: successCount, failed: failCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Query stream via API
|
||||
* @param {string} streamName - Stream to query
|
||||
* @param {number} expectedMinCount - Minimum expected record count (optional)
|
||||
* @returns {Promise<array>} Query results
|
||||
*/
|
||||
async queryStream(streamName, expectedMinCount = null) {
|
||||
testLogger.info('Querying stream via API', { streamName });
|
||||
|
||||
// Query for last 10 minutes
|
||||
const endTime = Date.now() * 1000; // microseconds
|
||||
const startTime = endTime - (10 * 60 * 1000 * 1000);
|
||||
|
||||
const query = {
|
||||
query: {
|
||||
sql: `SELECT * FROM "${streamName}"`,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
from: 0,
|
||||
size: 1000
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${this.org}/_search?type=logs`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': this.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(query)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const results = data.hits || [];
|
||||
|
||||
testLogger.info('Query results', { streamName, recordCount: results.length });
|
||||
|
||||
if (expectedMinCount !== null && expectedMinCount !== 0) {
|
||||
if (results.length < expectedMinCount) {
|
||||
throw new Error(`Expected at least ${expectedMinCount} records, got ${results.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to query stream', { streamName, error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify stream exists via API
|
||||
* @param {string} streamName - Name of the stream to verify
|
||||
* @returns {Promise<boolean>} True if stream exists
|
||||
*/
|
||||
async verifyStreamExists(streamName) {
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${this.org}/streams`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': this.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 200 && data.list) {
|
||||
const streamExists = data.list.some(s => s.name === streamName);
|
||||
testLogger.info('Stream existence check', { streamName, exists: streamExists });
|
||||
return streamExists;
|
||||
}
|
||||
|
||||
testLogger.warn('Failed to check stream existence', { streamName, status: response.status });
|
||||
return false;
|
||||
} catch (error) {
|
||||
testLogger.error('Error checking stream existence', { streamName, error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APICleanup;
|
||||
|
||||
@@ -2820,4 +2820,71 @@ export class LogsPage {
|
||||
// Click outside to trigger validation
|
||||
return await this.page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ==========================================
|
||||
* DATA INGESTION API METHOD
|
||||
* ==========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ingest data to a stream
|
||||
* Sends records individually to ensure uniqueness
|
||||
* @param {string} streamName - Stream name
|
||||
* @param {array} data - Array of log objects
|
||||
* @returns {Promise<object>} Result with success/fail counts
|
||||
*/
|
||||
async ingestData(streamName, data) {
|
||||
const fetch = (await import('node-fetch')).default;
|
||||
const orgId = process.env["ORGNAME"];
|
||||
const basicAuthCredentials = Buffer.from(
|
||||
`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
|
||||
).toString('base64');
|
||||
|
||||
testLogger.info('Ingesting data', { streamName, recordCount: data.length });
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
// Send records one by one to ensure each is treated as unique
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const record = data[i];
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${basicAuthCredentials}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify([record]) // Send as single-element array
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
if (response.status === 200 && responseData.code === 200) {
|
||||
successCount++;
|
||||
testLogger.debug(`Ingested record ${i+1}/${data.length}`, { name: record.name, test_id: record.test_id || 'no_id', unique_id: record.unique_id });
|
||||
} else {
|
||||
failCount++;
|
||||
testLogger.error(`Failed to ingest record ${i+1}/${data.length}`, { response: responseData, test_id: record.test_id || record.name });
|
||||
}
|
||||
|
||||
// Small delay between records
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
testLogger.error(`Error ingesting record ${i+1}/${data.length}`, { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
testLogger.info('Ingestion complete', { streamName, total: data.length, success: successCount, failed: failCount });
|
||||
|
||||
if (failCount > 0) {
|
||||
throw new Error(`Failed to ingest ${failCount} out of ${data.length} records`);
|
||||
}
|
||||
|
||||
return { total: data.length, success: successCount, failed: failCount };
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// pipelinesPage.js
|
||||
const { expect } = require('@playwright/test')
|
||||
const testLogger = require('../../playwright-tests/utils/test-logger.js');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const randomNodeName = `remote-node-${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
@@ -934,4 +935,144 @@ export class PipelinesPage {
|
||||
await this.confirmButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ==========================================
|
||||
* PIPELINE API METHODS
|
||||
* ==========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clean conditions for API (removes UI-only fields)
|
||||
* This removes fields like 'id' and 'groupId' that are only used in UI
|
||||
* @param {object} conditions - Condition configuration object with UI fields
|
||||
* @returns {object} Cleaned conditions for API
|
||||
*/
|
||||
cleanConditionsForAPI(conditions) {
|
||||
if (conditions.filterType === "condition") {
|
||||
// Remove UI-only fields: id, groupId
|
||||
// KEEP values field as it's required by the backend
|
||||
const { id, groupId, ...cleaned } = conditions;
|
||||
return cleaned;
|
||||
} else if (conditions.filterType === "group") {
|
||||
// Recursively clean nested conditions
|
||||
const { id, groupId, ...cleaned } = conditions;
|
||||
cleaned.conditions = conditions.conditions.map(c => this.cleanConditionsForAPI(c));
|
||||
return cleaned;
|
||||
}
|
||||
return conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pipeline via API
|
||||
* @param {string} pipelineName - Name of the pipeline
|
||||
* @param {string} sourceStream - Source stream name
|
||||
* @param {string} destStream - Destination stream name
|
||||
* @param {object} conditions - Condition configuration object
|
||||
* @returns {Promise<object>} API response
|
||||
*/
|
||||
async createPipeline(pipelineName, sourceStream, destStream, conditions) {
|
||||
const orgId = process.env["ORGNAME"];
|
||||
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",
|
||||
};
|
||||
|
||||
// Clean conditions to remove UI-only fields
|
||||
const cleanedConditions = this.cleanConditionsForAPI(conditions);
|
||||
testLogger.info('Cleaned conditions', { cleaned: JSON.stringify(cleanedConditions, null, 2) });
|
||||
|
||||
// Generate unique node IDs
|
||||
const inputNodeId = `input-${Date.now()}`;
|
||||
const conditionNodeId = `condition-${Date.now()}`;
|
||||
const outputNodeId = `output-${Date.now()}`;
|
||||
|
||||
const pipelinePayload = {
|
||||
pipeline_id: "",
|
||||
version: 0,
|
||||
enabled: true, // Enable pipeline for realtime processing
|
||||
org: orgId,
|
||||
name: pipelineName,
|
||||
description: `Validation test pipeline for ${pipelineName}`,
|
||||
source: {
|
||||
source_type: "realtime"
|
||||
},
|
||||
paused_at: null,
|
||||
nodes: [
|
||||
{
|
||||
id: inputNodeId,
|
||||
position: { x: 100, y: 100 },
|
||||
data: {
|
||||
node_type: "stream",
|
||||
stream_type: "logs",
|
||||
stream_name: sourceStream,
|
||||
org_id: orgId
|
||||
},
|
||||
io_type: "input"
|
||||
},
|
||||
{
|
||||
id: conditionNodeId,
|
||||
position: { x: 300, y: 200 },
|
||||
data: {
|
||||
node_type: "condition",
|
||||
version: 2,
|
||||
conditions: cleanedConditions
|
||||
},
|
||||
io_type: "intermediate"
|
||||
},
|
||||
{
|
||||
id: outputNodeId,
|
||||
position: { x: 500, y: 100 },
|
||||
data: {
|
||||
node_type: "stream",
|
||||
stream_type: "logs",
|
||||
stream_name: destStream,
|
||||
org_id: orgId
|
||||
},
|
||||
io_type: "output"
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: `${inputNodeId}-${conditionNodeId}`,
|
||||
source: inputNodeId,
|
||||
target: conditionNodeId,
|
||||
sourceHandle: "success"
|
||||
},
|
||||
{
|
||||
id: `${conditionNodeId}-${outputNodeId}`,
|
||||
source: conditionNodeId,
|
||||
target: outputNodeId,
|
||||
sourceHandle: "success"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
testLogger.info('Creating pipeline via API', { pipelineName, sourceStream, destStream });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.ZO_BASE_URL}/api/${orgId}/pipelines`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(pipelinePayload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 200 && data.code === 200) {
|
||||
testLogger.info('Pipeline created successfully', { pipelineName });
|
||||
} else {
|
||||
testLogger.warn('Pipeline creation returned non-200', { pipelineName, status: response.status, data });
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to create pipeline', { pipelineName, error: error.message });
|
||||
return { status: 500, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { IngestionPage } from '../generalPages/ingestionPage.js';
|
||||
import { ManagementPage } from '../generalPages/managementPage.js';
|
||||
|
||||
import { getHeaders, getIngestionUrl, sendRequest } from '../../utils/apiUtils.js';
|
||||
const testLogger = require('../../playwright-tests/utils/test-logger.js');
|
||||
|
||||
export class StreamsPage {
|
||||
constructor(page) {
|
||||
@@ -453,4 +454,141 @@ export class StreamsPage {
|
||||
async waitForUI(milliseconds) {
|
||||
await this.page.waitForTimeout(milliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ==========================================
|
||||
* STREAM API METHODS
|
||||
* ==========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a stream via API
|
||||
* @param {string} streamName - Name of the stream to create
|
||||
* @param {string} streamType - Type of stream (logs, metrics, traces)
|
||||
* @returns {Promise<object>} API response
|
||||
*/
|
||||
async createStream(streamName, streamType = 'logs') {
|
||||
const fetch = (await import('node-fetch')).default;
|
||||
const orgId = process.env["ORGNAME"];
|
||||
const headers = getHeaders();
|
||||
|
||||
const payload = {
|
||||
fields: [],
|
||||
settings: {
|
||||
partition_keys: [],
|
||||
index_fields: [],
|
||||
full_text_search_keys: [],
|
||||
bloom_filter_fields: [],
|
||||
defined_schema_fields: [],
|
||||
data_retention: 14
|
||||
}
|
||||
};
|
||||
|
||||
testLogger.info('Creating stream via API', { streamName, streamType });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${orgId}/streams/${streamName}?type=${streamType}`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 200 && data.code === 200) {
|
||||
testLogger.info('Stream created successfully', { streamName });
|
||||
} else {
|
||||
testLogger.warn('Stream creation returned non-200 or already exists', { streamName, status: response.status });
|
||||
}
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to create stream', { streamName, error: error.message });
|
||||
return { status: 500, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify stream exists via API
|
||||
* @param {string} streamName - Name of the stream to verify
|
||||
* @returns {Promise<boolean>} True if stream exists
|
||||
*/
|
||||
async verifyStreamExists(streamName) {
|
||||
const fetch = (await import('node-fetch')).default;
|
||||
const orgId = process.env["ORGNAME"];
|
||||
const headers = getHeaders();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${orgId}/streams`, {
|
||||
method: 'GET',
|
||||
headers: headers
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 200 && data.list) {
|
||||
const streamExists = data.list.some(s => s.name === streamName);
|
||||
testLogger.info('Stream existence check', { streamName, exists: streamExists });
|
||||
return streamExists;
|
||||
}
|
||||
|
||||
testLogger.warn('Failed to check stream existence', { streamName, status: response.status });
|
||||
return false;
|
||||
} catch (error) {
|
||||
testLogger.error('Error checking stream existence', { streamName, error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query stream via API
|
||||
* @param {string} streamName - Stream to query
|
||||
* @param {number} expectedMinCount - Minimum expected record count (optional)
|
||||
* @returns {Promise<array>} Query results
|
||||
*/
|
||||
async queryStream(streamName, expectedMinCount = null) {
|
||||
const fetch = (await import('node-fetch')).default;
|
||||
const orgId = process.env["ORGNAME"];
|
||||
const headers = getHeaders();
|
||||
|
||||
testLogger.info('Querying stream via API', { streamName });
|
||||
|
||||
// Query for last 10 minutes
|
||||
const endTime = Date.now() * 1000; // microseconds
|
||||
const startTime = endTime - (10 * 60 * 1000 * 1000);
|
||||
|
||||
const query = {
|
||||
query: {
|
||||
sql: `SELECT * FROM "${streamName}"`,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
from: 0,
|
||||
size: 1000
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${process.env.INGESTION_URL}/api/${orgId}/_search?type=logs`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(query)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const results = data.hits || [];
|
||||
|
||||
testLogger.info('Query results', { streamName, recordCount: results.length });
|
||||
|
||||
if (expectedMinCount !== null && expectedMinCount !== 0) {
|
||||
if (results.length < expectedMinCount) {
|
||||
throw new Error(`Expected at least ${expectedMinCount} records, got ${results.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
testLogger.error('Failed to query stream', { streamName, error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import { test, expect } from "../baseFixtures.js";
|
||||
import logData from "../../fixtures/log.json";
|
||||
import logsdata from "../../../test-data/logs_data.json";
|
||||
import PageManager from "../../pages/page-manager.js";
|
||||
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
||||
const testLogger = require('../utils/test-logger.js');
|
||||
const PageManager = require('../../pages/page-manager.js');
|
||||
const logData = require("../../fixtures/log.json");
|
||||
const logsdata = require("../../../test-data/logs_data.json");
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
@@ -12,66 +12,41 @@ test.use({
|
||||
}
|
||||
});
|
||||
|
||||
async function login(page) {
|
||||
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("label").filter({ hasText: "Password *" }).click();
|
||||
await page
|
||||
.locator('[data-cy="login-password"]')
|
||||
.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
|
||||
await page.locator('[data-cy="login-sign-in"]').click();
|
||||
}
|
||||
|
||||
async function ingestion(page, streamName = "e2e_automate_conditions") {
|
||||
const orgId = process.env["ORGNAME"];
|
||||
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",
|
||||
};
|
||||
|
||||
const response = 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
|
||||
});
|
||||
testLogger.debug('API response received', { response, streamName });
|
||||
}
|
||||
|
||||
test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
let pageManager;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
// Variables for lightweight cleanup
|
||||
let currentPipelineName;
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
|
||||
// Navigate to base URL with authentication
|
||||
await navigateToBase(page);
|
||||
pageManager = new PageManager(page);
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Post-authentication stabilization wait
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Ingest data to unique streams for each test to avoid conflicts
|
||||
await ingestion(page, "e2e_conditions_basic");
|
||||
await ingestion(page, "e2e_conditions_groups");
|
||||
await ingestion(page, "e2e_conditions_validation");
|
||||
await ingestion(page, "e2e_conditions_precedence");
|
||||
await ingestion(page, "e2e_conditions_multiple");
|
||||
await ingestion(page, "e2e_conditions_delete");
|
||||
await ingestion(page, "e2e_conditions_operators");
|
||||
// Use only first 10 records to avoid timeout (full dataset has 3800+ records)
|
||||
const streamNames = [
|
||||
"e2e_conditions_basic",
|
||||
"e2e_conditions_groups",
|
||||
"e2e_conditions_validation",
|
||||
"e2e_conditions_precedence",
|
||||
"e2e_conditions_multiple",
|
||||
"e2e_conditions_delete",
|
||||
"e2e_conditions_operators"
|
||||
];
|
||||
|
||||
for (const streamName of streamNames) {
|
||||
await pageManager.logsPage.ingestData(streamName, logsdata.slice(0, 10));
|
||||
}
|
||||
|
||||
// Brief wait for stream schemas to be established
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.goto(
|
||||
`${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`
|
||||
@@ -79,8 +54,24 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Lightweight pipeline cleanup
|
||||
try {
|
||||
if (currentPipelineName) {
|
||||
await pageManager.apiCleanup.deletePipeline(currentPipelineName).catch(() => {});
|
||||
testLogger.info('Cleaned up pipeline', { currentPipelineName });
|
||||
}
|
||||
} catch (error) {
|
||||
testLogger.warn('Cleanup failed', { error: error.message });
|
||||
}
|
||||
|
||||
// Reset variables
|
||||
currentPipelineName = null;
|
||||
});
|
||||
|
||||
test("should create, edit, and test basic condition operations with multiple operators", async ({ page }) => {
|
||||
const pipelineName = `pipeline-basic-ops-${Math.random().toString(36).substring(7)}`;
|
||||
currentPipelineName = pipelineName;
|
||||
|
||||
// Create pipeline with condition
|
||||
await pageManager.pipelinesPage.createPipelineWithCondition("e2e_conditions_basic");
|
||||
@@ -121,7 +112,20 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
|
||||
// Save and complete pipeline
|
||||
await pageManager.pipelinesPage.saveConditionAndCompletePipeline(pipelineName);
|
||||
await ingestion(page);
|
||||
|
||||
// Ingest test data to streams
|
||||
const streamNames = [
|
||||
"e2e_conditions_basic",
|
||||
"e2e_conditions_groups",
|
||||
"e2e_conditions_validation",
|
||||
"e2e_conditions_precedence",
|
||||
"e2e_conditions_multiple",
|
||||
"e2e_conditions_delete",
|
||||
"e2e_conditions_operators"
|
||||
];
|
||||
for (const streamName of streamNames) {
|
||||
await pageManager.logsPage.ingestData(streamName, logsdata.slice(0, 10));
|
||||
}
|
||||
|
||||
// Test 9: Edit existing condition
|
||||
await pageManager.pipelinesPage.openPipelineMenu();
|
||||
@@ -248,6 +252,7 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
|
||||
test("should test complex conditions and operator precedence (A OR B AND C)", async ({ page }) => {
|
||||
const pipelineName = `pipeline-complex-${Math.random().toString(36).substring(7)}`;
|
||||
currentPipelineName = pipelineName;
|
||||
|
||||
await pageManager.pipelinesPage.createPipelineWithCondition("e2e_conditions_precedence");
|
||||
|
||||
@@ -275,7 +280,20 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
}
|
||||
|
||||
await pageManager.pipelinesPage.saveConditionAndCompletePipeline(pipelineName);
|
||||
await ingestion(page);
|
||||
|
||||
// Ingest test data to streams
|
||||
const streamNames = [
|
||||
"e2e_conditions_basic",
|
||||
"e2e_conditions_groups",
|
||||
"e2e_conditions_validation",
|
||||
"e2e_conditions_precedence",
|
||||
"e2e_conditions_multiple",
|
||||
"e2e_conditions_delete",
|
||||
"e2e_conditions_operators"
|
||||
];
|
||||
for (const streamName of streamNames) {
|
||||
await pageManager.logsPage.ingestData(streamName, logsdata.slice(0, 10));
|
||||
}
|
||||
|
||||
await pageManager.pipelinesPage.openPipelineMenu();
|
||||
await pageManager.pipelinesPage.deletePipelineByName(pipelineName);
|
||||
@@ -283,6 +301,7 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
|
||||
test("should test multiple conditions: A, B, C, and D", async ({ page }) => {
|
||||
const pipelineName = `pipeline-multiple-${Math.random().toString(36).substring(7)}`;
|
||||
currentPipelineName = pipelineName;
|
||||
|
||||
await pageManager.pipelinesPage.createPipelineWithCondition("e2e_conditions_multiple");
|
||||
|
||||
@@ -312,7 +331,20 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
await pageManager.pipelinesPage.verifyConditionCount(4);
|
||||
|
||||
await pageManager.pipelinesPage.saveConditionAndCompletePipeline(pipelineName);
|
||||
await ingestion(page);
|
||||
|
||||
// Ingest test data to streams
|
||||
const streamNames = [
|
||||
"e2e_conditions_basic",
|
||||
"e2e_conditions_groups",
|
||||
"e2e_conditions_validation",
|
||||
"e2e_conditions_precedence",
|
||||
"e2e_conditions_multiple",
|
||||
"e2e_conditions_delete",
|
||||
"e2e_conditions_operators"
|
||||
];
|
||||
for (const streamName of streamNames) {
|
||||
await pageManager.logsPage.ingestData(streamName, logsdata.slice(0, 10));
|
||||
}
|
||||
|
||||
await pageManager.pipelinesPage.openPipelineMenu();
|
||||
await pageManager.pipelinesPage.deletePipelineByName(pipelineName);
|
||||
@@ -320,6 +352,7 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
|
||||
test("should delete condition node from pipeline", async ({ page }) => {
|
||||
const pipelineName = `pipeline-delete-${Math.random().toString(36).substring(7)}`;
|
||||
currentPipelineName = pipelineName;
|
||||
|
||||
// Create pipeline with condition
|
||||
await pageManager.pipelinesPage.createPipelineWithCondition("e2e_conditions_delete");
|
||||
@@ -355,6 +388,7 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
|
||||
test("should test different comparison operators (>=, !=, Contains, NotContains)", async ({ page }) => {
|
||||
const pipelineName = `pipeline-operators-${Math.random().toString(36).substring(7)}`;
|
||||
currentPipelineName = pipelineName;
|
||||
|
||||
await pageManager.pipelinesPage.createPipelineWithCondition("e2e_conditions_operators");
|
||||
|
||||
@@ -373,7 +407,20 @@ test.describe("Pipeline Conditions - Comprehensive Tests", () => {
|
||||
await pageManager.pipelinesPage.verifyConditionCount(3);
|
||||
|
||||
await pageManager.pipelinesPage.saveConditionAndCompletePipeline(pipelineName);
|
||||
await ingestion(page);
|
||||
|
||||
// Ingest test data to streams
|
||||
const streamNames = [
|
||||
"e2e_conditions_basic",
|
||||
"e2e_conditions_groups",
|
||||
"e2e_conditions_validation",
|
||||
"e2e_conditions_precedence",
|
||||
"e2e_conditions_multiple",
|
||||
"e2e_conditions_delete",
|
||||
"e2e_conditions_operators"
|
||||
];
|
||||
for (const streamName of streamNames) {
|
||||
await pageManager.logsPage.ingestData(streamName, logsdata.slice(0, 10));
|
||||
}
|
||||
|
||||
await pageManager.pipelinesPage.openPipelineMenu();
|
||||
await pageManager.pipelinesPage.deletePipelineByName(pipelineName);
|
||||
|
||||
@@ -61,7 +61,18 @@ test.describe("Pre-Test Cleanup", () => {
|
||||
/^stress_test/, // stress_test*, stress_test123, etc.
|
||||
/^sdr_/, // sdr_* (SDR test streams)
|
||||
/^e2e_join_/, // e2e_join_* (UNION test streams)
|
||||
/^[a-z]{8,9}$/ // Random 8-9 char lowercase strings
|
||||
/^e2e_conditions_/, // e2e_conditions_* (Pipeline conditions UI test streams)
|
||||
/^[a-z]{8,9}$/, // Random 8-9 char lowercase strings
|
||||
/^e2e_cond_prec_(src|dest)_\d+$/, // Test 1: Operator precedence test streams
|
||||
/^e2e_multiple_or_(src|dest)_\d+$/, // Test 2: Multiple OR test streams
|
||||
/^e2e_nested_or_(src|dest)_\d+$/, // Test 3: Nested OR test streams
|
||||
/^e2e_numeric_(src|dest)_\d+$/, // Test 4: Numeric comparison test streams
|
||||
/^e2e_multiple_and_(src|dest)_\d+$/, // Test 5: Multiple AND test streams
|
||||
/^e2e_deep_(src|dest)_\d+$/, // Test 6: Deeply nested test streams
|
||||
/^e2e_not_operator_(src|dest)_\d+$/, // Test 7: NOT operator test streams
|
||||
/^e2e_impossible_(src|dest)_\d+$/, // Test 8: Impossible condition test streams
|
||||
/^e2e_universal_(src|dest)_\d+$/, // Test 9: Universal condition test streams
|
||||
/^e2e_4level_(src|dest)_\d+$/ // Test 10: 4-level nested test streams
|
||||
],
|
||||
// Protected streams to never delete
|
||||
['default', 'sensitive', 'important', 'critical', 'production', 'staging', 'automation', 'e2e_automate']
|
||||
|
||||
Reference in New Issue
Block a user