mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-21 12:51:16 +08:00
test: Migrate remaining folders tests from Cypress to Playwright (#20128)
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
import {
|
||||
createFolderFromProjectHeader,
|
||||
createFolderInsideFolder,
|
||||
createNewProject,
|
||||
createWorkflowFromProjectHeader,
|
||||
dragAndDropToFolder,
|
||||
dragAndDropToProjectRoot,
|
||||
duplicateWorkflowFromCardActions,
|
||||
duplicateWorkflowFromWorkflowPage,
|
||||
getFolderCard,
|
||||
getFolderCards,
|
||||
getOverviewMenuItem,
|
||||
getProjectMenuItem,
|
||||
getWorkflowCard,
|
||||
getWorkflowCards,
|
||||
goToPersonalProject,
|
||||
} from '../../composables/folders';
|
||||
import { expandSidebar } from '../../composables/sidebar';
|
||||
import { visitWorkflowsPage } from '../../composables/workflowsPage';
|
||||
import { successToast } from '../../pages/notifications';
|
||||
|
||||
describe('Folders - Advanced Interactions', () => {
|
||||
before(() => {
|
||||
cy.resetDatabase();
|
||||
cy.enableFeature('sharing');
|
||||
cy.enableFeature('folders');
|
||||
cy.enableFeature('advancedPermissions');
|
||||
cy.enableFeature('projectRole:admin');
|
||||
cy.enableFeature('projectRole:editor');
|
||||
cy.changeQuota('maxTeamProjects', -1);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
visitWorkflowsPage();
|
||||
});
|
||||
|
||||
describe('Duplicate workflows', () => {
|
||||
beforeEach(() => {
|
||||
// Prevent the duplicated workflow from opening in a new tab
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').as('open');
|
||||
});
|
||||
});
|
||||
|
||||
it('should duplicate workflow within root folder from personal projects', () => {
|
||||
goToPersonalProject();
|
||||
createWorkflowFromProjectHeader(undefined, 'Duplicate Me From Root');
|
||||
goToPersonalProject();
|
||||
duplicateWorkflowFromCardActions('Duplicate Me From Root', 'Duplicate Me From Root (Copy)');
|
||||
getWorkflowCard('Duplicate Me From Root (Copy)').should('exist');
|
||||
});
|
||||
|
||||
it('should duplicate workflow within a folder from personal projects', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Parent folder for duplication');
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
createWorkflowFromProjectHeader(
|
||||
'Parent folder for duplication',
|
||||
'Duplicate Me From Personal',
|
||||
);
|
||||
goToPersonalProject();
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
duplicateWorkflowFromCardActions(
|
||||
'Duplicate Me From Personal',
|
||||
'Duplicate Me From Personal (Copy)',
|
||||
);
|
||||
getWorkflowCard('Duplicate Me From Personal (Copy)').should('exist');
|
||||
});
|
||||
|
||||
it('should duplicate workflow within a folder from overview', () => {
|
||||
expandSidebar();
|
||||
goToPersonalProject();
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
createWorkflowFromProjectHeader(
|
||||
'Parent folder for duplication',
|
||||
'Duplicate Me From Overview',
|
||||
);
|
||||
getOverviewMenuItem().click();
|
||||
duplicateWorkflowFromCardActions(
|
||||
'Duplicate Me From Overview',
|
||||
'Duplicate Me From Overview (Copy)',
|
||||
);
|
||||
getWorkflowCard('Duplicate Me From Overview (Copy)').should('exist');
|
||||
goToPersonalProject();
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
getWorkflowCard('Duplicate Me From Overview (Copy)').should('exist');
|
||||
});
|
||||
|
||||
it('should duplicate workflow within a folder from workflow', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Parent folder for duplication');
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
createWorkflowFromProjectHeader(
|
||||
'Parent folder for duplication',
|
||||
'Duplicate Me From Workflow',
|
||||
);
|
||||
duplicateWorkflowFromWorkflowPage('Duplicate Me From Workflow (Copy)');
|
||||
goToPersonalProject();
|
||||
getFolderCard('Parent folder for duplication').click();
|
||||
getWorkflowCard('Duplicate Me From Workflow (Copy)').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Drag and drop', () => {
|
||||
it('should drag and drop folders into folders', () => {
|
||||
const PROJECT_NAME = 'Drag and Drop Test';
|
||||
const TARGET_NAME = 'Drag me';
|
||||
const DESTINATION_NAME = 'Folder Destination';
|
||||
|
||||
expandSidebar();
|
||||
createNewProject(PROJECT_NAME, { openAfterCreate: true });
|
||||
createFolderFromProjectHeader(TARGET_NAME);
|
||||
createFolderFromProjectHeader(DESTINATION_NAME);
|
||||
|
||||
dragAndDropToFolder(TARGET_NAME, DESTINATION_NAME);
|
||||
successToast().should('contain.text', `${TARGET_NAME} has been moved to ${DESTINATION_NAME}`);
|
||||
// Only one folder card should remain
|
||||
getFolderCards().should('have.length', 1);
|
||||
// Check folder in the destination
|
||||
getFolderCard(DESTINATION_NAME).click();
|
||||
getFolderCard(TARGET_NAME).should('exist');
|
||||
});
|
||||
|
||||
it('should drag and drop folders into project root breadcrumb', () => {
|
||||
const PROJECT_NAME = 'Drag to root test';
|
||||
const TARGET_NAME = 'To Project root';
|
||||
const PARENT_NAME = 'Parent Folder';
|
||||
|
||||
expandSidebar();
|
||||
createNewProject(PROJECT_NAME, { openAfterCreate: true });
|
||||
createFolderFromProjectHeader(PARENT_NAME);
|
||||
createFolderInsideFolder(TARGET_NAME, PARENT_NAME);
|
||||
|
||||
dragAndDropToProjectRoot(TARGET_NAME);
|
||||
|
||||
// No folder cards should be shown in the parent folder
|
||||
getFolderCards().should('not.exist');
|
||||
successToast().should('contain.text', `${TARGET_NAME} has been moved to ${PROJECT_NAME}`);
|
||||
// Check folder in the project root
|
||||
getProjectMenuItem(PROJECT_NAME).click();
|
||||
getFolderCard(TARGET_NAME).should('exist');
|
||||
});
|
||||
|
||||
it('should drag and drop workflows into folders', () => {
|
||||
const PROJECT_NAME = 'Drag and Drop WF Test';
|
||||
const TARGET_NAME = 'Drag me - WF';
|
||||
const DESTINATION_NAME = 'Workflow Destination';
|
||||
|
||||
expandSidebar();
|
||||
createNewProject(PROJECT_NAME, { openAfterCreate: true });
|
||||
createFolderFromProjectHeader(DESTINATION_NAME);
|
||||
createWorkflowFromProjectHeader(undefined, TARGET_NAME);
|
||||
getProjectMenuItem(PROJECT_NAME).click();
|
||||
dragAndDropToFolder(TARGET_NAME, DESTINATION_NAME);
|
||||
// No workflow cards should be shown in the project root
|
||||
getWorkflowCards().should('not.exist');
|
||||
successToast().should('contain.text', `${TARGET_NAME} has been moved to ${DESTINATION_NAME}`);
|
||||
// Check workflow in the destination
|
||||
getFolderCard(DESTINATION_NAME).click();
|
||||
getWorkflowCard(TARGET_NAME).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,247 +0,0 @@
|
||||
import {
|
||||
createFolderFromProjectHeader,
|
||||
createFolderInsideFolder,
|
||||
createWorkflowFromProjectHeader,
|
||||
deleteAndTransferFolderContentsFromCardDropdown,
|
||||
deleteAndTransferFolderContentsFromListDropdown,
|
||||
deleteEmptyFolderFromCardDropdown,
|
||||
deleteEmptyFolderFromListDropdown,
|
||||
deleteFolderWithContentsFromCardDropdown,
|
||||
deleteFolderWithContentsFromListDropdown,
|
||||
getCurrentBreadcrumbText,
|
||||
getFolderCard,
|
||||
getFolderCards,
|
||||
getFolderEmptyState,
|
||||
getHomeProjectBreadcrumb,
|
||||
getListBreadcrumbItem,
|
||||
getListBreadcrumbs,
|
||||
getPersonalProjectMenuItem,
|
||||
getWorkflowCard,
|
||||
getWorkflowCards,
|
||||
goToPersonalProject,
|
||||
moveFolderFromFolderCardActions,
|
||||
moveFolderFromListActions,
|
||||
moveWorkflowToFolder,
|
||||
renameFolderFromCardActions,
|
||||
renameFolderFromListActions,
|
||||
} from '../../composables/folders';
|
||||
import { visitWorkflowsPage } from '../../composables/workflowsPage';
|
||||
import { successToast } from '../../pages/notifications';
|
||||
|
||||
describe('Folders - Operations', () => {
|
||||
before(() => {
|
||||
cy.resetDatabase();
|
||||
cy.enableFeature('sharing');
|
||||
cy.enableFeature('folders');
|
||||
cy.enableFeature('advancedPermissions');
|
||||
cy.enableFeature('projectRole:admin');
|
||||
cy.enableFeature('projectRole:editor');
|
||||
cy.changeQuota('maxTeamProjects', -1);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
visitWorkflowsPage();
|
||||
});
|
||||
|
||||
describe('Rename and delete folders', () => {
|
||||
it('should rename folder from main dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Rename Me');
|
||||
getFolderCard('Rename Me').should('exist');
|
||||
renameFolderFromListActions('Rename Me', 'Renamed');
|
||||
getCurrentBreadcrumbText().should('equal', 'Renamed');
|
||||
});
|
||||
|
||||
it('should rename folder from card dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Rename Me 2');
|
||||
renameFolderFromCardActions('Rename Me 2', 'Renamed 2');
|
||||
getFolderCard('Renamed 2').should('exist');
|
||||
});
|
||||
|
||||
it('should delete empty folder from card dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Delete Me');
|
||||
getFolderCard('Delete Me').should('exist');
|
||||
deleteEmptyFolderFromCardDropdown('Delete Me');
|
||||
});
|
||||
|
||||
it('should delete empty folder from main dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Delete Me 2');
|
||||
getFolderCard('Delete Me 2').should('exist');
|
||||
deleteEmptyFolderFromListDropdown('Delete Me 2');
|
||||
// Since we deleted the current folder, we should be back in the home project
|
||||
getListBreadcrumbs().should('not.exist');
|
||||
getPersonalProjectMenuItem().find('li').should('have.class', 'is-active');
|
||||
});
|
||||
|
||||
it('should warn before deleting non-empty folder from list dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('I have children');
|
||||
createFolderInsideFolder('Child 1', 'I have children');
|
||||
deleteFolderWithContentsFromListDropdown('I have children');
|
||||
// Since we deleted the current folder, we should be back in the home project
|
||||
getListBreadcrumbs().should('not.exist');
|
||||
getPersonalProjectMenuItem().find('li').should('have.class', 'is-active');
|
||||
});
|
||||
|
||||
it('should warn before deleting non-empty folder from card dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('I also have family');
|
||||
createFolderInsideFolder('Child 1', 'I also have family');
|
||||
// Back to home
|
||||
getHomeProjectBreadcrumb().click();
|
||||
getFolderCard('I also have family').should('exist');
|
||||
deleteFolderWithContentsFromCardDropdown('I also have family');
|
||||
});
|
||||
|
||||
it('should transfer contents when deleting non-empty folder - from card dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move my contents');
|
||||
createFolderFromProjectHeader('Destination');
|
||||
createFolderInsideFolder('Child 1', 'Move my contents');
|
||||
getHomeProjectBreadcrumb().click();
|
||||
getFolderCard('Move my contents').should('exist');
|
||||
deleteAndTransferFolderContentsFromCardDropdown('Move my contents', 'Destination');
|
||||
getFolderCard('Destination').click();
|
||||
// Should show the contents of the moved folder
|
||||
getFolderCard('Child 1').should('exist');
|
||||
});
|
||||
|
||||
it('should transfer contents when deleting non-empty folder - from list breadcrumbs', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move me too');
|
||||
createFolderFromProjectHeader('Destination 2');
|
||||
createFolderInsideFolder('Child 1', 'Move me too');
|
||||
deleteAndTransferFolderContentsFromListDropdown('Destination 2');
|
||||
getFolderCard('Destination').click();
|
||||
// Should show the contents of the moved folder
|
||||
getFolderCard('Child 1').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Move folders and workflows', () => {
|
||||
it('should move empty folder to another folder - from folder card action', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move me - I am empty');
|
||||
createFolderFromProjectHeader('Destination 3');
|
||||
moveFolderFromFolderCardActions('Move me - I am empty', 'Destination 3');
|
||||
getFolderCard('Destination 3').click();
|
||||
getFolderCard('Move me - I am empty').should('exist');
|
||||
getFolderCard('Move me - I am empty').click();
|
||||
getFolderEmptyState().should('exist');
|
||||
successToast().should('contain.text', 'Move me - I am empty has been moved to Destination 3');
|
||||
// Breadcrumbs should show the destination folder
|
||||
getListBreadcrumbItem('Destination 3').should('exist');
|
||||
});
|
||||
|
||||
it('should move folder with contents to another folder - from folder card action', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move me - I have family');
|
||||
createFolderFromProjectHeader('Destination 4');
|
||||
// Create a workflow and a folder inside the folder
|
||||
createFolderInsideFolder('Child 1', 'Move me - I have family');
|
||||
createWorkflowFromProjectHeader('Move me - I have family');
|
||||
goToPersonalProject();
|
||||
// Move the folder
|
||||
moveFolderFromFolderCardActions('Move me - I have family', 'Destination 4');
|
||||
successToast().should(
|
||||
'contain.text',
|
||||
'Move me - I have family has been moved to Destination 4',
|
||||
);
|
||||
// Go to destination folder and check if contents are there
|
||||
getFolderCard('Destination 4').click();
|
||||
// Moved folder should be there
|
||||
getFolderCard('Move me - I have family').should('exist').click();
|
||||
// Both the workflow and the folder should be there
|
||||
getFolderCards().should('have.length', 1);
|
||||
getWorkflowCards().should('have.length', 1);
|
||||
// Breadcrumbs should show the destination folder
|
||||
getListBreadcrumbItem('Destination 4').should('exist');
|
||||
});
|
||||
|
||||
it('should move empty folder to another folder - from list breadcrumbs', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move me too - I am empty');
|
||||
createFolderFromProjectHeader('Destination 5');
|
||||
moveFolderFromListActions('Move me too - I am empty', 'Destination 5');
|
||||
// Since we moved the current folder, we should be in the destination folder
|
||||
getCurrentBreadcrumbText().should('equal', 'Destination 5');
|
||||
});
|
||||
|
||||
it('should move folder with contents to another folder - from list dropdown', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Move me - I have family 2');
|
||||
createFolderFromProjectHeader('Destination 6');
|
||||
// Create a workflow and a folder inside the folder
|
||||
createFolderInsideFolder('Child 1', 'Move me - I have family 2');
|
||||
createWorkflowFromProjectHeader('Move me - I have family 2');
|
||||
// Navigate back to folder
|
||||
goToPersonalProject();
|
||||
getFolderCard('Move me - I have family 2').should('exist');
|
||||
// Move the folder
|
||||
moveFolderFromListActions('Move me - I have family 2', 'Destination 6');
|
||||
// Since we moved the current folder, we should be in the destination folder
|
||||
getCurrentBreadcrumbText().should('equal', 'Destination 6');
|
||||
// Moved folder should be there
|
||||
getFolderCard('Move me - I have family 2').should('exist').click();
|
||||
// After navigating to the moved folder, both the workflow and the folder should be there
|
||||
getFolderCards().should('have.length', 1);
|
||||
getWorkflowCards().should('have.length', 1);
|
||||
// Breadcrumbs should show the destination folder
|
||||
getListBreadcrumbItem('Destination 6').should('exist');
|
||||
});
|
||||
|
||||
it('should move folder to project root - from folder card action', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Test parent');
|
||||
createFolderInsideFolder('Move me to root', 'Test parent');
|
||||
moveFolderFromFolderCardActions('Move me to root', 'No folder (project root)');
|
||||
// Parent folder should be empty
|
||||
getFolderEmptyState().should('exist');
|
||||
// Child folder should be in the root
|
||||
goToPersonalProject();
|
||||
getFolderCard('Move me to root').should('exist');
|
||||
// Navigate to the moved folder and check breadcrumbs
|
||||
getFolderCard('Move me to root').click();
|
||||
getHomeProjectBreadcrumb().should('contain.text', 'Personal');
|
||||
getListBreadcrumbs().findChildByTestId('breadcrumbs-item').should('not.exist');
|
||||
getCurrentBreadcrumbText().should('equal', 'Move me to root');
|
||||
});
|
||||
|
||||
it('should move workflow from project root to folder', () => {
|
||||
goToPersonalProject();
|
||||
createWorkflowFromProjectHeader(undefined, 'Move me');
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Workflow destination');
|
||||
moveWorkflowToFolder('Move me', 'Workflow destination');
|
||||
successToast().should('contain.text', 'Move me has been moved to Workflow destination');
|
||||
// Navigate to the destination folder
|
||||
getFolderCard('Workflow destination').click();
|
||||
// Moved workflow should be there
|
||||
getWorkflowCards().should('have.length', 1);
|
||||
getWorkflowCard('Move me').should('exist');
|
||||
});
|
||||
|
||||
it('should move workflow to another folder', () => {
|
||||
goToPersonalProject();
|
||||
createFolderFromProjectHeader('Moving workflow from here');
|
||||
createFolderFromProjectHeader('Moving workflow to here');
|
||||
getFolderCard('Moving workflow from here').click();
|
||||
createWorkflowFromProjectHeader(undefined, 'Move me');
|
||||
goToPersonalProject();
|
||||
getFolderCard('Moving workflow from here').click();
|
||||
getWorkflowCard('Move me').should('exist');
|
||||
moveWorkflowToFolder('Move me', 'Moving workflow to here');
|
||||
// Now folder should be empty
|
||||
getFolderEmptyState().should('exist');
|
||||
// Navigate to the destination folder
|
||||
getHomeProjectBreadcrumb().click();
|
||||
getFolderCard('Moving workflow to here').click();
|
||||
// Moved workflow should be there
|
||||
getWorkflowCards().should('have.length', 1);
|
||||
getWorkflowCard('Move me').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,6 @@ export class CredentialsComposer {
|
||||
* Create a credential directly via API. Returns created credential object.
|
||||
*/
|
||||
async createFromApi(payload: CreateCredentialDto & { projectId?: string }) {
|
||||
return await this.n8n.api.credentialApi.createCredential(payload);
|
||||
return await this.n8n.api.credentials.createCredential(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import type { n8nPage } from '../pages/n8nPage';
|
||||
|
||||
/**
|
||||
@@ -44,10 +46,7 @@ export class TestEntryComposer {
|
||||
}
|
||||
|
||||
async fromNewProject() {
|
||||
await this.withProjectFeatures();
|
||||
// Create a project using the API
|
||||
const response = await this.n8n.api.projects.createProject();
|
||||
|
||||
const projectId = response.id;
|
||||
await this.n8n.navigate.toProject(projectId);
|
||||
return projectId;
|
||||
@@ -58,11 +57,26 @@ export class TestEntryComposer {
|
||||
* Returns the workflow import result for use in the test
|
||||
*/
|
||||
async fromImportedWorkflow(workflowFile: string) {
|
||||
const workflowImportResult = await this.n8n.api.workflowApi.importWorkflow(workflowFile);
|
||||
const workflowImportResult = await this.n8n.api.workflows.importWorkflow(workflowFile);
|
||||
await this.n8n.page.goto(`workflow/${workflowImportResult.workflowId}`);
|
||||
return workflowImportResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start UI test on a new page created by an action
|
||||
* @param action - The action that will create a new page
|
||||
* @returns n8nPage instance for the new page
|
||||
*/
|
||||
async fromNewPage(action: () => Promise<void>): Promise<n8nPage> {
|
||||
const newPagePromise = this.n8n.page.waitForEvent('popup');
|
||||
await action();
|
||||
const newPage = await newPagePromise;
|
||||
await newPage.waitForLoadState('domcontentloaded');
|
||||
// Use the constructor from the current instance to avoid circular dependency
|
||||
const n8nPageConstructor = this.n8n.constructor as new (page: Page) => n8nPage;
|
||||
return new n8nPageConstructor(newPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable project feature set
|
||||
* Allow project creation, sharing, and folder creation
|
||||
|
||||
@@ -138,6 +138,8 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
|
||||
const page = await context.newPage();
|
||||
const n8nInstance = new n8nPage(page);
|
||||
await n8nInstance.api.setupFromTags(testInfo.tags);
|
||||
// Enable project features for the tests, this is used in several tests, but is never disabled in tests, so we can have it on by default
|
||||
await n8nInstance.start.withProjectFeatures();
|
||||
await use(n8nInstance);
|
||||
},
|
||||
|
||||
|
||||
@@ -112,16 +112,14 @@ export class NavigationHelper {
|
||||
* - Existing workflow: /workflow/{workflowId}
|
||||
* - Project workflow: /projects/{projectId}/workflow/{workflowId}
|
||||
*/
|
||||
async toWorkflow(workflowId: string = 'new', projectId?: string): Promise<void> {
|
||||
const url = projectId
|
||||
? `/projects/${projectId}/workflow/${workflowId}`
|
||||
: `/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}
|
||||
* URL: /projects/{projectId}/folders/{folderId}/workflows or /home/folders/{folderId}/workflows
|
||||
*/
|
||||
async toFolder(folderId: string, projectId?: string): Promise<void> {
|
||||
const url = projectId
|
||||
@@ -133,8 +131,8 @@ export class NavigationHelper {
|
||||
/**
|
||||
* Navigate to workflow canvas (alias for toWorkflow)
|
||||
*/
|
||||
async toCanvas(workflowId: string = 'new', projectId?: string): Promise<void> {
|
||||
await this.toWorkflow(workflowId, projectId);
|
||||
async toCanvas(workflowId: string = 'new'): Promise<void> {
|
||||
await this.toWorkflow(workflowId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,11 @@ import type { Page } from '@playwright/test';
|
||||
import { BaseModal } from './components/BaseModal';
|
||||
|
||||
export abstract class BasePage {
|
||||
protected readonly baseModal = new BaseModal(this.page);
|
||||
constructor(protected readonly page: Page) {}
|
||||
protected readonly baseModal: BaseModal;
|
||||
|
||||
constructor(protected readonly page: Page) {
|
||||
this.baseModal = new BaseModal(this.page);
|
||||
}
|
||||
|
||||
protected async clickByTestId(testId: string) {
|
||||
await this.page.getByTestId(testId).click();
|
||||
|
||||
@@ -752,4 +752,8 @@ export class CanvasPage extends BasePage {
|
||||
getCanvasPlusButton(): Locator {
|
||||
return this.page.getByTestId('canvas-plus-button');
|
||||
}
|
||||
|
||||
getWorkflowName(): Locator {
|
||||
return this.page.getByTestId('workflow-name-input');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ export class WorkflowSettingsModal extends BasePage {
|
||||
getErrorWorkflowField(): Locator {
|
||||
return this.page.getByTestId('workflow-settings-error-workflow');
|
||||
}
|
||||
getDuplicateMenuItem(): Locator {
|
||||
return this.page.getByTestId('workflow-menu-item-duplicate');
|
||||
}
|
||||
|
||||
getSaveButton(): Locator {
|
||||
return this.page.getByRole('button', { name: 'Save' });
|
||||
|
||||
@@ -154,7 +154,6 @@ export class WorkflowsPage extends BasePage {
|
||||
async filterByTag(tag: string) {
|
||||
await this.filterByTags([tag]);
|
||||
}
|
||||
|
||||
getFolderBreadcrumbsActions() {
|
||||
return this.page.getByTestId('folder-breadcrumbs-actions');
|
||||
}
|
||||
@@ -187,9 +186,51 @@ export class WorkflowsPage extends BasePage {
|
||||
/**
|
||||
* Fill the folder modal
|
||||
* @param folderName - The name of the folder
|
||||
* @param buttonText - The text of the button to click (default: 'Create')
|
||||
*/
|
||||
async fillFolderModal(folderName: string) {
|
||||
async fillFolderModal(folderName: string, buttonText: string = 'Create') {
|
||||
await this.baseModal.fillInput(folderName);
|
||||
await this.baseModal.clickButton('Create');
|
||||
await this.baseModal.clickButton(buttonText);
|
||||
}
|
||||
|
||||
deleteFolderModal() {
|
||||
return this.page.getByTestId('deleteFolder-modal');
|
||||
}
|
||||
|
||||
deleteModalTransferRadioButton() {
|
||||
return this.deleteFolderModal().getByTestId('transfer-content-radio');
|
||||
}
|
||||
|
||||
deleteModalDeleteRadioButton() {
|
||||
return this.deleteFolderModal().getByTestId('delete-content-radio');
|
||||
}
|
||||
|
||||
deleteModalConfirmButton() {
|
||||
return this.deleteFolderModal().getByTestId('confirm-delete-folder-button');
|
||||
}
|
||||
|
||||
transferFolderDropdown() {
|
||||
return this.deleteFolderModal().getByRole('combobox', { name: 'Select a folder' });
|
||||
}
|
||||
|
||||
transferFolderOption(folderName: string) {
|
||||
return this.page.getByTestId('move-to-folder-option').filter({ hasText: folderName });
|
||||
}
|
||||
|
||||
// Move folder modal methods
|
||||
moveFolderModal() {
|
||||
return this.page.getByTestId('moveFolder-modal');
|
||||
}
|
||||
|
||||
moveFolderDropdown() {
|
||||
return this.moveFolderModal().getByTestId('move-to-folder-dropdown').getByRole('combobox');
|
||||
}
|
||||
|
||||
moveFolderOption(folderName: string) {
|
||||
return this.page.getByTestId('move-to-folder-option').filter({ hasText: folderName });
|
||||
}
|
||||
|
||||
moveFolderConfirmButton() {
|
||||
return this.moveFolderModal().getByTestId('confirm-move-folder-button');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,18 @@ export class Breadcrumbs {
|
||||
getActionToggleDropdown(resourceName: string) {
|
||||
return this.page.getByTestId('action-toggle-dropdown').getByTestId(`action-${resourceName}`);
|
||||
}
|
||||
}
|
||||
|
||||
getFolderBreadcrumbsActionToggle() {
|
||||
return this.page.getByTestId('folder-breadcrumbs-actions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the current breadcrumb by activating inline edit mode
|
||||
* @param newName - The new name for the breadcrumb item
|
||||
*/
|
||||
async renameCurrentBreadcrumb(newName: string) {
|
||||
await this.getCurrentBreadcrumb().getByTestId('inline-edit-preview').click();
|
||||
await this.getCurrentBreadcrumb().getByTestId('inline-edit-input').fill(newName);
|
||||
await this.page.keyboard.press('Enter');
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,14 @@ export class ResourceCards {
|
||||
}
|
||||
|
||||
getCardActionToggle(card: Locator): Locator {
|
||||
return card.getByTestId('card-append');
|
||||
return card
|
||||
.getByTestId('card-append')
|
||||
.locator('[class*="action-toggle"]')
|
||||
.filter({ visible: true });
|
||||
}
|
||||
|
||||
getCardAction(actionName: string): Locator {
|
||||
return this.page.getByTestId(`action-${actionName}`);
|
||||
return this.page.getByTestId(`action-${actionName}`).filter({ visible: true });
|
||||
}
|
||||
|
||||
async openCardActions(card: Locator): Promise<void> {
|
||||
@@ -66,4 +69,9 @@ export class ResourceCards {
|
||||
const folderCard = this.getFolder(folderName);
|
||||
await this.clickCardAction(folderCard, 'open');
|
||||
}
|
||||
|
||||
async deleteFolder(folderName: string): Promise<void> {
|
||||
const folderCard = this.getFolder(folderName);
|
||||
await this.clickCardAction(folderCard, 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +35,17 @@ const DB_TAGS = {
|
||||
|
||||
export class ApiHelpers {
|
||||
request: APIRequestContext;
|
||||
workflowApi: WorkflowApiHelper;
|
||||
workflows: WorkflowApiHelper;
|
||||
projects: ProjectApiHelper;
|
||||
credentialApi: CredentialApiHelper;
|
||||
variablesApi: VariablesApiHelper;
|
||||
credentials: CredentialApiHelper;
|
||||
variables: VariablesApiHelper;
|
||||
|
||||
constructor(requestContext: APIRequestContext) {
|
||||
this.request = requestContext;
|
||||
this.workflowApi = new WorkflowApiHelper(this);
|
||||
this.workflows = new WorkflowApiHelper(this);
|
||||
this.projects = new ProjectApiHelper(this);
|
||||
this.credentialApi = new CredentialApiHelper(this);
|
||||
this.variablesApi = new VariablesApiHelper(this);
|
||||
this.credentials = new CredentialApiHelper(this);
|
||||
this.variables = new VariablesApiHelper(this);
|
||||
}
|
||||
|
||||
// ===== MAIN SETUP METHODS =====
|
||||
|
||||
@@ -35,6 +35,48 @@ export class WorkflowApiHelper {
|
||||
return result.data ?? result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a workflow in a project with optional folder placement (Uses Internal API not public API)
|
||||
* @param project - Required project ID where the workflow will be created
|
||||
* @param options - Optional configuration for workflow creation
|
||||
* @param options.folder - Optional folder ID to place the workflow in
|
||||
* @param options.name - Optional workflow name. If not provided, generates a unique name using nanoid
|
||||
* @returns Object containing the name and ID of the created workflow
|
||||
*/
|
||||
async createInProject(
|
||||
project: string,
|
||||
options?: {
|
||||
folder?: string;
|
||||
name?: string;
|
||||
},
|
||||
): Promise<{ name: string; id: string }> {
|
||||
const workflowName = options?.name ?? `Test Workflow ${nanoid(8)}`;
|
||||
|
||||
const workflow = {
|
||||
name: workflowName,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
settings: {},
|
||||
active: false,
|
||||
projectId: project,
|
||||
...(options?.folder && { parentFolderId: options.folder }),
|
||||
};
|
||||
|
||||
const response = await this.api.request.post('/rest/workflows', { data: workflow });
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new TestError(`Failed to create workflow: ${await response.text()}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const workflowData = result.data ?? result;
|
||||
|
||||
return {
|
||||
name: workflowName,
|
||||
id: workflowData.id,
|
||||
};
|
||||
}
|
||||
|
||||
async setActive(workflowId: string, active: boolean) {
|
||||
const response = await this.api.request.patch(`/rest/workflows/${workflowId}?forceSave=true`, {
|
||||
data: { active },
|
||||
|
||||
@@ -11,12 +11,6 @@ const NOTIFICATIONS = {
|
||||
|
||||
test.describe('Workflows', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.api.enableFeature('sharing');
|
||||
await n8n.api.enableFeature('folders');
|
||||
await n8n.api.enableFeature('advancedPermissions');
|
||||
await n8n.api.enableFeature('projectRole:admin');
|
||||
await n8n.api.enableFeature('projectRole:editor');
|
||||
await n8n.api.setMaxTeamProjectsQuota(-1);
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
@@ -35,7 +29,7 @@ test.describe('Workflows', () => {
|
||||
const uniqueIdForCreate = nanoid(8);
|
||||
const workflowName = `Test Workflow ${uniqueIdForCreate}`;
|
||||
await n8n.canvas.setWorkflowName(workflowName);
|
||||
await n8n.canvas.clickSaveWorkflowButton();
|
||||
await n8n.canvas.saveWorkflow();
|
||||
|
||||
await expect(n8n.notifications.getNotificationByTitle(NOTIFICATIONS.CREATED)).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -26,14 +26,14 @@ test.describe('Credentials', () => {
|
||||
const credentialA = `A Credential ${nanoid()}`;
|
||||
const credentialZ = `Z Credential ${nanoid()}`;
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: credentialA,
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: credentialZ,
|
||||
type: 'trelloApi',
|
||||
data: { apiKey: 'test_api_key', apiToken: 'test_api_token' },
|
||||
@@ -94,14 +94,14 @@ test.describe('Credentials', () => {
|
||||
const serviceAccountCredentialName2 = `OAuth2 Credential ${nanoid()}`;
|
||||
const serviceAccountCredentialName = `Service Account Credential ${nanoid()}`;
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: serviceAccountCredentialName2,
|
||||
type: 'googleApi',
|
||||
data: { email: 'test@service.com', privateKey: 'test_key' },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: serviceAccountCredentialName,
|
||||
type: 'googleApi',
|
||||
data: { email: 'test@service.com', privateKey: 'test_key' },
|
||||
@@ -234,7 +234,7 @@ test.describe('Credentials', () => {
|
||||
const projectId = await n8n.start.fromNewProjectBlankCanvas();
|
||||
const credentialName = `My awesome Notion account ${nanoid()}`;
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: credentialName,
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
@@ -245,16 +245,16 @@ test.describe('Credentials', () => {
|
||||
await n8n.canvas.addNode('Notion', { action: 'Append a block' });
|
||||
await expect(n8n.ndv.getCredentialSelect()).toHaveValue(credentialName);
|
||||
|
||||
const credentials = await n8n.api.credentialApi.getCredentials();
|
||||
const credentials = await n8n.api.credentials.getCredentials();
|
||||
const credential = credentials.find((c) => c.name === credentialName);
|
||||
await n8n.api.credentialApi.deleteCredential(credential!.id);
|
||||
await n8n.api.credentials.deleteCredential(credential!.id);
|
||||
});
|
||||
|
||||
test('should set a default credential when editing a node', async ({ n8n }) => {
|
||||
const projectId = await n8n.start.fromNewProjectBlankCanvas();
|
||||
const credentialName = `My awesome Notion account ${nanoid()}`;
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: credentialName,
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
@@ -268,9 +268,9 @@ test.describe('Credentials', () => {
|
||||
await n8n.ndv.selectOptionInParameterDropdown('nodeCredentialType', 'Notion API');
|
||||
await expect(n8n.ndv.getCredentialSelect()).toHaveValue(credentialName);
|
||||
|
||||
const credentials = await n8n.api.credentialApi.getCredentials();
|
||||
const credentials = await n8n.api.credentials.getCredentials();
|
||||
const credential = credentials.find((c) => c.name === credentialName);
|
||||
await n8n.api.credentialApi.deleteCredential(credential!.id);
|
||||
await n8n.api.credentials.deleteCredential(credential!.id);
|
||||
});
|
||||
|
||||
test('should setup generic authentication for HTTP node', async ({ n8n }) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ test.describe('Variables', () => {
|
||||
test.describe('licensed', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.api.enableFeature('variables');
|
||||
await n8n.api.variablesApi.deleteAllVariables();
|
||||
await n8n.api.variables.deleteAllVariables();
|
||||
await n8n.navigate.toVariables();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
import { n8nPage } from '../../pages/n8nPage';
|
||||
import type { n8nPage } from '../../pages/n8nPage';
|
||||
|
||||
const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
||||
const EXECUTE_WORKFLOW_NODE_NAME = 'Execute Sub-workflow';
|
||||
@@ -18,12 +18,6 @@ async function getCredentialsForProject(n8n: n8nPage, projectId?: string) {
|
||||
|
||||
test.describe('Projects', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.api.enableFeature('sharing');
|
||||
await n8n.api.enableFeature('folders');
|
||||
await n8n.api.enableFeature('advancedPermissions');
|
||||
await n8n.api.enableFeature('projectRole:admin');
|
||||
await n8n.api.enableFeature('projectRole:editor');
|
||||
await n8n.api.setMaxTeamProjectsQuota(-1);
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
@@ -64,11 +58,9 @@ test.describe('Projects', () => {
|
||||
|
||||
await n8n.canvas.addNode(EXECUTE_WORKFLOW_NODE_NAME, { action: 'Execute A Sub Workflow' });
|
||||
|
||||
const subWorkflowPagePromise = n8n.page.waitForEvent('popup');
|
||||
|
||||
await n8n.ndv.selectWorkflowResource(`Create a Sub-Workflow in '${projectName}'`);
|
||||
|
||||
const subn8n = new n8nPage(await subWorkflowPagePromise);
|
||||
const subn8n = await n8n.start.fromNewPage(() =>
|
||||
n8n.ndv.selectWorkflowResource(`Create a Sub-Workflow in '${projectName}'`),
|
||||
);
|
||||
|
||||
await subn8n.ndv.clickBackToCanvasButton();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ test.describe('Workflow Selector Parameter @db:reset', () => {
|
||||
];
|
||||
|
||||
for (const { file } of subWorkflows) {
|
||||
await n8n.api.workflowApi.importWorkflow(file);
|
||||
await n8n.api.workflows.importWorkflow(file);
|
||||
}
|
||||
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Folders - Advanced Operations', () => {
|
||||
test.describe('Duplicate workflows', () => {
|
||||
test('should duplicate workflow within root folder from personal projects', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const { name: workflowName } = await n8n.api.workflows.createInProject(projectId);
|
||||
await n8n.navigate.toProject(projectId);
|
||||
const workflowCard = n8n.workflows.cards.getWorkflow(workflowName);
|
||||
await n8n.workflows.cards.openCardActions(workflowCard);
|
||||
await n8n.workflows.cards.getCardAction('duplicate').click();
|
||||
const duplicatePage = await n8n.start.fromNewPage(async () => {
|
||||
await n8n.modal.clickButton('Duplicate');
|
||||
});
|
||||
|
||||
const duplicatedName = `${workflowName} copy`;
|
||||
await duplicatePage.navigate.toProject(projectId);
|
||||
await expect(duplicatePage.workflows.cards.getWorkflow(duplicatedName)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should duplicate workflow within a folder from personal projects', async ({ n8n }) => {
|
||||
const projectId = await n8n.start.fromNewProject();
|
||||
const folder = await n8n.api.projects.createFolder(projectId);
|
||||
const { name: workflowName } = await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: folder.id,
|
||||
});
|
||||
await n8n.navigate.toFolder(folder.id, projectId);
|
||||
|
||||
const workflowCard = n8n.workflows.cards.getWorkflow(workflowName);
|
||||
await n8n.workflows.cards.openCardActions(workflowCard);
|
||||
await n8n.workflows.cards.getCardAction('duplicate').click();
|
||||
|
||||
const duplicatePage = await n8n.start.fromNewPage(async () => {
|
||||
await n8n.modal.clickButton('Duplicate');
|
||||
});
|
||||
|
||||
await duplicatePage.navigate.toFolder(folder.id);
|
||||
const duplicatedName = `${workflowName} copy`;
|
||||
await expect(duplicatePage.workflows.cards.getWorkflow(duplicatedName)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should duplicate workflow within a folder from workflow page', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const folder = await n8n.api.projects.createFolder(projectId);
|
||||
const { name: workflowName, id: workflowId } = await n8n.api.workflows.createInProject(
|
||||
projectId,
|
||||
{
|
||||
folder: folder.id,
|
||||
},
|
||||
);
|
||||
await n8n.navigate.toCanvas(workflowId);
|
||||
|
||||
await n8n.workflowSettingsModal.getWorkflowMenu().click();
|
||||
await n8n.workflowSettingsModal.getDuplicateMenuItem().click();
|
||||
const duplicatePage = await n8n.start.fromNewPage(async () => {
|
||||
await n8n.modal.clickButton('Duplicate');
|
||||
});
|
||||
|
||||
const duplicatedName = `${workflowName} copy`;
|
||||
await duplicatePage.navigate.toFolder(folder.id, projectId);
|
||||
await expect(duplicatePage.workflows.cards.getWorkflow(duplicatedName)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Drag and drop', () => {
|
||||
test('should drag and drop folders into folders', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject('Drag and Drop Test');
|
||||
await n8n.navigate.toProject(projectId);
|
||||
const targetFolder = await n8n.api.projects.createFolder(projectId, 'Drag me');
|
||||
const destinationFolder = await n8n.api.projects.createFolder(
|
||||
projectId,
|
||||
'Folder Destination',
|
||||
);
|
||||
|
||||
const sourceFolderCard = n8n.workflows.cards.getFolder(targetFolder.name);
|
||||
const destinationFolderCard = n8n.workflows.cards.getFolder(destinationFolder.name);
|
||||
|
||||
await n8n.interactions.precisionDragToTarget(sourceFolderCard, destinationFolderCard);
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent(
|
||||
`${targetFolder.name} has been moved to ${destinationFolder.name}`,
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(n8n.workflows.cards.getFolders()).toHaveCount(1);
|
||||
|
||||
await n8n.workflows.cards.openFolder(destinationFolder.name);
|
||||
await expect(n8n.workflows.cards.getFolder(targetFolder.name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should drag and drop folders into project root breadcrumb', async ({ n8n }) => {
|
||||
const project = await n8n.api.projects.createProject('Drag to root test');
|
||||
await n8n.navigate.toProject(project.id);
|
||||
const parentFolder = await n8n.api.projects.createFolder(project.id, 'Parent Folder');
|
||||
const targetFolder = await n8n.api.projects.createFolder(
|
||||
project.id,
|
||||
'To Project root',
|
||||
parentFolder.id,
|
||||
);
|
||||
|
||||
await n8n.navigate.toFolder(parentFolder.id, project.id);
|
||||
|
||||
const sourceFolderCard = n8n.workflows.cards.getFolder(targetFolder.name);
|
||||
const projectBreadcrumb = n8n.breadcrumbs.getHomeProjectBreadcrumb();
|
||||
|
||||
await n8n.interactions.precisionDragToTarget(sourceFolderCard, projectBreadcrumb);
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent(
|
||||
`${targetFolder.name} has been moved to ${project.name}`,
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(n8n.workflows.cards.getFolders()).toHaveCount(0);
|
||||
|
||||
await n8n.navigate.toProject(project.id);
|
||||
await expect(n8n.workflows.cards.getFolder(targetFolder.name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should drag and drop workflows into folders', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject('Drag and Drop WF Test');
|
||||
const { name: workflowName } = await n8n.api.workflows.createInProject(projectId, {});
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
await n8n.navigate.toProject(projectId);
|
||||
|
||||
const sourceWorkflowCard = n8n.workflows.cards.getWorkflow(workflowName);
|
||||
const destinationFolderCard = n8n.workflows.cards.getFolder(destinationFolder.name);
|
||||
|
||||
await n8n.interactions.precisionDragToTarget(sourceWorkflowCard, destinationFolderCard);
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent(
|
||||
`${workflowName} has been moved to ${destinationFolder.name}`,
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(n8n.workflows.cards.getWorkflows()).toHaveCount(0);
|
||||
|
||||
await n8n.workflows.cards.openFolder(destinationFolder.name);
|
||||
await expect(n8n.workflows.cards.getWorkflow(workflowName)).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -125,13 +125,15 @@ test.describe('Folders - Basic Operations', () => {
|
||||
});
|
||||
|
||||
test('should create workflow in a folder', async ({ n8n }) => {
|
||||
const projectId = await n8n.start.fromNewProject();
|
||||
const { name: projectName, id: projectId } = await n8n.api.projects.createProject();
|
||||
const folder = await n8n.api.projects.createFolder(projectId);
|
||||
const folderName = folder.name;
|
||||
await n8n.workflows.cards.openFolder(folderName);
|
||||
await n8n.navigate.toFolder(folder.id, projectId);
|
||||
await n8n.workflows.addResource.workflow();
|
||||
|
||||
await expect(n8n.breadcrumbs.getBreadcrumb(folderName)).toBeVisible();
|
||||
await n8n.canvas.saveWorkflow();
|
||||
const successMessage = `Workflow successfully created in "${projectName}", within "${folder.name}"`;
|
||||
await expect(n8n.notifications.getNotificationByTitleOrContent(successMessage)).toBeVisible();
|
||||
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 }) => {
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Folders - Operations', () => {
|
||||
test.describe('Rename and delete folders', () => {
|
||||
test('should rename folder from breadcrumb dropdown', async ({ n8n }) => {
|
||||
await n8n.start.fromNewProject();
|
||||
const folderName = await n8n.workflows.addFolder();
|
||||
const folderCard = n8n.workflows.cards.getFolder(folderName);
|
||||
await n8n.workflows.cards.openCardActions(folderCard);
|
||||
await n8n.workflows.cards.getCardAction('open').click();
|
||||
await n8n.breadcrumbs.renameCurrentBreadcrumb('Renamed');
|
||||
await n8n.breadcrumbs.getHomeProjectBreadcrumb().click();
|
||||
await expect(n8n.workflows.cards.getFolder('Renamed')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should rename folder from card dropdown', async ({ n8n }) => {
|
||||
await n8n.start.fromNewProject();
|
||||
const folderName = await n8n.workflows.addFolder();
|
||||
const folderCard = n8n.workflows.cards.getFolder(folderName);
|
||||
await n8n.workflows.cards.openCardActions(folderCard);
|
||||
await n8n.workflows.cards.getCardAction('rename').click();
|
||||
await n8n.workflows.fillFolderModal('Renamed', 'Rename');
|
||||
await expect(n8n.workflows.cards.getFolder('Renamed')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete empty folder from card dropdown', async ({ n8n }) => {
|
||||
await n8n.start.fromNewProject();
|
||||
const folderName = await n8n.workflows.addFolder();
|
||||
await n8n.workflows.cards.deleteFolder(folderName);
|
||||
await expect(n8n.workflows.cards.getFolder(folderName)).toBeHidden();
|
||||
});
|
||||
|
||||
test('should delete empty folder from breadcrumb dropdown', async ({ n8n }) => {
|
||||
await n8n.start.fromNewProject();
|
||||
const folderName = await n8n.workflows.addFolder();
|
||||
await n8n.workflows.cards.openFolder(folderName);
|
||||
await n8n.breadcrumbs.getFolderBreadcrumbsActionToggle().click();
|
||||
await n8n.breadcrumbs.getActionToggleDropdown('delete').click();
|
||||
await expect(n8n.workflows.cards.getFolder(folderName)).toBeHidden();
|
||||
});
|
||||
|
||||
test('should warn before deleting non-empty folder from breadcrumb dropdown', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const folder = await n8n.api.projects.createFolder(projectId);
|
||||
await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: folder.id,
|
||||
});
|
||||
await n8n.navigate.toFolder(folder.id, projectId);
|
||||
await n8n.breadcrumbs.getFolderBreadcrumbsActionToggle().click();
|
||||
await n8n.breadcrumbs.getActionToggleDropdown('delete').click();
|
||||
await expect(n8n.workflows.deleteFolderModal()).toBeVisible();
|
||||
await expect(n8n.workflows.deleteModalConfirmButton()).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should warn before deleting non-empty folder from card dropdown', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const folder = await n8n.api.projects.createFolder(projectId);
|
||||
await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: folder.id,
|
||||
});
|
||||
await n8n.navigate.toProject(projectId);
|
||||
const folderCard = n8n.workflows.cards.getFolder(folder.name);
|
||||
await n8n.workflows.cards.openCardActions(folderCard);
|
||||
await n8n.workflows.cards.getCardAction('delete').click();
|
||||
await expect(n8n.workflows.deleteFolderModal()).toBeVisible();
|
||||
await expect(n8n.workflows.deleteModalConfirmButton()).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should transfer contents when deleting non-empty folder - from card dropdown', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const folderToDelete = await n8n.api.projects.createFolder(projectId);
|
||||
await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: folderToDelete.id,
|
||||
});
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.navigate.toProject(projectId);
|
||||
|
||||
const folderCard = n8n.workflows.cards.getFolder(folderToDelete.name);
|
||||
await n8n.workflows.cards.openCardActions(folderCard);
|
||||
await n8n.workflows.cards.getCardAction('delete').click();
|
||||
await n8n.workflows.deleteModalTransferRadioButton().click();
|
||||
await n8n.workflows.transferFolderDropdown().click();
|
||||
await n8n.workflows.transferFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.deleteModalConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Folder deleted'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getWorkflows()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Move folders and workflows', () => {
|
||||
test('should move empty folder to another folder - from folder card action', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const sourceFolder = await n8n.api.projects.createFolder(projectId);
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.navigate.toProject(projectId);
|
||||
|
||||
const sourceFolderCard = n8n.workflows.cards.getFolder(sourceFolder.name);
|
||||
await n8n.workflows.cards.openCardActions(sourceFolderCard);
|
||||
await n8n.workflows.cards.getCardAction('move').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Successfully moved folder'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getFolder(sourceFolder.name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move folder with contents to another folder - from folder card action', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const sourceFolder = await n8n.api.projects.createFolder(projectId);
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: sourceFolder.id,
|
||||
});
|
||||
|
||||
await n8n.navigate.toProject(projectId);
|
||||
|
||||
const sourceFolderCard = n8n.workflows.cards.getFolder(sourceFolder.name);
|
||||
await n8n.workflows.cards.openCardActions(sourceFolderCard);
|
||||
await n8n.workflows.cards.getCardAction('move').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Successfully moved folder'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getFolder(sourceFolder.name)).toBeVisible();
|
||||
await n8n.workflows.cards.openFolder(sourceFolder.name);
|
||||
await expect(n8n.workflows.cards.getWorkflows()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move empty folder to another folder - from list breadcrumbs', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const sourceFolder = await n8n.api.projects.createFolder(projectId);
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.navigate.toFolder(sourceFolder.id, projectId);
|
||||
await n8n.breadcrumbs.getFolderBreadcrumbsActionToggle().click();
|
||||
await n8n.breadcrumbs.getActionToggleDropdown('move').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getFolder(sourceFolder.name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move folder with contents to another folder - from list dropdown', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const sourceFolder = await n8n.api.projects.createFolder(projectId);
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: sourceFolder.id,
|
||||
});
|
||||
|
||||
await n8n.navigate.toFolder(sourceFolder.id, projectId);
|
||||
await n8n.breadcrumbs.getFolderBreadcrumbsActionToggle().click();
|
||||
await n8n.breadcrumbs.getActionToggleDropdown('move').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getFolder(sourceFolder.name)).toBeVisible();
|
||||
await n8n.workflows.cards.openFolder(sourceFolder.name);
|
||||
await expect(n8n.workflows.cards.getWorkflows()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move folder to project root - from folder card action', async ({ n8n }) => {
|
||||
const project = await n8n.api.projects.createProject();
|
||||
const parentFolder = await n8n.api.projects.createFolder(project.id);
|
||||
const childFolderName = 'Child Folder';
|
||||
const childFolder = await n8n.api.projects.createFolder(
|
||||
project.id,
|
||||
childFolderName,
|
||||
parentFolder.id,
|
||||
);
|
||||
|
||||
await n8n.navigate.toFolder(parentFolder.id, project.id);
|
||||
|
||||
const childFolderCard = n8n.workflows.cards.getFolder(childFolder.name);
|
||||
await n8n.workflows.cards.openCardActions(childFolderCard);
|
||||
await n8n.workflows.cards.getCardAction('move').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
|
||||
const rootOption = 'No folder (project root)';
|
||||
await n8n.workflows.moveFolderOption(rootOption).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Successfully moved folder'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toProject(project.id);
|
||||
await expect(n8n.workflows.cards.getFolder(childFolder.name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move workflow from project root to folder', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
await n8n.api.workflows.createInProject(projectId);
|
||||
|
||||
await n8n.navigate.toProject(projectId);
|
||||
|
||||
const workflowCard = n8n.workflows.cards.getWorkflows().first();
|
||||
await n8n.workflows.cards.openCardActions(workflowCard);
|
||||
await n8n.workflows.cards.getCardAction('moveToFolder').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Successfully moved workflow'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getWorkflows()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should move workflow to another folder', async ({ n8n }) => {
|
||||
const { id: projectId } = await n8n.api.projects.createProject();
|
||||
const sourceFolder = await n8n.api.projects.createFolder(projectId);
|
||||
const destinationFolder = await n8n.api.projects.createFolder(projectId);
|
||||
|
||||
const { name: workflowName } = await n8n.api.workflows.createInProject(projectId, {
|
||||
folder: sourceFolder.id,
|
||||
});
|
||||
|
||||
await n8n.navigate.toFolder(sourceFolder.id, projectId);
|
||||
|
||||
const workflowCard = n8n.workflows.cards.getWorkflow(workflowName);
|
||||
await n8n.workflows.cards.openCardActions(workflowCard);
|
||||
await n8n.workflows.cards.getCardAction('moveToFolder').click();
|
||||
|
||||
await expect(n8n.workflows.moveFolderModal()).toBeVisible();
|
||||
await n8n.workflows.moveFolderDropdown().click();
|
||||
await n8n.workflows.moveFolderOption(destinationFolder.name).click();
|
||||
await n8n.workflows.moveFolderConfirmButton().click();
|
||||
|
||||
await expect(
|
||||
n8n.notifications.getNotificationByTitleOrContent('Successfully moved workflow'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(destinationFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getWorkflow(workflowName)).toBeVisible();
|
||||
|
||||
await n8n.navigate.toFolder(sourceFolder.id, projectId);
|
||||
await expect(n8n.workflows.cards.getWorkflow(workflowName)).toBeHidden();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -77,7 +77,7 @@ test.describe('04 - Credentials', () => {
|
||||
const uniqueCredentialName = `credential-${nanoid()}`;
|
||||
const projectId = await n8n.start.fromNewProjectBlankCanvas();
|
||||
|
||||
await n8n.api.credentialApi.createCredential({
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: uniqueCredentialName,
|
||||
type: 'notionApi',
|
||||
data: {
|
||||
|
||||
@@ -15,25 +15,25 @@ test.describe('Credential API Operations', () => {
|
||||
};
|
||||
|
||||
const { credentialId, createdCredential } =
|
||||
await api.credentialApi.createCredentialFromDefinition(credentialData);
|
||||
await api.credentials.createCredentialFromDefinition(credentialData);
|
||||
|
||||
expect(credentialId).toBeTruthy();
|
||||
expect(createdCredential.type).toBe('httpBasicAuth');
|
||||
expect(createdCredential.name).toContain('Test HTTP Basic Auth (Test');
|
||||
|
||||
const retrievedCredential = await api.credentialApi.getCredential(credentialId);
|
||||
const retrievedCredential = await api.credentials.getCredential(credentialId);
|
||||
expect(retrievedCredential.id).toBe(credentialId);
|
||||
expect(retrievedCredential.type).toBe('httpBasicAuth');
|
||||
expect(retrievedCredential.name).toBe(createdCredential.name);
|
||||
|
||||
const credentialWithData = await api.credentialApi.getCredential(credentialId, {
|
||||
const credentialWithData = await api.credentials.getCredential(credentialId, {
|
||||
includeData: true,
|
||||
});
|
||||
expect(credentialWithData.data).toBeDefined();
|
||||
expect(credentialWithData.data?.user).toBe('test_user');
|
||||
|
||||
const updatedName = 'Updated HTTP Basic Auth';
|
||||
const updatedCredential = await api.credentialApi.updateCredential(credentialId, {
|
||||
const updatedCredential = await api.credentials.updateCredential(credentialId, {
|
||||
name: updatedName,
|
||||
data: {
|
||||
user: 'updated_user',
|
||||
@@ -42,47 +42,47 @@ test.describe('Credential API Operations', () => {
|
||||
});
|
||||
expect(updatedCredential.name).toBe(updatedName);
|
||||
|
||||
const verifyUpdated = await api.credentialApi.getCredential(credentialId, {
|
||||
const verifyUpdated = await api.credentials.getCredential(credentialId, {
|
||||
includeData: true,
|
||||
});
|
||||
expect(verifyUpdated.name).toBe(updatedName);
|
||||
expect(verifyUpdated.data?.user).toBe('updated_user');
|
||||
|
||||
const deleteResult = await api.credentialApi.deleteCredential(credentialId);
|
||||
const deleteResult = await api.credentials.deleteCredential(credentialId);
|
||||
expect(deleteResult).toBe(true);
|
||||
|
||||
await expect(api.credentialApi.getCredential(credentialId)).rejects.toThrow();
|
||||
await expect(api.credentials.getCredential(credentialId)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Credential Listing', () => {
|
||||
test('should list credentials with different query options', async ({ api }) => {
|
||||
const credential1 = await api.credentialApi.createCredentialFromDefinition({
|
||||
const credential1 = await api.credentials.createCredentialFromDefinition({
|
||||
name: 'First Test Credential',
|
||||
type: 'httpBasicAuth',
|
||||
data: { user: 'user1', password: 'pass1' },
|
||||
});
|
||||
|
||||
const credential2 = await api.credentialApi.createCredentialFromDefinition({
|
||||
const credential2 = await api.credentials.createCredentialFromDefinition({
|
||||
name: 'Second Test Credential',
|
||||
type: 'httpHeaderAuth',
|
||||
data: { name: 'Authorization', value: 'Bearer token' },
|
||||
});
|
||||
|
||||
const allCredentials = await api.credentialApi.getCredentials();
|
||||
const allCredentials = await api.credentials.getCredentials();
|
||||
expect(allCredentials.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
const createdIds = [credential1.credentialId, credential2.credentialId];
|
||||
const foundCredentials = allCredentials.filter((c) => createdIds.includes(c.id));
|
||||
expect(foundCredentials).toHaveLength(2);
|
||||
|
||||
const credentialsWithScopes = await api.credentialApi.getCredentials({
|
||||
const credentialsWithScopes = await api.credentials.getCredentials({
|
||||
includeScopes: true,
|
||||
});
|
||||
expect(credentialsWithScopes[0].scopes).toBeDefined();
|
||||
expect(Array.isArray(credentialsWithScopes[0].scopes)).toBe(true);
|
||||
|
||||
const credentialsWithData = await api.credentialApi.getCredentials({
|
||||
const credentialsWithData = await api.credentials.getCredentials({
|
||||
includeData: true,
|
||||
});
|
||||
const foundWithData = credentialsWithData.filter((c) => createdIds.includes(c.id));
|
||||
@@ -98,14 +98,14 @@ test.describe('Credential API Operations', () => {
|
||||
|
||||
const project = await api.projects.createProject('Test Project for Credentials');
|
||||
|
||||
const credential = await api.credentialApi.createCredentialFromDefinition({
|
||||
const credential = await api.credentials.createCredentialFromDefinition({
|
||||
name: 'Project Credential',
|
||||
type: 'httpBasicAuth',
|
||||
data: { user: 'user', password: 'pass' },
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
const projectCredentials = await api.credentialApi.getCredentialsForWorkflow({
|
||||
const projectCredentials = await api.credentials.getCredentialsForWorkflow({
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
@@ -124,22 +124,22 @@ test.describe('Credential API Operations', () => {
|
||||
const sourceProject = await api.projects.createProject('Source Project');
|
||||
const destinationProject = await api.projects.createProject('Destination Project');
|
||||
|
||||
const credential = await api.credentialApi.createCredentialFromDefinition({
|
||||
const credential = await api.credentials.createCredentialFromDefinition({
|
||||
name: 'Transfer Test Credential',
|
||||
type: 'httpBasicAuth',
|
||||
data: { user: 'user', password: 'pass' },
|
||||
projectId: sourceProject.id,
|
||||
});
|
||||
|
||||
const sourceCredentials = await api.credentialApi.getCredentialsForWorkflow({
|
||||
const sourceCredentials = await api.credentials.getCredentialsForWorkflow({
|
||||
projectId: sourceProject.id,
|
||||
});
|
||||
const foundInSource = sourceCredentials.find((c) => c.id === credential.credentialId);
|
||||
expect(foundInSource).toBeDefined();
|
||||
|
||||
await api.credentialApi.transferCredential(credential.credentialId, destinationProject.id);
|
||||
await api.credentials.transferCredential(credential.credentialId, destinationProject.id);
|
||||
|
||||
const destinationCredentials = await api.credentialApi.getCredentialsForWorkflow({
|
||||
const destinationCredentials = await api.credentials.getCredentialsForWorkflow({
|
||||
projectId: destinationProject.id,
|
||||
});
|
||||
const foundInDestination = destinationCredentials.find(
|
||||
@@ -147,7 +147,7 @@ test.describe('Credential API Operations', () => {
|
||||
);
|
||||
expect(foundInDestination).toBeDefined();
|
||||
|
||||
const sourceCredentialsAfter = await api.credentialApi.getCredentialsForWorkflow({
|
||||
const sourceCredentialsAfter = await api.credentials.getCredentialsForWorkflow({
|
||||
projectId: sourceProject.id,
|
||||
});
|
||||
const stillInSource = sourceCredentialsAfter.find((c) => c.id === credential.credentialId);
|
||||
@@ -166,27 +166,27 @@ test.describe('Credential API Operations', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { credentialId } = await api.credentialApi.createCredentialFromDefinition(originalData);
|
||||
const { credentialId } = await api.credentials.createCredentialFromDefinition(originalData);
|
||||
|
||||
const afterCreate = await api.credentialApi.getCredential(credentialId, {
|
||||
const afterCreate = await api.credentials.getCredential(credentialId, {
|
||||
includeData: true,
|
||||
});
|
||||
expect(afterCreate.data?.user).toBe('persistent_user');
|
||||
|
||||
await api.credentialApi.updateCredential(credentialId, {
|
||||
await api.credentials.updateCredential(credentialId, {
|
||||
data: {
|
||||
user: 'updated_persistent_user',
|
||||
password: 'updated_persistent_password',
|
||||
},
|
||||
});
|
||||
|
||||
const afterUpdate = await api.credentialApi.getCredential(credentialId, {
|
||||
const afterUpdate = await api.credentials.getCredential(credentialId, {
|
||||
includeData: true,
|
||||
});
|
||||
expect(afterUpdate.data?.user).toBe('updated_persistent_user');
|
||||
expect(afterUpdate.data?.password).toBeDefined();
|
||||
|
||||
const allCredentials = await api.credentialApi.getCredentials();
|
||||
const allCredentials = await api.credentials.getCredentials();
|
||||
const foundCredential = allCredentials.find((c) => c.id === credentialId);
|
||||
expect(foundCredential).toBeDefined();
|
||||
expect(foundCredential!.type).toBe('httpBasicAuth');
|
||||
|
||||
@@ -11,7 +11,7 @@ test.describe('Evaluations @capability:proxy', () => {
|
||||
test('should load evaluations workflow and execute twice', async ({ n8n, proxyServer }) => {
|
||||
await proxyServer.loadExpectations('evaluations');
|
||||
|
||||
await n8n.api.credentialApi.createCredentialFromDefinition({
|
||||
await n8n.api.credentials.createCredentialFromDefinition({
|
||||
name: 'Test Google Sheets',
|
||||
type: 'googleApi',
|
||||
data: {
|
||||
|
||||
@@ -4,7 +4,7 @@ test.describe('External Webhook Triggering', () => {
|
||||
test('should create workflow via API, activate it, trigger webhook externally, and verify execution', async ({
|
||||
api,
|
||||
}) => {
|
||||
const { webhookPath, workflowId } = await api.workflowApi.importWorkflow(
|
||||
const { webhookPath, workflowId } = await api.workflows.importWorkflow(
|
||||
'simple-webhook-test.json',
|
||||
);
|
||||
|
||||
@@ -16,15 +16,15 @@ test.describe('External Webhook Triggering', () => {
|
||||
|
||||
expect(webhookResponse.ok()).toBe(true);
|
||||
|
||||
const execution = await api.workflowApi.waitForExecution(workflowId, 5000);
|
||||
const execution = await api.workflows.waitForExecution(workflowId, 5000);
|
||||
expect(execution.status).toBe('success');
|
||||
|
||||
const executionDetails = await api.workflowApi.getExecution(execution.id);
|
||||
const executionDetails = await api.workflows.getExecution(execution.id);
|
||||
expect(executionDetails.data).toContain('Hello from Playwright test');
|
||||
});
|
||||
|
||||
test('should surface workflow configuration errors to the caller', async ({ api }) => {
|
||||
const { webhookPath } = await api.workflowApi.importWorkflow(
|
||||
const { webhookPath } = await api.workflows.importWorkflow(
|
||||
'webhook-misconfiguration-test.json',
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Webhook Origin Isolation', () => {
|
||||
test.beforeAll(async ({ api }) => {
|
||||
await api.workflowApi.importWorkflow('webhook-origin-isolation.json', { makeUnique: false });
|
||||
await api.workflows.importWorkflow('webhook-origin-isolation.json', { makeUnique: false });
|
||||
});
|
||||
|
||||
const webhookPaths = [
|
||||
|
||||
Reference in New Issue
Block a user