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:
@@ -1,210 +0,0 @@
|
|||||||
import {
|
|
||||||
HTTP_REQUEST_NODE_NAME,
|
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
|
||||||
PIPEDRIVE_NODE_NAME,
|
|
||||||
EDIT_FIELDS_SET_NODE_NAME,
|
|
||||||
BACKEND_BASE_URL,
|
|
||||||
} from '../constants';
|
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
|
||||||
import { errorToast } from '../pages/notifications';
|
|
||||||
import { getVisiblePopper } from '../utils';
|
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
|
||||||
const ndv = new NDV();
|
|
||||||
|
|
||||||
describe('Data pinning', () => {
|
|
||||||
const maxPinnedDataSize = 16384;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
workflowPage.actions.visit();
|
|
||||||
cy.window().then((win) => {
|
|
||||||
win.maxPinnedDataSize = maxPinnedDataSize;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to pin node output', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
|
|
||||||
ndv.getters.container().should('be.visible');
|
|
||||||
ndv.getters.pinDataButton().should('not.exist');
|
|
||||||
ndv.getters.editPinnedDataButton().should('be.visible');
|
|
||||||
|
|
||||||
ndv.actions.execute();
|
|
||||||
|
|
||||||
ndv.getters.outputDataContainer().should('be.visible');
|
|
||||||
// We hover over the table to get rid of the pinning tooltip which would overlay the table
|
|
||||||
// slightly and cause the test to fail
|
|
||||||
ndv.getters.outputDataContainer().get('table').realHover().should('be.visible');
|
|
||||||
ndv.getters.outputTableRows().should('have.length', 2);
|
|
||||||
ndv.getters.outputTableHeaders().should('have.length.at.least', 10);
|
|
||||||
ndv.getters.outputTableHeaders().first().should('include.text', 'timestamp');
|
|
||||||
ndv.getters.outputTableHeaders().eq(1).should('include.text', 'Readable date');
|
|
||||||
|
|
||||||
ndv.getters
|
|
||||||
.outputTbodyCell(1, 0)
|
|
||||||
.invoke('text')
|
|
||||||
.then((prevValue) => {
|
|
||||||
ndv.actions.pinData();
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.executeWorkflow();
|
|
||||||
workflowPage.actions.openNode('Schedule Trigger');
|
|
||||||
|
|
||||||
ndv.getters.outputTbodyCell(1, 0).invoke('text').should('eq', prevValue);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to set pinned data', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
|
|
||||||
ndv.getters.container().should('be.visible');
|
|
||||||
ndv.getters.pinDataButton().should('not.exist');
|
|
||||||
ndv.getters.editPinnedDataButton().should('be.visible');
|
|
||||||
|
|
||||||
ndv.actions.setPinnedData([{ test: 1 }]);
|
|
||||||
|
|
||||||
ndv.getters.outputTableRows().should('have.length', 2);
|
|
||||||
ndv.getters.outputTableHeaders().should('have.length', 2);
|
|
||||||
ndv.getters.outputTableHeaders().first().should('include.text', 'test');
|
|
||||||
ndv.getters.outputTbodyCell(1, 0).should('include.text', 1);
|
|
||||||
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
|
||||||
|
|
||||||
workflowPage.actions.openNode('Schedule Trigger');
|
|
||||||
|
|
||||||
ndv.getters.outputTableHeaders().first().should('include.text', 'test');
|
|
||||||
ndv.getters.outputTbodyCell(1, 0).should('include.text', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display pin data edit button for Webhook node', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Webhook', { keepNdvOpen: true });
|
|
||||||
|
|
||||||
ndv.getters
|
|
||||||
.runDataPaneHeader()
|
|
||||||
.find('button')
|
|
||||||
.filter(':visible')
|
|
||||||
.should('have.attr', 'title', 'Edit Output');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be duplicating pin data when duplicating node', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
|
||||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
|
||||||
ndv.getters.container().should('be.visible');
|
|
||||||
ndv.getters.pinDataButton().should('not.exist');
|
|
||||||
ndv.getters.editPinnedDataButton().should('be.visible');
|
|
||||||
|
|
||||||
ndv.actions.setPinnedData([{ test: 1 }]);
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.duplicateNode(EDIT_FIELDS_SET_NODE_NAME);
|
|
||||||
|
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
|
||||||
|
|
||||||
workflowPage.actions.openNode('Edit Fields1');
|
|
||||||
|
|
||||||
ndv.getters.outputTableHeaders().first().should('include.text', 'test');
|
|
||||||
ndv.getters.outputTbodyCell(1, 0).should('include.text', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should show an error when maximum pin data size is exceeded', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
|
||||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
|
||||||
ndv.getters.container().should('be.visible');
|
|
||||||
ndv.getters.pinDataButton().should('not.exist');
|
|
||||||
ndv.getters.editPinnedDataButton().should('be.visible');
|
|
||||||
|
|
||||||
ndv.actions.pastePinnedData([
|
|
||||||
{
|
|
||||||
test: '1'.repeat(maxPinnedDataSize),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
errorToast().should('contain', 'Workflow has reached the maximum allowed pinned data size');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should show an error when pin data JSON in invalid', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
|
||||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
|
||||||
ndv.getters.container().should('be.visible');
|
|
||||||
ndv.getters.pinDataButton().should('not.exist');
|
|
||||||
ndv.getters.editPinnedDataButton().should('be.visible');
|
|
||||||
|
|
||||||
ndv.actions.setPinnedData('[ { "name": "First item", "code": 2dsa }]');
|
|
||||||
errorToast().should('contain', 'Unable to save due to invalid JSON');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to reference paired items in a node located before pinned data', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
|
||||||
workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true);
|
|
||||||
ndv.actions.setPinnedData([{ http: 123 }]);
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.addNodeToCanvas(PIPEDRIVE_NODE_NAME, true, true);
|
|
||||||
ndv.actions.setPinnedData(Array(3).fill({ pipedrive: 123 }));
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
|
||||||
|
|
||||||
setExpressionOnStringValueInSet(`{{ $('${HTTP_REQUEST_NODE_NAME}').item`);
|
|
||||||
|
|
||||||
const output = '[Object: {"json": {"http": 123}, "pairedItem": {"item": 0}}]';
|
|
||||||
|
|
||||||
cy.get('div').contains(output).should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use pin data in manual executions that are started by a webhook', () => {
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_webhook_with_pin_data.json', 'Test');
|
|
||||||
|
|
||||||
workflowPage.actions.executeWorkflow();
|
|
||||||
|
|
||||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/b0d79ddb-df2d-49b1-8555-9fa2b482608f`).then(
|
|
||||||
(response) => {
|
|
||||||
expect(response.status).to.eq(200);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
workflowPage.actions.openNode('End');
|
|
||||||
|
|
||||||
ndv.getters.outputTableRow(1).should('exist');
|
|
||||||
ndv.getters.outputTableRow(1).should('have.text', 'pin-overwritten');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not use pin data in production executions that are started by a webhook', () => {
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_webhook_with_pin_data.json', 'Test');
|
|
||||||
|
|
||||||
workflowPage.actions.activateWorkflow();
|
|
||||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook/b0d79ddb-df2d-49b1-8555-9fa2b482608f`).then(
|
|
||||||
(response) => {
|
|
||||||
expect(response.status).to.eq(200);
|
|
||||||
// Assert that we get the data hard coded in the edit fields node,
|
|
||||||
// instead of the data pinned in said node.
|
|
||||||
expect(response.body).to.deep.equal({
|
|
||||||
nodeData: 'pin',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not show pinned data tooltip', () => {
|
|
||||||
cy.createFixtureWorkflow('Pinned_webhook_node.json', 'Test');
|
|
||||||
workflowPage.actions.executeWorkflow();
|
|
||||||
|
|
||||||
// hide other visible popper on workflow execute button
|
|
||||||
workflowPage.getters.canvasNodes().eq(0).click();
|
|
||||||
|
|
||||||
getVisiblePopper().should('have.length', 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setExpressionOnStringValueInSet(expression: string) {
|
|
||||||
cy.get('button').contains('Execute step').click();
|
|
||||||
|
|
||||||
ndv.getters.assignmentCollectionAdd('assignments').click();
|
|
||||||
ndv.getters.assignmentValue('assignments').contains('Expression').invoke('show').click();
|
|
||||||
|
|
||||||
ndv.getters
|
|
||||||
.inlineExpressionEditorInput()
|
|
||||||
.clear()
|
|
||||||
.type(expression, { parseSpecialCharSequences: false })
|
|
||||||
// hide autocomplete
|
|
||||||
.type('{esc}');
|
|
||||||
}
|
|
||||||
@@ -37,6 +37,10 @@ export class CanvasPage extends BasePage {
|
|||||||
await this.clickByTestId('canvas-plus-button');
|
await this.clickByTestId('canvas-plus-button');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCanvasNodes() {
|
||||||
|
return this.page.getByTestId('canvas-node');
|
||||||
|
}
|
||||||
|
|
||||||
async clickNodeCreatorPlusButton(): Promise<void> {
|
async clickNodeCreatorPlusButton(): Promise<void> {
|
||||||
await this.clickByTestId('node-creator-plus-button');
|
await this.clickByTestId('node-creator-plus-button');
|
||||||
}
|
}
|
||||||
@@ -81,6 +85,10 @@ export class CanvasPage extends BasePage {
|
|||||||
await this.clickSaveWorkflowButton();
|
await this.clickSaveWorkflowButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExecuteWorkflowButton(): Locator {
|
||||||
|
return this.page.getByTestId('execute-workflow-button');
|
||||||
|
}
|
||||||
|
|
||||||
async clickExecuteWorkflowButton(): Promise<void> {
|
async clickExecuteWorkflowButton(): Promise<void> {
|
||||||
await this.page.getByTestId('execute-workflow-button').click();
|
await this.page.getByTestId('execute-workflow-button').click();
|
||||||
}
|
}
|
||||||
@@ -256,6 +264,11 @@ export class CanvasPage extends BasePage {
|
|||||||
await this.getProductionChecklistActionItem(actionText).click();
|
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 {
|
getCanvasNodes(): Locator {
|
||||||
return this.page.getByTestId('canvas-node');
|
return this.page.getByTestId('canvas-node');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,136 @@ export class NodeDisplayViewPage extends BasePage {
|
|||||||
return this.page.getByTestId('output-panel');
|
return this.page.getByTestId('output-panel');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContainer() {
|
||||||
|
return this.page.getByTestId('ndv');
|
||||||
|
}
|
||||||
|
|
||||||
getParameterExpressionPreviewValue() {
|
getParameterExpressionPreviewValue() {
|
||||||
return this.page.getByTestId('parameter-expression-preview-value');
|
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)
|
* Get parameter input by name (for Code node and similar)
|
||||||
* @param parameterName - The name of the parameter e.g 'jsCode', 'mode'
|
* @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.
|
* @param text The text or a regular expression to find within the notification's title.
|
||||||
* @returns A Locator for the notification container element.
|
* @returns A Locator for the notification container element.
|
||||||
*/
|
*/
|
||||||
notificationContainerByText(text: string | RegExp): Locator {
|
getNotificationByTitle(text: string | RegExp): Locator {
|
||||||
return this.page.getByRole('alert').filter({
|
return this.page.getByRole('alert').filter({
|
||||||
has: this.page.locator('.el-notification__title').filter({ hasText: text }),
|
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.
|
* Clicks the close button on the FIRST notification matching the text.
|
||||||
* Fast execution with short timeouts for snappy notifications.
|
* Fast execution with short timeouts for snappy notifications.
|
||||||
@@ -31,7 +67,7 @@ export class NotificationsPage {
|
|||||||
const { timeout = 2000 } = options;
|
const { timeout = 2000 } = options;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const notification = this.notificationContainerByText(text).first();
|
const notification = this.getNotificationByTitle(text).first();
|
||||||
await notification.waitFor({ state: 'visible', timeout });
|
await notification.waitFor({ state: 'visible', timeout });
|
||||||
|
|
||||||
const closeBtn = notification.locator('.el-notification__closeBtn');
|
const closeBtn = notification.locator('.el-notification__closeBtn');
|
||||||
@@ -61,7 +97,7 @@ export class NotificationsPage {
|
|||||||
|
|
||||||
while (retries < maxRetries) {
|
while (retries < maxRetries) {
|
||||||
try {
|
try {
|
||||||
const notifications = this.notificationContainerByText(text);
|
const notifications = this.getNotificationByTitle(text);
|
||||||
const count = await notifications.count();
|
const count = await notifications.count();
|
||||||
|
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
@@ -105,7 +141,7 @@ export class NotificationsPage {
|
|||||||
const { timeout = 500 } = options;
|
const { timeout = 500 } = options;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const notification = this.notificationContainerByText(text).first();
|
const notification = this.getNotificationByTitle(text).first();
|
||||||
await notification.waitFor({ state: 'visible', timeout });
|
await notification.waitFor({ state: 'visible', timeout });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -126,7 +162,7 @@ export class NotificationsPage {
|
|||||||
const { timeout = 5000 } = options;
|
const { timeout = 5000 } = options;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const notification = this.notificationContainerByText(text).first();
|
const notification = this.getNotificationByTitle(text).first();
|
||||||
await notification.waitFor({ state: 'visible', timeout });
|
await notification.waitFor({ state: 'visible', timeout });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -186,9 +222,7 @@ export class NotificationsPage {
|
|||||||
*/
|
*/
|
||||||
async getNotificationCount(text?: string | RegExp): Promise<number> {
|
async getNotificationCount(text?: string | RegExp): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const notifications = text
|
const notifications = text ? this.getNotificationByTitle(text) : this.page.getByRole('alert');
|
||||||
? this.notificationContainerByText(text)
|
|
||||||
: this.page.getByRole('alert');
|
|
||||||
return await notifications.count();
|
return await notifications.count();
|
||||||
} catch {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -202,7 +236,7 @@ export class NotificationsPage {
|
|||||||
*/
|
*/
|
||||||
async quickClose(text: string | RegExp): Promise<void> {
|
async quickClose(text: string | RegExp): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const notification = this.notificationContainerByText(text).first();
|
const notification = this.getNotificationByTitle(text).first();
|
||||||
if (await notification.isVisible({ timeout: 100 })) {
|
if (await notification.isVisible({ timeout: 100 })) {
|
||||||
await notification.locator('.el-notification__closeBtn').click({ timeout: 200 });
|
await notification.locator('.el-notification__closeBtn').click({ timeout: 200 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ test.describe('Workflows', () => {
|
|||||||
await n8n.canvas.setWorkflowName(workflowName);
|
await n8n.canvas.setWorkflowName(workflowName);
|
||||||
await n8n.canvas.clickSaveWorkflowButton();
|
await n8n.canvas.clickSaveWorkflowButton();
|
||||||
|
|
||||||
await expect(
|
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.CREATED)).toBeVisible();
|
||||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.CREATED),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should search for workflows', async ({ n8n }) => {
|
test('should search for workflows', async ({ n8n }) => {
|
||||||
@@ -72,18 +70,14 @@ test.describe('Workflows', () => {
|
|||||||
|
|
||||||
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
||||||
await n8n.workflows.archiveWorkflow(workflow);
|
await n8n.workflows.archiveWorkflow(workflow);
|
||||||
await expect(
|
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.ARCHIVED)).toBeVisible();
|
||||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.ARCHIVED),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(workflow).toBeHidden();
|
await expect(workflow).toBeHidden();
|
||||||
await n8n.workflows.toggleShowArchived();
|
await n8n.workflows.toggleShowArchived();
|
||||||
await expect(workflow).toBeVisible();
|
await expect(workflow).toBeVisible();
|
||||||
|
|
||||||
await n8n.workflows.unarchiveWorkflow(workflow);
|
await n8n.workflows.unarchiveWorkflow(workflow);
|
||||||
await expect(
|
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.UNARCHIVED)).toBeVisible();
|
||||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.UNARCHIVED),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should delete an archived workflow', async ({ n8n }) => {
|
test('should delete an archived workflow', async ({ n8n }) => {
|
||||||
@@ -95,16 +89,12 @@ test.describe('Workflows', () => {
|
|||||||
|
|
||||||
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
const workflow = n8n.workflows.getWorkflowByName(workflowName);
|
||||||
await n8n.workflows.archiveWorkflow(workflow);
|
await n8n.workflows.archiveWorkflow(workflow);
|
||||||
await expect(
|
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.ARCHIVED)).toBeVisible();
|
||||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.ARCHIVED),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await n8n.workflows.toggleShowArchived();
|
await n8n.workflows.toggleShowArchived();
|
||||||
|
|
||||||
await n8n.workflows.deleteWorkflow(workflow);
|
await n8n.workflows.deleteWorkflow(workflow);
|
||||||
await expect(
|
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.DELETED)).toBeVisible();
|
||||||
n8n.notifications.notificationContainerByText(NOTIFICATIONS.DELETED),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(workflow).toBeHidden();
|
await expect(workflow).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ test.describe('Canvas Actions', () => {
|
|||||||
await n8n.canvas.executeNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
await n8n.canvas.executeNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
n8n.notifications.notificationContainerByText('Node executed successfully'),
|
n8n.notifications.getNotificationByTitle('Node executed successfully'),
|
||||||
).toHaveCount(1);
|
).toHaveCount(1);
|
||||||
await expect(n8n.canvas.getCanvasNodes()).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 n8n.ndv.execute();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
n8n.notifications.notificationContainerByText('Node executed successfully').first(),
|
n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await n8n.ndv.getParameterInput('mode').click();
|
await n8n.ndv.getParameterInput('mode').click();
|
||||||
@@ -45,7 +45,7 @@ test.describe('Code node', () => {
|
|||||||
await n8n.ndv.execute();
|
await n8n.ndv.execute();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
n8n.notifications.notificationContainerByText('Node executed successfully').first(),
|
n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ test.describe('PDF Test', () => {
|
|||||||
await n8n.canvas.importWorkflow('test_pdf_workflow.json', 'PDF Workflow');
|
await n8n.canvas.importWorkflow('test_pdf_workflow.json', 'PDF Workflow');
|
||||||
await n8n.canvas.clickExecuteWorkflowButton();
|
await n8n.canvas.clickExecuteWorkflowButton();
|
||||||
await expect(
|
await expect(
|
||||||
n8n.notifications.notificationContainerByText('Workflow executed successfully'),
|
n8n.notifications.getNotificationByTitle('Workflow executed successfully'),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -129,9 +129,7 @@ test.describe('Security Notifications', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
|
|
||||||
// Verify security notification appears with default message
|
// Verify security notification appears with default message
|
||||||
const notification = n8n.notifications.notificationContainerByText(
|
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||||
'Critical update available',
|
|
||||||
);
|
|
||||||
await expect(notification).toBeVisible();
|
await expect(notification).toBeVisible();
|
||||||
await expect(notification).toContainText('Please update to latest version.');
|
await expect(notification).toContainText('Please update to latest version.');
|
||||||
await expect(notification).toContainText('More info');
|
await expect(notification).toContainText('More info');
|
||||||
@@ -153,7 +151,7 @@ test.describe('Security Notifications', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
|
|
||||||
// Verify notification shows specific fix version (dynamically generated)
|
// Verify notification shows specific fix version (dynamically generated)
|
||||||
const notificationWithFixVersion = n8n.notifications.notificationContainerByText(
|
const notificationWithFixVersion = n8n.notifications.getNotificationByTitle(
|
||||||
'Critical update available',
|
'Critical update available',
|
||||||
);
|
);
|
||||||
await expect(notificationWithFixVersion).toBeVisible();
|
await expect(notificationWithFixVersion).toBeVisible();
|
||||||
@@ -174,9 +172,7 @@ test.describe('Security Notifications', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
|
|
||||||
// Wait for and click the security notification
|
// Wait for and click the security notification
|
||||||
const notification = n8n.notifications.notificationContainerByText(
|
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||||
'Critical update available',
|
|
||||||
);
|
|
||||||
await expect(notification).toBeVisible();
|
await expect(notification).toBeVisible();
|
||||||
await notification.click();
|
await notification.click();
|
||||||
|
|
||||||
@@ -199,9 +195,7 @@ test.describe('Security Notifications', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
|
|
||||||
// Verify no security notification appears when no security issue
|
// Verify no security notification appears when no security issue
|
||||||
const notification = n8n.notifications.notificationContainerByText(
|
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||||
'Critical update available',
|
|
||||||
);
|
|
||||||
await expect(notification).toBeHidden();
|
await expect(notification).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,9 +206,7 @@ test.describe('Security Notifications', () => {
|
|||||||
await n8n.goHome();
|
await n8n.goHome();
|
||||||
|
|
||||||
// Verify no security notification appears on API failure
|
// Verify no security notification appears on API failure
|
||||||
const notification = n8n.notifications.notificationContainerByText(
|
const notification = n8n.notifications.getNotificationByTitle('Critical update available');
|
||||||
'Critical update available',
|
|
||||||
);
|
|
||||||
await expect(notification).toBeHidden();
|
await expect(notification).toBeHidden();
|
||||||
|
|
||||||
// Verify the app still functions normally
|
// 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