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"
+ }
+}