fix(editor): Show warning toast when executed node was not reached

When executing to a specific node that sits on a conditional branch
(e.g. after an If or Switch node), the node may not be reached if the
condition routes data down a different path. Previously the toast
incorrectly showed "Node executed successfully". Now it shows a warning
explaining the execution took a different path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Svetoslav Dekov
2026-03-16 14:35:07 +02:00
parent 6023d46fd1
commit 04bfdc8729
3 changed files with 115 additions and 1 deletions

View File

@@ -2341,6 +2341,8 @@
"personalizationModal.registerEmailForTrial.success.button": "Start using n8n",
"personalizationModal.registerEmailForTrial.error": "Error while registering for enterprise trial",
"pushConnection.nodeExecutedSuccessfully": "Node executed successfully",
"pushConnection.nodeNotExecuted": "Node was not executed",
"pushConnection.nodeNotExecuted.message": "The execution took a different path and didn't reach this node. This typically happens with conditional nodes like If or Switch.",
"pushConnection.workflowExecutedSuccessfully": "Workflow executed successfully",
"pushConnection.error.message": "Connection lost",
"pushConnection.error.tooltip": "You have a connection issue or the server is down. <br />n8n should reconnect automatically once the issue is resolved.",

View File

@@ -8,7 +8,7 @@ import {
handleExecutionFinishedWithErrorOrCanceled,
type SimplifiedExecution,
} from './executionFinished';
import type { IRunExecutionData, ITaskData } from 'n8n-workflow';
import type { IRunExecutionData, ITaskData, INodeTypeDescription } from 'n8n-workflow';
import { EVALUATION_TRIGGER_NODE_TYPE } from 'n8n-workflow';
import type { INodeUi, IWorkflowDb } from '@/Interface';
import type { Router } from 'vue-router';
@@ -21,12 +21,26 @@ import { useUIStore } from '@/app/stores/ui.store';
import { mockedStore } from '@/__tests__/utils';
import { useReadyToRunStore } from '@/features/workflows/readyToRun/stores/readyToRun.store';
import { useBuilderStore } from '@/features/ai/assistant/builder.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
const opts = {
workflowState: mock<WorkflowState>(),
router: mock<Router>(),
};
const mockShowMessage = vi.fn();
vi.mock('@/app/composables/useToast', () => ({
useToast: () => ({
showMessage: mockShowMessage,
}),
}));
vi.mock('@/app/composables/useDocumentTitle', () => ({
useDocumentTitle: () => ({
setDocumentTitle: vi.fn(),
}),
}));
const runWorkflow = vi.fn();
vi.mock('@/app/composables/useRunWorkflow', () => ({
@@ -578,6 +592,98 @@ describe('manual execution stats tracking', () => {
});
});
describe('toast messages', () => {
beforeEach(() => {
mockShowMessage.mockClear();
});
it('shows success toast when executed node has run data', () => {
const pinia = createTestingPinia();
setActivePinia(pinia);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const nodeName = 'Send Telegram';
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
executedNode: nodeName,
data: {
resultData: {
runData: {
[nodeName]: [mock<ITaskData>()],
},
},
},
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
mock<INodeUi>({ type: 'n8n-nodes-base.telegram', typeVersion: 1 }),
);
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', false);
expect(mockShowMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
});
it('shows warning toast when executed node was not reached', () => {
const pinia = createTestingPinia();
setActivePinia(pinia);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const nodeName = 'Send a text message';
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
executedNode: nodeName,
data: {
resultData: {
runData: {},
},
},
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
mock<INodeUi>({ type: 'n8n-nodes-base.vonage', typeVersion: 1 }),
);
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', false);
expect(mockShowMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'warning' }));
});
it('does not show warning toast when successToastAlreadyShown is true', () => {
const pinia = createTestingPinia();
setActivePinia(pinia);
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
const nodeName = 'Send a text message';
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
executedNode: nodeName,
data: {
resultData: {
runData: {},
},
},
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
mock<INodeUi>({ type: 'n8n-nodes-base.vonage', typeVersion: 1 }),
);
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', true);
expect(mockShowMessage).not.toHaveBeenCalled();
});
});
describe('handleExecutionFinishedWithErrorOrCanceled', () => {
it('increments error stats on execution error', () => {
const pinia = createTestingPinia();

View File

@@ -439,6 +439,12 @@ export function handleExecutionFinishedWithSuccessOrOther(
}),
type: 'success',
});
} else if (!nodeOutput && !successToastAlreadyShown) {
toast.showMessage({
title: i18n.baseText('pushConnection.nodeNotExecuted'),
message: i18n.baseText('pushConnection.nodeNotExecuted.message'),
type: 'warning',
});
} else if (!successToastAlreadyShown) {
handleExecutionFinishedSuccessfully(
workflowName,