first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user