first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
export class ClipboardHelper {
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
/**
|
||||
* Grant clipboard permissions
|
||||
* @param mode - Permission mode: 'read', 'write', or 'readwrite' (default)
|
||||
*/
|
||||
async grant(mode: 'read' | 'write' | 'readwrite' = 'readwrite'): Promise<void> {
|
||||
let permissions = ['clipboard-read', 'clipboard-write'];
|
||||
|
||||
if (mode === 'read') {
|
||||
permissions = ['clipboard-read'];
|
||||
} else if (mode === 'write') {
|
||||
permissions = ['clipboard-write'];
|
||||
}
|
||||
|
||||
await this.page.context().grantPermissions(permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write text to clipboard using page.evaluate.
|
||||
* @param text - The text to write to clipboard
|
||||
*/
|
||||
async writeText(text: string): Promise<void> {
|
||||
await this.page.evaluate(async (data) => {
|
||||
await navigator.clipboard.writeText(data);
|
||||
}, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write text to clipboard and simulate paste keyboard action.
|
||||
* @param text - The text to write to clipboard and paste
|
||||
*/
|
||||
async paste(text: string): Promise<void> {
|
||||
await this.grant();
|
||||
await this.writeText(text);
|
||||
await this.page.keyboard.press('ControlOrMeta+V');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read text from clipboard using page.evaluate.
|
||||
* @returns The text from clipboard
|
||||
*/
|
||||
async readText(): Promise<string> {
|
||||
return await this.page.evaluate(() => navigator.clipboard.readText());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* NavigationHelper provides centralized navigation methods for all n8n routes.
|
||||
* Handles both project-specific and global routes with proper URL construction.
|
||||
*
|
||||
* URLs are documented to help users understand where they're navigating:
|
||||
* - Home workflows: /home/workflows
|
||||
* - Project workflows: /projects/{projectId}/workflows
|
||||
* - Variables: /variables (global only, no project scope)
|
||||
* - Settings: /settings (global only)
|
||||
* - Credentials: /home/credentials or /projects/{projectId}/credentials
|
||||
* - Executions: /home/executions or /projects/{projectId}/executions
|
||||
*/
|
||||
export class NavigationHelper {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
/**
|
||||
* Navigate to the home dashboard
|
||||
* URL: /home
|
||||
*/
|
||||
async toHome(): Promise<void> {
|
||||
await this.page.goto('/home');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to workflows page
|
||||
* URLs:
|
||||
* - Home workflows: /home/workflows
|
||||
* - Project workflows: /projects/{projectId}/workflows
|
||||
*/
|
||||
async toWorkflows(projectId?: string): Promise<void> {
|
||||
const url = projectId ? `/projects/${projectId}/workflows` : '/home/workflows';
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to credentials page
|
||||
* URLs:
|
||||
* - Home credentials: /home/credentials
|
||||
* - Project credentials: /projects/{projectId}/credentials
|
||||
*/
|
||||
async toCredentials(projectId?: string): Promise<void> {
|
||||
const url = projectId ? `/projects/${projectId}/credentials` : '/home/credentials';
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
async toDatatables(projectId?: string): Promise<void> {
|
||||
const url = projectId ? `/projects/${projectId}/datatables` : '/home/datatables';
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to variables page (global only)
|
||||
* URL: /variables
|
||||
* Note: Variables are global and don't have project-specific scoping
|
||||
*/
|
||||
async toVariables(): Promise<void> {
|
||||
await this.page.goto('/variables');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to personal settings
|
||||
* URL: /settings/personal
|
||||
*/
|
||||
async toPersonalSettings(): Promise<void> {
|
||||
await this.page.goto('/settings/personal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific project's dashboard
|
||||
* URL: /projects/{projectId}
|
||||
*/
|
||||
async toProject(projectId: string): Promise<void> {
|
||||
await this.page.goto(`/projects/${projectId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to project settings
|
||||
* URL: /projects/{projectId}/settings
|
||||
*/
|
||||
async toProjectSettings(projectId: string): Promise<void> {
|
||||
await this.page.goto(`/projects/${projectId}/settings`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific workflow
|
||||
* URLs:
|
||||
* - New workflow: /workflow/new
|
||||
* - Existing workflow: /workflow/{workflowId}
|
||||
* - Project workflow: /projects/{projectId}/workflow/{workflowId}
|
||||
*/
|
||||
async toWorkflow(workflowId: string = 'new'): Promise<void> {
|
||||
const url = `/workflow/${workflowId}`;
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific folder
|
||||
* URL: /projects/{projectId}/folders/{folderId}/workflows or /home/folders/{folderId}/workflows
|
||||
*/
|
||||
async toFolder(folderId: string, projectId?: string): Promise<void> {
|
||||
const url = projectId
|
||||
? `/projects/${projectId}/folders/${folderId}/workflows`
|
||||
: `/home/folders/${folderId}/workflows`;
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to workflow canvas (alias for toWorkflow)
|
||||
*/
|
||||
async toCanvas(workflowId: string = 'new'): Promise<void> {
|
||||
await this.toWorkflow(workflowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to templates page
|
||||
* URL: /templates
|
||||
*/
|
||||
async toTemplates(): Promise<void> {
|
||||
await this.page.goto('/templates');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific template
|
||||
* URL: /templates/{templateId}
|
||||
*/
|
||||
async toTemplate(templateId: string): Promise<void> {
|
||||
await this.page.goto(`/templates/${templateId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to template onboarding flow
|
||||
* URL: /workflows/onboarding/{templateId}
|
||||
*/
|
||||
async toOnboardingTemplate(templateId: string): Promise<void> {
|
||||
await this.page.goto(`/workflows/onboarding/${templateId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to template import flow
|
||||
* URL: /workflows/templates/{templateId}
|
||||
*/
|
||||
async toTemplateImport(templateId: string): Promise<void> {
|
||||
await this.page.goto(`/workflows/templates/${templateId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a template collection page
|
||||
* URL: /collections/{collectionId}
|
||||
*/
|
||||
async toTemplateCollection(collectionId: number): Promise<void> {
|
||||
await this.page.goto(`/collections/${collectionId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to template credential setup page
|
||||
* URL: /templates/{templateId}/setup
|
||||
*/
|
||||
async toTemplateCredentialSetup(templateId: number): Promise<void> {
|
||||
await this.page.goto(`/templates/${templateId}/setup`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to community nodes
|
||||
* URL: /settings/community-nodes
|
||||
*/
|
||||
async toCommunityNodes(): Promise<void> {
|
||||
await this.page.goto('/settings/community-nodes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to log streaming settings
|
||||
* URL: /settings/log-streaming
|
||||
*/
|
||||
async toLogStreaming(): Promise<void> {
|
||||
await this.page.goto('/settings/log-streaming');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to users management
|
||||
* URL: /settings/users
|
||||
*/
|
||||
async toUsers(): Promise<void> {
|
||||
await this.page.goto('/settings/users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to API settings
|
||||
* URL: /settings/api
|
||||
*/
|
||||
async toApiSettings(): Promise<void> {
|
||||
await this.page.goto('/settings/api');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to environments settings
|
||||
* URL: /settings/environments
|
||||
*/
|
||||
async toEnvironments(): Promise<void> {
|
||||
await this.page.goto('/settings/environments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to settings page
|
||||
* URL: /settings/chat
|
||||
*/
|
||||
async toChatHubSettings(): Promise<void> {
|
||||
await this.page.goto('/settings/chat');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to ChatHub chat page
|
||||
* URL: /home/chat
|
||||
*/
|
||||
async toChatHub() {
|
||||
await this.page.goto('/home/chat');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to ChatHub personal agent list
|
||||
* URL: /home/chat/personal-agents
|
||||
*/
|
||||
async toChatHubPersonalAgents() {
|
||||
await this.page.goto('/home/chat/personal-agents');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to ChatHub workflow agent list
|
||||
* URL: /home/chat/workflow-agents
|
||||
*/
|
||||
async toChatHubWorkflowAgents() {
|
||||
await this.page.goto('/home/chat/workflow-agents');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { NodeDetailsViewPage } from '../pages/NodeDetailsViewPage';
|
||||
|
||||
/**
|
||||
* Helper class for setting node parameters in the NDV
|
||||
*/
|
||||
export class NodeParameterHelper {
|
||||
constructor(private ndv: NodeDetailsViewPage) {}
|
||||
|
||||
/**
|
||||
* Detects parameter type by checking DOM structure
|
||||
* Supports dropdown, text, and switch parameters
|
||||
* @param parameterName - The parameter name to check
|
||||
* @returns The detected parameter type
|
||||
*/
|
||||
async detectParameterType(parameterName: string): Promise<'dropdown' | 'text' | 'switch'> {
|
||||
const parameterContainer = this.ndv.getParameterInput(parameterName);
|
||||
const [hasSwitch, hasSelect, hasSelectCaret] = await Promise.all([
|
||||
parameterContainer
|
||||
.locator('.el-switch')
|
||||
.count()
|
||||
.then((count) => count > 0),
|
||||
parameterContainer
|
||||
.locator('.el-select')
|
||||
.count()
|
||||
.then((count) => count > 0),
|
||||
parameterContainer
|
||||
.locator('.el-select__caret')
|
||||
.count()
|
||||
.then((count) => count > 0),
|
||||
]);
|
||||
|
||||
if (hasSwitch) return 'switch';
|
||||
if (hasSelect && hasSelectCaret) return 'dropdown';
|
||||
return 'text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parameter value with automatic type detection or explicit type
|
||||
* Supports dropdown, text, and switch parameters
|
||||
* @param parameterName - Name of the parameter to set
|
||||
* @param value - Value to set (string or boolean)
|
||||
* @param type - Optional explicit type to skip detection for better performance
|
||||
*/
|
||||
async setParameter(
|
||||
parameterName: string,
|
||||
value: string | boolean,
|
||||
type?: 'dropdown' | 'text' | 'switch',
|
||||
): Promise<void> {
|
||||
if (typeof value === 'boolean') {
|
||||
await this.ndv.setParameterSwitch(parameterName, value);
|
||||
return;
|
||||
}
|
||||
|
||||
const parameterType = type ?? (await this.detectParameterType(parameterName));
|
||||
switch (parameterType) {
|
||||
case 'dropdown':
|
||||
await this.ndv.setParameterDropdown(parameterName, value);
|
||||
break;
|
||||
case 'text':
|
||||
await this.ndv.setParameterInput(parameterName, value);
|
||||
await this.ndv.waitForDebounce();
|
||||
break;
|
||||
case 'switch':
|
||||
await this.ndv.setParameterSwitch(parameterName, value === 'true');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async webhook(config: {
|
||||
httpMethod?: string;
|
||||
path?: string;
|
||||
authentication?: string;
|
||||
responseMode?: string;
|
||||
}): Promise<void> {
|
||||
if (config.httpMethod !== undefined)
|
||||
await this.setParameter('httpMethod', config.httpMethod, 'dropdown');
|
||||
if (config.path !== undefined) await this.setParameter('path', config.path, 'text');
|
||||
if (config.authentication !== undefined)
|
||||
await this.setParameter('authentication', config.authentication, 'dropdown');
|
||||
if (config.responseMode !== undefined)
|
||||
await this.setParameter('responseMode', config.responseMode, 'dropdown');
|
||||
}
|
||||
|
||||
async getWebhookPath(): Promise<string> {
|
||||
const input = this.ndv.getParameterInputField('path');
|
||||
return await input.inputValue();
|
||||
}
|
||||
|
||||
async httpRequest(config: {
|
||||
method?: string;
|
||||
url?: string;
|
||||
authentication?: string;
|
||||
sendQuery?: boolean;
|
||||
sendHeaders?: boolean;
|
||||
sendBody?: boolean;
|
||||
}): Promise<void> {
|
||||
if (config.method !== undefined) await this.setParameter('method', config.method, 'dropdown');
|
||||
if (config.url !== undefined) await this.setParameter('url', config.url, 'text');
|
||||
if (config.authentication !== undefined)
|
||||
await this.setParameter('authentication', config.authentication, 'dropdown');
|
||||
if (config.sendQuery !== undefined)
|
||||
await this.setParameter('sendQuery', config.sendQuery, 'switch');
|
||||
if (config.sendHeaders !== undefined)
|
||||
await this.setParameter('sendHeaders', config.sendHeaders, 'switch');
|
||||
if (config.sendBody !== undefined)
|
||||
await this.setParameter('sendBody', config.sendBody, 'switch');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user