Skip to content

Commit 413b14b

Browse files
authored
test: Add core entry points to allow easier test setup (#18597)
1 parent cf76165 commit 413b14b

File tree

15 files changed

+311
-197
lines changed

15 files changed

+311
-197
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { n8nPage } from '../pages/n8nPage';
2+
3+
/**
4+
* Composer for UI test entry points. All methods in this class navigate to or verify UI state.
5+
* For API-only testing, use the standalone `api` fixture directly instead.
6+
*/
7+
export class TestEntryComposer {
8+
constructor(private readonly n8n: n8nPage) {}
9+
10+
/**
11+
* Start UI test from the home page and navigate to canvas
12+
*/
13+
async fromHome() {
14+
await this.n8n.goHome();
15+
await this.n8n.page.waitForURL('/home/workflows');
16+
}
17+
18+
/**
19+
* Start UI test from a blank canvas (assumes already on canvas)
20+
*/
21+
async fromBlankCanvas() {
22+
await this.n8n.goHome();
23+
await this.n8n.workflows.clickAddWorkflowButton();
24+
// Verify we're on canvas
25+
await this.n8n.canvas.canvasPane().isVisible();
26+
}
27+
28+
/**
29+
* Start UI test from a workflow in a new project
30+
*/
31+
async fromNewProject() {
32+
// Enable features to allow us to create a new project
33+
await this.n8n.api.enableFeature('projectRole:admin');
34+
await this.n8n.api.enableFeature('projectRole:editor');
35+
await this.n8n.api.setMaxTeamProjectsQuota(-1);
36+
37+
// Create a project using the API
38+
const response = await this.n8n.api.projectApi.createProject();
39+
40+
const projectId = response.id;
41+
await this.n8n.page.goto(`workflow/new?projectId=${projectId}`);
42+
await this.n8n.canvas.canvasPane().isVisible();
43+
}
44+
45+
/**
46+
* Start UI test from the canvas of an imported workflow
47+
* Returns the workflow import result for use in the test
48+
*/
49+
async fromImportedWorkflow(workflowFile: string) {
50+
const workflowImportResult = await this.n8n.api.workflowApi.importWorkflow(workflowFile);
51+
await this.n8n.page.goto(`workflow/${workflowImportResult.workflowId}`);
52+
return workflowImportResult;
53+
}
54+
}

packages/testing/playwright/fixtures/base.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
6161
const envBaseURL = process.env.N8N_BASE_URL;
6262

6363
if (envBaseURL) {
64-
console.log(`Using external N8N_BASE_URL: ${envBaseURL}`);
6564
await use(null as unknown as N8NStack);
6665
return;
6766
}
@@ -141,8 +140,8 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
141140
await page.close();
142141
},
143142

144-
n8n: async ({ page }, use) => {
145-
const n8nInstance = new n8nPage(page);
143+
n8n: async ({ page, api }, use) => {
144+
const n8nInstance = new n8nPage(page, api);
146145
await use(n8nInstance);
147146
},
148147

packages/testing/playwright/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
},
2525
"devDependencies": {
2626
"@currents/playwright": "^1.15.3",
27+
"@n8n/api-types": "workspace:^",
2728
"@playwright/test": "1.54.2",
2829
"@types/lodash": "catalog:",
2930
"eslint-plugin-playwright": "2.2.2",
3031
"generate-schema": "2.6.0",
32+
"n8n": "workspace:*",
3133
"n8n-containers": "workspace:*",
34+
"n8n-core": "workspace:*",
35+
"n8n-workflow": "workspace:*",
3236
"nanoid": "catalog:",
33-
"tsx": "catalog:",
34-
"@n8n/api-types": "workspace:^"
37+
"tsx": "catalog:"
3538
}
3639
}

packages/testing/playwright/pages/WorkflowsPage.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import type { Locator } from '@playwright/test';
33
import { BasePage } from './BasePage';
44

