mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
test: Migrate small Cypress tests to Playwright (#18922)
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
31
packages/testing/playwright/pages/DemoPage.ts
Normal file
31
packages/testing/playwright/pages/DemoPage.ts
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
const pages = this.getOutputPaginationPages();
|
||||
await pages.nth(pageNumber - 1).click();
|
||||
}
|
||||
|
||||
async getCurrentOutputPage(): Promise<number> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
const input = this.getParameterInput(parameterName).locator('input');
|
||||
await input.clear();
|
||||
await input.fill(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
27
packages/testing/playwright/pages/WorkerViewPage.ts
Normal file
27
packages/testing/playwright/pages/WorkerViewPage.ts
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
46
packages/testing/playwright/tests/ui/31-demo.spec.ts
Normal file
46
packages/testing/playwright/tests/ui/31-demo.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
39
packages/testing/playwright/tests/ui/32-worker-view.spec.ts
Normal file
39
packages/testing/playwright/tests/ui/32-worker-view.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
75
packages/testing/playwright/tests/ui/36-versions.spec.ts
Normal file
75
packages/testing/playwright/tests/ui/36-versions.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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": {}
|
||||
}
|
||||
Reference in New Issue
Block a user