add e2e tests

This commit is contained in:
Mutasem Aldmour 2024-11-14 12:17:18 +01:00
parent 5f491fbde3
commit 40fd8b318b
No known key found for this signature in database
GPG key ID: 3DFA8122BB7FD6B8
10 changed files with 650 additions and 25 deletions

View file

@ -0,0 +1,29 @@
/**
* Getters
*/
export const getExecutionsSidebar = () => cy.getByTestId('executions-sidebar');
export const getWorkflowExecutionPreviewIframe = () => cy.getByTestId('workflow-preview-iframe');
export const getExecutionPreviewBody = () =>
getWorkflowExecutionPreviewIframe()
.its('0.contentDocument.body')
.then((el) => cy.wrap(el));
export const getExecutionPreviewBodyNodes = () =>
getExecutionPreviewBody().findChildByTestId('canvas-node');
export const getExecutionPreviewBodyNodesByName = (name: string) =>
getExecutionPreviewBody().findChildByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0);
export function getExecutionPreviewOutputPanelRelatedExecutionLink() {
return getExecutionPreviewBody().findChildByTestId('related-execution-link');
}
/**
* Actions
*/
export const openExecutionPreviewNode = (name: string) =>
getExecutionPreviewBodyNodesByName(name).dblclick();

View file

@ -48,10 +48,38 @@ export function getOutputTableRow(row: number) {
return getOutputTableRows().eq(row);
}
export function getOutputTableHeaders() {
return getOutputPanelDataContainer().find('table thead th');
}
export function getOutputTableHeaderByText(text: string) {
return getOutputTableHeaders().contains(text);
}
export function getOutputTbodyCell(row: number, col: number) {
return getOutputTableRows().eq(row).find('td').eq(col);
}
export function getOutputRunSelector() {
return getOutputPanel().findChildByTestId('run-selector');
}
export function getOutputRunSelectorInput() {
return getOutputRunSelector().find('input');
}
export function getOutputPanelTable() {
return getOutputPanelDataContainer().get('table');
}
export function getOutputPanelItemsCount() {
return getOutputPanel().getByTestId('ndv-items-count');
}
export function getOutputPanelRelatedExecutionLink() {
return getOutputPanel().getByTestId('related-execution-link');
}
/**
* Actions
*/
@ -90,3 +118,8 @@ export function setParameterSelectByContent(name: string, content: string) {
getParameterInputByName(name).realClick();
getVisibleSelect().find('.option-headline').contains(content).click();
}
export function changeOutputRunSelector(runName: string) {
getOutputRunSelector().click();
getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click();
}

View file

