diff --git a/cypress/e2e/33-settings-personal.cy.ts b/cypress/e2e/33-settings-personal.cy.ts deleted file mode 100644 index 6b5cc94687..0000000000 --- a/cypress/e2e/33-settings-personal.cy.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { errorToast, successToast } from '../pages/notifications'; - -const INVALID_NAMES = [ - 'https://n8n.io', - 'http://n8n.io', - 'www.n8n.io', - 'n8n.io', - 'n8n.бг', - 'n8n.io/home', - 'n8n.io/home?send=true', - 'Jack', - '', -]; - -const VALID_NAMES = [ - ['a', 'a'], - ['alice', 'alice'], - ['Robert', 'Downey Jr.'], - ['Mia', 'Mia-Downey'], - ['Mark', "O'neil"], - ['Thomas', 'Müler'], - ['ßáçøñ', 'ßáçøñ'], - ['أحمد', 'فلسطين'], - ['Милорад', 'Филиповић'], -]; - -describe('Personal Settings', () => { - it('should allow to change first and last name', () => { - cy.visit('/settings/personal'); - VALID_NAMES.forEach((name) => { - cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name[0]); - cy.getByTestId('personal-data-form').find('input[name="lastName"]').clear().type(name[1]); - cy.getByTestId('save-settings-button').click(); - successToast().should('contain', 'Personal details updated'); - successToast().find('.el-notification__closeBtn').click(); - }); - }); - // eslint-disable-next-line n8n-local-rules/no-skipped-tests - it('not allow malicious values for personal data', () => { - cy.visit('/settings/personal'); - INVALID_NAMES.forEach((name) => { - cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name); - cy.getByTestId('personal-data-form').find('input[name="lastName"]').clear().type(name); - cy.getByTestId('save-settings-button').click(); - errorToast().should('contain', 'Potentially malicious string'); - errorToast().find('.el-notification__closeBtn').click(); - }); - }); -}); diff --git a/cypress/e2e/43-oauth-flow.cy.ts b/cypress/e2e/43-oauth-flow.cy.ts deleted file mode 100644 index d91315627b..0000000000 --- a/cypress/e2e/43-oauth-flow.cy.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { getCredentialSaveButton } from '../composables/modals/credential-modal'; -import { CredentialsPage, CredentialsModal } from '../pages'; - -const credentialsPage = new CredentialsPage(); -const credentialsModal = new CredentialsModal(); - -describe('Credentials', () => { - it('create and connect with Google OAuth2', () => { - // Open credentials page - cy.visit(credentialsPage.url, { - onBeforeLoad(win) { - cy.stub(win, 'open').as('windowOpen'); - }, - }); - - // Add a new Google OAuth2 credential - credentialsPage.getters.emptyListCreateCredentialButton().click(); - credentialsModal.getters.newCredentialTypeOption('Google OAuth2 API').click(); - credentialsModal.getters.newCredentialTypeButton().click(); - - // Fill in the key/secret and save - credentialsModal.actions.fillField('clientId', 'test-key'); - credentialsModal.actions.fillField('clientSecret', 'test-secret'); - credentialsModal.actions.save(); - - // Connect to Google - credentialsModal.getters.oauthConnectButton().click(); - cy.get('@windowOpen').should( - 'have.been.calledOnceWith', - Cypress.sinon.match( - 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=test-key&redirect_uri=http%3A%2F%2Flocalhost%3A5678%2Frest%2Foauth2-credential%2Fcallback&response_type=code', - ), - 'OAuth Authorization', - 'scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700', - ); - - // Emulate successful save using BroadcastChannel - cy.window().then(() => { - const channel = new BroadcastChannel('oauth-callback'); - channel.postMessage('success'); - }); - - // Check that the credential was saved and connected successfully - getCredentialSaveButton().should('contain.text', 'Saved'); - credentialsModal.getters.oauthConnectSuccessBanner().should('be.visible'); - }); -}); diff --git a/cypress/e2e/52-rag-callout-experiment.cy.ts b/cypress/e2e/52-rag-callout-experiment.cy.ts deleted file mode 100644 index 7b51817fa7..0000000000 --- a/cypress/e2e/52-rag-callout-experiment.cy.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { openNodeCreator, searchBar } from '../composables/nodeCreator'; -import { addNodeToCanvas, navigateToNewWorkflowPage } from '../composables/workflow'; - -describe('RAG callout experiment', () => { - describe('NDV callout', () => { - it('should show callout and open template on click', () => { - cy.intercept('workflows/templates/rag-starter-template?fromJson=true'); - - navigateToNewWorkflowPage(); - - addNodeToCanvas('Zep Vector Store', true, true, 'Add documents to vector store'); - - cy.contains('Tip: Get a feel for vector stores in n8n with our').should('exist'); - - let openedUrl = ''; - cy.window().then((win) => { - cy.stub(win, 'open').callsFake((url) => { - openedUrl = url; - }); - }); - cy.contains('RAG starter template').click(); - cy.then(() => cy.visit(openedUrl)); - - cy.url().should('include', '/workflows/templates/rag-starter-template?fromJson=true'); - }); - }); - describe('search callout', () => { - it('should show callout and open template on click', () => { - cy.intercept('workflows/templates/rag-starter-template?fromJson=true'); - - navigateToNewWorkflowPage(); - - openNodeCreator(); - searchBar().type('rag'); - - let openedUrl = ''; - cy.window().then((win) => { - cy.stub(win, 'open').callsFake((url) => { - openedUrl = url; - }); - }); - cy.contains('RAG starter template').should('exist').click(); - cy.then(() => cy.visit(openedUrl)); - - cy.url().should('include', '/workflows/templates/rag-starter-template?fromJson=true'); - }); - }); -}); diff --git a/cypress/e2e/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.cy.ts b/cypress/e2e/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.cy.ts deleted file mode 100644 index 25ce64e1d2..0000000000 --- a/cypress/e2e/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.cy.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as workflow from '../composables/workflow'; -import { EDIT_FIELDS_SET_NODE_NAME, LOOP_OVER_ITEMS_NODE_NAME } from '../constants'; -import { NodeCreator } from '../pages/features/node-creator'; -import { NDV } from '../pages/ndv'; -import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; -const nodeCreatorFeature = new NodeCreator(); -const WorkflowPage = new WorkflowPageClass(); -const NDVModal = new NDV(); - -describe('CAT-726 Node connectors not rendered when nodes inserted on the canvas', () => { - beforeEach(() => { - WorkflowPage.actions.visit(); - }); - - it('should correctly append a No Op node when Loop Over Items node is added (from add button)', () => { - nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').type(EDIT_FIELDS_SET_NODE_NAME); - nodeCreatorFeature.getters.getCreatorItem(EDIT_FIELDS_SET_NODE_NAME).click(); - NDVModal.actions.close(); - - workflow.executeWorkflowAndWait(); - - cy.getByTestId('edge-label').realHover(); - cy.getByTestId('add-connection-button').realClick(); - - nodeCreatorFeature.getters.searchBar().find('input').type(LOOP_OVER_ITEMS_NODE_NAME); - nodeCreatorFeature.getters.getCreatorItem(LOOP_OVER_ITEMS_NODE_NAME).click(); - NDVModal.actions.close(); - - WorkflowPage.getters.canvasNodes().should('have.length', 4); - WorkflowPage.getters.nodeConnections().should('have.length', 4); - - WorkflowPage.getters - .getConnectionBetweenNodes(LOOP_OVER_ITEMS_NODE_NAME, 'Replace Me') - .should('exist') - .should('be.visible'); - WorkflowPage.getters - .getConnectionBetweenNodes(LOOP_OVER_ITEMS_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME) - .should('exist') - .should('be.visible'); - WorkflowPage.getters - .getConnectionBetweenNodes('Replace Me', LOOP_OVER_ITEMS_NODE_NAME) - .should('exist') - .should('be.visible'); - }); -}); diff --git a/cypress/e2e/812-AI-partial-execs-broken-when-using-chat-trigger.cy.ts b/cypress/e2e/812-AI-partial-execs-broken-when-using-chat-trigger.cy.ts deleted file mode 100644 index 4972289dc9..0000000000 --- a/cypress/e2e/812-AI-partial-execs-broken-when-using-chat-trigger.cy.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - getManualChatMessages, - getManualChatModal, - sendManualChatMessage, -} from '../composables/modals/chat-modal'; -import { clickExecuteNode } from '../composables/ndv'; -import { - clickZoomToFit, - openNode, - navigateToNewWorkflowPage, - openContextMenu, - clickContextMenuAction, - clickClearExecutionDataButton, -} from '../composables/workflow'; -import { clearNotifications } from '../pages/notifications'; - -describe('AI-812-partial-execs-broken-when-using-chat-trigger', () => { - beforeEach(() => { - navigateToNewWorkflowPage(); - cy.createFixtureWorkflow('Test_chat_partial_execution.json'); - clearNotifications(); - clickZoomToFit(); - openContextMenu('Edit Fields'); - clickContextMenuAction('deselect_all'); - }); - - // Check if the full execution still behaves as expected after the partial execution tests - afterEach(() => { - clearNotifications(); - clickClearExecutionDataButton(); - sendManualChatMessage('Test Full Execution'); - getManualChatMessages().should('have.length', 4); - getManualChatMessages().should('contain', 'Set 3 with chatInput: Test Full Execution'); - }); - - it('should do partial execution when using chat trigger and clicking NDV execute node', () => { - openNode('Edit Fields1'); - clickExecuteNode(); - getManualChatModal().should('exist'); - sendManualChatMessage('Test Partial Execution'); - - getManualChatMessages().should('have.length', 2); - getManualChatMessages().should('contain', 'Test Partial Execution'); - getManualChatMessages().should('contain', 'Set 2 with chatInput: Test Partial Execution'); - }); - - it('should do partial execution when using chat trigger and context-menu execute node', () => { - openContextMenu('Edit Fields'); - clickContextMenuAction('execute'); - getManualChatModal().should('exist'); - sendManualChatMessage('Test Partial Execution'); - - getManualChatMessages().should('have.length', 2); - getManualChatMessages().should('contain', 'Test Partial Execution'); - getManualChatMessages().should('contain', 'Set 1 with chatInput: Test Partial Execution'); - }); -}); diff --git a/cypress/fixtures/Test_chat_partial_execution.json b/cypress/fixtures/Test_chat_partial_execution.json deleted file mode 100644 index 96ec779785..0000000000 --- a/cypress/fixtures/Test_chat_partial_execution.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "nodes": [ - { - "parameters": { - "assignments": { - "assignments": [ - { - "id": "0c345346-8cef-415c-aa1a-3d3941bb4035", - "name": "text", - "value": "=Set 1 with chatInput: {{ $json.chatInput }}", - "type": "string" - } - ] - }, - "options": {} - }, - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - 220, - 0 - ], - "id": "b1584b5b-c17c-4fd9-9b75-dd61f2c4c20d", - "name": "Edit Fields" - }, - { - "parameters": { - "assignments": { - "assignments": [ - { - "id": "9a7bd7af-c3fb-4984-b15a-2f805b66ed02", - "name": "text", - "value": "=Set 2 with chatInput: {{ $('When chat message received').item.json.chatInput }}", - "type": "string" - } - ] - }, - "options": {} - }, - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - 440, - 0 - ], - "id": "e9e02219-4b6b-48d1-8d3d-2c850362abf2", - "name": "Edit Fields1" - }, - { - "parameters": { - "options": {} - }, - "type": "@n8n/n8n-nodes-langchain.chatTrigger", - "typeVersion": 1.1, - "position": [ - 0, - 0 - ], - "id": "c2dd390e-1360-4d6f-a922-4d295246a886", - "name": "When chat message received", - "webhookId": "28da48d8-cef1-4364-b4d6-429212d2e3f6" - }, - { - "parameters": { - "assignments": { - "assignments": [ - { - "id": "9a7bd7af-c3fb-4984-b15a-2f805b66ed02", - "name": "text", - "value": "=Set 3 with chatInput: {{ $('When chat message received').item.json.chatInput }}", - "type": "string" - } - ] - }, - "options": {} - }, - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - 660, - 0 - ], - "id": "766dba66-a4da-4d84-ad80-ca5579ce91e5", - "name": "Edit Fields2" - } - ], - "connections": { - "Edit Fields": { - "main": [ - [ - { - "node": "Edit Fields1", - "type": "main", - "index": 0 - } - ] - ] - }, - "Edit Fields1": { - "main": [ - [ - { - "node": "Edit Fields2", - "type": "main", - "index": 0 - } - ] - ] - }, - "When chat message received": { - "main": [ - [ - { - "node": "Edit Fields", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "pinData": {}, - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" - } -} \ No newline at end of file diff --git a/packages/testing/playwright/composables/TestEntryComposer.ts b/packages/testing/playwright/composables/TestEntryComposer.ts index ad8dab2da5..27eea670c0 100644 --- a/packages/testing/playwright/composables/TestEntryComposer.ts +++ b/packages/testing/playwright/composables/TestEntryComposer.ts @@ -40,6 +40,7 @@ export class TestEntryComposer { const projectId = response.id; await this.n8n.page.goto(`workflow/new?projectId=${projectId}`); await this.n8n.canvas.canvasPane().isVisible(); + return projectId; } /** diff --git a/packages/testing/playwright/config/intercepts.ts b/packages/testing/playwright/config/intercepts.ts index b003feba3d..50103402b1 100644 --- a/packages/testing/playwright/config/intercepts.ts +++ b/packages/testing/playwright/config/intercepts.ts @@ -17,6 +17,7 @@ export function getContextSettings(context: BrowserContext) { export async function setupDefaultInterceptors(target: BrowserContext) { // Global /rest/settings intercept - always active like Cypress + // TODO: Remove this as a global and move it per test await target.route('**/rest/settings', async (route: Route) => { try { const originalResponse = await route.fetch(); diff --git a/packages/testing/playwright/pages/CanvasPage.ts b/packages/testing/playwright/pages/CanvasPage.ts index a8dfe28c3c..19bedbdeb2 100644 --- a/packages/testing/playwright/pages/CanvasPage.ts +++ b/packages/testing/playwright/pages/CanvasPage.ts @@ -484,4 +484,50 @@ export class CanvasPage extends BasePage { await this.addNode(searchText); await this.nodeCreatorSubItem(subItemText).click(); } + + getRagCalloutTip(): Locator { + return this.page.getByText('Tip: Get a feel for vector stores in n8n with our'); + } + + getRagTemplateLink(): Locator { + return this.page.getByText('RAG starter template'); + } + + async clickRagTemplateLink(): Promise { + await this.getRagTemplateLink().click(); + } + + async rightClickNode(nodeName: string): Promise { + await this.nodeByName(nodeName).click({ button: 'right' }); + } + + async clickContextMenuAction(actionText: string): Promise { + await this.page.getByTestId('context-menu').getByText(actionText).click(); + } + + async executeNodeFromContextMenu(nodeName: string): Promise { + await this.rightClickNode(nodeName); + await this.clickContextMenuAction('execute'); + } + + async clearExecutionData(): Promise { + await this.page.getByTestId('clear-execution-data-button').click(); + } + + getManualChatModal(): Locator { + return this.page.getByTestId('canvas-chat'); + } + + getManualChatInput(): Locator { + return this.getManualChatModal().locator('.chat-inputs textarea'); + } + + getManualChatMessages(): Locator { + return this.getManualChatModal().locator('.chat-messages-list .chat-message'); + } + + async sendManualChatMessage(message: string): Promise { + await this.getManualChatInput().fill(message); + await this.getManualChatModal().locator('.chat-input-send-button').click(); + } } diff --git a/packages/testing/playwright/pages/CredentialsPage.ts b/packages/testing/playwright/pages/CredentialsPage.ts index 045bb9e900..98042cc711 100644 --- a/packages/testing/playwright/pages/CredentialsPage.ts +++ b/packages/testing/playwright/pages/CredentialsPage.ts @@ -41,6 +41,9 @@ export class CredentialsPage extends BasePage { await field.click(); await field.fill(value); } + get saveCredentialButton() { + return this.page.getByRole('button', { name: 'Save' }); + } async saveCredential() { await this.clickButtonByName('Save'); @@ -62,4 +65,16 @@ export class CredentialsPage extends BasePage { await this.page.getByText('Connection tested successfully').waitFor({ state: 'visible' }); await this.closeCredentialDialog(); } + + getOauthConnectButton() { + return this.page.getByTestId('oauth-connect-button'); + } + + getOauthConnectSuccessBanner() { + return this.page.getByTestId('oauth-connect-success-banner'); + } + + getSaveButton() { + return this.page.getByTestId('credential-save-button'); + } } diff --git a/packages/testing/playwright/pages/SettingsPage.ts b/packages/testing/playwright/pages/SettingsPage.ts index b1df9a86d9..00411b1104 100644 --- a/packages/testing/playwright/pages/SettingsPage.ts +++ b/packages/testing/playwright/pages/SettingsPage.ts @@ -16,4 +16,33 @@ export class SettingsPage extends BasePage { async goToSettings() { await this.page.goto('/settings'); } + + async goToPersonalSettings() { + await this.page.goto('/settings/personal'); + } + + getPersonalDataForm() { + return this.page.getByTestId('personal-data-form'); + } + + getFirstNameField() { + return this.getPersonalDataForm().locator('input[name="firstName"]'); + } + + getLastNameField() { + return this.getPersonalDataForm().locator('input[name="lastName"]'); + } + + getSaveSettingsButton() { + return this.page.getByTestId('save-settings-button'); + } + + async fillPersonalData(firstName: string, lastName: string) { + await this.getFirstNameField().fill(firstName); + await this.getLastNameField().fill(lastName); + } + + async saveSettings() { + await this.getSaveSettingsButton().click(); + } } diff --git a/packages/testing/playwright/tests/ui/33-settings-personal.spec.ts b/packages/testing/playwright/tests/ui/33-settings-personal.spec.ts new file mode 100644 index 0000000000..26831b89d6 --- /dev/null +++ b/packages/testing/playwright/tests/ui/33-settings-personal.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from '../../fixtures/base'; + +const INVALID_NAMES = [ + 'https://n8n.io', + 'http://n8n.io', + 'www.n8n.io', + 'n8n.io', + 'n8n.бг', + 'n8n.io/home', + 'n8n.io/home?send=true', + 'Jack', + '', +]; + +const VALID_NAMES = [ + ['a', 'a'], + ['alice', 'alice'], + ['Robert', 'Downey Jr.'], + ['Mia', 'Mia-Downey'], + ['Mark', "O'neil"], + ['Thomas', 'Müler'], + ['ßáçøñ', 'ßáçøñ'], + ['أحمد', 'فلسطين'], + ['Милорад', 'Филиповић'], +]; + +test.describe('Personal Settings', () => { + test('should allow to change first and last name', async ({ n8n }) => { + await n8n.settings.goToPersonalSettings(); + + for (const name of VALID_NAMES) { + await n8n.settings.fillPersonalData(name[0], name[1]); + await n8n.settings.saveSettings(); + + await expect( + n8n.notifications.getNotificationByTitleOrContent('Personal details updated'), + ).toBeVisible(); + await n8n.notifications.closeNotificationByText('Personal details updated'); + } + }); + + test('should not allow malicious values for personal data', async ({ n8n }) => { + await n8n.settings.goToPersonalSettings(); + + for (const name of INVALID_NAMES) { + await n8n.settings.fillPersonalData(name, name); + await n8n.settings.saveSettings(); + + await expect( + n8n.notifications.getNotificationByTitleOrContent('Problem updating your details'), + ).toBeVisible(); + await n8n.notifications.closeNotificationByText('Problem updating your details'); + } + }); +}); diff --git a/packages/testing/playwright/tests/ui/43-oauth-flow.spec.ts b/packages/testing/playwright/tests/ui/43-oauth-flow.spec.ts new file mode 100644 index 0000000000..e10348be97 --- /dev/null +++ b/packages/testing/playwright/tests/ui/43-oauth-flow.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe('OAuth Credentials', () => { + test('should create and connect with Google OAuth2', async ({ n8n, page }) => { + const projectId = await n8n.start.fromNewProject(); + await page.goto(`projects/${projectId}/credentials`); + await n8n.credentials.emptyListCreateCredentialButton.click(); + await n8n.credentials.openNewCredentialDialogFromCredentialList('Google OAuth2 API'); + await n8n.credentials.fillCredentialField('clientId', 'test-key'); + await n8n.credentials.fillCredentialField('clientSecret', 'test-secret'); + await n8n.credentials.saveCredential(); + + const popupPromise = page.waitForEvent('popup'); + await n8n.credentials.getOauthConnectButton().click(); + + const popup = await popupPromise; + const popupUrl = popup.url(); + expect(popupUrl).toContain('accounts.google.com'); + expect(popupUrl).toContain('client_id=test-key'); + + await popup.close(); + + await page.evaluate(() => { + const channel = new BroadcastChannel('oauth-callback'); + channel.postMessage('success'); + }); + + await expect(n8n.credentials.getSaveButton()).toContainText('Saved'); + await expect(n8n.credentials.getOauthConnectSuccessBanner()).toContainText('Account connected'); + }); +}); diff --git a/packages/testing/playwright/tests/ui/52-rag-callout-experiment.spec.ts b/packages/testing/playwright/tests/ui/52-rag-callout-experiment.spec.ts new file mode 100644 index 0000000000..9c8028d685 --- /dev/null +++ b/packages/testing/playwright/tests/ui/52-rag-callout-experiment.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe('RAG callout experiment', () => { + test.describe('NDV callout', () => { + test('should show callout and open template on click', async ({ n8n, page }) => { + await n8n.start.fromBlankCanvas(); + await n8n.canvas.addNode('Zep Vector Store', { + action: 'Add documents to vector store', + closeNDV: false, + }); + + await expect(n8n.canvas.getRagCalloutTip()).toBeVisible(); + + const popupPromise = page.waitForEvent('popup'); + await n8n.canvas.clickRagTemplateLink(); + + const popup = await popupPromise; + expect(popup.url()).toContain('/workflows/templates/rag-starter-template?fromJson=true'); + + await popup.close(); + }); + }); + + test.describe('search callout', () => { + test('should show callout and open template on click', async ({ n8n, page }) => { + await n8n.start.fromBlankCanvas(); + await n8n.canvas.clickNodeCreatorPlusButton(); + await n8n.canvas.fillNodeCreatorSearchBar('rag'); + + const popupPromise = page.waitForEvent('popup'); + await expect(n8n.canvas.getRagTemplateLink()).toBeVisible(); + await n8n.canvas.clickRagTemplateLink(); + + const popup = await popupPromise; + expect(popup.url()).toContain('/workflows/templates/rag-starter-template?fromJson=true'); + + await popup.close(); + }); + }); +}); diff --git a/packages/testing/playwright/tests/ui/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.spec.ts b/packages/testing/playwright/tests/ui/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.spec.ts new file mode 100644 index 0000000000..509c50324e --- /dev/null +++ b/packages/testing/playwright/tests/ui/726-CAT-canvas-node-connectors-not-rendered-when-nodes-inserted.spec.ts @@ -0,0 +1,33 @@ +import { EDIT_FIELDS_SET_NODE_NAME } from '../../config/constants'; +import { test, expect } from '../../fixtures/base'; + +test.describe('CAT-726 Node connectors not rendered when nodes inserted on the canvas', () => { + test('should correctly append a No Op node when Loop Over Items node is added (from add button)', async ({ + n8n, + }) => { + await n8n.start.fromBlankCanvas(); + await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true }); + await n8n.workflowComposer.executeWorkflowAndWaitForNotification( + 'Workflow executed successfully', + ); + + await n8n.canvas.addNodeBetweenNodes( + 'When clicking ‘Execute workflow’', + 'Edit Fields', + 'Loop Over Items (Split in Batches)', + ); + + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(4); + await expect(n8n.canvas.nodeConnections()).toHaveCount(4); + + await expect + .soft(n8n.canvas.connectionBetweenNodes('Loop Over Items', 'Replace Me')) + .toBeVisible(); + await expect + .soft(n8n.canvas.connectionBetweenNodes('Loop Over Items', 'Edit Fields')) + .toBeVisible(); + await expect + .soft(n8n.canvas.connectionBetweenNodes('Replace Me', 'Loop Over Items')) + .toBeVisible(); + }); +}); diff --git a/packages/testing/playwright/tests/ui/812-AI-partial-execs-broken-when-using-chat-trigger.spec.ts b/packages/testing/playwright/tests/ui/812-AI-partial-execs-broken-when-using-chat-trigger.spec.ts new file mode 100644 index 0000000000..b3f5c2b2f5 --- /dev/null +++ b/packages/testing/playwright/tests/ui/812-AI-partial-execs-broken-when-using-chat-trigger.spec.ts @@ -0,0 +1,57 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe('AI-812-partial-execs-broken-when-using-chat-trigger', () => { + test.beforeEach(async ({ n8n }) => { + await n8n.start.fromImportedWorkflow('Test_chat_partial_execution.json'); + await n8n.notifications.quickCloseAll(); + await n8n.canvas.clickZoomToFitButton(); + await n8n.canvas.deselectAll(); + }); + + test.afterEach(async ({ n8n }) => { + await n8n.notifications.quickCloseAll(); + await n8n.canvas.clearExecutionData(); + await n8n.canvas.sendManualChatMessage('Test Full Execution'); + + await expect(n8n.canvas.getManualChatMessages()).toHaveCount(4); + + await expect(n8n.canvas.getManualChatMessages().last()).toContainText( + 'Set 3 with chatInput: Test Full Execution', + ); + }); + + test('should do partial execution when using chat trigger and clicking NDV execute node', async ({ + n8n, + }) => { + await n8n.canvas.openNode('Edit Fields1'); + await n8n.ndv.execute(); + + await expect(n8n.canvas.getManualChatModal()).toBeVisible(); + await n8n.canvas.sendManualChatMessage('Test Partial Execution'); + + await expect(n8n.canvas.getManualChatMessages()).toHaveCount(2); + await expect(n8n.canvas.getManualChatMessages().first()).toContainText( + 'Test Partial Execution', + ); + await expect(n8n.canvas.getManualChatMessages().last()).toContainText( + 'Set 2 with chatInput: Test Partial Execution', + ); + }); + + test('should do partial execution when using chat trigger and context-menu execute node', async ({ + n8n, + }) => { + await n8n.canvas.executeNodeFromContextMenu('Edit Fields'); + + await expect(n8n.canvas.getManualChatModal()).toBeVisible(); + await n8n.canvas.sendManualChatMessage('Test Partial Execution'); + + await expect(n8n.canvas.getManualChatMessages()).toHaveCount(2); + await expect(n8n.canvas.getManualChatMessages().first()).toContainText( + 'Test Partial Execution', + ); + await expect(n8n.canvas.getManualChatMessages().last()).toContainText( + 'Set 1 with chatInput: Test Partial Execution', + ); + }); +}); diff --git a/packages/testing/playwright/workflows/Test_chat_partial_execution.json b/packages/testing/playwright/workflows/Test_chat_partial_execution.json new file mode 100644 index 0000000000..a12bc20f64 --- /dev/null +++ b/packages/testing/playwright/workflows/Test_chat_partial_execution.json @@ -0,0 +1,115 @@ +{ + "nodes": [ + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "0c345346-8cef-415c-aa1a-3d3941bb4035", + "name": "text", + "value": "=Set 1 with chatInput: {{ $json.chatInput }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [220, 0], + "id": "b1584b5b-c17c-4fd9-9b75-dd61f2c4c20d", + "name": "Edit Fields" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "9a7bd7af-c3fb-4984-b15a-2f805b66ed02", + "name": "text", + "value": "=Set 2 with chatInput: {{ $('When chat message received').item.json.chatInput }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [440, 0], + "id": "e9e02219-4b6b-48d1-8d3d-2c850362abf2", + "name": "Edit Fields1" + }, + { + "parameters": { + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.chatTrigger", + "typeVersion": 1.1, + "position": [0, 0], + "id": "c2dd390e-1360-4d6f-a922-4d295246a886", + "name": "When chat message received", + "webhookId": "28da48d8-cef1-4364-b4d6-429212d2e3f6" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "9a7bd7af-c3fb-4984-b15a-2f805b66ed02", + "name": "text", + "value": "=Set 3 with chatInput: {{ $('When chat message received').item.json.chatInput }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [660, 0], + "id": "766dba66-a4da-4d84-ad80-ca5579ce91e5", + "name": "Edit Fields2" + } + ], + "connections": { + "Edit Fields": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "When chat message received": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" + } +}