diff --git a/packages/frontend/editor-ui/src/features/execution/logs/components/LogsPanel.vue b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsPanel.vue
index 30a070ed04d..2582e11fc64 100644
--- a/packages/frontend/editor-ui/src/features/execution/logs/components/LogsPanel.vue
+++ b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsPanel.vue
@@ -57,7 +57,7 @@ const {
onOverviewPanelResizeEnd,
} = useLogsPanelLayout(workflowName, popOutContainer, popOutContent, container, logsContainer);
-const { currentSessionId, messages, refreshSession, displayExecution } = useChatState(
+const { currentSessionId, chatOptions, refreshSession, displayExecution } = useChatState(
props.isReadOnly,
);
@@ -168,7 +168,7 @@ function handleChangeOutputTableColumnCollapsing(columnName: string | null) {
>
;
- messages: ComputedRef;
previousChatMessages: ComputedRef;
refreshSession: () => void;
displayExecution: (executionId: string) => void;
@@ -261,7 +260,7 @@ export function useChatState(
messageComponents: {
[MessageComponentKey.WITH_BUTTONS]: MessageWithButtons,
},
- messageHistory: messages.value,
+ messageHistory: isReadOnly ? restoredChatMessages.value : messages.value,
disabled: ref(isReadOnly),
i18n: {
en: {
@@ -366,9 +365,6 @@ export function useChatState(
return {
currentSessionId: computed(() => logsStore.chatSessionId),
- messages: computed(() =>
- isReadOnly ? restoredChatMessages.value : logsStore.chatSessionMessages,
- ),
previousChatMessages,
refreshSession,
displayExecution,
diff --git a/packages/testing/playwright/expectations/execution.logs/1777281716392-unknown-host-POST-_v1_messages-3002af3d.json b/packages/testing/playwright/expectations/execution.logs/1777281716392-unknown-host-POST-_v1_messages-3002af3d.json
new file mode 100644
index 00000000000..4628ff0196a
--- /dev/null
+++ b/packages/testing/playwright/expectations/execution.logs/1777281716392-unknown-host-POST-_v1_messages-3002af3d.json
@@ -0,0 +1,120 @@
+{
+ "httpRequest": {
+ "method": "POST",
+ "path": "/v1/messages",
+ "body": {
+ "contentType": "application/json",
+ "type": "JSON",
+ "json": {
+ "model": "claude-sonnet-4-5-20250929",
+ "stream": true,
+ "max_tokens": 8192,
+ "thinking": {
+ "type": "disabled"
+ },
+ "messages": [
+ {
+ "role": "user",
+ "content": "Hi!"
+ }
+ ]
+ },
+ "rawBytes": "eyJtb2RlbCI6ImNsYXVkZS1zb25uZXQtNC01LTIwMjUwOTI5Iiwic3RyZWFtIjp0cnVlLCJtYXhfdG9rZW5zIjo4MTkyLCJ0aGlua2luZyI6eyJ0eXBlIjoiZGlzYWJsZWQifSwibWVzc2FnZXMiOlt7InJvbGUiOiJ1c2VyIiwiY29udGVudCI6IkhpISJ9XX0="
+ }
+ },
+ "httpResponse": {
+ "statusCode": 200,
+ "reasonPhrase": "OK",
+ "headers": {
+ "x-envoy-upstream-service-time": [
+ "1494"
+ ],
+ "strict-transport-security": [
+ "max-age=31536000; includeSubDomains; preload"
+ ],
+ "set-cookie": [
+ "_cfuvid=JjjjTwlTrrDFqgfRsM9he9mrHuj9XhEgxXbsiBhNeiE-1777281698.7332523-1.0.1.1-OyArYqG84afkWTE3QLvX8_xQHOn90JMyDIjk0jbx7qQ; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
+ ],
+ "request-id": [
+ "req_011CaU2iSzpUtRdjtvzUYN8p"
+ ],
+ "cf-cache-status": [
+ "DYNAMIC"
+ ],
+ "anthropic-ratelimit-tokens-reset": [
+ "2026-04-27T09:21:38Z"
+ ],
+ "anthropic-ratelimit-tokens-remaining": [
+ "27000000"
+ ],
+ "anthropic-ratelimit-tokens-limit": [
+ "27000000"
+ ],
+ "anthropic-ratelimit-requests-reset": [
+ "2026-04-27T09:21:38Z"
+ ],
+ "anthropic-ratelimit-requests-remaining": [
+ "19998"
+ ],
+ "anthropic-ratelimit-requests-limit": [
+ "20000"
+ ],
+ "anthropic-ratelimit-output-tokens-reset": [
+ "2026-04-27T09:21:38Z"
+ ],
+ "anthropic-ratelimit-output-tokens-remaining": [
+ "4500000"
+ ],
+ "anthropic-ratelimit-output-tokens-limit": [
+ "4500000"
+ ],
+ "anthropic-ratelimit-input-tokens-reset": [
+ "2026-04-27T09:21:38Z"
+ ],
+ "anthropic-ratelimit-input-tokens-remaining": [
+ "22500000"
+ ],
+ "anthropic-ratelimit-input-tokens-limit": [
+ "22500000"
+ ],
+ "X-Robots-Tag": [
+ "none"
+ ],
+ "Server": [
+ "cloudflare"
+ ],
+ "Date": [
+ "Mon, 27 Apr 2026 09:21:40 GMT"
+ ],
+ "Content-Type": [
+ "text/event-stream; charset=utf-8"
+ ],
+ "Content-Security-Policy": [
+ "default-src 'none'; frame-ancestors 'none'"
+ ],
+ "Cache-Control": [
+ "no-cache"
+ ],
+ "CF-RAY": [
+ "9f2cc2191dd9e527-TXL"
+ ]
+ },
+ "cookies": {
+ "_cfuvid": "JjjjTwlTrrDFqgfRsM9he9mrHuj9XhEgxXbsiBhNeiE-1777281698.7332523-1.0.1.1-OyArYqG84afkWTE3QLvX8_xQHOn90JMyDIjk0jbx7qQ"
+ },
+ "body": {
+ "type": "STRING",
+ "string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01Np2KsYNwWYH8AoYtUy4UZA\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":8,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello! How can I help you today\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"?\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":12} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
+ "rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNS0yMDI1MDkyOSIsImlkIjoibXNnXzAxTnAyS3NZTndXWUg4QW9ZdFV5NFVaQSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjksImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjo4LCJzZXJ2aWNlX3RpZXIiOiJzdGFuZGFyZCIsImluZmVyZW5jZV9nZW8iOiJub3RfYXZhaWxhYmxlIn19ICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6InRleHRfZGVsdGEiLCJ0ZXh0IjoiSGVsbG8hIEhvdyBjYW4gSSBoZWxwIHlvdSB0b2RheSJ9ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6Ij8ifSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoiZW5kX3R1cm4iLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjksImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsIm91dHB1dF90b2tlbnMiOjEyfSAgfQoKZXZlbnQ6IG1lc3NhZ2Vfc3RvcApkYXRhOiB7InR5cGUiOiJtZXNzYWdlX3N0b3AiICAgfQoK",
+ "contentType": "text/event-stream; charset=utf-8"
+ }
+ },
+ "id": "1777281716392-unknown-host-POST-_v1_messages-3002af3d.json",
+ "priority": 0,
+ "timeToLive": {
+ "unlimited": true
+ },
+ "times": {
+ "unlimited": true
+ }
+}
\ No newline at end of file
diff --git a/packages/testing/playwright/tests/e2e/workflows/editor/execution/fixtures.ts b/packages/testing/playwright/tests/e2e/workflows/editor/execution/fixtures.ts
new file mode 100644
index 00000000000..5bf31fee0b1
--- /dev/null
+++ b/packages/testing/playwright/tests/e2e/workflows/editor/execution/fixtures.ts
@@ -0,0 +1,70 @@
+import { test as base, expect as baseExpect } from '../../../../../fixtures/base';
+import type { CredentialResponse } from '../../../../../services/credential-api-helper';
+
+const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY ?? 'mock-anthropic-api-key';
+
+type ExecutionLogFixtures = {
+ anthropicCredential: CredentialResponse;
+ anthropicApiKey: string;
+ proxySetup: undefined;
+};
+
+export const executionLogsTestConfig = {
+ capability: {
+ services: ['proxy'],
+ env: {
+ N8N_COMMUNITY_PACKAGES_ENABLED: 'false', // suppress extraneous HTTP requests
+ },
+ },
+} as const;
+
+export const test = base.extend({
+ anthropicApiKey: async ({}, use) => {
+ await use(ANTHROPIC_API_KEY);
+ },
+
+ proxySetup: [
+ async ({ services }, use) => {
+ // Setup
+ await services.proxy.clearAllExpectations();
+ await services.proxy.loadExpectations('execution.logs', { strictBodyMatching: true });
+
+ await use(undefined);
+
+ // Teardown
+ if (!process.env.CI) {
+ await services.proxy.recordExpectations('execution.logs', {
+ dedupe: true,
+ transform: (expectation) => {
+ const response = expectation.httpResponse as {
+ headers?: Record;
+ };
+
+ if (response?.headers) {
+ delete response.headers['anthropic-organization-id'];
+ }
+
+ return expectation;
+ },
+ });
+ }
+ },
+ { auto: true },
+ ],
+
+ anthropicCredential: async ({ n8n, anthropicApiKey }, use) => {
+ const res = await n8n.api.credentials.createCredential({
+ name: `Anthropic cred ${crypto.randomUUID().slice(0, 8)}`,
+ type: 'anthropicApi',
+ data: {
+ apiKey: anthropicApiKey,
+ },
+ });
+
+ await use(res);
+
+ await n8n.api.credentials.deleteCredential(res.id);
+ },
+});
+
+export const expect = baseExpect;
diff --git a/packages/testing/playwright/tests/e2e/workflows/editor/execution/logs.spec.ts b/packages/testing/playwright/tests/e2e/workflows/editor/execution/logs.spec.ts
index 59101e13c21..fae48a32936 100644
--- a/packages/testing/playwright/tests/e2e/workflows/editor/execution/logs.spec.ts
+++ b/packages/testing/playwright/tests/e2e/workflows/editor/execution/logs.spec.ts
@@ -1,4 +1,6 @@
-import { test, expect } from '../../../../../fixtures/base';
+import { test, expect, executionLogsTestConfig } from './fixtures';
+
+test.use(executionLogsTestConfig);
// Node name constants
const NODES = {
@@ -101,58 +103,50 @@ test.describe(
await expect(n8n.canvas.logsPanel.getLogEntries().nth(2)).toContainText(NODES.IF);
});
- // TODO: make it possible to test workflows with AI model end-to-end
- test.fixme(
- 'should show input and output data in the selected display mode',
- async ({ n8n, setupRequirements }) => {
- await setupRequirements({ workflow: 'Workflow_ai_agent.json' });
+ test('should show input and output data in the selected display mode', async ({
+ n8n,
+ anthropicCredential,
+ setupRequirements,
+ }) => {
+ await setupRequirements({ workflow: 'Workflow_ai_agent.json' });
- await n8n.canvas.clickZoomToFitButton();
- await n8n.canvas.logsPanel.open();
- await n8n.canvas.logsPanel.sendManualChatMessage('Hi!');
- await n8n.workflowComposer.executeWorkflowAndWaitForNotification('Successful');
- await expect(n8n.canvas.logsPanel.getManualChatMessages().nth(0)).toContainText('Hi!');
- await expect(n8n.canvas.logsPanel.getManualChatMessages().nth(1)).toContainText(
- 'Hello from e2e model!!!',
- );
- await expect(n8n.canvas.logsPanel.getLogEntries().nth(2)).toHaveText('E2E Chat Model');
- await n8n.canvas.logsPanel.getLogEntries().nth(2).click();
+ await n8n.canvas.clickZoomToFitButton();
+ await n8n.canvas.openNode('E2E Chat Model');
+ await expect(n8n.ndv.getCredentialSelect()).toHaveValue(anthropicCredential.name);
+ await n8n.ndv.close();
+ await n8n.canvas.logsPanel.open();
+ await n8n.canvas.logsPanel.sendManualChatMessage('Hi!');
+ await expect(n8n.canvas.logsPanel.getManualChatMessages().nth(0)).toContainText('Hi!');
+ await expect(n8n.canvas.logsPanel.getManualChatMessages().nth(1)).toContainText('Hello');
+ await expect(n8n.canvas.logsPanel.getLogEntries().nth(2)).toHaveText('E2E Chat Model');
- await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText(
- 'Hello from e2e model!!!',
- );
- await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('table');
- await expect(n8n.canvas.logsPanel.outputPanel.getTbodyCell(0, 0)).toContainText(
- 'text:Hello from **e2e** model!!!',
- );
- await expect(n8n.canvas.logsPanel.outputPanel.getTbodyCell(0, 1)).toContainText(
- 'completionTokens:20',
- );
- await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('schema');
- await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText('generations[0]');
- await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText(
- 'Hello from **e2e** model!!!',
- );
- await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('json');
- await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText(
- '[{"response": {"generations": [',
- );
+ await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText('Hello');
+ await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('table');
+ await expect(n8n.canvas.logsPanel.outputPanel.getTbodyCell(0, 0)).toContainText('text:Hello');
+ await expect(n8n.canvas.logsPanel.outputPanel.getTbodyCell(0, 1)).toContainText(
+ /completionTokens:\d+promptTokens:\d+totalTokens:\d+/,
+ );
+ await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('schema');
+ await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText('generations[0]');
+ await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText('Hello');
+ await n8n.canvas.logsPanel.outputPanel.switchDisplayMode('json');
+ await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText(
+ /\[\s*{\s*"response":\s*{\s*"generations":\s*\[/,
+ );
- await n8n.canvas.logsPanel.toggleInputPanel();
- await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('Human: Hi!');
- await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('table');
- await expect(n8n.canvas.logsPanel.inputPanel.getTbodyCell(0, 0)).toContainText(
- '0:Human: Hi!',
- );
- await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('schema');
- await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('messages[0]');
- await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('Human: Hi!');
- await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('json');
- await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText(
- '[{"messages": ["Human: Hi!"],',
- );
- },
- );
+ await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('Human: Hi!');
+ await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('table');
+ await expect(n8n.canvas.logsPanel.inputPanel.getTbodyCell(0, 0)).toContainText(
+ '0:Human: Hi!',
+ );
+ await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('schema');
+ await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('messages[0]');
+ await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText('Human: Hi!');
+ await n8n.canvas.logsPanel.inputPanel.switchDisplayMode('json');
+ await expect(n8n.canvas.logsPanel.inputPanel.get()).toContainText(
+ /\[\s*{\s*"messages":\s*\[\s*"Human: Hi!"\s*\],/,
+ );
+ });
test('should show input and output data of correct run index and branch', async ({
n8n,
@@ -223,21 +217,25 @@ test.describe(
await expect(n8n.canvas.logsPanel.getLogEntries()).toHaveCount(6);
});
- // TODO: make it possible to test workflows with AI model end-to-end
- test.fixme('should show logs for a past execution', async ({ n8n, setupRequirements }) => {
+ test('should show logs for a past execution', async ({
+ n8n,
+ anthropicCredential,
+ setupRequirements,
+ }) => {
await setupRequirements({ workflow: 'Workflow_ai_agent.json' });
await n8n.canvas.clickZoomToFitButton();
+ await n8n.canvas.openNode('E2E Chat Model');
+ await expect(n8n.ndv.getCredentialSelect()).toHaveValue(anthropicCredential.name);
+ await n8n.ndv.close();
await n8n.canvas.logsPanel.open();
await n8n.canvas.logsPanel.sendManualChatMessage('Hi!');
- await n8n.workflowComposer.executeWorkflowAndWaitForNotification('Successful');
+ await n8n.notifications.waitForNotificationAndClose('Successful');
await n8n.canvas.openExecutions();
await n8n.executions.getAutoRefreshButton().click();
await expect(n8n.executions.logsPanel.getManualChatMessages().nth(0)).toContainText('Hi!');
- await expect(n8n.executions.logsPanel.getManualChatMessages().nth(1)).toContainText(
- 'Hello from e2e model!!!',
- );
+ await expect(n8n.executions.logsPanel.getManualChatMessages().nth(1)).toContainText('Hello');
await expect(
n8n.executions.logsPanel.getOverviewStatus().filter({ hasText: /Success in [\d.]+m?s/ }),
).toBeVisible();
diff --git a/packages/testing/playwright/workflows/Workflow_ai_agent.json b/packages/testing/playwright/workflows/Workflow_ai_agent.json
index 30e4cb34786..f281d2d68f0 100644
--- a/packages/testing/playwright/workflows/Workflow_ai_agent.json
+++ b/packages/testing/playwright/workflows/Workflow_ai_agent.json
@@ -23,10 +23,16 @@
},
{
"parameters": {
- "response": "Hello from **e2e** model!!!"
+ "model": {
+ "__rl": true,
+ "mode": "list",
+ "value": "claude-sonnet-4-5-20250929",
+ "cachedResultName": "Claude Sonnet 4.5"
+ },
+ "options": {}
},
- "type": "@n8n/n8n-nodes-langchain.lmChatE2eTest",
- "typeVersion": 1,
+ "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
+ "typeVersion": 1.3,
"position": [308, 220],
"id": "2f239d5b-95ef-4949-92b6-5a7541e1029f",
"name": "E2E Chat Model"