mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
test(editor): Migrate Paired item e2e tests to playwright (#19536)
This commit is contained in:
committed by
GitHub
parent
c6e147550c
commit
66e3f2b639
@@ -88,6 +88,10 @@ export class NodeDetailsViewPage extends BasePage {
|
||||
return this.page.getByTestId('parameter-expression-preview-value');
|
||||
}
|
||||
|
||||
getParameterExpressionPreviewOutput() {
|
||||
return this.page.getByTestId('parameter-expression-preview-output');
|
||||
}
|
||||
|
||||
getInlineExpressionEditorPreview() {
|
||||
return this.page.getByTestId('inline-expression-editor-output');
|
||||
}
|
||||
|
||||
320
packages/testing/playwright/tests/ui/24-ndv-paired-item.spec.ts
Normal file
320
packages/testing/playwright/tests/ui/24-ndv-paired-item.spec.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('NDV Paired Items', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('maps paired input and output items', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('Test_workflow_5.json');
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
|
||||
await n8n.canvas.openNode('Sort');
|
||||
|
||||
await expect(n8n.ndv.inputPanel.get()).toContainText('6 items');
|
||||
await expect(n8n.ndv.outputPanel.get()).toContainText('6 items');
|
||||
|
||||
await n8n.ndv.inputPanel.switchDisplayMode('table');
|
||||
await n8n.ndv.outputPanel.switchDisplayMode('table');
|
||||
|
||||
// input to output
|
||||
const inputTableRow1 = n8n.ndv.inputPanel.getTableRow(1);
|
||||
await expect(inputTableRow1).toBeVisible();
|
||||
await expect(inputTableRow1).toHaveAttribute('data-test-id', 'hovering-item');
|
||||
|
||||
// Move the cursor to simulate hover behavior
|
||||
await inputTableRow1.hover();
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(4)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.inputPanel.getTableRow(2).hover();
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(2)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.inputPanel.getTableRow(3).hover();
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(6)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
// output to input
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(4)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.outputPanel.getTableRow(4).hover();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.outputPanel.getTableRow(2).hover();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(2)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.outputPanel.getTableRow(6).hover();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(3)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(4)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
});
|
||||
|
||||
test('maps paired input and output items based on selected input node', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('Test_workflow_5.json');
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
await n8n.canvas.openNode('Set2');
|
||||
|
||||
await expect(n8n.ndv.inputPanel.get()).toContainText('6 items');
|
||||
await expect(n8n.ndv.outputPanel.getRunSelectorInput()).toHaveValue('2 of 2 (6 items)');
|
||||
|
||||
await n8n.ndv.inputPanel.switchDisplayMode('table');
|
||||
await n8n.ndv.outputPanel.switchDisplayMode('table');
|
||||
|
||||
// Default hover state should have first item from input node highlighted
|
||||
const hoveringItem = n8n.page.locator('[data-test-id="hovering-item"]');
|
||||
await expect(hoveringItem).toContainText('1111');
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('1111');
|
||||
|
||||
// Select different input node and check that the hover state is updated
|
||||
await n8n.ndv.inputPanel.getNodeInputOptions().click();
|
||||
await n8n.page.getByRole('option', { name: 'Set1' }).click();
|
||||
await expect(hoveringItem).toContainText('1000');
|
||||
|
||||
// Hover on input item and verify output hover state
|
||||
await n8n.ndv.inputPanel.getTable().locator('text=1000').hover();
|
||||
await expect(n8n.ndv.outputPanel.get().locator('[data-test-id="hovering-item"]')).toContainText(
|
||||
'1000',
|
||||
);
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('1000');
|
||||
|
||||
// Switch back to Sort input
|
||||
await n8n.ndv.inputPanel.getNodeInputOptions().click();
|
||||
await n8n.page.getByRole('option', { name: 'Sort' }).click();
|
||||
await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
|
||||
|
||||
await expect(hoveringItem).toContainText('1111');
|
||||
await n8n.ndv.inputPanel.getTable().locator('text=1111').hover();
|
||||
await expect(n8n.ndv.outputPanel.get().locator('[data-test-id="hovering-item"]')).toContainText(
|
||||
'1111',
|
||||
);
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('1111');
|
||||
});
|
||||
|
||||
test('maps paired input and output items based on selected run', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('Test_workflow_5.json');
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
await n8n.canvas.openNode('Set3');
|
||||
|
||||
await n8n.ndv.inputPanel.switchDisplayMode('table');
|
||||
await n8n.ndv.outputPanel.switchDisplayMode('table');
|
||||
|
||||
// Start from linked state
|
||||
await n8n.ndv.ensureOutputRunLinking(true);
|
||||
await n8n.ndv.inputPanel.getTbodyCell(0, 0).click(); // remove tooltip
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getRunSelectorInput()).toHaveValue('2 of 2 (6 items)');
|
||||
await expect(n8n.ndv.outputPanel.getRunSelectorInput()).toHaveValue('2 of 2 (6 items)');
|
||||
|
||||
await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
|
||||
await expect(n8n.ndv.inputPanel.getRunSelectorInput()).toHaveValue('1 of 2 (6 items)');
|
||||
await expect(n8n.ndv.outputPanel.getRunSelectorInput()).toHaveValue('1 of 2 (6 items)');
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toContainText('1111');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toContainText('1111');
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(3)).toContainText('4444');
|
||||
await n8n.ndv.outputPanel.getTableRow(3).hover();
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(3)).toContainText('4444');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(3)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.changeOutputRunSelector('2 of 2 (6 items)');
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toContainText('1000');
|
||||
await n8n.ndv.inputPanel.getTableRow(1).hover();
|
||||
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toContainText('1000');
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(3)).toContainText('2000');
|
||||
await n8n.ndv.outputPanel.getTableRow(3).hover();
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(3)).toContainText('2000');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(3)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
});
|
||||
|
||||
test('can pair items between input and output across branches and runs', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('Test_workflow_5.json');
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
await n8n.canvas.openNode('IF');
|
||||
|
||||
await n8n.ndv.inputPanel.switchDisplayMode('table');
|
||||
await n8n.ndv.outputPanel.switchDisplayMode('table');
|
||||
|
||||
// Switch to False Branch
|
||||
await n8n.ndv.outputPanel.get().getByText('False Branch (2 items)').click();
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toContainText('8888');
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(5)).toContainText('8888');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(5)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(2)).toContainText('9999');
|
||||
await n8n.ndv.outputPanel.getTableRow(2).hover();
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(6)).toContainText('9999');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(6)).toHaveAttribute(
|
||||
'data-test-id',
|
||||
'hovering-item',
|
||||
);
|
||||
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.openNode('Set5');
|
||||
|
||||
// Switch to True Branch for input
|
||||
await n8n.ndv.inputPanel.get().getByText('True Branch').click();
|
||||
|
||||
await n8n.ndv.changeOutputRunSelector('(2 items)');
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toContainText('8888');
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
|
||||
// Should not have matching hover state when branches don't match
|
||||
const hoveringItems = n8n.ndv.inputPanel.get().locator('[data-test-id="hovering-item"]');
|
||||
await expect(hoveringItems).toHaveCount(0);
|
||||
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toContainText('1111');
|
||||
await n8n.ndv.inputPanel.getTableRow(1).hover();
|
||||
const outputHoveringItems = n8n.ndv.outputPanel.get().locator('[data-test-id="hovering-item"]');
|
||||
await expect(outputHoveringItems).toHaveCount(0);
|
||||
|
||||
// Switch to False Branch
|
||||
await n8n.ndv.inputPanel.get().getByText('False Branch').click();
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toContainText('8888');
|
||||
await n8n.ndv.inputPanel.getTableRow(1).hover();
|
||||
|
||||
await n8n.ndv.changeOutputRunSelector('(4 items)');
|
||||
await expect(n8n.ndv.outputPanel.getTableRow(1)).toContainText('1111');
|
||||
await n8n.ndv.outputPanel.getTableRow(1).hover();
|
||||
|
||||
await n8n.ndv.changeOutputRunSelector('(2 items)');
|
||||
await expect(n8n.ndv.inputPanel.getTableRow(1)).toContainText('8888');
|
||||
await n8n.ndv.inputPanel.getTableRow(1).hover();
|
||||
await expect(n8n.ndv.outputPanel.get().locator('[data-test-id="hovering-item"]')).toContainText(
|
||||
'8888',
|
||||
);
|
||||
});
|
||||
|
||||
test('can resolve expression with paired item in multi-input node', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('expression_with_paired_item_in_multi_input_node.json');
|
||||
|
||||
await n8n.canvas.clickZoomToFitButton();
|
||||
|
||||
const PINNED_DATA = [
|
||||
{
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
id: 'abc',
|
||||
},
|
||||
{
|
||||
id: 'abc',
|
||||
},
|
||||
{
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await n8n.canvas.openNode('Get thread details1');
|
||||
await n8n.ndv.setPinnedData(PINNED_DATA);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
await n8n.canvas.openNode('Switch1');
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewOutput()).toContainText('1');
|
||||
|
||||
await n8n.ndv.getInlineExpressionEditorInput().click();
|
||||
await expect(n8n.ndv.getInlineExpressionEditorPreview()).toContainText('1');
|
||||
|
||||
// Select next item
|
||||
await n8n.ndv.expressionSelectNextItem();
|
||||
await expect(n8n.ndv.getInlineExpressionEditorPreview()).toContainText('3');
|
||||
|
||||
// Select next item again
|
||||
await n8n.ndv.expressionSelectNextItem();
|
||||
await expect(n8n.ndv.getInlineExpressionEditorPreview()).toContainText('1');
|
||||
|
||||
// Next button should be disabled
|
||||
await expect(n8n.ndv.getInlineExpressionEditorItemNextButton()).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"meta": {
|
||||
"instanceId": "abc"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "bcb6abdf-d34b-4ea7-a8ed-58155b708c43",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [20, 260]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\nitem.json.message_count = Math.min(item.json.messages.length, 3);\n}\n\nreturn $input.all();"
|
||||
},
|
||||
"id": "59c3889c-3671-4f49-b258-6131df8587d8",
|
||||
"name": "Set thread properties1",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 1,
|
||||
"position": [500, 520]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "thread",
|
||||
"operation": "get",
|
||||
"threadId": "={{ $json.id }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "e102b72e-1e47-4004-a6b9-38cef75f44a1",
|
||||
"name": "Get thread details1",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2,
|
||||
"position": [300, 520]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "expression",
|
||||
"output": "={{ $('Set thread properties1').item.json.message_count }}"
|
||||
},
|
||||
"id": "f3e42f07-df82-42ba-8e99-97cda707a9d9",
|
||||
"name": "Switch1",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 1,
|
||||
"position": [1220, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": true,
|
||||
"value2": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "c7fe521e-8c02-44bf-8a14-482b39749508",
|
||||
"name": "IF",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [720, 520]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "3b9f6a05-7f19-46c5-95d1-5dec732f00ae",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [960, 400]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get thread details1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set thread properties1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "IF",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get thread details1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set thread properties1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"IF": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Switch1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"No Operation, do nothing": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Switch1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user