test: Migrate UI tests from Cypress -> Playwright (no-changelog) (#18201)

This commit is contained in:
shortstacked
2025-08-12 12:06:42 +01:00
committed by GitHub
parent ecc4f41a11
commit 514825bd51
52 changed files with 2111 additions and 402 deletions

View File

@@ -0,0 +1,66 @@
import { BasePage } from './BasePage';
export class AIAssistantPage extends BasePage {
getAskAssistantFloatingButton() {
return this.page.getByTestId('ask-assistant-floating-button');
}
getAskAssistantCanvasActionButton() {
return this.page.getByTestId('ask-assistant-canvas-action-button');
}
getAskAssistantChat() {
return this.page.getByTestId('ask-assistant-chat');
}
getPlaceholderMessage() {
return this.page.getByTestId('placeholder-message');
}
getChatInput() {
return this.page.getByTestId('chat-input');
}
getSendMessageButton() {
return this.page.getByTestId('send-message-button');
}
getCloseChatButton() {
return this.page.getByTestId('close-chat-button');
}
getAskAssistantSidebarResizer() {
return this.page
.getByTestId('ask-assistant-sidebar')
.locator('[class*="_resizer"][data-dir="left"]')
.first();
}
getNodeErrorViewAssistantButton() {
return this.page.getByTestId('node-error-view-ask-assistant-button').locator('button').first();
}
getChatMessagesAll() {
return this.page.locator('[data-test-id^="chat-message"]');
}
getChatMessagesAssistant() {
return this.page.getByTestId('chat-message-assistant');
}
getChatMessagesUser() {
return this.page.getByTestId('chat-message-user');
}
getChatMessagesSystem() {
return this.page.getByTestId('chat-message-system');
}
getQuickReplyButtons() {
return this.page.getByTestId('quick-replies').locator('button');
}
getNewAssistantSessionModal() {
return this.page.getByTestId('new-assistant-session-modal');
}
}

View File

@@ -0,0 +1,15 @@
import { BasePage } from './BasePage';
export class BecomeCreatorCTAPage extends BasePage {
getBecomeTemplateCreatorCta() {
return this.page.getByTestId('become-template-creator-cta');
}
getCloseBecomeTemplateCreatorCtaButton() {
return this.page.getByTestId('close-become-template-creator-cta');
}
async closeBecomeTemplateCreatorCta() {
await this.getCloseBecomeTemplateCreatorCtaButton().click();
}
}

View File

@@ -1,4 +1,5 @@
import type { Locator } from '@playwright/test';
import { nanoid } from 'nanoid';
import { BasePage } from './BasePage';
import { resolveFromRoot } from '../utils/path-helper';
@@ -8,6 +9,10 @@ export class CanvasPage extends BasePage {
return this.page.getByRole('button', { name: 'Save' });
}
workflowSaveButton(): Locator {
return this.page.getByTestId('workflow-save-button');
}
canvasAddButton(): Locator {
return this.page.getByTestId('canvas-add-button');
}
@@ -132,7 +137,6 @@ export class CanvasPage extends BasePage {
async clickExecutionsTab(): Promise<void> {
await this.page.getByRole('radio', { name: 'Executions' }).click();
}
async setWorkflowName(name: string): Promise<void> {
await this.clickByTestId('inline-edit-preview');
await this.fillByTestId('inline-edit-input', name);
@@ -161,7 +165,6 @@ export class CanvasPage extends BasePage {
getWorkflowTags() {
return this.page.getByTestId('workflow-tags').locator('.el-tag');
}
async activateWorkflow() {
const responsePromise = this.page.waitForResponse(
(response) =>
@@ -170,4 +173,90 @@ export class CanvasPage extends BasePage {
await this.page.getByTestId('workflow-activate-switch').click();
await responsePromise;
}
async clickZoomToFitButton(): Promise<void> {
await this.clickByTestId('zoom-to-fit');
}
/**
* Get node issues for a specific node
*/
getNodeIssuesByName(nodeName: string) {
return this.nodeByName(nodeName).getByTestId('node-issues');
}
/**
* Add tags to the workflow
* @param count - The number of tags to add
* @returns An array of tag names
*/
async addTags(count: number = 1): Promise<string[]> {
const tags: string[] = [];
for (let i = 0; i < count; i++) {
const tag = `tag-${nanoid(8)}-${i}`;
tags.push(tag);
if (i === 0) {
await this.clickByText('Add tag');
} else {
await this.page
.getByTestId('tags-dropdown')
.getByText(tags[i - 1])
.click();
}
await this.page.getByRole('combobox').first().fill(tag);
await this.page.getByRole('combobox').first().press('Enter');
}
await this.page.click('body');
return tags;
}
// Production Checklist methods
getProductionChecklistButton(): Locator {
return this.page.getByTestId('suggested-action-count');
}
getProductionChecklistPopover(): Locator {
return this.page.locator('[data-reka-popper-content-wrapper=""]').filter({ hasText: /./ });
}
getProductionChecklistActionItem(text?: string): Locator {
const items = this.page.getByTestId('suggested-action-item');
if (text) {
return items.getByText(text);
}
return items;
}
getProductionChecklistIgnoreAllButton(): Locator {
return this.page.getByTestId('suggested-action-ignore-all');
}
getErrorActionItem(): Locator {
return this.getProductionChecklistActionItem('Set up error notifications');
}
getTimeSavedActionItem(): Locator {
return this.getProductionChecklistActionItem('Track time saved');
}
getEvaluationsActionItem(): Locator {
return this.getProductionChecklistActionItem('Test reliability of AI steps');
}
async clickProductionChecklistButton(): Promise<void> {
await this.getProductionChecklistButton().click();
}
async clickProductionChecklistIgnoreAll(): Promise<void> {
await this.getProductionChecklistIgnoreAllButton().click();
}
async clickProductionChecklistAction(actionText: string): Promise<void> {
await this.getProductionChecklistActionItem(actionText).click();
}
}

View File

@@ -0,0 +1,15 @@
import { BasePage } from './BasePage';
export class IframePage extends BasePage {
getIframe() {
return this.page.locator('iframe');
}
getIframeBySrc(src: string) {
return this.page.locator(`iframe[src="${src}"]`);
}
async waitForIframeRequest(url: string) {
await this.page.waitForResponse(url);
}
}

View File

@@ -35,4 +35,16 @@ export class NodeDisplayViewPage extends BasePage {
async close() {
await this.clickBackToCanvasButton();
}
async execute() {
await this.clickByTestId('node-execute-button');
}
getOutputPanel() {
return this.page.getByTestId('output-panel');
}
getParameterExpressionPreviewValue() {
return this.page.getByTestId('parameter-expression-preview-value');
}
}

View File

@@ -0,0 +1,15 @@
import { BasePage } from './BasePage';
export class SettingsPage extends BasePage {
getMenuItems() {
return this.page.getByTestId('menu-item');
}
getMenuItem(id: string) {
return this.page.getByTestId('menu-item').getByTestId(id);
}
async goToSettings() {
await this.page.goto('/settings');
}
}

View File

@@ -0,0 +1,35 @@
import { BasePage } from './BasePage';
export class VersionsPage extends BasePage {
getVersionUpdatesPanelOpenButton() {
return this.page.getByTestId('version-update-next-versions-link');
}
getVersionUpdatesPanel() {
return this.page.getByTestId('version-updates-panel');
}
getVersionUpdatesPanelCloseButton() {
return this.getVersionUpdatesPanel().getByRole('button', { name: 'Close' });
}
getVersionCard() {
return this.page.getByTestId('version-card');
}
getWhatsNewMenuItem() {
return this.page.getByTestId('menu-item').getByTestId('whats-new');
}
async openWhatsNewMenu() {
await this.getWhatsNewMenuItem().click();
}
async openVersionUpdatesPanel() {
await this.getVersionUpdatesPanelOpenButton().click();
}
async closeVersionUpdatesPanel() {
await this.getVersionUpdatesPanelCloseButton().click();
}
}

View File

@@ -0,0 +1,31 @@
import type { Locator } from '@playwright/test';
import { BasePage } from './BasePage';
export class WorkflowActivationModal extends BasePage {
getModal(): Locator {
return this.page.getByTestId('activation-modal');
}
getDontShowAgainCheckbox(): Locator {
return this.getModal().getByText("Don't show again");
}
getGotItButton(): Locator {
return this.getModal().getByRole('button', { name: 'Got it' });
}
async close(): Promise<void> {
await this.getDontShowAgainCheckbox().click();
await this.getGotItButton().click();
}
async clickDontShowAgain(): Promise<void> {
await this.getDontShowAgainCheckbox().click();
}
async clickGotIt(): Promise<void> {
await this.getGotItButton().click();
}
}

View File

@@ -0,0 +1,39 @@
import type { Locator } from '@playwright/test';
import { BasePage } from './BasePage';
export class WorkflowSettingsModal extends BasePage {
getModal(): Locator {
return this.page.getByTestId('workflow-settings-dialog');
}
getWorkflowMenu(): Locator {
return this.page.getByTestId('workflow-menu');
}
getSettingsMenuItem(): Locator {
return this.page.getByTestId('workflow-menu-item-settings');
}
getErrorWorkflowField(): Locator {
return this.page.getByTestId('workflow-settings-error-workflow');
}
getSaveButton(): Locator {
return this.page.getByRole('button', { name: 'Save' });
}
async open(): Promise<void> {
await this.getWorkflowMenu().click();
await this.getSettingsMenuItem().click();
}
async clickSave(): Promise<void> {
await this.getSaveButton().click();
}
async selectErrorWorkflow(workflowName: string): Promise<void> {
await this.getErrorWorkflowField().click();
await this.page.getByRole('option', { name: workflowName }).first().click();
}
}

View File

@@ -1,12 +1,19 @@
import type { Page } from '@playwright/test';
import { AIAssistantPage } from './AIAssistantPage';
import { BecomeCreatorCTAPage } from './BecomeCreatorCTAPage';
import { CanvasPage } from './CanvasPage';
import { CredentialsPage } from './CredentialsPage';
import { ExecutionsPage } from './ExecutionsPage';
import { IframePage } from './IframePage';
import { NodeDisplayViewPage } from './NodeDisplayViewPage';
import { NotificationsPage } from './NotificationsPage';
import { ProjectSettingsPage } from './ProjectSettingsPage';
import { SettingsPage } from './SettingsPage';
import { SidebarPage } from './SidebarPage';
import { VersionsPage } from './VersionsPage';
import { WorkflowActivationModal } from './WorkflowActivationModal';
import { WorkflowSettingsModal } from './WorkflowSettingsModal';
import { WorkflowSharingModal } from './WorkflowSharingModal';
import { WorkflowsPage } from './WorkflowsPage';
import { CanvasComposer } from '../composables/CanvasComposer';
@@ -18,18 +25,28 @@ export class n8nPage {
readonly page: Page;
// Pages
readonly aiAssistant: AIAssistantPage;
readonly becomeCreatorCTA: BecomeCreatorCTAPage;
readonly canvas: CanvasPage;
readonly iframe: IframePage;
readonly ndv: NodeDisplayViewPage;
readonly projectSettings: ProjectSettingsPage;
readonly settings: SettingsPage;
readonly versions: VersionsPage;
readonly workflows: WorkflowsPage;
readonly notifications: NotificationsPage;
readonly credentials: CredentialsPage;
readonly executions: ExecutionsPage;
readonly sideBar: SidebarPage;
// Modals
readonly workflowActivationModal: WorkflowActivationModal;
readonly workflowSettingsModal: WorkflowSettingsModal;
readonly workflowSharingModal: WorkflowSharingModal;
// Composables
readonly workflowComposer: WorkflowComposer;
readonly workflowSharingModal: WorkflowSharingModal;
readonly projectComposer: ProjectComposer;
readonly canvasComposer: CanvasComposer;
@@ -37,9 +54,15 @@ export class n8nPage {
this.page = page;
// Pages
this.aiAssistant = new AIAssistantPage(page);
this.becomeCreatorCTA = new BecomeCreatorCTAPage(page);
this.canvas = new CanvasPage(page);
this.iframe = new IframePage(page);
this.ndv = new NodeDisplayViewPage(page);
this.projectSettings = new ProjectSettingsPage(page);
this.settings = new SettingsPage(page);
this.versions = new VersionsPage(page);
this.workflows = new WorkflowsPage(page);
this.notifications = new NotificationsPage(page);
this.credentials = new CredentialsPage(page);
@@ -47,6 +70,10 @@ export class n8nPage {
this.sideBar = new SidebarPage(page);
this.workflowSharingModal = new WorkflowSharingModal(page);
// Modals
this.workflowActivationModal = new WorkflowActivationModal(page);
this.workflowSettingsModal = new WorkflowSettingsModal(page);
// Composables
this.workflowComposer = new WorkflowComposer(this);
this.projectComposer = new ProjectComposer(this);