fix: 修复TypeScript配置错误并更新项目文档

详细说明:
- 修复了@n8n/config包的TypeScript配置错误
- 移除了不存在的jest-expect-message类型引用
- 清理了所有TypeScript构建缓存
- 更新了可行性分析文档,添加了技术实施方案
- 更新了Agent prompt文档
- 添加了会展策划工作流文档
- 包含了n8n-chinese-translation子项目
- 添加了exhibition-demo展示系统框架
This commit is contained in:
Yep_Q
2025-09-08 10:49:45 +08:00
parent 8cf9d36d81
commit 3db7af209c
426 changed files with 71699 additions and 4401 deletions

View File

@@ -0,0 +1,40 @@
import type { n8nPage } from '../pages/n8nPage';
export class CanvasComposer {
constructor(private readonly n8n: n8nPage) {}
/**
* Pin the data on a node. Then close the node.
* @param nodeName - The name of the node to pin the data on.
*/
async pinNodeData(nodeName: string) {
await this.n8n.canvas.openNode(nodeName);
await this.n8n.ndv.togglePinData();
await this.n8n.ndv.close();
}
/**
* Execute a node and wait for success toast notification
* @param nodeName - The node to execute
*/
async executeNodeAndWaitForToast(nodeName: string): Promise<void> {
await this.n8n.canvas.executeNode(nodeName);
await this.n8n.notifications.waitForNotificationAndClose('Node executed successfully');
}
/**
* Copy selected nodes and verify success toast
*/
async copySelectedNodesWithToast(): Promise<void> {
await this.n8n.canvas.copyNodes();
await this.n8n.notifications.waitForNotificationAndClose('Copied to clipboard');
}
/**
* Select all nodes and copy them
*/
async selectAllAndCopy(): Promise<void> {
await this.n8n.canvas.selectAll();
await this.copySelectedNodesWithToast();
}
}

View File

@@ -0,0 +1,55 @@
import { nanoid } from 'nanoid';
import type { n8nPage } from '../pages/n8nPage';
export class ProjectComposer {
constructor(private readonly n8n: n8nPage) {}
/**
* Create a project and return the project name and ID. If no project name is provided, a unique name will be generated.
* @param projectName - The name of the project to create.
* @returns The project name and ID.
*/
async createProject(projectName?: string) {
await this.n8n.page.getByTestId('universal-add').click();
await this.n8n.page.getByTestId('navigation-menu-item').filter({ hasText: 'Project' }).click();
await this.n8n.notifications.waitForNotificationAndClose('saved successfully');
await this.n8n.page.waitForLoadState();
const projectNameUnique = projectName ?? `Project ${nanoid(8)}`;
await this.n8n.projectSettings.fillProjectName(projectNameUnique);
await this.n8n.projectSettings.clickSaveButton();
const projectId = this.extractProjectIdFromPage('projects', 'settings');
return { projectName: projectNameUnique, projectId };
}
/**
* Add a new credential to a project.
* @param projectName - The name of the project to add the credential to.
* @param credentialType - The type of credential to add by visible name e.g 'Notion API'
* @param credentialFieldName - The name of the field to add the credential to. e.g. 'apiKey' which would be data-test-id='parameter-input-apiKey'
* @param credentialValue - The value of the credential to add.
*/
async addCredentialToProject(
projectName: string,
credentialType: string,
credentialFieldName: string,
credentialValue: string,
) {
await this.n8n.sideBar.openNewCredentialDialogForProject(projectName);
await this.n8n.credentials.openNewCredentialDialogFromCredentialList(credentialType);
await this.n8n.credentials.fillCredentialField(credentialFieldName, credentialValue);
await this.n8n.credentials.saveCredential();
await this.n8n.notifications.waitForNotificationAndClose('Credential successfully created');
await this.n8n.credentials.closeCredentialDialog();
}
extractIdFromUrl(url: string, beforeWord: string, afterWord: string): string {
const path = url.includes('://') ? new URL(url).pathname : url;
const match = path.match(new RegExp(`/${beforeWord}/([^/]+)/${afterWord}`));
return match?.[1] ?? '';
}
extractProjectIdFromPage(beforeWord: string, afterWord: string): string {
return this.extractIdFromUrl(this.n8n.page.url(), beforeWord, afterWord);
}
}

View File

@@ -0,0 +1,54 @@
import type { n8nPage } from '../pages/n8nPage';
/**
* 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.goHome();
await this.n8n.workflows.clickAddWorkflowButton();
// Verify we're on canvas
await this.n8n.canvas.canvasPane().isVisible();
}
/**
* Start UI test from a workflow in a new project
*/
async fromNewProject() {
// 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.projectApi.createProject();
const projectId = response.id;
await this.n8n.page.goto(`workflow/new?projectId=${projectId}`);
await this.n8n.canvas.canvasPane().isVisible();
}
/**
* 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.workflowApi.importWorkflow(workflowFile);
await this.n8n.page.goto(`workflow/${workflowImportResult.workflowId}`);
return workflowImportResult;
}
}

View File

@@ -0,0 +1,65 @@
import { nanoid } from 'nanoid';
import type { n8nPage } from '../pages/n8nPage';
/**
* A class for user interactions with workflows that go across multiple pages.
*/
export class WorkflowComposer {
constructor(private readonly n8n: n8nPage) {}
/**
* Executes a successful workflow and waits for the notification to be closed.
* This waits for http calls and also closes the notification.
*/
async executeWorkflowAndWaitForNotification(
notificationMessage: string,
options: { timeout?: number } = {},
) {
const { timeout = 3000 } = options;
const responsePromise = this.n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/workflows/') &&
response.url().includes('/run') &&
response.request().method() === 'POST',
);
await this.n8n.canvas.clickExecuteWorkflowButton();
await responsePromise;
await this.n8n.notifications.waitForNotificationAndClose(notificationMessage, { timeout });
}
/**
* Creates a new workflow by clicking the add workflow button and setting the name
* @param workflowName - The name of the workflow to create
*/
async createWorkflow(workflowName = 'My New Workflow') {
await this.n8n.workflows.clickAddWorkflowButton();
await this.n8n.canvas.setWorkflowName(workflowName);
const responsePromise = this.n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/workflows') && response.request().method() === 'POST',
);
await this.n8n.canvas.saveWorkflow();
await responsePromise;
}
/**
* Creates a new workflow by importing a JSON file
* @param fileName - The workflow JSON file name (e.g., 'test_pdf_workflow.json', will search in workflows folder)
* @param name - Optional custom name. If not provided, generates a unique name
* @returns The actual workflow name that was used
*/
async createWorkflowFromJsonFile(
fileName: string,
name?: string,
): Promise<{ workflowName: string }> {
const workflowName = name ?? `Imported Workflow ${nanoid(8)}`;
await this.n8n.goHome();
await this.n8n.workflows.clickAddWorkflowButton();
await this.n8n.canvas.importWorkflow(fileName, workflowName);
return { workflowName };
}
}