2024-09-23 08:38:35 -07:00
|
|
|
import { type ICredentialType } from 'n8n-workflow';
|
|
|
|
|
2024-09-11 07:38:39 -07:00
|
|
|
import { clickCreateNewCredential, openCredentialSelect } from '../composables/ndv';
|
|
|
|
import { GMAIL_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
|
|
|
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
|
2024-08-20 04:05:09 -07:00
|
|
|
import { AIAssistant } from '../pages/features/ai-assistant';
|
2024-11-07 07:22:13 -08:00
|
|
|
import { NodeCreator } from '../pages/features/node-creator';
|
2024-09-23 08:38:35 -07:00
|
|
|
import { getVisibleSelect } from '../utils';
|
2024-08-20 04:05:09 -07:00
|
|
|
|
|
|
|
const wf = new WorkflowPage();
|
|
|
|
const ndv = new NDV();
|
|
|
|
const aiAssistant = new AIAssistant();
|
2024-09-11 07:38:39 -07:00
|
|
|
const credentialsPage = new CredentialsPage();
|
|
|
|
const credentialsModal = new CredentialsModal();
|
2024-11-07 07:22:13 -08:00
|
|
|
const nodeCreatorFeature = new NodeCreator();
|
2024-08-20 04:05:09 -07:00
|
|
|
|
|
|
|
describe('AI Assistant::disabled', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
aiAssistant.actions.disableAssistant();
|
|
|
|
wf.actions.visit();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not show assistant button if feature is disabled', () => {
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().should('not.exist');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('AI Assistant::enabled', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
aiAssistant.actions.enableAssistant();
|
|
|
|
wf.actions.visit();
|
|
|
|
});
|
|
|
|
|
|
|
|
after(() => {
|
|
|
|
aiAssistant.actions.disableAssistant();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders placeholder UI', () => {
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().should('be.visible');
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.getters.askAssistantChat().should('be.visible');
|
|
|
|
aiAssistant.getters.placeholderMessage().should('be.visible');
|
2024-09-05 01:54:35 -07:00
|
|
|
aiAssistant.getters.chatInput().should('be.visible');
|
|
|
|
aiAssistant.getters.sendMessageButton().should('be.disabled');
|
2024-08-20 04:05:09 -07:00
|
|
|
aiAssistant.getters.closeChatButton().should('be.visible');
|
|
|
|
aiAssistant.getters.closeChatButton().click();
|
2024-08-28 05:01:05 -07:00
|
|
|
aiAssistant.getters.askAssistantChat().should('not.be.visible');
|
2024-08-20 04:05:09 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should resize assistant chat up', () => {
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
|
|
|
|
aiAssistant.getters.askAssistantChat().then((element) => {
|
|
|
|
const { width, left } = element[0].getBoundingClientRect();
|
|
|
|
cy.drag(aiAssistant.getters.askAssistantSidebarResizer(), [left - 10, 0], {
|
|
|
|
abs: true,
|
|
|
|
clickToFinish: true,
|
|
|
|
});
|
|
|
|
aiAssistant.getters.askAssistantChat().then((newElement) => {
|
|
|
|
const newWidth = newElement[0].getBoundingClientRect().width;
|
|
|
|
expect(newWidth).to.be.greaterThan(width);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should resize assistant chat down', () => {
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
|
|
|
|
aiAssistant.getters.askAssistantChat().then((element) => {
|
|
|
|
const { width, left } = element[0].getBoundingClientRect();
|
|
|
|
cy.drag(aiAssistant.getters.askAssistantSidebarResizer(), [left + 10, 0], {
|
|
|
|
abs: true,
|
|
|
|
clickToFinish: true,
|
|
|
|
});
|
|
|
|
aiAssistant.getters.askAssistantChat().then((newElement) => {
|
|
|
|
const newWidth = newElement[0].getBoundingClientRect().width;
|
|
|
|
expect(newWidth).to.be.lessThan(width);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should start chat session from node error view', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Stop and Error');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesAll().should('have.length', 1);
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesAll()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'Hey, this is an assistant message');
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().should('be.disabled');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should render chat input correctly', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Stop and Error');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
// Send button should be disabled when input is empty
|
|
|
|
aiAssistant.getters.sendMessageButton().should('be.disabled');
|
|
|
|
aiAssistant.getters.chatInput().type('Yo ');
|
|
|
|
aiAssistant.getters.sendMessageButton().should('not.be.disabled');
|
|
|
|
aiAssistant.getters.chatInput().then((element) => {
|
|
|
|
const { height } = element[0].getBoundingClientRect();
|
|
|
|
// Shift + Enter should add a new line
|
|
|
|
aiAssistant.getters.chatInput().type('Hello{shift+enter}there');
|
|
|
|
aiAssistant.getters.chatInput().then((newElement) => {
|
|
|
|
const newHeight = newElement[0].getBoundingClientRect().height;
|
|
|
|
// Chat input should grow as user adds new lines
|
|
|
|
expect(newHeight).to.be.greaterThan(height);
|
|
|
|
aiAssistant.getters.sendMessageButton().click();
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
// New lines should be rendered as <br> in the chat
|
|
|
|
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
|
|
|
aiAssistant.getters.chatMessagesUser().eq(0).find('br').should('have.length', 1);
|
|
|
|
// Chat input should be cleared now
|
|
|
|
aiAssistant.getters.chatInput().should('have.value', '');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should render and handle quick replies', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/quick_reply_message_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Stop and Error');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
|
|
|
cy.wait('@chatRequest');
|
2024-09-03 01:43:24 -07:00
|
|
|
aiAssistant.getters.quickReplyButtons().should('have.length', 2);
|
|
|
|
aiAssistant.getters.quickReplyButtons().eq(0).click();
|
2024-08-20 04:05:09 -07:00
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
|
|
|
aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', "Sure, let's do it");
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should warn before starting a new session', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Edit Fields');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
2024-08-28 05:01:05 -07:00
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
2024-08-20 04:05:09 -07:00
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.closeChatButton().click();
|
|
|
|
ndv.getters.backToCanvas().click();
|
|
|
|
wf.actions.openNode('Stop and Error');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
2024-08-28 05:01:05 -07:00
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
2024-08-20 04:05:09 -07:00
|
|
|
// Since we already have an active session, a warning should be shown
|
|
|
|
aiAssistant.getters.newAssistantSessionModal().should('be.visible');
|
|
|
|
aiAssistant.getters
|
|
|
|
.newAssistantSessionModal()
|
|
|
|
.find('button')
|
|
|
|
.contains('Start new session')
|
|
|
|
.click();
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
// New session should start with initial assistant message
|
|
|
|
aiAssistant.getters.chatMessagesAll().should('have.length', 1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should apply code diff to code node', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/code_diff_suggestion_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat/apply-suggestion', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/apply_code_diff_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('applySuggestion');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Code');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true });
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
// Should have two assistant messages
|
|
|
|
aiAssistant.getters.chatMessagesAll().should('have.length', 2);
|
|
|
|
aiAssistant.getters.codeDiffs().should('have.length', 1);
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().first().click();
|
|
|
|
cy.wait('@applySuggestion');
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 0);
|
|
|
|
aiAssistant.getters.undoReplaceCodeButtons().should('have.length', 1);
|
|
|
|
aiAssistant.getters.codeReplacedMessage().should('be.visible');
|
|
|
|
ndv.getters
|
|
|
|
.parameterInput('jsCode')
|
|
|
|
.get('.cm-content')
|
|
|
|
.should('contain.text', 'item.json.myNewField = 1');
|
|
|
|
// Clicking undo should revert the code back but not call the assistant
|
|
|
|
aiAssistant.getters.undoReplaceCodeButtons().first().click();
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
|
|
|
aiAssistant.getters.codeReplacedMessage().should('not.exist');
|
|
|
|
cy.get('@applySuggestion.all').then((interceptions) => {
|
|
|
|
expect(interceptions).to.have.length(1);
|
|
|
|
});
|
|
|
|
ndv.getters
|
|
|
|
.parameterInput('jsCode')
|
|
|
|
.get('.cm-content')
|
|
|
|
.should('contain.text', 'item.json.myNewField = 1aaa');
|
|
|
|
// Replacing the code again should also not call the assistant
|
|
|
|
cy.get('@applySuggestion.all').then((interceptions) => {
|
|
|
|
expect(interceptions).to.have.length(1);
|
|
|
|
});
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().should('have.length', 1);
|
|
|
|
aiAssistant.getters.applyCodeDiffButtons().first().click();
|
|
|
|
ndv.getters
|
|
|
|
.parameterInput('jsCode')
|
|
|
|
.get('.cm-content')
|
|
|
|
.should('contain.text', 'item.json.myNewField = 1');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should end chat session when `end_session` event is received', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-08-20 04:05:09 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/end_session_response.json',
|
2024-08-20 04:05:09 -07:00
|
|
|
}).as('chatRequest');
|
2024-10-10 04:13:30 -07:00
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json');
|
2024-08-20 04:05:09 -07:00
|
|
|
wf.actions.openNode('Stop and Error');
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
aiAssistant.getters.nodeErrorViewAssistantButton().click();
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesSystem().should('have.length', 1);
|
|
|
|
aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended');
|
|
|
|
});
|
2024-09-05 01:54:35 -07:00
|
|
|
|
|
|
|
it('should reset session after it ended and sidebar is closed', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', (req) => {
|
2024-09-05 01:54:35 -07:00
|
|
|
req.reply((res) => {
|
|
|
|
if (['init-support-chat'].includes(req.body.payload.type)) {
|
2024-10-10 04:13:30 -07:00
|
|
|
res.send({
|
|
|
|
statusCode: 200,
|
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
|
|
|
});
|
2024-09-05 01:54:35 -07:00
|
|
|
} else {
|
2024-10-10 04:13:30 -07:00
|
|
|
res.send({ statusCode: 200, fixture: 'aiAssistant/responses/end_session_response.json' });
|
2024-09-05 01:54:35 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}).as('chatRequest');
|
|
|
|
aiAssistant.actions.openChat();
|
|
|
|
aiAssistant.actions.sendMessage('Hello');
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.actions.closeChat();
|
|
|
|
aiAssistant.actions.openChat();
|
|
|
|
// After closing and reopening the chat, all messages should be still there
|
|
|
|
aiAssistant.getters.chatMessagesAll().should('have.length', 2);
|
|
|
|
// End the session
|
|
|
|
aiAssistant.actions.sendMessage('Thanks, bye');
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesSystem().should('have.length', 1);
|
|
|
|
aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended');
|
|
|
|
aiAssistant.actions.closeChat();
|
|
|
|
aiAssistant.actions.openChat();
|
|
|
|
// Now, session should be reset
|
|
|
|
aiAssistant.getters.placeholderMessage().should('be.visible');
|
|
|
|
});
|
2024-09-06 02:06:51 -07:00
|
|
|
|
|
|
|
it('Should not reset assistant session when workflow is saved', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-09-06 02:06:51 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-09-06 02:06:51 -07:00
|
|
|
}).as('chatRequest');
|
|
|
|
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
|
|
|
aiAssistant.actions.openChat();
|
|
|
|
aiAssistant.actions.sendMessage('Hello');
|
|
|
|
wf.actions.openNode(SCHEDULE_TRIGGER_NODE_NAME);
|
|
|
|
ndv.getters.nodeExecuteButton().click();
|
|
|
|
wf.getters.isWorkflowSaved();
|
|
|
|
aiAssistant.getters.placeholderMessage().should('not.exist');
|
|
|
|
});
|
2024-11-07 07:22:13 -08:00
|
|
|
|
|
|
|
it('should send message via enter even with global NodeCreator panel opened', () => {
|
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
|
|
|
statusCode: 200,
|
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
|
|
|
}).as('chatRequest');
|
|
|
|
|
|
|
|
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
|
|
|
aiAssistant.actions.openChat();
|
|
|
|
nodeCreatorFeature.actions.openNodeCreator();
|
|
|
|
aiAssistant.getters.chatInput().type('Hello{Enter}');
|
|
|
|
|
|
|
|
aiAssistant.getters.placeholderMessage().should('not.exist');
|
|
|
|
});
|
2024-08-20 04:05:09 -07:00
|
|
|
});
|
2024-09-11 07:38:39 -07:00
|
|
|
|
|
|
|
describe('AI Assistant Credential Help', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
aiAssistant.actions.enableAssistant();
|
|
|
|
wf.actions.visit();
|
|
|
|
});
|
|
|
|
|
|
|
|
after(() => {
|
|
|
|
aiAssistant.actions.disableAssistant();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should start credential help from node credential', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-09-11 07:38:39 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-09-11 07:38:39 -07:00
|
|
|
}).as('chatRequest');
|
|
|
|
wf.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
|
|
|
wf.actions.addNodeToCanvas(GMAIL_NODE_NAME);
|
|
|
|
wf.actions.openNode('Gmail');
|
|
|
|
openCredentialSelect();
|
|
|
|
clickCreateNewCredential();
|
2024-09-23 08:38:35 -07:00
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.visible');
|
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').click();
|
2024-09-11 07:38:39 -07:00
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesUser()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'How do I set up the credentials for Gmail OAuth2 API?');
|
|
|
|
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesAssistant()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'Hey, this is an assistant message');
|
2024-09-23 08:38:35 -07:00
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.disabled');
|
2024-09-11 07:38:39 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should start credential help from credential list', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-09-11 07:38:39 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
2024-09-11 07:38:39 -07:00
|
|
|
}).as('chatRequest');
|
|
|
|
|
|
|
|
cy.visit(credentialsPage.url);
|
|
|
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
|
|
|
|
|
|
|
credentialsModal.getters.newCredentialModal().should('be.visible');
|
|
|
|
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
|
|
|
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
|
|
|
|
|
|
|
credentialsModal.getters.newCredentialTypeButton().click();
|
|
|
|
|
2024-09-23 08:38:35 -07:00
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.visible');
|
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').click();
|
2024-09-11 07:38:39 -07:00
|
|
|
cy.wait('@chatRequest');
|
|
|
|
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesUser()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'How do I set up the credentials for Notion API?');
|
|
|
|
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesAssistant()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'Hey, this is an assistant message');
|
2024-09-23 08:38:35 -07:00
|
|
|
aiAssistant.getters.credentialEditAssistantButton().find('button').should('be.disabled');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not show assistant button when click to connect', () => {
|
|
|
|
cy.intercept('/types/credentials.json', { middleware: true }, (req) => {
|
|
|
|
req.headers['cache-control'] = 'no-cache, no-store';
|
|
|
|
|
|
|
|
req.on('response', (res) => {
|
|
|
|
const credentials: ICredentialType[] = res.body || [];
|
|
|
|
|
|
|
|
const index = credentials.findIndex((c) => c.name === 'slackOAuth2Api');
|
|
|
|
|
|
|
|
credentials[index] = {
|
|
|
|
...credentials[index],
|
|
|
|
__overwrittenProperties: ['clientId', 'clientSecret'],
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
wf.actions.visit(true);
|
|
|
|
wf.actions.addNodeToCanvas('Manual');
|
|
|
|
wf.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
|
|
|
wf.getters.nodeCredentialsSelect().should('exist');
|
|
|
|
wf.getters.nodeCredentialsSelect().click();
|
|
|
|
getVisibleSelect().find('li').last().click();
|
|
|
|
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
|
|
|
ndv.getters.copyInput().should('not.exist');
|
|
|
|
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
|
|
|
credentialsModal.getters.credentialInputs().should('have.length', 0);
|
|
|
|
aiAssistant.getters.credentialEditAssistantButton().should('not.exist');
|
|
|
|
|
|
|
|
credentialsModal.getters.credentialAuthTypeRadioButtons().eq(1).click();
|
|
|
|
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
|
|
|
aiAssistant.getters.credentialEditAssistantButton().should('exist');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not show assistant button when click to connect with some fields', () => {
|
|
|
|
cy.intercept('/types/credentials.json', { middleware: true }, (req) => {
|
|
|
|
req.headers['cache-control'] = 'no-cache, no-store';
|
|
|
|
|
|
|
|
req.on('response', (res) => {
|
|
|
|
const credentials: ICredentialType[] = res.body || [];
|
|
|
|
|
|
|
|
const index = credentials.findIndex((c) => c.name === 'microsoftOutlookOAuth2Api');
|
|
|
|
|
|
|
|
credentials[index] = {
|
|
|
|
...credentials[index],
|
|
|
|
__overwrittenProperties: ['authUrl', 'accessTokenUrl', 'clientId', 'clientSecret'],
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
wf.actions.visit(true);
|
|
|
|
wf.actions.addNodeToCanvas('Manual');
|
|
|
|
wf.actions.addNodeToCanvas('Microsoft Outlook', true, true, 'Get a calendar');
|
|
|
|
wf.getters.nodeCredentialsSelect().should('exist');
|
|
|
|
wf.getters.nodeCredentialsSelect().click();
|
|
|
|
getVisibleSelect().find('li').last().click();
|
|
|
|
ndv.getters.copyInput().should('not.exist');
|
|
|
|
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
|
|
|
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
|
|
|
aiAssistant.getters.credentialEditAssistantButton().should('not.exist');
|
2024-09-11 07:38:39 -07:00
|
|
|
});
|
|
|
|
});
|
2024-09-16 03:27:44 -07:00
|
|
|
|
|
|
|
describe('General help', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
aiAssistant.actions.enableAssistant();
|
|
|
|
wf.actions.visit();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('assistant returns code snippet', () => {
|
2024-10-09 08:24:33 -07:00
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
2024-09-16 03:27:44 -07:00
|
|
|
statusCode: 200,
|
2024-10-10 04:13:30 -07:00
|
|
|
fixture: 'aiAssistant/responses/code_snippet_response.json',
|
2024-09-16 03:27:44 -07:00
|
|
|
}).as('chatRequest');
|
|
|
|
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().should('be.visible');
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.getters.askAssistantChat().should('be.visible');
|
|
|
|
aiAssistant.getters.placeholderMessage().should('be.visible');
|
|
|
|
aiAssistant.getters.chatInput().should('be.visible');
|
|
|
|
|
|
|
|
aiAssistant.getters.chatInput().type('Show me an expression');
|
|
|
|
aiAssistant.getters.sendMessageButton().click();
|
|
|
|
|
|
|
|
aiAssistant.getters.chatMessagesAll().should('have.length', 3);
|
|
|
|
aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', 'Show me an expression');
|
|
|
|
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesAssistant()
|
|
|
|
.eq(0)
|
|
|
|
.should('contain.text', 'To use expressions in n8n, follow these steps:');
|
|
|
|
|
|
|
|
aiAssistant.getters
|
|
|
|
.chatMessagesAssistant()
|
|
|
|
.eq(0)
|
|
|
|
.should(
|
|
|
|
'include.html',
|
|
|
|
`<pre><code class="language-json">[
|
|
|
|
{
|
|
|
|
"headers": {
|
|
|
|
"host": "n8n.instance.address",
|
|
|
|
...
|
|
|
|
},
|
|
|
|
"params": {},
|
|
|
|
"query": {},
|
|
|
|
"body": {
|
|
|
|
"name": "Jim",
|
|
|
|
"age": 30,
|
|
|
|
"city": "New York"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
</code></pre>`,
|
|
|
|
);
|
|
|
|
aiAssistant.getters.codeSnippet().should('have.text', '{{$json.body.city}}');
|
|
|
|
});
|
2024-10-10 04:13:30 -07:00
|
|
|
|
|
|
|
it('should send current context to support chat', () => {
|
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/simple_http_request_workflow.json');
|
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
|
|
|
statusCode: 200,
|
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
|
|
|
}).as('chatRequest');
|
|
|
|
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.actions.sendMessage('What is wrong with this workflow?');
|
|
|
|
|
|
|
|
cy.wait('@chatRequest').then((interception) => {
|
|
|
|
const { body } = interception.request;
|
|
|
|
// Body should contain the current workflow context
|
|
|
|
expect(body.payload).to.have.property('context');
|
|
|
|
expect(body.payload.context).to.have.property('currentView');
|
|
|
|
expect(body.payload.context.currentView.name).to.equal('NodeViewExisting');
|
|
|
|
expect(body.payload.context).to.have.property('currentWorkflow');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not send workflow context if nothing changed', () => {
|
|
|
|
cy.createFixtureWorkflow('aiAssistant/workflows/simple_http_request_workflow.json');
|
|
|
|
cy.intercept('POST', '/rest/ai/chat', {
|
|
|
|
statusCode: 200,
|
|
|
|
fixture: 'aiAssistant/responses/simple_message_response.json',
|
|
|
|
}).as('chatRequest');
|
|
|
|
|
|
|
|
aiAssistant.getters.askAssistantFloatingButton().click();
|
|
|
|
aiAssistant.actions.sendMessage('What is wrong with this workflow?');
|
|
|
|
cy.wait('@chatRequest');
|
|
|
|
|
|
|
|
// Send another message without changing workflow or executing any node
|
|
|
|
aiAssistant.actions.sendMessage('And now?');
|
|
|
|
|
|
|
|
cy.wait('@chatRequest').then((interception) => {
|
|
|
|
const { body } = interception.request;
|
|
|
|
// Workflow context should be empty
|
|
|
|
expect(body.payload).to.have.property('context');
|
|
|
|
expect(body.payload.context).not.to.have.property('currentWorkflow');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update http request node url
|
|
|
|
wf.actions.openNode('HTTP Request');
|
|
|
|
ndv.actions.typeIntoParameterInput('url', 'https://example.com');
|
|
|
|
ndv.actions.close();
|
|
|
|
// Also execute the workflow
|
|
|
|
wf.actions.executeWorkflow();
|
|
|
|
|
|
|
|
// Send another message
|
|
|
|
aiAssistant.actions.sendMessage('What about now?');
|
|
|
|
cy.wait('@chatRequest').then((interception) => {
|
|
|
|
const { body } = interception.request;
|
|
|
|
// Both workflow and execution context should be sent
|
|
|
|
expect(body.payload).to.have.property('context');
|
|
|
|
expect(body.payload.context).to.have.property('currentWorkflow');
|
|
|
|
expect(body.payload.context.currentWorkflow).not.to.be.empty;
|
|
|
|
expect(body.payload.context).to.have.property('executionData');
|
|
|
|
expect(body.payload.context.executionData).not.to.be.empty;
|
|
|
|
});
|
|
|
|
});
|
2024-09-16 03:27:44 -07:00
|
|
|
});
|