From 5df0ca908eddd9d04a90788b0a6a41c0ee08c70f Mon Sep 17 00:00:00 2001 From: shortstacked Date: Tue, 19 Aug 2025 16:13:43 +0100 Subject: [PATCH] test: Migrate NPS from Cypress -> Playwright (#18535) --- cypress/e2e/42-nps-survey.cy.ts | 149 ------------------ .../testing/playwright/pages/CanvasPage.ts | 11 ++ .../testing/playwright/pages/NpsSurveyPage.ts | 57 +++++++ packages/testing/playwright/pages/n8nPage.ts | 3 + .../playwright/tests/ui/42-nps-survey.spec.ts | 144 +++++++++++++++++ 5 files changed, 215 insertions(+), 149 deletions(-) delete mode 100644 cypress/e2e/42-nps-survey.cy.ts create mode 100644 packages/testing/playwright/pages/NpsSurveyPage.ts create mode 100644 packages/testing/playwright/tests/ui/42-nps-survey.spec.ts diff --git a/cypress/e2e/42-nps-survey.cy.ts b/cypress/e2e/42-nps-survey.cy.ts deleted file mode 100644 index c34124d3df..0000000000 --- a/cypress/e2e/42-nps-survey.cy.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { INSTANCE_ADMIN } from '../constants'; -import { clearNotifications } from '../pages/notifications'; -import { - getNpsSurvey, - getNpsSurveyClose, - getNpsSurveyFeedback, - getNpsSurveyRatings, - getNpsSurveySubmit, -} from '../pages/npsSurvey'; -import { WorkflowPage } from '../pages/workflow'; - -const workflowPage = new WorkflowPage(); - -const NOW = Date.now(); -const ONE_DAY = 24 * 60 * 60 * 1000; -const THREE_DAYS = ONE_DAY * 3; -const SEVEN_DAYS = ONE_DAY * 7; -const ABOUT_SIX_MONTHS = ONE_DAY * 30 * 6 + ONE_DAY; - -// eslint-disable-next-line n8n-local-rules/no-skipped-tests -describe.skip('NpsSurvey', () => { - beforeEach(() => { - cy.resetDatabase(); - cy.signin(INSTANCE_ADMIN); - }); - - it('shows nps survey to recently activated user and can submit feedback ', () => { - cy.intercept('/rest/settings', { middleware: true }, (req) => { - req.on('response', (res) => { - if (res.body.data) { - res.body.data.telemetry = { - enabled: true, - config: { - key: 'test', - url: 'https://telemetry-test.n8n.io', - proxy: 'http://localhost:5678/rest/telemetry/proxy', - sourceConfig: 'http://localhost:5678/rest/telemetry/rudderstack', - }, - }; - } - }); - }); - - cy.intercept('/rest/login', { middleware: true }, (req) => { - req.on('response', (res) => { - if (res.body.data) { - res.body.data.settings = res.body.data.settings || {}; - res.body.data.settings.userActivated = true; - res.body.data.settings.userActivatedAt = NOW - THREE_DAYS - 1000; - } - }); - }); - - workflowPage.actions.visit(true, NOW); - - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('be.visible'); - getNpsSurveyRatings().find('button').should('have.length', 11); - getNpsSurveyRatings().find('button').first().click(); - - getNpsSurveyFeedback().find('textarea').type('n8n is the best'); - getNpsSurveySubmit().find('button').click(); - - // test that modal does not show up again until 6 months later - workflowPage.actions.visit(true, NOW + ONE_DAY); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('not.be.visible'); - - // 6 months later - workflowPage.actions.visit(true, NOW + ABOUT_SIX_MONTHS); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('be.visible'); - }); - - it('allows user to ignore survey 3 times before stopping to show until 6 months later', () => { - cy.intercept('/rest/settings', { middleware: true }, (req) => { - req.on('response', (res) => { - if (res.body.data) { - res.body.data.telemetry = { - enabled: true, - config: { - key: 'test', - url: 'https://telemetry-test.n8n.io', - proxy: 'http://localhost:5678/rest/telemetry/proxy', - sourceConfig: 'http://localhost:5678/rest/telemetry/rudderstack', - }, - }; - } - }); - }); - - cy.intercept('/rest/login', { middleware: true }, (req) => { - req.on('response', (res) => { - if (res.body.data) { - res.body.data.settings = res.body.data.settings || {}; - res.body.data.settings.userActivated = true; - res.body.data.settings.userActivatedAt = NOW - THREE_DAYS - 1000; - } - }); - }); - - // can ignore survey and it won't show up again - workflowPage.actions.visit(true, NOW); - workflowPage.actions.saveWorkflowOnButtonClick(); - clearNotifications(); - - getNpsSurvey().should('be.visible'); - getNpsSurveyClose().click(); - getNpsSurvey().should('not.be.visible'); - - workflowPage.actions.visit(true, NOW + ONE_DAY); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('not.be.visible'); - - // shows up seven days later to ignore again - workflowPage.actions.visit(true, NOW + SEVEN_DAYS + 10000); - workflowPage.actions.saveWorkflowOnButtonClick(); - clearNotifications(); - getNpsSurvey().should('be.visible'); - getNpsSurveyClose().click(); - getNpsSurvey().should('not.be.visible'); - - workflowPage.actions.visit(true, NOW + SEVEN_DAYS + 10000); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('not.be.visible'); - - // shows up after at least seven days later to ignore again - workflowPage.actions.visit(true, NOW + (SEVEN_DAYS + 10000) * 2 + ONE_DAY); - workflowPage.actions.saveWorkflowOnButtonClick(); - clearNotifications(); - getNpsSurvey().should('be.visible'); - getNpsSurveyClose().click(); - getNpsSurvey().should('not.be.visible'); - - workflowPage.actions.visit(true, NOW + (SEVEN_DAYS + 10000) * 2 + ONE_DAY * 2); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('not.be.visible'); - - // does not show up again after at least 7 days - workflowPage.actions.visit(true, NOW + (SEVEN_DAYS + 10000) * 3 + ONE_DAY * 3); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('not.be.visible'); - - // shows up 6 months later - workflowPage.actions.visit(true, NOW + (SEVEN_DAYS + 10000) * 3 + ABOUT_SIX_MONTHS); - workflowPage.actions.saveWorkflowOnButtonClick(); - getNpsSurvey().should('be.visible'); - }); -}); diff --git a/packages/testing/playwright/pages/CanvasPage.ts b/packages/testing/playwright/pages/CanvasPage.ts index 0480d5cebc..3c6fb8b571 100644 --- a/packages/testing/playwright/pages/CanvasPage.ts +++ b/packages/testing/playwright/pages/CanvasPage.ts @@ -423,4 +423,15 @@ export class CanvasPage extends BasePage { await this.canvasPane().focus(); await this.page.keyboard.press(keyMap[direction]); } + + /** + * Visit the workflow page with a specific timestamp for NPS survey testing. + * Uses Playwright's clock API to set a fixed time. + */ + async visitWithTimestamp(timestamp: number): Promise { + // Set fixed time using Playwright's clock API + await this.page.clock.setFixedTime(timestamp); + + await this.page.goto('/workflow/new'); + } } diff --git a/packages/testing/playwright/pages/NpsSurveyPage.ts b/packages/testing/playwright/pages/NpsSurveyPage.ts new file mode 100644 index 0000000000..b8cb417c20 --- /dev/null +++ b/packages/testing/playwright/pages/NpsSurveyPage.ts @@ -0,0 +1,57 @@ +import type { Locator, Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class NpsSurveyPage extends BasePage { + constructor(page: Page) { + super(page); + } + + getNpsSurveyModal(): Locator { + return this.page.getByTestId('nps-survey-modal'); + } + + getNpsSurveyRatings(): Locator { + return this.page.getByTestId('nps-survey-ratings'); + } + + getNpsSurveyFeedback(): Locator { + return this.page.getByTestId('nps-survey-feedback'); + } + + getNpsSurveySubmitButton(): Locator { + return this.page.getByTestId('nps-survey-feedback-button'); + } + + getNpsSurveyCloseButton(): Locator { + return this.getNpsSurveyModal().locator('button.el-drawer__close-btn'); + } + + getRatingButton(rating: number): Locator { + return this.getNpsSurveyRatings().locator('button').nth(rating); + } + + getFeedbackTextarea(): Locator { + return this.getNpsSurveyFeedback().locator('textarea'); + } + + async clickRating(rating: number): Promise { + await this.getRatingButton(rating).click(); + } + + async fillFeedback(feedback: string): Promise { + await this.getFeedbackTextarea().fill(feedback); + } + + async clickSubmitButton(): Promise { + await this.getNpsSurveySubmitButton().click(); + } + + async closeSurvey(): Promise { + await this.getNpsSurveyCloseButton().click(); + } + + async getRatingButtonCount(): Promise { + return await this.getNpsSurveyRatings().locator('button').count(); + } +} diff --git a/packages/testing/playwright/pages/n8nPage.ts b/packages/testing/playwright/pages/n8nPage.ts index c4167f2979..969e9af5fa 100644 --- a/packages/testing/playwright/pages/n8nPage.ts +++ b/packages/testing/playwright/pages/n8nPage.ts @@ -8,6 +8,7 @@ import { ExecutionsPage } from './ExecutionsPage'; import { IframePage } from './IframePage'; import { NodeDisplayViewPage } from './NodeDisplayViewPage'; import { NotificationsPage } from './NotificationsPage'; +import { NpsSurveyPage } from './NpsSurveyPage'; import { ProjectSettingsPage } from './ProjectSettingsPage'; import { SettingsPage } from './SettingsPage'; import { SidebarPage } from './SidebarPage'; @@ -31,6 +32,7 @@ export class n8nPage { readonly iframe: IframePage; readonly ndv: NodeDisplayViewPage; + readonly npsSurvey: NpsSurveyPage; readonly projectSettings: ProjectSettingsPage; readonly settings: SettingsPage; readonly versions: VersionsPage; @@ -60,6 +62,7 @@ export class n8nPage { this.iframe = new IframePage(page); this.ndv = new NodeDisplayViewPage(page); + this.npsSurvey = new NpsSurveyPage(page); this.projectSettings = new ProjectSettingsPage(page); this.settings = new SettingsPage(page); this.versions = new VersionsPage(page); diff --git a/packages/testing/playwright/tests/ui/42-nps-survey.spec.ts b/packages/testing/playwright/tests/ui/42-nps-survey.spec.ts new file mode 100644 index 0000000000..bdd62b5330 --- /dev/null +++ b/packages/testing/playwright/tests/ui/42-nps-survey.spec.ts @@ -0,0 +1,144 @@ +import { test, expect } from '../../fixtures/base'; +import type { TestRequirements } from '../../Types'; + +const NOW = Date.now(); +const ONE_DAY = 24 * 60 * 60 * 1000; +const THREE_DAYS = ONE_DAY * 3; +const SEVEN_DAYS = ONE_DAY * 7; +const ABOUT_SIX_MONTHS = ONE_DAY * 30 * 6 + ONE_DAY; + +const ACTIVATED_USER_SETTINGS = { + userActivated: true, + userActivatedAt: NOW - THREE_DAYS - 1000, +}; + +const getNpsTestRequirements: TestRequirements = { + config: { + settings: { + telemetry: { + enabled: true, + }, + }, + }, + intercepts: { + npsSurveyApi: { + url: '**/rest/user-settings/nps-survey', + response: { success: true }, + }, + telemetryTest: { + url: '**/test/telemetry', + response: { status: 'ok' }, + }, + telemetryProxy: { + url: '**/rest/telemetry/proxy', + response: { status: 'ok' }, + }, + telemetryRudderstack: { + url: '**/rest/telemetry/rudderstack', + response: { status: 'ok' }, + }, + }, +}; + +test.describe('NPS Survey', () => { + test.beforeEach(async ({ n8n }) => { + await n8n.page.route('**/rest/login', async (route) => { + const response = await route.fetch(); + const originalJson = await response.json(); + + const modifiedData = { + ...originalJson, + data: { + ...originalJson.data, + settings: { + ...originalJson.data?.settings, + ...ACTIVATED_USER_SETTINGS, + }, + }, + }; + + await route.fulfill({ + status: response.status(), + headers: response.headers(), + contentType: 'application/json', + body: JSON.stringify(modifiedData), + }); + }); + + await n8n.goHome(); + }); + + test('shows nps survey to recently activated user and can submit feedback', async ({ + n8n, + setupRequirements, + }) => { + await setupRequirements(getNpsTestRequirements); + await n8n.canvas.visitWithTimestamp(NOW); + await n8n.canvas.clickSaveWorkflowButton(); + + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + expect(await n8n.npsSurvey.getRatingButtonCount()).toBe(11); + + await n8n.npsSurvey.clickRating(0); + await n8n.npsSurvey.fillFeedback('n8n is the best'); + await n8n.npsSurvey.clickSubmitButton(); + + await n8n.canvas.visitWithTimestamp(NOW + ONE_DAY); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + ABOUT_SIX_MONTHS); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + }); + + test('allows user to ignore survey 3 times before stopping to show until 6 months later', async ({ + n8n, + setupRequirements, + }) => { + await setupRequirements(getNpsTestRequirements); + await n8n.canvas.visitWithTimestamp(NOW); + await n8n.canvas.clickSaveWorkflowButton(); + await n8n.notifications.quickCloseAll(); + + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + await n8n.npsSurvey.closeSurvey(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + ONE_DAY); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + SEVEN_DAYS + 10000); + await n8n.canvas.clickSaveWorkflowButton(); + await n8n.notifications.quickCloseAll(); + + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + await n8n.npsSurvey.closeSurvey(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + SEVEN_DAYS + 10000); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + (SEVEN_DAYS + 10000) * 2 + ONE_DAY); + await n8n.canvas.clickSaveWorkflowButton(); + await n8n.notifications.quickCloseAll(); + + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + await n8n.npsSurvey.closeSurvey(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + (SEVEN_DAYS + 10000) * 2 + ONE_DAY * 2); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + (SEVEN_DAYS + 10000) * 3 + ONE_DAY * 3); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeHidden(); + + await n8n.canvas.visitWithTimestamp(NOW + (SEVEN_DAYS + 10000) * 3 + ABOUT_SIX_MONTHS); + await n8n.canvas.clickSaveWorkflowButton(); + await expect(n8n.npsSurvey.getNpsSurveyModal()).toBeVisible(); + }); +});