mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
test: Migrate data pinning tests from Cypress to Playwright (#18462)
This commit is contained in:
@@ -37,6 +37,10 @@ export class CanvasPage extends BasePage {
|
||||
await this.clickByTestId('canvas-plus-button');
|
||||
}
|
||||
|
||||
getCanvasNodes() {
|
||||
return this.page.getByTestId('canvas-node');
|
||||
}
|
||||
|
||||
async clickNodeCreatorPlusButton(): Promise<void> {
|
||||
await this.clickByTestId('node-creator-plus-button');
|
||||
}
|
||||
@@ -81,6 +85,10 @@ export class CanvasPage extends BasePage {
|
||||
await this.clickSaveWorkflowButton();
|
||||
}
|
||||
|
||||
getExecuteWorkflowButton(): Locator {
|
||||
return this.page.getByTestId('execute-workflow-button');
|
||||
}
|
||||
|
||||
async clickExecuteWorkflowButton(): Promise<void> {
|
||||
await this.page.getByTestId('execute-workflow-button').click();
|
||||
}
|
||||
@@ -256,6 +264,11 @@ export class CanvasPage extends BasePage {
|
||||
await this.getProductionChecklistActionItem(actionText).click();
|
||||
}
|
||||
|
||||
async duplicateNode(nodeName: string): Promise<void> {
|
||||
await this.nodeByName(nodeName).click({ button: 'right' });
|
||||
await this.page.getByTestId('context-menu').getByText('Duplicate').click();
|
||||
}
|
||||
|
||||
getCanvasNodes(): Locator {
|
||||
return this.page.getByTestId('canvas-node');
|
||||
}
|
||||
|
||||
@@ -44,10 +44,136 @@ export class NodeDisplayViewPage extends BasePage {
|
||||
return this.page.getByTestId('output-panel');
|
||||
}
|
||||
|
||||
getContainer() {
|
||||
return this.page.getByTestId('ndv');
|
||||
}
|
||||
|
||||
getParameterExpressionPreviewValue() {
|
||||
return this.page.getByTestId('parameter-expression-preview-value');
|
||||
}
|
||||
|
||||
getEditPinnedDataButton() {
|
||||
return this.page.getByTestId('ndv-edit-pinned-data');
|
||||
}
|
||||
|
||||
getPinDataButton() {
|
||||
return this.getOutputPanel().getByTestId('ndv-pin-data');
|
||||
}
|
||||
|
||||
getRunDataPaneHeader() {
|
||||
return this.page.getByTestId('run-data-pane-header');
|
||||
}
|
||||
|
||||
getOutputTable() {
|
||||
return this.getOutputPanel().getByTestId('ndv-data-container').locator('table');
|
||||
}
|
||||
|
||||
getOutputDataContainer() {
|
||||
return this.getOutputPanel().getByTestId('ndv-data-container');
|
||||
}
|
||||
|
||||
getOutputTableRows() {
|
||||
return this.getOutputTable().locator('tr');
|
||||
}
|
||||
|
||||
getOutputTableHeaders() {
|
||||
return this.getOutputTable().locator('thead th');
|
||||
}
|
||||
|
||||
getOutputTableRow(row: number) {
|
||||
return this.getOutputTableRows().nth(row);
|
||||
}
|
||||
|
||||
getOutputTableCell(row: number, col: number) {
|
||||
return this.getOutputTableRow(row).locator('td').nth(col);
|
||||
}
|
||||
|
||||
getOutputTbodyCell(row: number, col: number) {
|
||||
return this.getOutputTableRow(row).locator('td').nth(col);
|
||||
}
|
||||
|
||||
// Pin data operations
|
||||
async setPinnedData(data: object | string) {
|
||||
const pinnedData = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
await this.getEditPinnedDataButton().click();
|
||||
|
||||
// Wait for editor to appear and use broader selector
|
||||
const editor = this.getOutputPanel().locator('[contenteditable="true"]');
|
||||
await editor.waitFor();
|
||||
await editor.click();
|
||||
await editor.fill(pinnedData);
|
||||
|
||||
await this.savePinnedData();
|
||||
}
|
||||
|
||||
async pastePinnedData(data: object) {
|
||||
await this.getEditPinnedDataButton().click();
|
||||
|
||||
const editor = this.getOutputPanel().locator('[contenteditable="true"]');
|
||||
await editor.waitFor();
|
||||
await editor.click();
|
||||
await editor.fill('');
|
||||
|
||||
// Set clipboard data and paste
|
||||
await this.page.evaluate(async (jsonData) => {
|
||||
await navigator.clipboard.writeText(JSON.stringify(jsonData));
|
||||
}, data);
|
||||
await this.page.keyboard.press('ControlOrMeta+V');
|
||||
|
||||
await this.savePinnedData();
|
||||
}
|
||||
|
||||
async savePinnedData() {
|
||||
await this.getRunDataPaneHeader().locator('button:visible').filter({ hasText: 'Save' }).click();
|
||||
}
|
||||
|
||||
// Assignment collection methods for advanced tests
|
||||
getAssignmentCollectionAdd(paramName: string) {
|
||||
return this.page
|
||||
.getByTestId(`assignment-collection-${paramName}`)
|
||||
.getByTestId('assignment-collection-drop-area');
|
||||
}
|
||||
|
||||
getAssignmentValue(paramName: string) {
|
||||
return this.page
|
||||
.getByTestId(`assignment-collection-${paramName}`)
|
||||
.getByTestId('assignment-value');
|
||||
}
|
||||
|
||||
getInlineExpressionEditorInput() {
|
||||
return this.page.getByTestId('inline-expression-editor-input');
|
||||
}
|
||||
|
||||
getNodeParameters() {
|
||||
return this.page.getByTestId('node-parameters');
|
||||
}
|
||||
|
||||
getParameterInputHint() {
|
||||
return this.page.getByTestId('parameter-input-hint');
|
||||
}
|
||||
|
||||
async makeWebhookRequest(path: string) {
|
||||
return await this.page.request.get(path);
|
||||
}
|
||||
|
||||
getVisiblePoppers() {
|
||||
return this.page.locator('.el-popper:visible');
|
||||
}
|
||||
|
||||
async clearExpressionEditor() {
|
||||
const editor = this.getInlineExpressionEditorInput();
|
||||
await editor.click();
|
||||
await this.page.keyboard.press('ControlOrMeta+A');
|
||||
await this.page.keyboard.press('Delete');
|
||||
}
|
||||
|
||||
async typeInExpressionEditor(text: string) {
|
||||
const editor = this.getInlineExpressionEditorInput();
|
||||
await editor.click();
|
||||
// We have to use type() instead of fill() because the editor is a CodeMirror editor
|
||||
await editor.type(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter input by name (for Code node and similar)
|
||||
* @param parameterName - The name of the parameter e.g 'jsCode', 'mode'
|
||||
|
||||
@@ -8,16 +8,52 @@ export class NotificationsPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main container locator for a notification by its visible text.
|
||||
* Gets the main container locator for a notification by searching in its title text.
|
||||
* @param text The text or a regular expression to find within the notification's title.
|
||||
* @returns A Locator for the notification container element.
|
||||
*/
|
||||
notificationContainerByText(text: string | RegExp): Locator {
|
||||
getNotificationByTitle(text: string | RegExp): Locator {
|
||||
return this.page.getByRole('alert').filter({
|
||||
has: this.page.locator('.el-notification__title').filter({ hasText: text }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main container locator for a notification by searching in its content/body text.
|
||||
* This is useful for finding notifications where the detailed message is in the content
|
||||
* rather than the title (e.g., error messages with detailed descriptions).
|
||||
* @param text The text or a regular expression to find within the notification's content.
|
||||
* @returns A Locator for the notification container element.
|
||||
*/
|
||||
getNotificationByContent(text: string | RegExp): Locator {
|
||||
return this.page.getByRole('alert').filter({
|
||||
has: this.page.locator('.el-notification__content').filter({ hasText: text }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main container locator for a notification by searching in both title and content.
|
||||
* This is the most flexible method as it will find notifications regardless of whether
|
||||
* the text appears in the title or content section.
|
||||
* @param text The text or a regular expression to find within the notification's title or content.
|
||||
* @returns A Locator for the notification container element.
|
||||
*/
|
||||
getNotificationByTitleOrContent(text: string | RegExp): Locator {
|
||||
return this.page.getByRole('alert').filter({ hasText: text });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main container locator for a notification by searching in both title and content,
|
||||
* filtered to a specific node name. This is useful when multiple notifications might be present
|
||||
* and you want to ensure you're checking the right one for a specific node.
|
||||
* @param text The text or a regular expression to find within the notification's title or content.
|
||||
* @param nodeName The name of the node to filter notifications for.
|
||||
* @returns A Locator for the notification container element.
|
||||
*/
|
||||
getNotificationByTitleOrContentForNode(text: string | RegExp, nodeName: string): Locator {
|
||||
return this.page.getByRole('alert').filter({ hasText: text }).filter({ hasText: nodeName });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the close button on the FIRST notification matching the text.
|
||||
* Fast execution with short timeouts for snappy notifications.
|
||||
@@ -31,7 +67,7 @@ export class NotificationsPage {
|
||||
const { timeout = 2000 } = options;
|
||||
|
||||
try {
|
||||
const notification = this.notificationContainerByText(text).first();
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
await notification.waitFor({ state: 'visible', timeout });
|
||||
|
||||
const closeBtn = notification.locator('.el-notification__closeBtn');
|
||||
@@ -61,7 +97,7 @@ export class NotificationsPage {
|
||||
|
||||
while (retries < maxRetries) {
|
||||
try {
|
||||
const notifications = this.notificationContainerByText(text);
|
||||
const notifications = this.getNotificationByTitle(text);
|
||||
const count = await notifications.count();
|
||||
|
||||
if (count === 0) {
|
||||
@@ -105,7 +141,7 @@ export class NotificationsPage {
|
||||
const { timeout = 500 } = options;
|
||||
|
||||
try {
|
||||
const notification = this.notificationContainerByText(text).first();
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
await notification.waitFor({ state: 'visible', timeout });
|
||||
return true;
|
||||
} catch {
|
||||
@@ -126,7 +162,7 @@ export class NotificationsPage {
|
||||
const { timeout = 5000 } = options;
|
||||
|
||||
try {
|
||||
const notification = this.notificationContainerByText(text).first();
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
await notification.waitFor({ state: 'visible', timeout });
|
||||
return true;
|
||||
} catch {
|
||||
@@ -186,9 +222,7 @@ export class NotificationsPage {
|
||||
*/
|
||||
async getNotificationCount(text?: string | RegExp): Promise<number> {
|
||||
try {
|
||||
const notifications = text
|
||||
? this.notificationContainerByText(text)
|
||||
: this.page.getByRole('alert');
|
||||
const notifications = text ? this.getNotificationByTitle(text) : this.page.getByRole('alert');
|
||||
return await notifications.count();
|
||||
} catch {
|
||||
return 0;
|
||||
@@ -202,7 +236,7 @@ export class NotificationsPage {
|
||||
*/
|
||||
async quickClose(text: string | RegExp): Promise<void> {
|
||||
try {
|
||||
const notification = this.notificationContainerByText(text).first();
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
if (await notification.isVisible({ timeout: 100 })) {
|
||||
await notification.locator('.el-notification__closeBtn').click({ timeout: 200 });
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ test.describe('Workflows', () => {
|
||||
await n8n.canvas.setWorkflowName(workflowName);
|
||||
await n8n.canvas.clickSaveWorkflowButton();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.CREATED),
|
||||
).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.CREATED)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should search for workflows', async ({ n8n }) => {
|
||||
@@ -72,18 +70,14 @@ test.describe('Workflows', () => {
|
||||
|
||||
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
||||
await n8n.workflows.archiveWorkflow(workflow);
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.ARCHIVED),
|
||||
).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.ARCHIVED)).toBeVisible();
|
||||
|
||||
await expect(workflow).toBeHidden();
|
||||
await n8n.workflows.toggleShowArchived();
|
||||
await expect(workflow).toBeVisible();
|
||||
|
||||
await n8n.workflows.unarchiveWorkflow(workflow);
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.UNARCHIVED),
|
||||
).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.UNARCHIVED)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete an archived workflow', async ({ n8n }) => {
|
||||
@@ -95,16 +89,12 @@ test.describe('Workflows', () => {
|
||||
|
||||
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
||||
await n8n.workflows.archiveWorkflow(workflow);
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.ARCHIVED),
|
||||
).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.ARCHIVED)).toBeVisible();
|
||||
|
||||
await n8n.workflows.toggleShowArchived();
|
||||
|
||||
await n8n.workflows.deleteWorkflow(workflow);
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.DELETED),
|
||||
).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.DELETED)).toBeVisible();
|
||||
|
||||
await expect(workflow).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ test.describe('Canvas Actions', () => {
|
||||
await n8n.canvas.executeNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText('Node executed successfully'),
|
||||
n8n.notifications.getNotificationByTitle('Node executed successfully'),
|
||||
).toHaveCount(1);
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
});
|
||||
|
||||
234
packages/testing/playwright/tests/ui/13-pinning.spec.ts
Normal file
234
packages/testing/playwright/tests/ui/13-pinning.spec.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
import type { TestRequirements } from '../../Types';
|
||||
|
||||
const NODES = {
|
||||
MANUAL_TRIGGER: 'Manual Trigger',
|
||||
SCHEDULE_TRIGGER: 'Schedule Trigger',
|
||||
WEBHOOK: 'Webhook',
|
||||
HTTP_REQUEST: 'HTTP Request',
|
||||
PIPEDRIVE: 'Pipedrive',
|
||||
EDIT_FIELDS: 'Edit Fields (Set)', // Use the full node name that appears in the Node List, although when it's added to the canvas it's called "Edit Fields"
|
||||
CODE: 'Code',
|
||||
END: 'End',
|
||||
};
|
||||
|
||||
const webhookTestRequirements: TestRequirements = {
|
||||
workflow: {
|
||||
'Test_workflow_webhook_with_pin_data.json': 'Test',
|
||||
},
|
||||
};
|
||||
|
||||
const pinnedWebhookRequirements: TestRequirements = {
|
||||
workflow: {
|
||||
'Pinned_webhook_node.json': 'Test',
|
||||
},
|
||||
};
|
||||
|
||||
test.describe('Data pinning', () => {
|
||||
const maxPinnedDataSize = 16384;
|
||||
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
test.describe('Pin data operations', () => {
|
||||
test('should be able to pin node output', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.SCHEDULE_TRIGGER);
|
||||
|
||||
await n8n.ndv.execute();
|
||||
await expect(n8n.ndv.getOutputPanel()).toBeVisible();
|
||||
|
||||
const prevValue = await n8n.ndv.getOutputTbodyCell(1, 0).textContent();
|
||||
|
||||
await n8n.ndv.togglePinData();
|
||||
await n8n.ndv.close();
|
||||
|
||||
// Execute workflow and verify pinned data persists
|
||||
await n8n.canvas.clickExecuteWorkflowButton();
|
||||
await n8n.canvas.openNode(NODES.SCHEDULE_TRIGGER);
|
||||
|
||||
await expect(n8n.ndv.getOutputTbodyCell(1, 0)).toHaveText(prevValue ?? '');
|
||||
});
|
||||
|
||||
test('should be able to set custom pinned data', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.SCHEDULE_TRIGGER);
|
||||
|
||||
await expect(n8n.ndv.getEditPinnedDataButton()).toBeVisible();
|
||||
await expect(n8n.ndv.getPinDataButton()).toBeHidden();
|
||||
|
||||
await n8n.ndv.setPinnedData([{ test: 1 }]);
|
||||
|
||||
await expect(n8n.ndv.getOutputTableRows()).toHaveCount(2);
|
||||
await expect(n8n.ndv.getOutputTableHeaders()).toHaveCount(2);
|
||||
await expect(n8n.ndv.getOutputTableHeaders().first()).toContainText('test');
|
||||
await expect(n8n.ndv.getOutputTbodyCell(1, 0)).toContainText('1');
|
||||
|
||||
await n8n.ndv.close();
|
||||
await n8n.canvas.clickSaveWorkflowButton();
|
||||
await n8n.canvas.openNode(NODES.SCHEDULE_TRIGGER);
|
||||
|
||||
await expect(n8n.ndv.getOutputTableHeaders().first()).toContainText('test');
|
||||
await expect(n8n.ndv.getOutputTbodyCell(1, 0)).toContainText('1');
|
||||
});
|
||||
|
||||
test('should display pin data edit button for Webhook node', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.WEBHOOK);
|
||||
|
||||
const runDataHeader = n8n.ndv.getRunDataPaneHeader();
|
||||
const editButton = runDataHeader.getByRole('button', { name: 'Edit Output' });
|
||||
await expect(editButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('should duplicate pinned data when duplicating node', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.SCHEDULE_TRIGGER);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNode(NODES.EDIT_FIELDS);
|
||||
|
||||
await expect(n8n.ndv.getContainer()).toBeVisible();
|
||||
|
||||
await expect(n8n.ndv.getEditPinnedDataButton()).toBeVisible();
|
||||
await expect(n8n.ndv.getPinDataButton()).toBeHidden();
|
||||
|
||||
await n8n.ndv.setPinnedData([{ test: 1 }]);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.duplicateNode('Edit Fields');
|
||||
await n8n.canvas.clickSaveWorkflowButton();
|
||||
await n8n.canvas.openNode('Edit Fields1');
|
||||
|
||||
await expect(n8n.ndv.getOutputTableHeaders().first()).toContainText('test');
|
||||
await expect(n8n.ndv.getOutputTbodyCell(1, 0)).toContainText('1');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Error handling', () => {
|
||||
test('should show error when maximum pin data size is exceeded', async ({ n8n }) => {
|
||||
await n8n.page.evaluate((maxSize) => {
|
||||
(window as { maxPinnedDataSize?: number }).maxPinnedDataSize = maxSize;
|
||||
}, maxPinnedDataSize);
|
||||
|
||||
const actualMaxSize = await n8n.page.evaluate(() => {
|
||||
return (window as { maxPinnedDataSize?: number }).maxPinnedDataSize;
|
||||
});
|
||||
expect(actualMaxSize).toBe(maxPinnedDataSize);
|
||||
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.SCHEDULE_TRIGGER);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNode(NODES.EDIT_FIELDS);
|
||||
|
||||
await expect(n8n.ndv.getContainer()).toBeVisible();
|
||||
await expect(n8n.ndv.getEditPinnedDataButton()).toBeVisible();
|
||||
await expect(n8n.ndv.getPinDataButton()).toBeHidden();
|
||||
|
||||
const largeData = [{ test: '1'.repeat(maxPinnedDataSize + 1000) }];
|
||||
await n8n.ndv.setPinnedData(largeData);
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByContent(
|
||||
'Workflow has reached the maximum allowed pinned data size',
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error when pin data JSON is invalid', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.SCHEDULE_TRIGGER);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNode(NODES.EDIT_FIELDS);
|
||||
|
||||
await expect(n8n.ndv.getContainer()).toBeVisible();
|
||||
await expect(n8n.ndv.getEditPinnedDataButton()).toBeVisible();
|
||||
await expect(n8n.ndv.getPinDataButton()).toBeHidden();
|
||||
|
||||
await n8n.ndv.setPinnedData('[ { "name": "First item", "code": 2dsa }]');
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitle('Unable to save due to invalid JSON'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Advanced pinning scenarios', () => {
|
||||
test('should be able to reference paired items in node before pinned data', async ({ n8n }) => {
|
||||
await n8n.workflows.clickAddWorkflowButton();
|
||||
await n8n.canvas.addNode(NODES.MANUAL_TRIGGER);
|
||||
|
||||
await n8n.canvas.addNode(NODES.HTTP_REQUEST);
|
||||
await n8n.ndv.setPinnedData([{ http: 123 }]);
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNodeWithSubItem(NODES.PIPEDRIVE, 'Create an activity');
|
||||
await n8n.ndv.setPinnedData(Array(3).fill({ pipedrive: 123 }));
|
||||
await n8n.ndv.close();
|
||||
|
||||
await n8n.canvas.addNode(NODES.EDIT_FIELDS);
|
||||
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(n8n.ndv.getNodeParameters()).toBeVisible();
|
||||
|
||||
await expect(n8n.ndv.getAssignmentCollectionAdd('assignments')).toBeVisible();
|
||||
await n8n.ndv.getAssignmentCollectionAdd('assignments').click();
|
||||
await n8n.ndv.getAssignmentValue('assignments').getByText('Expression').click();
|
||||
|
||||
const expressionInput = n8n.ndv.getInlineExpressionEditorInput();
|
||||
await expressionInput.click();
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor(`{{ $('${NODES.HTTP_REQUEST}').item`);
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
const expectedOutput = '[Object: {"json": {"http": 123}, "pairedItem": {"item": 0}}]';
|
||||
await expect(n8n.ndv.getParameterInputHint().getByText(expectedOutput)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should use pin data in manual webhook executions', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(webhookTestRequirements);
|
||||
await n8n.canvas.clickExecuteWorkflowButton();
|
||||
await expect(n8n.canvas.getExecuteWorkflowButton()).toHaveText(
|
||||
'Waiting for trigger event from Webhook',
|
||||
);
|
||||
|
||||
const webhookPath = '/webhook-test/b0d79ddb-df2d-49b1-8555-9fa2b482608f';
|
||||
const response = await n8n.ndv.makeWebhookRequest(webhookPath);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
await n8n.canvas.openNode(NODES.END);
|
||||
|
||||
await expect(n8n.ndv.getOutputTableRow(1)).toBeVisible();
|
||||
await expect(n8n.ndv.getOutputTableRow(1)).toContainText('pin-overwritten');
|
||||
});
|
||||
|
||||
test('should not use pin data in production webhook executions', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements(webhookTestRequirements);
|
||||
await n8n.canvas.activateWorkflow();
|
||||
|
||||
const webhookUrl = '/webhook/b0d79ddb-df2d-49b1-8555-9fa2b482608f';
|
||||
const response = await n8n.ndv.makeWebhookRequest(webhookUrl);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const responseBody = await response.json();
|
||||
expect(responseBody).toEqual({ nodeData: 'pin' });
|
||||
});
|
||||
|
||||
test('should not show pinned data tooltip', async ({ n8n, setupRequirements }) => {
|
||||
await setupRequirements(pinnedWebhookRequirements);
|
||||
await n8n.canvas.clickExecuteWorkflowButton();
|
||||
|
||||
await n8n.canvas.getCanvasNodes().first().click();
|
||||
|
||||
const poppers = n8n.ndv.getVisiblePoppers();
|
||||
await expect(poppers).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -36,7 +36,7 @@ test.describe('Code node', () => {
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText('Node executed successfully').first(),
|
||||
n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.ndv.getParameterInput('mode').click();
|
||||
@@ -45,7 +45,7 @@ test.describe('Code node', () => {
|
||||
await n8n.ndv.execute();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText('Node executed successfully').first(),
|
||||
n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ test.describe('PDF Test', () => {
|
||||
await n8n.canvas.importWorkflow('test_pdf_workflow.json', 'PDF Workflow');
|
||||
await n8n.canvas.clickExecuteWorkflowButton();
|
||||
await expect(
|
||||
n8n.notifications.notificationContainerByText('Workflow executed successfully'),
|
||||
n8n.notifications.getNotificationByTitle('Workflow executed successfully'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,9 +129,7 @@ test.describe('Security Notifications', () => {
|
||||
await n8n.goHome();
|
||||
|
||||
// Verify security notification appears with default message
|
||||
const notification = n8n.notifications.notificationContainerByText(
|
||||
'Critical update available',
|
||||
);
|
||||
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||
await expect(notification).toBeVisible();
|
||||
await expect(notification).toContainText('Please update to latest version.');
|
||||
await expect(notification).toContainText('More info');
|
||||
@@ -153,7 +151,7 @@ test.describe('Security Notifications', () => {
|
||||
await n8n.goHome();
|
||||
|
||||
// Verify notification shows specific fix version (dynamically generated)
|
||||
const notificationWithFixVersion = n8n.notifications.notificationContainerByText(
|
||||
const notificationWithFixVersion = n8n.notifications.getNotificationByTitle(
|
||||
'Critical update available',
|
||||
);
|
||||
await expect(notificationWithFixVersion).toBeVisible();
|
||||
@@ -174,9 +172,7 @@ test.describe('Security Notifications', () => {
|
||||
await n8n.goHome();
|
||||
|
||||
// Wait for and click the security notification
|
||||
const notification = n8n.notifications.notificationContainerByText(
|
||||
'Critical update available',
|
||||
);
|
||||
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||
await expect(notification).toBeVisible();
|
||||
await notification.click();
|
||||
|
||||
@@ -199,9 +195,7 @@ test.describe('Security Notifications', () => {
|
||||
await n8n.goHome();
|
||||
|
||||
// Verify no security notification appears when no security issue
|
||||
const notification = n8n.notifications.notificationContainerByText(
|
||||
'Critical update available',
|
||||
);
|
||||
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||
await expect(notification).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -212,9 +206,7 @@ test.describe('Security Notifications', () => {
|
||||
await n8n.goHome();
|
||||
|
||||
// Verify no security notification appears on API failure
|
||||
const notification = n8n.notifications.notificationContainerByText(
|
||||
'Critical update available',
|
||||
);
|
||||
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||
await expect(notification).toBeHidden();
|
||||
|
||||
// Verify the app still functions normally
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"path": "FwrbSiaua2Xmvn6-Z-7CQ",
|
||||
"options": {}
|
||||
},
|
||||
"id": "8fcc7e5f-2cef-4938-9564-eea504c20aa0",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [360, 220],
|
||||
"webhookId": "9c778f2a-e882-46ed-a0e4-c8e2f76ccd65"
|
||||
}
|
||||
],
|
||||
"connections": {},
|
||||
"pinData": {
|
||||
"Webhook": [
|
||||
{
|
||||
"headers": {
|
||||
"connection": "keep-alive",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"accept": "*/*",
|
||||
"cookie": "n8n-auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjNiM2FhOTE5LWRhZDgtNDE5MS1hZWZiLTlhZDIwZTZkMjJjNiIsImhhc2giOiJ1ZVAxR1F3U2paIiwiaWF0IjoxNzI4OTE1NTQyLCJleHAiOjE3Mjk1MjAzNDJ9.fV02gpUnSiUoMxHwfB0npBjcjct7Mv9vGfj-jRTT3-I",
|
||||
"host": "localhost:5678",
|
||||
"accept-encoding": "gzip, deflate"
|
||||
},
|
||||
"params": {},
|
||||
"query": {},
|
||||
"body": {},
|
||||
"webhookUrl": "http://localhost:5678/webhook-test/FwrbSiaua2Xmvn6-Z-7CQ",
|
||||
"executionMode": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"name": "PinData Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "0a60e507-7f34-41c0-a0f9-697d852033b6",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [780, 320]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "b0d79ddb-df2d-49b1-8555-9fa2b482608f",
|
||||
"responseMode": "lastNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "66425ce3-450d-4aa6-a53b-a701ab89c2de",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [780, 540],
|
||||
"webhookId": "b0d79ddb-df2d-49b1-8555-9fa2b482608f"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "nodeData",
|
||||
"stringValue": "init"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": "none",
|
||||
"options": {}
|
||||
},
|
||||
"id": "3211b3c5-49e9-4694-8f86-7a5783bc653a",
|
||||
"name": "Init Data",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [1000, 320]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "nodeData",
|
||||
"stringValue": "pin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "97b31120-4720-4632-9d35-356f345119f7",
|
||||
"name": "Pin Data",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [1240, 320]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "1ee7be4f-7006-43bf-bb0c-29db3058a399",
|
||||
"name": "End",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1460, 320]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Pin Data": [
|
||||
{
|
||||
"json": {
|
||||
"nodeData": "pin-overwritten"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Init Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Init Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Init Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Pin Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Pin Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "End",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "ded8577a-3ed2-4611-842c-a7922ec58b98",
|
||||
"id": "weofVLZo0ssmPDrV",
|
||||
"meta": {
|
||||
"instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user