@ -76,6 +76,14 @@ export function getCanvasNodes() {
);
}
export function getSaveButton() {
return cy.getByTestId('workflow-save-button');
}
export function getZoomToFitButton() {
return cy.getByTestId('zoom-to-fit');
}
/**
* Actions
*/
@ -170,3 +178,19 @@ export function clickManualChatButton() {
export function openNode(nodeName: string) {
getNodeByName(nodeName).dblclick();
}
export function saveWorkflowOnButtonClick() {
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
getSaveButton().should('contain', 'Save');
getSaveButton().click();
getSaveButton().should('contain', 'Saved');
cy.url().should('not.have.string', '/new');
}
export function pasteWorkflow(workflow: object) {
cy.get('body').paste(JSON.stringify(workflow));
}
export function clickZoomToFit() {
getZoomToFitButton().click();
}

View file

@ -0,0 +1,140 @@
import {
getExecutionPreviewOutputPanelRelatedExecutionLink,
getExecutionsSidebar,
getWorkflowExecutionPreviewIframe,
openExecutionPreviewNode,
} from '../composables/executions';
import {
changeOutputRunSelector,
getOutputPanelItemsCount,
getOutputPanelRelatedExecutionLink,
getOutputRunSelectorInput,
getOutputTableHeaders,
getOutputTableRows,
getOutputTbodyCell,
} from '../composables/ndv';
import {
clickExecuteWorkflowButton,
clickZoomToFit,
getCanvasNodes,
navigateToNewWorkflowPage,
openNode,
pasteWorkflow,
saveWorkflowOnButtonClick,
} from '../composables/workflow';
import SUBWORKFLOW_DEBUGGING_EXAMPLE from '../fixtures/Subworkflow-debugging-execute-workflow.json';
describe('Subworkflow debugging', () => {
beforeEach(() => {
navigateToNewWorkflowPage();
pasteWorkflow(SUBWORKFLOW_DEBUGGING_EXAMPLE);
saveWorkflowOnButtonClick();
getCanvasNodes().should('have.length', 11);
clickZoomToFit();
clickExecuteWorkflowButton();
});
describe('can inspect sub executed workflow', () => {
it('(Run once with all items/ Wait for Sub-workflow completion) (default behavior)', () => {
openNode('Execute Workflow with param');
getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution');
getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution');
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
// ensure workflow executed and waited on output
getOutputTableHeaders().should('have.length', 2);
getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore');
});
it('(Run once for each item/ Wait for Sub-workflow completion)', () => {
openNode('Execute Workflow with param1');
getOutputPanelItemsCount().should('contain.text', '2 items, 2 sub-execution');
getOutputPanelRelatedExecutionLink().should('not.exist');
// ensure workflow executed and waited on output
getOutputTableHeaders().should('have.length', 3);
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
getOutputTbodyCell(1, 1).should('have.text', 'world Natalie Moore');
});
it('(Run once with all items/ Wait for Sub-workflow completion)', () => {
openNode('Execute Workflow with param2');
getOutputPanelItemsCount().should('not.exist');
getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution');
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
// ensure workflow executed but returned same data as input
getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 1 sub-execution)');
getOutputTableHeaders().should('have.length', 6);
getOutputTableHeaders().eq(0).should('have.text', 'uid');
getOutputTableRows().should('have.length', 4);
getOutputTbodyCell(1, 1).should('include.text', 'Jon_Ebert@yahoo.com');
changeOutputRunSelector('1 of 2 (2 items, 1 sub-execution)');
getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 1 sub-execution)');
getOutputTableHeaders().should('have.length', 6);
getOutputTableHeaders().eq(0).should('have.text', 'uid');
getOutputTableRows().should('have.length', 3);
getOutputTbodyCell(1, 1).should('include.text', 'Terry.Dach@hotmail.com');
});
it('(Run once for each item/ Wait for Sub-workflow completion)', () => {
openNode('Execute Workflow with param3');
// ensure workflow executed but returned same data as input
getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 3 sub-executions)');
getOutputTableHeaders().should('have.length', 7);
getOutputTableHeaders().eq(1).should('have.text', 'uid');
getOutputTableRows().should('have.length', 4);
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
getOutputTbodyCell(1, 2).should('include.text', 'Jon_Ebert@yahoo.com');
changeOutputRunSelector('1 of 2 (2 items, 2 sub-executions)');
getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 2 sub-executions)');
getOutputTableHeaders().should('have.length', 7);
getOutputTableHeaders().eq(1).should('have.text', 'uid');
getOutputTableRows().should('have.length', 3);
getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href');
getOutputTbodyCell(1, 2).should('include.text', 'Terry.Dach@hotmail.com');
});
});
it('can inspect parent executions', () => {
cy.url().then((workflowUrl) => {
openNode('Execute Workflow with param');
getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution');
getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution');
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
// ensure workflow executed and waited on output
getOutputTableHeaders().should('have.length', 2);
getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore');
// cypress cannot handle new tabs so removing it
getOutputPanelRelatedExecutionLink().invoke('removeAttr', 'target').click();
getExecutionsSidebar().should('be.visible');
getWorkflowExecutionPreviewIframe().should('be.visible');
openExecutionPreviewNode('Execute Workflow Trigger');
getExecutionPreviewOutputPanelRelatedExecutionLink().should(
'include.text',
'Inspect Parent Execution',
);
getExecutionPreviewOutputPanelRelatedExecutionLink()
.invoke('removeAttr', 'target')
.click({ force: true });
cy.url().then((currentUrl) => {
expect(currentUrl === workflowUrl);
});
});
});
});

View file

