Skip to content

Commit 68693b5

Browse files
authored
test: Split workflow-actions.spec.ts into focused test files (#22850)
1 parent d3e7713 commit 68693b5

File tree

8 files changed

+634
-581
lines changed

8 files changed

+634
-581
lines changed

packages/testing/playwright/tests/ui/workflows/editor/workflow-actions.spec.ts

Lines changed: 0 additions & 581 deletions
This file was deleted.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { SCHEDULE_TRIGGER_NODE_NAME } from '../../../../../config/constants';
2+
import { test, expect } from '../../../../../fixtures/base';
3+
import type { n8nPage } from '../../../../../pages/n8nPage';
4+
5+
async function saveWorkflowAndGetId(n8n: n8nPage): Promise<string> {
6+
const saveResponsePromise = n8n.page.waitForResponse(
7+
(response) =>
8+
response.url().includes('/rest/workflows') &&
9+
(response.request().method() === 'POST' || response.request().method() === 'PATCH'),
10+
);
11+
await n8n.canvas.saveWorkflow();
12+
const saveResponse = await saveResponsePromise;
13+
const {
14+
data: { id },
15+
} = await saveResponse.json();
16+
return id;
17+
}
18+
19+
async function goToWorkflow(n8n: n8nPage, workflowId: string): Promise<void> {
20+
const loadResponsePromise = n8n.page.waitForResponse(
21+
(response) =>
22+
response.url().includes(`/rest/workflows/${workflowId}`) &&
23+
response.request().method() === 'GET' &&
24+
response.status() === 200,
25+
);
26+
await n8n.page.goto(`/workflow/${workflowId}`);
27+
await loadResponsePromise;
28+
}
29+
30+
// eslint-disable-next-line playwright/no-skipped-test
31+
test.skip('Workflow Archive', () => {
32+
test.beforeEach(async ({ n8n }) => {
33+
await n8n.start.fromBlankCanvas();
34+
});
35+
36+
test('should not be able to archive or delete unsaved workflow', async ({ n8n }) => {
37+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
38+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
39+
40+
await expect(n8n.workflowSettingsModal.getDeleteMenuItem()).toBeHidden();
41+
await expect(n8n.workflowSettingsModal.getArchiveMenuItem().locator('..')).toHaveClass(
42+
/is-disabled/,
43+
);
44+
});
45+
46+
test('should archive nonactive workflow and then delete it', async ({ n8n }) => {
47+
const workflowId = await saveWorkflowAndGetId(n8n);
48+
await expect(n8n.canvas.getArchivedTag()).not.toBeAttached();
49+
50+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
51+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
52+
53+
await n8n.workflowSettingsModal.clickArchiveMenuItem();
54+
55+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
56+
await expect(n8n.page).toHaveURL(/\/workflows$/);
57+
58+
await goToWorkflow(n8n, workflowId);
59+
60+
await expect(n8n.canvas.getArchivedTag()).toBeVisible();
61+
await expect(n8n.canvas.getNodeCreatorPlusButton()).not.toBeAttached();
62+
63+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
64+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
65+
await n8n.workflowSettingsModal.clickDeleteMenuItem();
66+
await n8n.workflowSettingsModal.confirmDeleteModal();
67+
68+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
69+
await expect(n8n.page).toHaveURL(/\/workflows$/);
70+
});
71+
72+
// eslint-disable-next-line n8n-local-rules/no-skipped-tests -- Flaky in multi-main mode
73+
test.skip('should archive published workflow and then delete it', async ({ n8n }) => {
74+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
75+
const workflowId = await saveWorkflowAndGetId(n8n);
76+
await n8n.canvas.publishWorkflow();
77+
await n8n.page.keyboard.press('Escape');
78+
79+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
80+
await expect(n8n.canvas.getArchivedTag()).not.toBeAttached();
81+
82+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
83+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
84+
await n8n.workflowSettingsModal.clickArchiveMenuItem();
85+
await n8n.workflowSettingsModal.confirmArchiveModal();
86+
87+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
88+
await expect(n8n.page).toHaveURL(/\/workflows$/);
89+
90+
await goToWorkflow(n8n, workflowId);
91+
92+
await expect(n8n.canvas.getArchivedTag()).toBeVisible();
93+
await expect(n8n.canvas.getNodeCreatorPlusButton()).not.toBeAttached();
94+
await expect(n8n.canvas.getPublishedIndicator()).not.toBeVisible();
95+
96+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
97+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
98+
await n8n.workflowSettingsModal.clickDeleteMenuItem();
99+
await n8n.workflowSettingsModal.confirmDeleteModal();
100+
101+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
102+
await expect(n8n.page).toHaveURL(/\/workflows$/);
103+
});
104+
105+
test('should archive nonactive workflow and then unarchive it', async ({ n8n }) => {
106+
const workflowId = await saveWorkflowAndGetId(n8n);
107+
await expect(n8n.canvas.getArchivedTag()).not.toBeAttached();
108+
109+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
110+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
111+
112+
await n8n.workflowSettingsModal.clickArchiveMenuItem();
113+
114+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
115+
await expect(n8n.page).toHaveURL(/\/workflows$/);
116+
117+
await goToWorkflow(n8n, workflowId);
118+
119+
await expect(n8n.canvas.getArchivedTag()).toBeVisible();
120+
await expect(n8n.canvas.getNodeCreatorPlusButton()).not.toBeAttached();
121+
122+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
123+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
124+
await n8n.workflowSettingsModal.clickUnarchiveMenuItem();
125+
126+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
127+
await expect(n8n.canvas.getArchivedTag()).not.toBeAttached();
128+
await expect(n8n.canvas.getNodeCreatorPlusButton()).toBeVisible();
129+
});
130+
131+
test('should not show unpublish menu item for non-published workflow', async ({ n8n }) => {
132+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
133+
await n8n.canvas.saveWorkflow();
134+
135+
await expect(n8n.canvas.getPublishedIndicator()).not.toBeVisible();
136+
137+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
138+
await expect(n8n.workflowSettingsModal.getUnpublishMenuItem()).not.toBeAttached();
139+
});
140+
141+
// TODO: flaky test - 18 similar failures across 10 branches in last 14 days
142+
test.skip('should unpublish a published workflow', async ({ n8n }) => {
143+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
144+
await n8n.canvas.publishWorkflow();
145+
await n8n.page.keyboard.press('Escape');
146+
147+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
148+
149+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
150+
await n8n.workflowSettingsModal.clickUnpublishMenuItem();
151+
152+
await expect(n8n.workflowSettingsModal.getUnpublishModal()).toBeVisible();
153+
await n8n.workflowSettingsModal.confirmUnpublishModal();
154+
155+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
156+
await expect(n8n.canvas.getPublishedIndicator()).not.toBeVisible();
157+
});
158+
159+
// eslint-disable-next-line n8n-local-rules/no-skipped-tests -- Flaky in multi-main mode
160+
test.skip('should unpublish published workflow on archive', async ({ n8n }) => {
161+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
162+
const workflowId = await saveWorkflowAndGetId(n8n);
163+
await n8n.canvas.publishWorkflow();
164+
await n8n.page.keyboard.press('Escape');
165+
166+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
167+
168+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
169+
await n8n.workflowSettingsModal.clickArchiveMenuItem();
170+
await n8n.workflowSettingsModal.confirmArchiveModal();
171+
172+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
173+
await expect(n8n.page).toHaveURL(/\/workflows$/);
174+
175+
await goToWorkflow(n8n, workflowId);
176+
177+
await expect(n8n.canvas.getArchivedTag()).toBeVisible();
178+
await expect(n8n.canvas.getPublishedIndicator()).not.toBeVisible();
179+
await expect(n8n.canvas.getPublishButton()).not.toBeVisible();
180+
181+
await expect(n8n.workflowSettingsModal.getWorkflowMenu()).toBeVisible();
182+
await n8n.workflowSettingsModal.getWorkflowMenu().click();
183+
await n8n.workflowSettingsModal.clickUnarchiveMenuItem();
184+
185+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
186+
await expect(n8n.canvas.getArchivedTag()).not.toBeAttached();
187+
188+
await n8n.canvas.publishWorkflow();
189+
await n8n.page.keyboard.press('Escape');
190+
191+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
192+
await expect(n8n.canvas.getOpenPublishModalButton()).toBeVisible();
193+
});
194+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import fs from 'fs';
2+
3+
import { CODE_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME } from '../../../../../config/constants';
4+
import { test, expect } from '../../../../../fixtures/base';
5+
import { resolveFromRoot } from '../../../../../utils/path-helper';
6+
7+
// eslint-disable-next-line playwright/no-skipped-test
8+
test.skip('Workflow Copy Paste', () => {
9+
test.beforeEach(async ({ n8n }) => {
10+
await n8n.start.fromBlankCanvas();
11+
});
12+
13+
test('should copy nodes', async ({ n8n }) => {
14+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
15+
await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
16+
17+
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
18+
19+
await expect(n8n.canvas.nodeCreator.getRoot()).not.toBeAttached();
20+
21+
await n8n.clipboard.grant();
22+
23+
await n8n.canvas.selectAll();
24+
await n8n.canvas.copyNodes();
25+
26+
await n8n.notifications.waitForNotificationAndClose('Copied to clipboard');
27+
const clipboardText = await n8n.clipboard.readText();
28+
const copiedWorkflow = JSON.parse(clipboardText);
29+
30+
expect(copiedWorkflow.nodes).toHaveLength(2);
31+
});
32+
33+
test('should paste nodes (both current and old node versions)', async ({ n8n }) => {
34+
const workflowJson = fs.readFileSync(
35+
resolveFromRoot('workflows', 'Test_workflow-actions_paste-data.json'),
36+
'utf-8',
37+
);
38+
39+
await n8n.canvas.canvasPane().click();
40+
await n8n.clipboard.paste(workflowJson);
41+
await n8n.canvas.clickZoomToFitButton();
42+
43+
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5);
44+
await expect(n8n.canvas.nodeConnections()).toHaveCount(5);
45+
});
46+
47+
test('should allow importing nodes without names', async ({ n8n }) => {
48+
const workflowJson = fs.readFileSync(
49+
resolveFromRoot('workflows', 'Test_workflow-actions_import_nodes_empty_name.json'),
50+
'utf-8',
51+
);
52+
53+
await n8n.canvas.canvasPane().click();
54+
await n8n.clipboard.paste(workflowJson);
55+
await n8n.canvas.clickZoomToFitButton();
56+
57+
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
58+
await expect(n8n.canvas.nodeConnections()).toHaveCount(2);
59+
60+
const nodes = n8n.canvas.getCanvasNodes();
61+
const count = await nodes.count();
62+
for (let i = 0; i < count; i++) {
63+
await expect(nodes.nth(i)).toHaveAttribute('data-node-name');
64+
}
65+
});
66+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { nanoid } from 'nanoid';
2+
3+
import { MANUAL_TRIGGER_NODE_NAME } from '../../../../../config/constants';
4+
import { test, expect } from '../../../../../fixtures/base';
5+
6+
// eslint-disable-next-line playwright/no-skipped-test
7+
test.skip('Workflow Duplicate', () => {
8+
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
9+
10+
test.beforeEach(async ({ n8n }) => {
11+
await n8n.start.fromBlankCanvas();
12+
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
13+
});
14+
15+
test('should duplicate unsaved workflow', async ({ n8n }) => {
16+
const uniqueTag = `Duplicate-${nanoid(6)}`;
17+
await n8n.workflowComposer.duplicateWorkflow(DUPLICATE_WORKFLOW_NAME, uniqueTag);
18+
19+
await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0);
20+
});
21+
22+
test('should duplicate saved workflow', async ({ n8n }) => {
23+
await n8n.canvas.saveWorkflow();
24+
await expect(n8n.canvas.getWorkflowSaveButton()).toContainText('Saved');
25+
26+
const uniqueTag = `Duplicate-${nanoid(6)}`;
27+
await n8n.workflowComposer.duplicateWorkflow(DUPLICATE_WORKFLOW_NAME, uniqueTag);
28+
29+
await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0);
30+
});
31+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
MANUAL_TRIGGER_NODE_NAME,
3+
NOTION_NODE_NAME,
4+
SCHEDULE_TRIGGER_NODE_NAME,
5+
} from '../../../../../config/constants';
6+
import { test, expect } from '../../../../../fixtures/base';
7+
8+
// eslint-disable-next-line playwright/no-skipped-test
9+
test.skip('Workflow Publish', () => {
10+
test.beforeEach(async ({ n8n }) => {
11+
await n8n.start.fromBlankCanvas();
12+
});
13+
14+
test('should not be able to publish workflow without trigger node', async ({ n8n }) => {
15+
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
16+
await n8n.canvas.getOpenPublishModalButton().click();
17+
18+
await expect(n8n.canvas.getPublishButton()).toBeDisabled();
19+
});
20+
21+
test('should be able to publish workflow', async ({ n8n }) => {
22+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
23+
await expect(n8n.canvas.getPublishedIndicator()).not.toBeVisible();
24+
25+
await n8n.canvas.publishWorkflow();
26+
27+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
28+
});
29+
30+
test('should not be able to publish workflow when nodes have errors', async ({ n8n }) => {
31+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
32+
await n8n.canvas.addNode(NOTION_NODE_NAME, { action: 'Append a block', closeNDV: true });
33+
await n8n.canvas.saveWorkflow();
34+
35+
await n8n.canvas.getOpenPublishModalButton().click();
36+
37+
await expect(n8n.canvas.getPublishButton()).toBeDisabled();
38+
39+
await expect(n8n.canvas.getPublishModalCallout()).toBeVisible();
40+
});
41+
42+
test('should be able to publish workflow when nodes with errors are disabled', async ({
43+
n8n,
44+
}) => {
45+
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
46+
await n8n.canvas.addNode(NOTION_NODE_NAME, { action: 'Append a block', closeNDV: true });
47+
await n8n.canvas.saveWorkflow();
48+
49+
await expect(n8n.notifications.getSuccessNotifications().first()).toBeVisible();
50+
51+
await n8n.canvas.getOpenPublishModalButton().click();
52+
53+
await expect(n8n.canvas.getPublishButton()).toBeDisabled();
54+
await n8n.canvas.cancelPublishWorkflowModal();
55+
56+
const nodeName = await n8n.canvas.getCanvasNodes().last().getAttribute('data-node-name');
57+
await n8n.canvas.toggleNodeEnabled(nodeName!);
58+
59+
await n8n.canvas.publishWorkflow();
60+
61+
await expect(n8n.canvas.getPublishedIndicator()).toBeVisible();
62+
});
63+
});

0 commit comments

Comments
 (0)