mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
test(editor): Migrate inline-expression-editor to playwright (#19411)
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
import { EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const ndv = new NDV();
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('Inline expression editor', () => {
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.actions.addInitialNodeToCanvas('Schedule');
|
||||
cy.on('uncaught:exception', (error) => error.name !== 'ExpressionError');
|
||||
});
|
||||
|
||||
describe('Basic UI functionality', () => {
|
||||
it('should open and close inline expression preview', () => {
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.openNode('Schedule');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('123');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^123$/);
|
||||
// click outside to close
|
||||
ndv.getters.outputPanel().click();
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('not.exist');
|
||||
});
|
||||
|
||||
it('should switch between expression and fixed using keyboard', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
WorkflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
|
||||
// Should switch to expression with =
|
||||
ndv.getters.assignmentCollectionAdd('assignments').click();
|
||||
ndv.actions.typeIntoParameterInput('value', '=');
|
||||
|
||||
// Should complete {{ --> {{ | }}
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().should('have.text', '{{ }}');
|
||||
|
||||
// Should switch back to fixed with backspace on empty expression
|
||||
ndv.actions.typeIntoParameterInput('value', '{selectall}{backspace}');
|
||||
ndv.getters.parameterInput('value').click();
|
||||
ndv.actions.typeIntoParameterInput('value', '{backspace}');
|
||||
ndv.getters.inlineExpressionEditorInput().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static data', () => {
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.openNode('Get many items');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
});
|
||||
|
||||
it('should resolve primitive resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('1 + 2');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^3$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('"ab"');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('{rightArrow}+');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('"cd"');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^abcd$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('true && false');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^false$/);
|
||||
});
|
||||
|
||||
it('should resolve object resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type('{ a: 1 }', { parseSpecialCharSequences: false });
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Object: \{"a": 1\}\]$/);
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type('{ a: 1 }.a', { parseSpecialCharSequences: false });
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/);
|
||||
});
|
||||
|
||||
it('should resolve array resolvables', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Array: \[1,2,3\]\]$/);
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('[0]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^1$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dynamic data', () => {
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.openNode('Schedule Trigger');
|
||||
ndv.actions.setPinnedData([{ myStr: 'Monday' }]);
|
||||
ndv.actions.close();
|
||||
WorkflowPage.actions.addNodeToCanvas('No Operation');
|
||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.openNode('Get many items');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
});
|
||||
|
||||
it('should resolve $parameter[]', () => {
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
// Resolving $parameter is slow, especially on CI runner
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$parameter["operation"]');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'getAll');
|
||||
});
|
||||
|
||||
it('should resolve input: $json,$input,$(nodeName)', () => {
|
||||
// Previous nodes have not run, input is empty
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type("$('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should('have.text', '[Execute previous nodes for preview]');
|
||||
|
||||
// Run workflow
|
||||
ndv.actions.close();
|
||||
WorkflowPage.actions.executeNode('No Operation, do nothing', { anchor: 'topLeft' });
|
||||
WorkflowPage.actions.openNode('Get many items');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
|
||||
// Previous nodes have run, input can be resolved
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$json.myStr');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().type('$input.item.json.myStr');
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday');
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().clear();
|
||||
WorkflowPage.getters.inlineExpressionEditorInput().click().type('{{');
|
||||
WorkflowPage.getters
|
||||
.inlineExpressionEditorInput()
|
||||
.type("$('Schedule Trigger').item.json.myStr");
|
||||
WorkflowPage.getters.inlineExpressionEditorOutput().should('have.text', 'Monday');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,6 +33,8 @@ export const AI_MEMORY_POSTGRES_NODE_NAME = 'Postgres Chat Memory';
|
||||
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';
|
||||
export const WEBHOOK_NODE_NAME = 'Webhook';
|
||||
export const EXECUTE_WORKFLOW_NODE_NAME = 'Execute Workflow';
|
||||
export const NO_OPERATION_NODE_NAME = 'No Operation, do nothing';
|
||||
export const HACKER_NEWS_NODE_NAME = 'Hacker News';
|
||||
|
||||
export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account';
|
||||
export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account';
|
||||
|
||||
@@ -141,7 +141,16 @@ export class NodeDetailsViewPage extends BasePage {
|
||||
.getByTestId('assignment-value');
|
||||
}
|
||||
|
||||
getInlineExpressionEditorInput() {
|
||||
/**
|
||||
* Get the inline expression editor input
|
||||
* @param parameterName - The name of the parameter to get the inline expression editor input for. If not set, gets the first inline expression editor input on page
|
||||
* @returns The inline expression editor input
|
||||
*/
|
||||
getInlineExpressionEditorInput(parameterName?: string) {
|
||||
if (parameterName) {
|
||||
const parameterInput = this.getParameterInput(parameterName);
|
||||
return parameterInput.getByTestId('inline-expression-editor-input');
|
||||
}
|
||||
return this.page.getByTestId('inline-expression-editor-input');
|
||||
}
|
||||
|
||||
@@ -165,15 +174,15 @@ export class NodeDetailsViewPage extends BasePage {
|
||||
return this.page.locator('.el-popper:visible');
|
||||
}
|
||||
|
||||
async clearExpressionEditor() {
|
||||
const editor = this.getInlineExpressionEditorInput();
|
||||
async clearExpressionEditor(parameterName?: string) {
|
||||
const editor = this.getInlineExpressionEditorInput(parameterName);
|
||||
await editor.click();
|
||||
await this.page.keyboard.press('ControlOrMeta+A');
|
||||
await this.page.keyboard.press('Delete');
|
||||
}
|
||||
|
||||
async typeInExpressionEditor(text: string) {
|
||||
const editor = this.getInlineExpressionEditorInput();
|
||||
async typeInExpressionEditor(text: string, parameterName?: string) {
|
||||
const editor = this.getInlineExpressionEditorInput(parameterName);
|
||||
await editor.click();
|
||||
await editor.type(text);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
NO_OPERATION_NODE_NAME,
|
||||
HACKER_NEWS_NODE_NAME,
|
||||
} from '../../config/constants';
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
const SCHEDULE_PARAMETER_NAME = 'daysInterval';
|
||||
const HACKER_NEWS_ACTION = 'Get many items';
|
||||
const HACKER_NEWS_PARAMETER_NAME = 'limit';
|
||||
|
||||
test.describe('Inline expression editor', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test.describe('Basic UI functionality', () => {
|
||||
test('should open and close inline expression preview', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.ndv.activateParameterExpressionEditor(SCHEDULE_PARAMETER_NAME);
|
||||
|
||||
await n8n.ndv.getInlineExpressionEditorInput(SCHEDULE_PARAMETER_NAME).click();
|
||||
await n8n.ndv.clearExpressionEditor(SCHEDULE_PARAMETER_NAME);
|
||||
await n8n.ndv.typeInExpressionEditor('{{ 123', SCHEDULE_PARAMETER_NAME);
|
||||
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('123');
|
||||
|
||||
// Click outside to close
|
||||
await n8n.ndv.outputPanel.get().click();
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toBeHidden();
|
||||
});
|
||||
|
||||
test('should switch between expression and fixed using keyboard', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
|
||||
// Should switch to expression with =
|
||||
await n8n.ndv.getAssignmentCollectionAdd('assignments').click();
|
||||
await n8n.ndv.fillParameterInputByName('value', '=');
|
||||
|
||||
// Should complete {{ --> {{ | }}
|
||||
await n8n.ndv.getInlineExpressionEditorInput().click();
|
||||
await n8n.ndv.typeInExpressionEditor('{{');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorInput()).toHaveText('{{ }}');
|
||||
|
||||
// Should switch back to fixed with backspace on empty expression
|
||||
await n8n.ndv.clearExpressionEditor('value');
|
||||
const parameterInput = n8n.ndv.getParameterInput('value');
|
||||
await parameterInput.click();
|
||||
await parameterInput.focus();
|
||||
await parameterInput.press('Backspace');
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await n8n.page.waitForTimeout(1000);
|
||||
await expect(n8n.ndv.getInlineExpressionEditorInput()).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Static data', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.ndv.activateParameterExpressionEditor(SCHEDULE_PARAMETER_NAME);
|
||||
});
|
||||
|
||||
test('should resolve primitive resolvables', async ({ n8n }) => {
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ 1 + 2');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('3');
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ "ab" + "cd"');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('abcd');
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ true && false');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('false');
|
||||
});
|
||||
|
||||
test('should resolve object resolvables', async ({ n8n }) => {
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ { a: 1 }');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText(
|
||||
/^\[Object: \{"a": 1\}\]$/,
|
||||
);
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ { a: 1 }.a');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('1');
|
||||
});
|
||||
|
||||
test('should resolve array resolvables', async ({ n8n }) => {
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ [1, 2, 3]');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText(/^\[Array: \[1,2,3\]\]$/);
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ [1, 2, 3][0]');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('1');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Dynamic data', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
await n8n.ndv.setPinnedData([{ myStr: 'Monday' }]);
|
||||
await n8n.ndv.close();
|
||||
await n8n.canvas.addNode(NO_OPERATION_NODE_NAME, { closeNDV: true });
|
||||
await n8n.canvas.addNode(HACKER_NEWS_NODE_NAME, { action: HACKER_NEWS_ACTION });
|
||||
await n8n.ndv.activateParameterExpressionEditor(HACKER_NEWS_PARAMETER_NAME);
|
||||
});
|
||||
|
||||
test('should resolve $parameter[]', async ({ n8n }) => {
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
// Resolving $parameter is slow, especially on CI runner
|
||||
await n8n.ndv.typeInExpressionEditor('{{ $parameter["operation"]');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('getAll');
|
||||
});
|
||||
|
||||
test('should resolve input: $json,$input,$(nodeName)', async ({ n8n }) => {
|
||||
// Previous nodes have not run, input is empty
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ $json.myStr');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText(
|
||||
'[Execute previous nodes for preview]',
|
||||
);
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ $input.item.json.myStr');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText(
|
||||
'[Execute previous nodes for preview]',
|
||||
);
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor("{{ $('No Operation, do nothing').item.json.myStr");
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText(
|
||||
'[Execute previous nodes for preview]',
|
||||
);
|
||||
|
||||
// Run workflow
|
||||
await n8n.ndv.close();
|
||||
await n8n.canvas.executeNode(NO_OPERATION_NODE_NAME);
|
||||
await n8n.canvas.openNode(HACKER_NEWS_ACTION);
|
||||
await n8n.ndv.activateParameterExpressionEditor(HACKER_NEWS_PARAMETER_NAME);
|
||||
|
||||
// Previous nodes have run, input can be resolved
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ $json.myStr');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('Monday');
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor('{{ $input.item.json.myStr');
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('Monday');
|
||||
|
||||
await n8n.ndv.clearExpressionEditor();
|
||||
await n8n.ndv.typeInExpressionEditor("{{ $('No Operation, do nothing').item.json.myStr");
|
||||
await expect(n8n.ndv.getInlineExpressionEditorOutput()).toHaveText('Monday');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user