mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
test: Migrate NDV tests from Cypress -> Playwright (#19148)
This commit is contained in:
@@ -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: '<div></div>',
|
||||
},
|
||||
].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('<div>Hello World</div>');
|
||||
ndv.getters.codeEditorFullscreen().should('contain.text', '<div>Hello World</div>');
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters
|
||||
.parameterInput('html')
|
||||
.get('.cm-content')
|
||||
.should('contain.text', '<div>Hello World</div>');
|
||||
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', '<?xml version="1.0" encoding="UTF-8"?> <library>');
|
||||
|
||||
cy.document().trigger('keyup', { key: '/' });
|
||||
ndv.getters.searchInput().filter(':focus').type('<lib');
|
||||
|
||||
ndv.getters.outputTableRow(1).find('mark').should('have.text', '<lib');
|
||||
|
||||
ndv.getters.outputDisplayMode().find('label').eq(2).should('include.text', 'JSON');
|
||||
ndv.getters
|
||||
.outputDisplayMode()
|
||||
.find('label')
|
||||
.eq(2)
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
ndv.getters.outputDataContainer().find('.json-data').should('exist');
|
||||
ndv.getters
|
||||
.outputDataContainer()
|
||||
.should(
|
||||
'have.text',
|
||||
'[{"body": "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?> <library> <book> <title>Introduction to XML</title> <author>John Doe</author> <publication_year>2020</publication_year> <isbn>1234567890</isbn> </book> <book> <title>Data Science Basics</title> <author>Jane Smith</author> <publication_year>2019</publication_year> <isbn>0987654321</isbn> </book> <book> <title>Programming in Python</title> <author>Bob Johnson</author> <publication_year>2021</publication_year> <isbn>5432109876</isbn> </book> </library>"}]',
|
||||
);
|
||||
ndv.getters.outputDataContainer().find('mark').should('have.text', '<lib');
|
||||
|
||||
ndv.getters.outputDisplayMode().find('label').eq(0).should('include.text', 'Schema');
|
||||
ndv.getters.outputDisplayMode().find('label').eq(0).click({ force: true });
|
||||
ndv.getters
|
||||
.outputDataContainer()
|
||||
.findChildByTestId('run-data-schema-item-value')
|
||||
.should('include.text', '<?xml version="1.0" encoding="UTF-8"?>');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
42
packages/testing/playwright/fixtures/Dummy_javascript.txt
Normal file
42
packages/testing/playwright/fixtures/Dummy_javascript.txt
Normal file
@@ -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;
|
||||
};
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
const input = this.getParameterInputField(parameterName);
|
||||
await input.click();
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click parameter options expansion (e.g. for Response Code)
|
||||
*/
|
||||
async clickParameterOptions(): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.getParameterInput(parameterName).click();
|
||||
|
||||
await this.page.getByRole('option', { name: optionText }).click();
|
||||
}
|
||||
|
||||
async changeNodeOperation(operationName: string): Promise<void> {
|
||||
await this.setParameterDropdown('operation', operationName);
|
||||
}
|
||||
|
||||
async setParameterInput(parameterName: string, value: string): Promise<void> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
800
packages/testing/playwright/tests/ui/5-ndv.spec.ts
Normal file
800
packages/testing/playwright/tests/ui/5-ndv.spec.ts
Normal file
@@ -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('<div></div>');
|
||||
await n8n.ndv.getParameterInputField('name').click();
|
||||
await expect(n8n.ndv.getParameterExpressionPreviewValue()).toContainText('<div></div>');
|
||||
});
|
||||
|
||||
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(
|
||||
'<?xml version="1.0" encoding="UTF-8"?> <library>',
|
||||
);
|
||||
|
||||
await n8n.page.keyboard.press('/');
|
||||
|
||||
const searchInput = n8n.ndv.getOutputSearchInput();
|
||||
await expect(searchInput).toBeFocused();
|
||||
await searchInput.fill('<lib');
|
||||
|
||||
await expect(n8n.ndv.getOutputTableRow(1).locator('mark')).toContainText('<lib');
|
||||
|
||||
await n8n.ndv.switchOutputMode('JSON');
|
||||
|
||||
await expect(n8n.ndv.getOutputDataContainer().locator('.json-data')).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe('Run Data & Selectors - Advanced', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
221
packages/testing/playwright/workflows/Floating_Nodes.json
Normal file
221
packages/testing/playwright/workflows/Floating_Nodes.json
Normal file
@@ -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": []
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
126
packages/testing/playwright/workflows/Test_ndv_search.json
Normal file
126
packages/testing/playwright/workflows/Test_ndv_search.json
Normal file
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
258
packages/testing/playwright/workflows/Test_workflow_5.json
Normal file
258
packages/testing/playwright/workflows/Test_workflow_5.json
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <library> <book> <title>Introduction to XML</title> <author>John Doe</author> <publication_year>2020</publication_year> <isbn>1234567890</isbn> </book> <book> <title>Data Science Basics</title> <author>Jane Smith</author> <publication_year>2019</publication_year> <isbn>0987654321</isbn> </book> <book> <title>Programming in Python</title> <author>Bob Johnson</author> <publication_year>2021</publication_year> <isbn>5432109876</isbn> </book> </library>"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
Reference in New Issue
Block a user