diff --git a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts deleted file mode 100644 index 508f606cb6..0000000000 --- a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts +++ /dev/null @@ -1,280 +0,0 @@ -import type { ExecutionError } from 'n8n-workflow'; - -import * as logs from '../composables/logs'; -import { - closeManualChatModal, - getManualChatMessages, - sendManualChatMessage, -} from '../composables/modals/chat-modal'; -import { setCredentialValues } from '../composables/modals/credential-modal'; -import { - clickCreateNewCredential, - clickExecuteNode, - clickGetBackToCanvas, -} from '../composables/ndv'; -import { - addLanguageModelNodeToParent, - addMemoryNodeToParent, - addNodeToCanvas, - addToolNodeToParent, - navigateToNewWorkflowPage, - openNode, -} from '../composables/workflow'; -import { - AGENT_NODE_NAME, - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AI_MEMORY_POSTGRES_NODE_NAME, - AI_TOOL_CALCULATOR_NODE_NAME, - CHAT_TRIGGER_NODE_DISPLAY_NAME, - MANUAL_CHAT_TRIGGER_NODE_NAME, - MANUAL_TRIGGER_NODE_DISPLAY_NAME, - MANUAL_TRIGGER_NODE_NAME, -} from '../constants'; -import { NDV, WorkflowPage as WorkflowPageClass } from '../pages'; -import { createMockNodeExecutionData, getVisibleSelect, runMockWorkflowExecution } from '../utils'; - -const ndv = new NDV(); -const WorkflowPage = new WorkflowPageClass(); - -function createRunDataWithError(inputMessage: string) { - return [ - createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, { - jsonData: { - main: { input: inputMessage }, - }, - }), - createMockNodeExecutionData(AI_MEMORY_POSTGRES_NODE_NAME, { - jsonData: { - ai_memory: { - json: { - action: 'loadMemoryVariables', - values: { - input: inputMessage, - system_message: 'You are a helpful assistant', - formatting_instructions: - 'IMPORTANT: Always call `format_final_json_response` to format your final response!', - }, - }, - }, - }, - inputOverride: { - ai_memory: [ - [ - { - json: { - action: 'loadMemoryVariables', - values: { - input: inputMessage, - system_message: 'You are a helpful assistant', - formatting_instructions: - 'IMPORTANT: Always call `format_final_json_response` to format your final response!', - }, - }, - }, - ], - ], - }, - source: [{ previousNode: AGENT_NODE_NAME, previousNodeRun: 0 }], - error: { - message: 'Internal error', - timestamp: 1722591723244, - name: 'NodeOperationError', - description: 'Internal error', - context: {}, - cause: { - name: 'error', - severity: 'FATAL', - code: '3D000', - file: 'postinit.c', - line: '885', - routine: 'InitPostgres', - } as unknown as Error, - } as ExecutionError, - metadata: { - subRun: [ - { - node: 'Postgres Chat Memory', - runIndex: 0, - }, - ], - }, - }), - createMockNodeExecutionData(AGENT_NODE_NAME, { - executionStatus: 'error', - error: { - level: 'error', - tags: { - packageName: 'workflow', - }, - context: {}, - functionality: 'configuration-node', - name: 'NodeOperationError', - timestamp: 1722591723244, - node: { - parameters: { - notice: '', - sessionIdType: 'fromInput', - tableName: 'n8n_chat_histories', - }, - id: '6b9141da-0135-4e9d-94d1-2d658cbf48b5', - name: 'Postgres Chat Memory', - type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', - typeVersion: 1, - position: [1140, 500], - credentials: { - postgres: { - id: 'RkyZetVpGsSfEAhQ', - name: 'Postgres account', - }, - }, - }, - messages: ['database "chat11" does not exist'], - description: 'Internal error', - message: 'Internal error', - } as unknown as ExecutionError, - }), - ]; -} - -function setupTestWorkflow(chatTrigger: boolean = false) { - // Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model - if (chatTrigger) { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - } else { - addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); - } - - addNodeToCanvas(AGENT_NODE_NAME, true); - - if (!chatTrigger) { - // Remove chat trigger - WorkflowPage.getters - .canvasNodeByName(CHAT_TRIGGER_NODE_DISPLAY_NAME) - .find('[data-test-id="delete-node-button"]') - .click({ force: true }); - - // Set manual trigger to output standard pinned data - openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME); - ndv.actions.editPinnedData(); - ndv.actions.savePinnedData(); - ndv.actions.close(); - } - - // Calculator is added just to make OpenAI Chat Model work (tools can not be empty with OpenAI model) - addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - - addMemoryNodeToParent(AI_MEMORY_POSTGRES_NODE_NAME, AGENT_NODE_NAME); - - clickCreateNewCredential(); - setCredentialValues({ - password: 'testtesttest', - }); - - ndv.getters.parameterInput('sessionIdType').click(); - getVisibleSelect().contains('Define below').click(); - ndv.getters.parameterInput('sessionKey').type('asdasd'); - - clickGetBackToCanvas(); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - WorkflowPage.actions.zoomToFit(); -} - -function checkMessages(inputMessage: string, outputMessage: string) { - const messages = getManualChatMessages(); - messages.should('have.length', 2); - messages.should('contain', inputMessage); - messages.should('contain', outputMessage); - - logs.getOverviewPanelBody().should('exist'); - logs.getLogEntries().should('have.length', 2); - logs.getSelectedLogEntry().should('have.text', 'AI Agent'); - logs.getOutputPanel().should('contain', AI_MEMORY_POSTGRES_NODE_NAME); -} - -describe("AI-233 Make root node's logs pane active in case of an error in sub-nodes", () => { - beforeEach(() => { - navigateToNewWorkflowPage(); - }); - - it('should open logs tab by default when there was an error', () => { - setupTestWorkflow(true); - - openNode(AGENT_NODE_NAME); - - const inputMessage = 'Test the code tool'; - - clickExecuteNode(); - runMockWorkflowExecution({ - trigger: () => sendManualChatMessage(inputMessage), - runData: createRunDataWithError(inputMessage), - lastNodeExecuted: AGENT_NODE_NAME, - }); - - checkMessages(inputMessage, '[ERROR: Internal error]'); - closeManualChatModal(); - - // Open the AI Agent node to see the logs - openNode(AGENT_NODE_NAME); - - // Finally check that logs pane is opened by default - ndv.getters.outputDataContainer().should('be.visible'); - - ndv.getters.aiOutputModeToggle().should('be.visible'); - ndv.getters - .aiOutputModeToggle() - .find('[role="radio"]') - .should('have.length', 2) - .eq(1) - .should('have.attr', 'aria-checked', 'true'); - - ndv.getters - .outputPanel() - .findChildByTestId('node-error-message') - .should('be.visible') - .should('contain', 'Error in sub-node'); - }); - - it('should switch to logs tab on error, when NDV is already opened', () => { - setupTestWorkflow(false); - - openNode(AGENT_NODE_NAME); - - const inputMessage = 'Test the code tool'; - - runMockWorkflowExecution({ - trigger: () => clickExecuteNode(), - runData: createRunDataWithError(inputMessage), - lastNodeExecuted: AGENT_NODE_NAME, - }); - - // Check that logs pane is opened by default - ndv.getters.outputDataContainer().should('be.visible'); - - ndv.getters.aiOutputModeToggle().should('be.visible'); - ndv.getters - .aiOutputModeToggle() - .find('[role="radio"]') - .should('have.length', 2) - .eq(1) - .should('have.attr', 'aria-checked', 'true'); - - ndv.getters - .outputPanel() - .findChildByTestId('node-error-message') - .should('be.visible') - .should('contain', 'Error in sub-node'); - }); -}); diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts deleted file mode 100644 index 4aca21e3f6..0000000000 --- a/cypress/e2e/30-langchain.cy.ts +++ /dev/null @@ -1,549 +0,0 @@ -import { - AGENT_NODE_NAME, - MANUAL_CHAT_TRIGGER_NODE_NAME, - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - MANUAL_TRIGGER_NODE_NAME, - AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, - AI_TOOL_CALCULATOR_NODE_NAME, - AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, - AI_TOOL_CODE_NODE_NAME, - AI_TOOL_WIKIPEDIA_NODE_NAME, - BASIC_LLM_CHAIN_NODE_NAME, - EDIT_FIELDS_SET_NODE_NAME, - CHAT_TRIGGER_NODE_DISPLAY_NAME, -} from './../constants'; -import * as logs from '../composables/logs'; -import { - closeManualChatModal, - getManualChatInput, - getManualChatMessages, - getManualChatModal, - sendManualChatMessage, -} from '../composables/modals/chat-modal'; -import { setCredentialValues } from '../composables/modals/credential-modal'; -import * as ndv from '../composables/ndv'; -import { - clickCreateNewCredential, - clickExecuteNode, - clickGetBackToCanvas, - getRunDataInfoCallout, - getOutputPanelTable, - checkParameterCheckboxInputByName, -} from '../composables/ndv'; -import * as workflow from '../composables/workflow'; -import { - addLanguageModelNodeToParent, - addMemoryNodeToParent, - addNodeToCanvas, - addOutputParserNodeToParent, - addToolNodeToParent, - clickExecuteWorkflowButton, - clickManualChatButton, - navigateToNewWorkflowPage, - getNodes, - openNode, - getConnectionBySourceAndTarget, - disableNode, - getExecuteWorkflowButton, -} from '../composables/workflow'; -import { WorkflowPage } from '../pages'; -import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils'; - -describe('Langchain Integration', () => { - beforeEach(() => { - navigateToNewWorkflowPage(); - }); - - it('should not open chat modal', () => { - addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); - addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); - - clickGetBackToCanvas(); - - addNodeToCanvas(AGENT_NODE_NAME, true, true); - clickGetBackToCanvas(); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - clickGetBackToCanvas(); - - clickExecuteWorkflowButton(); - getManualChatModal().should('not.exist'); - }); - - it('should remove test workflow button', () => { - addNodeToCanvas('Schedule Trigger', true); - addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true); - - clickGetBackToCanvas(); - - addNodeToCanvas(AGENT_NODE_NAME, true, true); - clickGetBackToCanvas(); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - clickGetBackToCanvas(); - - disableNode('Schedule Trigger'); - getExecuteWorkflowButton().should('not.exist'); - }); - - it('should add nodes to all Agent node input types', () => { - addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); - addNodeToCanvas(AGENT_NODE_NAME, true, true); - checkParameterCheckboxInputByName('hasOutputParser'); - clickGetBackToCanvas(); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - clickGetBackToCanvas(); - - addMemoryNodeToParent(AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - - addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - - addOutputParserNodeToParent(AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - }); - - it('should add multiple tool nodes to Agent node tool input type', () => { - addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); - addNodeToCanvas(AGENT_NODE_NAME, true); - - [ - AI_TOOL_CALCULATOR_NODE_NAME, - AI_TOOL_CODE_NODE_NAME, - AI_TOOL_CODE_NODE_NAME, - AI_TOOL_CODE_NODE_NAME, - AI_TOOL_WIKIPEDIA_NODE_NAME, - ].forEach((tool) => { - addToolNodeToParent(tool, AGENT_NODE_NAME); - clickGetBackToCanvas(); - }); - }); - - it('should be able to open and execute Basic LLM Chain node', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(BASIC_LLM_CHAIN_NODE_NAME, true); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - BASIC_LLM_CHAIN_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - openNode(BASIC_LLM_CHAIN_NODE_NAME); - const inputMessage = 'Hello!'; - const outputMessage = 'Hi there! How can I assist you today?'; - - clickExecuteNode(); - runMockWorkflowExecution({ - trigger: () => sendManualChatMessage(inputMessage), - runData: [ - createMockNodeExecutionData(BASIC_LLM_CHAIN_NODE_NAME, { - jsonData: { - main: { output: outputMessage }, - }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, - }), - ], - lastNodeExecuted: BASIC_LLM_CHAIN_NODE_NAME, - }); - - getManualChatMessages().should('contain', outputMessage); - }); - - it('should be able to open and execute Agent node', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(AGENT_NODE_NAME, true); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - openNode(AGENT_NODE_NAME); - - const inputMessage = 'Hello!'; - const outputMessage = 'Hi there! How can I assist you today?'; - - clickExecuteNode(); - runMockWorkflowExecution({ - trigger: () => sendManualChatMessage(inputMessage), - runData: [ - createMockNodeExecutionData(AGENT_NODE_NAME, { - jsonData: { - main: { output: outputMessage }, - }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, - }), - ], - lastNodeExecuted: AGENT_NODE_NAME, - }); - - getManualChatMessages().should('contain', outputMessage); - }); - - it('should add and use Manual Chat Trigger node together with Agent node', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(AGENT_NODE_NAME, true); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - clickManualChatButton(); - - const inputMessage = 'Hello!'; - const outputMessage = 'Hi there! How can I assist you today?'; - const runData = [ - createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, { - jsonData: { - main: { input: inputMessage }, - }, - }), - createMockNodeExecutionData(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { - jsonData: { - ai_languageModel: { - response: { - generations: [ - { - text: `{ - "action": "Final Answer", - "action_input": "${outputMessage}" -}`, - message: { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'AIMessage'], - kwargs: { - content: `{ - "action": "Final Answer", - "action_input": "${outputMessage}" -}`, - additional_kwargs: {}, - }, - }, - generationInfo: { finish_reason: 'stop' }, - }, - ], - llmOutput: { - tokenUsage: { - completionTokens: 26, - promptTokens: 519, - totalTokens: 545, - }, - }, - }, - }, - }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, - source: [{ previousNode: AGENT_NODE_NAME, previousNodeRun: 0 }], - inputOverride: { - ai_languageModel: [ - [ - { - json: { - messages: [ - { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'SystemMessage'], - kwargs: { - content: - 'Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist. However, above all else, all responses must adhere to the format of RESPONSE FORMAT INSTRUCTIONS.', - additional_kwargs: {}, - }, - }, - { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'HumanMessage'], - kwargs: { - content: - 'TOOLS\n------\nAssistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:\n\n\n\nRESPONSE FORMAT INSTRUCTIONS\n----------------------------\n\nOutput a JSON markdown code snippet containing a valid JSON object in one of two formats:\n\n**Option 1:**\nUse this if you want the human to use a tool.\nMarkdown code snippet formatted in the following schema:\n\n```json\n{\n "action": string, // The action to take. Must be one of []\n "action_input": string // The input to the action. May be a stringified object.\n}\n```\n\n**Option #2:**\nUse this if you want to respond directly and conversationally to the human. Markdown code snippet formatted in the following schema:\n\n```json\n{\n "action": "Final Answer",\n "action_input": string // You should put what you want to return to use here and make sure to use valid json newline characters.\n}\n```\n\nFor both options, remember to always include the surrounding markdown code snippet delimiters (begin with "```json" and end with "```")!\n\n\nUSER\'S INPUT\n--------------------\nHere is the user\'s input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):\n\nHello!', - additional_kwargs: {}, - }, - }, - ], - options: { stop: ['Observation:'], promptIndex: 0 }, - }, - }, - ], - ], - }, - }), - createMockNodeExecutionData(AGENT_NODE_NAME, { - jsonData: { - main: { output: 'Hi there! How can I assist you today?' }, - }, - }), - ]; - - runMockWorkflowExecution({ - trigger: () => { - sendManualChatMessage(inputMessage); - }, - runData, - lastNodeExecuted: AGENT_NODE_NAME, - }); - - const messages = getManualChatMessages(); - messages.should('have.length', 2); - messages.should('contain', inputMessage); - messages.should('contain', outputMessage); - - logs.getOverviewPanel().should('be.visible'); - logs.getLogEntries().should('have.length', 2); - logs.getLogEntries().eq(0).should('have.text', 'AI Agent'); - logs.getLogEntries().eq(1).should('have.text', 'OpenAI Chat Model'); - - closeManualChatModal(); - logs.getOverviewPanelBody().should('not.exist'); - getManualChatInput().should('not.exist'); - }); - - it('should auto-add chat trigger and basic LLM chain when adding LLM node', () => { - addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); - - getConnectionBySourceAndTarget( - CHAT_TRIGGER_NODE_DISPLAY_NAME, - BASIC_LLM_CHAIN_NODE_NAME, - ).should('exist'); - - getConnectionBySourceAndTarget( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - BASIC_LLM_CHAIN_NODE_NAME, - ).should('exist'); - getNodes().should('have.length', 3); - }); - - it('should not auto-add nodes if AI nodes are already present', () => { - addNodeToCanvas(AGENT_NODE_NAME, true); - - addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); - getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist'); - getNodes().should('have.length', 3); - }); - it('should not auto-add nodes if ChatTrigger is already present', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(AGENT_NODE_NAME, true); - - addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); - getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist'); - getNodes().should('have.length', 3); - }); - - it('should render runItems for sub-nodes and allow switching between them', () => { - const workflowPage = new WorkflowPage(); - - cy.visit(workflowPage.url); - cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json'); - workflowPage.actions.zoomToFit(); - workflowPage.actions.deselectAll(); - - workflowPage.actions.executeNode('Populate VS'); - workflow.waitForSuccessBannerToAppear(); - - const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => { - ndv.getOutputPanel().contains(text).should(assertion); - ndv.getOutputPanel().contains(text).should(assertion); - }; - - workflowPage.actions.openNode('Character Text Splitter'); - - 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('Berlin', 'not.exist'); - assertInputOutputText('Prague', 'not.exist'); - - ndv.changeOutputRunSelector('2 of 3'); - assertInputOutputText('Berlin', 'exist'); - assertInputOutputText('Kyiv', 'not.exist'); - assertInputOutputText('Prague', 'not.exist'); - - ndv.changeOutputRunSelector('1 of 3'); - assertInputOutputText('Prague', 'exist'); - assertInputOutputText('Berlin', 'not.exist'); - assertInputOutputText('Kyiv', 'not.exist'); - - ndv.toggleInputRunLinking(); - ndv.changeOutputRunSelector('2 of 3'); - ndv.getInputRunSelector().find('input').should('include.value', '1 of 3'); - ndv.getOutputRunSelector().find('input').should('include.value', '2 of 3'); - ndv.getInputPanel().contains('Prague').should('exist'); - ndv.getInputPanel().contains('Berlin').should('not.exist'); - - ndv.getOutputPanel().contains('Berlin').should('exist'); - ndv.getOutputPanel().contains('Prague').should('not.exist'); - - ndv.toggleInputRunLinking(); - ndv.getInputRunSelector().find('input').should('include.value', '1 of 3'); - ndv.getOutputRunSelector().find('input').should('include.value', '1 of 3'); - assertInputOutputText('Prague', 'exist'); - assertInputOutputText('Berlin', 'not.exist'); - assertInputOutputText('Kyiv', 'not.exist'); - }); - - it('should show tool info notice if no existing tools were used during execution', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(AGENT_NODE_NAME, true); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - openNode(AGENT_NODE_NAME); - - const inputMessage = 'Hello!'; - const outputMessage = 'Hi there! How can I assist you today?'; - - clickExecuteNode(); - - runMockWorkflowExecution({ - trigger: () => sendManualChatMessage(inputMessage), - runData: [ - createMockNodeExecutionData(AGENT_NODE_NAME, { - jsonData: { - main: { output: outputMessage }, - }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, - }), - ], - lastNodeExecuted: AGENT_NODE_NAME, - }); - closeManualChatModal(); - openNode(AGENT_NODE_NAME); - - getRunDataInfoCallout().should('exist'); - }); - - it('should not show tool info notice if tools were used during execution', () => { - addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true); - addNodeToCanvas(AGENT_NODE_NAME, true, true); - getRunDataInfoCallout().should('not.exist'); - clickGetBackToCanvas(); - - addLanguageModelNodeToParent( - AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, - AGENT_NODE_NAME, - true, - ); - - clickCreateNewCredential(); - setCredentialValues({ - apiKey: 'sk_test_123', - }); - clickGetBackToCanvas(); - - addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); - clickGetBackToCanvas(); - openNode(AGENT_NODE_NAME); - - getRunDataInfoCallout().should('not.exist'); - - const inputMessage = 'Hello!'; - const outputMessage = 'Hi there! How can I assist you today?'; - - clickExecuteNode(); - - runMockWorkflowExecution({ - trigger: () => sendManualChatMessage(inputMessage), - runData: [ - createMockNodeExecutionData(AGENT_NODE_NAME, { - jsonData: { - main: { output: outputMessage }, - }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, - }), - createMockNodeExecutionData(AI_TOOL_CALCULATOR_NODE_NAME, {}), - ], - lastNodeExecuted: AGENT_NODE_NAME, - }); - - closeManualChatModal(); - openNode(AGENT_NODE_NAME); - // This waits to ensure the output panel is rendered - getOutputPanelTable(); - - getRunDataInfoCallout().should('not.exist'); - }); - - it('should execute up to Node 1 when using partial execution', () => { - const workflowPage = new WorkflowPage(); - - cy.visit(workflowPage.url); - cy.createFixtureWorkflow('Test_workflow_chat_partial_execution.json'); - workflowPage.actions.zoomToFit(); - - getManualChatModal().find('main').should('not.exist'); - openNode('Node 1'); - ndv.clickExecuteNode(); - - getManualChatModal().find('main').should('exist'); - sendManualChatMessage('Test'); - - getManualChatMessages().should('contain', 'this_my_field_1'); - cy.getByTestId('refresh-session-button').click(); - getManualChatMessages().should('not.exist'); - - sendManualChatMessage('Another test'); - getManualChatMessages().should('contain', 'this_my_field_3'); - getManualChatMessages().should('contain', 'this_my_field_4'); - }); -}); diff --git a/cypress/fixtures/In_memory_vector_store_fake_embeddings.json b/cypress/fixtures/In_memory_vector_store_fake_embeddings.json deleted file mode 100644 index 6654653cc1..0000000000 --- a/cypress/fixtures/In_memory_vector_store_fake_embeddings.json +++ /dev/null @@ -1,347 +0,0 @@ -{ - "name": "fake_embeddings", - "nodes": [ - { - "parameters": {}, - "id": "de3c1210-3be7-49a6-86ef-9435e661f23f", - "name": "When clicking ‘Execute workflow’", - "type": "n8n-nodes-base.manualTrigger", - "typeVersion": 1, - "position": [ - 480, - 760 - ] - }, - { - "parameters": { - "jsonMode": "expressionData", - "jsonData": "={{ $('Code').item.json.city }}", - "options": {} - }, - "id": "de3cb132-14ef-426b-ad33-8365a93dd11f", - "name": "Default Data Loader", - "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", - "typeVersion": 1, - "position": [ - 1100, - 900 - ] - }, - { - "parameters": { - "jsCode": "const kyiv = `Kyiv (also Kiev)[a] is the capital and most populous city of Ukraine. It is in north-central Ukraine along the Dnieper River. As of 1 January 2022, its population was 2,952,301,[2] making Kyiv the seventh-most populous city in Europe.[11] Kyiv is an important industrial, scientific, educational, and cultural center in Eastern Europe. It is home to many high-tech industries, higher education institutions, and historical landmarks. The city has an extensive system of public transport and infrastructure, including the Kyiv Metro.\n\nThe city's name is said to derive from the name of Kyi, one of its four legendary founders. During its history, Kyiv, one of the oldest cities in Eastern Europe, passed through several stages of prominence and obscurity. The city probably existed as a commercial center as early as the 5th century. A Slavic settlement on the great trade route between Scandinavia and Constantinople, Kyiv was a tributary of the Khazars,[12] until its capture by the Varangians (Vikings) in the mid-9th century. Under Varangian rule, the city became a capital of Kievan Rus', the first East Slavic state. Completely destroyed during the Mongol invasions in 1240, the city lost most of its influence for the centuries to come. Coming under Lithuania, then Poland and then Russia, the city would grow from a frontier market into an important centre of Orthodox learning in the sixteenth century, and later of industry, commerce, and administration by the nineteenth.[1]\n\nThe city prospered again during the Russian Empire's Industrial Revolution in the late 19th century. In 1918, when the Ukrainian People's Republic declared independence from the Russian Republic after the October Revolution there, Kyiv became its capital. From the end of the Ukrainian-Soviet and Polish-Soviet wars in 1921, Kyiv was a city of the Ukrainian SSR, and made its capital in 1934. The city suffered significant destruction during World War II but quickly recovered in the postwar years, remaining the Soviet Union's third-largest city.\n\nFollowing the collapse of the Soviet Union and Ukrainian independence in 1991, Kyiv remained Ukraine's capital and experienced a steady influx of ethnic Ukrainian migrants from other regions of the country.[13] During the country's transformation to a market economy and electoral democracy, Kyiv has continued to be Ukraine's largest and wealthiest city. Its armament-dependent industrial output fell after the Soviet collapse, adversely affecting science and technology, but new sectors of the economy such as services and finance facilitated Kyiv's growth in salaries and investment, as well as providing continuous funding for the development of housing and urban infrastructure. Kyiv emerged as the most pro-Western region of Ukraine; parties advocating tighter integration with the European Union dominate during elections.`\n\nconst berlin = `Berlin[a] is the capital and largest city of Germany, both by area and by population.[11] Its more than 3.85 million inhabitants[12] make it the European Union's most populous city, as measured by population within city limits.[13] The city is also one of the states of Germany, and is the third smallest state in the country in terms of area. Berlin is surrounded by the state of Brandenburg, and Brandenburg's capital Potsdam is nearby. The urban area of Berlin has a population of over 4.5 million and is therefore the most populous urban area in Germany.[5][14] The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany's second-largest metropolitan region after the Rhine-Ruhr region, and the sixth-biggest metropolitan region by GDP in the European Union.[15]\n\nBerlin was built along the banks of the Spree river, which flows into the Havel in the western borough of Spandau. The city incorporates lakes in the western and southeastern boroughs, the largest of which is Müggelsee. About one-third of the city's area is composed of forests, parks and gardens, rivers, canals, and lakes.[16]\n\nFirst documented in the 13th century[10] and at the crossing of two important historic trade routes,[17] Berlin was designated the capital of the Margraviate of Brandenburg (1417–1701), Kingdom of Prussia (1701–1918), German Empire (1871–1918), Weimar Republic (1919–1933), and Nazi Germany (1933–1945). Berlin has served as a scientific, artistic, and philosophical hub during the Age of Enlightenment, Neoclassicism, and the German revolutions of 1848–1849. During the Gründerzeit, an industrialization-induced economic boom triggered a rapid population increase in Berlin. 1920s Berlin was the third-largest city in the world by population.[18]\n\nAfter World War II and following Berlin's occupation, the city was split into West Berlin and East Berlin, divided by the Berlin Wall.[19] East Berlin was declared the capital of East Germany, while Bonn became the West German capital. Following German reunification in 1990, Berlin once again became the capital of all of Germany. Due to its geographic location and history, Berlin has been called \"the heart of Europe\".[20][21][22]`\n\nconst prague = `Prague (/ˈprɑːɡ/ PRAHG; Czech: Praha [ˈpraɦa] ⓘ)[a] is the capital and largest city of the Czech Republic[9] and the historical capital of Bohemia. Situated on the Vltava river, Prague is home to about 1.4 million people.\n\nPrague is a political, cultural, and economic hub of Central Europe, with a rich history and Romanesque, Gothic, Renaissance and Baroque architectures. It was the capital of the Kingdom of Bohemia and residence of several Holy Roman Emperors, most notably Charles IV (r. 1346–1378) and Rudolf II (r. 1575–1611).[9] It was an important city to the Habsburg monarchy and Austria-Hungary. The city played major roles in the Bohemian and the Protestant Reformations, the Thirty Years' War and in 20th-century history as the capital of Czechoslovakia between the World Wars and the post-war Communist era.[10]\n\nPrague is home to a number of cultural attractions including Prague Castle, Charles Bridge, Old Town Square with the Prague astronomical clock, the Jewish Quarter, Petřín hill and Vyšehrad. Since 1992, the historic center of Prague has been included in the UNESCO list of World Heritage Sites.\n\nThe city has more than ten major museums, along with numerous theatres, galleries, cinemas, and other historical exhibits. An extensive modern public transportation system connects the city. It is home to a wide range of public and private schools, including Charles University in Prague, the oldest university in Central Europe.\n\nPrague is classified as an \"Alpha-\" global city according to GaWC studies.[11] In 2019, the city was ranked as 69th most livable city in the world by Mercer.[12] In the same year, the PICSA Index ranked the city as 13th most livable city in the world.[13] Its rich history makes it a popular tourist destination and as of 2017, the city receives more than 8.5 million international visitors annually. In 2017, Prague was listed as the fifth most visited European city after London, Paris, Rome, and Istanbul.[14]`\n\nreturn [prague, berlin, kyiv].map(i => ({ city: i}))" - }, - "id": "ce9d517e-2dd9-45e4-a566-79bd79cd809b", - "name": "Code", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 740, - 760 - ] - }, - { - "parameters": { - "chunkSize": 300 - }, - "id": "ebe5f3a5-4d90-4a33-bf48-f160f0e83967", - "name": "Character Text Splitter", - "type": "@n8n/n8n-nodes-langchain.textSplitterCharacterTextSplitter", - "typeVersion": 1, - "position": [ - 1100, - 1060 - ] - }, - { - "parameters": { - "code": { - "supplyData": { - "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" - } - }, - "outputs": { - "output": [ - { - "type": "ai_embedding" - } - ] - } - }, - "id": "0eac6c5b-89a9-48a4-bd21-19f2b20c3424", - "name": "Fake Embeddings 3", - "type": "@n8n/n8n-nodes-langchain.code", - "typeVersion": 1, - "position": [ - 660, - 1220 - ] - }, - { - "parameters": { - "mode": "load", - "prompt": "Tester", - "topK": 3 - }, - "id": "8c9b39bf-59d6-4769-98e1-54988d9d6b53", - "name": "Get All VS", - "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", - "typeVersion": 1, - "position": [ - 680, - 1080 - ] - }, - { - "parameters": { - "code": { - "supplyData": { - "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" - } - }, - "outputs": { - "output": [ - { - "type": "ai_embedding" - } - ] - } - }, - "id": "e46004ec-baf6-425c-9897-3faec9e29676", - "name": "Fake Embeddings", - "type": "@n8n/n8n-nodes-langchain.code", - "typeVersion": 1, - "position": [ - 920, - 900 - ] - }, - { - "parameters": { - "promptType": "define", - "text": "Just testing", - "options": {} - }, - "id": "b132b323-a813-469c-859b-f1b3ede743a3", - "name": "Question and Answer Chain", - "type": "@n8n/n8n-nodes-langchain.chainRetrievalQa", - "typeVersion": 1.3, - "position": [ - 1680, - 780 - ] - }, - { - "parameters": {}, - "id": "b9c412e5-d739-4c82-9a2e-6c0af0cae8f9", - "name": "Vector Store Retriever", - "type": "@n8n/n8n-nodes-langchain.retrieverVectorStore", - "typeVersion": 1, - "position": [ - 1760, - 920 - ] - }, - { - "parameters": { - "code": { - "supplyData": { - "code": "const { FakeChatModel } = require('@langchain/core/utils/testing');\n\nreturn new FakeChatModel({});" - } - }, - "outputs": { - "output": [ - { - "type": "ai_languageModel" - } - ] - } - }, - "id": "962b4b87-ffd6-4ab8-8776-6e9c0920930a", - "name": "Fake Language Model", - "type": "@n8n/n8n-nodes-langchain.code", - "typeVersion": 1, - "position": [ - 1620, - 920 - ] - }, - { - "parameters": { - "code": { - "supplyData": { - "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" - } - }, - "outputs": { - "output": [ - { - "type": "ai_embedding" - } - ] - } - }, - "id": "c78be34f-6459-4414-86bd-f2670ece129d", - "name": "Fake Embeddings 2", - "type": "@n8n/n8n-nodes-langchain.code", - "typeVersion": 1, - "position": [ - 1700, - 1200 - ] - }, - { - "parameters": {}, - "id": "3cee9727-6b97-477c-8277-e8883a98786d", - "name": "Retriever VS", - "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", - "typeVersion": 1, - "position": [ - 1700, - 1060 - ] - }, - { - "parameters": { - "mode": "insert" - }, - "id": "5793ec6b-ac00-4a5d-a79c-ff557143e46b", - "name": "Populate VS", - "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", - "typeVersion": 1, - "position": [ - 980, - 760 - ] - } - ], - "pinData": {}, - "connections": { - "When clicking ‘Execute workflow’": { - "main": [ - [ - { - "node": "Code", - "type": "main", - "index": 0 - }, - { - "node": "Get All VS", - "type": "main", - "index": 0 - } - ] - ] - }, - "Default Data Loader": { - "ai_document": [ - [ - { - "node": "Populate VS", - "type": "ai_document", - "index": 0 - } - ] - ] - }, - "Code": { - "main": [ - [ - { - "node": "Populate VS", - "type": "main", - "index": 0 - } - ] - ] - }, - "Character Text Splitter": { - "ai_textSplitter": [ - [ - { - "node": "Default Data Loader", - "type": "ai_textSplitter", - "index": 0 - } - ] - ] - }, - "Fake Embeddings 3": { - "ai_embedding": [ - [ - { - "node": "Get All VS", - "type": "ai_embedding", - "index": 0 - } - ] - ] - }, - "Fake Embeddings": { - "ai_embedding": [ - [ - { - "node": "Populate VS", - "type": "ai_embedding", - "index": 0 - } - ] - ] - }, - "Vector Store Retriever": { - "ai_retriever": [ - [ - { - "node": "Question and Answer Chain", - "type": "ai_retriever", - "index": 0 - } - ] - ] - }, - "Fake Language Model": { - "ai_languageModel": [ - [ - { - "node": "Question and Answer Chain", - "type": "ai_languageModel", - "index": 0 - } - ] - ] - }, - "Fake Embeddings 2": { - "ai_embedding": [ - [ - { - "node": "Retriever VS", - "type": "ai_embedding", - "index": 0 - } - ] - ] - }, - "Retriever VS": { - "ai_vectorStore": [ - [ - { - "node": "Vector Store Retriever", - "type": "ai_vectorStore", - "index": 0 - } - ] - ] - } - }, - "active": false, - "settings": { - "executionOrder": "v1" - }, - "versionId": "4ad44cc6-d5f7-48af-8455-c3957baba04c", - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" - }, - "id": "ZjxsuN0rMHRVCb2c", - "tags": [] -} diff --git a/cypress/fixtures/Test_workflow_chat_partial_execution.json b/cypress/fixtures/Test_workflow_chat_partial_execution.json deleted file mode 100644 index 451ddcf964..0000000000 --- a/cypress/fixtures/Test_workflow_chat_partial_execution.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "nodes": [ - { - "parameters": { - "options": {} - }, - "id": "535fd3dd-e78f-4ffa-a085-79723fc81b38", - "name": "When chat message received", - "type": "@n8n/n8n-nodes-langchain.chatTrigger", - "typeVersion": 1.1, - "position": [ - 320, - -380 - ], - "webhookId": "4fb58136-3481-494a-a30f-d9e064dac186" - }, - { - "parameters": { - "mode": "raw", - "jsonOutput": "{\n \"this_my_field_1\": \"value\",\n \"this_my_field_2\": 1\n}\n", - "options": {} - }, - "id": "78201ec2-6def-40b7-85e5-97b580d7f642", - "name": "Node 1", - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - 580, - -380 - ] - }, - { - "parameters": { - "mode": "raw", - "jsonOutput": "{\n \"this_my_field_3\": \"value\",\n \"this_my_field_4\": 1\n}\n", - "options": {} - }, - "id": "1cfca06d-3ec3-427f-89f7-1ef321e025ff", - "name": "Node 2", - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - 780, - -380 - ] - } - ], - "connections": { - "When chat message received": { - "main": [ - [ - { - "node": "Node 1", - "type": "main", - "index": 0 - } - ] - ] - }, - "Node 1": { - "main": [ - [ - { - "node": "Node 2", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "pinData": {}, - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "178ef8a5109fc76c716d40bcadb720c455319f7b7a3fd5a39e4f336a091f524a" - } -} diff --git a/packages/testing/playwright/expectations/langchain/1757337994261-unknown-host-POST-_v1_chat_completions-1561df08.json b/packages/testing/playwright/expectations/langchain/1757337994261-unknown-host-POST-_v1_chat_completions-1561df08.json new file mode 100644 index 0000000000..2ea0b4d15a --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757337994261-unknown-host-POST-_v1_chat_completions-1561df08.json @@ -0,0 +1,80 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_b8f45478a83a4dd2b6d3d6c0df25b482"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999997"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWB6cgun4QRcijeXQYPQWTaqtLmN", + "object": "chat.completion", + "created": 1757337992, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I assist you today?", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 9, + "total_tokens": 18, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_6d7dcc9a98" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCNmNndW40UVJjaWplWFFZUFFXVGFxdExtTiIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzZkN2RjYzlhOTgiCn0K" + } + }, + "id": "1757337994261-unknown-host-POST-_v1_chat_completions-1561df08.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/expectations/langchain/1757337994532-unknown-host-POST-_v1_chat_completions-1561df08.json b/packages/testing/playwright/expectations/langchain/1757337994532-unknown-host-POST-_v1_chat_completions-1561df08.json new file mode 100644 index 0000000000..b55b601393 --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757337994532-unknown-host-POST-_v1_chat_completions-1561df08.json @@ -0,0 +1,82 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_11f2fb25bd0b4a758224bba6f1e68e50"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999995"], + "x-ratelimit-remaining-requests": ["29999"], + "x-ratelimit-limit-tokens": ["150000000"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWB7f4flZjMHmcuet4JNe5pRTfVM", + "object": "chat.completion", + "created": 1757337993, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I assist you today?", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 9, + "total_tokens": 18, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_4fce0778af" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCN2Y0Zmxaak1IbWN1ZXQ0Sk5lNXBSVGZWTSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MywKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K" + } + }, + "id": "1757337994532-unknown-host-POST-_v1_chat_completions-1561df08.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/expectations/langchain/1757337995023-unknown-host-POST-_v1_chat_completions-1561df08.json b/packages/testing/playwright/expectations/langchain/1757337995023-unknown-host-POST-_v1_chat_completions-1561df08.json new file mode 100644 index 0000000000..ce6289b151 --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757337995023-unknown-host-POST-_v1_chat_completions-1561df08.json @@ -0,0 +1,82 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_2476c446b9a74442be397d438277a7c1"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999995"], + "x-ratelimit-remaining-requests": ["29999"], + "x-ratelimit-limit-tokens": ["150000000"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWB6wRC1FBoWeZr36clt56dB1EzT", + "object": "chat.completion", + "created": 1757337992, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I assist you today?", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 9, + "total_tokens": 18, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_4fce0778af" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCNndSQzFGQm9XZVpyMzZjbHQ1NmRCMUV6VCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K" + } + }, + "id": "1757337995023-unknown-host-POST-_v1_chat_completions-1561df08.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/expectations/langchain/1757338000172-unknown-host-POST-_v1_chat_completions-fdac7829.json b/packages/testing/playwright/expectations/langchain/1757338000172-unknown-host-POST-_v1_chat_completions-fdac7829.json new file mode 100644 index 0000000000..9b3eba67fd --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757338000172-unknown-host-POST-_v1_chat_completions-fdac7829.json @@ -0,0 +1,101 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "tools": [ + { + "type": "function", + "function": { + "name": "calculator", + "description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.", + "parameters": { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + } + ], + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_b9dd655cc0ac465980074d2301a4f058"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999995"], + "x-ratelimit-remaining-requests": ["29999"], + "x-ratelimit-limit-tokens": ["150000000"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWBAps4xOl2Ps9V9GQ7RwmRWYGnU", + "object": "chat.completion", + "created": 1757337996, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I assist you today?", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 68, + "completion_tokens": 10, + "total_tokens": 78, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_4fce0778af" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCQXBzNHhPbDJQczlWOUdRN1J3bVJXWUduVSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5NiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA2OCwKICAgICJjb21wbGV0aW9uX3Rva2VucyI6IDEwLAogICAgInRvdGFsX3Rva2VucyI6IDc4LAogICAgInByb21wdF90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgImNhY2hlZF90b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMAogICAgfSwKICAgICJjb21wbGV0aW9uX3Rva2Vuc19kZXRhaWxzIjogewogICAgICAicmVhc29uaW5nX3Rva2VucyI6IDAsCiAgICAgICJhdWRpb190b2tlbnMiOiAwLAogICAgICAiYWNjZXB0ZWRfcHJlZGljdGlvbl90b2tlbnMiOiAwLAogICAgICAicmVqZWN0ZWRfcHJlZGljdGlvbl90b2tlbnMiOiAwCiAgICB9CiAgfSwKICAic2VydmljZV90aWVyIjogImRlZmF1bHQiLAogICJzeXN0ZW1fZmluZ2VycHJpbnQiOiAiZnBfNGZjZTA3NzhhZiIKfQo=" + } + }, + "id": "1757338000172-unknown-host-POST-_v1_chat_completions-fdac7829.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/expectations/langchain/1757338002750-unknown-host-POST-_v1_chat_completions-9d7fafef.json b/packages/testing/playwright/expectations/langchain/1757338002750-unknown-host-POST-_v1_chat_completions-9d7fafef.json new file mode 100644 index 0000000000..8c021324a3 --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757338002750-unknown-host-POST-_v1_chat_completions-9d7fafef.json @@ -0,0 +1,111 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "tools": [ + { + "type": "function", + "function": { + "name": "calculator", + "description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.", + "parameters": { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + } + ], + "messages": [ + { + "role": "user", + "content": "What is 1000 * 10?" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJXaGF0IGlzIDEwMDAgKiAxMD8ifV19" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_56023c245be3448c8373ff3aad21f7c4"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999992"], + "x-ratelimit-remaining-requests": ["29999"], + "x-ratelimit-limit-tokens": ["150000000"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWBDR6zMgzDe9t4hkJ0Xq3dxsK3y", + "object": "chat.completion", + "created": 1757337999, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_88I326c3cCx7lOEXL3Wpp30c", + "type": "function", + "function": { + "name": "calculator", + "arguments": "{\"input\":\"1000 * 10\"}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 75, + "completion_tokens": 18, + "total_tokens": 93, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_4fce0778af" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCRFI2ek1nekRlOXQ0aGtKMFhxM2R4c0szeSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5OSwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogbnVsbCwKICAgICAgICAidG9vbF9jYWxscyI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImlkIjogImNhbGxfODhJMzI2YzNjQ3g3bE9FWEwzV3BwMzBjIiwKICAgICAgICAgICAgInR5cGUiOiAiZnVuY3Rpb24iLAogICAgICAgICAgICAiZnVuY3Rpb24iOiB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsY3VsYXRvciIsCiAgICAgICAgICAgICAgImFyZ3VtZW50cyI6ICJ7XCJpbnB1dFwiOlwiMTAwMCAqIDEwXCJ9IgogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAicmVmdXNhbCI6IG51bGwsCiAgICAgICAgImFubm90YXRpb25zIjogW10KICAgICAgfSwKICAgICAgImxvZ3Byb2JzIjogbnVsbCwKICAgICAgImZpbmlzaF9yZWFzb24iOiAidG9vbF9jYWxscyIKICAgIH0KICBdLAogICJ1c2FnZSI6IHsKICAgICJwcm9tcHRfdG9rZW5zIjogNzUsCiAgICAiY29tcGxldGlvbl90b2tlbnMiOiAxOCwKICAgICJ0b3RhbF90b2tlbnMiOiA5MywKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K" + } + }, + "id": "1757338002750-unknown-host-POST-_v1_chat_completions-9d7fafef.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/expectations/langchain/1757338002751-unknown-host-POST-_v1_chat_completions-e8887e9d.json b/packages/testing/playwright/expectations/langchain/1757338002751-unknown-host-POST-_v1_chat_completions-e8887e9d.json new file mode 100644 index 0000000000..1f45d0d307 --- /dev/null +++ b/packages/testing/playwright/expectations/langchain/1757338002751-unknown-host-POST-_v1_chat_completions-e8887e9d.json @@ -0,0 +1,120 @@ +{ + "httpRequest": { + "method": "POST", + "path": "/v1/chat/completions", + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "model": "gpt-4.1-mini", + "stream": false, + "tools": [ + { + "type": "function", + "function": { + "name": "calculator", + "description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.", + "parameters": { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + } + ], + "messages": [ + { + "role": "user", + "content": "What is 1000 * 10?" + }, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_88I326c3cCx7lOEXL3Wpp30c", + "type": "function", + "function": { + "name": "calculator", + "arguments": "{\"input\":\"1000 * 10\"}" + } + } + ] + }, + { + "role": "tool", + "content": "10000", + "tool_call_id": "call_88I326c3cCx7lOEXL3Wpp30c" + } + ] + }, + "rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJXaGF0IGlzIDEwMDAgKiAxMD8ifSx7InJvbGUiOiJhc3Npc3RhbnQiLCJjb250ZW50IjoiIiwidG9vbF9jYWxscyI6W3siaWQiOiJjYWxsXzg4STMyNmMzY0N4N2xPRVhMM1dwcDMwYyIsInR5cGUiOiJmdW5jdGlvbiIsImZ1bmN0aW9uIjp7Im5hbWUiOiJjYWxjdWxhdG9yIiwiYXJndW1lbnRzIjoie1wiaW5wdXRcIjpcIjEwMDAgKiAxMFwifSJ9fV19LHsicm9sZSI6InRvb2wiLCJjb250ZW50IjoiMTAwMDAiLCJ0b29sX2NhbGxfaWQiOiJjYWxsXzg4STMyNmMzY0N4N2xPRVhMM1dwcDMwYyJ9XX0=" + } + }, + "httpResponse": { + "statusCode": 200, + "reasonPhrase": "OK", + "headers": { + "x-request-id": ["req_65b7f6b5aad740e0965555ae8df92d43"], + "x-ratelimit-reset-tokens": ["0s"], + "x-ratelimit-reset-requests": ["2ms"], + "x-ratelimit-remaining-tokens": ["149999992"], + "x-ratelimit-remaining-requests": ["29999"], + "x-ratelimit-limit-tokens": ["150000000"] + }, + "body": { + "contentType": "application/json", + "type": "JSON", + "json": { + "id": "chatcmpl-CDWBEPUWN3El2oq4aRaezkvUlRjv8", + "object": "chat.completion", + "created": 1757338000, + "model": "gpt-4.1-mini-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "1000 multiplied by 10 equals 10,000.", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 102, + "completion_tokens": 13, + "total_tokens": 115, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_4fce0778af" + }, + "rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCRVBVV04zRWwyb3E0YVJhZXprdlVsUmp2OCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzODAwMCwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIjEwMDAgbXVsdGlwbGllZCBieSAxMCBlcXVhbHMgMTAsMDAwLiIsCiAgICAgICAgInJlZnVzYWwiOiBudWxsLAogICAgICAgICJhbm5vdGF0aW9ucyI6IFtdCiAgICAgIH0sCiAgICAgICJsb2dwcm9icyI6IG51bGwsCiAgICAgICJmaW5pc2hfcmVhc29uIjogInN0b3AiCiAgICB9CiAgXSwKICAidXNhZ2UiOiB7CiAgICAicHJvbXB0X3Rva2VucyI6IDEwMiwKICAgICJjb21wbGV0aW9uX3Rva2VucyI6IDEzLAogICAgInRvdGFsX3Rva2VucyI6IDExNSwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K" + } + }, + "id": "1757338002751-unknown-host-POST-_v1_chat_completions-e8887e9d.json", + "priority": 0, + "timeToLive": { + "unlimited": true + }, + "times": { + "unlimited": true + } +} diff --git a/packages/testing/playwright/package.json b/packages/testing/playwright/package.json index d0bd994a48..d36be8f0be 100644 --- a/packages/testing/playwright/package.json +++ b/packages/testing/playwright/package.json @@ -34,6 +34,7 @@ "n8n-containers": "workspace:*", "n8n-core": "workspace:*", "n8n-workflow": "workspace:*", + "flatted": "catalog:", "nanoid": "catalog:", "tsx": "catalog:", "mockserver-client": "^5.15.0", diff --git a/packages/testing/playwright/pages/CanvasPage.ts b/packages/testing/playwright/pages/CanvasPage.ts index 6a48b8cb13..ef3299b8b8 100644 --- a/packages/testing/playwright/pages/CanvasPage.ts +++ b/packages/testing/playwright/pages/CanvasPage.ts @@ -541,6 +541,28 @@ export class CanvasPage extends BasePage { await this.clickContextMenuAction('execute'); } + async clearExecutionData(): Promise { + await this.page.getByTestId('clear-execution-data-button').click(); + } + + getManualChatModal(): Locator { + return this.page.getByTestId('canvas-chat'); + } + + getManualChatInput(): Locator { + return this.getManualChatModal().locator('.chat-inputs textarea'); + } + + getManualChatMessages(): Locator { + return this.getManualChatModal().locator('.chat-messages-list .chat-message'); + } + + getManualChatLatestBotMessage(): Locator { + return this.getManualChatModal() + .locator('.chat-messages-list .chat-message.chat-message-from-bot') + .last(); + } + getNodesWithSpinner(): Locator { return this.page.getByTestId('canvas-node').filter({ has: this.page.locator('[data-icon=refresh-cw]'), @@ -560,6 +582,67 @@ export class CanvasPage extends BasePage { return this.page.locator('[data-test-id="canvas-node"].selected'); } + // Disable node via context menu + async disableNodeFromContextMenu(nodeName: string): Promise { + await this.rightClickNode(nodeName); + await this.page + .getByTestId('context-menu') + .getByTestId('context-menu-item-toggle_activation') + .click(); + } + + // Chat open/close buttons (manual chat) + async clickManualChatButton(): Promise { + await this.page.getByTestId('workflow-chat-button').click(); + await this.getManualChatModal().waitFor({ state: 'visible' }); + } + + async closeManualChatModal(): Promise { + // Same toggle button closes the chat + await this.page.getByTestId('workflow-chat-button').click(); + } + + // Input plus endpoints (to add supplemental nodes to parent inputs) + getInputPlusEndpointByType(nodeName: string, endpointType: string) { + return this.page + .locator( + `[data-test-id="canvas-node-input-handle"][data-connection-type="${endpointType}"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`, + ) + .first(); + } + + // Generic supplemental node addition, then wrappers for specific types + async addSupplementalNodeToParent( + childNodeName: string, + endpointType: + | 'main' + | 'ai_chain' + | 'ai_document' + | 'ai_embedding' + | 'ai_languageModel' + | 'ai_memory' + | 'ai_outputParser' + | 'ai_tool' + | 'ai_retriever' + | 'ai_textSplitter' + | 'ai_vectorRetriever' + | 'ai_vectorStore', + parentNodeName: string, + { closeNDV = false, exactMatch = false }: { closeNDV?: boolean; exactMatch?: boolean } = {}, + ): Promise { + await this.getInputPlusEndpointByType(parentNodeName, endpointType).click(); + + if (exactMatch) { + await this.nodeCreatorNodeItems().getByText(childNodeName, { exact: true }).click(); + } else { + await this.nodeCreatorNodeItems().filter({ hasText: childNodeName }).first().click(); + } + + if (closeNDV) { + await this.page.keyboard.press('Escape'); + } + } + async openExecutions() { await this.page.getByTestId('radio-button-executions').click(); } diff --git a/packages/testing/playwright/pages/CredentialsEditModal.ts b/packages/testing/playwright/pages/CredentialsEditModal.ts new file mode 100644 index 0000000000..e0423552ac --- /dev/null +++ b/packages/testing/playwright/pages/CredentialsEditModal.ts @@ -0,0 +1,67 @@ +import type { Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class CredentialsEditModal extends BasePage { + constructor(page: Page) { + super(page); + } + + getModal(): Locator { + return this.page.getByTestId('editCredential-modal'); + } + + async waitForModal(): Promise { + await this.getModal().waitFor({ state: 'visible' }); + } + + async fillField(key: string, value: string): Promise { + const input = this.page.getByTestId(`parameter-input-${key}`).locator('input, textarea'); + await input.fill(value); + await expect(input).toHaveValue(value); + } + + async fillAllFields(values: Record): Promise { + for (const [key, val] of Object.entries(values)) { + await this.fillField(key, val); + } + } + + getSaveButton(): Locator { + return this.page.getByTestId('credential-save-button'); + } + + async save(): Promise { + const saveBtn = this.getSaveButton(); + await saveBtn.click(); + await saveBtn.waitFor({ state: 'visible' }); + + // Saved state changes the button text to "Saved" + // Defensive wait for text when UI updates + try { + await saveBtn + .getByText('Saved', { exact: true }) + .waitFor({ state: 'visible', timeout: 3000 }); + } catch { + // ignore if text assertion is flaky; modal close below will still ensure flow continues + } + } + + async close(): Promise { + const closeBtn = this.getModal().locator('.el-dialog__close').first(); + if (await closeBtn.isVisible()) { + await closeBtn.click(); + } + } + + async setValues(values: Record, save: boolean = true): Promise { + await this.waitForModal(); + await this.fillAllFields(values); + + if (save) { + await this.save(); + await this.close(); + } + } +} diff --git a/packages/testing/playwright/pages/NodeDetailsViewPage.ts b/packages/testing/playwright/pages/NodeDetailsViewPage.ts index 19f88f4924..725dfc7d1a 100644 --- a/packages/testing/playwright/pages/NodeDetailsViewPage.ts +++ b/packages/testing/playwright/pages/NodeDetailsViewPage.ts @@ -52,10 +52,18 @@ export class NodeDetailsViewPage extends BasePage { await this.clickByTestId('node-execute-button'); } + getOutputPanel() { + return this.page.getByTestId('output-panel'); + } + getContainer() { return this.page.getByTestId('ndv'); } + getInputPanel() { + return this.page.getByTestId('ndv-input-panel'); + } + getParameterExpressionPreviewValue() { return this.page.getByTestId('parameter-expression-preview-value'); } @@ -81,6 +89,14 @@ export class NodeDetailsViewPage extends BasePage { return this.page.getByTestId('run-data-pane-header'); } + getOutputTable() { + return this.getOutputPanel().getByTestId('ndv-data-container').locator('table'); + } + + getOutputDataContainer() { + return this.getOutputPanel().getByTestId('ndv-data-container'); + } + async setPinnedData(data: object | string) { const pinnedData = typeof data === 'string' ? data : JSON.stringify(data); await this.getEditPinnedDataButton().click(); @@ -373,6 +389,14 @@ export class NodeDetailsViewPage extends BasePage { await this.page.getByRole('option', { name: nodeName }).click(); } + getInputTableHeader(index: number = 0) { + return this.getInputPanel().locator('table th').nth(index); + } + + getInputTbodyCell(row: number, col: number) { + return this.getInputPanel().locator('table tbody tr').nth(row).locator('td').nth(col); + } + getAssignmentName(paramName: string, index = 0) { return this.getAssignmentCollectionContainer(paramName) .getByTestId('assignment') @@ -457,6 +481,14 @@ export class NodeDetailsViewPage extends BasePage { await input.type(content); } + getInputTable() { + return this.getInputPanel().locator('table'); + } + + getInputTableCellSpan(row: number, col: number, dataName: string) { + return this.getInputTbodyCell(row, col).locator(`span[data-name="${dataName}"]`).first(); + } + getAddFieldToSortByButton() { return this.getNodeParameters().getByText('Add Field To Sort By'); } @@ -493,6 +525,46 @@ export class NodeDetailsViewPage extends BasePage { await input.fill(value); } + async clickGetBackToCanvas(): Promise { + await this.clickBackToCanvasButton(); + } + + getRunDataInfoCallout() { + return this.page.getByTestId('run-data-callout'); + } + + getOutputPanelTable() { + return this.getOutputTable(); + } + + async checkParameterCheckboxInputByName(name: string): Promise { + const checkbox = this.getParameterInput(name).locator('.el-switch.switch-input'); + await checkbox.click(); + } + + // Credentials modal helpers + async clickCreateNewCredential(eq: number = 0): Promise { + await this.page.getByTestId('node-credentials-select').nth(eq).click(); + await this.page.getByTestId('node-credentials-select-item-new').click(); + } + + // Run selector and linking helpers + getInputRunSelector() { + return this.page.locator('[data-test-id="ndv-input-panel"] [data-test-id="run-selector"]'); + } + + getOutputRunSelector() { + return this.page.locator('[data-test-id="output-panel"] [data-test-id="run-selector"]'); + } + + getInputRunSelectorInput() { + return this.getInputRunSelector().locator('input'); + } + + async toggleInputRunLinking(): Promise { + await this.getInputPanel().getByTestId('link-run').click(); + } + getNodeRunErrorMessage() { return this.page.getByTestId('node-error-message'); } @@ -725,6 +797,19 @@ export class NodeDetailsViewPage extends BasePage { getInputSelect() { return this.page.getByTestId('ndv-input-select').locator('input'); } + + getInputTableRows() { + return this.getInputTable().locator('tr'); + } + + getOutputRunSelectorInput() { + return this.getOutputPanel().locator('[data-test-id="run-selector"] input'); + } + + getAiOutputModeToggle() { + return this.page.getByTestId('ai-output-mode-select'); + } + getCredentialLabel(credentialType: string) { return this.page.getByText(credentialType); } diff --git a/packages/testing/playwright/pages/n8nPage.ts b/packages/testing/playwright/pages/n8nPage.ts index 5f4aa3c5ac..24e8737fe4 100644 --- a/packages/testing/playwright/pages/n8nPage.ts +++ b/packages/testing/playwright/pages/n8nPage.ts @@ -4,6 +4,7 @@ import { AIAssistantPage } from './AIAssistantPage'; import { BecomeCreatorCTAPage } from './BecomeCreatorCTAPage'; import { CanvasPage } from './CanvasPage'; import { CommunityNodesPage } from './CommunityNodesPage'; +import { CredentialsEditModal } from './CredentialsEditModal'; import { CredentialsPage } from './CredentialsPage'; import { DemoPage } from './DemoPage'; import { ExecutionsPage } from './ExecutionsPage'; @@ -59,6 +60,7 @@ export class n8nPage { readonly workflowActivationModal: WorkflowActivationModal; readonly workflowSettingsModal: WorkflowSettingsModal; readonly workflowSharingModal: WorkflowSharingModal; + readonly credentialsModal: CredentialsEditModal; // Composables readonly workflowComposer: WorkflowComposer; @@ -98,6 +100,7 @@ export class n8nPage { // Modals this.workflowActivationModal = new WorkflowActivationModal(page); this.workflowSettingsModal = new WorkflowSettingsModal(page); + this.credentialsModal = new CredentialsEditModal(page); // Composables this.workflowComposer = new WorkflowComposer(this); diff --git a/packages/testing/playwright/tests/ui/30-langchain.spec.ts b/packages/testing/playwright/tests/ui/30-langchain.spec.ts new file mode 100644 index 0000000000..9e8adc55f5 --- /dev/null +++ b/packages/testing/playwright/tests/ui/30-langchain.spec.ts @@ -0,0 +1,534 @@ +import { + AGENT_NODE_NAME, + EDIT_FIELDS_SET_NODE_NAME, + MANUAL_CHAT_TRIGGER_NODE_NAME, + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, + AI_MEMORY_POSTGRES_NODE_NAME, + AI_TOOL_CALCULATOR_NODE_NAME, + AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_WIKIPEDIA_NODE_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + CHAT_TRIGGER_NODE_DISPLAY_NAME, + SCHEDULE_TRIGGER_NODE_NAME, +} from '../../config/constants'; +import { test, expect } from '../../fixtures/base'; +import type { n8nPage } from '../../pages/n8nPage'; + +// Helper functions for common operations +async function addOpenAILanguageModelWithCredentials( + n8n: n8nPage, + parentNode: string, + options: { exactMatch?: boolean; closeNDV?: boolean } = { exactMatch: true, closeNDV: false }, +) { + await n8n.canvas.addSupplementalNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + 'ai_languageModel', + parentNode, + options, + ); + + await n8n.ndv.clickCreateNewCredential(); + await n8n.credentialsModal.setValues({ + apiKey: 'abcd', + }); + await n8n.ndv.clickBackToCanvasButton(); +} + +async function waitForWorkflowSuccess(n8n: n8nPage, timeout = 3000) { + await n8n.notifications.waitForNotificationAndClose('Workflow executed successfully', { + timeout, + }); +} + +async function executeChatAndWaitForResponse(n8n: n8nPage, message: string) { + await n8n.canvas.logsPanel.sendManualChatMessage(message); + await waitForWorkflowSuccess(n8n); +} + +async function verifyChatMessages(n8n: n8nPage, expectedCount: number, inputMessage?: string) { + const messages = n8n.canvas.getManualChatMessages(); + await expect(messages).toHaveCount(expectedCount); + if (inputMessage) { + await expect(messages.first()).toContainText(inputMessage); + } + await expect(messages.last()).toBeVisible(); + return messages; +} + +async function verifyLogsPanelEntries(n8n: n8nPage, expectedEntries: string[]) { + await expect(n8n.canvas.logsPanel.getLogEntries().first()).toBeVisible(); + await expect(n8n.canvas.logsPanel.getLogEntries()).toHaveCount(expectedEntries.length); + for (let i = 0; i < expectedEntries.length; i++) { + await expect(n8n.canvas.logsPanel.getLogEntries().nth(i)).toHaveText(expectedEntries[i]); + } +} + +async function setupBasicAgentWorkflow(n8n: n8nPage, additionalNodes: string[] = []) { + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + // Add additional nodes if specified + for (const nodeName of additionalNodes) { + await n8n.canvas.addSupplementalNodeToParent(nodeName, 'ai_tool', AGENT_NODE_NAME, { + closeNDV: true, + }); + } + + // Always add OpenAI Language Model + await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); +} + +test.describe('Langchain Integration @capability:proxy', () => { + test.beforeEach(async ({ n8n, proxyServer }) => { + await proxyServer.clearAllExpectations(); + await proxyServer.loadExpectations('langchain'); + await n8n.canvas.openNewWorkflow(); + }); + + test.describe('Workflow Execution Behavior', () => { + test('should not open chat modal', async ({ n8n }) => { + await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addSupplementalNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + 'ai_languageModel', + AGENT_NODE_NAME, + { exactMatch: true, closeNDV: true }, + ); + + await n8n.canvas.clickExecuteWorkflowButton(); + await expect(n8n.canvas.getManualChatModal()).toBeHidden(); + }); + + test('should remove test workflow button', async ({ n8n }) => { + await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addSupplementalNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + 'ai_languageModel', + AGENT_NODE_NAME, + { exactMatch: true, closeNDV: true }, + ); + + await n8n.canvas.disableNodeFromContextMenu(SCHEDULE_TRIGGER_NODE_NAME); + await expect(n8n.canvas.getExecuteWorkflowButton()).toBeHidden(); + }); + }); + + test.describe('Node Connection and Configuration', () => { + test('should add nodes to all Agent node input types', async ({ n8n }) => { + const agentSubNodes = [ + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, + AI_TOOL_CALCULATOR_NODE_NAME, + AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, + ]; + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: false }); + + await n8n.ndv.checkParameterCheckboxInputByName('hasOutputParser'); + await n8n.ndv.clickBackToCanvasButton(); + await n8n.canvas.addSupplementalNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + 'ai_languageModel', + AGENT_NODE_NAME, + { exactMatch: true, closeNDV: true }, + ); + + await n8n.canvas.addSupplementalNodeToParent( + AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME, + 'ai_memory', + AGENT_NODE_NAME, + { closeNDV: true }, + ); + + await n8n.canvas.addSupplementalNodeToParent( + AI_TOOL_CALCULATOR_NODE_NAME, + 'ai_tool', + AGENT_NODE_NAME, + { closeNDV: true }, + ); + + await n8n.canvas.addSupplementalNodeToParent( + AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME, + 'ai_outputParser', + AGENT_NODE_NAME, + { closeNDV: true }, + ); + for (const nodeName of agentSubNodes) { + await expect(n8n.canvas.connectionBetweenNodes(nodeName, AGENT_NODE_NAME)).toBeAttached(); + } + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2 + agentSubNodes.length); // Chat Trigger + Agent + 4 inputs + }); + + test('should add multiple tool nodes to Agent node tool input type', async ({ n8n }) => { + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + const tools = [ + AI_TOOL_CALCULATOR_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_CODE_NODE_NAME, + AI_TOOL_WIKIPEDIA_NODE_NAME, + ]; + + for (const tool of tools) { + await n8n.canvas.addSupplementalNodeToParent(tool, 'ai_tool', AGENT_NODE_NAME, { + closeNDV: true, + }); + await expect(n8n.canvas.connectionBetweenNodes(tool, AGENT_NODE_NAME)).toBeAttached(); + } + + // Chat Trigger + Agent + Tools + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2 + tools.length); + }); + }); + + test.describe('Auto-add Behavior', () => { + test('should auto-add chat trigger and basic LLM chain when adding LLM node', async ({ + n8n, + }) => { + await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true }); + + await expect( + n8n.canvas.connectionBetweenNodes( + CHAT_TRIGGER_NODE_DISPLAY_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + ), + ).toBeAttached(); + + await expect( + n8n.canvas.connectionBetweenNodes( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + BASIC_LLM_CHAIN_NODE_NAME, + ), + ).toBeAttached(); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3); + }); + + test('should not auto-add nodes if AI nodes are already present', async ({ n8n }) => { + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true }); + + await expect( + n8n.canvas.connectionBetweenNodes(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME), + ).toBeAttached(); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3); + }); + + test('should not auto-add nodes if ChatTrigger is already present', async ({ n8n }) => { + await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true }); + + await expect( + n8n.canvas.connectionBetweenNodes(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME), + ).toBeAttached(); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3); + }); + }); + + test.describe('Chat Execution and Interaction', () => { + test('should be able to open and execute Basic LLM Chain node', async ({ n8n }) => { + await n8n.canvas.addNode(BASIC_LLM_CHAIN_NODE_NAME, { closeNDV: true }); + + await addOpenAILanguageModelWithCredentials(n8n, BASIC_LLM_CHAIN_NODE_NAME); + + await n8n.canvas.openNode(BASIC_LLM_CHAIN_NODE_NAME); + const inputMessage = 'Hello!'; + + await n8n.ndv.execute(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + // Verify chat message appears + await expect(n8n.canvas.getManualChatLatestBotMessage()).toBeVisible(); + }); + test('should be able to open and execute Agent node', async ({ n8n }) => { + await setupBasicAgentWorkflow(n8n); + + const inputMessage = 'Hello!'; + await n8n.canvas.clickManualChatButton(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + // Verify chat message appears + await expect(n8n.canvas.getManualChatLatestBotMessage()).toBeVisible(); + }); + test('should add and use Manual Chat Trigger node together with Agent node', async ({ + n8n, + }) => { + await setupBasicAgentWorkflow(n8n); + + const inputMessage = 'Hello!'; + await n8n.canvas.clickManualChatButton(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + await verifyChatMessages(n8n, 2, inputMessage); + await verifyLogsPanelEntries(n8n, [ + 'When chat message received', + 'AI Agent', + 'OpenAI Chat Model', + ]); + + await n8n.canvas.closeManualChatModal(); + await expect(n8n.canvas.logsPanel.getLogEntries()).toBeHidden(); + await expect(n8n.canvas.getManualChatInput()).toBeHidden(); + }); + }); + + test.describe('Tool Usage Notifications', () => { + test('should show tool info notice if no existing tools were used during execution', async ({ + n8n, + }) => { + await setupBasicAgentWorkflow(n8n, [AI_TOOL_CALCULATOR_NODE_NAME]); + await n8n.canvas.openNode(AGENT_NODE_NAME); + + const inputMessage = 'Hello!'; + await n8n.ndv.execute(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + await n8n.canvas.closeManualChatModal(); + await n8n.canvas.openNode(AGENT_NODE_NAME); + + await expect(n8n.ndv.getRunDataInfoCallout()).toBeVisible(); + }); + test('should not show tool info notice if tools were used during execution', async ({ + n8n, + }) => { + await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: true }); + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: false }); + await expect(n8n.ndv.getRunDataInfoCallout()).toBeHidden(); + await n8n.ndv.clickBackToCanvasButton(); + + await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); + + await n8n.canvas.addSupplementalNodeToParent( + AI_TOOL_CALCULATOR_NODE_NAME, + 'ai_tool', + AGENT_NODE_NAME, + { closeNDV: true }, + ); + + const inputMessage = 'What is 1000 * 10?'; + await n8n.canvas.clickManualChatButton(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + await n8n.canvas.closeManualChatModal(); + await n8n.canvas.openNode(AGENT_NODE_NAME); + + await expect(n8n.ndv.getRunDataInfoCallout()).toBeHidden(); + }); + }); + + test.describe('Error Handling and Logs Display', () => { + // Helper function to set up the agent workflow with Postgres error configuration + async function setupAgentWorkflowWithPostgresError(n8n: n8nPage) { + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + // Add Calculator Tool (required for OpenAI model) + await n8n.canvas.addSupplementalNodeToParent( + AI_TOOL_CALCULATOR_NODE_NAME, + 'ai_tool', + AGENT_NODE_NAME, + { closeNDV: true }, + ); + + // Add and configure Postgres Memory + await n8n.canvas.addSupplementalNodeToParent( + AI_MEMORY_POSTGRES_NODE_NAME, + 'ai_memory', + AGENT_NODE_NAME, + { closeNDV: false }, + ); + + await n8n.ndv.clickCreateNewCredential(); + await n8n.credentialsModal.setValues({ + password: 'testtesttest', + }); + + await n8n.ndv.getParameterInput('sessionIdType').click(); + await n8n.page.getByRole('option', { name: 'Define below' }).click(); + await n8n.ndv.getParameterInput('sessionKey').locator('input').fill('asdasd'); + await n8n.ndv.clickBackToCanvasButton(); + + // Add and configure OpenAI Language Model + await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); + + await n8n.canvas.clickZoomToFitButton(); + } + + // Helper function to assert logs tab is active + async function assertLogsTabIsActive(n8n: n8nPage) { + await expect(n8n.ndv.getOutputDataContainer()).toBeVisible(); + await expect(n8n.ndv.getAiOutputModeToggle()).toBeVisible(); + + const radioButtons = n8n.ndv.getAiOutputModeToggle().locator('[role="radio"]'); + await expect(radioButtons).toHaveCount(2); + await expect(radioButtons.nth(1)).toHaveAttribute('aria-checked', 'true'); + } + + // Helper function to assert error message is visible + async function assertErrorMessageVisible(n8n: n8nPage) { + await expect( + n8n.ndv.getOutputPanel().getByTestId('node-error-message').first(), + ).toBeVisible(); + await expect( + n8n.ndv.getOutputPanel().getByTestId('node-error-message').first(), + ).toContainText('Error in sub-node'); + } + + test('should open logs tab by default when there was an error', async ({ n8n }) => { + await setupAgentWorkflowWithPostgresError(n8n); + + const inputMessage = 'Test the code tool'; + + // Execute workflow with chat trigger + await n8n.canvas.clickManualChatButton(); + await executeChatAndWaitForResponse(n8n, inputMessage); + + // Check that messages and logs are displayed + const messages = await verifyChatMessages(n8n, 2, inputMessage); + await expect(messages.last()).toContainText( + '[ERROR: The service refused the connection - perhaps it is offline]', + ); + + await expect(n8n.canvas.logsPanel.getLogEntries().first()).toBeVisible(); + await expect(n8n.canvas.logsPanel.getLogEntries()).toHaveCount(3); + await expect(n8n.canvas.logsPanel.getSelectedLogEntry()).toHaveText('AI Agent'); + await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText( + AI_MEMORY_POSTGRES_NODE_NAME, + ); + + await n8n.canvas.closeManualChatModal(); + + // Open the AI Agent node to see the logs + await n8n.canvas.openNode(AGENT_NODE_NAME); + + // Assert that logs tab is active and error is displayed + await assertLogsTabIsActive(n8n); + await assertErrorMessageVisible(n8n); + }); + + test('should switch to logs tab on error, when NDV is already opened', async ({ n8n }) => { + // Remove the auto-added chat trigger + await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: false }); + + // Set manual trigger to output standard pinned data + await n8n.ndv.getEditPinnedDataButton().click(); + await n8n.ndv.savePinnedData(); + await n8n.ndv.close(); + + // Set up the same workflow components but with manual trigger + await setupAgentWorkflowWithPostgresError(n8n); + + // Open the AI Agent node + await n8n.canvas.openNode(AGENT_NODE_NAME); + await n8n.ndv.getParameterInput('promptType').click(); + await n8n.page.getByRole('option', { name: 'Define below' }).click(); + await n8n.ndv.getParameterInput('text').locator('textarea').fill('Some text'); + await n8n.ndv.execute(); + await waitForWorkflowSuccess(n8n); + + // Assert that logs tab is active and error is displayed + await assertLogsTabIsActive(n8n); + await assertErrorMessageVisible(n8n); + }); + }); + + test.describe('Advanced Workflow Features', () => { + test('should render runItems for sub-nodes and allow switching between them', async ({ + n8n, + }) => { + await n8n.start.fromImportedWorkflow('In_memory_vector_store_fake_embeddings.json'); + await n8n.canvas.clickZoomToFitButton(); + await n8n.canvas.deselectAll(); + + await n8n.canvas.executeNode('Populate VS'); + await waitForWorkflowSuccess(n8n); + + const assertInputOutputTextExists = async (text: string) => { + await expect(n8n.ndv.getOutputPanel()).toContainText(text); + await expect(n8n.ndv.getInputPanel()).toContainText(text); + }; + + const assertInputOutputTextNotExists = async (text: string) => { + await expect(n8n.ndv.getOutputPanel()).not.toContainText(text); + await expect(n8n.ndv.getInputPanel()).not.toContainText(text); + }; + + await n8n.canvas.openNode('Character Text Splitter'); + + await expect(n8n.ndv.getOutputRunSelector()).toBeVisible(); + await expect(n8n.ndv.getInputRunSelector()).toBeVisible(); + await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('3 of 3'); + await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('3 of 3'); + await assertInputOutputTextExists('Kyiv'); + await assertInputOutputTextNotExists('Berlin'); + await assertInputOutputTextNotExists('Prague'); + + await n8n.ndv.changeOutputRunSelector('2 of 3'); + await assertInputOutputTextExists('Berlin'); + await assertInputOutputTextNotExists('Kyiv'); + await assertInputOutputTextNotExists('Prague'); + + await n8n.ndv.changeOutputRunSelector('1 of 3'); + await assertInputOutputTextExists('Prague'); + await assertInputOutputTextNotExists('Berlin'); + await assertInputOutputTextNotExists('Kyiv'); + + await n8n.ndv.toggleInputRunLinking(); + await n8n.ndv.changeOutputRunSelector('2 of 3'); + await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('1 of 3'); + await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('2 of 3'); + await expect(n8n.ndv.getInputPanel()).toContainText('Prague'); + await expect(n8n.ndv.getInputPanel()).not.toContainText('Berlin'); + + await expect(n8n.ndv.getOutputPanel()).toContainText('Berlin'); + await expect(n8n.ndv.getOutputPanel()).not.toContainText('Prague'); + + await n8n.ndv.toggleInputRunLinking(); + await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('1 of 3'); + await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('1 of 3'); + await assertInputOutputTextExists('Prague'); + await assertInputOutputTextNotExists('Berlin'); + await assertInputOutputTextNotExists('Kyiv'); + }); + + test('should execute up to Node 1 when using partial execution', async ({ n8n }) => { + await n8n.start.fromImportedWorkflow('Test_workflow_chat_partial_execution.json'); + await n8n.canvas.clickZoomToFitButton(); + + // Check that chat modal is not initially visible + await expect(n8n.canvas.getManualChatModal().locator('main')).toBeHidden(); + + // Open Node 1 and execute it + await n8n.canvas.openNode('Node 1'); + await n8n.ndv.execute(); + // Chat modal should now be visible + await expect(n8n.canvas.getManualChatModal().locator('main')).toBeVisible(); + + // Send first message + await n8n.canvas.logsPanel.sendManualChatMessage('Test'); + await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_1'); + + // Refresh session + await n8n.page.getByTestId('refresh-session-button').click(); + await expect(n8n.canvas.getManualChatMessages()).not.toBeAttached(); + + // Send another message + await n8n.canvas.logsPanel.sendManualChatMessage('Another test'); + await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_3'); + await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_4'); + }); + }); +}); diff --git a/packages/testing/playwright/workflows/In_memory_vector_store_fake_embeddings.json b/packages/testing/playwright/workflows/In_memory_vector_store_fake_embeddings.json new file mode 100644 index 0000000000..40044e70c8 --- /dev/null +++ b/packages/testing/playwright/workflows/In_memory_vector_store_fake_embeddings.json @@ -0,0 +1,308 @@ +{ + "name": "fake_embeddings", + "nodes": [ + { + "parameters": {}, + "id": "de3c1210-3be7-49a6-86ef-9435e661f23f", + "name": "When clicking ‘Execute workflow’", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [480, 760] + }, + { + "parameters": { + "jsonMode": "expressionData", + "jsonData": "={{ $('Code').item.json.city }}", + "options": {} + }, + "id": "de3cb132-14ef-426b-ad33-8365a93dd11f", + "name": "Default Data Loader", + "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", + "typeVersion": 1, + "position": [1100, 900] + }, + { + "parameters": { + "jsCode": "const kyiv = `Kyiv (also Kiev)[a] is the capital and most populous city of Ukraine. It is in north-central Ukraine along the Dnieper River. As of 1 January 2022, its population was 2,952,301,[2] making Kyiv the seventh-most populous city in Europe.[11] Kyiv is an important industrial, scientific, educational, and cultural center in Eastern Europe. It is home to many high-tech industries, higher education institutions, and historical landmarks. The city has an extensive system of public transport and infrastructure, including the Kyiv Metro.\n\nThe city's name is said to derive from the name of Kyi, one of its four legendary founders. During its history, Kyiv, one of the oldest cities in Eastern Europe, passed through several stages of prominence and obscurity. The city probably existed as a commercial center as early as the 5th century. A Slavic settlement on the great trade route between Scandinavia and Constantinople, Kyiv was a tributary of the Khazars,[12] until its capture by the Varangians (Vikings) in the mid-9th century. Under Varangian rule, the city became a capital of Kievan Rus', the first East Slavic state. Completely destroyed during the Mongol invasions in 1240, the city lost most of its influence for the centuries to come. Coming under Lithuania, then Poland and then Russia, the city would grow from a frontier market into an important centre of Orthodox learning in the sixteenth century, and later of industry, commerce, and administration by the nineteenth.[1]\n\nThe city prospered again during the Russian Empire's Industrial Revolution in the late 19th century. In 1918, when the Ukrainian People's Republic declared independence from the Russian Republic after the October Revolution there, Kyiv became its capital. From the end of the Ukrainian-Soviet and Polish-Soviet wars in 1921, Kyiv was a city of the Ukrainian SSR, and made its capital in 1934. The city suffered significant destruction during World War II but quickly recovered in the postwar years, remaining the Soviet Union's third-largest city.\n\nFollowing the collapse of the Soviet Union and Ukrainian independence in 1991, Kyiv remained Ukraine's capital and experienced a steady influx of ethnic Ukrainian migrants from other regions of the country.[13] During the country's transformation to a market economy and electoral democracy, Kyiv has continued to be Ukraine's largest and wealthiest city. Its armament-dependent industrial output fell after the Soviet collapse, adversely affecting science and technology, but new sectors of the economy such as services and finance facilitated Kyiv's growth in salaries and investment, as well as providing continuous funding for the development of housing and urban infrastructure. Kyiv emerged as the most pro-Western region of Ukraine; parties advocating tighter integration with the European Union dominate during elections.`\n\nconst berlin = `Berlin[a] is the capital and largest city of Germany, both by area and by population.[11] Its more than 3.85 million inhabitants[12] make it the European Union's most populous city, as measured by population within city limits.[13] The city is also one of the states of Germany, and is the third smallest state in the country in terms of area. Berlin is surrounded by the state of Brandenburg, and Brandenburg's capital Potsdam is nearby. The urban area of Berlin has a population of over 4.5 million and is therefore the most populous urban area in Germany.[5][14] The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany's second-largest metropolitan region after the Rhine-Ruhr region, and the sixth-biggest metropolitan region by GDP in the European Union.[15]\n\nBerlin was built along the banks of the Spree river, which flows into the Havel in the western borough of Spandau. The city incorporates lakes in the western and southeastern boroughs, the largest of which is Müggelsee. About one-third of the city's area is composed of forests, parks and gardens, rivers, canals, and lakes.[16]\n\nFirst documented in the 13th century[10] and at the crossing of two important historic trade routes,[17] Berlin was designated the capital of the Margraviate of Brandenburg (1417–1701), Kingdom of Prussia (1701–1918), German Empire (1871–1918), Weimar Republic (1919–1933), and Nazi Germany (1933–1945). Berlin has served as a scientific, artistic, and philosophical hub during the Age of Enlightenment, Neoclassicism, and the German revolutions of 1848–1849. During the Gründerzeit, an industrialization-induced economic boom triggered a rapid population increase in Berlin. 1920s Berlin was the third-largest city in the world by population.[18]\n\nAfter World War II and following Berlin's occupation, the city was split into West Berlin and East Berlin, divided by the Berlin Wall.[19] East Berlin was declared the capital of East Germany, while Bonn became the West German capital. Following German reunification in 1990, Berlin once again became the capital of all of Germany. Due to its geographic location and history, Berlin has been called \"the heart of Europe\".[20][21][22]`\n\nconst prague = `Prague (/ˈprɑːɡ/ PRAHG; Czech: Praha [ˈpraɦa] ⓘ)[a] is the capital and largest city of the Czech Republic[9] and the historical capital of Bohemia. Situated on the Vltava river, Prague is home to about 1.4 million people.\n\nPrague is a political, cultural, and economic hub of Central Europe, with a rich history and Romanesque, Gothic, Renaissance and Baroque architectures. It was the capital of the Kingdom of Bohemia and residence of several Holy Roman Emperors, most notably Charles IV (r. 1346–1378) and Rudolf II (r. 1575–1611).[9] It was an important city to the Habsburg monarchy and Austria-Hungary. The city played major roles in the Bohemian and the Protestant Reformations, the Thirty Years' War and in 20th-century history as the capital of Czechoslovakia between the World Wars and the post-war Communist era.[10]\n\nPrague is home to a number of cultural attractions including Prague Castle, Charles Bridge, Old Town Square with the Prague astronomical clock, the Jewish Quarter, Petřín hill and Vyšehrad. Since 1992, the historic center of Prague has been included in the UNESCO list of World Heritage Sites.\n\nThe city has more than ten major museums, along with numerous theatres, galleries, cinemas, and other historical exhibits. An extensive modern public transportation system connects the city. It is home to a wide range of public and private schools, including Charles University in Prague, the oldest university in Central Europe.\n\nPrague is classified as an \"Alpha-\" global city according to GaWC studies.[11] In 2019, the city was ranked as 69th most livable city in the world by Mercer.[12] In the same year, the PICSA Index ranked the city as 13th most livable city in the world.[13] Its rich history makes it a popular tourist destination and as of 2017, the city receives more than 8.5 million international visitors annually. In 2017, Prague was listed as the fifth most visited European city after London, Paris, Rome, and Istanbul.[14]`\n\nreturn [prague, berlin, kyiv].map(i => ({ city: i}))" + }, + "id": "ce9d517e-2dd9-45e4-a566-79bd79cd809b", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [740, 760] + }, + { + "parameters": { + "chunkSize": 300 + }, + "id": "ebe5f3a5-4d90-4a33-bf48-f160f0e83967", + "name": "Character Text Splitter", + "type": "@n8n/n8n-nodes-langchain.textSplitterCharacterTextSplitter", + "typeVersion": 1, + "position": [1100, 1060] + }, + { + "parameters": { + "code": { + "supplyData": { + "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" + } + }, + "outputs": { + "output": [ + { + "type": "ai_embedding" + } + ] + } + }, + "id": "0eac6c5b-89a9-48a4-bd21-19f2b20c3424", + "name": "Fake Embeddings 3", + "type": "@n8n/n8n-nodes-langchain.code", + "typeVersion": 1, + "position": [660, 1220] + }, + { + "parameters": { + "mode": "load", + "prompt": "Tester", + "topK": 3 + }, + "id": "8c9b39bf-59d6-4769-98e1-54988d9d6b53", + "name": "Get All VS", + "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", + "typeVersion": 1, + "position": [680, 1080] + }, + { + "parameters": { + "code": { + "supplyData": { + "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" + } + }, + "outputs": { + "output": [ + { + "type": "ai_embedding" + } + ] + } + }, + "id": "e46004ec-baf6-425c-9897-3faec9e29676", + "name": "Fake Embeddings", + "type": "@n8n/n8n-nodes-langchain.code", + "typeVersion": 1, + "position": [920, 900] + }, + { + "parameters": { + "promptType": "define", + "text": "Just testing", + "options": {} + }, + "id": "b132b323-a813-469c-859b-f1b3ede743a3", + "name": "Question and Answer Chain", + "type": "@n8n/n8n-nodes-langchain.chainRetrievalQa", + "typeVersion": 1.3, + "position": [1680, 780] + }, + { + "parameters": {}, + "id": "b9c412e5-d739-4c82-9a2e-6c0af0cae8f9", + "name": "Vector Store Retriever", + "type": "@n8n/n8n-nodes-langchain.retrieverVectorStore", + "typeVersion": 1, + "position": [1760, 920] + }, + { + "parameters": { + "code": { + "supplyData": { + "code": "const { FakeChatModel } = require('@langchain/core/utils/testing');\n\nreturn new FakeChatModel({});" + } + }, + "outputs": { + "output": [ + { + "type": "ai_languageModel" + } + ] + } + }, + "id": "962b4b87-ffd6-4ab8-8776-6e9c0920930a", + "name": "Fake Language Model", + "type": "@n8n/n8n-nodes-langchain.code", + "typeVersion": 1, + "position": [1620, 920] + }, + { + "parameters": { + "code": { + "supplyData": { + "code": "const { FakeEmbeddings } = require('@langchain/core/utils/testing');\n\nreturn new FakeEmbeddings();" + } + }, + "outputs": { + "output": [ + { + "type": "ai_embedding" + } + ] + } + }, + "id": "c78be34f-6459-4414-86bd-f2670ece129d", + "name": "Fake Embeddings 2", + "type": "@n8n/n8n-nodes-langchain.code", + "typeVersion": 1, + "position": [1700, 1200] + }, + { + "parameters": {}, + "id": "3cee9727-6b97-477c-8277-e8883a98786d", + "name": "Retriever VS", + "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", + "typeVersion": 1, + "position": [1700, 1060] + }, + { + "parameters": { + "mode": "insert" + }, + "id": "5793ec6b-ac00-4a5d-a79c-ff557143e46b", + "name": "Populate VS", + "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", + "typeVersion": 1, + "position": [980, 760] + } + ], + "pinData": {}, + "connections": { + "When clicking ‘Execute workflow’": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + }, + { + "node": "Get All VS", + "type": "main", + "index": 0 + } + ] + ] + }, + "Default Data Loader": { + "ai_document": [ + [ + { + "node": "Populate VS", + "type": "ai_document", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [ + { + "node": "Populate VS", + "type": "main", + "index": 0 + } + ] + ] + }, + "Character Text Splitter": { + "ai_textSplitter": [ + [ + { + "node": "Default Data Loader", + "type": "ai_textSplitter", + "index": 0 + } + ] + ] + }, + "Fake Embeddings 3": { + "ai_embedding": [ + [ + { + "node": "Get All VS", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "Fake Embeddings": { + "ai_embedding": [ + [ + { + "node": "Populate VS", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "Vector Store Retriever": { + "ai_retriever": [ + [ + { + "node": "Question and Answer Chain", + "type": "ai_retriever", + "index": 0 + } + ] + ] + }, + "Fake Language Model": { + "ai_languageModel": [ + [ + { + "node": "Question and Answer Chain", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Fake Embeddings 2": { + "ai_embedding": [ + [ + { + "node": "Retriever VS", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "Retriever VS": { + "ai_vectorStore": [ + [ + { + "node": "Vector Store Retriever", + "type": "ai_vectorStore", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "4ad44cc6-d5f7-48af-8455-c3957baba04c", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" + }, + "id": "ZjxsuN0rMHRVCb2c", + "tags": [] +} diff --git a/packages/testing/playwright/workflows/Test_workflow_chat_partial_execution.json b/packages/testing/playwright/workflows/Test_workflow_chat_partial_execution.json new file mode 100644 index 0000000000..0078255a18 --- /dev/null +++ b/packages/testing/playwright/workflows/Test_workflow_chat_partial_execution.json @@ -0,0 +1,68 @@ +{ + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "535fd3dd-e78f-4ffa-a085-79723fc81b38", + "name": "When chat message received", + "type": "@n8n/n8n-nodes-langchain.chatTrigger", + "typeVersion": 1.1, + "position": [320, -380], + "webhookId": "4fb58136-3481-494a-a30f-d9e064dac186" + }, + { + "parameters": { + "mode": "raw", + "jsonOutput": "{\n \"this_my_field_1\": \"value\",\n \"this_my_field_2\": 1\n}\n", + "options": {} + }, + "id": "78201ec2-6def-40b7-85e5-97b580d7f642", + "name": "Node 1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [580, -380] + }, + { + "parameters": { + "mode": "raw", + "jsonOutput": "{\n \"this_my_field_3\": \"value\",\n \"this_my_field_4\": 1\n}\n", + "options": {} + }, + "id": "1cfca06d-3ec3-427f-89f7-1ef321e025ff", + "name": "Node 2", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [780, -380] + } + ], + "connections": { + "When chat message received": { + "main": [ + [ + { + "node": "Node 1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Node 1": { + "main": [ + [ + { + "node": "Node 2", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "178ef8a5109fc76c716d40bcadb720c455319f7b7a3fd5a39e4f336a091f524a" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b76d38b2e8..0a76ae0465 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3162,6 +3162,9 @@ importers: eslint-plugin-playwright: specifier: 2.2.2 version: 2.2.2(eslint@9.29.0(jiti@1.21.7)) + flatted: + specifier: 'catalog:' + version: 3.2.7 generate-schema: specifier: 2.6.0 version: 2.6.0 @@ -6385,201 +6388,101 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.46.2': - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.49.0': resolution: {integrity: sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.46.2': - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.49.0': resolution: {integrity: sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.46.2': - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.49.0': resolution: {integrity: sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.2': - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.49.0': resolution: {integrity: sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.46.2': - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.49.0': resolution: {integrity: sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.2': - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.49.0': resolution: {integrity: sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.49.0': resolution: {integrity: sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.49.0': resolution: {integrity: sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.46.2': - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.49.0': resolution: {integrity: sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.46.2': - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.49.0': resolution: {integrity: sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} - cpu: [loong64] - os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.49.0': resolution: {integrity: sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.49.0': resolution: {integrity: sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.49.0': resolution: {integrity: sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.46.2': - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.49.0': resolution: {integrity: sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.46.2': - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.49.0': resolution: {integrity: sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.46.2': - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.49.0': resolution: {integrity: sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.46.2': - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.49.0': resolution: {integrity: sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.46.2': - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.49.0': resolution: {integrity: sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.2': - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.49.0': resolution: {integrity: sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.2': - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.49.0': resolution: {integrity: sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==} cpu: [x64] @@ -14333,7 +14236,6 @@ packages: resolution: {integrity: sha512-gv6vLGcmAOg96/fgo3d9tvA4dJNZL3fMyBqVRrGxQ+Q/o4k9QzbJ3NQF9cOO/71wRodoXhaPgphvMFU68qVAJQ==} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode.vue@3.3.4: @@ -14723,11 +14625,6 @@ packages: rndm@1.2.0: resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} - rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.49.0: resolution: {integrity: sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -19663,7 +19560,7 @@ snapshots: '@jest/console@29.6.2': dependencies: '@jest/types': 29.6.1 - '@types/node': 20.19.10 + '@types/node': 20.19.11 chalk: 4.1.2 jest-message-util: 29.6.2 jest-util: 29.6.2 @@ -19783,7 +19680,7 @@ snapshots: '@jest/transform': 29.6.2 '@jest/types': 29.6.1 '@jridgewell/trace-mapping': 0.3.30 - '@types/node': 20.19.10 + '@types/node': 20.19.11 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -21083,123 +20980,63 @@ snapshots: optionalDependencies: rollup: 4.49.0 - '@rollup/rollup-android-arm-eabi@4.46.2': - optional: true - '@rollup/rollup-android-arm-eabi@4.49.0': optional: true - '@rollup/rollup-android-arm64@4.46.2': - optional: true - '@rollup/rollup-android-arm64@4.49.0': optional: true - '@rollup/rollup-darwin-arm64@4.46.2': - optional: true - '@rollup/rollup-darwin-arm64@4.49.0': optional: true - '@rollup/rollup-darwin-x64@4.46.2': - optional: true - '@rollup/rollup-darwin-x64@4.49.0': optional: true - '@rollup/rollup-freebsd-arm64@4.46.2': - optional: true - '@rollup/rollup-freebsd-arm64@4.49.0': optional: true - '@rollup/rollup-freebsd-x64@4.46.2': - optional: true - '@rollup/rollup-freebsd-x64@4.49.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.49.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.49.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.49.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-arm64-musl@4.49.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.49.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.49.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.49.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-riscv64-musl@4.49.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.49.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.2': - optional: true - '@rollup/rollup-linux-x64-gnu@4.49.0': optional: true - '@rollup/rollup-linux-x64-musl@4.46.2': - optional: true - '@rollup/rollup-linux-x64-musl@4.49.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.49.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.49.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.46.2': - optional: true - '@rollup/rollup-win32-x64-msvc@4.49.0': optional: true @@ -22309,7 +22146,7 @@ snapshots: '@types/basic-auth@1.1.3': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/bcryptjs@2.4.2': {} @@ -22318,7 +22155,7 @@ snapshots: '@types/body-parser@1.19.2': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/caseless@0.12.5': {} @@ -22336,7 +22173,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/convict@6.1.1': dependencies: @@ -22376,7 +22213,7 @@ snapshots: '@types/express-serve-static-core@5.0.6(patch_hash=d602248fcd302cf5a794d1e85a411633ba9635ea5d566d6f2e0429c7ae0fa3eb)': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/qs': 6.9.15 '@types/range-parser': 1.2.4 '@types/send': 0.17.4 @@ -22480,7 +22317,7 @@ snapshots: '@types/jsonwebtoken@9.0.9': dependencies: '@types/ms': 2.1.0 - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/k6@0.52.0': {} @@ -22548,7 +22385,7 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/node-fetch@2.6.13': dependencies: @@ -22587,7 +22424,7 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 pg-protocol: 1.6.1 pg-types: 2.2.0 @@ -22624,7 +22461,7 @@ snapshots: '@types/readable-stream@4.0.10': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 safe-buffer: 5.1.2 '@types/replacestream@4.0.1': {} @@ -22632,7 +22469,7 @@ snapshots: '@types/request@2.48.12': dependencies: '@types/caseless': 0.12.5 - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/tough-cookie': 4.0.5 form-data: 4.0.4 @@ -22653,12 +22490,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/serve-static@1.15.0': dependencies: '@types/mime': 3.0.1 - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/shelljs@0.8.11': dependencies: @@ -22722,7 +22559,7 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/tedious@4.0.9': dependencies: @@ -22776,7 +22613,7 @@ snapshots: '@types/xml2js@0.4.14': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 '@types/yamljs@0.2.31': {} @@ -22788,7 +22625,7 @@ snapshots: '@types/yauzl@2.10.0': dependencies: - '@types/node': 20.19.10 + '@types/node': 20.19.11 optional: true '@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2)': @@ -26540,7 +26377,7 @@ snapshots: dependencies: magic-string: 0.30.17 mlly: 1.7.4 - rollup: 4.46.2 + rollup: 4.49.0 flat-cache@4.0.1: dependencies: @@ -28036,7 +27873,7 @@ snapshots: dependencies: '@jest/types': 29.6.1 '@types/graceful-fs': 4.1.6 - '@types/node': 20.19.10 + '@types/node': 20.19.11 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -28148,7 +27985,7 @@ snapshots: '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.19.10 + '@types/node': 20.19.11 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -28176,7 +28013,7 @@ snapshots: '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.19.10 + '@types/node': 20.19.11 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -28250,7 +28087,7 @@ snapshots: dependencies: '@jest/test-result': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.19.10 + '@types/node': 20.19.11 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -31430,32 +31267,6 @@ snapshots: rndm@1.2.0: {} - rollup@4.46.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 - fsevents: 2.3.3 - rollup@4.49.0: dependencies: '@types/estree': 1.0.8 @@ -32923,7 +32734,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.19.3) resolve-from: 5.0.0 - rollup: 4.46.2 + rollup: 4.49.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 @@ -33424,10 +33235,10 @@ snapshots: vite@6.3.5(@types/node@20.19.11)(jiti@1.21.7)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3): dependencies: esbuild: 0.25.9 - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.46.2 + rollup: 4.49.0 tinyglobby: 0.2.14 optionalDependencies: '@types/node': 20.19.11