n8n/packages/testing/playwright/composables/TestEntryComposer.ts
n8n-cat-bot[bot] 051a20e9f9
chore: Route instance-ai benchmark tabs through n8n facade (#32512)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Declan Carroll <declan@n8n.io>
2026-06-18 11:32:50 +00:00

141 lines
5.2 KiB
TypeScript

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');
// Wait for the canvas loader to clear before returning so tests don't
// interact with a canvas still covered by the full-screen loader.
await this.n8n.canvas.waitForBlankCanvasReady();
}
/**
* 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.waitForBlankCanvasReady();
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}`);
// Wait for the canvas loading overlay to clear and the imported nodes to
// render before returning, so tests don't interact with a canvas that is
// still covered by the full-screen loader.
await this.n8n.canvas.waitForCanvasReady();
await this.n8n.canvas.getCanvasNodes().first().waitFor({ state: 'visible' });
return workflowImportResult;
}
/**
* Start UI test on the canvas of an existing workflow (e.g. one created via
* the API). Waits for the canvas loader to clear and the workflow's nodes to
* render before returning, so tests don't interact with a canvas still
* covered by the full-screen loader.
*/
async fromExistingWorkflow(workflowId: string) {
await this.n8n.navigate.toWorkflow(workflowId);
await this.n8n.canvas.waitForCanvasReady();
await this.n8n.canvas.getCanvasNodes().first().waitFor({ state: 'visible' });
}
/**
* 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<void>): Promise<n8nPage> {
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);
}
/**
* Open a fresh tab in the current browser context (shared session) and
* return an n8nPage facade bound to it. Use for multi-tab scenarios such
* as the instance-ai memory benchmarks that drive several threads in
* parallel within the same authenticated context.
*/
async newTab(): Promise<n8nPage> {
const newPage = await this.n8n.page.context().newPage();
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<TestUser, 'email' | 'password'>): Promise<n8nPage> {
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;
}
}