-
Notifications
You must be signed in to change notification settings - Fork 0
test: Migrate basic folder tests to Playwright #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: eval-pr-20074-target-1758921939688
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -119,6 +119,17 @@ export class NavigationHelper { | |||||
| await this.page.goto(url); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Navigate to a specific folder | ||||||
| * URL: /projects/{projectId}/folders/{folderId} | ||||||
| */ | ||||||
| async toFolder(folderId: string, projectId?: string): Promise<void> { | ||||||
| const url = projectId | ||||||
| ? `/projects/${projectId}/folders/${folderId}/workflows` | ||||||
| : `/folders/${folderId}/workflows`; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent
Suggested change
|
||||||
| await this.page.goto(url); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Navigate to workflow canvas (alias for toWorkflow) | ||||||
| */ | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,9 @@ | ||
| import type { Page } from '@playwright/test'; | ||
|
|
||
| import { BaseModal } from './components/BaseModal'; | ||
|
|
||
| export abstract class BasePage { | ||
| protected readonly baseModal = new BaseModal(this.page); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BaseModal is instantiated in a field initializer using Reasoning: • Libraries consulted: TypeScript useDefineForClassFields field initialization order, Typescript Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Functional Bugs Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding Reasoning: Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: System Design Agent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class field initializer constructs BaseModal with undefined this.page under TS 5.x define semantics, causing runtime errors when baseModal is used. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Functional Bugs Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class field initializer uses this.page before it’s assigned, constructing BaseModal with undefined and causing runtime errors when baseModal is used. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: Functional Bugs Agent |
||
| constructor(protected readonly page: Page) {} | ||
|
|
||
| protected async clickByTestId(testId: string) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -75,7 +75,7 @@ export class WorkflowsPage extends BasePage { | |||||
| await this.page.getByRole('button', { name: 'delete' }).click(); | ||||||
| } | ||||||
|
|
||||||
| async searchWorkflows(searchTerm: string) { | ||||||
| async search(searchTerm: string) { | ||||||
| await this.clickByTestId('resources-list-search'); | ||||||
| await this.fillByTestId('resources-list-search', searchTerm); | ||||||
| } | ||||||
|
|
@@ -154,4 +154,42 @@ export class WorkflowsPage extends BasePage { | |||||
| async filterByTag(tag: string) { | ||||||
| await this.filterByTags([tag]); | ||||||
| } | ||||||
|
|
||||||
| getFolderBreadcrumbsActions() { | ||||||
| return this.page.getByTestId('folder-breadcrumbs-actions'); | ||||||
| } | ||||||
|
|
||||||
| getFolderBreadcrumbsActionToggle() { | ||||||
| return this.page.getByTestId('action-toggle-dropdown'); | ||||||
| } | ||||||
|
|
||||||
| getFolderBreadcrumbsAction(actionName: string) { | ||||||
| return this.getFolderBreadcrumbsActionToggle().getByTestId(`action-${actionName}`); | ||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Reasoning: • Libraries consulted: Element Plus ElDropdown popper append to body, Element-plus Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| addFolderButton() { | ||||||
| return this.page.getByTestId('add-folder-button'); | ||||||
| } | ||||||
|
|
||||||
| // Add region for actions | ||||||
|
|
||||||
| /** | ||||||
| * Add a folder from the add resource dropdown | ||||||
| * @returns The name of the folder | ||||||
| */ | ||||||
| async addFolder() { | ||||||
| const folderName = 'My Test Folder'; | ||||||
| await this.addResource.folder(); | ||||||
| await this.fillFolderModal(folderName); | ||||||
| return folderName; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Fill the folder modal | ||||||
| * @param folderName - The name of the folder | ||||||
| */ | ||||||
| async fillFolderModal(folderName: string) { | ||||||
| await this.baseModal.fillInput(folderName); | ||||||
| await this.baseModal.clickButton('Create'); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,14 +36,14 @@ const DB_TAGS = { | |
| export class ApiHelpers { | ||
| request: APIRequestContext; | ||
| workflowApi: WorkflowApiHelper; | ||
| projectApi: ProjectApiHelper; | ||
| projects: ProjectApiHelper; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming inconsistency: DEV MODE: This violation would have been filtered out by screening filters. Failing filters: typescriptUnderstanding, functionalImpact. Reasoning: Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: System Design Agent |
||
| credentialApi: CredentialApiHelper; | ||
| variablesApi: VariablesApiHelper; | ||
|
|
||
| constructor(requestContext: APIRequestContext) { | ||
| this.request = requestContext; | ||
| this.workflowApi = new WorkflowApiHelper(this); | ||
| this.projectApi = new ProjectApiHelper(this); | ||
| this.projects = new ProjectApiHelper(this); | ||
| this.credentialApi = new CredentialApiHelper(this); | ||
| this.variablesApi = new VariablesApiHelper(this); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { nanoid } from 'nanoid'; | ||
| import type { Folder } from '@n8n/db'; | ||
|
|
||
| import type { ApiHelpers } from './api-helper'; | ||
| import { TestError } from '../Types'; | ||
|
|
@@ -42,4 +43,142 @@ export class ProjectApiHelper { | |
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Create a new folder in a project | ||
| * @param projectId The ID of the project to create the folder in | ||
| * @param folderName The name of the folder to create | ||
| * @param parentFolderId Optional parent folder ID for nested folders | ||
| * @returns The created folder data | ||
| */ | ||
| async createFolder( | ||
| projectId: string, | ||
| folderName?: string, | ||
| parentFolderId?: string, | ||
| ): Promise<Folder> { | ||
| const uniqueName = folderName ? `${folderName} (${nanoid(8)})` : `Test Folder ${nanoid(8)}`; | ||
|
|
||
| const response = await this.api.request.post(`/rest/projects/${projectId}/folders`, { | ||
| data: { | ||
| name: uniqueName, | ||
| ...(parentFolderId && { parentFolderId }), | ||
| }, | ||
| }); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to create folder: ${await response.text()}`); | ||
| } | ||
|
|
||
| const result = await response.json(); | ||
| return result.data ?? result; | ||
| } | ||
|
|
||
| /** | ||
| * Update a folder | ||
| * @param projectId The ID of the project containing the folder | ||
| * @param folderId The ID of the folder to update | ||
| * @param updates Object containing folder updates (name, parentFolderId, tagIds) | ||
| * @returns True if update was successful | ||
| */ | ||
| async updateFolder( | ||
| projectId: string, | ||
| folderId: string, | ||
| updates: { name?: string; parentFolderId?: string; tagIds?: string[] }, | ||
| ): Promise<boolean> { | ||
| const response = await this.api.request.patch( | ||
| `/rest/projects/${projectId}/folders/${folderId}`, | ||
| { | ||
| data: updates, | ||
| }, | ||
| ); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to update folder: ${await response.text()}`); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Delete a folder | ||
| * @param projectId The ID of the project containing the folder | ||
| * @param folderId The ID of the folder to delete | ||
| * @param deleteWorkflows Whether to delete workflows in the folder (default: false) | ||
| * @returns True if deletion was successful | ||
| */ | ||
| async deleteFolder( | ||
| projectId: string, | ||
| folderId: string, | ||
| deleteWorkflows: boolean = false, | ||
| ): Promise<boolean> { | ||
| const response = await this.api.request.delete( | ||
| `/rest/projects/${projectId}/folders/${folderId}?deleteWorkflows=${deleteWorkflows}`, | ||
| ); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to delete folder: ${await response.text()}`); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Get folder tree for a specific folder | ||
| * @param projectId The ID of the project | ||
| * @param folderId The ID of the folder to get the tree for | ||
| * @returns The folder tree data | ||
| */ | ||
| async getFolderTree(projectId: string, folderId: string) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Folder tree retrieval logic duplicates Reasoning: Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent |
||
| const response = await this.api.request.get( | ||
| `/rest/projects/${projectId}/folders/${folderId}/tree`, | ||
| ); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to get folder tree: ${await response.text()}`); | ||
| } | ||
|
|
||
| const result = await response.json(); | ||
| return result.data ?? result; | ||
| } | ||
|
|
||
| /** | ||
| * List folders in a project | ||
| * @param projectId The ID of the project | ||
| * @param filters Optional filters for the folder list | ||
| * @returns The folder list data | ||
| */ | ||
| async listFolders(projectId: string, filters?: { search?: string; parentFolderId?: string }) { | ||
| const queryParams = new URLSearchParams(); | ||
| if (filters?.search) queryParams.append('search', filters.search); | ||
| if (filters?.parentFolderId) queryParams.append('parentFolderId', filters.parentFolderId); | ||
|
|
||
| const url = `/rest/projects/${projectId}/folders${queryParams.toString() ? `?${queryParams}` : ''}`; | ||
| const response = await this.api.request.get(url); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to list folders: ${await response.text()}`); | ||
| } | ||
|
|
||
| const result = await response.json(); | ||
| return result.data ?? result; | ||
| } | ||
|
|
||
| /** | ||
| * Get folder content (counts of sub-folders and workflows) | ||
| * @param projectId The ID of the project | ||
| * @param folderId The ID of the folder | ||
| * @returns Folder content counts | ||
| */ | ||
| async getFolderContent(projectId: string, folderId: string) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Folder content retrieval logic duplicates DEV MODE: This violation would have been filtered out by screening filters. Failing filters: typescriptUnderstanding. Reasoning: Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent |
||
| const response = await this.api.request.get( | ||
| `/rest/projects/${projectId}/folders/${folderId}/content`, | ||
| ); | ||
|
|
||
| if (!response.ok()) { | ||
| throw new TestError(`Failed to get folder content: ${await response.text()}`); | ||
| } | ||
|
|
||
| const result = await response.json(); | ||
| return result.data ?? result; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fromNewProjectnow enables project features (folders, sharing) globally for any new-project test, expanding test setup scope and introducing side effects in unrelated tests.Reasoning:
• GPT-5: Although
fromNewProjectnow enables additional feature flags (sharing,folders,advancedPermissions), there is no evidence that this results in incorrect behavior or impacts unrelated tests. Without demonstrable negative impact, the issue is not severe enough to report.Prompt for AI agents
[internal] Confidence score: 9/10
[internal] Posted by: System Design Agent