test: Migrate form trigger test from cypress -> playwright (#19453)

This commit is contained in:
Elias Meire
2025-09-15 13:59:58 +02:00
committed by GitHub
parent 2b0203ef54
commit 7ded694ce7
5 changed files with 99 additions and 116 deletions

View File

@@ -1,101 +0,0 @@
import { WorkflowPage, NDV } from '../pages';
import { getVisibleSelect } from '../utils';
const workflowPage = new WorkflowPage();
const ndv = new NDV();
describe('n8n Form Trigger', () => {
beforeEach(() => {
workflowPage.actions.visit();
});
it("add node by clicking on 'On form submission'", () => {
workflowPage.getters.canvasPlusButton().click();
workflowPage.getters.nodeCreatorNodeItems().contains('On form submission').click();
ndv.getters.parameterInput('formTitle').type('Test Form');
ndv.getters.parameterInput('formDescription').type('Test Form Description');
ndv.getters.backToCanvas().click();
workflowPage.getters.nodeIssuesByName('On form submission').should('not.exist');
});
it('should fill up form fields', () => {
workflowPage.actions.addInitialNodeToCanvas('n8n Form Trigger', {
isTrigger: true,
action: 'On new n8n Form event',
});
ndv.getters.parameterInput('formTitle').type('Test Form');
ndv.getters.parameterInput('formDescription').type('Test Form Description');
cy.get('[data-test-id="fixed-collection-add"]').click();
ndv.getters.parameterInput('fieldLabel').type('Test Field 1');
ndv.getters.parameterInput('fieldType').click();
//fill up first field of type number
getVisibleSelect().contains('Number').click();
cy.get(
'[data-test-id="parameter-input-requiredField"] > .parameter-input > .el-switch > .el-switch__core',
).click();
//fill up second field of type text
cy.get('.fixed-collection-parameter > :nth-child(2) > .button > span').click();
cy.get('.border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item')
.find('input[placeholder*="e.g. What is your name?"]')
.type('Test Field 2');
//fill up second field of type date
cy.get('.fixed-collection-parameter > :nth-child(2) > .button > span').click();
cy.get(
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
)
.find('input[placeholder*="e.g. What is your name?"]')
.type('Test Field 3');
cy.get(
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
).click();
getVisibleSelect().contains('Date').click();
// fill up second field of type dropdown
cy.get('.fixed-collection-parameter > :nth-child(2) > .button').click();
cy.get(
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
)
.find('input[placeholder*="e.g. What is your name?"]')
.type('Test Field 4');
cy.get(
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
).click();
getVisibleSelect().contains('Dropdown').click();
cy.contains('button', 'Add Field Option').click();
cy.contains('label', 'Field Options')
.parent()
.parent()
.nextAll()
.find('[data-test-id="parameter-input-field"]')
.eq(0)
.type('Option 1');
cy.contains('label', 'Field Options')
.parent()
.parent()
.nextAll()
.find('[data-test-id="parameter-input-field"]')
.eq(1)
.type('Option 2');
//add optional submitted message
cy.get('.param-options').click();
// wait for selector debounce
getVisibleSelect().find('span').contains('Form Response').click().wait(300);
cy.contains('span', 'Text to Show')
.should('exist')
.parent()
.parent()
.next()
.children()
.children()
.children()
.children()
.children()
.children()
.children()
.first()
.type('Your test form was successfully submitted');
ndv.getters.backToCanvas().click();
workflowPage.getters.nodeIssuesByName('On form submission').should('not.exist');
});
});

View File

@@ -44,6 +44,10 @@ export class CanvasPage extends BasePage {
return this.page.locator(`[data-test-id="canvas-node"][data-node-name="${nodeName}"]`); return this.page.locator(`[data-test-id="canvas-node"][data-node-name="${nodeName}"]`);
} }
nodeIssuesBadge(nodeName: string) {
return this.nodeByName(nodeName).getByTestId('node-issues');
}
nodeToolbar(nodeName: string): Locator { nodeToolbar(nodeName: string): Locator {
return this.nodeByName(nodeName).getByTestId('canvas-node-toolbar'); return this.nodeByName(nodeName).getByTestId('canvas-node-toolbar');
} }