55
export class WorkflowsPage extends BasePage {
6-
async clickNewWorkflowCard() {
7-
await this.clickByTestId('new-workflow-card');
8-
}
9-
106
async clickAddFirstProjectButton() {
117
await this.clickByTestId('add-first-project-button');
128
}
@@ -15,10 +11,20 @@ export class WorkflowsPage extends BasePage {
1511
await this.clickByTestId('project-plus-button');
1612
}
1713

14+
/**
15+
* This is the add workflow button on the workflows page, visible when there are already workflows.
16+
*/
1817
async clickAddWorkflowButton() {
1918
await this.clickByTestId('add-resource-workflow');
2019
}
2120

21+
/**
22+
* This is the new workflow button on the workflows page, visible when there are no workflows.
23+
*/
24+
async clickNewWorkflowCard() {
25+
await this.clickByTestId('new-workflow-card');
26+
}
27+
2228
getNewWorkflowCard() {
2329
return this.page.getByTestId('new-workflow-card');
2430
}

packages/testing/playwright/pages/n8nPage.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ import { WorkflowSharingModal } from './WorkflowSharingModal';
1919
import { WorkflowsPage } from './WorkflowsPage';
2020
import { CanvasComposer } from '../composables/CanvasComposer';
2121
import { ProjectComposer } from '../composables/ProjectComposer';
22+
import { TestEntryComposer } from '../composables/TestEntryComposer';
2223
import { WorkflowComposer } from '../composables/WorkflowComposer';
24+
import type { ApiHelpers } from '../services/api-helper';
2325

2426
// eslint-disable-next-line @typescript-eslint/naming-convention
2527
export class n8nPage {
2628
readonly page: Page;
29+
readonly api: ApiHelpers;
2730

2831
// Pages
2932
readonly aiAssistant: AIAssistantPage;
@@ -51,9 +54,11 @@ export class n8nPage {
5154
readonly workflowComposer: WorkflowComposer;
5255
readonly projectComposer: ProjectComposer;
5356
readonly canvasComposer: CanvasComposer;
57+
readonly start: TestEntryComposer;
5458

55-
constructor(page: Page) {
59+
constructor(page: Page, api: ApiHelpers) {
5660
this.page = page;
61+
this.api = api;
5762

5863
// Pages
5964
this.aiAssistant = new AIAssistantPage(page);
@@ -81,6 +86,7 @@ export class n8nPage {
8186
this.workflowComposer = new WorkflowComposer(this);
8287
this.projectComposer = new ProjectComposer(this);
8388
this.canvasComposer = new CanvasComposer(this);
89+
this.start = new TestEntryComposer(this);
8490
}
8591

8692
async goHome() {

packages/testing/playwright/playwright.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export default defineConfig({
2727
retries: IS_CI ? 2 : 0,
2828
workers: WORKERS,
2929
timeout: 60000,
30-
30+
expect: {
31+
timeout: 10000,
32+
},
3133
projects: getProjects(),
3234

3335
// We use this if an n8n url is passed in. If the server is already running, we reuse it.

packages/testing/playwright/services/api-helper.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
INSTANCE_ADMIN_CREDENTIALS,
1010
} from '../config/test-users';
1111
import { TestError } from '../Types';
12+
import { ProjectApiHelper } from './project-api-helper';
1213
import { WorkflowApiHelper } from './workflow-api-helper';
1314

1415
export interface LoginResponseData {
@@ -33,10 +34,12 @@ const DB_TAGS = {
3334
export class ApiHelpers {
3435
request: APIRequestContext;
3536
workflowApi: WorkflowApiHelper;
37+
projectApi: ProjectApiHelper;
3638

3739
constructor(requestContext: APIRequestContext) {
3840
this.request = requestContext;
3941
this.workflowApi = new WorkflowApiHelper(this);
42+
this.projectApi = new ProjectApiHelper(this);
4043
}
4144

4245
// ===== MAIN SETUP METHODS =====
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { nanoid } from 'nanoid';
2+
3+
import type { ApiHelpers } from './api-helper';
4+
import { TestError } from '../Types';
5+
6+
export class ProjectApiHelper {
7+
constructor(private api: ApiHelpers) {}
8+
9+
/**
10+
* Create a new project with a unique name
11+
* @param projectName Optional base name for the project. If not provided, generates a default name.
12+
* @returns The created project data
13+
*/
14+
async createProject(projectName?: string) {
15+
const uniqueName = projectName ? `${projectName} (${nanoid(8)})` : `Test Project ${nanoid(8)}`;
16+
17+
const response = await this.api.request.post('/rest/projects', {
18+
data: {
19+
name: uniqueName,
20+
},
21+
});
22+
23+
if (!response.ok()) {
24+
throw new TestError(`Failed to create project: ${await response.text()}`);
25+
}
26+
27+
const result = await response.json();
28+
return result.data ?? result;
29+
}
30+
}

packages/testing/playwright/services/webhook-helper.ts

Lines changed: 0 additions & 160 deletions
This file was deleted.

0 commit comments

Comments
 (0)