mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix: Add Exact Match Option for NDV (no-changelog) (#15787)
Co-authored-by: Eugene Molodkin <eugene@n8n.io>
This commit is contained in:
@@ -36,6 +36,10 @@ export function getInputSelect() {
|
|||||||
return cy.getByTestId('ndv-input-select').find('input');
|
return cy.getByTestId('ndv-input-select').find('input');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getInputLinkRun() {
|
||||||
|
return getInputPanel().findChildByTestId('link-run');
|
||||||
|
}
|
||||||
|
|
||||||
export function getMainPanel() {
|
export function getMainPanel() {
|
||||||
return cy.getByTestId('node-parameters');
|
return cy.getByTestId('node-parameters');
|
||||||
}
|
}
|
||||||
@@ -68,6 +72,14 @@ export function getInputTbodyCell(row: number, col: number) {
|
|||||||
return getInputTableRows().eq(row).find('td').eq(col);
|
return getInputTableRows().eq(row).find('td').eq(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getInputRunSelector() {
|
||||||
|
return getInputPanel().findChildByTestId('run-selector');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInputPanelItemsCount() {
|
||||||
|
return getInputPanel().getByTestId('ndv-items-count');
|
||||||
|
}
|
||||||
|
|
||||||
export function getOutputPanelDataContainer() {
|
export function getOutputPanelDataContainer() {
|
||||||
return getOutputPanel().findChildByTestId('ndv-data-container');
|
return getOutputPanel().findChildByTestId('ndv-data-container');
|
||||||
}
|
}
|
||||||
@@ -329,3 +341,7 @@ export function resetHoverState() {
|
|||||||
export function setInputDisplayMode(mode: 'Schema' | 'Table' | 'JSON' | 'Binary') {
|
export function setInputDisplayMode(mode: 'Schema' | 'Table' | 'JSON' | 'Binary') {
|
||||||
getInputPanel().findChildByTestId('ndv-run-data-display-mode').contains(mode).click();
|
getInputPanel().findChildByTestId('ndv-run-data-display-mode').contains(mode).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleInputRunLinking() {
|
||||||
|
getInputLinkRun().click();
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,35 +179,52 @@ export function executeWorkflowAndWait(waitForSuccessBannerToDisappear = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param nodeDisplayName - The name of the node to add to the canvas
|
||||||
|
* @param plusButtonClick - Whether to click the plus button to open the node creator
|
||||||
|
* @param preventNdvClose - Whether to prevent the Ndv from closing
|
||||||
|
* @param action - The action to select in the node creator
|
||||||
|
* @param useExactMatch - Whether to use an exact match for the node name will use selector instead of enter key
|
||||||
|
*/
|
||||||
export function addNodeToCanvas(
|
export function addNodeToCanvas(
|
||||||
nodeDisplayName: string,
|
nodeDisplayName: string,
|
||||||
plusButtonClick = true,
|
plusButtonClick = true,
|
||||||
preventNdvClose?: boolean,
|
preventNdvClose?: boolean,
|
||||||
action?: string,
|
action?: string,
|
||||||
|
useExactMatch = false,
|
||||||
) {
|
) {
|
||||||
if (plusButtonClick) {
|
if (plusButtonClick) {
|
||||||
getNodeCreatorPlusButton().click();
|
getNodeCreatorPlusButton().click();
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeCreatorSearchBar().type(nodeDisplayName);
|
getNodeCreatorSearchBar().type(nodeDisplayName);
|
||||||
getNodeCreatorSearchBar().type('{enter}');
|
|
||||||
|
if (useExactMatch) {
|
||||||
|
cy.getByTestId('node-creator-item-name').contains(nodeDisplayName).click();
|
||||||
|
} else {
|
||||||
|
getNodeCreatorSearchBar().type('{enter}');
|
||||||
|
}
|
||||||
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
|
|
||||||
cy.get('body').then((body) => {
|
cy.get('body').then((body) => {
|
||||||
if (body.find('[data-test-id=node-creator]').length > 0) {
|
if (body.find('[data-test-id=node-creator]').length > 0) {
|
||||||
if (action) {
|
if (action) {
|
||||||
cy.contains(action).click();
|
cy.contains(action).click();
|
||||||
} else {
|
} else {
|
||||||
// Select the first action
|
|
||||||
cy.get('[data-keyboard-nav-type="action"]').eq(0).click();
|
cy.get('[data-keyboard-nav-type="action"]').eq(0).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!preventNdvClose) cy.get('body').type('{esc}');
|
if (!preventNdvClose) {
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
|
export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
|
||||||
cy.visit(ROUTES.NEW_WORKFLOW_PAGE);
|
cy.visit(ROUTES.NEW_WORKFLOW_PAGE);
|
||||||
|
cy.getByTestId('node-creator-plus-button').should('be.visible');
|
||||||
cy.waitForLoad();
|
cy.waitForLoad();
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
|
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ function createRunDataWithError(inputMessage: string) {
|
|||||||
function setupTestWorkflow(chatTrigger: boolean = false) {
|
function setupTestWorkflow(chatTrigger: boolean = false) {
|
||||||
// Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model
|
// Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model
|
||||||
if (chatTrigger) {
|
if (chatTrigger) {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
} else {
|
} else {
|
||||||
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
sendManualChatMessage,
|
sendManualChatMessage,
|
||||||
} from '../composables/modals/chat-modal';
|
} from '../composables/modals/chat-modal';
|
||||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||||
|
import * as ndv from '../composables/ndv';
|
||||||
import {
|
import {
|
||||||
clickCreateNewCredential,
|
clickCreateNewCredential,
|
||||||
clickExecuteNode,
|
clickExecuteNode,
|
||||||
@@ -29,6 +30,7 @@ import {
|
|||||||
getOutputPanelTable,
|
getOutputPanelTable,
|
||||||
checkParameterCheckboxInputByName,
|
checkParameterCheckboxInputByName,
|
||||||
} from '../composables/ndv';
|
} from '../composables/ndv';
|
||||||
|
import * as workflow from '../composables/workflow';
|
||||||
import {
|
import {
|
||||||
addLanguageModelNodeToParent,
|
addLanguageModelNodeToParent,
|
||||||
addMemoryNodeToParent,
|
addMemoryNodeToParent,
|
||||||
@@ -44,7 +46,7 @@ import {
|
|||||||
disableNode,
|
disableNode,
|
||||||
getExecuteWorkflowButton,
|
getExecuteWorkflowButton,
|
||||||
} from '../composables/workflow';
|
} from '../composables/workflow';
|
||||||
import { NDV, WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||||
|
|
||||||
describe('Langchain Integration', () => {
|
describe('Langchain Integration', () => {
|
||||||
@@ -132,7 +134,7 @@ describe('Langchain Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to open and execute Basic LLM Chain node', () => {
|
it('should be able to open and execute Basic LLM Chain node', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(BASIC_LLM_CHAIN_NODE_NAME, true);
|
addNodeToCanvas(BASIC_LLM_CHAIN_NODE_NAME, true);
|
||||||
|
|
||||||
addLanguageModelNodeToParent(
|
addLanguageModelNodeToParent(
|
||||||
@@ -171,7 +173,7 @@ describe('Langchain Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to open and execute Agent node', () => {
|
it('should be able to open and execute Agent node', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||||
|
|
||||||
addLanguageModelNodeToParent(
|
addLanguageModelNodeToParent(
|
||||||
@@ -211,7 +213,7 @@ describe('Langchain Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add and use Manual Chat Trigger node together with Agent node', () => {
|
it('should add and use Manual Chat Trigger node together with Agent node', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||||
|
|
||||||
addLanguageModelNodeToParent(
|
addLanguageModelNodeToParent(
|
||||||
@@ -362,7 +364,7 @@ describe('Langchain Integration', () => {
|
|||||||
getNodes().should('have.length', 3);
|
getNodes().should('have.length', 3);
|
||||||
});
|
});
|
||||||
it('should not auto-add nodes if ChatTrigger is already present', () => {
|
it('should not auto-add nodes if ChatTrigger is already present', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||||
|
|
||||||
addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true);
|
addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true);
|
||||||
@@ -372,7 +374,6 @@ describe('Langchain Integration', () => {
|
|||||||
|
|
||||||
it('should render runItems for sub-nodes and allow switching between them', () => {
|
it('should render runItems for sub-nodes and allow switching between them', () => {
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
|
||||||
|
|
||||||
cy.visit(workflowPage.url);
|
cy.visit(workflowPage.url);
|
||||||
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
|
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
|
||||||
@@ -380,51 +381,56 @@ describe('Langchain Integration', () => {
|
|||||||
workflowPage.actions.deselectAll();
|
workflowPage.actions.deselectAll();
|
||||||
|
|
||||||
workflowPage.actions.executeNode('Populate VS');
|
workflowPage.actions.executeNode('Populate VS');
|
||||||
|
workflow.getNodesWithSpinner().should('not.exist');
|
||||||
|
|
||||||
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
|
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
|
||||||
ndv.getters.outputPanel().contains(text).should(assertion);
|
ndv.getOutputPanel().contains(text).should(assertion);
|
||||||
ndv.getters.inputPanel().contains(text).should(assertion);
|
ndv.getOutputPanel().contains(text).should(assertion);
|
||||||
};
|
};
|
||||||
|
|
||||||
workflowPage.actions.openNode('Character Text Splitter');
|
workflowPage.actions.openNode('Character Text Splitter');
|
||||||
ndv.getters.outputRunSelector().should('exist');
|
|
||||||
ndv.getters.inputRunSelector().should('exist');
|
// Wait for the input panel to switch to Debugging mode
|
||||||
ndv.getters.inputRunSelector().find('input').should('include.value', '3 of 3');
|
ndv.getInputPanelItemsCount().should('not.exist');
|
||||||
ndv.getters.outputRunSelector().find('input').should('include.value', '3 of 3');
|
|
||||||
|
ndv.getOutputRunSelector().should('exist');
|
||||||
|
ndv.getInputRunSelector().should('exist');
|
||||||
|
ndv.getInputRunSelector().find('input').should('include.value', '3 of 3');
|
||||||
|
ndv.getOutputRunSelector().find('input').should('include.value', '3 of 3');
|
||||||
assertInputOutputText('Kyiv', 'exist');
|
assertInputOutputText('Kyiv', 'exist');
|
||||||
assertInputOutputText('Berlin', 'not.exist');
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
assertInputOutputText('Prague', 'not.exist');
|
assertInputOutputText('Prague', 'not.exist');
|
||||||
|
|
||||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
ndv.changeOutputRunSelector('2 of 3');
|
||||||
assertInputOutputText('Berlin', 'exist');
|
assertInputOutputText('Berlin', 'exist');
|
||||||
assertInputOutputText('Kyiv', 'not.exist');
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
assertInputOutputText('Prague', 'not.exist');
|
assertInputOutputText('Prague', 'not.exist');
|
||||||
|
|
||||||
ndv.actions.changeOutputRunSelector('1 of 3');
|
ndv.changeOutputRunSelector('1 of 3');
|
||||||
assertInputOutputText('Prague', 'exist');
|
assertInputOutputText('Prague', 'exist');
|
||||||
assertInputOutputText('Berlin', 'not.exist');
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
assertInputOutputText('Kyiv', 'not.exist');
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
|
|
||||||
ndv.actions.toggleInputRunLinking();
|
ndv.toggleInputRunLinking();
|
||||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
ndv.changeOutputRunSelector('2 of 3');
|
||||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
ndv.getInputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 3');
|
ndv.getOutputRunSelector().find('input').should('include.value', '2 of 3');
|
||||||
ndv.getters.inputPanel().contains('Prague').should('exist');
|
ndv.getInputPanel().contains('Prague').should('exist');
|
||||||
ndv.getters.inputPanel().contains('Berlin').should('not.exist');
|
ndv.getInputPanel().contains('Berlin').should('not.exist');
|
||||||
|
|
||||||
ndv.getters.outputPanel().contains('Berlin').should('exist');
|
ndv.getOutputPanel().contains('Berlin').should('exist');
|
||||||
ndv.getters.outputPanel().contains('Prague').should('not.exist');
|
ndv.getOutputPanel().contains('Prague').should('not.exist');
|
||||||
|
|
||||||
ndv.actions.toggleInputRunLinking();
|
ndv.toggleInputRunLinking();
|
||||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
ndv.getInputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 3');
|
ndv.getOutputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
assertInputOutputText('Prague', 'exist');
|
assertInputOutputText('Prague', 'exist');
|
||||||
assertInputOutputText('Berlin', 'not.exist');
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
assertInputOutputText('Kyiv', 'not.exist');
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show tool info notice if no existing tools were used during execution', () => {
|
it('should show tool info notice if no existing tools were used during execution', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true);
|
||||||
|
|
||||||
addLanguageModelNodeToParent(
|
addLanguageModelNodeToParent(
|
||||||
@@ -469,7 +475,7 @@ describe('Langchain Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not show tool info notice if tools were used during execution', () => {
|
it('should not show tool info notice if tools were used during execution', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||||
getRunDataInfoCallout().should('not.exist');
|
getRunDataInfoCallout().should('not.exist');
|
||||||
clickGetBackToCanvas();
|
clickGetBackToCanvas();
|
||||||
@@ -523,7 +529,6 @@ describe('Langchain Integration', () => {
|
|||||||
|
|
||||||
it('should execute up to Node 1 when using partial execution', () => {
|
it('should execute up to Node 1 when using partial execution', () => {
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
|
||||||
|
|
||||||
cy.visit(workflowPage.url);
|
cy.visit(workflowPage.url);
|
||||||
cy.createFixtureWorkflow('Test_workflow_chat_partial_execution.json');
|
cy.createFixtureWorkflow('Test_workflow_chat_partial_execution.json');
|
||||||
@@ -531,7 +536,7 @@ describe('Langchain Integration', () => {
|
|||||||
|
|
||||||
getManualChatModal().find('main').should('not.exist');
|
getManualChatModal().find('main').should('not.exist');
|
||||||
openNode('Node 1');
|
openNode('Node 1');
|
||||||
ndv.actions.execute();
|
ndv.clickExecuteNode();
|
||||||
|
|
||||||
getManualChatModal().find('main').should('exist');
|
getManualChatModal().find('main').should('exist');
|
||||||
sendManualChatMessage('Test');
|
sendManualChatMessage('Test');
|
||||||
|
|||||||
@@ -548,7 +548,7 @@ describe('Node Creator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add node directly for sub-connection as tool', () => {
|
it('should add node directly for sub-connection as tool', () => {
|
||||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||||
clickGetBackToCanvas();
|
clickGetBackToCanvas();
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ describe('Sub-workflow creation and typed usage', () => {
|
|||||||
// NAVIGATE TO CHILD WORKFLOW
|
// NAVIGATE TO CHILD WORKFLOW
|
||||||
// **************************
|
// **************************
|
||||||
// Close NDV before opening the node creator
|
// Close NDV before opening the node creator
|
||||||
cy.get('body').type('{esc}');
|
clickGetBackToCanvas();
|
||||||
openNode('When Executed by Another Workflow');
|
openNode('When Executed by Another Workflow');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user