From 7dd89d77d90ede54f2e8362ead30190f23d3b409 Mon Sep 17 00:00:00 2001 From: shortstacked Date: Fri, 29 Aug 2025 09:04:47 +0100 Subject: [PATCH] test: Migrate small Cypress tests to Playwright (#18922) Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .../2230-ADO-ndv-reset-data-pagination.cy.ts | 34 --------- cypress/e2e/31-demo.cy.ts | 40 ---------- cypress/e2e/32-worker-view.cy.ts | 42 ----------- cypress/e2e/36-versions.cy.ts | 35 --------- cypress/e2e/37-become-creator-cta.cy.ts | 34 --------- packages/testing/playwright/pages/DemoPage.ts | 31 ++++++++ .../playwright/pages/NodeDetailsViewPage.ts | 35 +++++++++ .../testing/playwright/pages/SettingsPage.ts | 4 + .../testing/playwright/pages/VersionsPage.ts | 2 +- .../playwright/pages/WorkerViewPage.ts | 27 +++++++ packages/testing/playwright/pages/n8nPage.ts | 8 +- .../services/workflow-api-helper.ts | 9 ++- ...2230-ADO-ndv-reset-data-pagination.spec.ts | 35 +++++++++ .../playwright/tests/ui/31-demo.spec.ts | 46 ++++++++++++ .../tests/ui/32-worker-view.spec.ts | 39 ++++++++++ .../playwright/tests/ui/36-versions.spec.ts | 75 +++++++++++++++++++ .../tests/ui/37-become-creator-cta.spec.ts | 40 ++++++++++ .../workflows/NDV-debug-generate-data.json | 42 +++++++++++ 18 files changed, 388 insertions(+), 190 deletions(-) delete mode 100644 cypress/e2e/2230-ADO-ndv-reset-data-pagination.cy.ts delete mode 100644 cypress/e2e/31-demo.cy.ts delete mode 100644 cypress/e2e/32-worker-view.cy.ts delete mode 100644 cypress/e2e/36-versions.cy.ts delete mode 100644 cypress/e2e/37-become-creator-cta.cy.ts create mode 100644 packages/testing/playwright/pages/DemoPage.ts create mode 100644 packages/testing/playwright/pages/WorkerViewPage.ts create mode 100644 packages/testing/playwright/tests/ui/2230-ADO-ndv-reset-data-pagination.spec.ts create mode 100644 packages/testing/playwright/tests/ui/31-demo.spec.ts create mode 100644 packages/testing/playwright/tests/ui/32-worker-view.spec.ts create mode 100644 packages/testing/playwright/tests/ui/36-versions.spec.ts create mode 100644 packages/testing/playwright/tests/ui/37-become-creator-cta.spec.ts create mode 100644 packages/testing/playwright/workflows/NDV-debug-generate-data.json diff --git a/cypress/e2e/2230-ADO-ndv-reset-data-pagination.cy.ts b/cypress/e2e/2230-ADO-ndv-reset-data-pagination.cy.ts deleted file mode 100644 index 995423bc3a..0000000000 --- a/cypress/e2e/2230-ADO-ndv-reset-data-pagination.cy.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NDV, WorkflowPage } from '../pages'; -import { clearNotifications } from '../pages/notifications'; - -const workflowPage = new WorkflowPage(); -const ndv = new NDV(); - -describe('ADO-2230 NDV Pagination Reset', () => { - it('should reset pagaintion if data size changes to less than current page', () => { - // setup, load workflow with debughelper node with random seed - workflowPage.actions.visit(); - cy.createFixtureWorkflow('NDV-debug-generate-data.json', 'Debug workflow'); - workflowPage.actions.openNode('DebugHelper'); - - // execute node outputting 10 pages, check output of first page - ndv.actions.execute(); - clearNotifications(); - ndv.getters.outputTbodyCell(1, 1).invoke('text').should('eq', 'Terry.Dach@hotmail.com'); - - // open 4th page, check output - ndv.getters.pagination().should('be.visible'); - ndv.getters.pagination().find('li.number').should('have.length', 5); - ndv.getters.pagination().find('li.number').eq(3).click(); - ndv.getters.outputTbodyCell(1, 1).invoke('text').should('eq', 'Shane.Cormier68@yahoo.com'); - - // output a lot less data - ndv.getters.parameterInput('randomDataCount').find('input').clear().type('20'); - ndv.actions.execute(); - clearNotifications(); - - // check we are back to second page now - ndv.getters.pagination().find('li.number').should('have.length', 2); - ndv.getters.outputTbodyCell(1, 1).invoke('text').should('eq', 'Sylvia.Weber@hotmail.com'); - }); -}); diff --git a/cypress/e2e/31-demo.cy.ts b/cypress/e2e/31-demo.cy.ts deleted file mode 100644 index 7f13d1659a..0000000000 --- a/cypress/e2e/31-demo.cy.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getOutputTableRow } from '../composables/ndv'; -import { getCanvasNodes, openNode } from '../composables/workflow'; -import SIMPLE_WORKFLOW from '../fixtures/Manual_wait_set.json'; -import WORKFLOW_WITH_PINNED from '../fixtures/Webhook_set_pinned.json'; -import { importWorkflow, visitDemoPage } from '../pages/demo'; -import { errorToast } from '../pages/notifications'; - -describe('Demo', () => { - beforeEach(() => { - cy.overrideSettings({ previewMode: true }); - }); - - it('can import template', () => { - visitDemoPage(); - errorToast().should('not.exist'); - importWorkflow(SIMPLE_WORKFLOW); - getCanvasNodes().should('have.length', 3); - }); - - it('can import workflow with pin data', () => { - visitDemoPage(); - importWorkflow(WORKFLOW_WITH_PINNED); - getCanvasNodes().should('have.length', 2); - openNode('Webhook'); - getOutputTableRow(0).should('include.text', 'headers'); - getOutputTableRow(1).should('include.text', 'dragons'); - }); - - it('can override theme to dark', () => { - visitDemoPage('dark'); - cy.get('body').should('have.attr', 'data-theme', 'dark'); - errorToast().should('not.exist'); - }); - - it('can override theme to light', () => { - visitDemoPage('light'); - cy.get('body').should('have.attr', 'data-theme', 'light'); - errorToast().should('not.exist'); - }); -}); diff --git a/cypress/e2e/32-worker-view.cy.ts b/cypress/e2e/32-worker-view.cy.ts deleted file mode 100644 index de9afc2891..0000000000 --- a/cypress/e2e/32-worker-view.cy.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { WorkerViewPage } from '../pages'; - -const workerViewPage = new WorkerViewPage(); - -describe('Worker View (unlicensed)', () => { - beforeEach(() => { - cy.disableFeature('workerView'); - cy.disableQueueMode(); - }); - - it('should not show up in the menu sidebar', () => { - cy.signinAsMember(0); - cy.visit(workerViewPage.url); - workerViewPage.getters.menuItem().should('not.exist'); - }); - - it('should show action box', () => { - cy.signinAsMember(0); - cy.visit(workerViewPage.url); - workerViewPage.getters.workerViewUnlicensed().should('exist'); - }); -}); - -describe('Worker View (licensed)', () => { - beforeEach(() => { - cy.enableFeature('workerView'); - cy.enableQueueMode(); - }); - - it('should show up in the menu sidebar', () => { - cy.signinAsOwner(); - cy.enableQueueMode(); - cy.visit(workerViewPage.url); - workerViewPage.getters.menuItem().should('exist'); - }); - - it('should show worker list view', () => { - cy.signinAsMember(0); - cy.visit(workerViewPage.url); - workerViewPage.getters.workerViewLicensed().should('exist'); - }); -}); diff --git a/cypress/e2e/36-versions.cy.ts b/cypress/e2e/36-versions.cy.ts deleted file mode 100644 index 0c1bd25bc5..0000000000 --- a/cypress/e2e/36-versions.cy.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - closeVersionUpdatesPanel, - getVersionCard, - getVersionUpdatesPanelOpenButton, - openWhatsNewMenu, - openVersionUpdatesPanel, -} from '../composables/versions'; -import { WorkflowsPage } from '../pages/workflows'; - -const workflowsPage = new WorkflowsPage(); - -describe('Versions', () => { - it('should open updates panel', () => { - cy.overrideSettings({ - releaseChannel: 'stable', - versionCli: '1.0.0', - versionNotifications: { - enabled: true, - endpoint: 'https://api.n8n.io/api/versions/', - whatsNewEnabled: true, - whatsNewEndpoint: 'https://api.n8n.io/api/whats-new', - infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html', - }, - }); - - cy.visit(workflowsPage.url); - cy.wait('@loadSettings'); - - openWhatsNewMenu(); - getVersionUpdatesPanelOpenButton().should('contain', '2 versions behind'); - openVersionUpdatesPanel(); - getVersionCard().should('have.length', 2); - closeVersionUpdatesPanel(); - }); -}); diff --git a/cypress/e2e/37-become-creator-cta.cy.ts b/cypress/e2e/37-become-creator-cta.cy.ts deleted file mode 100644 index 8c10c2ebd0..0000000000 --- a/cypress/e2e/37-become-creator-cta.cy.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - getBecomeTemplateCreatorCta, - getCloseBecomeTemplateCreatorCtaButton, - interceptCtaRequestWithResponse, -} from '../composables/becomeTemplateCreatorCta'; -import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; - -const WorkflowsPage = new WorkflowsPageClass(); - -// Migrated to Playwright -// eslint-disable-next-line n8n-local-rules/no-skipped-tests -describe.skip('Become creator CTA', () => { - it('should not show the CTA if user is not eligible', () => { - interceptCtaRequestWithResponse(false).as('cta'); - cy.visit(WorkflowsPage.url); - - cy.wait('@cta'); - - getBecomeTemplateCreatorCta().should('not.exist'); - }); - - it('should show the CTA if the user is eligible', () => { - interceptCtaRequestWithResponse(true).as('cta'); - cy.visit(WorkflowsPage.url); - - cy.wait('@cta'); - - getBecomeTemplateCreatorCta().should('be.visible'); - - getCloseBecomeTemplateCreatorCtaButton().click(); - - getBecomeTemplateCreatorCta().should('not.exist'); - }); -}); diff --git a/packages/testing/playwright/pages/DemoPage.ts b/packages/testing/playwright/pages/DemoPage.ts new file mode 100644 index 0000000000..92cdbc96f9 --- /dev/null +++ b/packages/testing/playwright/pages/DemoPage.ts @@ -0,0 +1,31 @@ +import { BasePage } from './BasePage'; + +export class DemoPage extends BasePage { + async visitDemoPage(theme?: 'dark' | 'light') { + const query = theme ? `?theme=${theme}` : ''; + await this.page.goto('/workflows/demo' + query); + await this.getBody().waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-networkidle + await this.page.waitForLoadState('networkidle'); + await this.page.evaluate(() => { + // @ts-expect-error - this is a custom property added by the demo page + window.preventNodeViewBeforeUnload = true; + }); + } + + /** + * Import a workflow into the demo page + * @param workflow - The workflow to import + */ + async importWorkflow(workflow: object) { + const OPEN_WORKFLOW = { command: 'openWorkflow', workflow }; + await this.page.evaluate((message) => { + console.log('Posting message:', JSON.stringify(message)); + window.postMessage(JSON.stringify(message), '*'); + }, OPEN_WORKFLOW); + } + + getBody() { + return this.page.locator('body'); + } +} diff --git a/packages/testing/playwright/pages/NodeDetailsViewPage.ts b/packages/testing/playwright/pages/NodeDetailsViewPage.ts index 1844c5c05f..a15b1275b3 100644 --- a/packages/testing/playwright/pages/NodeDetailsViewPage.ts +++ b/packages/testing/playwright/pages/NodeDetailsViewPage.ts @@ -552,4 +552,39 @@ export class NodeDetailsViewPage extends BasePage { // eslint-disable-next-line playwright/no-wait-for-timeout await this.page.waitForTimeout(2500); } + + // Pagination methods for output panel + getOutputPagination() { + return this.getOutputPanel().getByTestId('ndv-data-pagination'); + } + + getOutputPaginationPages() { + return this.getOutputPagination().locator('.el-pager li.number'); + } + + async navigateToOutputPage(pageNumber: number): Promise { + const pages = this.getOutputPaginationPages(); + await pages.nth(pageNumber - 1).click(); + } + + async getCurrentOutputPage(): Promise { + const activePage = this.getOutputPagination().locator('.el-pager li.is-active').first(); + const pageText = await activePage.textContent(); + return parseInt(pageText ?? '1', 10); + } + + async getOutputPageContent(row: number = 0, col: number = 0): Promise { + return (await this.getOutputTbodyCell(row, col).textContent()) ?? ''; + } + + /** + * Set parameter input value by clearing and filling (for parameters without standard test-id) + * @param parameterName - The parameter name + * @param value - The value to set + */ + async setParameterInputValue(parameterName: string, value: string): Promise { + const input = this.getParameterInput(parameterName).locator('input'); + await input.clear(); + await input.fill(value); + } } diff --git a/packages/testing/playwright/pages/SettingsPage.ts b/packages/testing/playwright/pages/SettingsPage.ts index 35e80f24cd..b1df9a86d9 100644 --- a/packages/testing/playwright/pages/SettingsPage.ts +++ b/packages/testing/playwright/pages/SettingsPage.ts @@ -9,6 +9,10 @@ export class SettingsPage extends BasePage { return this.page.getByTestId('menu-item').getByTestId(id); } + getMenuItemByText(text: string) { + return this.page.getByTestId('menu-item').getByText(text, { exact: true }); + } + async goToSettings() { await this.page.goto('/settings'); } diff --git a/packages/testing/playwright/pages/VersionsPage.ts b/packages/testing/playwright/pages/VersionsPage.ts index 3e55f467a8..545ba40314 100644 --- a/packages/testing/playwright/pages/VersionsPage.ts +++ b/packages/testing/playwright/pages/VersionsPage.ts @@ -18,7 +18,7 @@ export class VersionsPage extends BasePage { } getWhatsNewMenuItem() { - return this.page.getByTestId('menu-item').getByTestId('whats-new'); + return this.page.getByText('What’s New'); } async openWhatsNewMenu() { diff --git a/packages/testing/playwright/pages/WorkerViewPage.ts b/packages/testing/playwright/pages/WorkerViewPage.ts new file mode 100644 index 0000000000..6e63dbc5b4 --- /dev/null +++ b/packages/testing/playwright/pages/WorkerViewPage.ts @@ -0,0 +1,27 @@ +import { BasePage } from './BasePage'; + +export class WorkerViewPage extends BasePage { + getWorkerCards() { + return this.page.getByTestId('worker-card'); + } + + getWorkerCard(workerId: string) { + return this.getWorkerCards().filter({ hasText: workerId }); + } + + getWorkerViewLicensed() { + return this.page.getByTestId('worker-view-licensed'); + } + + getWorkerViewUnlicensed() { + return this.page.getByTestId('worker-view-unlicensed'); + } + + getWorkerMenuItem() { + return this.page.getByTestId('menu-item').getByText('Workers', { exact: true }); + } + + async visitWorkerView() { + await this.page.goto('/settings/workers'); + } +} diff --git a/packages/testing/playwright/pages/n8nPage.ts b/packages/testing/playwright/pages/n8nPage.ts index 22a68cfbd1..bf2732f336 100644 --- a/packages/testing/playwright/pages/n8nPage.ts +++ b/packages/testing/playwright/pages/n8nPage.ts @@ -4,6 +4,7 @@ import { AIAssistantPage } from './AIAssistantPage'; import { BecomeCreatorCTAPage } from './BecomeCreatorCTAPage'; import { CanvasPage } from './CanvasPage'; import { CredentialsPage } from './CredentialsPage'; +import { DemoPage } from './DemoPage'; import { ExecutionsPage } from './ExecutionsPage'; import { IframePage } from './IframePage'; import { InteractionsPage } from './InteractionsPage'; @@ -14,6 +15,7 @@ import { ProjectSettingsPage } from './ProjectSettingsPage'; import { SettingsPage } from './SettingsPage'; import { SidebarPage } from './SidebarPage'; import { VersionsPage } from './VersionsPage'; +import { WorkerViewPage } from './WorkerViewPage'; import { WorkflowActivationModal } from './WorkflowActivationModal'; import { WorkflowSettingsModal } from './WorkflowSettingsModal'; import { WorkflowSharingModal } from './WorkflowSharingModal'; @@ -33,7 +35,7 @@ export class n8nPage { readonly aiAssistant: AIAssistantPage; readonly becomeCreatorCTA: BecomeCreatorCTAPage; readonly canvas: CanvasPage; - + readonly demo: DemoPage; readonly iframe: IframePage; readonly interactions: InteractionsPage; readonly ndv: NodeDetailsViewPage; @@ -41,6 +43,7 @@ export class n8nPage { readonly projectSettings: ProjectSettingsPage; readonly settings: SettingsPage; readonly versions: VersionsPage; + readonly workerView: WorkerViewPage; readonly workflows: WorkflowsPage; readonly notifications: NotificationsPage; readonly credentials: CredentialsPage; @@ -66,7 +69,7 @@ export class n8nPage { this.aiAssistant = new AIAssistantPage(page); this.becomeCreatorCTA = new BecomeCreatorCTAPage(page); this.canvas = new CanvasPage(page); - + this.demo = new DemoPage(page); this.iframe = new IframePage(page); this.interactions = new InteractionsPage(page); this.ndv = new NodeDetailsViewPage(page); @@ -74,6 +77,7 @@ export class n8nPage { this.projectSettings = new ProjectSettingsPage(page); this.settings = new SettingsPage(page); this.versions = new VersionsPage(page); + this.workerView = new WorkerViewPage(page); this.workflows = new WorkflowsPage(page); this.notifications = new NotificationsPage(page); this.credentials = new CredentialsPage(page); diff --git a/packages/testing/playwright/services/workflow-api-helper.ts b/packages/testing/playwright/services/workflow-api-helper.ts index 2e95a8161a..69e32a93f6 100644 --- a/packages/testing/playwright/services/workflow-api-helper.ts +++ b/packages/testing/playwright/services/workflow-api-helper.ts @@ -59,11 +59,16 @@ export class WorkflowApiHelper { const webhookPrefix = options?.webhookPrefix ?? 'test-webhook'; const uniqueSuffix = nanoid(idLength); - // Make workflow name unique - if (workflow.name) { + // Make workflow name unique; add a default if missing + if (workflow.name && workflow.name.trim().length > 0) { workflow.name = `${workflow.name} (Test ${uniqueSuffix})`; + } else { + workflow.name = `Test Workflow ${uniqueSuffix}`; } + // Ensure workflow is inactive by default when not specified + workflow.active ??= false; + // Check if workflow has webhook nodes and process them let webhookId: string | undefined; let webhookPath: string | undefined; diff --git a/packages/testing/playwright/tests/ui/2230-ADO-ndv-reset-data-pagination.spec.ts b/packages/testing/playwright/tests/ui/2230-ADO-ndv-reset-data-pagination.spec.ts new file mode 100644 index 0000000000..c1cb9e32d1 --- /dev/null +++ b/packages/testing/playwright/tests/ui/2230-ADO-ndv-reset-data-pagination.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe('ADO-2230 NDV Pagination Reset', () => { + test('should reset pagination if data size changes to less than current page', async ({ + n8n, + }) => { + await n8n.start.fromImportedWorkflow('NDV-debug-generate-data.json'); + + await n8n.canvas.openNode('DebugHelper'); + await n8n.ndv.execute(); + await n8n.notifications.quickCloseAll(); + + const outputPagination = n8n.ndv.getOutputPagination(); + await expect(outputPagination).toBeVisible(); + + await expect(n8n.ndv.getOutputPaginationPages()).toHaveCount(5); + + await expect(n8n.ndv.getOutputTbodyCell(0, 0)).not.toBeEmpty(); + const firstPageContent = await n8n.ndv.getOutputPageContent(0, 0); + + await n8n.ndv.navigateToOutputPage(4); + + const fourthPageContent = await n8n.ndv.getOutputPageContent(0, 0); + expect(fourthPageContent).not.toBe(firstPageContent); + + await n8n.ndv.setParameterInputValue('randomDataCount', '20'); + + await n8n.ndv.execute(); + await n8n.notifications.quickCloseAll(); + + await expect(n8n.ndv.getOutputPaginationPages()).toHaveCount(2); + + await expect(n8n.ndv.getOutputTbodyCell(0, 0)).not.toBeEmpty(); + }); +}); diff --git a/packages/testing/playwright/tests/ui/31-demo.spec.ts b/packages/testing/playwright/tests/ui/31-demo.spec.ts new file mode 100644 index 0000000000..292c2ec0c7 --- /dev/null +++ b/packages/testing/playwright/tests/ui/31-demo.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from '../../fixtures/base'; +import type { TestRequirements } from '../../Types'; +import simpleWorkflow from '../../workflows/Manual_wait_set.json'; +import workflowWithPinned from '../../workflows/Webhook_set_pinned.json'; + +const requirements: TestRequirements = { + config: { + settings: { + previewMode: true, + }, + }, +}; + +test.describe('Demo', () => { + test.beforeEach(async ({ setupRequirements }) => { + await setupRequirements(requirements); + }); + + test('can import template', async ({ n8n }) => { + await n8n.demo.visitDemoPage(); + expect(await n8n.notifications.getAllNotificationTexts()).toHaveLength(0); + await n8n.demo.importWorkflow(simpleWorkflow); + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3); + }); + + test('can import workflow with pin data', async ({ n8n }) => { + await n8n.demo.visitDemoPage(); + await n8n.demo.importWorkflow(workflowWithPinned); + await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2); + await n8n.canvas.openNode('Webhook'); + await expect(n8n.ndv.getOutputTableHeaders().first()).toContainText('headers'); + await expect(n8n.ndv.getOutputTableCell(1, 3)).toContainText('dragons'); + }); + + test('can override theme to dark', async ({ n8n }) => { + await n8n.demo.visitDemoPage('dark'); + await expect(n8n.demo.getBody()).toHaveAttribute('data-theme', 'dark'); + expect(await n8n.notifications.getAllNotificationTexts()).toHaveLength(0); + }); + + test('can override theme to light', async ({ n8n }) => { + await n8n.demo.visitDemoPage('light'); + await expect(n8n.demo.getBody()).toHaveAttribute('data-theme', 'light'); + expect(await n8n.notifications.getAllNotificationTexts()).toHaveLength(0); + }); +}); diff --git a/packages/testing/playwright/tests/ui/32-worker-view.spec.ts b/packages/testing/playwright/tests/ui/32-worker-view.spec.ts new file mode 100644 index 0000000000..d04a47d02a --- /dev/null +++ b/packages/testing/playwright/tests/ui/32-worker-view.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from '../../fixtures/base'; + +test.describe + .serial('Worker View', () => { + test.describe('unlicensed', () => { + test.beforeEach(async ({ api }) => { + await api.disableFeature('workerView'); + await api.setQueueMode(false); + }); + + test('should not show up in the menu sidebar', async ({ n8n }) => { + await n8n.workerView.visitWorkerView(); + await expect(n8n.workerView.getWorkerMenuItem()).toBeHidden(); + }); + + test('should show action box', async ({ n8n }) => { + await n8n.workerView.visitWorkerView(); + await expect(n8n.workerView.getWorkerViewUnlicensed()).toBeVisible(); + }); + }); + + test.describe('licensed', () => { + test.beforeEach(async ({ api }) => { + await api.enableFeature('workerView'); + await api.setQueueMode(true); + }); + + test('should show up in the menu sidebar', async ({ n8n }) => { + await n8n.goHome(); + await n8n.workerView.visitWorkerView(); + await expect(n8n.workerView.getWorkerMenuItem()).toBeVisible(); + }); + + test('should show worker list view', async ({ n8n }) => { + await n8n.workerView.visitWorkerView(); + await expect(n8n.workerView.getWorkerViewLicensed()).toBeVisible(); + }); + }); + }); diff --git a/packages/testing/playwright/tests/ui/36-versions.spec.ts b/packages/testing/playwright/tests/ui/36-versions.spec.ts new file mode 100644 index 0000000000..b05818e7d6 --- /dev/null +++ b/packages/testing/playwright/tests/ui/36-versions.spec.ts @@ -0,0 +1,75 @@ +import { test, expect } from '../../fixtures/base'; +import type { TestRequirements } from '../../Types'; + +const requirements: TestRequirements = { + config: { + settings: { + releaseChannel: 'stable', + versionCli: '1.0.0', + versionNotifications: { + enabled: true, + endpoint: 'https://api.n8n.io/api/versions/', + whatsNewEnabled: true, + whatsNewEndpoint: 'https://api.n8n.io/api/whats-new', + infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html', + }, + }, + }, + intercepts: { + versions: { + url: '**/api/versions/**', + response: [ + { + name: '1.0.0', + nodes: [], + createdAt: '2025-06-01T00:00:00Z', + description: 'Current version', + documentationUrl: 'https://docs.n8n.io', + hasBreakingChange: false, + hasSecurityFix: false, + hasSecurityIssue: false, + securityIssueFixVersion: '', + }, + { + name: '1.0.1', + nodes: [], + createdAt: '2025-06-15T00:00:00Z', + description: 'Version 1.0.1', + documentationUrl: 'https://docs.n8n.io', + hasBreakingChange: false, + hasSecurityFix: false, + hasSecurityIssue: false, + securityIssueFixVersion: '', + }, + { + name: '1.0.2', + nodes: [], + createdAt: '2025-06-30T00:00:00Z', + description: 'Version 1.0.2', + documentationUrl: 'https://docs.n8n.io', + hasBreakingChange: false, + hasSecurityFix: false, + hasSecurityIssue: false, + securityIssueFixVersion: '', + }, + ], + }, + }, +}; + +test.describe('Versions', () => { + test('should open updates panel', async ({ n8n, setupRequirements }) => { + await setupRequirements(requirements); + await n8n.goHome(); + await n8n.versions.openWhatsNewMenu(); + await expect(n8n.versions.getVersionUpdatesPanelOpenButton()).toContainText( + '2 versions behind', + ); + + await n8n.versions.openVersionUpdatesPanel(); + await expect(n8n.versions.getVersionCard()).toHaveCount(2); + + await n8n.versions.closeVersionUpdatesPanel(); + await expect(n8n.versions.getVersionUpdatesPanel()).toBeHidden(); + }); +}); diff --git a/packages/testing/playwright/tests/ui/37-become-creator-cta.spec.ts b/packages/testing/playwright/tests/ui/37-become-creator-cta.spec.ts new file mode 100644 index 0000000000..afb9bfe8ca --- /dev/null +++ b/packages/testing/playwright/tests/ui/37-become-creator-cta.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '../../fixtures/base'; +import type { TestRequirements } from '../../Types'; + +test.describe('Become creator CTA', () => { + test('should not show the CTA if user is not eligible', async ({ n8n, setupRequirements }) => { + const notEligibleRequirements: TestRequirements = { + intercepts: { + cta: { + url: '**/rest/cta/become-creator', + response: false, + }, + }, + }; + + await setupRequirements(notEligibleRequirements); + await n8n.goHome(); + + await expect(n8n.becomeCreatorCTA.getBecomeTemplateCreatorCta()).toBeHidden(); + }); + + test('should show the CTA if the user is eligible', async ({ n8n, setupRequirements }) => { + const eligibleRequirements: TestRequirements = { + intercepts: { + cta: { + url: '**/rest/cta/become-creator', + response: true, + }, + }, + }; + + await setupRequirements(eligibleRequirements); + await n8n.goHome(); + + await expect(n8n.becomeCreatorCTA.getBecomeTemplateCreatorCta()).toBeVisible(); + + await n8n.becomeCreatorCTA.closeBecomeTemplateCreatorCta(); + + await expect(n8n.becomeCreatorCTA.getBecomeTemplateCreatorCta()).toBeHidden(); + }); +}); diff --git a/packages/testing/playwright/workflows/NDV-debug-generate-data.json b/packages/testing/playwright/workflows/NDV-debug-generate-data.json new file mode 100644 index 0000000000..6899cd4d81 --- /dev/null +++ b/packages/testing/playwright/workflows/NDV-debug-generate-data.json @@ -0,0 +1,42 @@ +{ + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "5b397bc122efafc165b2a6e67d5e8d75b8138f0d24d6352fac713e4845b002a6" + }, + "nodes": [ + { + "parameters": {}, + "id": "df260de7-6f28-4d07-b7b5-29588e27335b", + "name": "When clicking \"Execute workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [780, 500] + }, + { + "parameters": { + "category": "randomData", + "randomDataSeed": "0", + "randomDataCount": 100 + }, + "id": "9e9a0708-86dc-474f-a60e-4315e757c08e", + "name": "DebugHelper", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [1000, 500] + } + ], + "connections": { + "When clicking \"Execute workflow\"": { + "main": [ + [ + { + "node": "DebugHelper", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} +}