From f1c52461cfa6502dc1d5e72fb9f562805f9bd2a5 Mon Sep 17 00:00:00 2001 From: Svetoslav Dekov Date: Fri, 29 Aug 2025 14:14:38 +0300 Subject: [PATCH] test: Migrate 28-resource-mapper to playwright (no-changelog) (#18944) --- cypress/e2e/28-resource-mapper.cy.ts | 95 ------------------- .../src/components/ParameterInputFull.vue | 1 + .../testing/playwright/config/constants.ts | 1 + .../playwright/pages/NodeDetailsViewPage.ts | 51 +++++++++- .../tests/ui/28-resource-mapper.spec.ts | 78 +++++++++++++++ 5 files changed, 127 insertions(+), 99 deletions(-) delete mode 100644 cypress/e2e/28-resource-mapper.cy.ts create mode 100644 packages/testing/playwright/tests/ui/28-resource-mapper.spec.ts diff --git a/cypress/e2e/28-resource-mapper.cy.ts b/cypress/e2e/28-resource-mapper.cy.ts deleted file mode 100644 index 3f5909de62..0000000000 --- a/cypress/e2e/28-resource-mapper.cy.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { WorkflowPage, NDV } from '../pages'; - -const workflowPage = new WorkflowPage(); -const ndv = new NDV(); - -describe('Resource Mapper', () => { - beforeEach(() => { - workflowPage.actions.visit(); - }); - - it('should not retrieve list options when required params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { - action: 'Resource Mapping Component', - }); - - ndv.getters - .resourceMapperFieldsContainer() - .should('be.visible') - .findChildByTestId('parameter-input') - .should('have.length', 3); - - ndv.actions.setInvalidExpression({ fieldName: 'fieldId' }); - - ndv.actions.refreshResourceMapperColumns(); - ndv.getters.resourceMapperFieldsContainer().should('not.exist'); - }); - - it('should retrieve list options when optional params throw errors', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { - action: 'Resource Mapping Component', - }); - - ndv.getters - .resourceMapperFieldsContainer() - .should('be.visible') - .findChildByTestId('parameter-input') - .should('have.length', 3); - - ndv.actions.setInvalidExpression({ fieldName: 'otherField' }); - - ndv.actions.refreshResourceMapperColumns(); - ndv.getters - .resourceMapperFieldsContainer() - .should('be.visible') - .findChildByTestId('parameter-input') - .should('have.length', 3); - }); - - it('should correctly delete single field', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { - action: 'Resource Mapping Component', - }); - ndv.getters.parameterInput('id').type('001'); - ndv.getters.parameterInput('name').type('John'); - ndv.getters.parameterInput('age').type('30'); - ndv.getters.nodeExecuteButton().click(); - ndv.getters.outputTableHeaderByText('id').should('exist'); - ndv.getters.outputTableHeaderByText('name').should('exist'); - ndv.getters.outputTableHeaderByText('age').should('exist'); - // Remove the 'name' field - ndv.getters.resourceMapperRemoveFieldButton('name').should('exist').click({ force: true }); - ndv.getters.nodeExecuteButton().click(); - ndv.getters.parameterInput('id').should('exist'); - ndv.getters.outputTableHeaderByText('id').should('exist'); - // After removing the field, text field and the output table column for the 'name' should not be there anymore - ndv.getters.parameterInput('age').should('exist'); - ndv.getters.outputTableHeaderByText('age').should('exist'); - ndv.getters.parameterInput('name').should('not.exist'); - ndv.getters.outputTableHeaderByText('name').should('not.exist'); - }); - - it('should correctly delete all fields', () => { - workflowPage.actions.addInitialNodeToCanvas('E2e Test', { - action: 'Resource Mapping Component', - }); - ndv.getters.parameterInput('id').type('001'); - ndv.getters.parameterInput('name').type('John'); - ndv.getters.parameterInput('age').type('30'); - ndv.getters.nodeExecuteButton().click(); - ndv.getters.outputTableHeaderByText('id').should('exist'); - ndv.getters.outputTableHeaderByText('name').should('exist'); - ndv.getters.outputTableHeaderByText('age').should('exist'); - ndv.getters.resourceMapperColumnsOptionsButton().click(); - // Click on the 'Remove All Fields' option - ndv.getters.resourceMapperRemoveAllFieldsOption().should('be.visible').click(); - ndv.getters.nodeExecuteButton().click(); - ndv.getters.parameterInput('id').should('exist'); - ndv.getters.outputTableHeaderByText('id').should('exist'); - // After removing the all fields, only required one should be in UI and output table - ndv.getters.parameterInput('name').should('not.exist'); - ndv.getters.outputTableHeaderByText('name').should('not.exist'); - ndv.getters.parameterInput('age').should('not.exist'); - ndv.getters.outputTableHeaderByText('age').should('not.exist'); - }); -}); diff --git a/packages/frontend/editor-ui/src/components/ParameterInputFull.vue b/packages/frontend/editor-ui/src/components/ParameterInputFull.vue index a1122d06ad..1b15c6a5c0 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInputFull.vue @@ -318,6 +318,7 @@ function removeOverride(clearField = false) { :options-position="optionsPosition" :bold="false" :size="label.size" + :input-name="parameter.name" color="text-dark" @mouseenter="onWrapperMouseEnter" @mouseleave="onWrapperMouseLeave" diff --git a/packages/testing/playwright/config/constants.ts b/packages/testing/playwright/config/constants.ts index 547dd536cd..dcad2f99c9 100644 --- a/packages/testing/playwright/config/constants.ts +++ b/packages/testing/playwright/config/constants.ts @@ -38,6 +38,7 @@ export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account'; export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account'; export const NEW_NOTION_ACCOUNT_NAME = 'Notion account'; export const NEW_QUERY_AUTH_ACCOUNT_NAME = 'Query Auth account'; +export const E2E_TEST_NODE_NAME = 'E2E Test'; export const ROUTES = { NEW_WORKFLOW_PAGE: '/workflow/new', diff --git a/packages/testing/playwright/pages/NodeDetailsViewPage.ts b/packages/testing/playwright/pages/NodeDetailsViewPage.ts index a15b1275b3..1a133f4256 100644 --- a/packages/testing/playwright/pages/NodeDetailsViewPage.ts +++ b/packages/testing/playwright/pages/NodeDetailsViewPage.ts @@ -70,6 +70,19 @@ export class NodeDetailsViewPage extends BasePage { return this.page.getByTestId('parameter-expression-preview-value'); } + getInlineExpressionEditorPreview() { + return this.page.getByTestId('inline-expression-editor-output'); + } + + async activateParameterExpressionEditor(parameterName: string) { + const parameterInput = this.getParameterInput(parameterName); + await parameterInput.click(); + await this.page + .getByTestId(`${parameterName}-parameter-input-options-container`) + .getByTestId('radio-button-expression') + .click(); + } + getEditPinnedDataButton() { return this.page.getByTestId('ndv-edit-pinned-data'); } @@ -210,7 +223,7 @@ export class NodeDetailsViewPage extends BasePage { * @param parameterName - The name of the parameter */ getParameterInputField(parameterName: string) { - return this.getParameterInput(parameterName).getByTestId('parameter-input-field'); + return this.getParameterInput(parameterName).locator('input'); } /** @@ -267,9 +280,7 @@ export class NodeDetailsViewPage extends BasePage { * Ported from Cypress pattern with Playwright selectors */ getVisiblePopper() { - return this.page - .locator('.el-popper') - .filter({ hasNot: this.page.locator('[aria-hidden="true"]') }); + return this.page.locator('.el-popper:visible'); } /** @@ -517,6 +528,38 @@ export class NodeDetailsViewPage extends BasePage { .getByTestId('assignment-name'); } + getResourceMapperFieldsContainer() { + return this.page.getByTestId('mapping-fields-container'); + } + + getResourceMapperParameterInputs() { + return this.getResourceMapperFieldsContainer().getByTestId('parameter-input'); + } + + getResourceMapperSelectColumn() { + return this.page.getByTestId('matching-column-select'); + } + + getResourceMapperColumnsOptionsButton() { + return this.page.getByTestId('columns-parameter-input-options-container'); + } + + getResourceMapperRemoveFieldButton(fieldName: string) { + return this.page.getByTestId(`remove-field-button-${fieldName}`); + } + + getResourceMapperRemoveAllFieldsOption() { + return this.page.getByTestId('action-removeAllFields'); + } + + async refreshResourceMapperColumns() { + const selectColumn = this.getResourceMapperSelectColumn(); + await selectColumn.hover(); + await selectColumn.getByTestId('action-toggle').click(); + await expect(this.getVisiblePopper().getByTestId('action-refreshFieldList')).toBeVisible(); + await this.getVisiblePopper().getByTestId('action-refreshFieldList').click(); + } + getAddValueButton() { return this.getNodeParameters().locator('input[placeholder*="Add Value"]'); } diff --git a/packages/testing/playwright/tests/ui/28-resource-mapper.spec.ts b/packages/testing/playwright/tests/ui/28-resource-mapper.spec.ts new file mode 100644 index 0000000000..949150ec2a --- /dev/null +++ b/packages/testing/playwright/tests/ui/28-resource-mapper.spec.ts @@ -0,0 +1,78 @@ +import { E2E_TEST_NODE_NAME } from '../../config/constants'; +import { test, expect } from '../../fixtures/base'; + +test.describe('Resource Mapper', () => { + test.beforeEach(async ({ n8n }) => { + await n8n.goHome(); + await n8n.workflows.clickAddWorkflowButton(); + await n8n.canvas.addNode(E2E_TEST_NODE_NAME, { action: 'Resource Mapping Component' }); + }); + + test('should not retrieve list options when required params throw errors', async ({ n8n }) => { + const fieldsContainer = n8n.ndv.getResourceMapperFieldsContainer(); + await expect(fieldsContainer).toBeVisible(); + await expect(n8n.ndv.getResourceMapperParameterInputs()).toHaveCount(3); + + await n8n.ndv.activateParameterExpressionEditor('fieldId'); + await n8n.ndv.typeInExpressionEditor("{{ $('unknown')"); + await expect(n8n.ndv.getInlineExpressionEditorPreview()).toContainText("node doesn't exist"); + + await n8n.ndv.refreshResourceMapperColumns(); + + await expect(n8n.ndv.getResourceMapperFieldsContainer()).toHaveCount(0); + }); + + test('should retrieve list options when optional params throw errors', async ({ n8n }) => { + await n8n.ndv.activateParameterExpressionEditor('otherField'); + await n8n.ndv.typeInExpressionEditor("{{ $('unknown')"); + await expect(n8n.ndv.getInlineExpressionEditorPreview()).toContainText("node doesn't exist"); + + await n8n.ndv.refreshResourceMapperColumns(); + + await expect(n8n.ndv.getResourceMapperFieldsContainer()).toBeVisible(); + await expect(n8n.ndv.getResourceMapperParameterInputs()).toHaveCount(3); + }); + + test('should correctly delete single field', async ({ n8n }) => { + await n8n.ndv.fillParameterInputByName('id', '001'); + await n8n.ndv.fillParameterInputByName('name', 'John'); + await n8n.ndv.fillParameterInputByName('age', '30'); + await n8n.ndv.execute(); + + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'id' })).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'name' })).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'age' })).toBeVisible(); + + await n8n.ndv.getResourceMapperRemoveFieldButton('name').click(); + await n8n.ndv.execute(); + + await expect(n8n.ndv.getParameterInput('id')).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'id' })).toBeVisible(); + await expect(n8n.ndv.getParameterInput('age')).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'age' })).toBeVisible(); + await expect(n8n.ndv.getParameterInput('name')).toHaveCount(0); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'name' })).toHaveCount(0); + }); + + test('should correctly delete all fields', async ({ n8n }) => { + await n8n.ndv.fillParameterInputByName('id', '001'); + await n8n.ndv.fillParameterInputByName('name', 'John'); + await n8n.ndv.fillParameterInputByName('age', '30'); + await n8n.ndv.execute(); + + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'id' })).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'name' })).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'age' })).toBeVisible(); + + await n8n.ndv.getResourceMapperColumnsOptionsButton().click(); + await n8n.ndv.getResourceMapperRemoveAllFieldsOption().click(); + await n8n.ndv.execute(); + + await expect(n8n.ndv.getParameterInput('id')).toBeVisible(); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'id' })).toBeVisible(); + await expect(n8n.ndv.getParameterInput('name')).toHaveCount(0); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'name' })).toHaveCount(0); + await expect(n8n.ndv.getParameterInput('age')).toHaveCount(0); + await expect(n8n.ndv.getOutputTableHeaders().filter({ hasText: 'age' })).toHaveCount(0); + }); +});