mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
test: Migrate Cypress E2E tests to Playwright (#18970)
This commit is contained in:
@@ -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',
|
|
||||||
'<a href="#">Jack</a>',
|
|
||||||
'<script>alert("Hello")</script>',
|
|
||||||
];
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,7 @@ export class TestEntryComposer {
|
|||||||
const projectId = response.id;
|
const projectId = response.id;
|
||||||
await this.n8n.page.goto(`workflow/new?projectId=${projectId}`);
|
await this.n8n.page.goto(`workflow/new?projectId=${projectId}`);
|
||||||
await this.n8n.canvas.canvasPane().isVisible();
|
await this.n8n.canvas.canvasPane().isVisible();
|
||||||
|
return projectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export function getContextSettings(context: BrowserContext) {
|
|||||||
|
|
||||||
export async function setupDefaultInterceptors(target: BrowserContext) {
|
export async function setupDefaultInterceptors(target: BrowserContext) {
|
||||||
// Global /rest/settings intercept - always active like Cypress
|
// 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) => {
|
await target.route('**/rest/settings', async (route: Route) => {
|
||||||
try {
|
try {
|
||||||
const originalResponse = await route.fetch();
|
const originalResponse = await route.fetch();
|
||||||
|
|||||||
@@ -484,4 +484,50 @@ export class CanvasPage extends BasePage {
|
|||||||
await this.addNode(searchText);
|
await this.addNode(searchText);
|
||||||
await this.nodeCreatorSubItem(subItemText).click();
|
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<void> {
|
||||||
|
await this.getRagTemplateLink().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async rightClickNode(nodeName: string): Promise<void> {
|
||||||
|
await this.nodeByName(nodeName).click({ button: 'right' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickContextMenuAction(actionText: string): Promise<void> {
|
||||||
|
await this.page.getByTestId('context-menu').getByText(actionText).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNodeFromContextMenu(nodeName: string): Promise<void> {
|
||||||
|
await this.rightClickNode(nodeName);
|
||||||
|
await this.clickContextMenuAction('execute');
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearExecutionData(): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
await this.getManualChatInput().fill(message);
|
||||||
|
await this.getManualChatModal().locator('.chat-input-send-button').click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export class CredentialsPage extends BasePage {
|
|||||||
await field.click();
|
await field.click();
|
||||||
await field.fill(value);
|
await field.fill(value);
|
||||||
}
|
}
|
||||||
|
get saveCredentialButton() {
|
||||||
|
return this.page.getByRole('button', { name: 'Save' });
|
||||||
|
}
|
||||||
|
|
||||||
async saveCredential() {
|
async saveCredential() {
|
||||||
await this.clickButtonByName('Save');
|
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.page.getByText('Connection tested successfully').waitFor({ state: 'visible' });
|
||||||
await this.closeCredentialDialog();
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,33 @@ export class SettingsPage extends BasePage {
|
|||||||
async goToSettings() {
|
async goToSettings() {
|
||||||
await this.page.goto('/settings');
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
'<a href="#">Jack</a>',
|
||||||
|
'<script>alert("Hello")</script>',
|
||||||
|
];
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
31
packages/testing/playwright/tests/ui/43-oauth-flow.spec.ts
Normal file
31
packages/testing/playwright/tests/ui/43-oauth-flow.spec.ts
Normal file
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user