import { AGENT_NODE_NAME, AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, AI_TOOL_CODE_NODE_NAME, CHAT_TRIGGER_NODE_DISPLAY_NAME, HITL_TOOL_SUBCATEGORY, MANUAL_CHAT_TRIGGER_NODE_NAME, } from '../../../config/constants'; import { expect, test } from '../../../fixtures/base'; import type { n8nPage } from '../../../pages/n8nPage'; 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.credentialsComposer.createFromNdv({ apiKey: 'abcd', }); await n8n.ndv.clickBackToCanvasButton(); } async function waitForWorkflowSuccess(n8n: n8nPage, timeout = 3000) { await n8n.notifications.waitForNotificationAndClose('Workflow executed successfully', { timeout, }); } async function setEditorText(n8n: n8nPage, parameterName: string, value: string) { const codeEditor = n8n.ndv.getParameterInput(parameterName).locator('.cm-content'); await codeEditor.click(); await n8n.page.keyboard.press('ControlOrMeta+a'); await n8n.page.keyboard.press('Delete'); await codeEditor.fill(value); } const hitlForToolsTestConfig = { capability: { services: ['proxy'], env: { N8N_COMMUNITY_PACKAGES_ENABLED: 'false', }, }, } as const; test.use(hitlForToolsTestConfig); test.describe('HITL for Tools @capability:proxy', { annotation: [ { type: 'owner', description: 'AI' }, ], }, () => { test.beforeEach(async ({ n8n, services }) => { await services.proxy.clearAllExpectations(); await services.proxy.loadExpectations('hitl-for-tools'); await n8n.canvas.openNewWorkflow(); }); test('should add a HITL node between Agent and Tool node', async ({ n8n }) => { await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); await n8n.canvas.addSupplementalNodeToParent( AI_TOOL_CODE_NODE_NAME, 'ai_tool', AGENT_NODE_NAME, { closeNDV: true }, ); await n8n.canvas.dragNodeToRelativePosition(AI_TOOL_CODE_NODE_NAME, 100, 50); const specificConnection = n8n.canvas.connectionBetweenNodes( AI_TOOL_CODE_NODE_NAME, AGENT_NODE_NAME, ); await expect(specificConnection).toBeVisible(); // eslint-disable-next-line playwright/no-force-option await specificConnection.hover({ force: true }); const addNodeButton = n8n.page.getByTestId('add-connection-button'); await expect(addNodeButton).toBeVisible(); await addNodeButton.click(); await n8n.canvas.clickNodeCreatorItemName(MANUAL_CHAT_TRIGGER_NODE_NAME); await n8n.page.keyboard.press('Escape'); await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5); await expect(n8n.canvas.nodeConnections()).toHaveCount(4); }); test('should add a HITL tool node and run it', async ({ n8n }) => { await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); await n8n.canvas.addSupplementalNodeToParent( MANUAL_CHAT_TRIGGER_NODE_NAME, 'ai_tool', AGENT_NODE_NAME, { closeNDV: true, subcategory: HITL_TOOL_SUBCATEGORY, exactMatch: true }, ); await n8n.canvas.addSupplementalNodeToParent( AI_TOOL_CODE_NODE_NAME, 'ai_tool', MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: false }, ); await n8n.ndv.getParameterInput('description').locator('textarea').fill('Send email'); await setEditorText(n8n, 'jsCode', 'return "Email sent";'); await n8n.ndv.setParameterSwitch('specifyInputSchema', true); await setEditorText(n8n, 'jsonSchemaExample', '{"receiver": "", "body": ""}'); await n8n.ndv.clickBackToCanvasButton(); await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: false, action: 'Send a message', fromNode: AGENT_NODE_NAME, }); await n8n.ndv.openExpressionEditorModal('message'); await n8n.ndv.fillExpressionEditorModalInput('{{ $json.output }}'); await n8n.ndv.getExpressionEditorModalOutput().click(); await n8n.page.keyboard.press('Escape'); await n8n.ndv.clickBackToCanvasButton(); await n8n.canvas.openNode(CHAT_TRIGGER_NODE_DISPLAY_NAME); await n8n.ndv.addParameterOptionByName('Response mode'); await n8n.ndv.selectOptionInParameterDropdown('responseMode', 'Using Response Nodes'); await n8n.ndv.clickBackToCanvasButton(); await n8n.canvas.clickManualChatButton(); await n8n.canvas.logsPanel.sendManualChatMessage('Send welcome email to john@gmail.com'); const approveButton = n8n.page.getByTestId('canvas-chat').getByText('Approve'); await expect(approveButton).toBeVisible({ timeout: 15000 }); await approveButton.click({ button: 'middle' }); await waitForWorkflowSuccess(n8n); }); });