import { test, expect } from '../../../fixtures/base'; test.describe( 'Folders - Basic Operations', { annotation: [{ type: 'owner', description: 'Identity & Access' }], }, () => { const FOLDER_CREATED_NOTIFICATION = 'Folder created'; test('should create folder from the workflows page using addResource dropdown', async ({ n8n, }) => { await n8n.start.fromNewProject(); const folderName = await n8n.workflows.addFolder(); await expect(n8n.workflows.cards.getFolder(folderName)).toBeVisible(); await expect(n8n.workflows.cards.getFolders()).toHaveCount(1); }); test('should create folder from inside a folder', async ({ n8n }) => { const projectId = await n8n.start.fromNewProject(); const folder = await n8n.api.projects.createFolder(projectId); const folderName = folder.name; await n8n.workflows.cards.openFolder(folderName); const childFolderName = await n8n.workflows.addFolder(); await expect(n8n.workflows.cards.getFolder(childFolderName)).toBeVisible(); }); test('should create a folder from breadcrumbs', async ({ n8n }) => { const projectId = await n8n.start.fromNewProject(); const folder = await n8n.api.projects.createFolder(projectId); const folderName = folder.name; await n8n.workflows.cards.openFolder(folderName); // This opens the folder actions menu await n8n.workflows.getFolderBreadcrumbsActions().click(); await n8n.workflows.getFolderBreadcrumbsAction('create').click(); const childFolderName = 'My Child Folder'; await n8n.workflows.fillFolderModal(childFolderName); await expect(n8n.workflows.cards.getFolder(childFolderName)).toBeVisible(); }); test('should create a folder from the card dropdown', async ({ n8n }) => { const projectId = await n8n.start.fromNewProject(); const folder = await n8n.api.projects.createFolder(projectId); const folderName = folder.name; const folderCard = n8n.workflows.cards.getFolder(folderName); await n8n.workflows.cards.openCardActions(folderCard); await n8n.workflows.cards.getCardAction('create').click(); const childFolderName = 'My Child Folder'; await n8n.workflows.fillFolderModal(childFolderName); await expect(n8n.workflows.cards.getFolder(childFolderName)).toBeVisible(); }); test('should navigate from nested folder back to project root via breadcrumbs', async ({ n8n, }) => { const projectId = await n8n.start.fromNewProject(); const parentFolder = await n8n.api.projects.createFolder(projectId); const childFolder = await n8n.api.projects.createFolder( projectId, 'Child Folder', parentFolder.id, ); const grandChildFolder = await n8n.api.projects.createFolder( projectId, 'Grand Child Folder', childFolder.id, ); await n8n.navigate.toFolder(grandChildFolder.id, projectId); await expect(n8n.breadcrumbs.getCurrentBreadcrumb()).toContainText(grandChildFolder.name); // Hidden breadcrumb should be visible because not all breadcrumbs can fit in the UI await n8n.breadcrumbs.getHiddenBreadcrumbs().click(); await expect(n8n.breadcrumbs.getActionToggleDropdown(parentFolder.id)).toBeVisible(); await n8n.breadcrumbs.getBreadcrumb(childFolder.name).click(); await expect(n8n.workflows.cards.getFolder(grandChildFolder.name)).toBeVisible(); await n8n.breadcrumbs.getBreadcrumb(parentFolder.name).click(); await expect(n8n.workflows.cards.getFolder(childFolder.name)).toBeVisible(); await n8n.breadcrumbs.getHomeProjectBreadcrumb().click(); await expect(n8n.workflows.cards.getFolder(parentFolder.name)).toBeVisible(); }); test('should find nested folders through search from project root', async ({ n8n }) => { const projectId = await n8n.start.fromNewProject(); const rootFolder = await n8n.api.projects.createFolder(projectId, 'Root Test Folder'); const childFolder = await n8n.api.projects.createFolder( projectId, 'Child Test Folder', rootFolder.id, ); const grandChildFolder = await n8n.api.projects.createFolder( projectId, 'Grand Child Test Folder', childFolder.id, ); // Start at project root await n8n.navigate.toProject(projectId); // Search for "Grand Child" from root - should find the deeply nested folder await n8n.workflows.search('Grand Child'); // Verify the grandchild folder appears in search results await expect(n8n.workflows.cards.getFolder(grandChildFolder.name)).toBeVisible(); // Verify other folders are filtered out await expect(n8n.workflows.cards.getFolder(rootFolder.name)).toBeHidden(); await expect(n8n.workflows.cards.getFolder(childFolder.name)).toBeHidden(); // Clear search and verify all folders are shown again await n8n.workflows.clearSearch(); await expect(n8n.workflows.cards.getFolder(rootFolder.name)).toBeVisible(); await expect(n8n.workflows.cards.getFolder(childFolder.name)).toBeHidden(); // Child is inside root await expect(n8n.workflows.cards.getFolder(grandChildFolder.name)).toBeHidden(); // Grandchild is inside child }); test('should create workflow in a folder', async ({ n8n }) => { const { id: projectId } = await n8n.api.projects.createProject(); const folder = await n8n.api.projects.createFolder(projectId); await n8n.navigate.toFolder(folder.id, projectId); await n8n.workflows.addResource.workflow(); // Change name to trigger save await n8n.canvas.setWorkflowName('Workflow in Folder'); await n8n.page.keyboard.press('Enter'); await n8n.canvas.waitForSaveWorkflowCompleted(); await n8n.navigate.toFolder(folder.id, projectId); await expect(n8n.workflows.cards.getWorkflows()).toBeVisible(); }); test('should not create folders with invalid names in the UI', async ({ n8n }) => { await n8n.start.fromNewProject(); const invalidNames = ['folder[test]', 'folder/test']; const errorMessage = 'Folder name cannot contain the following characters'; const emptyErrorMessage = 'Folder name cannot be empty'; const tooLongErrorMessage = 'Folder name cannot be longer than 128 characters'; const dotsErrorMessage = 'Folder name cannot contain only dots'; await n8n.workflows.addResource.folder(); for (const invalidName of invalidNames) { await n8n.modal.fillInput(invalidName); await expect(n8n.modal.container.getByText(errorMessage, { exact: false })).toBeVisible(); } await n8n.modal.fillInput(''); await expect(n8n.modal.container.getByText(emptyErrorMessage)).toBeVisible(); await n8n.modal.fillInput('a'.repeat(129)); await expect(n8n.modal.container.getByText(tooLongErrorMessage)).toBeVisible(); await n8n.modal.fillInput('...'); await expect(n8n.modal.container.getByText(dotsErrorMessage)).toBeVisible(); }); test('should navigate to a folder using card actions', async ({ n8n }) => { const projectId = await n8n.start.fromNewProject(); const folder = await n8n.api.projects.createFolder(projectId); const folderName = folder.name; const folderCard = n8n.workflows.cards.getFolder(folderName); await n8n.workflows.cards.openCardActions(folderCard); await n8n.workflows.cards.getCardAction('open').click(); await expect(n8n.breadcrumbs.getCurrentBreadcrumb()).toContainText(folderName); }); test('should navigate to a folder using notification', async ({ n8n }) => { await n8n.start.fromNewProject(); const folderName = await n8n.workflows.addFolder(); await n8n.notifications .getNotificationByTitleOrContent(FOLDER_CREATED_NOTIFICATION) .getByText('Open folder') .click(); await expect(n8n.breadcrumbs.getCurrentBreadcrumb()).toContainText(folderName); }); }, );