Files
openobserve/tests/ui-testing/playwright-tests/Functions/result-array-functions.spec.js
T
Ashish KolheandClaude Opus 4.5 ce09cd0a4d feat: add js for claim parser (#9873)
### **User description**
Design at : security/sso-claim-parser/configuration.md


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Introduce rquickjs-based JS runtime for functions

- Add compile_js_function and apply_js_fn utilities

- Extend pipeline to support JS via CompiledFunctionRuntime

- Update web UI: select JS/VRL transforms, show JS errors


___

### Diagram Walkthrough


```mermaid
flowchart LR
  A["FunctionNode"] -->|JS| B["compile_js_function"]
  A -->|VRL| C["compile_vrl_function"]
  B --> D["apply_js_fn"]
  C --> E["apply_vrl_fn"]
  D --> F["send to children"]
  E --> F
```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>13
files</summary><table>
<tr>
<td><strong>function.ts</strong><dd><code>Add optional `trans_type` to
TestFunctionPayload</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c6766f6c671adce05f9eef5d4a57a32371fa7da35628ef5c142fa503d191cb16">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>js.rs</strong><dd><code>Introduce JS runtime, compile and
apply functions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-88e490bed5fa6c9c8e0aa2738b95c5284bb97899298f54b9cd587713e3ef0109">+756/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>batch_execution.rs</strong><dd><code>Extend pipeline
execution to handle JS functions</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-3f6824a6eddaf1603e5cb0f1a4311ac6a45e1ffb967266ccc1f0bba944625e69">+465/-96</a></td>

</tr>

<tr>
<td><strong>functions.rs</strong><dd><code>Validate and test-run JS
transforms, restrict to _meta</code>&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ed5611a9aa6b5c033e70d7631ba72fafad3cc842672077a7fcf965d11a4fef23">+319/-35</a></td>

</tr>

<tr>
<td><strong>mod.rs</strong><dd><code>Forbid JavaScript functions in
pipelines globally</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c186c796a7bdfd9d1301b1429df11456430f1d0d4758783810b9e2e44384e3b2">+39/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>jwt.rs</strong><dd><code>Add JS claim parser execution
function</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-232cab74f4cbf135af424744272e17732fbda8ac005d33c0acb4597e65479d02">+25/-2</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>mod.rs</strong><dd><code>Wrap JS compile/apply in ingestion
API</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-7c7b5bca85f0ced652965cae66540c96f56dfcca4f4c16b77bf7b45aac203d1b">+24/-1</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>mod.rs</strong><dd><code>Include `trans_type` in
test_function API payload</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-a5e1b3644cef9feb689dfe66382e44e5434ecadbff3469127346a333d4733102">+8/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>AddFunction.vue</strong><dd><code>UI: add JS/VRL transform
type selector</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ebfd6c538f3cd3ecb9ab17550fd76e2e89e7a1abccc83a7505077e4af3eee110">+42/-25</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>FunctionsToolbar.vue</strong><dd><code>Toolbar: transform
type radio buttons</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-8283d2ff9c4a66b4b59eac306672aca2d54655a462e2ddb88e14e039d79dcdaf">+51/-3</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>DomainManagement.vue</strong><dd><code>Track claim-parser
function changes for save button</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-5e47cd01fcb63f7ef27dc64d2ecf66c39e17097e29944951c3ae1bfe715bb97c">+17/-9</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>TestFunction.vue</strong><dd><code>Support JS error
messaging in test UI</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-b9af9a2d1bf04d23422c26a13e6a763c6953c8713357a2f64ed8073ce6c926a3">+15/-5</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>PipelineEditor.vue</strong><dd><code>Filter out JS functions
in pipeline editor UI</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-68c21f647336067eb79737ce9aca2d49632f2fa1c0878bcfdae55607e3473a15">+12/-4</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>2 files</summary><table>
<tr>
<td><strong>function.rs</strong><dd><code>Extend `Transform` model for
JS support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-27c9f3d93723397a0e234bc45c9f543b43eff5fd518d76e735f78d95a58f3f1c">+16/-6</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>mod.rs</strong><dd><code>Register new `js` utils
module</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2aa59ce4464e7dbabdb9367fc8bb8c1b489c914b8690577d4916870463ab12db">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Bug
fix</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>errors.rs</strong><dd><code>Rename
`MemoryTableOverflowError` display message</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-bf45932466265b732f13d0cd9b9cffbaefbe37c5861ace9315449eeca9250713">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>3
files</summary><table>
<tr>
<td><strong>result-array-functions.spec.js</strong><dd><code>Add E2E
tests for `#ResultArray#` functions in UI</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-e9911625ad2bab76f2b760f0ad254791331993a6a874b96f6a11fc6d88038b10">+470/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>cleanup.spec.js</strong><dd><code>Clean up JS function test
data in UI tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c214275a512fa285a7e4f2ab61dfcb42d71a0436b0e4241f94d7b45693587983">+31/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>page-manager.js</strong><dd><code>Register `FunctionsPage`
in `PageManager`</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-565eab8b3c767ae50c9d05e591621d14f66b81af84d6b5ea6efe136c1c3933df">+6/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>Cargo.toml</strong><dd><code>Add `rquickjs` dependency for
JS runtime</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Additional
files</strong></td><td><details><summary>18 files</summary><table>
<tr>
  <td><strong>test_functions_meta_org_restriction.py</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-99ef1207d6ae6d7eeaed37d70c8cf0c4770ba78f6f51fbdc067d35492d2fe9ea">+401/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>test_js_enrichment.py</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c5547d46bec60b888d947d1e9b7c5445aee72cf8e837182f7138e0fc903eedbb">+356/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>test_js_result_array.py</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-9ed1363a1f58c94e018bb2b0aaa9d08fa71f89e7e55a6ff6be4a82bbeed8985c">+390/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>functionsPage.js</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-7bab9cc2eb8f36d3ee5d0aab453c353ec6d6dfecbc401cf401322ff57faa8440">+326/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>javascript-functions.spec.js</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-bdae6529346a7222bdec19f75f490cb9d0f3578a260ba835225efcd27db3f64e">+314/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>meta-org-js-restriction.spec.js</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-16cc2373f6f1955f8344a59d5f2edcd6ce121c4fdee1d432b1f0ec8e27f25881">+483/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>FunctionList.vue</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-6ff9af50a1597504b7f0dca91e61cf30c800b4a17b4be2ed2c3a4f7a7b2d7068">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>de.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-78f24ecb2dbcfc38fdf3ec22b2559a5b40d236ea689d1c55f5d3e87168310264">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>en.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-892aa3d75ab6e129ef9116aa21bec1b9a15ba69ce5366d2a19128078c690d824">+15/-7</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>es.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ac2a2167b8b973e3f776f9fe3b43bef4576470d8ad33f3d7cfaff6edff0e5778">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>fr.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-db80e762b88ddf183820c050cdc6b215e6b02b365b5e73f5152af46b0d26f1a2">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>it.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2de78074a5f887afddce60d694ead9322e6ab223eb70251c13819f3028bdc70d">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>ja.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-092e49c5f9fbc1b539ab9c2866d108d0ce1d314022b4c294eb4611b285e7bbd3">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>ko.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-50e27ca1b4825a5026f3a251bfcf79c97cab07cfade2ebd512d332453d707c48">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>nl.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-a1706021687ae5d8a1c006c49eab3946174fecd895f9b70a89c2e76d97250906">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>pt.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-38e804f64cb5e0e451849fc84e9b46e53c954a4a4263d51c18665ad45f4b4815">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>tr.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-6f0f131e9e2591480c4c5d1eaffc72d02dc31cbd8d3f90d8f012f08cabac8818">+30/-18</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>zh.json</strong></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/9873/files#diff-8818055db58f83230aeea4ebfd4cec31d924e8c47b6f91298e356959ab25339e">+254/-242</a></td>

</tr>
</table></details></td></tr></tr></tbody></table>

</details>

___

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 22:47:35 +05:30

471 lines
18 KiB
JavaScript

const { test, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
const { expect } = require('@playwright/test');
test.describe('#ResultArray# Functionality Tests', { tag: ['@resultArray', '@functions'] }, () => {
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
// Navigate to functions page
testLogger.info('Navigating to Functions page');
await pm.functionsPage.navigate();
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
// Wait for page to be ready
await page.waitForTimeout(2000);
// Verify we're on the functions page
const addButtonVisible = await page.locator('[data-test="function-list-add-function-btn"]').isVisible({ timeout: 5000 }).catch(() => false);
testLogger.info(`Functions page loaded - Add button visible: ${addButtonVisible}`);
});
test.describe('VRL #ResultArray# Tests', () => {
test('P0: Create VRL function with #ResultArray# marker', { tag: ['@smoke', '@P0', '@vrlResultArray'] }, async ({ page }) => {
testLogger.info('Test: Create VRL function with #ResultArray# marker');
const functionName = `vrl_result_array_${Date.now()}`;
const vrlCode = `#ResultArray#
.new_field = "processed"
.source = "vrl"
[., .]`;
// Create VRL function with #ResultArray#
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
testLogger.info(`✅ VRL function with #ResultArray# created: ${functionName}`);
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P0: VRL #ResultArray# - Multiple row expansion (1 input -> 3 outputs)', { tag: ['@smoke', '@P0', '@vrlResultArray'] }, async ({ page }) => {
testLogger.info('Test: VRL #ResultArray# with multiple row expansion');
const functionName = `vrl_array_expand_${Date.now()}`;
const vrlCode = `#ResultArray#
.result_type = "expanded"
[
merge(., {"index": 0}),
merge(., {"index": 1}),
merge(., {"index": 2})
]`;
await pm.functionsPage.clickAddFunctionButton();
await pm.functionsPage.fillFunctionName(functionName);
await pm.functionsPage.selectVRLType();
await pm.functionsPage.enterFunctionCode(vrlCode);
// Test the function with sample data
const testEvent = '{"name": "test", "value": 100}';
await pm.functionsPage.clickTestButton();
await pm.functionsPage.enterTestEvent(testEvent);
const runSuccess = await pm.functionsPage.clickRunTestButton();
if (runSuccess) {
const output = await pm.functionsPage.getTestOutput();
testLogger.info(`VRL array expansion output: ${output}`);
// Verify output contains array elements (looking for index values)
if (output.includes('index') && (output.includes('0') || output.includes('1') || output.includes('2'))) {
testLogger.info('✅ VRL #ResultArray# successfully expanded to multiple rows');
} else {
testLogger.warn('⚠️ Output format may need verification');
}
}
// Save and cleanup
await pm.functionsPage.clickSaveButton();
await page.waitForTimeout(2000);
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P1: Verify #ResultArray# marker preserved after save (VRL)', { tag: ['@P1', '@vrlResultArray'] }, async ({ page }) => {
testLogger.info('Test: Verify VRL #ResultArray# marker is preserved');
const functionName = `vrl_preserve_marker_${Date.now()}`;
const vrlCode = `#ResultArray#
.preserved = true
[., .]`;
// Create function
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
testLogger.info('VRL function with #ResultArray# saved');
// Navigate away and back
await page.goto(`${process.env.ZO_BASE_URL}/web/logs?org_identifier=${process.env.ORGID}`);
await page.waitForLoadState('networkidle');
await pm.functionsPage.navigate();
// Open function and check if #ResultArray# is still there
await pm.functionsPage.searchFunction(functionName);
await pm.functionsPage.clickFunctionByName(functionName);
await page.waitForTimeout(2000);
// Check editor content contains #ResultArray#
const editorContent = await page.locator('[data-test="logs-vrl-function-editor"]').textContent();
if (editorContent && editorContent.includes('#ResultArray#')) {
testLogger.info('✅ #ResultArray# marker preserved in VRL function');
} else {
testLogger.warn('⚠️ #ResultArray# marker may not be visible in editor');
}
// Cleanup
await pm.functionsPage.clickCancelButton();
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P2: VRL #ResultArray# - Empty array return', { tag: ['@P2', '@vrlResultArray', '@edgeCases'] }, async ({ page }) => {
testLogger.info('Test: VRL #ResultArray# with empty array');
const functionName = `vrl_empty_array_${Date.now()}`;
const vrlCode = `#ResultArray#
# Return empty array based on condition
if .filter_out == true {
[]
} else {
[.]
}`;
await pm.functionsPage.clickAddFunctionButton();
await pm.functionsPage.fillFunctionName(functionName);
await pm.functionsPage.selectVRLType();
await pm.functionsPage.enterFunctionCode(vrlCode);
// Test with filter_out=true to get empty array
const testEvent = '{"filter_out": true, "name": "test"}';
await pm.functionsPage.clickTestButton();
await pm.functionsPage.enterTestEvent(testEvent);
const runSuccess = await pm.functionsPage.clickRunTestButton();
if (runSuccess) {
const output = await pm.functionsPage.getTestOutput();
testLogger.info(`VRL empty array output: ${output}`);
testLogger.info('✅ VRL #ResultArray# handles empty array case');
}
// Cancel without saving
await pm.functionsPage.clickCancelButton();
});
test('P2: VRL #ResultArray# - Single element array', { tag: ['@P2', '@vrlResultArray', '@edgeCases'] }, async ({ page }) => {
testLogger.info('Test: VRL #ResultArray# with single element array');
const functionName = `vrl_single_array_${Date.now()}`;
const vrlCode = `#ResultArray#
.single_element = true
[.]`;
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
testLogger.info('✅ VRL #ResultArray# with single element created');
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
});
test.describe('JavaScript #ResultArray# Tests', () => {
test('P0: Create JS function with #ResultArray# marker', { tag: ['@smoke', '@P0', '@jsResultArray'] }, async ({ page }) => {
testLogger.info('Test: Create JavaScript function with #ResultArray# marker');
const functionName = `js_result_array_${Date.now()}`;
const jsCode = `#ResultArray#
rows[0].new_field = "processed";
rows[0].source = "javascript";
rows;`;
// Create JS function with #ResultArray#
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
testLogger.info(`✅ JavaScript function with #ResultArray# created: ${functionName}`);
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P0: JS #ResultArray# - Multiple row expansion (1 input -> 3 outputs)', { tag: ['@smoke', '@P0', '@jsResultArray'] }, async ({ page }) => {
testLogger.info('Test: JavaScript #ResultArray# with multiple row expansion');
const functionName = `js_array_expand_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable (array input)
// Expand: take first row and create 3 copies with different indices
if (rows.length > 0) {
var firstRow = rows[0];
rows.length = 0; // Clear array
for (var i = 0; i < 3; i++) {
var newRow = {};
for (var key in firstRow) {
newRow[key] = firstRow[key];
}
newRow.index = i;
newRow.expanded = true;
rows.push(newRow);
}
}`;
await pm.functionsPage.clickAddFunctionButton();
await pm.functionsPage.fillFunctionName(functionName);
await pm.functionsPage.selectJavaScriptType();
await pm.functionsPage.enterFunctionCode(jsCode);
// Test the function with sample data
const testEvent = '{"name": "test", "value": 100}';
await pm.functionsPage.clickTestButton();
await pm.functionsPage.enterTestEvent(testEvent);
const runSuccess = await pm.functionsPage.clickRunTestButton();
if (runSuccess) {
const output = await pm.functionsPage.getTestOutput();
testLogger.info(`JavaScript array expansion output: ${output}`);
// Verify output contains array elements
if (output.includes('index') && output.includes('expanded')) {
testLogger.info('✅ JavaScript #ResultArray# successfully expanded to multiple rows');
} else {
testLogger.warn('⚠️ Output format may need verification');
}
}
// Save and cleanup
await pm.functionsPage.clickSaveButton();
await page.waitForTimeout(2000);
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P1: Verify #ResultArray# marker preserved after save (JS)', { tag: ['@P1', '@jsResultArray'] }, async ({ page }) => {
testLogger.info('Test: Verify JavaScript #ResultArray# marker is preserved');
const functionName = `js_preserve_marker_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
for (var i = 0; i < rows.length; i++) {
rows[i].preserved = true;
}`;
// Create function
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
testLogger.info('JavaScript function with #ResultArray# saved');
// Navigate away and back
await page.goto(`${process.env.ZO_BASE_URL}/web/logs?org_identifier=${process.env.ORGID}`);
await page.waitForLoadState('networkidle');
await pm.functionsPage.navigate();
// Open function and check type and #ResultArray# marker
const functionType = await pm.functionsPage.openFunctionAndCheckType(functionName);
expect(functionType).toBe('js');
testLogger.info('✅ JavaScript type verified');
// Check editor content contains #ResultArray#
const editorContent = await page.locator('[data-test="logs-vrl-function-editor"]').textContent();
if (editorContent && editorContent.includes('#ResultArray#')) {
testLogger.info('✅ #ResultArray# marker preserved in JavaScript function');
} else {
testLogger.warn('⚠️ #ResultArray# marker may not be visible in editor');
}
// Cleanup
await pm.functionsPage.clickCancelButton();
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P2: JS #ResultArray# - Empty array return', { tag: ['@P2', '@jsResultArray', '@edgeCases'] }, async ({ page }) => {
testLogger.info('Test: JavaScript #ResultArray# with empty array');
const functionName = `js_empty_array_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
// Filter out rows based on condition
var filtered = [];
for (var i = 0; i < rows.length; i++) {
if (rows[i].filter_out !== true) {
filtered.push(rows[i]);
}
}
rows.length = 0;
for (var i = 0; i < filtered.length; i++) {
rows.push(filtered[i]);
}`;
await pm.functionsPage.clickAddFunctionButton();
await pm.functionsPage.fillFunctionName(functionName);
await pm.functionsPage.selectJavaScriptType();
await pm.functionsPage.enterFunctionCode(jsCode);
// Test with filter_out=true to get empty array
const testEvent = '{"filter_out": true, "name": "test"}';
await pm.functionsPage.clickTestButton();
await pm.functionsPage.enterTestEvent(testEvent);
const runSuccess = await pm.functionsPage.clickRunTestButton();
if (runSuccess) {
const output = await pm.functionsPage.getTestOutput();
testLogger.info(`JavaScript empty array output: ${output}`);
testLogger.info('✅ JavaScript #ResultArray# handles empty array case');
}
// Cancel without saving
await pm.functionsPage.clickCancelButton();
});
test('P2: JS #ResultArray# - Single element array', { tag: ['@P2', '@jsResultArray', '@edgeCases'] }, async ({ page }) => {
testLogger.info('Test: JavaScript #ResultArray# with single element array');
const functionName = `js_single_array_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
for (var i = 0; i < rows.length; i++) {
rows[i].single_element = true;
}`;
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
testLogger.info('✅ JavaScript #ResultArray# with single element created');
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
test('P1: JS #ResultArray# - Complex transformation with map', { tag: ['@P1', '@jsResultArray'] }, async ({ page }) => {
testLogger.info('Test: JavaScript #ResultArray# with complex array transformation');
const functionName = `js_complex_array_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
// Create multiple derived rows from each input row
var statuses = ['pending', 'processing', 'completed'];
var expanded = [];
for (var i = 0; i < rows.length; i++) {
for (var j = 0; j < statuses.length; j++) {
expanded.push({
original_id: rows[i].id,
status: statuses[j],
timestamp: Date.now(),
source: 'javascript'
});
}
}
rows.length = 0;
for (var i = 0; i < expanded.length; i++) {
rows.push(expanded[i]);
}`;
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
testLogger.info('✅ JavaScript #ResultArray# with complex transformation created');
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
});
test.describe('Cross-Language Comparison', () => {
test('P1: Compare VRL and JS #ResultArray# output', { tag: ['@P1', '@comparison'] }, async ({ page }) => {
testLogger.info('Test: Compare VRL and JavaScript #ResultArray# behavior');
const timestamp = Date.now();
const vrlFunctionName = `vrl_compare_${timestamp}`;
const jsFunctionName = `js_compare_${timestamp}`;
// Create VRL version
const vrlCode = `#ResultArray#
[
merge(., {"type": "vrl", "index": 0}),
merge(., {"type": "vrl", "index": 1})
]`;
await pm.functionsPage.createVRLFunction(vrlFunctionName, vrlCode);
testLogger.info('Created VRL comparison function');
// Create JS version (using 'rows' for #ResultArray#)
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
for (var i = 0; i < rows.length; i++) {
rows[i].type = "javascript";
rows[i].index = i;
}`;
await pm.functionsPage.createJavaScriptFunction(jsFunctionName, jsCode);
testLogger.info('Created JavaScript comparison function');
testLogger.info('✅ Both VRL and JavaScript #ResultArray# functions created for comparison');
// Cleanup
await pm.functionsPage.deleteFunctionByName(vrlFunctionName);
await pm.functionsPage.deleteFunctionByName(jsFunctionName);
});
});
test.describe('Pipeline Integration', () => {
test('P1: #ResultArray# function in pipeline context', { tag: ['@P1', '@pipelineIntegration'] }, async ({ page }) => {
testLogger.info('Test: #ResultArray# function integration with pipelines');
const functionName = `pipeline_array_fn_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
// Expand each log entry into multiple events
var events = ['login', 'action', 'logout'];
var expanded = [];
for (var i = 0; i < rows.length; i++) {
for (var j = 0; j < events.length; j++) {
var newRow = {};
for (var key in rows[i]) {
newRow[key] = rows[i][key];
}
newRow.event_type = events[j];
newRow.pipeline_processed = true;
expanded.push(newRow);
}
}
rows.length = 0;
for (var i = 0; i < expanded.length; i++) {
rows.push(expanded[i]);
}`;
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
testLogger.info('✅ #ResultArray# function created for pipeline integration');
// Note: Full pipeline integration test would require:
// 1. Create a pipeline with this function
// 2. Send test data through the pipeline
// 3. Verify multiple output rows are generated
// This is documented for future pipeline E2E tests
// Cleanup
await pm.functionsPage.deleteFunctionByName(functionName);
});
});
test.describe('Error Handling', () => {
test('P2: #ResultArray# with invalid return value', { tag: ['@P2', '@errorHandling'] }, async ({ page }) => {
testLogger.info('Test: #ResultArray# error handling for non-array return');
const functionName = `invalid_array_${Date.now()}`;
const jsCode = `#ResultArray#
// With #ResultArray#, use 'rows' variable
// This is valid - just marks rows as invalid
for (var i = 0; i < rows.length; i++) {
rows[i].invalid = true;
}`;
await pm.functionsPage.clickAddFunctionButton();
await pm.functionsPage.fillFunctionName(functionName);
await pm.functionsPage.selectJavaScriptType();
await pm.functionsPage.enterFunctionCode(jsCode);
// Try to save - may succeed (validation at runtime)
await pm.functionsPage.clickSaveButton();
await page.waitForTimeout(2000);
testLogger.info('✅ Function created - validation occurs at runtime');
// Cleanup
const cancelVisible = await page.locator('[data-test="add-function-cancel-btn"]').isVisible({ timeout: 2000 }).catch(() => false);
if (cancelVisible) {
await pm.functionsPage.clickCancelButton();
} else {
await pm.functionsPage.deleteFunctionByName(functionName);
}
});
});
});