import type { Page } from '@playwright/test'; import { setupDefaultInterceptors } from '../config/intercepts'; import type { n8nPage } from '../pages/n8nPage'; import type { TestUser } from '../services/user-api-helper'; /** * Composer for UI test entry points. All methods in this class navigate to or verify UI state. * For API-only testing, use the standalone `api` fixture directly instead. */ export class TestEntryComposer { constructor(private readonly n8n: n8nPage) {} /** * Start UI test from the home page and navigate to canvas */ async fromHome() { await this.n8n.goHome(); await this.n8n.page.waitForURL('/home/workflows'); } /** * Start UI test from a blank canvas (assumes already on canvas) */ async fromBlankCanvas() { await this.n8n.navigate.toWorkflow('new'); // Verify we're on canvas await this.n8n.canvas.canvasPane().isVisible(); } /** * Start UI test from a workflow in a new project on a new canvas */ async fromNewProjectBlankCanvas() { // Enable features to allow us to create a new project await this.n8n.api.enableFeature('projectRole:admin'); await this.n8n.api.enableFeature('projectRole:editor'); await this.n8n.api.setMaxTeamProjectsQuota(-1); // Create a project using the API const response = await this.n8n.api.projects.createProject(); const projectId = response.id; await this.n8n.page.goto(`workflow/new?projectId=${projectId}`); await this.n8n.canvas.canvasPane().isVisible(); return projectId; } async fromNewProject() { const response = await this.n8n.api.projects.createProject(); const projectId = response.id; await this.n8n.navigate.toProject(projectId); return projectId; } /** * Start UI test from the canvas of an imported workflow * Returns the workflow import result for use in the test */ async fromImportedWorkflow(workflowFile: string) { const workflowImportResult = await this.n8n.api.workflows.importWorkflowFromFile(workflowFile); await this.n8n.page.goto(`workflow/${workflowImportResult.workflowId}`); return workflowImportResult; } /** * Start UI test on a new page created by an action * @param action - The action that will create a new page * @returns n8nPage instance for the new page */ async fromNewPage(action: () => Promise): Promise { const newPagePromise = this.n8n.page.waitForEvent('popup'); await action(); const newPage = await newPagePromise; await newPage.waitForLoadState('domcontentloaded'); // Use the constructor from the current instance to avoid circular dependency const n8nPageConstructor = this.n8n.constructor as new (page: Page) => n8nPage; return new n8nPageConstructor(newPage); } /** * Enable project feature set * Allow project creation, sharing, and folder creation */ async withProjectFeatures() { await this.n8n.api.enableFeature('sharing'); await this.n8n.api.enableFeature('folders'); await this.n8n.api.enableFeature('advancedPermissions'); await this.n8n.api.enableFeature('projectRole:admin'); await this.n8n.api.enableFeature('projectRole:editor'); await this.n8n.api.setMaxTeamProjectsQuota(-1); } /** * Create a new isolated user context with fresh page and authentication. * Use this when you need a browser context for UI interactions. * For API-only operations, use `api.createApiForUser()` instead. * @param user - User with email and password * @returns Fresh n8nPage instance with user authentication */ async withUser(user: Pick): Promise { const browser = this.n8n.page.context().browser()!; const context = await browser.newContext(); await setupDefaultInterceptors(context); const page = await context.newPage(); const newN8n = new (this.n8n.constructor as new (page: Page) => n8nPage)(page); await newN8n.api.login({ email: user.email, password: user.password }); return newN8n; } }