View File

@@ -5,6 +5,7 @@ import { BasePage } from './BasePage';
import { RunDataPanel } from './components/RunDataPanel'; import { RunDataPanel } from './components/RunDataPanel';
import { NodeParameterHelper } from '../helpers/NodeParameterHelper'; import { NodeParameterHelper } from '../helpers/NodeParameterHelper';
import { EditFieldsNode } from './nodes/EditFieldsNode'; import { EditFieldsNode } from './nodes/EditFieldsNode';
import { locatorByIndex } from '../utils/index-helper';
export class NodeDetailsViewPage extends BasePage { export class NodeDetailsViewPage extends BasePage {
readonly setupHelper: NodeParameterHelper; readonly setupHelper: NodeParameterHelper;
@@ -46,8 +47,10 @@ export class NodeDetailsViewPage extends BasePage {
return this.getContainer().locator('.parameter-item').filter({ hasText: labelName }); return this.getContainer().locator('.parameter-item').filter({ hasText: labelName });
} }
async fillParameterInput(labelName: string, value: string) { async fillParameterInput(labelName: string, value: string, index?: number) {
await this.getParameterByLabel(labelName).getByTestId('parameter-input-field').fill(value); await locatorByIndex(this.getParameterByLabel(labelName), index)
.getByTestId('parameter-input-field')
.fill(value);
} }
async selectWorkflowResource(createItemText: string, searchText: string = '') { async selectWorkflowResource(createItemText: string, searchText: string = '') {
@@ -68,6 +71,10 @@ export class NodeDetailsViewPage extends BasePage {
await this.clickBackToCanvasButton(); await this.clickBackToCanvasButton();
} }
async addFixedCollectionItem() {
await this.clickByTestId('fixed-collection-add');
}
async execute() { async execute() {
await this.clickByTestId('node-execute-button'); await this.clickByTestId('node-execute-button');
} }
@@ -211,31 +218,29 @@ export class NodeDetailsViewPage extends BasePage {
await editor.type(text); await editor.type(text);
} }
getParameterInput(parameterName: string) { getParameterInput(parameterName: string, index?: number) {
return this.page.getByTestId(`parameter-input-${parameterName}`); return locatorByIndex(this.page.getByTestId(`parameter-input-${parameterName}`), index);
} }
getParameterInputField(parameterName: string) { getParameterInputField(parameterName: string, index?: number) {
return this.getParameterInput(parameterName).locator('input'); return this.getParameterInput(parameterName, index).locator('input');
} }
async selectOptionInParameterDropdown(parameterName: string, optionText: string) { async selectOptionInParameterDropdown(parameterName: string, optionText: string, index = 0) {
const dropdown = this.getParameterInput(parameterName); await this.clickParameterDropdown(parameterName, index);
await dropdown.click(); await this.selectFromVisibleDropdown(optionText);
await this.page.getByRole('option', { name: optionText }).click();
} }
async clickParameterDropdown(parameterName: string): Promise<void> { async clickParameterDropdown(parameterName: string, index = 0): Promise<void> {
await this.clickByTestId(`parameter-input-${parameterName}`); await locatorByIndex(this.page.getByTestId(`parameter-input-${parameterName}`), index).click();
} }
async selectFromVisibleDropdown(optionText: string): Promise<void> { async selectFromVisibleDropdown(optionText: string): Promise<void> {
await this.page.getByRole('option', { name: optionText }).click(); await this.page.getByRole('option', { name: optionText }).click();
} }
async fillParameterInputByName(parameterName: string, value: string): Promise<void> { async fillParameterInputByName(parameterName: string, value: string, index = 0): Promise<void> {
const input = this.getParameterInputField(parameterName); const input = this.getParameterInputField(parameterName, index);
await input.click(); await input.click();
await input.fill(value); await input.fill(value);
} }
@@ -244,6 +249,11 @@ export class NodeDetailsViewPage extends BasePage {
await this.page.locator('.param-options').click(); await this.page.locator('.param-options').click();
} }
async addParameterOptionByName(optionName: string): Promise<void> {
await this.clickParameterOptions();
await this.selectFromVisibleDropdown(optionName);
}
getVisiblePopper() { getVisiblePopper() {
return this.page.locator('.el-popper:visible'); return this.page.locator('.el-popper:visible');
} }

