n8n/cypress/e2e/6-code-node.cy.ts

210 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { nanoid } from 'nanoid';
import { NDV } from '../pages/ndv';
import { successToast } from '../pages/notifications';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
const WorkflowPage = new WorkflowPageClass();
const ndv = new NDV();
describe('Code node', () => {
describe('Code editor', () => {
beforeEach(() => {
WorkflowPage.actions.visit();
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
WorkflowPage.actions.addNodeToCanvas('Code', true, true);
});
it('should show correct placeholders switching modes', () => {
cy.contains('// Loop over input items and add a new field').should('be.visible');
ndv.getters.parameterInput('mode').click();
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
cy.contains("// Add a new field called 'myNewField'").should('be.visible');
ndv.getters.parameterInput('mode').click();
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for All Items');
cy.contains('// Loop over input items and add a new field').should('be.visible');
});
it('should execute the placeholder successfully in both modes', () => {
ndv.actions.execute();
successToast().contains('Node executed successfully');
ndv.getters.parameterInput('mode').click();
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
ndv.actions.execute();
successToast().contains('Node executed successfully');
});
it('should show lint errors in `runOnceForAllItems` mode', () => {
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
const getEditor = () => getParameter().find('.cm-content').should('exist');
getEditor()
.type('{selectall}')
.paste(`$input.itemMatching()
$input.item
$('When clicking Test workflow').item
$input.first(1)
for (const item of $input.all()) {
item.foo
}
return
`);
getParameter().get('.cm-lint-marker-error').should('have.length', 6);
getParameter().contains('itemMatching').realHover();
cy.get('.cm-tooltip-lint').should(
'have.text',
'`.itemMatching()` expects an item index to be passed in as its argument.',
);
});
it('should show lint errors in `runOnceForEachItem` mode', () => {
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
const getEditor = () => getParameter().find('.cm-content').should('exist');
ndv.getters.parameterInput('mode').click();
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
getEditor()
.type('{selectall}')
.paste(`$input.itemMatching()
$input.all()
$input.first()
$input.item()
return []
`);
getParameter().get('.cm-lint-marker-error').should('have.length', 5);
getParameter().contains('all').realHover();
cy.get('.cm-tooltip-lint').should(
'have.text',
"Method `$input.all()` is only available in the 'Run Once for All Items' mode.",
);
});
});
describe('Ask AI', () => {
it('tab should display based on experiment', () => {
WorkflowPage.actions.visit();
cy.window().then((win) => {
win.featureFlags.override('011_ask_AI', 'control');
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
WorkflowPage.actions.addNodeToCanvas('Code');
WorkflowPage.actions.openNode('Code');
cy.getByTestId('code-node-tab-ai').should('not.exist');
ndv.actions.close();
win.featureFlags.override('011_ask_AI', undefined);
WorkflowPage.actions.openNode('Code');
cy.getByTestId('code-node-tab-ai').should('not.exist');
});
});
describe('Enabled', () => {
beforeEach(() => {
WorkflowPage.actions.visit();
cy.window().then((win) => {
win.featureFlags.override('011_ask_AI', 'gpt3');
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
WorkflowPage.actions.addNodeToCanvas('Code', true, true);
});
});
it('tab should exist if experiment selected and be selectable', () => {
cy.getByTestId('code-node-tab-ai').should('exist');
cy.get('#tab-ask-ai').click();
cy.contains('Hey AI, generate JavaScript').should('exist');
});
it('generate code button should have correct state & tooltips', () => {
cy.getByTestId('code-node-tab-ai').should('exist');
cy.get('#tab-ask-ai').click();
cy.getByTestId('ask-ai-cta').should('be.disabled');
cy.getByTestId('ask-ai-cta').realHover();
cy.getByTestId('ask-ai-cta-tooltip-no-input-data').should('exist');
ndv.actions.executePrevious();
cy.getByTestId('ask-ai-cta').realHover();
cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist');
cy.getByTestId('ask-ai-prompt-input')
// Type random 14 character string
.type(nanoid(14));
cy.getByTestId('ask-ai-cta').realHover();
cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist');
cy.getByTestId('ask-ai-prompt-input')
.clear()
// Type random 15 character string
.type(nanoid(15));
cy.getByTestId('ask-ai-cta').should('be.enabled');
cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600');
});
it('should send correct schema and replace code', () => {
const prompt = nanoid(20);
cy.get('#tab-ask-ai').click();
ndv.actions.executePrevious();
cy.getByTestId('ask-ai-prompt-input').type(prompt);
cy.intercept('POST', '/rest/ask-ai', {
statusCode: 200,
body: {
data: {
code: 'console.log("Hello World")',
},
},
}).as('ask-ai');
cy.getByTestId('ask-ai-cta').click();
const askAiReq = cy.wait('@ask-ai');
askAiReq
.its('request.body')
.should('have.keys', ['question', 'model', 'context', 'n8nVersion']);
askAiReq.its('context').should('have.keys', ['schema', 'ndvPushRef', 'pushRef']);
cy.contains('Code generation completed').should('be.visible');
cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")');
cy.get('#tab-code').should('have.class', 'is-active');
});
it('should show error based on status code', () => {
const prompt = nanoid(20);
cy.get('#tab-ask-ai').click();
ndv.actions.executePrevious();
cy.getByTestId('ask-ai-prompt-input').type(prompt);
const handledCodes = [
{ code: 400, message: 'Code generation failed due to an unknown reason' },
{ code: 413, message: 'Your workflow data is too large for AI to process' },
{ code: 429, message: "We've hit our rate limit with our AI partner" },
{ code: 500, message: 'Code generation failed due to an unknown reason' },
];
handledCodes.forEach(({ code, message }) => {
cy.intercept('POST', '/rest/ask-ai', {
statusCode: code,
status: code,
}).as('ask-ai');
cy.getByTestId('ask-ai-cta').click();
cy.contains(message).should('be.visible');
});
});
});
});
});