mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
test: Create addNode convenience method (#18750)
This commit is contained in:
@@ -57,36 +57,63 @@ export class CanvasPage extends BasePage {
|
||||
await this.nodeCreatorItemByName(text).click();
|
||||
}
|
||||
|
||||
async addNode(text: string): Promise<void> {
|
||||
/**
|
||||
* Add a node to the canvas with flexible options
|
||||
* @param nodeName - The name of the node to search for and add
|
||||
* @param options - Configuration options for node addition
|
||||
* @param options.closeNDV - Whether to close the NDV after adding (default: false, keeps open)
|
||||
* @param options.action - Specific action to select (Actions tab is default)
|
||||
* @param options.trigger - Specific trigger to select (will switch to Triggers)
|
||||
* @example
|
||||
* // Basic node addition
|
||||
* await canvas.addNode('Code');
|
||||
*
|
||||
* // Add with specific action
|
||||
* await canvas.addNode('Linear', { action: 'Create an issue' });
|
||||
*
|
||||
* // Add with trigger
|
||||
* await canvas.addNode('Jira', { trigger: 'On issue created' });
|
||||
*
|
||||
* // Add and explicitly close with back button
|
||||
* await canvas.addNode('Code', { closeNDV: true });
|
||||
*/
|
||||
async addNode(
|
||||
nodeName: string,
|
||||
options?: {
|
||||
closeNDV?: boolean;
|
||||
action?: string;
|
||||
trigger?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
// Always start with canvas plus button
|
||||
await this.clickNodeCreatorPlusButton();
|
||||
await this.fillNodeCreatorSearchBar(text);
|
||||
await this.clickNodeCreatorItemName(text);
|
||||
}
|
||||
|
||||
async addNodeAndCloseNDV(text: string, subItemText?: string): Promise<void> {
|
||||
if (subItemText) {
|
||||
await this.addNodeWithSubItem(text, subItemText);
|
||||
} else {
|
||||
await this.addNode(text);
|
||||
// Search for and select the node, works on exact name match only
|
||||
await this.fillNodeCreatorSearchBar(nodeName);
|
||||
await this.clickNodeCreatorItemName(nodeName);
|
||||
|
||||
if (options?.action) {
|
||||
// Check if Actions category is collapsed and expand if needed
|
||||
const actionsCategory = this.page
|
||||
.getByTestId('node-creator-category-item')
|
||||
.getByText('Actions');
|
||||
if ((await actionsCategory.getAttribute('data-category-collapsed')) === 'true') {
|
||||
await actionsCategory.click();
|
||||
}
|
||||
await this.nodeCreatorSubItem(options.action).click();
|
||||
} else if (options?.trigger) {
|
||||
// Check if Triggers category is collapsed and expand if needed
|
||||
const triggersCategory = this.page
|
||||
.getByTestId('node-creator-category-item')
|
||||
.getByText('Triggers');
|
||||
if ((await triggersCategory.getAttribute('data-category-collapsed')) === 'true') {
|
||||
await triggersCategory.click();
|
||||
}
|
||||
await this.nodeCreatorSubItem(options.trigger).click();
|
||||
}
|
||||
if (options?.closeNDV) {
|
||||
await this.page.getByTestId('back-to-canvas').click();
|
||||
}
|
||||
await this.page.keyboard.press('Escape');
|
||||
}
|
||||
|
||||
async addNodeWithSubItem(searchText: string, subItemText: string): Promise<void> {
|
||||
await this.addNode(searchText);
|
||||
await this.nodeCreatorSubItem(subItemText).click();
|
||||
}
|
||||
|
||||
async addActionNode(searchText: string, subItemText: string): Promise<void> {
|
||||
await this.addNode(searchText);
|
||||
await this.page.getByText('Actions').click();
|
||||
await this.nodeCreatorSubItem(subItemText).click();
|
||||
}
|
||||
|
||||
async addTriggerNode(searchText: string, subItemText: string): Promise<void> {
|
||||
await this.addNode(searchText);
|
||||
await this.page.getByText('Triggers').click();
|
||||
await this.nodeCreatorSubItem(subItemText).click();
|
||||
}
|
||||
|
||||
async deleteNodeByName(nodeName: string): Promise<void> {
|
||||
@@ -301,7 +328,7 @@ export class CanvasPage extends BasePage {
|
||||
}
|
||||
|
||||
nodeCreatorNodeItems(): Locator {
|
||||
return this.page.getByTestId('node-creator-node-item');
|
||||
return this.page.getByTestId('node-creator-item-name');
|
||||
}
|
||||
|
||||
nodeCreatorActionItems(): Locator {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { MANUAL_TRIGGER_NODE_NAME, MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../config/constants';
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Canvas Node Actions', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
});
|
||||
|
||||
test.describe('Node Search and Add', () => {
|
||||
test('should search and add a basic node', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
await expect(n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should search and add Linear node with action', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode('Linear', { action: 'Create an issue' });
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||
await expect(n8n.canvas.nodeByName('Create an issue')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should search and add Webhook node (no actions)', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode('Webhook');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
await expect(n8n.canvas.nodeByName('Webhook')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should search and add Jira node with trigger', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode('Jira Software', { trigger: 'On issue created' });
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
await expect(n8n.canvas.nodeByName('Jira Trigger')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should clear search and show all nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await n8n.canvas.fillNodeCreatorSearchBar('Linear');
|
||||
const searchCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
await expect(n8n.canvas.nodeCreatorNodeItems()).toHaveCount(1);
|
||||
|
||||
await n8n.canvas.nodeCreatorSearchBar().clear();
|
||||
const nodeCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
expect(nodeCount).toBeGreaterThan(searchCount);
|
||||
});
|
||||
|
||||
test('should add connected node via plus endpoint', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.fillNodeCreatorSearchBar('Code');
|
||||
await n8n.page.keyboard.press('Enter');
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should add disconnected node when nothing selected', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.deselectAll();
|
||||
await n8n.canvas.addNode('Code', { closeNDV: true });
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Node Creator Interactions', () => {
|
||||
test('should close node creator with escape key', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await expect(n8n.canvas.nodeCreatorSearchBar()).toBeVisible();
|
||||
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
await expect(n8n.canvas.nodeCreatorSearchBar()).toBeHidden();
|
||||
});
|
||||
|
||||
test('should filter nodes by search term', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
const initialCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
await n8n.canvas.fillNodeCreatorSearchBar('HTTP');
|
||||
const filteredCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
|
||||
expect(filteredCount).toBeLessThan(initialCount);
|
||||
expect(filteredCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -61,7 +61,7 @@ test.describe('Canvas Actions', () => {
|
||||
test('should add disconnected node if nothing is selected', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.deselectAll();
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||
@@ -70,7 +70,7 @@ test.describe('Canvas Actions', () => {
|
||||
test('should add node between two connected nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||
@@ -96,7 +96,7 @@ test.describe('Canvas Actions', () => {
|
||||
test('should delete connections by clicking on the delete button', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.deleteConnectionBetweenNodes(MANUAL_TRIGGER_NODE_DISPLAY_NAME, CODE_NODE_NAME);
|
||||
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
|
||||
@@ -116,7 +116,7 @@ test.describe('Canvas Actions', () => {
|
||||
|
||||
test('should disable and enable node', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
|
||||
const disableButton = n8n.canvas.nodeDisableButton(CODE_NODE_NAME);
|
||||
await disableButton.click();
|
||||
@@ -130,7 +130,7 @@ test.describe('Canvas Actions', () => {
|
||||
|
||||
test('should delete node', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.deleteNodeByName(CODE_NODE_NAME);
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
@@ -140,7 +140,7 @@ test.describe('Canvas Actions', () => {
|
||||
|
||||
test('should copy selected nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvasComposer.selectAllAndCopy();
|
||||
await n8n.canvas.nodeByName(CODE_NODE_NAME).click();
|
||||
await n8n.canvasComposer.copySelectedNodesWithToast();
|
||||
@@ -150,7 +150,7 @@ test.describe('Canvas Actions', () => {
|
||||
|
||||
test('should select/deselect all nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.selectAll();
|
||||
|
||||
await expect(n8n.canvas.selectedNodes()).toHaveCount(2);
|
||||
@@ -162,7 +162,7 @@ test.describe('Canvas Actions', () => {
|
||||
test('should select nodes using arrow keys', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.getCanvasNodes().first().waitFor();
|
||||
await n8n.canvas.navigateNodesWithArrows('left');
|
||||
|
||||
@@ -177,7 +177,7 @@ test.describe('Canvas Actions', () => {
|
||||
test('should select nodes using shift and arrow keys', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
await n8n.canvas.addNodeAndCloseNDV(CODE_NODE_NAME);
|
||||
await n8n.canvas.addNode(CODE_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.getCanvasNodes().first().waitFor();
|
||||
await n8n.canvas.extendSelectionWithArrows('left');
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ test.describe('Data pinning', () => {
|
||||
await n8n.ndv.setPinnedData([{ http: 123 }]);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNodeWithSubItem(NODES.PIPEDRIVE, 'Create an activity');
|
||||
await n8n.canvas.addNode(NODES.PIPEDRIVE, { action: 'Create an activity' });
|
||||
await n8n.ndv.setPinnedData(Array(3).fill({ pipedrive: 123 }));
|
||||
await n8n.ndv.close();
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ test.describe('Projects', () => {
|
||||
n8n.page.getByText('Workflow successfully created', { exact: false }),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.canvas.addNodeWithSubItem(EXECUTE_WORKFLOW_NODE_NAME, 'Execute A Sub Workflow');
|
||||
await n8n.canvas.addNode(EXECUTE_WORKFLOW_NODE_NAME, { action: 'Execute A Sub Workflow' });
|
||||
|
||||
const subWorkflowPagePromise = n8n.page.waitForEvent('popup');
|
||||
|
||||
@@ -74,7 +74,7 @@ test.describe('Projects', () => {
|
||||
await subn8n.ndv.clickBackToCanvasButton();
|
||||
|
||||
await subn8n.canvas.deleteNodeByName('Replace me with your logic');
|
||||
await subn8n.canvas.addNodeWithSubItem(NOTION_NODE_NAME, 'Append a block');
|
||||
await subn8n.canvas.addNode(NOTION_NODE_NAME, { action: 'Append a block' });
|
||||
|
||||
await subn8n.credentials.createAndSaveNewCredential('apiKey', NOTION_API_KEY);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
test('should show suggested actions automatically when workflow is first activated', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
|
||||
await expect(n8n.canvas.getProductionChecklistButton()).toBeHidden();
|
||||
@@ -34,8 +34,8 @@ test.describe('Workflow Production Checklist', () => {
|
||||
}) => {
|
||||
await api.enableFeature('evaluation');
|
||||
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNodeAndCloseNDV('OpenAI', 'Create an assistant');
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.addNode('OpenAI', { action: 'Create an assistant', closeNDV: true });
|
||||
|
||||
await n8n.canvas.nodeDisableButton('Create an assistant').click();
|
||||
|
||||
@@ -56,7 +56,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
test('should open workflow settings modal when error workflow action is clicked', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
@@ -73,7 +73,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
});
|
||||
|
||||
test('should open workflow settings modal when time saved action is clicked', async ({ n8n }) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
@@ -89,7 +89,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
});
|
||||
|
||||
test('should allow ignoring individual actions', async ({ n8n }) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
@@ -109,7 +109,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
});
|
||||
|
||||
test('should show completed state for configured actions', async ({ n8n }) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
@@ -134,7 +134,7 @@ test.describe('Workflow Production Checklist', () => {
|
||||
});
|
||||
|
||||
test('should allow ignoring all actions with confirmation', async ({ n8n }) => {
|
||||
await n8n.canvas.addNodeAndCloseNDV(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.saveWorkflow();
|
||||
await n8n.canvas.activateWorkflow();
|
||||
await expect(n8n.workflowActivationModal.getModal()).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user