View File

@@ -0,0 +1,60 @@
import { test, expect } from '../../fixtures/base';
test.describe('Form Trigger', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromBlankCanvas();
});
test("add node by clicking on 'On form submission'", async ({ n8n }) => {
await n8n.canvas.clickNodeCreatorPlusButton();
await n8n.canvas.nodeCreatorItemByName('On form submission').click();
await n8n.ndv.fillParameterInput('Form Title', 'Test Form');
await n8n.ndv.fillParameterInput('Form Description', 'Test Form Description');
await n8n.ndv.clickBackToCanvasButton();
await expect(n8n.canvas.nodeByName('On form submission')).toBeVisible();
await expect(n8n.canvas.nodeIssuesBadge('On form submission')).toBeHidden();
});
test('should fill up form fields', async ({ n8n }) => {
await n8n.canvas.clickNodeCreatorPlusButton();
await n8n.canvas.nodeCreatorItemByName('On form submission').click();
await n8n.ndv.fillParameterInput('Form Title', 'Test Form');
await n8n.ndv.fillParameterInput('Form Description', 'Test Form Description');
// Add first field - Number type with required flag
await n8n.ndv.addFixedCollectionItem();
await n8n.ndv.fillParameterInputByName('fieldLabel', 'Test Field 1');
await n8n.ndv.selectOptionInParameterDropdown('fieldType', 'Number');
await n8n.ndv.setParameterSwitch('requiredField', true);
// Add second field - Text type
await n8n.ndv.addFixedCollectionItem();
await n8n.ndv.fillParameterInputByName('fieldLabel', 'Test Field 2', 1);
// Add third field - Date type
await n8n.ndv.addFixedCollectionItem();
await n8n.ndv.fillParameterInputByName('fieldLabel', 'Test Field 3', 2);
await n8n.ndv.selectOptionInParameterDropdown('fieldType', 'Date', 2);
// Add fourth field - Dropdown type with options
await n8n.ndv.addFixedCollectionItem();
await n8n.ndv.fillParameterInputByName('fieldLabel', 'Test Field 4', 3);
await n8n.ndv.selectOptionInParameterDropdown('fieldType', 'Dropdown', 3);
// Configure dropdown field options
await n8n.page.getByRole('button', { name: 'Add Field Option' }).click();
await n8n.ndv.fillParameterInputByName('option', 'Option 1');
await n8n.ndv.fillParameterInputByName('option', 'Option 2', 1);
// Add optional submitted message
await n8n.ndv.addParameterOptionByName('Form Response');
await n8n.ndv.fillParameterInput('Text to Show', 'Your test form was successfully submitted');
await n8n.ndv.clickBackToCanvasButton();
await expect(n8n.canvas.nodeByName('On form submission')).toBeVisible();
await expect(n8n.canvas.nodeIssuesBadge('On form submission')).toBeHidden();
});
});

View File

@@ -0,0 +1,10 @@
import type { Locator } from '@playwright/test';
/**
* Returns a locator for a specific element by index, or the original locator if no index is provided.
* Without an index, Playwright throws an error when multiple matching elements are found.
* @see https://playwright.dev/docs/locators#strictness
*/
export function locatorByIndex(locator: Locator, index?: number) {
return typeof index === 'number' ? locator.nth(index) : locator;
}