@ -0,0 +1,354 @@
{
"meta": {
"instanceId": "08ce71ad998aeaade0abedb8dd96153d8eaa03fcb84cfccc1530095bf9ee478e"
},
"nodes": [
{
"parameters": {},
"id": "4535ce3e-280e-49b0-8854-373472ec86d1",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [80, 860]
},
{
"parameters": {
"category": "randomData",
"randomDataSeed": "0",
"randomDataCount": 2
},
"id": "d7fba18a-d51f-4509-af45-68cd9425ac6b",
"name": "DebugHelper1",
"type": "n8n-nodes-base.debugHelper",
"typeVersion": 1,
"position": [280, 860]
},
{
"parameters": {
"source": "parameter",
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
"mode": "each",
"options": {
"waitForSubWorkflow": false
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [680, 1540],
"id": "f90a25da-dd89-4bf8-8f5b-bf8ee1de0b70",
"name": "Execute Workflow with param3"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c93f26bd-3489-467b-909e-6462e1463707",
"name": "uid",
"value": "={{ $json.uid }}",
"type": "string"
},
{
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
"name": "email",
"value": "={{ $json.email }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [900, 1540],
"id": "3be57648-3be8-4b0f-abfa-8fdcafee804d",
"name": "Edit Fields8"
},
{
"parameters": {
"source": "parameter",
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
"options": {
"waitForSubWorkflow": false
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [620, 1220],
"id": "dabc2356-3660-4d17-b305-936a002029ba",
"name": "Execute Workflow with param2"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c93f26bd-3489-467b-909e-6462e1463707",
"name": "uid",
"value": "={{ $json.uid }}",
"type": "string"
},
{
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
"name": "email",
"value": "={{ $json.email }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [840, 1220],
"id": "9d2a9dda-e2a1-43e8-a66f-a8a555692e5f",
"name": "Edit Fields7"
},
{
"parameters": {
"source": "parameter",
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [560, 900],
"id": "07e47f60-622a-484c-ab24-35f6f2280595",
"name": "Execute Workflow with param1"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c93f26bd-3489-467b-909e-6462e1463707",
"name": "uid",
"value": "={{ $json.uid }}",
"type": "string"
},
{
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
"name": "email",
"value": "={{ $json.email }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [760, 900],
"id": "80563d0a-0bab-444f-a04c-4041a505d78b",
"name": "Edit Fields6"
},
{
"parameters": {
"source": "parameter",
"workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [560, 580],
"id": "f04af481-f4d9-4d91-a60a-a377580e8393",
"name": "Execute Workflow with param"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c93f26bd-3489-467b-909e-6462e1463707",
"name": "uid",
"value": "={{ $json.uid }}",
"type": "string"
},
{
"id": "3dd706ce-d925-4219-8531-ad12369972fe",
"name": "email",
"value": "={{ $json.email }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [760, 580],
"id": "80c10607-a0ac-4090-86a1-890da0a2aa52",
"name": "Edit Fields2"
},
{
"parameters": {
"content": "## Execute Workflow (Run once with all items/ DONT Wait for Sub-workflow completion)",
"height": 254.84308966329985,
"width": 457.58120569815793
},
"id": "534ef523-3453-4a16-9ff0-8ac9f025d47d",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [500, 1080]
},
{
"parameters": {
"content": "## Execute Workflow (Run once with for each item/ DONT Wait for Sub-workflow completion) ",
"height": 284.59778445962905,
"width": 457.58120569815793
},
"id": "838f0fa3-5ee4-4d1a-afb8-42e009f1aa9e",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [580, 1400]
},
{
"parameters": {
"category": "randomData",
"randomDataSeed": "1",
"randomDataCount": 3
},
"id": "86699a49-2aa7-488e-8ea9-828404c98f08",
"name": "DebugHelper",
"type": "n8n-nodes-base.debugHelper",
"typeVersion": 1,
"position": [320, 1120]
},
{
"parameters": {
"content": "## Execute Workflow (Run once with for each item/ Wait for Sub-workflow completion) ",
"height": 284.59778445962905,
"width": 457.58120569815793
},
"id": "885d35f0-8ae6-45ec-821b-a82c27e7577a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [480, 760]
},
{
"parameters": {
"content": "## Execute Workflow (Run once with all items/ Wait for Sub-workflow completion) (default behavior)",
"height": 254.84308966329985,
"width": 457.58120569815793
},
"id": "505bd7f2-767e-41b8-9325-77300aed5883",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [460, 460]
}
],
"connections": {
"When clicking Test workflow": {
"main": [
[
{
"node": "DebugHelper1",
"type": "main",
"index": 0
},
{
"node": "DebugHelper",
"type": "main",
"index": 0
}
]
]
},
"DebugHelper1": {
"main": [
[
{
"node": "Execute Workflow with param3",
"type": "main",
"index": 0
},
{
"node": "Execute Workflow with param2",
"type": "main",
"index": 0
},
{
"node": "Execute Workflow with param1",
"type": "main",
"index": 0
},
{
"node": "Execute Workflow with param",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow with param3": {
"main": [
[
{
"node": "Edit Fields8",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow with param2": {
"main": [
[
{
"node": "Edit Fields7",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow with param1": {
"main": [
[
{
"node": "Edit Fields6",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow with param": {
"main": [
[
{
"node": "Edit Fields2",
"type": "main",
"index": 0
}
]
]
},
"DebugHelper": {
"main": [
[
{
"node": "Execute Workflow with param2",
"type": "main",
"index": 0
},
{
"node": "Execute Workflow with param3",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {}
}

View file

@ -12,8 +12,11 @@ import type { INodeExecutionData, ITaskData, ITaskMetadata } from 'n8n-workflow'
import { setActivePinia } from 'pinia';
import { useNodeTypesStore } from '../stores/nodeTypes.store';
const { openRelatedExecution } = vi.hoisted(() => ({
openRelatedExecution: vi.fn(),
const MOCK_EXECUTION_URL = 'execution.url/123';
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = vi.hoisted(() => ({
trackOpeningRelatedExecution: vi.fn(),
resolveRelatedExecutionUrl: vi.fn(),
}));
vi.mock('vue-router', () => {
@ -26,7 +29,8 @@ vi.mock('vue-router', () => {
vi.mock('@/composables/useExecutionHelpers', () => ({
useExecutionHelpers: () => ({
openRelatedExecution,
trackOpeningRelatedExecution,
resolveRelatedExecutionUrl,
}),
}));
@ -42,6 +46,10 @@ const nodes = [
] as INodeUi[];
describe('RunData', () => {
beforeAll(() => {
resolveRelatedExecutionUrl.mockReturnValue('execution.url/123');
});
it("should render pin button in output panel disabled when there's binary data", () => {
const { getByTestId } = render({
defaultRunItems: [
@ -253,11 +261,13 @@ describe('RunData', () => {
expect(getByTestId('related-execution-link')).toBeInTheDocument();
expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Sub-Execution 123');
expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata);
expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL);
expect(getByTestId('ndv-items-count')).toHaveTextContent('1 item, 1 sub-execution');
getByTestId('related-execution-link').click();
expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'table');
expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'table');
});
it('should render parent-execution link in header', async () => {
@ -280,11 +290,13 @@ describe('RunData', () => {
expect(getByTestId('related-execution-link')).toBeInTheDocument();
expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Parent Execution 123');
expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata);
expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL);
expect(getByTestId('ndv-items-count')).toHaveTextContent('1 item');
getByTestId('related-execution-link').click();
expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'table');
expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'table');
});
it('should render sub-execution link in header with multiple items', async () => {
@ -311,11 +323,13 @@ describe('RunData', () => {
expect(getByTestId('related-execution-link')).toBeInTheDocument();
expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Sub-Execution 123');
expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata);
expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL);
expect(getByTestId('ndv-items-count')).toHaveTextContent('2 items, 3 sub-executions');
getByTestId('related-execution-link').click();
expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'json');
expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'json');
});
it('should render sub-execution link in header with multiple runs', async () => {
@ -359,7 +373,7 @@ describe('RunData', () => {
expect(getByTestId('run-selector')).toBeInTheDocument();
getByTestId('related-execution-link').click();
expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'json');
expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'json');
});
const render = ({

View file

@ -182,7 +182,7 @@ const nodeHelpers = useNodeHelpers();
const externalHooks = useExternalHooks();
const telemetry = useTelemetry();
const i18n = useI18n();
const { openRelatedExecution } = useExecutionHelpers();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const node = toRef(props, 'node');
@ -1414,7 +1414,9 @@ defineExpose({ enterEditMode });
"
:class="$style.relatedExecutionInfo"
data-test-id="related-execution-link"
@click.stop="openRelatedExecution(activeTaskMetadata, displayMode)"
:href="resolveRelatedExecutionUrl(activeTaskMetadata)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ getExecutionLinkLabel(activeTaskMetadata) }}
@ -1496,7 +1498,9 @@ defineExpose({ enterEditMode });
"
:class="$style.relatedExecutionInfo"
data-test-id="related-execution-link"
@click.stop="openRelatedExecution(activeTaskMetadata, displayMode)"
:href="resolveRelatedExecutionUrl(activeTaskMetadata)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ getExecutionLinkLabel(activeTaskMetadata) }}

View file

@ -32,7 +32,7 @@ const props = defineProps<{
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const { openRelatedExecution } = useExecutionHelpers();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
type TokenUsageData = {
completionTokens: number;
@ -140,7 +140,11 @@ const outputError = computed(() => {
</n8n-tooltip>
</li>
<li v-if="runMeta?.subExecution">
<a @click.stop="openRelatedExecution(runMeta, 'ai')">
<a
:href="resolveRelatedExecutionUrl(runMeta)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(runMeta, 'ai')"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{
$locale.baseText('runData.openSubExecution', {

View file

@ -64,7 +64,7 @@ const workflowsStore = useWorkflowsStore();
const i18n = useI18n();
const telemetry = useTelemetry();
const { openRelatedExecution } = useExecutionHelpers();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const {
hoveringItem,
@ -455,7 +455,9 @@ watch(focusedMappableInput, (curr) => {
icon="external-link-alt"
data-test-id="debug-sub-execution"
size="mini"
@click="openRelatedExecution(tableData.metadata.data[index1], 'table')"
:href="resolveRelatedExecutionUrl(tableData.metadata.data[index1])"
target="_blank"
@click="trackOpeningRelatedExecution(tableData.metadata.data[index1], 'table')"
/>
</N8nTooltip>
</td>
@ -582,15 +584,20 @@ watch(focusedMappableInput, (curr) => {
placement="left"
:hide-after="0"
>
<N8nIconButton
<a
v-if="tableData.metadata.data[index1]"
v-show="showExecutionLink(index1)"
type="secondary"
icon="external-link-alt"
data-test-id="debug-sub-execution"
size="mini"
@click="openRelatedExecution(tableData.metadata.data[index1], 'table')"
/>
:href="resolveRelatedExecutionUrl(tableData.metadata.data[index1])"
target="_blank"
@click="trackOpeningRelatedExecution(tableData.metadata.data[index1], 'table')"
>
<N8nIconButton
type="secondary"
icon="external-link-alt"
data-test-id="debug-sub-execution"
size="mini"
/>
</a>
</N8nTooltip>
</td>
<td

View file

@ -84,7 +84,24 @@ export function useExecutionHelpers() {
window.open(route.href, '_blank');
}
function openRelatedExecution(
function resolveRelatedExecutionUrl(metadata: {
parentExecution?: RelatedExecution;
subExecution?: RelatedExecution;
}): string {
const info = metadata.parentExecution || metadata.subExecution;
if (!info) {
return '';
}
const { workflowId, executionId } = info;
return router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflowId, executionId },
}).fullPath;
}
function trackOpeningRelatedExecution(
metadata: { parentExecution?: RelatedExecution; subExecution?: RelatedExecution },
view: IRunDataDisplayMode,
) {
@ -93,8 +110,6 @@ export function useExecutionHelpers() {
return;
}
openExecutionInNewTab(info.executionId, info.workflowId);
telemetry.track(
metadata.parentExecution
? 'User clicked parent execution button'
@ -110,6 +125,7 @@ export function useExecutionHelpers() {
formatDate,
isExecutionRetriable,
openExecutionInNewTab,
openRelatedExecution,
trackOpeningRelatedExecution,
resolveRelatedExecutionUrl,
};
}