diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts
deleted file mode 100644
index 6dad7d1619..0000000000
--- a/cypress/e2e/5-ndv.cy.ts
+++ /dev/null
@@ -1,966 +0,0 @@
-import { setCredentialValues } from '../composables/modals/credential-modal';
-import {
- clickCreateNewCredential,
- clickGetBackToCanvas,
- setParameterSelectByContent,
-} from '../composables/ndv';
-import { openNode } from '../composables/workflow';
-import {
- EDIT_FIELDS_SET_NODE_NAME,
- MANUAL_TRIGGER_NODE_DISPLAY_NAME,
- NOTION_NODE_NAME,
-} from '../constants';
-import { NDV, WorkflowPage } from '../pages';
-import { NodeCreator } from '../pages/features/node-creator';
-
-const workflowPage = new WorkflowPage();
-const ndv = new NDV();
-
-describe('NDV', () => {
- beforeEach(() => {
- workflowPage.actions.visit();
- workflowPage.actions.renameWithUniqueName();
- workflowPage.actions.saveWorkflowOnButtonClick();
- });
-
- it('should show up when double clicked on a node and close when Back to canvas clicked', () => {
- workflowPage.actions.addInitialNodeToCanvas('Manual');
- workflowPage.getters.canvasNodes().first().dblclick();
- ndv.getters.container().should('be.visible');
- ndv.getters.backToCanvas().click();
- ndv.getters.container().should('not.be.visible');
- });
-
- it('should show input panel when node is not connected', () => {
- workflowPage.actions.addInitialNodeToCanvas('Manual');
- workflowPage.actions.deselectAll();
- workflowPage.actions.addNodeToCanvas('Set');
- workflowPage.getters.canvasNodes().last().dblclick();
- ndv.getters.container().should('be.visible').should('contain', 'Wire me up');
- });
-
- it('should test webhook node', () => {
- workflowPage.actions.addInitialNodeToCanvas('Webhook');
- workflowPage.getters.canvasNodes().first().dblclick();
-
- ndv.actions.execute();
- ndv.getters.copyInput().click();
-
- cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
-
- cy.readClipboard().then((url) => {
- cy.request({
- method: 'GET',
- url,
- }).then((resp) => {
- expect(resp.status).to.eq(200);
- });
- });
-
- ndv.getters.outputDisplayMode().should('have.length.at.least', 1).and('be.visible');
- });
-
- it('should change input and go back to canvas', () => {
- cy.createFixtureWorkflow('NDV-test-select-input.json', 'NDV test select input');
- workflowPage.actions.zoomToFit();
- workflowPage.getters.canvasNodes().last().dblclick();
- ndv.actions.switchInputMode('Table');
- ndv.getters.inputSelect().click();
- ndv.getters.inputOption().last().click();
- ndv.getters.inputDataContainer().should('be.visible');
- ndv.getters.inputDataContainer().should('contain', 'start');
- ndv.getters.backToCanvas().click();
- ndv.getters.container().should('not.be.visible');
- cy.shouldNotHaveConsoleErrors();
- });
-
- it('should show correct validation state for resource locator params', () => {
- workflowPage.actions.addNodeToCanvas('Typeform', true, true);
- ndv.getters.container().should('be.visible');
- cy.get('.has-issues').should('have.length', 0);
- cy.get('[class*=hasIssues]').should('have.length', 0);
- ndv.getters.backToCanvas().click();
- // Both credentials and resource locator errors should be visible
- workflowPage.actions.openNode('Typeform');
- cy.get('.has-issues').should('have.length', 1);
- cy.get('[class*=hasIssues]').should('have.length', 1);
- });
-
- it('should show validation errors only after blur or re-opening of NDV', () => {
- workflowPage.actions.addNodeToCanvas('Manual');
- workflowPage.actions.addNodeToCanvas('Airtable', true, true, 'Search records');
- ndv.getters.container().should('be.visible');
- cy.get('.has-issues').should('have.length', 0);
- ndv.getters.parameterInput('table').find('input').eq(1).focus().blur();
- ndv.getters.parameterInput('base').find('input').eq(1).focus().blur();
- cy.get('.has-issues').should('have.length', 2);
- ndv.getters.backToCanvas().click();
- workflowPage.actions.openNode('Search records');
- cy.get('.has-issues').should('have.length', 2);
- cy.get('[class*=hasIssues]').should('have.length', 1);
- });
-
- // Correctly failing in V2 - node issues are only shows after execution
- it('should show all validation errors when opening pasted node', () => {
- cy.createFixtureWorkflow('Test_workflow_ndv_errors.json', 'Validation errors');
- workflowPage.getters.canvasNodes().should('have.have.length', 1);
- workflowPage.actions.openNode('Airtable');
- cy.get('.has-issues').should('have.length', 3);
- cy.get('[class*=hasIssues]').should('have.length', 1);
- });
-
- it('should render run errors correctly', () => {
- cy.createFixtureWorkflow('Test_workflow_ndv_run_error.json', 'Run error');
- workflowPage.actions.openNode('Error');
- ndv.actions.execute();
- ndv.getters
- .nodeRunErrorMessage()
- .should(
- 'have.text',
- "Paired item data for item from node 'Break pairedItem chain' is unavailable. Ensure 'Break pairedItem chain' is providing the required output.",
- );
- ndv.getters
- .nodeRunErrorDescription()
- .should(
- 'contains.text',
- "An expression here won't work because it uses .item and n8n can't figure out the matching item.",
- );
- ndv.getters.nodeRunErrorIndicator().should('be.visible');
- ndv.getters.nodeRunTooltipIndicator().should('be.visible');
- // The error details should be hidden behind a tooltip
- ndv.getters.nodeRunTooltipIndicator().should('not.contain', 'Start Time');
- ndv.getters.nodeRunTooltipIndicator().should('not.contain', 'Execution Time');
- });
-
- it('should save workflow using keyboard shortcut from NDV', () => {
- workflowPage.actions.addNodeToCanvas('Manual');
- workflowPage.actions.addNodeToCanvas('Set', true, true);
- ndv.getters.container().should('be.visible');
- workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
- workflowPage.getters.isWorkflowSaved();
- });
-
- describe('test output schema view', () => {
- const schemaKeys = [
- 'id',
- 'name',
- 'email',
- 'notes',
- 'country',
- 'created',
- 'objectValue',
- 'prop1',
- 'prop2',
- ];
- function setupSchemaWorkflow() {
- cy.createFixtureWorkflow('Test_workflow_schema_test.json');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.openNode('Set');
- ndv.actions.execute();
- }
-
- it('should switch to output schema view and validate it', () => {
- setupSchemaWorkflow();
- ndv.getters.outputDisplayMode().children().should('have.length', 3);
- ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
- ndv.actions.switchOutputMode('Schema');
- ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
-
- schemaKeys.forEach((key) => {
- ndv.getters
- .outputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .contains(key)
- .should('exist');
- });
- });
- it('should preserve schema view after execution', () => {
- setupSchemaWorkflow();
- ndv.actions.switchOutputMode('Schema');
- ndv.actions.execute();
- ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
- });
- it('should collapse and expand nested schema object', () => {
- setupSchemaWorkflow();
- const expandedObjectProps = ['prop1', 'prop2'];
- const getObjectValueItem = () =>
- ndv.getters
- .outputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .filter(':contains("objectValue")');
- ndv.actions.switchOutputMode('Schema');
-
- expandedObjectProps.forEach((key) => {
- ndv.getters
- .outputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .contains(key)
- .should('be.visible');
- });
- getObjectValueItem().find('.toggle').click({ force: true });
- expandedObjectProps.forEach((key) => {
- ndv.getters
- .outputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .contains(key)
- .should('not.be.visible');
- });
- });
-
- it('should not display pagination for schema', () => {
- setupSchemaWorkflow();
- ndv.getters.backToCanvas().click();
- workflowPage.actions.deselectAll();
- workflowPage.getters.canvasNodeByName('Set').click();
- workflowPage.actions.addNodeToCanvas(
- 'Customer Datastore (n8n training)',
- true,
- true,
- 'Get All People',
- );
- ndv.actions.execute();
- ndv.getters.outputPanel().contains('25 items').should('exist');
- ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
- ndv.actions.switchOutputMode('Schema');
- ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
- ndv.actions.switchOutputMode('JSON');
- ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
- });
- it('should display large schema', () => {
- cy.createFixtureWorkflow(
- 'Test_workflow_schema_test_pinned_data.json',
- 'NDV test schema view 2',
- );
- workflowPage.actions.zoomToFit();
- workflowPage.actions.openNode('Set');
-
- ndv.getters.outputPanel().contains('20 items').should('exist');
- ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
- ndv.actions.switchOutputMode('Schema');
- ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
- ndv.getters
- .outputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .should('have.length.above', 10);
- });
- });
-
- it('can link and unlink run selectors between input and output', () => {
- cy.createFixtureWorkflow('Test_workflow_5.json', 'Test');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.executeWorkflow();
- workflowPage.actions.openNode('Set3');
-
- ndv.actions.switchInputMode('Table');
- ndv.actions.switchOutputMode('Table');
-
- // Start from linked state
- ndv.getters.outputLinkRun().then(($el) => {
- const classList = Array.from($el[0].classList);
- if (!classList.includes('linked')) {
- ndv.actions.toggleOutputRunLinking();
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- }
- });
-
- ndv.getters
- .inputRunSelector()
- .should('exist')
- .find('input')
- .should('include.value', '2 of 2 (6 items)');
- ndv.getters
- .outputRunSelector()
- .should('exist')
- .find('input')
- .should('include.value', '2 of 2 (6 items)');
-
- ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
- ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
- ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111');
- ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111');
-
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
- ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
-
- // unlink
- ndv.actions.toggleOutputRunLinking();
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
- ndv.getters
- .inputRunSelector()
- .should('exist')
- .find('input')
- .should('include.value', '2 of 2 (6 items)');
-
- // link again
- ndv.actions.toggleOutputRunLinking();
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
-
- // unlink again
- ndv.actions.toggleInputRunLinking();
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
- ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
-
- // link again
- ndv.actions.toggleInputRunLinking();
- ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
- ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
- });
-
- it('should display parameter hints correctly', () => {
- workflowPage.actions.visit();
-
- cy.createFixtureWorkflow('Test_workflow_3.json', 'My test workflow 1');
- workflowPage.actions.openNode('Set1');
-
- ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions
-
- [
- {
- input: 'hello',
- },
- {
- input: '',
- output: '[empty]',
- },
- {
- input: ' test',
- },
- {
- input: ' ',
- },
- {
- input: '
',
- },
- ].forEach(({ input, output }) => {
- if (input) {
- ndv.actions.typeIntoParameterInput('value', input);
- }
- ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
-
- ndv.actions.validateExpressionPreview('value', output ?? input);
- ndv.getters.parameterInput('value').clear();
- });
- });
-
- it('webhook shoul fallback to webhookId if path is empty', () => {
- workflowPage.actions.addInitialNodeToCanvas('Webhook');
- workflowPage.getters.canvasNodes().first().dblclick();
-
- workflowPage.getters.nodeIssuesByName('Webhook').should('not.exist');
- ndv.getters.nodeExecuteButton().should('not.be.disabled');
- ndv.getters.triggerPanelExecuteButton().should('exist');
-
- ndv.getters.parameterInput('path').clear();
-
- const urrWithUUID =
- /https?:\/\/[^\/]+\/webhook-test\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
-
- cy.contains('Webhook URLs')
- .parent()
- .invoke('text')
- .then((text) => {
- const match = text.match(urrWithUUID);
- expect(match, 'Should contain dynamic URL with UUID').to.not.be.null;
- });
-
- ndv.actions.close();
-
- workflowPage.getters.canvasNodes().first().dblclick();
- ndv.getters.parameterInput('path').type('test-path');
-
- const urlWithCustomPath = /https?:\/\/[^\/]+\/webhook-test\/test-path/i;
-
- cy.contains('Webhook URLs')
- .parent()
- .invoke('text')
- .then((text) => {
- const match = text.match(urlWithCustomPath);
- expect(match, 'Should contain URL with custom path').to.not.be.null;
- });
-
- ndv.getters.nodeExecuteButton().should('not.be.disabled');
- ndv.getters.triggerPanelExecuteButton().should('exist');
-
- ndv.actions.close();
- workflowPage.getters.nodeIssuesByName('Webhook').should('not.exist');
- });
-
- it('should not push NDV header out with a lot of code in Code node editor', () => {
- workflowPage.actions.addInitialCodeNodeToCanvas({ keepNdvOpen: true });
- ndv.getters.parameterInput('jsCode').get('.cm-content').type('{selectall}').type('{backspace}');
- cy.fixture('Dummy_javascript.txt').then((code) => {
- ndv.getters.parameterInput('jsCode').get('.cm-content').paste(code);
- });
- ndv.getters.nodeExecuteButton().should('be.visible');
- });
-
- it('should allow editing code in fullscreen in the code editors', () => {
- // Code (JavaScript)
- workflowPage.actions.addInitialCodeNodeToCanvas({ keepNdvOpen: true });
- ndv.actions.openCodeEditorFullscreen();
-
- ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
- ndv.getters.codeEditorFullscreen().should('contain.text', 'foo()');
- ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
- ndv.getters.parameterInput('jsCode').get('.cm-content').should('contain.text', 'foo()');
- ndv.actions.close();
-
- // SQL
- workflowPage.actions.addNodeToCanvas('Postgres', true, true, 'Execute a SQL query');
- ndv.actions.openCodeEditorFullscreen();
-
- ndv.getters
- .codeEditorFullscreen()
- .type('{selectall}')
- .type('{backspace}')
- .paste('SELECT * FROM workflows');
- ndv.getters.codeEditorFullscreen().should('contain.text', 'SELECT * FROM workflows');
- ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
- ndv.getters
- .parameterInput('query')
- .get('.cm-content')
- .should('contain.text', 'SELECT * FROM workflows');
- ndv.actions.close();
-
- // HTML
- workflowPage.actions.addNodeToCanvas('HTML', true, true, 'Generate HTML template');
- ndv.actions.openCodeEditorFullscreen();
-
- ndv.getters
- .codeEditorFullscreen()
- .type('{selectall}')
- .type('{backspace}')
- .type('Hello World
');
- ndv.getters.codeEditorFullscreen().should('contain.text', 'Hello World
');
- ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
- ndv.getters
- .parameterInput('html')
- .get('.cm-content')
- .should('contain.text', 'Hello World
');
- ndv.actions.close();
-
- // JSON
- workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
- setParameterSelectByContent('mode', 'JSON');
- ndv.actions.openCodeEditorFullscreen();
- ndv.getters
- .codeEditorFullscreen()
- .type('{selectall}')
- .type('{backspace}')
- .type('{ "key": "value" }', { parseSpecialCharSequences: false });
- ndv.getters.codeEditorFullscreen().should('contain.text', '{ "key": "value" }');
- cy.wait(200);
- ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
- ndv.getters
- .parameterInput('jsonOutput')
- .get('.cm-content')
- .should('contain.text', '{ "key": "value" }');
- });
-
- it('should not retrieve remote options when a parameter value changes', () => {
- cy.intercept(
- 'POST',
- '/rest/dynamic-node-parameters/options',
- cy.spy().as('fetchParameterOptions'),
- );
- workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
- // Type something into the field
- ndv.actions.typeIntoParameterInput('otherField', 'test');
- // Should call the endpoint only once (on mount), not for every keystroke
- cy.get('@fetchParameterOptions').should('have.been.calledOnce');
- });
-
- describe('floating nodes', () => {
- function getFloatingNodeByPosition(
- position: 'inputMain' | 'outputMain' | 'outputSub' | 'inputSub',
- ) {
- return cy.get(`[data-node-placement=${position}]`);
- }
-
- it('should traverse floating nodes with mouse', () => {
- cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
-
- workflowPage.actions.deselectAll();
-
- workflowPage.getters.canvasNodes().first().dblclick();
- getFloatingNodeByPosition('inputMain').should('not.exist');
- getFloatingNodeByPosition('outputMain').should('exist');
- // Traverse 4 connected node forwards
- Array.from(Array(4).keys()).forEach((i) => {
- getFloatingNodeByPosition('outputMain').click({ force: true });
- ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`);
- getFloatingNodeByPosition('inputMain').should('exist');
- getFloatingNodeByPosition('outputMain').should('exist');
- ndv.actions.close();
- // These two lines are broken in V2
- workflowPage.getters.selectedNodes().should('have.length', 1);
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', `Node ${i + 1}`);
- workflowPage.getters.selectedNodes().first().dblclick();
- });
- getFloatingNodeByPosition('outputMain').click({ force: true });
- ndv.getters.nodeNameContainer().should('contain', 'Chain');
- // Traverse 4 connected node backwards
- Array.from(Array(4).keys()).forEach((i) => {
- getFloatingNodeByPosition('inputMain').click({ force: true });
- ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`);
- getFloatingNodeByPosition('outputMain').should('exist');
- getFloatingNodeByPosition('inputMain').should('exist');
- });
- getFloatingNodeByPosition('inputMain').click({ force: true });
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
- getFloatingNodeByPosition('inputMain').should('not.exist');
- getFloatingNodeByPosition('inputSub').should('not.exist');
- getFloatingNodeByPosition('outputSub').should('not.exist');
- ndv.actions.close();
- workflowPage.getters.selectedNodes().should('have.length', 1);
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
- });
-
- it('should traverse floating nodes with keyboard', () => {
- cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
- workflowPage.actions.deselectAll();
-
- workflowPage.getters.canvasNodes().first().dblclick();
- getFloatingNodeByPosition('inputMain').should('not.exist');
- getFloatingNodeByPosition('outputMain').should('exist');
- // Traverse 4 connected node forwards
- Array.from(Array(4).keys()).forEach((i) => {
- cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']);
- ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`);
- getFloatingNodeByPosition('inputMain').should('exist');
- getFloatingNodeByPosition('outputMain').should('exist');
- ndv.actions.close();
- workflowPage.getters.selectedNodes().should('have.length', 1);
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', `Node ${i + 1}`);
- workflowPage.getters.selectedNodes().first().dblclick();
- });
-
- cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']);
- ndv.getters.nodeNameContainer().should('contain', 'Chain');
-
- // Traverse 4 connected node backwards
- Array.from(Array(4).keys()).forEach((i) => {
- cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']);
- ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`);
- getFloatingNodeByPosition('outputMain').should('exist');
- getFloatingNodeByPosition('inputMain').should('exist');
- });
- cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']);
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
- getFloatingNodeByPosition('inputMain').should('not.exist');
- getFloatingNodeByPosition('inputSub').should('not.exist');
- getFloatingNodeByPosition('outputSub').should('not.exist');
- ndv.actions.close();
- workflowPage.getters.selectedNodes().should('have.length', 1);
- workflowPage.getters
- .selectedNodes()
- .first()
- .should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
- });
-
- it('should connect floating sub-nodes', () => {
- const nodeCreator = new NodeCreator();
- const connectionGroups = [
- {
- title: 'Language Models',
- id: 'ai_languageModel',
- index: 0,
- },
- {
- title: 'Tools',
- id: 'ai_tool',
- index: 0,
- },
- ];
-
- workflowPage.actions.addInitialNodeToCanvas('AI Agent', { keepNdvOpen: true });
-
- connectionGroups.forEach((group) => {
- cy.getByTestId(`add-subnode-${group.id}-${group.index}`).should('exist');
- cy.getByTestId(`add-subnode-${group.id}-${group.index}`).click();
-
- cy.getByTestId('nodes-list-header').contains(group.title).should('exist');
- // Add HTTP Request tool
- nodeCreator.getters.getNthCreatorItem(2).click();
- getFloatingNodeByPosition('outputSub').should('exist');
- getFloatingNodeByPosition('outputSub').click({ force: true });
-
- if (group.id === 'ai_languageModel') {
- cy.getByTestId(`add-subnode-${group.id}-${group.index}`).should('not.exist');
- } else {
- cy.getByTestId(`add-subnode-${group.id}-${group.index}`).should('exist');
- // Expand the subgroup
- cy.getByTestId('subnode-connection-group-ai_tool-0').click();
- cy.getByTestId(`add-subnode-${group.id}-${group.index}`).click();
- // Add HTTP Request tool
- nodeCreator.getters.getNthCreatorItem(2).click();
- getFloatingNodeByPosition('outputSub').click({ force: true });
- cy.getByTestId('subnode-connection-group-ai_tool-0')
- .findChildByTestId('floating-subnode')
- .should('have.length', 2);
- }
- });
-
- // Since language model has no credentials set, it should show an error
- // Since HTTP Request tool requires URL it would also show an error(2 errors, 1 for each tool node)
- cy.get('[class*=hasIssues]').should('have.length', 3);
- });
-
- it('should have the floating nodes in correct order', () => {
- cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
-
- workflowPage.actions.deselectAll();
-
- // The first merge node has the wires crossed, so `Edit Fields1` is first in the order of connected nodes
- openNode('Merge');
- getFloatingNodeByPosition('inputMain').should('exist');
- getFloatingNodeByPosition('inputMain').should('have.length', 2);
- getFloatingNodeByPosition('inputMain')
- .first()
- .should('have.attr', 'data-node-name', 'Edit Fields1');
- getFloatingNodeByPosition('inputMain')
- .last()
- .should('have.attr', 'data-node-name', 'Edit Fields0');
-
- clickGetBackToCanvas();
-
- // The second merge node does not have wires crossed, so `Edit Fields0` is first
- openNode('Merge1');
- getFloatingNodeByPosition('inputMain').should('exist');
- getFloatingNodeByPosition('inputMain').should('have.length', 2);
- getFloatingNodeByPosition('inputMain')
- .first()
- .should('have.attr', 'data-node-name', 'Edit Fields0');
- getFloatingNodeByPosition('inputMain')
- .last()
- .should('have.attr', 'data-node-name', 'Edit Fields1');
- });
- });
-
- it('should show node name and version in settings', () => {
- cy.createFixtureWorkflow('Test_workflow_ndv_version.json', 'NDV test version');
-
- workflowPage.actions.openNode('Edit Fields (old)');
- ndv.actions.openSettings();
- ndv.getters.nodeVersion().should('have.text', 'Set node version 2 (Latest version: 3.4)');
- ndv.actions.close();
-
- workflowPage.actions.openNode('Edit Fields (latest)');
- ndv.actions.openSettings();
- ndv.getters.nodeVersion().should('have.text', 'Edit Fields (Set) node version 3.4 (Latest)');
- ndv.actions.close();
-
- workflowPage.actions.openNode('Edit Fields (no typeVersion)');
- ndv.actions.openSettings();
- ndv.getters.nodeVersion().should('have.text', 'Edit Fields (Set) node version 3.4 (Latest)');
- ndv.actions.close();
-
- workflowPage.actions.openNode('Function');
- ndv.actions.openSettings();
- ndv.getters.nodeVersion().should('have.text', 'Function node version 1 (Deprecated)');
- ndv.actions.close();
- });
-
- it('Should render xml and html tags as strings and can search', () => {
- cy.createFixtureWorkflow('Test_workflow_xml_output.json', 'test');
-
- workflowPage.actions.executeWorkflow();
-
- workflowPage.actions.openNode('Edit Fields');
-
- ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
-
- ndv.getters
- .outputTableRow(1)
- .should('include.text', ' ');
-
- cy.document().trigger('keyup', { key: '/' });
- ndv.getters.searchInput().filter(':focus').type(' Introduction to XML John Doe 2020 1234567890 Data Science Basics Jane Smith 2019 0987654321 Programming in Python Bob Johnson 2021 5432109876 "}]',
- );
- ndv.getters.outputDataContainer().find('mark').should('have.text', '');
- });
-
- it('should properly show node execution indicator', () => {
- workflowPage.actions.addInitialCodeNodeToCanvas();
- // Should not show run info before execution
- ndv.getters.nodeRunSuccessIndicator().should('not.exist');
- ndv.getters.nodeRunErrorIndicator().should('not.exist');
- ndv.getters.nodeRunTooltipIndicator().should('not.exist');
- ndv.getters.nodeExecuteButton().click();
- ndv.getters.nodeRunSuccessIndicator().should('exist');
- ndv.getters.nodeRunTooltipIndicator().should('exist');
- });
-
- it('should properly show node execution indicator for multiple nodes', () => {
- workflowPage.actions.addInitialCodeNodeToCanvas();
- ndv.actions.typeIntoParameterInput('jsCode', 'testets');
- ndv.getters.backToCanvas().click();
- workflowPage.actions.executeWorkflow();
- // Manual tigger node should show success indicator
- workflowPage.actions.openNode('When clicking ‘Execute workflow’');
- ndv.getters.nodeRunSuccessIndicator().should('exist');
- ndv.getters.nodeRunTooltipIndicator().should('exist');
- // Code node should show error
- ndv.getters.backToCanvas().click();
- workflowPage.actions.openNode('Code');
- ndv.getters.nodeRunErrorIndicator().should('exist');
- });
-
- it('Should clear mismatched collection parameters', () => {
- workflowPage.actions.addInitialNodeToCanvas('LDAP', {
- keepNdvOpen: true,
- action: 'Create a new entry',
- });
- // Add some attributes in Create operation
- cy.getByTestId('parameter-item').contains('Add Attributes').click();
- ndv.actions.changeNodeOperation('Update');
- // Attributes should be empty after operation change
- cy.getByTestId('parameter-item').contains('Currently no items exist').should('exist');
- });
-
- it('Should keep RLC values after operation change', () => {
- const TEST_DOC_ID = '1111';
- workflowPage.actions.addInitialNodeToCanvas('Google Sheets', {
- keepNdvOpen: true,
- action: 'Append row in sheet',
- });
- ndv.actions.setRLCValue('documentId', TEST_DOC_ID);
- ndv.actions.changeNodeOperation('Update Row');
- ndv.getters.resourceLocatorInput('documentId').find('input').should('have.value', TEST_DOC_ID);
- });
-
- it('Should not clear resource/operation after credential change', () => {
- workflowPage.actions.addInitialNodeToCanvas('Discord', {
- keepNdvOpen: true,
- action: 'Delete a message',
- });
-
- clickCreateNewCredential();
- setCredentialValues({
- botToken: 'sk_test_123',
- });
-
- ndv.getters.parameterInput('resource').find('input').should('have.value', 'Message');
- ndv.getters.parameterInput('operation').find('input').should('have.value', 'Delete');
- });
-
- it('Should show a notice when remote options cannot be fetched because of missing credentials', () => {
- cy.intercept('POST', '/rest/dynamic-node-parameters/options', { statusCode: 403 }).as(
- 'parameterOptions',
- );
-
- workflowPage.actions.addInitialNodeToCanvas(NOTION_NODE_NAME, {
- keepNdvOpen: true,
- action: 'Update a database page',
- });
-
- ndv.actions.addItemToFixedCollection('propertiesUi');
- ndv.getters
- .parameterInput('key')
- .find('input')
- .should('have.value', 'Set up credential to see options');
- });
-
- it('Should show error state when remote options cannot be fetched', () => {
- cy.intercept('POST', '/rest/dynamic-node-parameters/options', { statusCode: 500 }).as(
- 'parameterOptions',
- );
-
- workflowPage.actions.addInitialNodeToCanvas(NOTION_NODE_NAME, {
- keepNdvOpen: true,
- action: 'Update a database page',
- });
-
- clickCreateNewCredential();
- setCredentialValues({
- apiKey: 'sk_test_123',
- });
-
- ndv.actions.addItemToFixedCollection('propertiesUi');
- ndv.getters
- .parameterInput('key')
- .find('input')
- .should('have.value', 'Error fetching options from Notion');
- });
-
- // Correctly failing in V2 - NodeCreator is not opened after clicking on the link
- it('Should open appropriate node creator after clicking on connection hint link', () => {
- const nodeCreator = new NodeCreator();
- const hintMapper = {
- Memory: 'AI Nodes',
- 'Output Parser': 'AI Nodes',
- 'Token Splitter': 'Document Loaders',
- Tool: 'AI Nodes',
- Embeddings: 'Vector Stores',
- 'Vector Store': 'Retrievers',
- };
- cy.createFixtureWorkflow(
- 'open_node_creator_for_connection.json',
- 'open_node_creator_for_connection',
- );
-
- Object.entries(hintMapper).forEach(([node, group]) => {
- workflowPage.actions.openNode(node);
- // This fails to open the NodeCreator
- cy.get('[data-action=openSelectiveNodeCreator]').contains('Insert one').click();
- nodeCreator.getters.activeSubcategory().should('contain', group);
- cy.realPress('Escape');
- });
- });
-
- it('should allow selecting item for expressions', () => {
- workflowPage.actions.visit();
-
- cy.createFixtureWorkflow('Test_workflow_3.json', 'My test workflow 2');
- workflowPage.actions.openNode('Set');
-
- ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions
- ndv.actions.typeIntoParameterInput('value', '{{', {
- parseSpecialCharSequences: false,
- });
- ndv.actions.typeIntoParameterInput('value', '$json.input[0].count');
- ndv.getters.inlineExpressionEditorOutput().should('have.text', '0');
-
- ndv.actions.expressionSelectNextItem();
- ndv.getters.inlineExpressionEditorOutput().should('have.text', '1');
- ndv.getters.inlineExpressionEditorItemInput().should('have.value', '1');
- ndv.getters.inlineExpressionEditorItemNextButton().should('be.disabled');
-
- ndv.actions.expressionSelectPrevItem();
- ndv.getters.inlineExpressionEditorOutput().should('have.text', '0');
- ndv.getters.inlineExpressionEditorItemInput().should('have.value', '0');
- ndv.getters.inlineExpressionEditorItemPrevButton().should('be.disabled');
-
- ndv.actions.expressionSelectItem(1);
- ndv.getters.inlineExpressionEditorOutput().should('have.text', '1');
- });
-
- it('should show data from the correct output in schema view', () => {
- cy.createFixtureWorkflow('Test_workflow_multiple_outputs.json');
- workflowPage.actions.zoomToFit();
-
- workflowPage.actions.executeWorkflow();
- workflowPage.actions.openNode('Only Item 1');
- ndv.getters.inputPanel().should('be.visible');
- ndv.getters
- .inputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .should('contain.text', 'onlyOnItem1');
- ndv.actions.close();
-
- workflowPage.actions.openNode('Only Item 2');
- ndv.getters.inputPanel().should('be.visible');
- ndv.getters
- .inputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .should('contain.text', 'onlyOnItem2');
- ndv.actions.close();
-
- workflowPage.actions.openNode('Only Item 3');
- ndv.getters.inputPanel().should('be.visible');
- ndv.getters
- .inputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .should('contain.text', 'onlyOnItem3');
- });
-
- it('should keep search expanded after Execute step node run', () => {
- cy.createFixtureWorkflow('Test_ndv_search.json');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.executeWorkflow();
- workflowPage.actions.openNode('Edit Fields');
- ndv.getters.outputPanel().should('be.visible');
- ndv.getters.outputPanel().findChildByTestId('ndv-search').click().type('US');
- ndv.getters.outputTableRow(1).find('mark').should('have.text', 'US');
-
- ndv.actions.execute();
- ndv.getters
- .outputPanel()
- .findChildByTestId('ndv-search')
- .should('be.visible')
- .should('have.value', 'US');
- });
-
- it('should not show items count when seaching in schema view', () => {
- cy.createFixtureWorkflow('Test_ndv_search.json');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.openNode('Edit Fields');
- ndv.getters.outputPanel().should('be.visible');
- ndv.actions.execute();
- ndv.actions.switchOutputMode('Schema');
- ndv.getters.outputPanel().find('[data-test-id=ndv-search]').click().type('US');
- ndv.getters.outputPanel().find('[data-test-id=ndv-items-count]').should('not.exist');
- });
-
- it('should show additional tooltip when seaching in schema view if no matches', () => {
- cy.createFixtureWorkflow('Test_ndv_search.json');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.openNode('Edit Fields');
- ndv.getters.outputPanel().should('be.visible');
- ndv.actions.execute();
- ndv.actions.switchOutputMode('Schema');
- ndv.getters.outputPanel().find('[data-test-id=ndv-search]').click().type('foo');
- ndv.getters
- .outputPanel()
- .contains('To search field values, switch to table or JSON view.')
- .should('exist');
- });
-
- it('ADO-2931 - should handle multiple branches of the same input with the first branch empty correctly', () => {
- cy.createFixtureWorkflow('Test_ndv_two_branches_of_same_parent_false_populated.json');
- workflowPage.actions.zoomToFit();
- workflowPage.actions.openNode('DebugHelper');
- ndv.getters.inputPanel().should('be.visible');
- ndv.getters.outputPanel().should('be.visible');
- ndv.actions.execute();
- // This ensures we rendered the inputPanel
- ndv.getters
- .inputPanel()
- .find('[data-test-id=run-data-schema-item]')
- .should('contain.text', 'a1');
- });
-});
diff --git a/packages/testing/playwright/fixtures/Dummy_javascript.txt b/packages/testing/playwright/fixtures/Dummy_javascript.txt
new file mode 100644
index 0000000000..9ccfe6c5d1
--- /dev/null
+++ b/packages/testing/playwright/fixtures/Dummy_javascript.txt
@@ -0,0 +1,42 @@
+var File = function(url, object){
+ File.list = Array.isArray(File.list)? File.list : [];
+ File.progress = File.progress || 0;
+ this.progress = 0;
+ this.object = object;
+ this.url = url;
+};
+
+File.indexOf = function(term){
+ for(var index in File.list){
+ var file = File.list[index];
+ if (file.equals(term) || file.url === term || file.object === term) {
+ return index;
+ }
+ }
+ return -1;
+};
+
+File.find = function(term){
+ var index = File.indexOf(term);
+ return ~index && File.list[index];
+};
+
+File.prototype.equals = function(file){
+ var isFileType = file instanceof File;
+ return isFileType && this.url === file.url && this.object === file.object;
+};
+
+File.prototype.save = function(update){
+ update = typeof update === 'undefined'? true : update;
+ if(Array.isArray(File.list)){
+ var index = File.indexOf(this);
+ if(~index && update) {
+ File.list[index] = this;
+ console.warn('File `%s` has been loaded before and updated now for: %O.', this.url, this);
+ }else File.list.push(this);
+ console.log(File.list)
+ }else{
+ File.list = [this];
+ }
+ return this;
+};
diff --git a/packages/testing/playwright/fixtures/Test_workflow_schema_test.json b/packages/testing/playwright/fixtures/Test_workflow_schema_test.json
new file mode 100644
index 0000000000..5eb5b8b4d2
--- /dev/null
+++ b/packages/testing/playwright/fixtures/Test_workflow_schema_test.json
@@ -0,0 +1,83 @@
+{
+ "name": "My workflow 8",
+ "nodes": [
+ {
+ "parameters": {
+ "operation": "getAllPeople",
+ "limit": 10
+ },
+ "id": "39cd80ce-5a8f-4339-b3d5-c4af969dd330",
+ "name": "Customer Datastore (n8n training)",
+ "type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
+ "typeVersion": 1,
+ "position": [940, 680]
+ },
+ {
+ "parameters": {
+ "values": {
+ "number": [
+ {
+ "name": "objectValue.prop1",
+ "value": 123
+ }
+ ],
+ "string": [
+ {
+ "name": "objectValue.prop2",
+ "value": "someText"
+ }
+ ]
+ },
+ "options": {
+ "dotNotation": true
+ }
+ },
+ "id": "6e4490f6-ba95-4400-beec-2caefdd4895a",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [1300, 680]
+ },
+ {
+ "parameters": {},
+ "id": "58512a93-dabf-4584-817f-27c608c1bdd5",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [720, 680]
+ }
+ ],
+ "pinData": {},
+ "connections": {
+ "Customer Datastore (n8n training)": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Customer Datastore (n8n training)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "4a4f292a-92be-427c-848a-9582527f5ed3",
+ "id": "8",
+ "meta": {
+ "instanceId": "032eceae7493054b723340499be69ecbf4cbe28a7ec6df676b759000750b968d"
+ },
+ "tags": []
+}
diff --git a/packages/testing/playwright/pages/CanvasPage.ts b/packages/testing/playwright/pages/CanvasPage.ts
index 985aeb1bd8..c7364d83ae 100644
--- a/packages/testing/playwright/pages/CanvasPage.ts
+++ b/packages/testing/playwright/pages/CanvasPage.ts
@@ -17,6 +17,19 @@ export class CanvasPage extends BasePage {
return this.page.getByTestId('node-creator-item-name').getByText(subItemText, { exact: true });
}
+ getNthCreatorItem(index: number): Locator {
+ return this.page.getByTestId('node-creator-item').nth(index);
+ }
+
+ getNodeCreatorHeader(text?: string) {
+ const header = this.page.getByTestId('nodes-list-header');
+ return text ? header.filter({ hasText: text }) : header.first();
+ }
+
+ async selectNodeCreatorItemByText(nodeName: string) {
+ await this.page.getByText(nodeName).click();
+ }
+
nodeByName(nodeName: string): Locator {
return this.page.locator(`[data-test-id="canvas-node"][data-node-name="${nodeName}"]`);
}
@@ -546,6 +559,12 @@ export class CanvasPage extends BasePage {
await this.getManualChatInput().fill(message);
await this.getManualChatModal().locator('.chat-input-send-button').click();
}
+ /**
+ * Get all currently selected nodes on the canvas
+ */
+ getSelectedNodes() {
+ return this.page.locator('[data-test-id="canvas-node"].selected');
+ }
async openExecutions() {
await this.page.getByTestId('radio-button-executions').click();
diff --git a/packages/testing/playwright/pages/NodeDetailsViewPage.ts b/packages/testing/playwright/pages/NodeDetailsViewPage.ts
index 788857b699..59ffa32084 100644
--- a/packages/testing/playwright/pages/NodeDetailsViewPage.ts
+++ b/packages/testing/playwright/pages/NodeDetailsViewPage.ts
@@ -23,11 +23,6 @@ export class NodeDetailsViewPage extends BasePage {
return this.getContainer().locator('.parameter-item').filter({ hasText: labelName });
}
- /**
- * Fill a parameter input field
- * @param labelName - The label of the parameter e.g URL
- * @param value - The value to fill in the input field e.g https://foo.bar
- */
async fillParameterInput(labelName: string, value: string) {
await this.getParameterByLabel(labelName).getByTestId('parameter-input-field').fill(value);
}
@@ -119,21 +114,14 @@ export class NodeDetailsViewPage extends BasePage {
return this.getOutputTableRow(row).locator('td').nth(col);
}
- /**
- * Get a cell from the output table body, this doesn't include the header row
- * @param row - The row index
- * @param col - The column index
- */
getOutputTbodyCell(row: number, col: number) {
return this.getOutputTable().locator('tbody tr').nth(row).locator('td').nth(col);
}
- // Pin data operations
async setPinnedData(data: object | string) {
const pinnedData = typeof data === 'string' ? data : JSON.stringify(data);
await this.getEditPinnedDataButton().click();
- // Wait for editor to appear and use broader selector
const editor = this.getOutputPanel().locator('[contenteditable="true"]');
await editor.waitFor();
await editor.click();
@@ -150,7 +138,6 @@ export class NodeDetailsViewPage extends BasePage {
await editor.click();
await editor.fill('');
- // Set clipboard data and paste
await this.page.evaluate(async (jsonData) => {
await navigator.clipboard.writeText(JSON.stringify(jsonData));
}, data);
@@ -163,7 +150,6 @@ export class NodeDetailsViewPage extends BasePage {
await this.getRunDataPaneHeader().locator('button:visible').filter({ hasText: 'Save' }).click();
}
- // Assignment collection methods for advanced tests
getAssignmentCollectionAdd(paramName: string) {
return this.page
.getByTestId(`assignment-collection-${paramName}`)
@@ -192,6 +178,10 @@ export class NodeDetailsViewPage extends BasePage {
return await this.page.request.get(path);
}
+ getWebhookUrl() {
+ return this.page.locator('.webhook-url').textContent();
+ }
+
getVisiblePoppers() {
return this.page.locator('.el-popper:visible');
}
@@ -206,104 +196,56 @@ export class NodeDetailsViewPage extends BasePage {
async typeInExpressionEditor(text: string) {
const editor = this.getInlineExpressionEditorInput();
await editor.click();
- // We have to use type() instead of fill() because the editor is a CodeMirror editor
await editor.type(text);
}
- /**
- * Get parameter input by name (for Code node and similar)
- * @param parameterName - The name of the parameter e.g 'jsCode', 'mode'
- */
getParameterInput(parameterName: string) {
return this.page.getByTestId(`parameter-input-${parameterName}`);
}
- /**
- * Get parameter input field
- * @param parameterName - The name of the parameter
- */
getParameterInputField(parameterName: string) {
return this.getParameterInput(parameterName).locator('input');
}
- /**
- * Select option in parameter dropdown (improved with Playwright best practices)
- * @param parameterName - The parameter name
- * @param optionText - The text of the option to select
- */
async selectOptionInParameterDropdown(parameterName: string, optionText: string) {
const dropdown = this.getParameterInput(parameterName);
await dropdown.click();
- // Wait for dropdown to be visible and select option - following Playwright best practices
await this.page.getByRole('option', { name: optionText }).click();
}
- /**
- * Click parameter dropdown by name (test-id based selector)
- * @param parameterName - The parameter name e.g 'httpMethod', 'authentication'
- */
async clickParameterDropdown(parameterName: string): Promise {
await this.clickByTestId(`parameter-input-${parameterName}`);
}
- /**
- * Select option from visible dropdown using Playwright role-based selectors
- * This follows the pattern used in working n8n tests
- * @param optionText - The text of the option to select
- */
async selectFromVisibleDropdown(optionText: string): Promise {
- // Use Playwright's role-based selector - this is more reliable than CSS selectors
await this.page.getByRole('option', { name: optionText }).click();
}
- /**
- * Fill parameter input field by parameter name
- * @param parameterName - The parameter name e.g 'path', 'url'
- * @param value - The value to fill
- */
async fillParameterInputByName(parameterName: string, value: string): Promise {
const input = this.getParameterInputField(parameterName);
await input.click();
await input.fill(value);
}
- /**
- * Click parameter options expansion (e.g. for Response Code)
- */
async clickParameterOptions(): Promise {
await this.page.locator('.param-options').click();
}
- /**
- * Get visible Element UI popper (dropdown/popover)
- * Ported from Cypress pattern with Playwright selectors
- */
getVisiblePopper() {
return this.page.locator('.el-popper:visible');
}
- /**
- * Wait for parameter dropdown to be visible and ready for interaction
- * @param parameterName - The parameter name
- */
async waitForParameterDropdown(parameterName: string): Promise {
const dropdown = this.getParameterInput(parameterName);
await dropdown.waitFor({ state: 'visible' });
await expect(dropdown).toBeEnabled();
}
- /**
- * Click on a floating node in the NDV (for switching between connected nodes)
- * @param nodeName - The name of the node to click
- */
async clickFloatingNode(nodeName: string) {
await this.page.locator(`[data-test-id="floating-node"][data-node-name="${nodeName}"]`).click();
}
- /**
- * Execute the previous node (useful for providing input data)
- */
async executePrevious() {
await this.clickByTestId('execute-previous-node');
}
@@ -378,9 +320,14 @@ export class NodeDetailsViewPage extends BasePage {
async setParameterDropdown(parameterName: string, optionText: string): Promise {
await this.getParameterInput(parameterName).click();
+
await this.page.getByRole('option', { name: optionText }).click();
}
+ async changeNodeOperation(operationName: string): Promise {
+ await this.setParameterDropdown('operation', operationName);
+ }
+
async setParameterInput(parameterName: string, value: string): Promise {
await this.fillParameterInputByName(parameterName, value);
}
@@ -423,31 +370,21 @@ export class NodeDetailsViewPage extends BasePage {
case 'switch':
return await this.getSwitchParameterValue(parameterName);
default:
- // Fallback for unknown types
return (await this.getParameterInput(parameterName).textContent()) ?? '';
}
}
- /**
- * Get value from a text parameter - simplified approach
- */
private async getTextParameterValue(parameterName: string): Promise {
const parameterContainer = this.getParameterInput(parameterName);
const input = parameterContainer.locator('input').first();
return await input.inputValue();
}
- /**
- * Get value from a dropdown parameter
- */
private async getDropdownParameterValue(parameterName: string): Promise {
const selectedOption = this.getParameterInput(parameterName).locator('.el-select__tags-text');
return (await selectedOption.textContent()) ?? '';
}
- /**
- * Get value from a switch parameter
- */
private async getSwitchParameterValue(parameterName: string): Promise {
const switchElement = this.getParameterInput(parameterName).locator('.el-switch');
const isEnabled = (await switchElement.getAttribute('aria-checked')) === 'true';
@@ -503,8 +440,12 @@ export class NodeDetailsViewPage extends BasePage {
.first();
}
+ getNodeInputOptions() {
+ return this.getInputPanel().getByTestId('ndv-input-select');
+ }
+
async selectInputNode(nodeName: string) {
- const inputSelect = this.getInputPanel().getByTestId('ndv-input-select');
+ const inputSelect = this.getNodeInputOptions();
await inputSelect.click();
await this.page.getByRole('option', { name: nodeName }).click();
}
@@ -572,6 +513,35 @@ export class NodeDetailsViewPage extends BasePage {
return this.getInlineExpressionEditorInput().locator('.cm-content');
}
+ getInlineExpressionEditorOutput() {
+ return this.page.getByTestId('inline-expression-editor-output');
+ }
+
+ getInlineExpressionEditorItemInput() {
+ return this.page.getByTestId('inline-expression-editor-item-input').locator('input');
+ }
+
+ getInlineExpressionEditorItemPrevButton() {
+ return this.page.getByTestId('inline-expression-editor-item-prev');
+ }
+
+ getInlineExpressionEditorItemNextButton() {
+ return this.page.getByTestId('inline-expression-editor-item-next');
+ }
+
+ async expressionSelectNextItem() {
+ await this.getInlineExpressionEditorItemNextButton().click();
+ }
+
+ async expressionSelectPrevItem() {
+ await this.getInlineExpressionEditorItemPrevButton().click();
+ }
+
+ async typeIntoParameterInput(parameterName: string, content: string): Promise {
+ const input = this.getParameterInput(parameterName);
+ await input.type(content);
+ }
+
getInputTable() {
return this.getInputPanel().locator('table');
}
@@ -587,12 +557,10 @@ export class NodeDetailsViewPage extends BasePage {
async toggleCodeMode(switchTo: 'Run Once for Each Item' | 'Run Once for All Items') {
await this.getParameterInput('mode').click();
await this.page.getByRole('option', { name: switchTo }).click();
- // This is a workaround to wait for the code editor to reinitialize after the mode switch
// eslint-disable-next-line playwright/no-wait-for-timeout
await this.page.waitForTimeout(2500);
}
- // Pagination methods for output panel
getOutputPagination() {
return this.getOutputPanel().getByTestId('ndv-data-pagination');
}
@@ -616,17 +584,297 @@ export class NodeDetailsViewPage extends BasePage {
return (await this.getOutputTbodyCell(row, col).textContent()) ?? '';
}
- /**
- * Set parameter input value by clearing and filling (for parameters without standard test-id)
- * @param parameterName - The parameter name
- * @param value - The value to set
- */
async setParameterInputValue(parameterName: string, value: string): Promise {
const input = this.getParameterInput(parameterName).locator('input');
await input.clear();
await input.fill(value);
}
+ getNodeRunErrorMessage() {
+ return this.page.getByTestId('node-error-message');
+ }
+
+ getNodeRunErrorDescription() {
+ return this.page.getByTestId('node-error-description');
+ }
+
+ getInputRunSelector() {
+ return this.getInputPanel().getByTestId('run-selector');
+ }
+
+ getOutputRunSelector() {
+ return this.getOutputPanel().getByTestId('run-selector');
+ }
+
+ async toggleOutputRunLinking() {
+ await this.getOutputPanel().getByTestId('link-run').click();
+ }
+
+ async toggleInputRunLinking() {
+ await this.getInputPanel().getByTestId('link-run').click();
+ }
+
+ getOutputLinkRun() {
+ return this.getOutputPanel().getByTestId('link-run');
+ }
+
+ getInputLinkRun() {
+ return this.getInputPanel().getByTestId('link-run');
+ }
+
+ async isOutputRunLinkingEnabled() {
+ const linkButton = this.getOutputLinkRun();
+ const classList = await linkButton.getAttribute('class');
+ return classList?.includes('linked') ?? false;
+ }
+
+ async ensureOutputRunLinking(shouldBeLinked: boolean = true) {
+ const isLinked = await this.isOutputRunLinkingEnabled();
+ if (isLinked !== shouldBeLinked) {
+ await this.toggleOutputRunLinking();
+ }
+ }
+
+ async changeInputRunSelector(value: string) {
+ const selector = this.getInputRunSelector();
+ await selector.click();
+ await this.page.getByRole('option', { name: value }).click();
+ }
+
+ async changeOutputRunSelector(value: string) {
+ const selector = this.getOutputRunSelector();
+ await selector.click();
+ await this.page.getByRole('option', { name: value }).click();
+ }
+
+ async getInputRunSelectorValue() {
+ return await this.getInputRunSelector().locator('input').inputValue();
+ }
+
+ async getOutputRunSelectorValue() {
+ return await this.getOutputRunSelector().locator('input').inputValue();
+ }
+
+ getOutputDisplayMode() {
+ return this.getOutputPanel().getByTestId('ndv-output-display-mode');
+ }
+
+ getSchemaViewItems() {
+ return this.getOutputPanel().locator('[data-test-id="run-data-schema-item"]');
+ }
+
+ getSchemaItem(key: string) {
+ return this.getSchemaViewItems().filter({ hasText: key });
+ }
+
+ async expandSchemaItem(itemText: string) {
+ const item = this.getSchemaItem(itemText);
+ await item.locator('.toggle').click();
+ }
+
+ getPaginationContainer() {
+ return this.getOutputPanel().locator('[class*="_pagination"]');
+ }
+
+ getExecuteNodeButton() {
+ return this.page.getByTestId('node-execute-button');
+ }
+
+ getTriggerPanelExecuteButton() {
+ return this.page.getByTestId('trigger-execute-button');
+ }
+
+ async openCodeEditorFullscreen() {
+ await this.page.getByTestId('code-editor-fullscreen-button').click();
+ }
+
+ getCodeEditorFullscreen() {
+ return this.page.getByTestId('code-editor-fullscreen').locator('.cm-content');
+ }
+
+ getCodeEditorDialog() {
+ return this.page.locator('.el-dialog');
+ }
+
+ async closeCodeEditorDialog() {
+ await this.getCodeEditorDialog().locator('.el-dialog__close').click();
+ }
+
+ getWebhookTriggerListening() {
+ return this.page.getByTestId('trigger-listening');
+ }
+
+ getNodeRunSuccessIndicator() {
+ return this.page.getByTestId('node-run-status-success');
+ }
+
+ getNodeRunErrorIndicator() {
+ return this.page.getByTestId('node-run-status-danger');
+ }
+
+ getNodeRunTooltipIndicator() {
+ return this.page.getByTestId('node-run-info');
+ }
+
+ async openSettings() {
+ await this.page.getByTestId('tab-settings').click();
+ }
+
+ getNodeVersion() {
+ return this.page.getByTestId('node-version');
+ }
+
+ getOutputSearchInput() {
+ return this.getOutputPanel().getByTestId('ndv-search');
+ }
+
+ getInputSearchInput() {
+ return this.getInputPanel().getByTestId('ndv-search');
+ }
+
+ async searchOutputData(searchTerm: string) {
+ const searchInput = this.getOutputSearchInput();
+ await searchInput.click();
+ await searchInput.fill(searchTerm);
+ }
+
+ async searchInputData(searchTerm: string) {
+ const searchInput = this.getInputSearchInput();
+ await searchInput.click();
+ await searchInput.fill(searchTerm);
+ }
+
+ getOutputItemsCount() {
+ return this.getOutputPanel().getByTestId('ndv-items-count');
+ }
+
+ getInputItemsCount() {
+ return this.getInputPanel().getByTestId('ndv-items-count');
+ }
+
+ /**
+ * Type multiple values into the first available text parameter field
+ * Useful for testing multiple parameter changes
+ */
+ async fillFirstAvailableTextParameterMultipleTimes(values: string[]) {
+ const firstTextField = this.getNodeParameters().locator('input[type="text"]').first();
+ await firstTextField.click();
+
+ for (const value of values) {
+ await firstTextField.fill(value);
+ }
+ }
+
+ getFloatingNodeByPosition(position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub') {
+ return this.page.locator(`[data-node-placement="${position}"]`);
+ }
+
+ getNodeNameContainer() {
+ return this.getContainer().getByTestId('node-title-container');
+ }
+
+ async clickFloatingNodeByPosition(
+ position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub',
+ ) {
+ // eslint-disable-next-line playwright/no-force-option
+ await this.getFloatingNodeByPosition(position).click({ force: true });
+ }
+
+ async navigateToNextFloatingNodeWithKeyboard() {
+ await this.page.keyboard.press('Shift+Meta+Alt+ArrowRight');
+ }
+
+ async navigateToPreviousFloatingNodeWithKeyboard() {
+ await this.page.keyboard.press('Shift+Meta+Alt+ArrowLeft');
+ }
+
+ async verifyFloatingNodeName(
+ position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub',
+ nodeName: string,
+ index: number = 0,
+ ) {
+ const floatingNode = this.getFloatingNodeByPosition(position).nth(index);
+ await expect(floatingNode).toHaveAttribute('data-node-name', nodeName);
+ }
+
+ async getFloatingNodeCount(position: 'inputMain' | 'outputMain' | 'inputSub' | 'outputSub') {
+ return await this.getFloatingNodeByPosition(position).count();
+ }
+
+ getAddSubNodeButton(connectionType: string, index: number = 0) {
+ return this.page.getByTestId(`add-subnode-${connectionType}-${index}`);
+ }
+
+ getSubNodeConnectionGroup(connectionType: string, index: number = 0) {
+ return this.page.getByTestId(`subnode-connection-group-${connectionType}-${index}`);
+ }
+
+ getFloatingSubNodes(connectionType: string, index: number = 0) {
+ return this.getSubNodeConnectionGroup(connectionType, index).getByTestId('floating-subnode');
+ }
+
+ getNodesWithIssues() {
+ return this.page.locator('[class*="hasIssues"]');
+ }
+
+ async connectAISubNode(connectionType: string, nodeName: string, index: number = 0) {
+ await this.getAddSubNodeButton(connectionType, index).click();
+ await this.page.getByText(nodeName).click();
+ await this.getFloatingNode().click();
+ }
+
+ getFloatingNode() {
+ return this.page.getByTestId('floating-node');
+ }
+
+ async getNodesWithIssuesCount() {
+ return await this.getNodesWithIssues().count();
+ }
+
+ async addItemToFixedCollection(collectionName: string) {
+ await this.page.getByTestId(`fixed-collection-${collectionName}`).click();
+ }
+
+ async clickParameterItemAction(actionText: string) {
+ await this.page.getByTestId('parameter-item').getByText(actionText).click();
+ }
+
+ getParameterItemWithText(text: string) {
+ return this.page.getByTestId('parameter-item').getByText(text);
+ }
+
+ getParameterInputWithIssues(parameterPath: string) {
+ return this.page.locator(
+ `[data-test-id="parameter-input-field"][title*="${parameterPath}"][title*="has issues"]`,
+ );
+ }
+
+ getResourceLocator(paramName: string) {
+ return this.page.getByTestId(`resource-locator-${paramName}`);
+ }
+
+ getResourceLocatorInput(paramName: string) {
+ return this.getResourceLocator(paramName).getByTestId('rlc-input-container');
+ }
+
+ getResourceLocatorModeSelector(paramName: string) {
+ return this.getResourceLocator(paramName).getByTestId('rlc-mode-selector');
+ }
+
+ async setRLCValue(paramName: string, value: string): Promise {
+ await this.getResourceLocatorModeSelector(paramName).click();
+
+ const visibleOptions = this.page.locator('.el-popper:visible .el-select-dropdown__item');
+ await visibleOptions.last().click();
+
+ const input = this.getResourceLocatorInput(paramName).locator('input');
+ await input.fill(value);
+ }
+
+ async clickNodeCreatorInsertOneButton() {
+ await this.page.getByText('Insert one').click();
+ }
+
getInputSelect() {
return this.page.getByTestId('ndv-input-select').locator('input');
}
diff --git a/packages/testing/playwright/tests/ui/1-workflows.spec.ts b/packages/testing/playwright/tests/ui/1-workflows.spec.ts
index 439e0498d2..99f64c05d8 100644
--- a/packages/testing/playwright/tests/ui/1-workflows.spec.ts
+++ b/packages/testing/playwright/tests/ui/1-workflows.spec.ts
@@ -52,7 +52,6 @@ test.describe('Workflows', () => {
// Search for specific workflow
await n8n.workflows.searchWorkflows(specificName);
- await expect(n8n.workflows.getWorkflowItems()).toHaveCount(1);
await expect(n8n.workflows.getWorkflowByName(specificName)).toBeVisible();
// Search with partial term
diff --git a/packages/testing/playwright/tests/ui/5-ndv.spec.ts b/packages/testing/playwright/tests/ui/5-ndv.spec.ts
new file mode 100644
index 0000000000..7ac9e01686
--- /dev/null
+++ b/packages/testing/playwright/tests/ui/5-ndv.spec.ts
@@ -0,0 +1,800 @@
+import {
+ CODE_NODE_NAME,
+ CODE_NODE_DISPLAY_NAME,
+ MANUAL_TRIGGER_NODE_DISPLAY_NAME,
+} from '../../config/constants';
+import { test, expect } from '../../fixtures/base';
+import type { n8nPage } from '../../pages/n8nPage';
+
+test.describe('NDV', () => {
+ test.beforeEach(async ({ n8n }) => {
+ await n8n.start.fromBlankCanvas();
+ });
+
+ test('should show up when double clicked on a node and close when Back to canvas clicked', async ({
+ n8n,
+ }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ const canvasNodes = n8n.canvas.getCanvasNodes();
+ await canvasNodes.first().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+ await n8n.ndv.clickBackToCanvasButton();
+ await expect(n8n.ndv.getContainer()).toBeHidden();
+ });
+
+ test('should show input panel when node is not connected', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.deselectAll();
+ await n8n.canvas.addNode('Edit Fields (Set)', { closeNDV: true });
+ const canvasNodes = n8n.canvas.getCanvasNodes();
+ await canvasNodes.last().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+ await expect(n8n.ndv.getInputPanel()).toContainText('Wire me up');
+ });
+
+ test('should test webhook node', async ({ n8n }) => {
+ await n8n.canvas.addNode('Webhook', { closeNDV: false });
+
+ await n8n.ndv.execute();
+
+ const webhookUrl = await n8n.ndv.getWebhookUrl();
+ await expect(n8n.ndv.getWebhookTriggerListening()).toBeVisible();
+ const response = await n8n.ndv.makeWebhookRequest(webhookUrl as string);
+ expect(response.status()).toBe(200);
+
+ await expect(n8n.ndv.getOutputPanel()).toBeVisible();
+ await expect(n8n.ndv.getOutputDataContainer()).toBeVisible();
+ });
+
+ test('should change input and go back to canvas', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('NDV-test-select-input.json');
+ await n8n.canvas.clickZoomToFitButton();
+ await n8n.canvas.getCanvasNodes().last().dblclick();
+ await n8n.ndv.execute();
+
+ await n8n.ndv.switchInputMode('Table');
+
+ await n8n.ndv.getNodeInputOptions().last().click();
+
+ await expect(n8n.ndv.getInputPanel()).toContainText('start');
+
+ await n8n.ndv.clickBackToCanvasButton();
+ await expect(n8n.ndv.getContainer()).toBeHidden();
+ });
+
+ test('should show correct validation state for resource locator params', async ({ n8n }) => {
+ await n8n.canvas.addNode('Typeform Trigger', { closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.clickBackToCanvasButton();
+
+ await n8n.canvas.openNode('Typeform Trigger');
+ await expect(n8n.canvas.getNodeIssuesByName('Typeform Trigger')).toBeVisible();
+ });
+
+ test('should show validation errors only after blur or re-opening of NDV', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Airtable', { closeNDV: false, action: 'Search records' });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await expect(n8n.canvas.getNodeIssuesByName('Airtable')).toBeHidden();
+
+ await n8n.ndv.getParameterInputField('table').nth(1).focus();
+ await n8n.ndv.getParameterInputField('table').nth(1).blur();
+ await n8n.ndv.getParameterInputField('base').nth(1).focus();
+ await n8n.ndv.getParameterInputField('base').nth(1).blur();
+
+ await expect(n8n.ndv.getParameterInput('base')).toHaveClass(/has-issues|error|invalid/);
+ await expect(n8n.ndv.getParameterInput('table')).toHaveClass(/has-issues|error|invalid/);
+
+ await n8n.ndv.clickBackToCanvasButton();
+
+ await n8n.canvas.openNode('Search records');
+ await expect(n8n.canvas.getNodeIssuesByName('Search records')).toBeVisible();
+ });
+
+ test('should show all validation errors when opening pasted node', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_ndv_errors.json');
+ const canvasNodes = n8n.canvas.getCanvasNodes();
+ await expect(canvasNodes).toHaveCount(1);
+
+ await n8n.canvas.openNode('Airtable');
+ await expect(n8n.canvas.getNodeIssuesByName('Airtable')).toBeVisible();
+ });
+
+ test('should render run errors correctly', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_ndv_run_error.json');
+ await n8n.canvas.openNode('Error');
+ await n8n.ndv.execute();
+
+ await expect(n8n.ndv.getNodeRunErrorMessage()).toHaveText(
+ "Paired item data for item from node 'Break pairedItem chain' is unavailable. Ensure 'Break pairedItem chain' is providing the required output.",
+ );
+
+ await expect(n8n.ndv.getNodeRunErrorDescription()).toContainText(
+ "An expression here won't work because it uses .item and n8n can't figure out the matching item.",
+ );
+
+ await expect(n8n.ndv.getNodeRunErrorMessage()).toBeVisible();
+ await expect(n8n.ndv.getNodeRunErrorDescription()).toBeVisible();
+ });
+
+ test('should save workflow using keyboard shortcut from NDV', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Edit Fields (Set)', { closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.page.keyboard.press('ControlOrMeta+s');
+
+ await expect(n8n.canvas.getWorkflowSaveButton()).toBeHidden();
+ });
+
+ test('webhook should fallback to webhookId if path is empty', async ({ n8n }) => {
+ await n8n.canvas.addNode('Webhook', { closeNDV: false });
+
+ await expect(n8n.canvas.getNodeIssuesByName('Webhook')).toBeHidden();
+ await expect(n8n.ndv.getExecuteNodeButton()).toBeEnabled();
+ await expect(n8n.ndv.getTriggerPanelExecuteButton()).toBeVisible();
+
+ await n8n.ndv.getParameterInputField('path').clear();
+
+ const webhookUrlsContainer = n8n.ndv.getContainer().getByText('Webhook URLs').locator('..');
+ const urlText = await webhookUrlsContainer.textContent();
+ const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
+ expect(urlText).toMatch(uuidRegex);
+
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Webhook');
+ await n8n.ndv.fillParameterInput('path', 'test-path');
+
+ const updatedUrlText = await webhookUrlsContainer.textContent();
+ expect(updatedUrlText).toContain('test-path');
+ expect(updatedUrlText).not.toMatch(uuidRegex);
+ });
+
+ test.describe('test output schema view', () => {
+ const schemaKeys = [
+ 'id',
+ 'name',
+ 'email',
+ 'notes',
+ 'country',
+ 'created',
+ 'objectValue',
+ 'prop1',
+ 'prop2',
+ ];
+
+ const setupSchemaWorkflow = async (n8n: n8nPage) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_schema_test.json');
+ await n8n.canvas.clickZoomToFitButton();
+ await n8n.canvas.openNode('Set');
+ await n8n.ndv.execute();
+ };
+
+ test('should switch to output schema view and validate it', async ({ n8n }) => {
+ await setupSchemaWorkflow(n8n);
+ await n8n.ndv.switchOutputMode('Schema');
+
+ for (const key of schemaKeys) {
+ await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
+ }
+ });
+
+ test('should preserve schema view after execution', async ({ n8n }) => {
+ await setupSchemaWorkflow(n8n);
+ await n8n.ndv.switchOutputMode('Schema');
+ await n8n.ndv.execute();
+
+ for (const key of schemaKeys) {
+ await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
+ }
+ });
+
+ test('should collapse and expand nested schema object', async ({ n8n }) => {
+ await setupSchemaWorkflow(n8n);
+ const expandedObjectProps = ['prop1', 'prop2'];
+
+ await n8n.ndv.switchOutputMode('Schema');
+
+ for (const key of expandedObjectProps) {
+ await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).toBeVisible();
+ }
+
+ const objectValueItem = n8n.ndv.getSchemaViewItems().filter({ hasText: 'objectValue' });
+ await objectValueItem.locator('.toggle').click();
+
+ for (const key of expandedObjectProps) {
+ await expect(n8n.ndv.getSchemaViewItems().filter({ hasText: key })).not.toBeInViewport();
+ }
+ });
+
+ test('should not display pagination for schema', async ({ n8n }) => {
+ await setupSchemaWorkflow(n8n);
+
+ await n8n.ndv.clickBackToCanvasButton();
+ await n8n.canvas.deselectAll();
+ await n8n.canvas.nodeByName('Set').click();
+ await n8n.canvas.addNode('Customer Datastore (n8n training)');
+
+ await n8n.canvas.openNode('Customer Datastore (n8n training)');
+
+ await n8n.ndv.execute();
+
+ await expect(n8n.ndv.getOutputPanel().getByText('5 items')).toBeVisible();
+
+ await n8n.ndv.switchOutputMode('Schema');
+
+ const schemaItemsCount = await n8n.ndv.getSchemaViewItems().count();
+ expect(schemaItemsCount).toBeGreaterThan(0);
+
+ await n8n.ndv.switchOutputMode('JSON');
+ });
+
+ test('should display large schema', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_schema_test_pinned_data.json');
+ await n8n.canvas.clickZoomToFitButton();
+ await n8n.canvas.openNode('Set');
+
+ await expect(n8n.ndv.getOutputPanel().getByText('20 items')).toBeVisible();
+ await expect(n8n.ndv.getOutputPanel().locator('[class*="_pagination"]')).toBeVisible();
+
+ await n8n.ndv.switchOutputMode('Schema');
+
+ await expect(n8n.ndv.getOutputPanel().locator('[class*="_pagination"]')).toBeHidden();
+ });
+ });
+
+ test('should display parameter hints correctly', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_3.json');
+ await n8n.canvas.openNode('Set1');
+
+ await n8n.ndv.getParameterInputField('value').clear();
+ await n8n.ndv.getParameterInputField('value').fill('=');
+
+ await n8n.ndv.getInlineExpressionEditorContent().fill('hello');
+ await n8n.ndv.getParameterInputField('name').click();
+ await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('hello');
+
+ await n8n.ndv.getInlineExpressionEditorContent().fill('');
+ await n8n.ndv.getParameterInputField('name').click();
+ await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('[empty]');
+
+ await n8n.ndv.getInlineExpressionEditorContent().fill(' test');
+ await n8n.ndv.getParameterInputField('name').click();
+ await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText(' test');
+
+ await n8n.ndv.getInlineExpressionEditorContent().fill(' ');
+ await n8n.ndv.getParameterInputField('name').click();
+ await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText(' ');
+
+ await n8n.ndv.getInlineExpressionEditorContent().fill('');
+ await n8n.ndv.getParameterInputField('name').click();
+ await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('');
+ });
+
+ test('should properly show node execution indicator', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
+
+ await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeHidden();
+ await expect(n8n.ndv.getNodeRunErrorIndicator()).toBeHidden();
+ await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeHidden();
+
+ await n8n.ndv.execute();
+
+ await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
+ await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeVisible();
+ });
+
+ test('should show node name and version in settings', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_ndv_version.json');
+
+ await n8n.canvas.openNode('Edit Fields (old)');
+ await n8n.ndv.openSettings();
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Set node version 2');
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Latest version: 3.4');
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Edit Fields (latest)');
+ await n8n.ndv.openSettings();
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Edit Fields (Set) node version 3.4');
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Latest');
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Function');
+ await n8n.ndv.openSettings();
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Function node version 1');
+ await expect(n8n.ndv.getNodeVersion()).toContainText('Deprecated');
+ await n8n.ndv.close();
+ });
+
+ test('should not push NDV header out with a lot of code in Code node editor', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
+
+ const codeEditor = n8n.ndv.getParameterInput('jsCode').locator('.cm-content');
+ await codeEditor.click();
+ await n8n.page.keyboard.press('ControlOrMeta+a');
+ await n8n.page.keyboard.press('Delete');
+
+ const dummyCode = Array(50)
+ .fill(
+ 'console.log("This is a very long line of dummy JavaScript code that should not push the NDV header out of view");',
+ )
+ .join('\n');
+
+ await codeEditor.fill(dummyCode);
+
+ await expect(n8n.ndv.getExecuteNodeButton()).toBeVisible();
+ });
+
+ test('should allow editing code in fullscreen in the code editors', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Code', { action: 'Code in JavaScript', closeNDV: false });
+
+ await n8n.ndv.openCodeEditorFullscreen();
+
+ const fullscreenEditor = n8n.ndv.getCodeEditorFullscreen();
+ await fullscreenEditor.click();
+ await n8n.page.keyboard.press('ControlOrMeta+a');
+ await fullscreenEditor.fill('foo()');
+
+ await expect(fullscreenEditor).toContainText('foo()');
+
+ await n8n.ndv.closeCodeEditorDialog();
+
+ await expect(n8n.ndv.getParameterInput('jsCode').locator('.cm-content')).toContainText('foo()');
+ });
+
+ test('should keep search expanded after Execute step node run', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_ndv_search.json');
+ await n8n.canvas.clickZoomToFitButton();
+ await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
+ 'Workflow executed successfully',
+ );
+
+ await n8n.canvas.openNode('Edit Fields');
+ await expect(n8n.ndv.getOutputPanel()).toBeVisible();
+
+ await n8n.ndv.searchOutputData('US');
+
+ await expect(n8n.ndv.getOutputTableRow(1).locator('mark')).toContainText('US');
+
+ await n8n.ndv.execute();
+
+ await expect(n8n.ndv.getOutputSearchInput()).toBeVisible();
+ await expect(n8n.ndv.getOutputSearchInput()).toHaveValue('US');
+ });
+
+ test('Should render xml and html tags as strings and can search', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Test_workflow_xml_output.json');
+ await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
+ 'Workflow executed successfully',
+ );
+ await n8n.canvas.openNode('Edit Fields');
+
+ await expect(n8n.ndv.getOutputPanel().locator('[class*="active"]')).toContainText('Table');
+
+ await expect(n8n.ndv.getOutputTableRow(1)).toContainText(
+ ' ',
+ );
+
+ await n8n.page.keyboard.press('/');
+
+ const searchInput = n8n.ndv.getOutputSearchInput();
+ await expect(searchInput).toBeFocused();
+ await searchInput.fill(' {
+ test('can link and unlink run selectors between input and output', 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.switchInputMode('Table');
+ await n8n.ndv.switchOutputMode('Table');
+
+ await n8n.ndv.ensureOutputRunLinking(true);
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ expect(await n8n.ndv.getInputRunSelectorValue()).toContain('2 of 2 (6 items)');
+ expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
+
+ await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
+ expect(await n8n.ndv.getInputRunSelectorValue()).toContain('1 of 2 (6 items)');
+ await expect(n8n.ndv.getInputTbodyCell(0, 0)).toHaveText('1111');
+ await expect(n8n.ndv.getOutputTbodyCell(0, 0)).toHaveText('1111');
+
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ await n8n.ndv.changeInputRunSelector('2 of 2 (6 items)');
+ expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
+
+ await n8n.ndv.toggleOutputRunLinking();
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ await n8n.ndv.changeOutputRunSelector('1 of 2 (6 items)');
+ expect(await n8n.ndv.getInputRunSelectorValue()).toContain('2 of 2 (6 items)');
+
+ await n8n.ndv.toggleOutputRunLinking();
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ expect(await n8n.ndv.getInputRunSelectorValue()).toContain('1 of 2 (6 items)');
+
+ await n8n.ndv.toggleInputRunLinking();
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ await n8n.ndv.changeInputRunSelector('2 of 2 (6 items)');
+ expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('1 of 2 (6 items)');
+
+ await n8n.ndv.toggleInputRunLinking();
+ await n8n.ndv.getInputTbodyCell(0, 0).click();
+ expect(await n8n.ndv.getOutputRunSelectorValue()).toContain('2 of 2 (6 items)');
+ });
+ });
+
+ test.describe('Remote Options & Network', () => {
+ test('should not retrieve remote options when a parameter value changes', async ({ n8n }) => {
+ let fetchParameterOptionsCallCount = 0;
+ await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
+ fetchParameterOptionsCallCount++;
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ data: [] }),
+ });
+ });
+
+ await n8n.canvas.addNode('E2E Test', { action: 'Remote Options' });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.fillFirstAvailableTextParameterMultipleTimes(['test1', 'test2', 'test3']);
+
+ expect(fetchParameterOptionsCallCount).toBe(1);
+ });
+
+ test('Should show a notice when remote options cannot be fetched because of missing credentials', async ({
+ n8n,
+ }) => {
+ await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
+ await route.fulfill({ status: 403 });
+ });
+
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Notion', { action: 'Update a database page', closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.addItemToFixedCollection('propertiesUi');
+ await expect(
+ n8n.ndv.getParameterInputWithIssues('propertiesUi.propertyValues[0].key'),
+ ).toBeVisible();
+ });
+
+ test('Should show error state when remote options cannot be fetched', async ({ n8n }) => {
+ await n8n.page.route('**/rest/dynamic-node-parameters/options', async (route) => {
+ await route.fulfill({ status: 500 });
+ });
+
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Notion', { action: 'Update a database page', closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.credentials.createAndSaveNewCredential('apiKey', 'sk_test_123');
+ await n8n.ndv.addItemToFixedCollection('propertiesUi');
+ await expect(
+ n8n.ndv.getParameterInputWithIssues('propertiesUi.propertyValues[0].key'),
+ ).toBeVisible();
+ });
+ });
+
+ test.describe('Floating Nodes Navigation', () => {
+ test('should traverse floating nodes with mouse', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
+ await n8n.canvas.getCanvasNodes().first().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+ for (let i = 0; i < 4; i++) {
+ await n8n.ndv.clickFloatingNodeByPosition('outputMain');
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+
+ await n8n.ndv.close();
+ await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
+
+ await n8n.canvas.getSelectedNodes().first().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+ }
+
+ await n8n.ndv.clickFloatingNodeByPosition('outputMain');
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+
+ for (let i = 0; i < 4; i++) {
+ await n8n.ndv.clickFloatingNodeByPosition('inputMain');
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+ }
+
+ await n8n.ndv.clickFloatingNodeByPosition('inputMain');
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputSub')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputSub')).toBeHidden();
+
+ await n8n.ndv.close();
+ await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
+ });
+
+ test('should traverse floating nodes with keyboard', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
+
+ await n8n.canvas.getCanvasNodes().first().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+ for (let i = 0; i < 4; i++) {
+ await n8n.ndv.navigateToNextFloatingNodeWithKeyboard();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+
+ await n8n.ndv.close();
+ await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
+
+ await n8n.canvas.getSelectedNodes().first().dblclick();
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+ }
+
+ await n8n.ndv.navigateToNextFloatingNodeWithKeyboard();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+
+ for (let i = 0; i < 4; i++) {
+ await n8n.ndv.navigateToPreviousFloatingNodeWithKeyboard();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputMain')).toBeVisible();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeVisible();
+ }
+
+ await n8n.ndv.navigateToPreviousFloatingNodeWithKeyboard();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputMain')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('inputSub')).toBeHidden();
+ await expect(n8n.ndv.getFloatingNodeByPosition('outputSub')).toBeHidden();
+
+ await n8n.ndv.close();
+ await expect(n8n.canvas.getSelectedNodes()).toHaveCount(1);
+ });
+
+ test('should connect floating sub-nodes', async ({ n8n }) => {
+ await n8n.canvas.addNode('AI Agent', { closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.connectAISubNode('ai_languageModel', 'Anthropic Chat Model');
+ await n8n.ndv.connectAISubNode('ai_memory', 'Simple Memory');
+ await n8n.ndv.connectAISubNode('ai_tool', 'HTTP Request Tool');
+
+ expect(await n8n.ndv.getNodesWithIssuesCount()).toBeGreaterThanOrEqual(2);
+ });
+
+ test('should have the floating nodes in correct order', async ({ n8n }) => {
+ await n8n.start.fromImportedWorkflow('Floating_Nodes.json');
+
+ await n8n.canvas.openNode('Merge');
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ expect(await n8n.ndv.getFloatingNodeCount('inputMain')).toBe(2);
+ await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields1', 0);
+ await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields0', 1);
+
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Merge1');
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ expect(await n8n.ndv.getFloatingNodeCount('inputMain')).toBe(2);
+ await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields0', 0);
+ await n8n.ndv.verifyFloatingNodeName('inputMain', 'Edit Fields1', 1);
+ });
+ });
+
+ test.describe('Parameter Management - Advanced', () => {
+ test('Should clear mismatched collection parameters', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Notion', { action: 'Create a database page', closeNDV: false });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.addItemToFixedCollection('propertiesUi');
+ await n8n.ndv.changeNodeOperation('Update');
+
+ await expect(n8n.ndv.getParameterItemWithText('Currently no items exist')).toBeVisible();
+ });
+
+ test('Should keep RLC values after operation change', async ({ n8n }) => {
+ const TEST_DOC_ID = '1111';
+
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Google Sheets', { closeNDV: false, action: 'Append row in sheet' });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.ndv.setRLCValue('documentId', TEST_DOC_ID);
+ await n8n.ndv.changeNodeOperation('Append or Update Row');
+ const input = n8n.ndv.getResourceLocatorInput('documentId').locator('input');
+ await expect(input).toHaveValue(TEST_DOC_ID);
+ });
+
+ test('Should not clear resource/operation after credential change', async ({ n8n }) => {
+ await n8n.canvas.addNode('Manual Trigger');
+ await n8n.canvas.addNode('Discord', { closeNDV: false, action: 'Delete a message' });
+ await expect(n8n.ndv.getContainer()).toBeVisible();
+
+ await n8n.credentials.createAndSaveNewCredential('botToken', 'sk_test_123');
+ const resourceInput = n8n.ndv.getParameterInputField('resource');
+ const operationInput = n8n.ndv.getParameterInputField('operation');
+
+ await expect(resourceInput).toHaveValue('Message');
+ await expect(operationInput).toHaveValue('Delete');
+ });
+ });
+
+ test.describe('Node Creator Integration', () => {
+ test('Should open appropriate node creator after clicking on connection hint link', async ({
+ n8n,
+ }) => {
+ const hintMapper = {
+ Memory: 'AI Nodes',
+ 'Output Parser': 'AI Nodes',
+ 'Token Splitter': 'Document Loaders',
+ Tool: 'AI Nodes',
+ Embeddings: 'Vector Stores',
+ 'Vector Store': 'Retrievers',
+ };
+
+ await n8n.canvas.importWorkflow(
+ 'open_node_creator_for_connection.json',
+ 'open_node_creator_for_connection',
+ );
+
+ for (const [node, group] of Object.entries(hintMapper)) {
+ await n8n.canvas.openNode(node);
+
+ await n8n.ndv.clickNodeCreatorInsertOneButton();
+ await expect(n8n.canvas.getNodeCreatorHeader(group)).toBeVisible();
+ await n8n.page.keyboard.press('Escape');
+ }
+ });
+ });
+
+ test.describe('Expression Editor Features', () => {
+ test('should allow selecting item for expressions', async ({ n8n }) => {
+ await n8n.canvas.importWorkflow('Test_workflow_3.json', 'My test workflow 2');
+
+ await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
+ 'Workflow executed successfully',
+ );
+
+ await n8n.canvas.openNode('Set');
+
+ await n8n.ndv.getAssignmentValue('assignments').getByText('Expression').click();
+
+ const expressionInput = n8n.ndv.getInlineExpressionEditorInput();
+ await expressionInput.click();
+ await n8n.ndv.clearExpressionEditor();
+ await n8n.ndv.typeInExpressionEditor('{{ $json.input[0].count');
+
+ await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('0');
+
+ await n8n.ndv.expressionSelectNextItem();
+ await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('1');
+ await expect(n8n.ndv.getInlineExpressionEditorItemInput()).toHaveValue('1');
+
+ await expect(n8n.ndv.getInlineExpressionEditorItemNextButton()).toBeDisabled();
+
+ await n8n.ndv.expressionSelectPrevItem();
+ await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('0');
+ await expect(n8n.ndv.getInlineExpressionEditorItemInput()).toHaveValue('0');
+ });
+ });
+
+ test.describe('Schema & Data Views', () => {
+ test('should show data from the correct output in schema view', async ({ n8n }) => {
+ await n8n.canvas.importWorkflow('Test_workflow_multiple_outputs.json', 'Multiple outputs');
+ await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
+ 'Workflow executed successfully',
+ );
+
+ await n8n.canvas.openNode('Only Item 1');
+ await expect(n8n.ndv.getInputPanel()).toBeVisible();
+ await n8n.ndv.switchInputMode('Schema');
+ await expect(n8n.ndv.getInputSchemaItem('onlyOnItem1')).toBeVisible();
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Only Item 2');
+ await expect(n8n.ndv.getInputPanel()).toBeVisible();
+ await n8n.ndv.switchInputMode('Schema');
+ await expect(n8n.ndv.getInputSchemaItem('onlyOnItem2')).toBeVisible();
+ await n8n.ndv.close();
+
+ await n8n.canvas.openNode('Only Item 3');
+ await expect(n8n.ndv.getInputPanel()).toBeVisible();
+ await n8n.ndv.switchInputMode('Schema');
+ await expect(n8n.ndv.getInputSchemaItem('onlyOnItem3')).toBeVisible();
+ await n8n.ndv.close();
+ });
+ });
+
+ test.describe('Search Functionality - Advanced', () => {
+ test('should not show items count when searching in schema view', async ({ n8n }) => {
+ await n8n.canvas.importWorkflow('Test_ndv_search.json', 'NDV Search Test');
+ await n8n.canvas.openNode('Edit Fields');
+ await expect(n8n.ndv.getOutputPanel()).toBeVisible();
+
+ await n8n.ndv.execute();
+ await n8n.ndv.switchOutputMode('Schema');
+ await n8n.ndv.searchOutputData('US');
+
+ await expect(n8n.ndv.getOutputItemsCount()).toBeHidden();
+ });
+
+ test('should show additional tooltip when searching in schema view if no matches', async ({
+ n8n,
+ }) => {
+ await n8n.canvas.importWorkflow('Test_ndv_search.json', 'NDV Search Test');
+
+ await n8n.canvas.openNode('Edit Fields');
+ await expect(n8n.ndv.getOutputPanel()).toBeVisible();
+
+ await n8n.ndv.execute();
+ await n8n.ndv.switchOutputMode('Schema');
+ await n8n.ndv.searchOutputData('foo');
+
+ await expect(
+ n8n.ndv.getOutputPanel().getByText('To search field values, switch to table or JSON view.'),
+ ).toBeVisible();
+ });
+ });
+
+ test.describe('Complex Edge Cases', () => {
+ test('ADO-2931 - should handle multiple branches of the same input with the first branch empty correctly', async ({
+ n8n,
+ }) => {
+ await n8n.canvas.importWorkflow(
+ 'Test_ndv_two_branches_of_same_parent_false_populated.json',
+ 'Multiple Branches Test',
+ );
+
+ await n8n.canvas.openNode('DebugHelper');
+ await expect(n8n.ndv.getInputPanel()).toBeVisible();
+ await expect(n8n.ndv.getOutputPanel()).toBeVisible();
+
+ await n8n.ndv.execute();
+
+ await expect(
+ n8n.ndv.getInputPanel().getByTestId('run-data-schema-item').filter({ hasText: 'a1' }),
+ ).toBeVisible();
+ });
+ });
+
+ test.describe('Execution Indicators - Multi-Node', () => {
+ test('should properly show node execution indicator for multiple nodes', async ({ n8n }) => {
+ await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });
+ await n8n.ndv.clickBackToCanvasButton();
+
+ await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
+ 'Workflow executed successfully',
+ );
+
+ await n8n.canvas.openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
+ await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
+ await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeVisible();
+
+ await n8n.ndv.clickBackToCanvasButton();
+ await n8n.canvas.openNode(CODE_NODE_DISPLAY_NAME);
+ await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeVisible();
+ });
+ });
+});
diff --git a/packages/testing/playwright/workflows/Floating_Nodes.json b/packages/testing/playwright/workflows/Floating_Nodes.json
new file mode 100644
index 0000000000..2c95497b01
--- /dev/null
+++ b/packages/testing/playwright/workflows/Floating_Nodes.json
@@ -0,0 +1,221 @@
+{
+ "name": "Floating Nodes",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "d0eda550-2526-42a1-aa19-dee411c8acf9",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [700, 560]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "30412165-1229-4b21-9890-05bfbd9952ab",
+ "name": "Node 1",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [920, 560]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "201cc8fc-3124-47a3-bc08-b3917c1ddcd9",
+ "name": "Node 2",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [1100, 560]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "a29802bb-a284-495d-9917-6c6e42fef01e",
+ "name": "Node 3",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [1280, 560]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "a95a72b3-8b39-44e2-a05b-d8d677741c80",
+ "name": "Node 4",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [1440, 560]
+ },
+ {
+ "parameters": {},
+ "id": "4674f10d-6144-4a17-bbbb-350c3974438e",
+ "name": "Chain",
+ "type": "@n8n/n8n-nodes-langchain.chainLlm",
+ "typeVersion": 1,
+ "position": [1580, 560]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "58e12ea5-bd3e-4abf-abec-fcfb5c0a7955",
+ "name": "Model",
+ "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
+ "typeVersion": 1,
+ "position": [1600, 740]
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [440, -140],
+ "id": "a00959d3-8d4b-40af-b4f2-35ca3d73fd84",
+ "name": "Merge"
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.4,
+ "position": [-20, -120],
+ "id": "a5cbc221-ccfd-4034-a648-6a192834af81",
+ "name": "Edit Fields0"
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.4,
+ "position": [0, 100],
+ "id": "d3b4c17a-bee8-418b-a721-5debafd1ce11",
+ "name": "Edit Fields1"
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [440, 100],
+ "id": "b23a2a43-ffac-41a5-a265-054e21a57d70",
+ "name": "Merge1"
+ }
+ ],
+ "pinData": {},
+ "connections": {
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Node 1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Node 1": {
+ "main": [
+ [
+ {
+ "node": "Node 2",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Node 3": {
+ "main": [
+ [
+ {
+ "node": "Node 4",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Node 2": {
+ "main": [
+ [
+ {
+ "node": "Node 3",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Chain": {
+ "main": [[]]
+ },
+ "Model": {
+ "ai_languageModel": [
+ [
+ {
+ "node": "Chain",
+ "type": "ai_languageModel",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Node 4": {
+ "main": [
+ [
+ {
+ "node": "Chain",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Edit Fields0": {
+ "main": [
+ [
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 1
+ },
+ {
+ "node": "Merge1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Edit Fields1": {
+ "main": [
+ [
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Merge1",
+ "type": "main",
+ "index": 1
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "versionId": "2730d156-a98a-4ac8-b481-5c16361fdba2",
+ "id": "6bzXMGxHuxeEaqsA",
+ "meta": {
+ "instanceId": "1838be0fa0389fbaf5e2e4aaedab4ddc79abc4175b433401abb22a281001b853"
+ },
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/NDV-test-select-input.json b/packages/testing/playwright/workflows/NDV-test-select-input.json
new file mode 100644
index 0000000000..f64371e7fb
--- /dev/null
+++ b/packages/testing/playwright/workflows/NDV-test-select-input.json
@@ -0,0 +1,74 @@
+{
+ "name": "ff739753-9d6e-46a7-94d3-25bd03dd4973",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "c30d1114-d7f7-44dc-b55a-15312ef2d76d",
+ "name": "On clicking 'execute'",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [600, 580]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "5bf514bd-65ae-4a1c-8b69-a01bfeaad411",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [820, 580]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "02bf49e9-44b2-4f4e-8cb2-8c02399208af",
+ "name": "Set1",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [1040, 580]
+ }
+ ],
+ "pinData": {
+ "On clicking 'execute'": [
+ {
+ "json": {
+ "start": true
+ }
+ }
+ ]
+ },
+ "connections": {
+ "On clicking 'execute'": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set": {
+ "main": [
+ [
+ {
+ "node": "Set1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "hash": "abd7b28aa2605c96ba24474d72cbe610",
+ "id": 3,
+ "meta": {
+ "instanceId": "08a83d394781701f5c18052cde68e8d92c88b26f5cc8809eb10ad545f14015cb"
+ },
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_ndv_search.json b/packages/testing/playwright/workflows/Test_ndv_search.json
new file mode 100644
index 0000000000..b374dce37b
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_ndv_search.json
@@ -0,0 +1,126 @@
+{
+ "name": "NDV search bugs (introduced by schema view?)",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "55635c7b-92ee-4d2d-a0c0-baff9ab071da",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "position": [800, 380],
+ "typeVersion": 1
+ },
+ {
+ "parameters": {
+ "operation": "getAllPeople"
+ },
+ "id": "4737af43-e49b-4c92-b76f-32605c047114",
+ "name": "Customer Datastore (n8n training)",
+ "type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
+ "typeVersion": 1,
+ "position": [1020, 380]
+ },
+ {
+ "parameters": {
+ "assignments": {
+ "assignments": []
+ },
+ "includeOtherFields": true,
+ "options": {}
+ },
+ "id": "8cc9b374-1856-4f3f-9315-08e6e27840d8",
+ "name": "Edit Fields",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.4,
+ "position": [1240, 380]
+ }
+ ],
+ "pinData": {
+ "Customer Datastore (n8n training)": [
+ {
+ "json": {
+ "id": "23423532",
+ "name": "Jay Gatsby",
+ "email": "gatsby@west-egg.com",
+ "notes": "Keeps asking about a green light??",
+ "country": "US",
+ "created": "1925-04-10"
+ }
+ },
+ {
+ "json": {
+ "id": "23423533",
+ "name": "José Arcadio Buendía",
+ "email": "jab@macondo.co",
+ "notes": "Lots of people named after him. Very confusing",
+ "country": "CO",
+ "created": "1967-05-05"
+ }
+ },
+ {
+ "json": {
+ "id": "23423534",
+ "name": "Max Sendak",
+ "email": "info@in-and-out-of-weeks.org",
+ "notes": "Keeps rolling his terrible eyes",
+ "country": "US",
+ "created": "1963-04-09"
+ }
+ },
+ {
+ "json": {
+ "id": "23423535",
+ "name": "Zaphod Beeblebrox",
+ "email": "captain@heartofgold.com",
+ "notes": "Felt like I was talking to more than one person",
+ "country": null,
+ "created": "1979-10-12"
+ }
+ },
+ {
+ "json": {
+ "id": "23423536",
+ "name": "Edmund Pevensie",
+ "email": "edmund@narnia.gov",
+ "notes": "Passionate sailor",
+ "country": "UK",
+ "created": "1950-10-16"
+ }
+ }
+ ]
+ },
+ "connections": {
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Customer Datastore (n8n training)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Customer Datastore (n8n training)": {
+ "main": [
+ [
+ {
+ "node": "Edit Fields",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "versionId": "20178044-fb64-4443-88dd-e941517520d0",
+ "meta": {
+ "templateCredsSetupCompleted": true,
+ "instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
+ },
+ "id": "aBVnTRON9Y2cSmse",
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_ndv_two_branches_of_same_parent_false_populated.json b/packages/testing/playwright/workflows/Test_ndv_two_branches_of_same_parent_false_populated.json
new file mode 100644
index 0000000000..8fb74eded8
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_ndv_two_branches_of_same_parent_false_populated.json
@@ -0,0 +1,94 @@
+{
+ "nodes": [
+ {
+ "parameters": {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict",
+ "version": 2
+ },
+ "conditions": [
+ {
+ "id": "6f0cf983-824b-4339-a5de-6b374a23b4b0",
+ "leftValue": "={{ $json.a }}",
+ "rightValue": 3,
+ "operator": {
+ "type": "number",
+ "operation": "equals"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [220, 0],
+ "id": "1755282a-ec4a-4d02-a833-0316ca413cc4",
+ "name": "If"
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [0, 0],
+ "id": "de1e7acf-12d8-4e56-ba42-709ffb397db2",
+ "name": "When clicking ‘Execute workflow’"
+ },
+ {
+ "parameters": {
+ "category": "randomData"
+ },
+ "type": "n8n-nodes-base.debugHelper",
+ "typeVersion": 1,
+ "position": [580, 0],
+ "id": "86440d33-f833-453c-bcaa-fff7e0083501",
+ "name": "DebugHelper",
+ "alwaysOutputData": true
+ }
+ ],
+ "connections": {
+ "If": {
+ "main": [
+ [
+ {
+ "node": "DebugHelper",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "DebugHelper",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "If",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "pinData": {
+ "When clicking ‘Execute workflow’": [
+ {
+ "a": 1
+ },
+ {
+ "a": 2
+ }
+ ]
+ }
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_5.json b/packages/testing/playwright/workflows/Test_workflow_5.json
new file mode 100644
index 0000000000..cbdb780b4d
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_5.json
@@ -0,0 +1,258 @@
+{
+ "meta": {
+ "instanceId": "8147b3a74cd161276e0f3bfc17369a724afab0d377593fada8be82d34c0c6a95"
+ },
+ "nodes": [
+ {
+ "parameters": {
+ "jsCode": "return [\n {\n id: 6666\n },\n {\n id: 3333\n },\n {\n id: 9999\n },\n {\n id: 1111\n },\n {\n id: 4444\n },\n {\n id: 8888\n },\n]"
+ },
+ "id": "5f023c7c-67ca-47a0-8a90-8227fcf29b9c",
+ "name": "Code",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 1,
+ "position": [-520, 580]
+ },
+ {
+ "parameters": {
+ "values": {
+ "string": [
+ {
+ "name": "id",
+ "value": "={{ $json.id }}"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "bd454282-9dd7-465f-9b9a-654a0c8532ec",
+ "name": "Set2",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [-40, 780]
+ },
+ {
+ "parameters": {},
+ "id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [-740, 580]
+ },
+ {
+ "parameters": {
+ "sortFieldsUi": {
+ "sortField": [
+ {
+ "fieldName": "id"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "555a150c-d735-4331-b628-c1f1cfed2da1",
+ "name": "Sort",
+ "type": "n8n-nodes-base.sort",
+ "typeVersion": 1,
+ "position": [-280, 580]
+ },
+ {
+ "parameters": {
+ "values": {
+ "string": [
+ {
+ "name": "id",
+ "value": "={{ $json.id }}"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "02372cb6-aac8-45c3-8600-f699901289ac",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [-60, 580]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "00d73944-218c-4896-af68-3f2855a922d1",
+ "name": "Set1",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [-280, 780]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "number": [
+ {
+ "value1": "={{ $json.id }}",
+ "operation": "smallerEqual",
+ "value2": 6666
+ }
+ ]
+ }
+ },
+ "id": "211a7bef-32d1-4928-9cef-3a45f2e61379",
+ "name": "IF",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 1,
+ "position": [160, 580]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "dcbd4745-832f-43d8-8a3c-dd80e8ca2777",
+ "name": "Set3",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [140, 780]
+ },
+ {
+ "parameters": {
+ "jsCode": "return [\n {\n id: 1000\n },\n {\n id: 300\n },\n {\n id: 2000\n },\n {\n id: 100\n },\n {\n id: 400\n },\n {\n id: 1300\n },\n]"
+ },
+ "id": "ec9c8f16-f3c8-4054-a6e9-4f1ebcdebb71",
+ "name": "Code1",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 1,
+ "position": [-520, 780]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "42e89478-a53a-4d10-b20c-1dc5d5f953d5",
+ "name": "Set4",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [460, 460]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "5085eb1c-0345-4b9d-856a-2955279f2c5d",
+ "name": "Set5",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [460, 660]
+ }
+ ],
+ "connections": {
+ "Code": {
+ "main": [
+ [
+ {
+ "node": "Sort",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set2": {
+ "main": [
+ [
+ {
+ "node": "Set3",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Code",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Code1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Sort": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Set2",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set": {
+ "main": [
+ [
+ {
+ "node": "IF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set1": {
+ "main": [
+ [
+ {
+ "node": "Set2",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "IF": {
+ "main": [
+ [
+ {
+ "node": "Set4",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Set5",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Set5",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Code1": {
+ "main": [
+ [
+ {
+ "node": "Set1",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ }
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_multiple_outputs.json b/packages/testing/playwright/workflows/Test_workflow_multiple_outputs.json
new file mode 100644
index 0000000000..0ecd6e9457
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_multiple_outputs.json
@@ -0,0 +1,208 @@
+{
+ "name": "Multiple outputs",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "64b27674-3da6-46ce-9008-e173182efa48",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "position": [16, -32],
+ "typeVersion": 1
+ },
+ {
+ "parameters": {
+ "rules": {
+ "values": [
+ {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict"
+ },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.code }}",
+ "rightValue": 1,
+ "operator": {
+ "type": "number",
+ "operation": "equals"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Item1"
+ },
+ {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict"
+ },
+ "conditions": [
+ {
+ "id": "a659050f-0867-471d-8914-d499b6ad7b31",
+ "leftValue": "={{ $json.code }}",
+ "rightValue": 2,
+ "operator": {
+ "type": "number",
+ "operation": "equals"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Item2"
+ },
+ {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict"
+ },
+ "conditions": [
+ {
+ "id": "109fc001-53af-48f1-b79c-5e9afc8b94bd",
+ "leftValue": "={{ $json.code }}",
+ "rightValue": 3,
+ "operator": {
+ "type": "number",
+ "operation": "equals"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Item3"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.switch",
+ "position": [192, -32],
+ "id": "3863cc7a-8f45-46fc-a60c-36aad5b12877",
+ "name": "Switch",
+ "typeVersion": 3
+ },
+ {
+ "parameters": {
+ "assignments": {
+ "assignments": [
+ {
+ "id": "f71bac89-8852-41b2-98dd-cb689f011dcb",
+ "name": "",
+ "value": "",
+ "type": "string"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.set",
+ "position": [480, -192],
+ "id": "85940094-4656-4cdf-a871-1b3b46421de3",
+ "name": "Only Item 1",
+ "typeVersion": 3.4
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.set",
+ "position": [480, -32],
+ "id": "a7f4e2b5-8cc9-4881-aa06-38601988740e",
+ "name": "Only Item 2",
+ "typeVersion": 3.4
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.set",
+ "position": [480, 128],
+ "id": "7e44ad56-415a-4991-a70e-fea86c430031",
+ "name": "Only Item 3",
+ "typeVersion": 3.4
+ }
+ ],
+ "pinData": {
+ "When clicking ‘Execute workflow’": [
+ {
+ "json": {
+ "name": "First item",
+ "onlyOnItem1": true,
+ "code": 1
+ }
+ },
+ {
+ "json": {
+ "name": "Second item",
+ "onlyOnItem2": true,
+ "code": 2
+ }
+ },
+ {
+ "json": {
+ "name": "Third item",
+ "onlyOnItem3": true,
+ "code": 3
+ }
+ }
+ ]
+ },
+ "connections": {
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Switch",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Switch": {
+ "main": [
+ [
+ {
+ "node": "Only Item 1",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Only Item 2",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Only Item 3",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "versionId": "1e2a7b45-7730-42d6-989e-f3fa80de303e",
+ "meta": {
+ "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
+ },
+ "id": "V2ld4YU11fsHgr1z",
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_ndv_errors.json b/packages/testing/playwright/workflows/Test_workflow_ndv_errors.json
new file mode 100644
index 0000000000..7f55dcee49
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_ndv_errors.json
@@ -0,0 +1,29 @@
+{
+ "meta": {
+ "instanceId": "3204fc455f5cbeb4e71fdbd3b1dfaf0b088088dea3e639de49e61462b80ffc1d"
+ },
+ "nodes": [
+ {
+ "parameters": {
+ "application": {
+ "__rl": true,
+ "mode": "url",
+ "value": "",
+ "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
+ },
+ "table": {
+ "__rl": true,
+ "mode": "url",
+ "value": "",
+ "__regex": "https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})"
+ }
+ },
+ "id": "e0c0cf7e-aa98-4b72-9645-6e64e2902bd1",
+ "name": "Airtable",
+ "type": "n8n-nodes-base.airtable",
+ "typeVersion": 1,
+ "position": [380, 180]
+ }
+ ],
+ "connections": {}
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_ndv_run_error.json b/packages/testing/playwright/workflows/Test_workflow_ndv_run_error.json
new file mode 100644
index 0000000000..fb66d53f62
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_ndv_run_error.json
@@ -0,0 +1,150 @@
+{
+ "name": "My workflow 52",
+ "nodes": [
+ {
+ "parameters": {
+ "jsCode": "\nreturn [\n {\n \"field\": \"the same\"\n }\n];"
+ },
+ "id": "38c14c4a-7af1-4b04-be76-f8e474c95569",
+ "name": "Break pairedItem chain",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [240, 1020]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "78c4964a-c4e8-47e5-81f3-89ba778feb8b",
+ "name": "Edit Fields",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [40, 1020]
+ },
+ {
+ "parameters": {},
+ "id": "4f4c6527-d565-448a-96bd-8f5414caf8cc",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [-180, 1020]
+ },
+ {
+ "parameters": {
+ "fields": {
+ "values": [
+ {
+ "stringValue": "={{ $('Edit Fields').item.json.name }}"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "44f4e5da-bfe9-4dc3-8d1f-f38e9f364754",
+ "name": "Error",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [460, 1020]
+ }
+ ],
+ "pinData": {
+ "Edit Fields": [
+ {
+ "json": {
+ "id": "23423532",
+ "name": "Jay Gatsby",
+ "email": "gatsby@west-egg.com",
+ "notes": "Keeps asking about a green light??",
+ "country": "US",
+ "created": "1925-04-10"
+ }
+ },
+ {
+ "json": {
+ "id": "23423533",
+ "name": "José Arcadio Buendía",
+ "email": "jab@macondo.co",
+ "notes": "Lots of people named after him. Very confusing",
+ "country": "CO",
+ "created": "1967-05-05"
+ }
+ },
+ {
+ "json": {
+ "id": "23423534",
+ "name": "Max Sendak",
+ "email": "info@in-and-out-of-weeks.org",
+ "notes": "Keeps rolling his terrible eyes",
+ "country": "US",
+ "created": "1963-04-09"
+ }
+ },
+ {
+ "json": {
+ "id": "23423535",
+ "name": "Zaphod Beeblebrox",
+ "email": "captain@heartofgold.com",
+ "notes": "Felt like I was talking to more than one person",
+ "country": null,
+ "created": "1979-10-12"
+ }
+ },
+ {
+ "json": {
+ "id": "23423536",
+ "name": "Edmund Pevensie",
+ "email": "edmund@narnia.gov",
+ "notes": "Passionate sailor",
+ "country": "UK",
+ "created": "1950-10-16"
+ }
+ }
+ ]
+ },
+ "connections": {
+ "Break pairedItem chain": {
+ "main": [
+ [
+ {
+ "node": "Error",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Edit Fields": {
+ "main": [
+ [
+ {
+ "node": "Break pairedItem chain",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Edit Fields",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "versionId": "ca53267f-4eb4-481d-9e09-ecb97f6b09e2",
+ "meta": {
+ "templateCredsSetupCompleted": true,
+ "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
+ },
+ "id": "6fr8GiRyMlZCiDQW",
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_ndv_version.json b/packages/testing/playwright/workflows/Test_workflow_ndv_version.json
new file mode 100644
index 0000000000..9f1a6e6950
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_ndv_version.json
@@ -0,0 +1,41 @@
+{
+ "name": "Node versions",
+ "nodes": [
+ {
+ "id": "2acca986-10a6-451e-b20a-86e95b50e627",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [460, 460]
+ },
+ {
+ "id": "1ea0a87c-3395-4bd9-84fb-cf8b0f769cc4",
+ "name": "Function",
+ "type": "n8n-nodes-base.function",
+ "typeVersion": 1,
+ "position": [960, 460]
+ },
+ {
+ "id": "30bb9fab-bc89-4309-b42b-0fc586519c76",
+ "name": "Edit Fields (old)",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 2,
+ "position": [800, 460]
+ },
+ {
+ "id": "a266b96c-3539-4034-b24c-c86c6d0ca31e",
+ "name": "Edit Fields (no typeVersion)",
+ "type": "n8n-nodes-base.set",
+ "position": [1120, 460]
+ },
+ {
+ "id": "273f60c9-08e7-457e-b01d-31e16c565171",
+ "name": "Edit Fields (latest)",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.4,
+ "position": [640, 460]
+ }
+ ],
+ "connections": {},
+ "pinData": {}
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_schema_test.json b/packages/testing/playwright/workflows/Test_workflow_schema_test.json
new file mode 100644
index 0000000000..5eb5b8b4d2
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_schema_test.json
@@ -0,0 +1,83 @@
+{
+ "name": "My workflow 8",
+ "nodes": [
+ {
+ "parameters": {
+ "operation": "getAllPeople",
+ "limit": 10
+ },
+ "id": "39cd80ce-5a8f-4339-b3d5-c4af969dd330",
+ "name": "Customer Datastore (n8n training)",
+ "type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
+ "typeVersion": 1,
+ "position": [940, 680]
+ },
+ {
+ "parameters": {
+ "values": {
+ "number": [
+ {
+ "name": "objectValue.prop1",
+ "value": 123
+ }
+ ],
+ "string": [
+ {
+ "name": "objectValue.prop2",
+ "value": "someText"
+ }
+ ]
+ },
+ "options": {
+ "dotNotation": true
+ }
+ },
+ "id": "6e4490f6-ba95-4400-beec-2caefdd4895a",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [1300, 680]
+ },
+ {
+ "parameters": {},
+ "id": "58512a93-dabf-4584-817f-27c608c1bdd5",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [720, 680]
+ }
+ ],
+ "pinData": {},
+ "connections": {
+ "Customer Datastore (n8n training)": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Customer Datastore (n8n training)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "4a4f292a-92be-427c-848a-9582527f5ed3",
+ "id": "8",
+ "meta": {
+ "instanceId": "032eceae7493054b723340499be69ecbf4cbe28a7ec6df676b759000750b968d"
+ },
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_schema_test_pinned_data.json b/packages/testing/playwright/workflows/Test_workflow_schema_test_pinned_data.json
new file mode 100644
index 0000000000..f452c0f543
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_schema_test_pinned_data.json
@@ -0,0 +1,565 @@
+{
+ "name": "My workflow",
+ "nodes": [
+ {
+ "parameters": {
+ "operation": "getAllPeople",
+ "limit": 10
+ },
+ "id": "441afcbf-a678-4463-bc89-7e0b6693af5c",
+ "name": "Customer Datastore (n8n training)",
+ "type": "n8n-nodes-base.n8nTrainingCustomerDatastore",
+ "typeVersion": 1,
+ "position": [720, 440]
+ },
+ {
+ "parameters": {
+ "values": {
+ "number": [
+ {
+ "name": "objectValue.prop1",
+ "value": 123
+ }
+ ],
+ "string": [
+ {
+ "name": "objectValue.prop2",
+ "value": "someText"
+ }
+ ]
+ },
+ "options": {
+ "dotNotation": true
+ }
+ },
+ "id": "44094a05-b3b7-49bf-bfbf-a711e6ba45d8",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [1080, 440]
+ },
+ {
+ "parameters": {},
+ "id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [500, 440]
+ }
+ ],
+ "pinData": {
+ "Set": [
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ },
+ {
+ "json": {
+ "key0": 0,
+ "key1": 1,
+ "key2": 2,
+ "key3": 3,
+ "key4": 4,
+ "key5": 5,
+ "key6": 6,
+ "key7": 7,
+ "key8": 8,
+ "key9": 9,
+ "key10": 10,
+ "key11": 11,
+ "key12": 12,
+ "key13": 13,
+ "key14": 14,
+ "key15": 15,
+ "key16": 16,
+ "key17": 17,
+ "key18": 18,
+ "key19": 19
+ }
+ }
+ ]
+ },
+ "connections": {
+ "Customer Datastore (n8n training)": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Customer Datastore (n8n training)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "",
+ "meta": {
+ "instanceId": "363581be2c2581d1b11e189456a090887e137f8393a4b5cb85641b1ee4fae479"
+ },
+ "tags": []
+}
diff --git a/packages/testing/playwright/workflows/Test_workflow_xml_output.json b/packages/testing/playwright/workflows/Test_workflow_xml_output.json
new file mode 100644
index 0000000000..9131f8d8bc
--- /dev/null
+++ b/packages/testing/playwright/workflows/Test_workflow_xml_output.json
@@ -0,0 +1,47 @@
+{
+ "meta": {
+ "instanceId": "2d1cf27f75b18bb9e146336f791c37884f4fc7ddb97c2def27c0444d106778bf"
+ },
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [420, 220]
+ },
+ {
+ "parameters": {
+ "fields": {
+ "values": [
+ {
+ "name": "body",
+ "stringValue": " Introduction to XML John Doe 2020 1234567890 Data Science Basics Jane Smith 2019 0987654321 Programming in Python Bob Johnson 2021 5432109876 "
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "45888152-7c5f-4d88-9039-660c594da084",
+ "name": "Edit Fields",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 3.2,
+ "position": [640, 220]
+ }
+ ],
+ "connections": {
+ "When clicking ‘Execute workflow’": {
+ "main": [
+ [
+ {
+ "node": "Edit Fields",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "pinData": {}
+}
diff --git a/packages/testing/playwright/workflows/open_node_creator_for_connection.json b/packages/testing/playwright/workflows/open_node_creator_for_connection.json
new file mode 100644
index 0000000000..58cd4d7522
--- /dev/null
+++ b/packages/testing/playwright/workflows/open_node_creator_for_connection.json
@@ -0,0 +1,89 @@
+{
+ "name": "open_node_creator_for_connection",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "25ff0c17-7064-4e14-aec6-45c71d63201b",
+ "name": "When clicking ‘Execute workflow’",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [740, 520]
+ },
+ {
+ "parameters": {},
+ "id": "49f376ca-845b-4737-aac0-073d0e4fa95c",
+ "name": "Token Splitter",
+ "type": "@n8n/n8n-nodes-langchain.textSplitterTokenSplitter",
+ "typeVersion": 1,
+ "position": [1180, 540]
+ },
+ {
+ "parameters": {},
+ "id": "d1db5111-4b01-4620-8ccb-a16ea576c363",
+ "name": "Memory",
+ "type": "@n8n/n8n-nodes-langchain.memoryXata",
+ "typeVersion": 1.2,
+ "position": [940, 540],
+ "credentials": {
+ "xataApi": {
+ "id": "q1ckaYlHTWCYDtF0",
+ "name": "Xata Api account"
+ }
+ }
+ },
+ {
+ "parameters": {},
+ "id": "b08b6d3a-bef8-42ac-9cef-ec9d4e5402b1",
+ "name": "Output Parser",
+ "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
+ "typeVersion": 1.1,
+ "position": [1060, 540]
+ },
+ {
+ "parameters": {},
+ "id": "ee557938-9cf1-4b78-afef-c783c52fd307",
+ "name": "Tool",
+ "type": "@n8n/n8n-nodes-langchain.toolWikipedia",
+ "typeVersion": 1,
+ "position": [1300, 540]
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "id": "814f2e9c-cc7b-4f3c-89b4-d6eb82bc24df",
+ "name": "Embeddings",
+ "type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference",
+ "typeVersion": 1,
+ "position": [1420, 540]
+ },
+ {
+ "parameters": {
+ "tableName": {
+ "__rl": true,
+ "mode": "list",
+ "value": ""
+ },
+ "options": {}
+ },
+ "id": "e8569b0b-a580-4249-9c5e-f1feed5c644e",
+ "name": "Vector Store",
+ "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
+ "typeVersion": 1,
+ "position": [1540, 540]
+ }
+ ],
+ "pinData": {},
+ "connections": {},
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "versionId": "8e90604c-f7e9-489d-8e43-1cc699b7db04",
+ "meta": {
+ "templateCredsSetupCompleted": true,
+ "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
+ },
+ "id": "L3tgfoW660UOSuX6",
+ "tags": []
+}